Added API and supporting code for rebooting or shutting down XenServer hosts.

This commit is contained in:
Ed Leafe
2011-07-07 15:24:12 +00:00
parent 9d29dc60d9
commit 6b83e1cd31
12 changed files with 137 additions and 8 deletions
+26
View File
@@ -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):
+6
View File
@@ -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."""
+6
View File
@@ -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."""
+28 -4
View File
@@ -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,
+4
View File
@@ -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()
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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
+4
View File
@@ -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):
"""
+18 -3
View File
@@ -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
+4
View File
@@ -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"""
@@ -103,6 +103,33 @@ def set_host_enabled(self, arg_dict):
return {"status": status}
@jsonify
def set_power_state(self, arg_dict):
"""Reboots or powers off this host. Ideally, we would also like to be
able to power *on* a host, but right now this is not technically
feasible.
"""
power_state = arg_dict.get("power_state")
if power_state is None:
raise pluginlib.PluginError(
_("Missing 'power_state' argument to set_power_state"))
# Host must be disabled first
# result = _run_command("xe host-disable")
# if result:
# raise pluginlib.PluginError(result)
# # All running VMs must be shutdown
# result = _run_command("xe vm-shutdown --multiple power-state=running")
# if result:
# raise pluginlib.PluginError(result)
# cmds = {"reboot": "xe host-reboot", "on": "xe host-power-on",
# "off": "xe host-shutdown"}
# result = _run_command(cmds[power_state])
# # Should be empty string
# if result:
# raise pluginlib.PluginError(result)
return {"power_state": power_state}
@jsonify
def host_data(self, arg_dict):
"""Runs the commands on the xenstore host to return the current status
@@ -217,4 +244,5 @@ def cleanup(dct):
if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"host_data": host_data,
"set_host_enabled": set_host_enabled})
"set_host_enabled": set_host_enabled,
"set_power_state": set_power_state})