summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorCerberus <matt.dietz@rackspace.com>2010-12-28 15:49:36 -0600
committerCerberus <matt.dietz@rackspace.com>2010-12-28 15:49:36 -0600
commit79abcb065a07ca342cee6b843960dc877300c750 (patch)
tree474ed7125d6136bd0fd1db0f85afb5219b2d425a /nova
parent902df6eb4968743dd451e54cde27ce88fc83ddaa (diff)
parent675ca7c5f38af0fa1150936e881482aa20fdaa45 (diff)
Merge from trunk
Diffstat (limited to 'nova')
-rw-r--r--nova/api/openstack/__init__.py2
-rw-r--r--nova/api/openstack/servers.py29
-rw-r--r--nova/compute/api.py18
-rw-r--r--nova/compute/manager.py33
-rw-r--r--nova/compute/power_state.py4
-rw-r--r--nova/tests/api/openstack/test_servers.py32
-rw-r--r--nova/tests/test_compute.py12
-rw-r--r--nova/utils.py3
-rw-r--r--nova/virt/fake.py12
-rw-r--r--nova/virt/libvirt_conn.py8
-rw-r--r--nova/virt/xenapi/vm_utils.py6
-rw-r--r--nova/virt/xenapi/vmops.py20
-rw-r--r--nova/virt/xenapi_conn.py8
13 files changed, 177 insertions, 10 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 1f78820c8..66aceee2d 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -93,6 +93,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 d18889563..2232d24b2 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -43,7 +43,8 @@ def _translate_detail_keys(inst):
power_state.NOSTATE: 'build',
power_state.RUNNING: 'active',
power_state.BLOCKED: 'active',
- power_state.PAUSED: 'suspended',
+ power_state.SUSPENDED: 'suspended',
+ power_state.PAUSED: 'error',
power_state.SHUTDOWN: 'active',
power_state.SHUTOFF: 'active',
power_state.CRASHED: 'error'}
@@ -182,7 +183,7 @@ class Controller(wsgi.Controller):
self.compute_api.pause(ctxt, id)
except:
readable = traceback.format_exc()
- logging.error("Compute.api::pause %s", readable)
+ logging.error(_("Compute.api::pause %s"), readable)
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
@@ -193,6 +194,28 @@ class Controller(wsgi.Controller):
self.compute_api.unpause(ctxt, id)
except:
readable = traceback.format_exc()
- logging.error("Compute.api::unpause %s", readable)
+ 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 a3245940e..a47703461 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -284,6 +284,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 ff8202cca..70b175e7c 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -297,6 +297,39 @@ 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/compute/power_state.py b/nova/compute/power_state.py
index cefdf2d9e..37039d2ec 100644
--- a/nova/compute/power_state.py
+++ b/nova/compute/power_state.py
@@ -26,6 +26,7 @@ PAUSED = 0x03
SHUTDOWN = 0x04
SHUTOFF = 0x05
CRASHED = 0x06
+SUSPENDED = 0x07
def name(code):
@@ -36,5 +37,6 @@ def name(code):
PAUSED: 'paused',
SHUTDOWN: 'shutdown',
SHUTOFF: 'shutdown',
- CRASHED: 'crashed'}
+ CRASHED: 'crashed',
+ SUSPENDED: 'suspended'}
return d[code]
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/test_compute.py b/nova/tests/test_compute.py
index 348bb3351..bcb8a1526 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -101,13 +101,13 @@ class ComputeTestCase(test.TestCase):
self.compute.run_instance(self.context, instance_id)
instances = db.instance_get_all(context.get_admin_context())
- logging.info("Running instances: %s", instances)
+ logging.info(_("Running instances: %s"), instances)
self.assertEqual(len(instances), 1)
self.compute.terminate_instance(self.context, instance_id)
instances = db.instance_get_all(context.get_admin_context())
- logging.info("After terminating instances: %s", instances)
+ logging.info(_("After terminating instances: %s"), instances)
self.assertEqual(len(instances), 0)
def test_run_terminate_timestamps(self):
@@ -136,6 +136,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/utils.py b/nova/utils.py
index b9045a50c..15112faa2 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -48,7 +48,8 @@ def import_class(import_str):
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
- except (ImportError, ValueError, AttributeError):
+ except (ImportError, ValueError, AttributeError), exc:
+ logging.debug(_('Inner Exception: %s'), exc)
raise exception.NotFound(_('Class %s cannot be found') % class_str)
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 238acf798..706888b0d 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -148,6 +148,18 @@ 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 67b9fc47e..65cf65098 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -280,6 +280,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/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 2f02f4677..47fb6db53 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -39,7 +39,7 @@ XENAPI_POWER_STATE = {
'Halted': power_state.SHUTDOWN,
'Running': power_state.RUNNING,
'Paused': power_state.PAUSED,
- 'Suspended': power_state.SHUTDOWN, # FIXME
+ 'Suspended': power_state.SUSPENDED,
'Crashed': power_state.CRASHED}
@@ -283,6 +283,10 @@ class VMHelper(HelperBase):
@classmethod
def compile_info(cls, record):
"""Fill record with VM status information"""
+ logging.info(_("(VM_UTILS) xenserver vm state -> |%s|"),
+ record['power_state'])
+ logging.info(_("(VM_UTILS) xenapi power_state -> |%s|"),
+ XENAPI_POWER_STATE[record['power_state']])
return {'state': XENAPI_POWER_STATE[record['power_state']],
'max_mem': long(record['memory_static_max']) >> 10,
'mem': long(record['memory_dynamic_max']) >> 10,
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index b1de73641..ba502ffa2 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -188,6 +188,26 @@ class VMOps(object):
task = self._session.call_xenapi('Async.VM.unpause', vm)
self._wait_with_callback(instance.id, 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, False, True)
+ self._wait_with_callback(task, callback)
+
def get_info(self, instance_id):
"""Return data about VM instance"""
vm = VMHelper.lookup(self._session, instance_id)
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 11c66c974..7f03d6c2b 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -147,6 +147,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)