From 6b83e1cd31f5e138af20fbd5c118d55da092eb35 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 7 Jul 2011 15:24:12 +0000 Subject: Added API and supporting code for rebooting or shutting down XenServer hosts. --- nova/api/openstack/contrib/hosts.py | 26 ++++++++++++++++++++++++++ nova/compute/api.py | 6 ++++++ nova/compute/manager.py | 6 ++++++ nova/tests/test_hosts.py | 32 ++++++++++++++++++++++++++++---- nova/virt/driver.py | 4 ++++ nova/virt/fake.py | 4 ++++ nova/virt/hyperv.py | 4 ++++ nova/virt/libvirt/connection.py | 4 ++++ nova/virt/vmwareapi_conn.py | 4 ++++ nova/virt/xenapi/vmops.py | 21 ++++++++++++++++++--- nova/virt/xenapi_conn.py | 4 ++++ 11 files changed, 108 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py index 55e57e1a4..cc71cadbd 100644 --- a/nova/api/openstack/contrib/hosts.py +++ b/nova/api/openstack/contrib/hosts.py @@ -78,6 +78,12 @@ class HostController(object): else: explanation = _("Invalid status: '%s'") % raw_val raise webob.exc.HTTPBadRequest(explanation=explanation) + elif key == "power_state": + if val in ("reboot", "off", "on"): + return self._set_power_state(req, id, val) + else: + explanation = _("Invalid status: '%s'") % raw_val + raise webob.exc.HTTPBadRequest(explanation=explanation) else: explanation = _("Invalid update setting: '%s'") % raw_key raise webob.exc.HTTPBadRequest(explanation=explanation) @@ -89,8 +95,28 @@ class HostController(object): LOG.audit(_("Setting host %(host)s to %(state)s.") % locals()) result = self.compute_api.set_host_enabled(context, host=host, enabled=enabled) + if result not in ("enabled", "disabled"): + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) return {"host": host, "status": result} + def _set_power_state(self, req, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + context = req.environ['nova.context'] + if power_state == "on": + raise webob.exc.HTTPNotImplemented() + if power_state == "reboot": + msg = _("Rebooting host %(host)s") + else: + msg = _("Powering off host %(host)s.") + LOG.audit(msg % locals()) + result = self.compute_api.set_power_state(context, host=host, + power_state=power_state) + if result != power_state: + # An error message was returned + raise webob.exc.HTTPBadRequest(explanation=result) + return {"host": host, "power_state": result} + class Hosts(extensions.ExtensionDescriptor): def get_name(self): diff --git a/nova/compute/api.py b/nova/compute/api.py index b0eedcd64..71e11c5ea 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -917,6 +917,12 @@ class API(base.Base): return self._call_compute_message("set_host_enabled", context, instance_id=None, host=host, params={"enabled": enabled}) + def set_power_state(self, context, host, power_state): + """Turns the specified host on/off, or reboots the host.""" + return self._call_compute_message("set_power_state", context, + instance_id=None, host=host, + params={"power_state": power_state}) + @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 91a604934..eb8e4df3c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -880,6 +880,12 @@ class ComputeManager(manager.SchedulerDependentManager): """Sets the specified host's ability to accept new instances.""" return self.driver.set_host_enabled(host, enabled) + @exception.wrap_exception + def set_power_state(self, context, instance_id=None, host=None, + power_state=None): + """Turns the specified host on/off, or reboots the host.""" + return self.driver.set_power_state(host, power_state) + @exception.wrap_exception def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this host.""" diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py index 548f81f8b..5a52e36e2 100644 --- a/nova/tests/test_hosts.py +++ b/nova/tests/test_hosts.py @@ -48,6 +48,16 @@ def stub_set_host_enabled(context, host, enabled): return status +def stub_set_power_state(context, host, power_state): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + if host == "host_c1": + return power_state + else: + return "fail" + + class FakeRequest(object): environ = {"nova.context": context.get_admin_context()} @@ -62,6 +72,8 @@ class HostTestCase(test.TestCase): self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) self.stubs.Set(self.controller.compute_api, 'set_host_enabled', stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'set_power_state', + stub_set_power_state) def test_list_hosts(self): """Verify that the compute hosts are returned.""" @@ -87,15 +99,27 @@ class HostTestCase(test.TestCase): result_c2 = self.controller.update(self.req, "host_c2", body=en_body) self.assertEqual(result_c2["status"], "disabled") + def test_power_state(self): + en_body = {"power_state": "reboot"} + result_c1 = self.controller.update(self.req, "host_c1", body=en_body) + self.assertEqual(result_c1["power_state"], "reboot") + result_c2 = self.controller.update(self.req, "host_c2", body=en_body) + self.assertEqual(result_c2["power_state"], "fail") + + def test_bad_power_state_value(self): + bad_body = {"power_state": "bad"} + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") + def test_bad_status_value(self): bad_body = {"status": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_update_key(self): bad_body = {"crazy": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) + result = self.controller.update(self.req, "host_c1", body=bad_body) + self.assertEqual(str(result.wrapped_exc)[:15], "400 Bad Request") def test_bad_host(self): self.assertRaises(exception.HostNotFound, self.controller.update, diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 3c4a073bf..eed32d8d6 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -253,3 +253,7 @@ class ComputeDriver(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..0596079e8 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -518,3 +518,7 @@ class FakeConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..a438ff2e8 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -503,3 +503,7 @@ class HyperVConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index b80a3daee..2a02e5a2d 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -1595,3 +1595,7 @@ class LibvirtConnection(driver.ComputeDriver): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" pass + + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index d80e14931..0136225dd 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -194,6 +194,10 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" pass + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + pass + class VMWareAPISession(object): """ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cb96930c1..ec90ba9fe 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -936,10 +936,25 @@ class VMOps(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" args = {"enabled": json.dumps(enabled)} - json_resp = self._call_xenhost("set_host_enabled", args) - resp = json.loads(json_resp) + xenapi_resp = self._call_xenhost("set_host_enabled", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] return resp["status"] + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + args = {"power_state": power_state} + xenapi_resp = self._call_xenhost("set_power_state", args) + try: + resp = json.loads(xenapi_resp) + except TypeError as e: + # Already logged; return the message + return xenapi_resp.details[-1] + return resp["power_state"] + def _call_xenhost(self, method, arg_dict): """There will be several methods that will need this general handling for interacting with the xenhost plugin, so this abstracts @@ -953,7 +968,7 @@ class VMOps(object): #args={"params": arg_dict}) ret = self._session.wait_for_task(task, task_id) except self.XenAPI.Failure as e: - ret = None + ret = e LOG.error(_("The call to %(method)s returned an error: %(e)s.") % locals()) return ret diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..0b88e0999 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -340,6 +340,10 @@ class XenAPIConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances.""" return self._vmops.set_host_enabled(host, enabled) + def set_power_state(self, host, power_state): + """Reboots, shuts down or starts up the host.""" + return self._vmops.set_power_state(host, power_state) + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit