summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEd Leafe <ed@leafe.com>2011-08-08 15:34:04 +0000
committerEd Leafe <ed@leafe.com>2011-08-08 15:34:04 +0000
commit6dcea0da62e881eaeb02027ea868d49c2402209d (patch)
treed7195fb631676bc6b880a46cc85b2af6d4289543
parent966b7218a0fc96222e0ef0a22526f1bf3e7f2a9c (diff)
parenteb66810f6034a29724630e89739217a83b858d86 (diff)
downloadnova-6dcea0da62e881eaeb02027ea868d49c2402209d.tar.gz
nova-6dcea0da62e881eaeb02027ea868d49c2402209d.tar.xz
nova-6dcea0da62e881eaeb02027ea868d49c2402209d.zip
Merged trunk
-rw-r--r--nova/api/openstack/contrib/admin_only.py30
-rw-r--r--nova/api/openstack/contrib/hosts.py50
-rw-r--r--nova/compute/api.py5
-rw-r--r--nova/compute/manager.py6
-rw-r--r--nova/tests/test_hosts.py19
-rw-r--r--nova/virt/driver.py4
-rw-r--r--nova/virt/fake.py4
-rw-r--r--nova/virt/hyperv.py4
-rw-r--r--nova/virt/libvirt/connection.py4
-rw-r--r--nova/virt/vmwareapi_conn.py4
-rw-r--r--nova/virt/xenapi/vmops.py18
-rw-r--r--nova/virt/xenapi_conn.py4
-rwxr-xr-xplugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost67
13 files changed, 209 insertions, 10 deletions
diff --git a/nova/api/openstack/contrib/admin_only.py b/nova/api/openstack/contrib/admin_only.py
new file mode 100644
index 000000000..e821c9e1f
--- /dev/null
+++ b/nova/api/openstack/contrib/admin_only.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2011 Openstack, LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Decorator for limiting extensions that should be admin-only."""
+
+from functools import wraps
+from nova import flags
+FLAGS = flags.FLAGS
+
+
+def admin_only(fnc):
+ @wraps(fnc)
+ def _wrapped(self, *args, **kwargs):
+ if FLAGS.allow_admin_api:
+ return fnc(self, *args, **kwargs)
+ return []
+ _wrapped.func_name = fnc.func_name
+ return _wrapped
diff --git a/nova/api/openstack/contrib/hosts.py b/nova/api/openstack/contrib/hosts.py
index 55e57e1a4..2714d7888 100644
--- a/nova/api/openstack/contrib/hosts.py
+++ b/nova/api/openstack/contrib/hosts.py
@@ -24,6 +24,7 @@ from nova import log as logging
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import faults
+from nova.api.openstack.contrib import admin_only
from nova.scheduler import api as scheduler_api
@@ -70,7 +71,7 @@ class HostController(object):
key = raw_key.lower().strip()
val = raw_val.lower().strip()
# NOTE: (dabo) Right now only 'status' can be set, but other
- # actions may follow.
+ # settings may follow.
if key == "status":
if val[:6] in ("enable", "disabl"):
return self._set_enabled_status(req, id,
@@ -78,6 +79,20 @@ class HostController(object):
else:
explanation = _("Invalid status: '%s'") % raw_val
raise webob.exc.HTTPBadRequest(explanation=explanation)
+ elif key == "power_state":
+ if val == "startup":
+ # The only valid values for 'state' are 'reboot' or
+ # 'shutdown'. For completeness' sake there is the
+ # 'startup' option to start up a host, but this is not
+ # technically feasible now, as we run the host on the
+ # XenServer box.
+ msg = _("Host startup on XenServer is not supported.")
+ raise webob.exc.HTTPBadRequest(explanation=msg)
+ elif val in ("reboot", "shutdown"):
+ return self._set_powerstate(req, id, val)
+ else:
+ explanation = _("Invalid powerstate: '%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 +104,34 @@ 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 _host_power_action(self, req, host, action):
+ """Reboots or shuts down the host."""
+ context = req.environ['nova.context']
+ result = self.compute_api.host_power_action(context, host=host,
+ action=action)
+ return {"host": host, "power_action": result}
+
+ def startup(self, req, id):
+ """The only valid values for 'action' are 'reboot' or
+ 'shutdown'. For completeness' sake there is the
+ 'startup' option to start up a host, but this is not
+ technically feasible now, as we run the host on the
+ XenServer box.
+ """
+ msg = _("Host startup on XenServer is not supported.")
+ raise webob.exc.HTTPBadRequest(explanation=msg)
+
+ def shutdown(self, req, id):
+ return self._host_power_action(req, host=id, action="shutdown")
+
+ def reboot(self, req, id):
+ return self._host_power_action(req, host=id, action="reboot")
+
class Hosts(extensions.ExtensionDescriptor):
def get_name(self):
@@ -108,7 +149,10 @@ class Hosts(extensions.ExtensionDescriptor):
def get_updated(self):
return "2011-06-29T00:00:00+00:00"
+ @admin_only.admin_only
def get_resources(self):
- resources = [extensions.ResourceExtension('os-hosts', HostController(),
- collection_actions={'update': 'PUT'}, member_actions={})]
+ resources = [extensions.ResourceExtension('os-hosts',
+ HostController(), collection_actions={'update': 'PUT'},
+ member_actions={"startup": "GET", "shutdown": "GET",
+ "reboot": "GET"})]
return resources
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 752f29384..e5e5f7f57 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -999,6 +999,11 @@ class API(base.Base):
return self._call_compute_message("set_host_enabled", context,
instance_id=None, host=host, params={"enabled": enabled})
+ def host_power_action(self, context, host, action):
+ """Reboots or shuts down the host."""
+ return self._call_compute_message("host_power_action", context,
+ instance_id=None, host=host, params={"action": action})
+
@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 69acf6e95..fb135e1f2 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -958,6 +958,12 @@ class ComputeManager(manager.SchedulerDependentManager):
result))
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
+ def host_power_action(self, context, instance_id=None, host=None,
+ action=None):
+ """Reboots or shuts down the host."""
+ return self.driver.host_power_action(host, action)
+
+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
def set_host_enabled(self, context, instance_id=None, host=None,
enabled=None):
"""Sets the specified host's ability to accept new instances."""
diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py
index 548f81f8b..cd22571e6 100644
--- a/nova/tests/test_hosts.py
+++ b/nova/tests/test_hosts.py
@@ -48,6 +48,10 @@ def stub_set_host_enabled(context, host, enabled):
return status
+def stub_host_power_action(context, host, action):
+ return action
+
+
class FakeRequest(object):
environ = {"nova.context": context.get_admin_context()}
@@ -62,6 +66,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, 'host_power_action',
+ stub_host_power_action)
def test_list_hosts(self):
"""Verify that the compute hosts are returned."""
@@ -87,6 +93,19 @@ class HostTestCase(test.TestCase):
result_c2 = self.controller.update(self.req, "host_c2", body=en_body)
self.assertEqual(result_c2["status"], "disabled")
+ def test_host_startup(self):
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.startup,
+ self.req, "host_c1")
+
+ def test_host_shutdown(self):
+ result = self.controller.shutdown(self.req, "host_c1")
+ print "RES", result
+ self.assertEqual(result["power_action"], "shutdown")
+
+ def test_host_reboot(self):
+ result = self.controller.reboot(self.req, "host_c1")
+ self.assertEqual(result["power_action"], "reboot")
+
def test_bad_status_value(self):
bad_body = {"status": "bad"}
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 4f3cfefad..052c6607e 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -282,6 +282,10 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ raise NotImplementedError()
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
# TODO(Vek): Need to pass context in for access to auth_token
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 80abcc644..db51c258b 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -512,6 +512,10 @@ class FakeConnection(driver.ComputeDriver):
"""Return fake Host Status of ram, disk, network."""
return self.host_status
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ pass
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index 3428a7fc1..f0efeb581 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -499,6 +499,10 @@ class HyperVConnection(driver.ComputeDriver):
"""See xenapi_conn.py implementation."""
pass
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ pass
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index d4160b280..4cb5f87cb 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -1562,6 +1562,10 @@ class LibvirtConnection(driver.ComputeDriver):
"""See xenapi_conn.py implementation."""
pass
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ pass
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 3d209fa99..5937d9585 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -191,6 +191,10 @@ class VMWareESXConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
return
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ pass
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
pass
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index a78413370..b549b33d1 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -1031,11 +1031,23 @@ class VMOps(object):
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ args = {"action": json.dumps(action)}
+ methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"}
+ json_resp = self._call_xenhost(methods[action], args)
+ resp = json.loads(json_resp)
+ return resp["power_action"]
+
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 _call_xenhost(self, method, arg_dict):
@@ -1051,7 +1063,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 49ae2623e..a1b5dba27 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -332,6 +332,10 @@ class XenAPIConnection(driver.ComputeDriver):
True, run the update first."""
return self.HostState.get_host_stats(refresh=refresh)
+ def host_power_action(self, host, action):
+ """Reboots or shuts down the host."""
+ return self._vmops.host_power_action(host, action)
+
def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances."""
return self._vmops.set_host_enabled(host, enabled)
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
index 873d1fe63..a28f5abfb 100755
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost
@@ -104,6 +104,7 @@ def set_host_enabled(self, arg_dict):
return {"status": status}
+<<<<<<< TREE
def _write_config_dict(dct):
conf_file = file(config_file_path, "w")
json.dump(dct, conf_file)
@@ -129,8 +130,14 @@ def _get_config_dict():
@jsonify
def get_config(self, arg_dict):
+ """Return the value stored for the specified key, or None if no match."""
conf = _get_config_dict()
- key = arg_dict["key"]
+ params = arg_dict["params"]
+ try:
+ dct = json.loads(params)
+ except Exception, e:
+ dct = params
+ key = dct["key"]
ret = conf.get(key)
if ret is None:
# Can't jsonify None
@@ -140,13 +147,62 @@ def get_config(self, arg_dict):
@jsonify
def set_config(self, arg_dict):
+ """Write the specified key/value pair, overwriting any existing value."""
conf = _get_config_dict()
- key = arg_dict["key"]
- val = arg_dict["value"]
- conf.update({key: val})
+ params = arg_dict["params"]
+ try:
+ dct = json.loads(params)
+ except Exception, e:
+ dct = params
+ key = dct["key"]
+ val = dct["value"]
+ if val is None:
+ # Delete the key, if present
+ conf.pop(key, None)
+ else:
+ conf.update({key: val})
_write_config_dict(conf)
+def _power_action(action):
+ host_uuid = _get_host_uuid()
+ # 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", "startup": "xe host-power-on",
+ "shutdown": "xe host-shutdown"}
+ result = _run_command(cmds[action])
+ # Should be empty string
+ if result:
+ raise pluginlib.PluginError(result)
+ return {"power_action": action}
+
+
+@jsonify
+def host_reboot(self, arg_dict):
+ """Reboots the host."""
+ return _power_action("reboot")
+
+
+@jsonify
+def host_shutdown(self, arg_dict):
+ """Reboots the host."""
+ return _power_action("shutdown")
+
+
+@jsonify
+def host_start(self, arg_dict):
+ """Starts the host. NOTE: Currently not feasible, since the host
+ runs on the same machine as Xen.
+ """
+ return _power_action("startup")
+
+
@jsonify
def host_data(self, arg_dict):
"""Runs the commands on the xenstore host to return the current status
@@ -264,5 +320,8 @@ if __name__ == "__main__":
XenAPIPlugin.dispatch(
{"host_data": host_data,
"set_host_enabled": set_host_enabled,
+ "host_shutdown": host_shutdown,
+ "host_reboot": host_reboot,
+ "host_start": host_start,
"get_config": get_config,
"set_config": set_config})