[Kimchi-devel] [PATCH] Issue #526: Support updating name for VMs with snapshots and reverting snapshot to old name

gouzongmei at ourfuture.cn gouzongmei at ourfuture.cn
Tue Jan 13 08:49:53 UTC 2015


From: Zongmei Gou <gouzongmei at ourfuture.cn>

---
 src/kimchi/control/base.py      | 14 ++++++++---
 src/kimchi/model/vms.py         | 53 ++++++++++++++++++++++++++++++++++++++++-
 src/kimchi/model/vmsnapshots.py |  7 +++++-
 tests/test_model.py             | 41 +++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 5 deletions(-)

diff --git a/src/kimchi/control/base.py b/src/kimchi/control/base.py
index 60db1df..2d89b87 100644
--- a/src/kimchi/control/base.py
+++ b/src/kimchi/control/base.py
@@ -58,10 +58,18 @@ class Resource(object):
         self.role_key = None
         self.admin_methods = []
 
-    def _redirect(self, ident, code=303):
-        if ident is not None and ident != self.ident:
+    def _redirect(self, action_result, code=303):
+        if isinstance(action_result, list):
+            uri_params = []
+            for arg in action_result:
+                if arg is None:
+                    arg = ''
+                uri_params.append(urllib2.quote(arg.encode('utf-8'), safe=""))
+            raise cherrypy.HTTPRedirect(self.uri_fmt % tuple(uri_params), code)
+        elif action_result is not None and action_result != self.ident:
             uri_params = list(self.model_args[:-1])
-            uri_params += [urllib2.quote(ident.encode('utf-8'), safe="")]
+            uri_params += [urllib2.quote(action_result.encode('utf-8'),
+                           safe="")]
             raise cherrypy.HTTPRedirect(self.uri_fmt % tuple(uri_params), code)
 
     def generate_action_handler(self, action_name, action_args=None):
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index 3aa1145..1800365 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -649,6 +649,39 @@ class VMModel(object):
                               libvirt.VIR_DOMAIN_AFFECT_LIVE)
         return xml
 
+    def _backup_snapshots(self, snap, all_info):
+        """ Append "snap" and the children of "snap" to the list "all_info".
+
+        The list *must* always contain the parent snapshots before their
+        children so the function "_redefine_snapshots" can work correctly.
+
+        Arguments:
+        snap -- a native domain snapshot.
+        all_info -- a list of dict keys:
+        "{'xml': <snap XML>, 'current': <is snap current?>'}"
+        """
+        all_info.append({'xml': snap.getXMLDesc(0),
+                         'current': snap.isCurrent(0)})
+
+        for child in snap.listAllChildren(0):
+            self._backup_snapshots(child, all_info)
+
+    def _redefine_snapshots(self, dom, all_info):
+        """ Restore the snapshots stored in "all_info" to the domain "dom".
+
+        Arguments:
+        dom -- the domain which will have its snapshots restored.
+        all_info -- a list of dict keys, as described in "_backup_snapshots",
+        containing the original snapshot information.
+        """
+        for info in all_info:
+            flags = libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE
+
+            if info['current']:
+                flags |= libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT
+
+            dom.snapshotCreateXML(info['xml'], flags)
+
     def _static_vm_update(self, dom, params):
         state = DOM_STATE_MAP[dom.info()[0]]
         old_xml = new_xml = dom.XMLDesc(0)
@@ -667,13 +700,27 @@ class VMModel(object):
         if 'graphics' in params:
             new_xml = self._update_graphics(dom, new_xml, params)
 
+        snapshots_info = []
+        vm_name = dom.name()
+
         conn = self.conn.get()
         try:
             if 'name' in params:
                 if state == 'running':
-                    msg_args = {'name': dom.name(), 'new_name': params['name']}
+                    msg_args = {'name': vm_name, 'new_name': params['name']}
                     raise InvalidParameter("KCHVM0003E", msg_args)
 
+                lflags = libvirt.VIR_DOMAIN_SNAPSHOT_LIST_ROOTS
+                dflags = (libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
+                          libvirt.VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)
+
+                for virt_snap in dom.listAllSnapshots(lflags):
+                    snapshots_info.append({'xml': virt_snap.getXMLDesc(0),
+                                           'current': virt_snap.isCurrent(0)})
+                    self._backup_snapshots(virt_snap, snapshots_info)
+
+                    virt_snap.delete(dflags)
+
                 # Undefine old vm, only if name is going to change
                 dom.undefine()
 
