[Kimchi-devel] [PATCH 8/8] Add tests and mockmodel for the cloning feature

Crístian Viana vianac at linux.vnet.ibm.com
Mon Nov 3 01:05:29 UTC 2014


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 at 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)
-- 
1.9.3




More information about the Kimchi-devel mailing list