[Kimchi-devel] [PATCH 8/8] Add tests and mockmodel for the cloning feature

Aline Manera alinefm at linux.vnet.ibm.com
Mon Nov 3 16:50:24 UTC 2014


Reviewed-by: Aline Manera <alinefm at linux.vnet.ibm.com>

On 11/02/2014 11:05 PM, Crístian Viana wrote:
> The function "vm_clone" is now implemented on the mockmodel with a
> similar implementation as the real model; the biggest difference is that
> it doesn't deal with the different storage pool details.
> Also, new tests were added.
>
> Signed-off-by: Crístian Viana <vianac at linux.vnet.ibm.com>
> ---
>   src/kimchi/mockmodel.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++
>   tests/test_model.py     | 38 ++++++++++++++++++++++++++++++++
>   tests/test_rest.py      | 31 ++++++++++++++++++++++++++
>   3 files changed, 127 insertions(+)
>
> diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
> index baee0b6..2265298 100644
> --- a/src/kimchi/mockmodel.py
> +++ b/src/kimchi/mockmodel.py
> @@ -28,6 +28,7 @@ import os
>   import shutil
>   import psutil
>   import random
> +import re
>   import string
>   import time
>   import uuid
> @@ -181,6 +182,63 @@ class MockModel(object):
>       def vm_connect(self, name):
>           pass
>   
> +    def _clone_get_next_name(self, basename):
> +        re_group_num = 'num'
> +
> +        max_num = 0
> +        re_cloned_vm = re.compile(u'%s-clone-(?P<%s>\d+)' %
> +                                  (basename, re_group_num))
> +
> +        vm_names = self.vms_get_list()
> +
> +        for v in vm_names:
> +            match = re_cloned_vm.match(v)
> +            if match is not None:
> +                max_num = max(max_num, int(match.group(re_group_num)))
> +
> +        return u'%s-clone-%d' % (basename, max_num + 1)
> +
> +    def vm_clone(self, name):
> +        vm = self._mock_vms[name]
> +        if vm.info['state'] != u'shutoff':
> +            raise InvalidParameter('KCHVM0033E', {'name': name})
> +
> +        new_name = self._clone_get_next_name(name)
> +
> +        taskid = self.add_task(u'/vms/%s' % new_name, self._do_clone,
> +                               {'name': name, 'new_name': new_name})
> +        return self.task_lookup(taskid)
> +
> +    def _do_clone(self, cb, params):
> +        name = params['name']
> +        new_name = params['new_name']
> +
> +        vm = self._mock_vms[name]
> +        new_vm = copy.deepcopy(vm)
> +
> +        new_uuid = unicode(uuid.uuid4())
> +
> +        new_vm.name = new_name
> +        new_vm.info['name'] = new_name
> +        new_vm.uuid = new_uuid
> +        new_vm.info['uuid'] = new_uuid
> +
> +        for mac, iface in new_vm.ifaces.items():
> +            new_mac = MockVMIface.get_mac()
> +            iface.info['mac'] = new_mac
> +            new_vm.ifaces[new_mac] = iface
> +
> +        storage_names = new_vm.storagedevices.keys()
> +        for i, storage_name in enumerate(storage_names):
> +            storage = new_vm.storagedevices[storage_name]
> +            basename, ext = os.path.splitext(storage.info['path'])
> +            new_path = u'%s-%d%s' % (basename, i, ext)
> +            new_vm.storagedevices[storage_name].path = new_path
> +
> +        self._mock_vms[new_name] = new_vm
> +
> +        cb('OK', True)
> +
>       def vms_create(self, params):
>           t_name = template_name_from_uri(params['template'])
>           name = get_vm_name(params.get('name'), t_name, self._mock_vms.keys())
> diff --git a/tests/test_model.py b/tests/test_model.py
> index b165731..b05d71d 100644
> --- a/tests/test_model.py
> +++ b/tests/test_model.py
> @@ -1256,6 +1256,44 @@ class ModelTests(unittest.TestCase):
>   
>               self.assertEquals(vms, sorted(vms, key=unicode.lower))
>   
> +    def test_vm_clone(self):
> +        inst = model.Model('test:///default', objstore_loc=self.tmp_store)
> +
> +        all_vm_names = inst.vms_get_list()
> +        name = all_vm_names[0]
> +
> +        original_vm = inst.vm_lookup(name)
> +
> +        # the VM 'test' should be running by now, so we can't clone it yet
> +        self.assertRaises(InvalidParameter, inst.vm_clone, name)
> +
> +        with RollbackContext() as rollback:
> +            inst.vm_poweroff(name)
> +            rollback.prependDefer(inst.vm_start, name)
> +
> +            task = inst.vm_clone(name)
> +            clone_name = task['target_uri'].split('/')[-1]
> +            rollback.prependDefer(inst.vm_delete, clone_name)
> +            inst.task_wait(task['id'])
> +
> +            # update the original VM info because its state has changed
> +            original_vm = inst.vm_lookup(name)
> +            clone_vm = inst.vm_lookup(clone_name)
> +
> +            self.assertNotEqual(original_vm['name'], clone_vm['name'])
> +            self.assertTrue(re.match(u'%s-clone-\d+' % original_vm['name'],
> +                                     clone_vm['name']))
> +            del original_vm['name']
> +            del clone_vm['name']
> +
> +            self.assertNotEqual(original_vm['uuid'], clone_vm['uuid'])
> +            del original_vm['uuid']
> +            del clone_vm['uuid']
> +
> +            # compare all VM settings except the ones already compared
> +            # (and removed) above (i.e. 'name' and 'uuid')
> +            self.assertEquals(original_vm, clone_vm)
> +
>       def test_use_test_host(self):
>           inst = model.Model('test:///default',
>                              objstore_loc=self.tmp_store)
> diff --git a/tests/test_rest.py b/tests/test_rest.py
> index 66072bc..09cf52b 100644
> --- a/tests/test_rest.py
> +++ b/tests/test_rest.py
> @@ -22,6 +22,7 @@ import base64
>   import json
>   import os
>   import random
> +import re
>   import requests
>   import shutil
>   import time
> @@ -341,6 +342,10 @@ class RestTests(unittest.TestCase):
>           self.assertEquals(200, resp.status)
>           self.assertTrue(resp.getheader('Content-type').startswith('image'))
>   
> +        # Clone a running VM
> +        resp = self.request('/vms/test-vm/clone', '{}', 'POST')
> +        self.assertEquals(400, resp.status)
> +
>           # Force poweroff the VM
>           resp = self.request('/vms/test-vm/poweroff', '{}', 'POST')
>           vm = json.loads(self.request('/vms/test-vm').read())
> @@ -351,6 +356,32 @@ class RestTests(unittest.TestCase):
>           resp = self.request('/vms', req, 'POST')
>           self.assertEquals(400, resp.status)
>   
> +        # Clone a VM
> +        resp = self.request('/vms/test-vm/clone', '{}', 'POST')
> +        self.assertEquals(202, resp.status)
> +        task = json.loads(resp.read())
> +        wait_task(self._task_lookup, task['id'])
> +        task = json.loads(self.request('/tasks/%s' % task['id'], '{}').read())
> +        self.assertEquals('finished', task['status'])
> +        clone_vm_name = task['target_uri'].split('/')[-1]
> +        self.assertTrue(re.match(u'test-vm-clone-\d+', clone_vm_name))
> +
> +        resp = self.request('/vms/test-vm', '{}')
> +        original_vm_info = json.loads(resp.read())
> +        resp = self.request('/vms/%s' % clone_vm_name, '{}')
> +        self.assertEquals(200, resp.status)
> +        clone_vm_info = json.loads(resp.read())
> +
> +        self.assertNotEqual(original_vm_info['name'], clone_vm_info['name'])
> +        del original_vm_info['name']
> +        del clone_vm_info['name']
> +
> +        self.assertNotEqual(original_vm_info['uuid'], clone_vm_info['uuid'])
> +        del original_vm_info['uuid']
> +        del clone_vm_info['uuid']
> +
> +        self.assertEquals(original_vm_info, clone_vm_info)
> +
>           # Delete the VM
>           resp = self.request('/vms/test-vm', '{}', 'DELETE')
>           self.assertEquals(204, resp.status)




More information about the Kimchi-devel mailing list