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(a)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