Thanks for the patch, Cristian!
But as those are new functionalities it will be merged after 1.4.1 release.
On 22/01/2015 12:52, CrÃstian Viana wrote:
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 Viana <vianac(a)linux.vnet.ibm.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 1a84b6c..4f1f578 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -45,6 +45,8 @@ class VM(Resource):
self.reset = self.generate_action_handler('reset')
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 4eccc3e..79c0b23 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -106,6 +106,10 @@ messages = {
"KCHVM0033E": _("Virtual machine '%(name)s' must be stopped
before cloning it."),
"KCHVM0034E": _("Insufficient disk space to clone virtual machine
'%(name)s'"),
"KCHVM0035E": _("Unable to clone VM '%(name)s'. Details:
%(err)s"),
+ "KCHVM0036E": _("Cannot suspend VM '%(name)s' because it is
not running."),
+ "KCHVM0037E": _("Unable to suspend VM '%(name)s'. Details:
%(err)s"),
+ "KCHVM0038E": _("Cannot resume VM '%(name)s' because it is
not paused."),
+ "KCHVM0039E": _("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 5128d1e..55a80e6 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -939,6 +939,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('KCHVM0036E', {'name': name})
+
+ vir_dom = self.get_vm(name, self.conn)
+
+ try:
+ vir_dom.suspend()
+ except libvirt.libvirtError, e:
+ raise OperationFailed('KCHVM0037E', {'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('KCHVM0038E', {'name': name})
+
+ vir_dom = self.get_vm(name, self.conn)
+
+ try:
+ vir_dom.resume()
+ except libvirt.libvirtError, e:
+ raise OperationFailed('KCHVM0039E', {'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 c956007..2013f3c 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -187,6 +187,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 7416463..6ff7cf8 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -460,6 +460,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)