@@ -682,8 +729,12 @@ class VMModel(object):
             if currentMem is not None:
                 root.remove(currentMem)
             dom = conn.defineXML(ET.tostring(root, encoding="utf-8"))
+            if 'name' in params:
+                self._redefine_snapshots(dom, snapshots_info)
         except libvirt.libvirtError as e:
             dom = conn.defineXML(old_xml)
+            if 'name' in params:
+                self._redefine_snapshots(dom, snapshots_info)
             raise OperationFailed("KCHVM0008E", {'name': dom.name(),
                                                  'err': e.get_error_message()})
         return dom
diff --git a/src/kimchi/model/vmsnapshots.py b/src/kimchi/model/vmsnapshots.py
index 725770d..2c9345f 100644
--- a/src/kimchi/model/vmsnapshots.py
+++ b/src/kimchi/model/vmsnapshots.py
@@ -29,6 +29,7 @@ from kimchi.model.tasks import TaskModel
 from kimchi.model.vms import DOM_STATE_MAP, VMModel
 from kimchi.model.vmstorages import VMStorageModel, VMStoragesModel
 from kimchi.utils import add_task
+from kimchi.xmlutils.utils import xpath_get_text
 
 
 class VMSnapshotsModel(object):
@@ -155,7 +156,11 @@ class VMSnapshotModel(object):
         try:
             vir_dom = VMModel.get_vm(vm_name, self.conn)
             vir_snap = self.get_vmsnapshot(vm_name, name)
-            vir_dom.revertToSnapshot(vir_snap, 0)
+            result = vir_dom.revertToSnapshot(vir_snap, 0)
+            if result == 0:
+                xmlObj = vir_snap.getXMLDesc(0)
+                vm_new_name = xpath_get_text(xmlObj, 'domain/name')[0]
+                return [vm_new_name, name]
         except libvirt.libvirtError, e:
             raise OperationFailed('KCHSNAP0009E', {'name': name,
                                                    'vm': vm_name,
diff --git a/tests/test_model.py b/tests/test_model.py
index f1ad017..509c2c9 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -983,6 +983,9 @@ class ModelTests(unittest.TestCase):
             vms = inst.vms_get_list()
             self.assertTrue('kimchi-vm1' in vms)
 
+            # make sure "vm_update" works when the domain has a snapshot
+            inst.vmsnapshots_create(u'kimchi-vm1')
+
             # update vm graphics when vm is not running
             inst.vm_update(u'kimchi-vm1',
                            {"graphics": {"passwd": "123456"}})
@@ -1074,6 +1077,44 @@ class ModelTests(unittest.TestCase):
             self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['users'])
             self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups'])
 
+    def test_vm_snapshots(self):
+        config.set("authentication", "method", "pam")
+        inst = model.Model(None,
+                           objstore_loc=self.tmp_store)
+
+        orig_params = {'name': 'test', 'memory': '1024', 'cpus': '1',
+                       'cdrom': self.kimchi_iso}
+        inst.templates_create(orig_params)
+
+        with RollbackContext() as rollback:
+            params_1 = {'name': 'kimchi-vm', 'template': '/templates/test'}
+            inst.vms_create(params_1)
+            rollback.prependDefer(self._rollback_wrapper, inst.vm_delete,
+                                  'kimchi-vm')
+
+            # create snap
+            params_snap = {'name': u'mysnap'}
+            task = inst.vmsnapshots_create(u'kimchi-vm', params_snap)
+            rollback.prependDefer(inst.vmsnapshot_delete,
+                                  u'kimchi-vm', params_snap['name'])
+            inst.task_wait(task['id'])
+            task = inst.task_lookup(task['id'])
+            self.assertEquals('finished', task['status'])
+
+            # update vm
+            params = {'name': u'kimchi-vm-new', 'cpus': 2, 'memory': 2048}
+            inst.vm_update('kimchi-vm', params)
+
+            info = inst.vm_lookup('kimchi-vm-new')
+            self.assertEquals(2048, info['memory'])
+            self.assertEquals(2, info['cpus'])
+
+            # snapshot revert
+            snap = inst.vmsnapshot_lookup(u'kimchi-vm-new',
+                                          params_snap['name'])
+            result = inst.vmsnapshot_revert(u'kimchi-vm-new', snap['name'])
+            self.assertEquals(result, [u'kimchi-vm', params_snap['name']])
+
     @unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
     def test_network(self):
         inst = model.Model(None, self.tmp_store)
-- 
1.8.3.1




More information about the Kimchi-devel mailing list