[Kimchi-devel] [PATCH 10/10] snapshot: Clone snapshots when cloning a VM

Crístian Viana vianac at linux.vnet.ibm.com
Wed Nov 12 13:08:48 UTC 2014


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 at linux.vnet.ibm.com>
---
 src/kimchi/mockmodel.py |  2 ++
 src/kimchi/model/vms.py | 35 ++++++++++++++++++++++++++++++++---
 tests/test_model.py     | 11 +++++++++++
 tests/test_rest.py      | 19 +++++++++++++++++--
 4 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index 0af6056..c956255 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 811739e..5b92e8e 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -268,8 +268,10 @@ class VMModel(object):
         self.storagepool = model.storagepools.StoragePoolModel(**kargs)
         self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs)
         self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs)
-        self.vmsnapshot = model.vmsnapshots.VMSnapshotModel(**kargs)
-        self.vmsnapshots = model.vmsnapshots.VMSnapshotsModel(**kargs)
+        cls = import_class('kimchi.model.vmsnapshots.VMSnapshotModel')
+        self.vmsnapshot = cls(**kargs)
+        cls = import_class('kimchi.model.vmsnapshots.VMSnapshotsModel')
+        self.vmsnapshots = cls(**kargs)
 
     def update(self, name, params):
         dom = self.get_vm(name, self.conn)
@@ -369,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()
 
@@ -536,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




More information about the Kimchi-devel mailing list