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

From: Zongmei Gou <gouzongmei@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
participants (1)
-
gouzongmei@ourfuture.cn