summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrey Morris <trey.morris@rackspace.com>2010-12-20 22:55:11 +0000
committerTrey Morris <trey.morris@rackspace.com>2010-12-20 22:55:11 +0000
commitaded4faba96e4de88f0294604927ef824cb249be (patch)
treeb24d35cfa25df0bc5feee0c947b5c9dbfd36d6b2
parent086f2d87be3c56ac8dafaf4551096868d57454db (diff)
added suspend and resume
-rw-r--r--nova/api/openstack/__init__.py2
-rw-r--r--nova/api/openstack/servers.py22
-rw-r--r--nova/compute/api.py18
-rw-r--r--nova/compute/manager.py32
-rw-r--r--nova/tests/api/openstack/test_servers.py32
-rw-r--r--nova/tests/compute_unittest.py8
-rw-r--r--nova/virt/fake.py8
-rw-r--r--nova/virt/libvirt_conn.py8
-rw-r--r--nova/virt/xenapi/vmops.py23
-rw-r--r--nova/virt/xenapi_conn.py8
10 files changed, 157 insertions, 4 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 210df8d24..2553313e4 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -176,6 +176,8 @@ class APIRouter(wsgi.Router):
logging.debug("Including admin operations in API.")
server_members['pause'] = 'POST'
server_members['unpause'] = 'POST'
+ server_members['suspend'] = 'POST'
+ server_members['resume'] = 'POST'
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 5c3322f7c..e6700ee96 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -195,3 +195,25 @@ class Controller(wsgi.Controller):
logging.error("Compute.api::unpause %s", readable)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+
+ def suspend(self, req, id):
+ """permit admins to suspend the server"""
+ context = req.environ['nova.context']
+ try:
+ self.compute_api.suspend(context, id)
+ except:
+ readable = traceback.format_exc()
+ logging.error("compute.api::suspend %s", readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
+
+ def resume(self, req, id):
+ """permit admins to resume the server from suspend"""
+ context = req.environ['nova.context']
+ try:
+ self.compute_api.resume(context, id)
+ except:
+ readable = traceback.format_exc()
+ logging.error("compute.api::resume %s", readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
diff --git a/nova/compute/api.py b/nova/compute/api.py
index c740814da..bb144d12f 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -298,6 +298,24 @@ class ComputeAPI(base.Base):
{"method": "unpause_instance",
"args": {"instance_id": instance['id']}})
+ def suspend(self, context, instance_id):
+ """suspend the instance with instance_id"""
+ instance = self.db.instance_get_by_internal_id(context, instance_id)
+ host = instance['host']
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "suspend_instance",
+ "args": {"instance_id": instance['id']}})
+
+ def resume(self, context, instance_id):
+ """resume the instance with instance_id"""
+ instance = self.db.instance_get_by_internal_id(context, instance_id)
+ host = instance['host']
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "resume_instance",
+ "args": {"instance_id": instance['id']}})
+
def rescue(self, context, instance_id):
"""Rescue the given instance."""
instance = self.db.instance_get_by_internal_id(context, instance_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index a84af6bb9..b1ac2db88 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -228,6 +228,38 @@ class ComputeManager(manager.Manager):
result))
@exception.wrap_exception
+ def suspend_instance(self, context, instance_id):
+ """suspend the instance with instance_id"""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+
+ logging.debug('instance %s: suspending', instance_ref['internal_id'])
+ self.db.instance_set_state(context, instance_id,
+ power_state.NOSTATE,
+ 'suspending')
+ self.driver.suspend(instance_ref,
+ lambda result: self._update_state_callback(self,
+ context,
+ instance_id,
+ result))
+
+ @exception.wrap_exception
+ def resume_instance(self, context, instance_id):
+ """resume the suspended instance with instance_id"""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+
+ logging.debug('instance %s: resuming', instance_ref['internal_id'])
+ self.db.instance_set_state(context, instance_id,
+ power_state.NOSTATE,
+ 'resuming')
+ self.driver.resume(instance_ref,
+ lambda result: self._update_state_callback(self,
+ context,
+ instance_id,
+ result))
+
+ @exception.wrap_exception
def get_console_output(self, context, instance_id):
"""Send the console output for an instance."""
context = context.elevated()
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 3820f5f27..5d23db588 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -88,9 +88,13 @@ class ServersTest(unittest.TestCase):
self.stubs.Set(nova.db.api, 'instance_get_floating_address',
instance_address)
self.stubs.Set(nova.compute.api.ComputeAPI, 'pause',
- fake_compute_api)
+ fake_compute_api)
self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause',
- fake_compute_api)
+ fake_compute_api)
+ self.stubs.Set(nova.compute.api.ComputeAPI, 'suspend',
+ fake_compute_api)
+ self.stubs.Set(nova.compute.api.ComputeAPI, 'resume',
+ fake_compute_api)
self.allow_admin = FLAGS.allow_admin_api
def tearDown(self):
@@ -246,6 +250,30 @@ class ServersTest(unittest.TestCase):
res = req.get_response(nova.api.API('os'))
self.assertEqual(res.status_int, 202)
+ def test_server_suspend(self):
+ FLAGS.allow_admin_api = True
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/suspend')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API('os'))
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_resume(self):
+ FLAGS.allow_admin_api = True
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/resume')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API('os'))
+ self.assertEqual(res.status_int, 202)
+
def test_server_reboot(self):
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py
index 187ca31de..111a43cdc 100644
--- a/nova/tests/compute_unittest.py
+++ b/nova/tests/compute_unittest.py
@@ -135,6 +135,14 @@ class ComputeTestCase(test.TestCase):
self.compute.unpause_instance(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
+ def test_suspend(self):
+ """ensure instance can be suspended"""
+ instance_id = self._create_instance()
+ self.compute.run_instance(self.context, instance_id)
+ self.compute.suspend_instance(self.context, instance_id)
+ self.compute.resume_instance(self.context, instance_id)
+ self.compute.terminate_instance(self.context, instance_id)
+
def test_reboot(self):
"""Ensure instance can be rebooted"""
instance_id = self._create_instance()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 55c6dcef9..54787751e 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -142,6 +142,14 @@ class FakeConnection(object):
"""
pass
+ def suspend(self, instance, callback):
+ """suspend the specified instance"""
+ pass
+
+ def resume(self, instance, callback):
+ """resume the specified instance"""
+ pass
+
def destroy(self, instance):
"""
Destroy (shutdown and delete) the specified instance.
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index ad101db2a..6c77a2693 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -270,6 +270,14 @@ class LibvirtConnection(object):
raise exception.APIError("unpause not supported for libvirt.")
@exception.wrap_exception
+ def suspend(self, instance, callback):
+ raise exception.APIError("suspend not supported for libvirt")
+
+ @exception.wrap_exception
+ def resume(self, instance, callback):
+ raise exception.APIError("resume not supported for libvirt")
+
+ @exception.wrap_exception
def rescue(self, instance):
self.destroy(instance, False)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index a18eacf07..eb6743a7a 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -144,11 +144,29 @@ class VMOps(object):
task = self._session.call_xenapi('Async.VM.unpause', vm)
self._wait_with_callback(task, callback)
+ def suspend(self, instance, callback):
+ """suspend the specified instance"""
+ instance_name = instance.name
+ vm = VMHelper.lookup(self._session, instance_name)
+ if vm is None:
+ raise Exception("suspend: instance not present %s" % instance_name)
+ task = self._session.call_xenapi('Async.VM.suspend', vm)
+ self._wait_with_callback(task, callback)
+
+ def resume(self, instance, callback):
+ """resume the specified instance"""
+ instance_name = instance.name
+ vm = VMHelper.lookup(self._session, instance_name)
+ if vm is None:
+ raise Exception("resume: instance not present %s" % instance_name)
+ task = self._session.call_xenapi('Async.VM.resume', vm)
+ self._wait_with_callback(task, callback)
+
def get_info(self, instance_id):
""" Return data about VM instance """
vm = VMHelper.lookup_blocking(self._session, instance_id)
if vm is None:
- raise Exception('instance not present %s' % instance_id)
+ raise Exception("get_info: instance not present %s" % instance_id)
rec = self._session.get_xenapi().VM.get_record(vm)
return VMHelper.compile_info(rec)
@@ -156,7 +174,8 @@ class VMOps(object):
"""Return data about VM diagnostics"""
vm = VMHelper.lookup(self._session, instance_id)
if vm is None:
- raise Exception("instance not present %s" % instance_id)
+ raise Exception("get_diagnostics: instance not present %s" % \
+ instance_id)
rec = self._session.get_xenapi().VM.get_record(vm)
return VMHelper.compile_diagnostics(self._session, rec)
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 21ed2cd65..7e430cbdc 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -132,6 +132,14 @@ class XenAPIConnection(object):
""" Unpause paused VM instance """
self._vmops.unpause(instance, callback)
+ def suspend(self, instance, callback):
+ """suspend the specified instance"""
+ self._vmops.suspend(instance, callback)
+
+ def resume(self, instance, callback):
+ """resume the specified instance"""
+ self._vmops.resume(instance, callback)
+
def get_info(self, instance_id):
""" Return data about VM instance """
return self._vmops.get_info(instance_id)