
If a virtual machine is cloned, its snapshots should also be cloned so we can have a real clone of the original VM. Update the function "VMModel.clone" in order to clone the snapshots when cloning a VM. Signed-off-by: Crístian Viana <vianac@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 2 ++ src/kimchi/model/vms.py | 29 ++++++++++++++++++++++++++++- tests/test_model.py | 11 +++++++++++ tests/test_rest.py | 19 +++++++++++++++++-- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index ed57cb7..3a858ca 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -218,6 +218,8 @@ class MockModel(object): new_path = u'%s-%d%s' % (basename, i, ext) new_vm.storagedevices[storage_name].path = new_path + new_vm.snapshots = copy.deepcopy(vm.snapshots) + self._mock_vms[new_name] = new_vm cb('OK', True) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index 211e438..5b92e8e 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -371,10 +371,15 @@ class VMModel(object): # create new guest cb('defining new VM') try: - vir_conn.defineXML(xml) + vir_new_dom = vir_conn.defineXML(xml) except libvirt.libvirtError, e: raise OperationFailed('KCHVM0035E', {'name': name, 'err': e.message}) + rollback.prependDefer(vir_new_dom.undefine) + + # copy snapshots + cb('copying VM snapshots') + self._clone_copy_snapshots(name, new_name) rollback.commitAll() @@ -538,6 +543,28 @@ class VMModel(object): # remove the new object store entry should an error occur later rollback.prependDefer(_rollback_objstore) + def _clone_copy_snapshots(self, vm_name, new_vm_name): + dom = self.get_vm(new_vm_name, self.conn) + flags = libvirt.VIR_DOMAIN_XML_SECURE + + # libvirt's Test driver does not support the function + # "virDomainListAllSnapshots", so "VMSnapshots.get_list" will raise + # "OperationFailed" in that case. + try: + snapshot_names = self.vmsnapshots.get_list(vm_name) + except OperationFailed, e: + kimchi_log.error('cannot list snapshots: %s; ' + 'skipping snapshot cloning...' % e.message) + else: + for s in snapshot_names: + vir_snap = self.vmsnapshot.get_vmsnapshot(vm_name, s) + try: + snap_xml = vir_snap.getXMLDesc(flags).decode('utf-8') + dom.snapshotCreateXML(snap_xml) + except libvirt.libvirtError, e: + raise OperationFailed('KCHVM0035E', {'name': vm_name, + 'err': e.message}) + def _build_access_elem(self, users, groups): access = E.access() for user in users: diff --git a/tests/test_model.py b/tests/test_model.py index 86ab24a..d0c0aed 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -156,6 +156,17 @@ class ModelTests(unittest.TestCase): self.assertEquals(sorted([params['name'], snap_name], key=unicode.lower), snaps) + # Clone the VM and check whether the snapshots have been cloned + # as well + task = inst.vm_clone(u'kimchi-vm') + clone_name = task['target_uri'].split('/')[-1] + rollback.prependDefer(inst.vm_delete, clone_name) + inst.task_wait(task['id']) + task = inst.task_lookup(task['id']) + self.assertEquals('finished', task['status']) + clone_snaps = inst.vmsnapshots_get_list(clone_name) + self.assertEquals(snaps, clone_snaps) + snap = inst.vmsnapshot_lookup(u'kimchi-vm', snap_name) current_snap = inst.currentvmsnapshot_lookup(u'kimchi-vm') self.assertEquals(snap, current_snap) diff --git a/tests/test_rest.py b/tests/test_rest.py index b1bfbcc..a64c88d 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -434,8 +434,8 @@ class RestTests(unittest.TestCase): resp = self.request('/vms/test-vm/snapshots', '{}', 'GET') self.assertEquals(200, resp.status) - snaps = json.loads(resp.read()) - self.assertEquals(2, len(snaps)) + orig_snaps = json.loads(resp.read()) + self.assertEquals(2, len(orig_snaps)) # Look up current snapshot (the one created above) resp = self.request('/vms/test-vm/snapshots/current', '{}', 'GET') @@ -457,6 +457,21 @@ class RestTests(unittest.TestCase): current_snap = json.loads(resp.read()) self.assertEquals(snap, current_snap) + # Clone a VM with snapshot + 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']) + resp = self.request('/tasks/%s' % task['id'], '{}', 'GET') + self.assertEquals(200, resp.status) + task = json.loads(resp.read()) + self.assertEquals('finished', task['status']) + vm_name = task['target_uri'].split('/')[-1] + resp = self.request('/vms/%s/snapshots' % vm_name, '{}', 'GET') + self.assertEquals(200, resp.status) + clone_snaps = json.loads(resp.read()) + self.assertEquals(orig_snaps, clone_snaps) + # Delete a snapshot resp = self.request('/vms/test-vm/snapshots/foobar', '{}', 'DELETE') self.assertEquals(404, resp.status) -- 1.9.3