[PATCH v2 0/3] VM suspend/resume

This is the difference between this and the previous patchset: - Use string (instead of Unicode) when comparing VM states. Crístian Viana (3): Optimize VM update function Update some VM state conditions Add support for VM suspend and resume src/kimchi/control/vms.py | 2 ++ src/kimchi/i18n.py | 4 ++++ src/kimchi/model/vms.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- tests/test_model.py | 20 ++++++++++++++++++++ tests/test_rest.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-) -- 2.1.0

From: Crístian Viana <vianac@linux.vnet.ibm.com> When updating a VM, Kimchi always fetches the current VM state regardless of whether it will be needed. Move the VM state fetching to block where it's needed. That way, Kimchi will save a libvirt call when that result isn't important for the operation. Signed-off-by: Crístian Deives <cristiandeives@gmail.com> --- src/kimchi/model/vms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index af5e765..c39a407 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -732,7 +732,6 @@ class VMModel(object): 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) for key, val in params.items(): @@ -754,6 +753,7 @@ class VMModel(object): conn = self.conn.get() try: if 'name' in params: + state = DOM_STATE_MAP[dom.info()[0]] if state == 'running': msg_args = {'name': vm_name, 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) -- 2.1.0

From: Crístian Viana <vianac@linux.vnet.ibm.com> Some functions assume that a VM can only be in two states: shutoff and running. However, that's not always the case (e.g. not being running doesn't mean it's shutoff). Update some functions so the VM state conditions are more accurate. Signed-off-by: Crístian Deives <cristiandeives@gmail.com> --- src/kimchi/model/vms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index c39a407..fd5aad5 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -754,7 +754,7 @@ class VMModel(object): try: if 'name' in params: state = DOM_STATE_MAP[dom.info()[0]] - if state == 'running': + if state != 'shutoff': msg_args = {'name': vm_name, 'new_name': params['name']} raise InvalidParameter("KCHVM0003E", msg_args) @@ -891,7 +891,7 @@ class VMModel(object): paths = self._vm_get_disk_paths(dom) info = self.lookup(name) - if info['state'] == 'running': + if info['state'] != 'shutoff': self.poweroff(name) # delete existing snapshots before deleting VM -- 2.1.0

