
Reviewed-by: Aline Manera <alinefm@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@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)