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