From: Crístian Viana <vianac@linux.vnet.ibm.com> The user is now able to suspend the VM and resume its execution in a later moment with the following commands: POST /vms/<vm-name>/suspend POST /vms/<vm-name>/resume Signed-off-by: Crístian Deives <cristiandeives@gmail.com> --- src/kimchi/control/vms.py | 2 ++ src/kimchi/i18n.py | 4 ++++ src/kimchi/model/vms.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/test_model.py | 20 ++++++++++++++++++++ tests/test_rest.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index 5068b7c..6352a26 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -48,6 +48,8 @@ class VM(Resource): destructive=True) self.connect = self.generate_action_handler('connect') self.clone = self.generate_action_handler_task('clone') + self.suspend = self.generate_action_handler('suspend') + self.resume = self.generate_action_handler('resume') @property def data(self): diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index 46990a5..c8986cf 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -107,6 +107,10 @@ messages = { "KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"), "KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"), "KCHVM0036E": _("Invalid operation for non-persistent virtual machine %(name)s"), + "KCHVM0037E": _("Cannot suspend VM '%(name)s' because it is not running."), + "KCHVM0038E": _("Unable to suspend VM '%(name)s'. Details: %(err)s"), + "KCHVM0039E": _("Cannot resume VM '%(name)s' because it is not paused."), + "KCHVM0040E": _("Unable to resume VM '%(name)s'. Details: %(err)s"), "KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."), "KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index fd5aad5..e2c92ac 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -1044,6 +1044,47 @@ class VMModel(object): kimchi_log.error('Error trying to delete vm screenshot from ' 'database due error: %s', e.message) + def suspend(self, name): + """Suspend the virtual machine's execution and puts it in the + state 'paused'. Use the function "resume" to restore its state. + If the VM is not running, an exception will be raised. + + Parameters: + name -- the name of the VM to be suspended. + """ + vm = self.lookup(name) + if vm['state'] != 'running': + raise InvalidOperation('KCHVM0037E', {'name': name}) + + vir_dom = self.get_vm(name, self.conn) + + try: + vir_dom.suspend() + except libvirt.libvirtError, e: + raise OperationFailed('KCHVM0038E', {'name': name, + 'err': e.message}) + + def resume(self, name): + """Resume the virtual machine's execution and puts it in the + state 'running'. The VM should have been suspended previously by the + function "suspend" and be in the state 'paused', otherwise an exception + will be raised. + + Parameters: + name -- the name of the VM to be resumed. + """ + vm = self.lookup(name) + if vm['state'] != 'paused': + raise InvalidOperation('KCHVM0039E', {'name': name}) + + vir_dom = self.get_vm(name, self.conn) + + try: + vir_dom.resume() + except libvirt.libvirtError, e: + raise OperationFailed('KCHVM0040E', {'name': name, + 'err': e.message}) + class VMScreenshotModel(object): def __init__(self, **kargs): diff --git a/tests/test_model.py b/tests/test_model.py index 63bd721..f305a3c 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -211,6 +211,26 @@ class ModelTests(unittest.TestCase): self.assertRaises(NotFoundError, inst.vmsnapshot_delete, u'kimchi-vm', u'foobar') + # suspend and resume the VM + info = inst.vm_lookup(u'kimchi-vm') + self.assertEquals(info['state'], 'shutoff') + self.assertRaises(InvalidOperation, inst.vm_suspend, u'kimchi-vm') + inst.vm_start(u'kimchi-vm') + info = inst.vm_lookup(u'kimchi-vm') + self.assertEquals(info['state'], 'running') + inst.vm_suspend(u'kimchi-vm') + info = inst.vm_lookup(u'kimchi-vm') + self.assertEquals(info['state'], 'paused') + self.assertRaises(InvalidParameter, inst.vm_update, u'kimchi-vm', + {'name': 'foo'}) + inst.vm_resume(u'kimchi-vm') + info = inst.vm_lookup(u'kimchi-vm') + self.assertEquals(info['state'], 'running') + self.assertRaises(InvalidOperation, inst.vm_resume, u'kimchi-vm') + # leave the VM suspended to make sure a paused VM can be + # deleted correctly + inst.vm_suspend(u'kimchi-vm') + vms = inst.vms_get_list() self.assertFalse('kimchi-vm' in vms) diff --git a/tests/test_rest.py b/tests/test_rest.py index 4ecf3ce..16ff41d 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -459,6 +459,34 @@ class RestTests(unittest.TestCase): '{}', 'DELETE') self.assertEquals(204, resp.status) + # Suspend the VM + resp = self.request('/vms/test-vm', '{}', 'GET') + self.assertEquals(200, resp.status) + vm = json.loads(resp.read()) + self.assertEquals(vm['state'], 'shutoff') + resp = self.request('/vms/test-vm/suspend', '{}', 'POST') + self.assertEquals(400, resp.status) + resp = self.request('/vms/test-vm/start', '{}', 'POST') + self.assertEquals(200, resp.status) + resp = self.request('/vms/test-vm', '{}', 'GET') + self.assertEquals(200, resp.status) + vm = json.loads(resp.read()) + self.assertEquals(vm['state'], 'running') + resp = self.request('/vms/test-vm/suspend', '{}', 'POST') + self.assertEquals(200, resp.status) + resp = self.request('/vms/test-vm', '{}', 'GET') + self.assertEquals(200, resp.status) + vm = json.loads(resp.read()) + self.assertEquals(vm['state'], 'paused') + + # Resume the VM + resp = self.request('/vms/test-vm/resume', '{}', 'POST') + self.assertEquals(200, resp.status) + resp = self.request('/vms/test-vm', '{}', 'GET') + self.assertEquals(200, resp.status) + vm = json.loads(resp.read()) + self.assertEquals(vm['state'], 'running') + # Delete the VM resp = self.request('/vms/test-vm', '{}', 'DELETE') self.assertEquals(204, resp.status) -- 2.1.0

Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com> On 08/04/2015 09:28, Crístian Deives wrote:
This is the difference between this and the previous patchset:
- Use string (instead of Unicode) when comparing VM states.
Crístian Viana (3): Optimize VM update function Update some VM state conditions Add support for VM suspend and resume
src/kimchi/control/vms.py | 2 ++ src/kimchi/i18n.py | 4 ++++ src/kimchi/model/vms.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- tests/test_model.py | 20 ++++++++++++++++++++ tests/test_rest.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 3 deletions(-)
participants (2)
-
Aline Manera
-
Crístian Deives