From aded4faba96e4de88f0294604927ef824cb249be Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Mon, 20 Dec 2010 22:55:11 +0000 Subject: added suspend and resume --- nova/api/openstack/__init__.py | 2 ++ nova/api/openstack/servers.py | 22 ++++++++++++++++++++++ nova/compute/api.py | 18 ++++++++++++++++++ nova/compute/manager.py | 32 ++++++++++++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 32 ++++++++++++++++++++++++++++++-- nova/tests/compute_unittest.py | 8 ++++++++ nova/virt/fake.py | 8 ++++++++ nova/virt/libvirt_conn.py | 8 ++++++++ nova/virt/xenapi/vmops.py | 23 +++++++++++++++++++++-- nova/virt/xenapi_conn.py | 8 ++++++++ 10 files changed, 157 insertions(+), 4 deletions(-) (limited to 'nova') 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 @@ -227,6 +227,38 @@ class ComputeManager(manager.Manager): instance_id, 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.""" 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 @@ -269,6 +269,14 @@ class LibvirtConnection(object): def unpause(self, instance, callback): 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) -- cgit From f9e2bbdf1182f54d69f6005eb7c39007eddbd3cd Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 21 Dec 2010 20:06:53 +0000 Subject: correct xenapi resume call --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index eb6743a7a..3ec131600 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -159,7 +159,7 @@ class VMOps(object): 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) + task = self._session.call_xenapi('Async.VM.resume', vm, False, True) self._wait_with_callback(task, callback) def get_info(self, instance_id): -- cgit From 62286399b69218418020baaf524292c1677d27d3 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 23 Dec 2010 06:48:15 +0000 Subject: added suspend as a power state --- nova/api/openstack/servers.py | 3 ++- nova/compute/power_state.py | 4 +++- nova/virt/xenapi/vm_utils.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e6700ee96..9ee52ef2f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -45,7 +45,8 @@ def _entity_detail(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'} 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/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 017a6eab0..667da27ea 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -37,7 +37,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} XenAPI = None -- cgit From 45c75b0c8ecea6952d68cc28d2925c6a42a799de Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 23 Dec 2010 07:05:45 +0000 Subject: added power state logging to nova.virt.xenapi.vm_utils --- nova/virt/xenapi/vm_utils.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 667da27ea..095e32ae2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -217,6 +217,10 @@ class VMHelper(): @classmethod def compile_info(cls, record): + 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, -- cgit From a0ca9d4a9550370cc262574fbee097e5b70e408d Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 23 Dec 2010 20:35:16 +0000 Subject: added _() for gettext and a couple of pep8s --- nova/api/openstack/servers.py | 8 ++++---- nova/compute/manager.py | 4 ++-- nova/tests/compute_unittest.py | 4 ++-- nova/virt/fake.py | 8 ++++++-- nova/virt/xenapi/vm_utils.py | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9ee52ef2f..d3e5badea 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -182,7 +182,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,7 +193,7 @@ 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() @@ -204,7 +204,7 @@ class Controller(wsgi.Controller): self.compute_api.suspend(context, id) except: readable = traceback.format_exc() - logging.error("compute.api::suspend %s", readable) + logging.error(_("compute.api::suspend %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() @@ -215,6 +215,6 @@ class Controller(wsgi.Controller): self.compute_api.resume(context, id) except: readable = traceback.format_exc() - logging.error("compute.api::resume %s", readable) + logging.error(_("compute.api::resume %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b1ac2db88..f96f0ca9c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -233,7 +233,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: suspending', instance_ref['internal_id']) + logging.debug(_('instance %s: suspending'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'suspending') @@ -249,7 +249,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug('instance %s: resuming', instance_ref['internal_id']) + logging.debug(_('instance %s: resuming'), instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'resuming') diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 111a43cdc..14954c3a2 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -100,13 +100,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): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 54787751e..3bb062294 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -143,11 +143,15 @@ class FakeConnection(object): pass def suspend(self, instance, callback): - """suspend the specified instance""" + """ + suspend the specified instance + """ pass def resume(self, instance, callback): - """resume the specified instance""" + """ + resume the specified instance + """ pass def destroy(self, instance): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 095e32ae2..dc8359204 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -217,9 +217,9 @@ class VMHelper(): @classmethod def compile_info(cls, record): - logging.info("(VM_UTILS) xenserver vm state -> |%s|", + logging.info(_("(VM_UTILS) xenserver vm state -> |%s|"), record['power_state']) - logging.info("(VM_UTILS) xenapi power_state -> |%s|", + 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, -- cgit From 1c00947aa86597d918d651b5385a6a4d72671c10 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sun, 26 Dec 2010 14:08:38 +0000 Subject: logs inner exception in nova/utils.py->import_class --- nova/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') 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) -- cgit From 7cc68042a911dc38f1c2c24b3361757c16142b74 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Mon, 27 Dec 2010 22:59:08 +0000 Subject: missed a couple of gettext _() --- nova/virt/xenapi/vmops.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cb5a49350..d756eae53 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -178,7 +178,8 @@ class VMOps(object): instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: - raise Exception("suspend: instance not present %s" % instance_name) + raise Exception(_("suspend: instance not present %s") % + instance_name) task = self._session.call_xenapi('Async.VM.suspend', vm) self._wait_with_callback(task, callback) @@ -187,7 +188,8 @@ class VMOps(object): instance_name = instance.name vm = VMHelper.lookup(self._session, instance_name) if vm is None: - raise Exception("resume: instance not present %s" % instance_name) + 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) -- cgit From 32bfe6acdf8e462f90c72c9230b77c8c6fdca93b Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 28 Dec 2010 05:14:21 +0000 Subject: fixed a line length --- nova/compute/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d5e0c38b0..70b175e7c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -302,7 +302,8 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: suspending'), instance_ref['internal_id']) + logging.debug(_('instance %s: suspending'), + instance_ref['internal_id']) self.db.instance_set_state(context, instance_id, power_state.NOSTATE, 'suspending') -- cgit