[Kimchi-devel] [PATCH v2 3/3] Add support for VM suspend and resume

Crístian Deives cristiandeives at gmail.com
Wed Apr 8 12:29:01 UTC 2015


From: Crístian Viana <vianac at 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 at 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




More information about the Kimchi-devel mailing list