Added API and supporting code for rebooting or shutting down XenServer hosts.
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in New Issue
Block a user