Merge lp:~ed-leafe/nova/powerstate into lp:~hudson-openstack/nova/trunk

Proposed by Ed Leafe
Status: Merged
Approved by: Ed Leafe
Approved revision: 1359
Merged at revision: 1394
Proposed branch: lp:~ed-leafe/nova/powerstate
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 376 lines (+180/-10)
13 files modified
nova/api/openstack/contrib/admin_only.py (+30/-0)
nova/api/openstack/contrib/hosts.py (+29/-3)
nova/compute/api.py (+6/-1)
nova/compute/manager.py (+6/-2)
nova/tests/test_hosts.py (+18/-0)
nova/virt/driver.py (+4/-0)
nova/virt/fake.py (+4/-0)
nova/virt/hyperv.py (+4/-0)
nova/virt/libvirt/connection.py (+4/-0)
nova/virt/vmwareapi_conn.py (+4/-0)
nova/virt/xenapi/vmops.py (+15/-3)
nova/virt/xenapi_conn.py (+13/-0)
plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost (+43/-1)
To merge this branch: bzr merge lp:~ed-leafe/nova/powerstate
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Josh Kearney (community) Approve
Trey Morris (community) Approve
Vish Ishaya (community) Needs Information
Review via email: mp+70230@code.launchpad.net

Description of the change

This branch adds additional capability to the hosts API extension. The new options allow an admin to reboot or shutdown a host. I also added code to hide this extension if the --allow-admin-api is False, as regular users should have no access to host API calls.

To post a comment you must log in.
Revision history for this message
Vish Ishaya (vishvananda) wrote :

79 -# vim: tabstop=4 shiftwidth=4 softtabstop=4
80 +#: tabstop=4 shiftwidth=4 softtabstop=4

That looks accidental.

I find the terminology a little confusing. What is the difference between set_power_state and set_host_powerstate?
Also, if i understand the code correctly, implementing it for libvirt it should be as simple as utils.execute("sudo", "shutdown", "-r", "now")

review: Needs Information
Revision history for this message
Ed Leafe (ed-leafe) wrote :

> 79 -# vim: tabstop=4 shiftwidth=4 softtabstop=4
> 80 +#: tabstop=4 shiftwidth=4 softtabstop=4
>
> That looks accidental.

It sure wasn't intentional. Fixed.

> I find the terminology a little confusing. What is the difference between
> set_power_state and set_host_powerstate?

Ugh, that's from an earlier version of the branch that I merged. There was already some stuff for VMs that used the term 'power_state', so I changed them in the newer version to be explicit that they were concerned with the host's power state. I'll have to clean that up and re-propose for merging.

> Also, if i understand the code correctly, implementing it for libvirt it
> should be as simple as utils.execute("sudo", "shutdown", "-r", "now")

For reboot, yeah, probably. Since I'm not using libvert, though, I left that for others to code and test. :)

Revision history for this message
Soren Hansen (soren) wrote :

It seems odd to me to think of "reboot" as a state.

Also, I think we should aim to add a get_* method for every set_* method, but reading back a "shutdown" or "reboot" state sounds really strange. :)

Maybe a more RPC-like interface would be in order?

Revision history for this message
Ed Leafe (ed-leafe) wrote :

> It seems odd to me to think of "reboot" as a state.

Would it help to think of it as a state change? :)

> Also, I think we should aim to add a get_* method for every set_* method, but
> reading back a "shutdown" or "reboot" state sounds really strange. :)

That's probably why dogmatic coding is a bad thing. :)

> Maybe a more RPC-like interface would be in order?

The request was for a RESTful API for managing the hosts for admins. Presumably then tools will be developed around that API. The tools themselves will likely be developed by each company that uses OpenStack for their own needs and deployment details. If this were for an action that would be carried out as part of normal Nova activity, then yes, an RPC interface might be more appropriate, but as this deals with the physical hosts and not the VMs, we went the RESTful API route.

Revision history for this message
Trey Morris (tr3buchet) wrote :

I'm with soren, reboot isn't really a state, I see it as an action.
Let's say you take action reboot, the host goes in to state "shutdown" until it comes back up and then it's "online" or i guess maybe "enabled" from the looks of it. It doesn't look like we track host state as we do vm states. Unless I'm mistaken hosts are either enabled or not.

I like the idea of specific actions for reboot, shutdown, and start (similar to _set_enabled_status) instead of a single set_powerstate action. It just seems more accurate and more simple.

These are somewhat drastic suggestions though..

I watched you demonstrate this and the code looks alright otherwise.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

OK, I've changed from the PUT approach to an action-based API. I've also changed all references to 'host_power_state' to 'host_power_action'.

Revision history for this message
Trey Morris (tr3buchet) wrote :

I definitely like this better. The admin_only decorator is way better than checking FLAGS.allow_admin_api. I wonder if there is a way to decorate each exposed function with it instead of get_resources().

Out of curiosity, why keep power_action at all? Why combine only reboot and shutdown into power_action? If all of the actions were going to use power_action, it makes sense, but all of your docstrings say "reboot or shutdown". Maybe it would read better if the docstrings were more accurate and startup used power_action as well.

I also think that raising when calling startup should be moved in the virt/connection area. API shouldn't claim to know underlying technology and block the request, the underlying technology should raise if it can't complete the call.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

> I definitely like this better. The admin_only decorator is way better than
> checking FLAGS.allow_admin_api. I wonder if there is a way to decorate each
> exposed function with it instead of get_resources().

Probably not, but that's a good thing - we shouldn't be mixing admin-only stuff with general user functionality - too much of a chance for someone down the line to confuse which is which. The idea is that if this is not running in an admin situation, the module will not get loaded at all.

> Out of curiosity, why keep power_action at all? Why combine only reboot and
> shutdown into power_action? If all of the actions were going to use
> power_action, it makes sense, but all of your docstrings say "reboot or
> shutdown". Maybe it would read better if the docstrings were more accurate and
> startup used power_action as well.

OK, I changed the docstrings to allow for all the actions.

> I also think that raising when calling startup should be moved in the
> virt/connection area. API shouldn't claim to know underlying technology and
> block the request, the underlying technology should raise if it can't complete
> the call.

Good catch - done.

Revision history for this message
Trey Morris (tr3buchet) wrote :

 > Probably not, but that's a good thing - we shouldn't be mixing admin-only stuff with general user
 > functionality - too much of a chance for someone down the line to confuse which is which. The idea
 > is that if this is not running in an admin situation, the module will not get loaded at all.

we're saying the same thing here. Would be nice if we could decorate the functions as @admin_api so that right there with the function definition you specify it's going to be a part of the admin api. Then get_resources() could always return all of the functions, excepting those with @admin_only decorators when the admin api isn't specified, which in this case would turn out to be [].

 > Good catch - done.

why not make the non-xen hypervisors raise NotImplemented instead of passing in virt/hypervisor/connection.py? This way it will be logged when you try to perform action that isn't underneath the hood, otherwise how would you know?

Revision history for this message
Trey Morris (tr3buchet) wrote :

Just want to add, I don't think you should come up with a function decorator for @admin_only as I described, I was just thinking out loud i guess.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

> > Probably not, but that's a good thing - we shouldn't be mixing admin-only
> stuff with general user
> > functionality - too much of a chance for someone down the line to confuse
> which is which. The idea
> > is that if this is not running in an admin situation, the module will not
> get loaded at all.
>
> we're saying the same thing here. Would be nice if we could decorate the
> functions as @admin_api so that right there with the function definition you
> specify it's going to be a part of the admin api. Then get_resources() could
> always return all of the functions, excepting those with @admin_only
> decorators when the admin api isn't specified, which in this case would turn
> out to be [].

Again, I think that mixing the two in a single extension is a poor design. It would be much safer and cleaner to create two separate extensions if you needed both public and admin functionality.

> > Good catch - done.
>
> why not make the non-xen hypervisors raise NotImplemented instead of passing
> in virt/hypervisor/connection.py? This way it will be logged when you try to
> perform action that isn't underneath the hood, otherwise how would you know?

Just following the pattern of all the other non-implemented methods. If you think that these should raise exceptions instead of simply passing, you should file a separate bug, and have them all changed together.

Revision history for this message
Trey Morris (tr3buchet) wrote :

alrighty, good to go on my end!

review: Approve
Revision history for this message
Josh Kearney (jk0) wrote :

Everything looks good except for one little thing:

275 + except TypeError as e:

There are two spaces before as.

review: Needs Fixing
Revision history for this message
Rick Harris (rconradharris) wrote :

Nice job, Ed. Some small nits:

> + """Starts the host. NOTE: Currently not feasible, since the host

Per Hacking, this should probably be NOTE(dabo):

> + except TypeError as e:

Two spaces between 'TypeError' and 'as'. (Surprised Pep8 didn't catch this)

> 124 + def host_power_action(self, context, instance_id=None, host=None,
> 125 + action=None):

Should this take an instance_id parameter?

Since compute api requires that 'host' and 'action' are specified, could we have manager.py mirror that?

    def host_power_action(self, context, host, action):
        do stuff

In that case,

110 + return self._call_compute_message("host_power_action", context,
111 + instance_id=None, host=host, params={"action": action})

would become:

    return self._call_compute_message("host_power_action", context,
        host=host, action=action)

> 32 + return []

Out of curiosity, should this raise a 4XX (maybe a 401 error) to let the user know that they are doing something wrong if they are trying to access an admin_only api call?

review: Needs Fixing
Revision history for this message
Ed Leafe (ed-leafe) wrote :

> > + """Starts the host. NOTE: Currently not feasible, since the host
>
> Per Hacking, this should probably be NOTE(dabo):

That was left over when copied from another part of the code. I just removed the NOTE:, since it really isn't needed.

> > + except TypeError as e:
>
> Two spaces between 'TypeError' and 'as'. (Surprised Pep8 didn't catch this)

You and me both. Fixed.

> > 124 + def host_power_action(self, context, instance_id=None, host=None,
> > 125 + action=None):
>
> Should this take an instance_id parameter?

I think that this was the result of copying the signatures of similar methods. I've removed the instance_id references from both manager and api.

> > 32 + return []
>
> Out of curiosity, should this raise a 4XX (maybe a 401 error) to let the user
> know that they are doing something wrong if they are trying to access an
> admin_only api call?

This isn't a runtime thing; it only comes into play when the extensions are loaded. It is analogous to the code for the non-extension admin API methods: if the flag is False, they simply aren't loaded. At runtime, if the user tries to call them, they get a 404, since they simply aren't defined.

Revision history for this message
Josh Kearney (jk0) wrote :

LGTM

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

> This isn't a runtime thing; it only comes into play when the extensions are loaded.

Gotcha.

> 124 + def host_power_action(self, context, instance_id=None, host=None,
> 125 + action=None):

The other part that slightly stood out was having host and action default to None. I would think those should be required arguments (?)

Otherwise, looks great, thanks for the fixes, Ed.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'nova/api/openstack/contrib/admin_only.py'
2--- nova/api/openstack/contrib/admin_only.py 1970-01-01 00:00:00 +0000
3+++ nova/api/openstack/contrib/admin_only.py 2011-08-08 17:37:25 +0000
4@@ -0,0 +1,30 @@
5+# Copyright (c) 2011 Openstack, LLC.
6+# All Rights Reserved.
7+#
8+# Licensed under the Apache License, Version 2.0 (the "License"); you may
9+# not use this file except in compliance with the License. You may obtain
10+# a copy of the License at
11+#
12+# http://www.apache.org/licenses/LICENSE-2.0
13+#
14+# Unless required by applicable law or agreed to in writing, software
15+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17+# License for the specific language governing permissions and limitations
18+# under the License.
19+
20+"""Decorator for limiting extensions that should be admin-only."""
21+
22+from functools import wraps
23+from nova import flags
24+FLAGS = flags.FLAGS
25+
26+
27+def admin_only(fnc):
28+ @wraps(fnc)
29+ def _wrapped(self, *args, **kwargs):
30+ if FLAGS.allow_admin_api:
31+ return fnc(self, *args, **kwargs)
32+ return []
33+ _wrapped.func_name = fnc.func_name
34+ return _wrapped
35
36=== modified file 'nova/api/openstack/contrib/hosts.py'
37--- nova/api/openstack/contrib/hosts.py 2011-07-06 16:53:08 +0000
38+++ nova/api/openstack/contrib/hosts.py 2011-08-08 17:37:25 +0000
39@@ -24,6 +24,7 @@
40 from nova.api.openstack import common
41 from nova.api.openstack import extensions
42 from nova.api.openstack import faults
43+from nova.api.openstack.contrib import admin_only
44 from nova.scheduler import api as scheduler_api
45
46
47@@ -70,7 +71,7 @@
48 key = raw_key.lower().strip()
49 val = raw_val.lower().strip()
50 # NOTE: (dabo) Right now only 'status' can be set, but other
51- # actions may follow.
52+ # settings may follow.
53 if key == "status":
54 if val[:6] in ("enable", "disabl"):
55 return self._set_enabled_status(req, id,
56@@ -89,8 +90,30 @@
57 LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
58 result = self.compute_api.set_host_enabled(context, host=host,
59 enabled=enabled)
60+ if result not in ("enabled", "disabled"):
61+ # An error message was returned
62+ raise webob.exc.HTTPBadRequest(explanation=result)
63 return {"host": host, "status": result}
64
65+ def _host_power_action(self, req, host, action):
66+ """Reboots, shuts down or powers up the host."""
67+ context = req.environ['nova.context']
68+ try:
69+ result = self.compute_api.host_power_action(context, host=host,
70+ action=action)
71+ except NotImplementedError as e:
72+ raise webob.exc.HTTPBadRequest(explanation=e.msg)
73+ return {"host": host, "power_action": result}
74+
75+ def startup(self, req, id):
76+ return self._host_power_action(req, host=id, action="startup")
77+
78+ def shutdown(self, req, id):
79+ return self._host_power_action(req, host=id, action="shutdown")
80+
81+ def reboot(self, req, id):
82+ return self._host_power_action(req, host=id, action="reboot")
83+
84
85 class Hosts(extensions.ExtensionDescriptor):
86 def get_name(self):
87@@ -108,7 +131,10 @@
88 def get_updated(self):
89 return "2011-06-29T00:00:00+00:00"
90
91+ @admin_only.admin_only
92 def get_resources(self):
93- resources = [extensions.ResourceExtension('os-hosts', HostController(),
94- collection_actions={'update': 'PUT'}, member_actions={})]
95+ resources = [extensions.ResourceExtension('os-hosts',
96+ HostController(), collection_actions={'update': 'PUT'},
97+ member_actions={"startup": "GET", "shutdown": "GET",
98+ "reboot": "GET"})]
99 return resources
100
101=== modified file 'nova/compute/api.py'
102--- nova/compute/api.py 2011-08-05 23:41:03 +0000
103+++ nova/compute/api.py 2011-08-08 17:37:25 +0000
104@@ -997,7 +997,12 @@
105 def set_host_enabled(self, context, host, enabled):
106 """Sets the specified host's ability to accept new instances."""
107 return self._call_compute_message("set_host_enabled", context,
108- instance_id=None, host=host, params={"enabled": enabled})
109+ host=host, params={"enabled": enabled})
110+
111+ def host_power_action(self, context, host, action):
112+ """Reboots, shuts down or powers up the host."""
113+ return self._call_compute_message("host_power_action", context,
114+ host=host, params={"action": action})
115
116 @scheduler_api.reroute_compute("diagnostics")
117 def get_diagnostics(self, context, instance_id):
118
119=== modified file 'nova/compute/manager.py'
120--- nova/compute/manager.py 2011-08-05 14:07:48 +0000
121+++ nova/compute/manager.py 2011-08-08 17:37:25 +0000
122@@ -958,8 +958,12 @@
123 result))
124
125 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
126- def set_host_enabled(self, context, instance_id=None, host=None,
127- enabled=None):
128+ def host_power_action(self, context, host=None, action=None):
129+ """Reboots, shuts down or powers up the host."""
130+ return self.driver.host_power_action(host, action)
131+
132+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
133+ def set_host_enabled(self, context, host=None, enabled=None):
134 """Sets the specified host's ability to accept new instances."""
135 return self.driver.set_host_enabled(host, enabled)
136
137
138=== modified file 'nova/tests/test_hosts.py'
139--- nova/tests/test_hosts.py 2011-07-06 17:14:46 +0000
140+++ nova/tests/test_hosts.py 2011-08-08 17:37:25 +0000
141@@ -48,6 +48,10 @@
142 return status
143
144
145+def stub_host_power_action(context, host, action):
146+ return action
147+
148+
149 class FakeRequest(object):
150 environ = {"nova.context": context.get_admin_context()}
151
152@@ -62,6 +66,8 @@
153 self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list)
154 self.stubs.Set(self.controller.compute_api, 'set_host_enabled',
155 stub_set_host_enabled)
156+ self.stubs.Set(self.controller.compute_api, 'host_power_action',
157+ stub_host_power_action)
158
159 def test_list_hosts(self):
160 """Verify that the compute hosts are returned."""
161@@ -87,6 +93,18 @@
162 result_c2 = self.controller.update(self.req, "host_c2", body=en_body)
163 self.assertEqual(result_c2["status"], "disabled")
164
165+ def test_host_startup(self):
166+ result = self.controller.startup(self.req, "host_c1")
167+ self.assertEqual(result["power_action"], "startup")
168+
169+ def test_host_shutdown(self):
170+ result = self.controller.shutdown(self.req, "host_c1")
171+ self.assertEqual(result["power_action"], "shutdown")
172+
173+ def test_host_reboot(self):
174+ result = self.controller.reboot(self.req, "host_c1")
175+ self.assertEqual(result["power_action"], "reboot")
176+
177 def test_bad_status_value(self):
178 bad_body = {"status": "bad"}
179 self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
180
181=== modified file 'nova/virt/driver.py'
182--- nova/virt/driver.py 2011-07-29 21:58:27 +0000
183+++ nova/virt/driver.py 2011-08-08 17:37:25 +0000
184@@ -282,6 +282,10 @@
185 # TODO(Vek): Need to pass context in for access to auth_token
186 raise NotImplementedError()
187
188+ def host_power_action(self, host, action):
189+ """Reboots, shuts down or powers up the host."""
190+ raise NotImplementedError()
191+
192 def set_host_enabled(self, host, enabled):
193 """Sets the specified host's ability to accept new instances."""
194 # TODO(Vek): Need to pass context in for access to auth_token
195
196=== modified file 'nova/virt/fake.py'
197--- nova/virt/fake.py 2011-08-02 21:45:57 +0000
198+++ nova/virt/fake.py 2011-08-08 17:37:25 +0000
199@@ -512,6 +512,10 @@
200 """Return fake Host Status of ram, disk, network."""
201 return self.host_status
202
203+ def host_power_action(self, host, action):
204+ """Reboots, shuts down or powers up the host."""
205+ pass
206+
207 def set_host_enabled(self, host, enabled):
208 """Sets the specified host's ability to accept new instances."""
209 pass
210
211=== modified file 'nova/virt/hyperv.py'
212--- nova/virt/hyperv.py 2011-07-29 21:58:27 +0000
213+++ nova/virt/hyperv.py 2011-08-08 17:37:25 +0000
214@@ -499,6 +499,10 @@
215 """See xenapi_conn.py implementation."""
216 pass
217
218+ def host_power_action(self, host, action):
219+ """Reboots, shuts down or powers up the host."""
220+ pass
221+
222 def set_host_enabled(self, host, enabled):
223 """Sets the specified host's ability to accept new instances."""
224 pass
225
226=== modified file 'nova/virt/libvirt/connection.py'
227--- nova/virt/libvirt/connection.py 2011-08-05 18:14:26 +0000
228+++ nova/virt/libvirt/connection.py 2011-08-08 17:37:25 +0000
229@@ -1562,6 +1562,10 @@
230 """See xenapi_conn.py implementation."""
231 pass
232
233+ def host_power_action(self, host, action):
234+ """Reboots, shuts down or powers up the host."""
235+ pass
236+
237 def set_host_enabled(self, host, enabled):
238 """Sets the specified host's ability to accept new instances."""
239 pass
240
241=== modified file 'nova/virt/vmwareapi_conn.py'
242--- nova/virt/vmwareapi_conn.py 2011-07-29 21:58:27 +0000
243+++ nova/virt/vmwareapi_conn.py 2011-08-08 17:37:25 +0000
244@@ -191,6 +191,10 @@
245 """This method is supported only by libvirt."""
246 return
247
248+ def host_power_action(self, host, action):
249+ """Reboots, shuts down or powers up the host."""
250+ pass
251+
252 def set_host_enabled(self, host, enabled):
253 """Sets the specified host's ability to accept new instances."""
254 pass
255
256=== modified file 'nova/virt/xenapi/vmops.py'
257--- nova/virt/xenapi/vmops.py 2011-08-04 21:04:21 +0000
258+++ nova/virt/xenapi/vmops.py 2011-08-08 17:37:25 +0000
259@@ -1031,11 +1031,23 @@
260 # TODO: implement this!
261 return 'http://fakeajaxconsole/fake_url'
262
263+ def host_power_action(self, host, action):
264+ """Reboots or shuts down the host."""
265+ args = {"action": json.dumps(action)}
266+ methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"}
267+ json_resp = self._call_xenhost(methods[action], args)
268+ resp = json.loads(json_resp)
269+ return resp["power_action"]
270+
271 def set_host_enabled(self, host, enabled):
272 """Sets the specified host's ability to accept new instances."""
273 args = {"enabled": json.dumps(enabled)}
274- json_resp = self._call_xenhost("set_host_enabled", args)
275- resp = json.loads(json_resp)
276+ xenapi_resp = self._call_xenhost("set_host_enabled", args)
277+ try:
278+ resp = json.loads(xenapi_resp)
279+ except TypeError as e:
280+ # Already logged; return the message
281+ return xenapi_resp.details[-1]
282 return resp["status"]
283
284 def _call_xenhost(self, method, arg_dict):
285@@ -1051,7 +1063,7 @@
286 #args={"params": arg_dict})
287 ret = self._session.wait_for_task(task, task_id)
288 except self.XenAPI.Failure as e:
289- ret = None
290+ ret = e
291 LOG.error(_("The call to %(method)s returned an error: %(e)s.")
292 % locals())
293 return ret
294
295=== modified file 'nova/virt/xenapi_conn.py'
296--- nova/virt/xenapi_conn.py 2011-08-05 14:07:48 +0000
297+++ nova/virt/xenapi_conn.py 2011-08-08 17:37:25 +0000
298@@ -332,6 +332,19 @@
299 True, run the update first."""
300 return self.HostState.get_host_stats(refresh=refresh)
301
302+ def host_power_action(self, host, action):
303+ """The only valid values for 'action' on XenServer are 'reboot' or
304+ 'shutdown', even though the API also accepts 'startup'. As this is
305+ not technically possible on XenServer, since the host is the same
306+ physical machine as the hypervisor, if this is requested, we need to
307+ raise an exception.
308+ """
309+ if action in ("reboot", "shutdown"):
310+ return self._vmops.host_power_action(host, action)
311+ else:
312+ msg = _("Host startup on XenServer is not supported.")
313+ raise NotImplementedError(msg)
314+
315 def set_host_enabled(self, host, enabled):
316 """Sets the specified host's ability to accept new instances."""
317 return self._vmops.set_host_enabled(host, enabled)
318
319=== modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost'
320--- plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost 2011-07-20 15:16:36 +0000
321+++ plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost 2011-08-08 17:37:25 +0000
322@@ -103,6 +103,45 @@
323 return {"status": status}
324
325
326+def _power_action(action):
327+ host_uuid = _get_host_uuid()
328+ # Host must be disabled first
329+ result = _run_command("xe host-disable")
330+ if result:
331+ raise pluginlib.PluginError(result)
332+ # All running VMs must be shutdown
333+ result = _run_command("xe vm-shutdown --multiple power-state=running")
334+ if result:
335+ raise pluginlib.PluginError(result)
336+ cmds = {"reboot": "xe host-reboot", "startup": "xe host-power-on",
337+ "shutdown": "xe host-shutdown"}
338+ result = _run_command(cmds[action])
339+ # Should be empty string
340+ if result:
341+ raise pluginlib.PluginError(result)
342+ return {"power_action": action}
343+
344+
345+@jsonify
346+def host_reboot(self, arg_dict):
347+ """Reboots the host."""
348+ return _power_action("reboot")
349+
350+
351+@jsonify
352+def host_shutdown(self, arg_dict):
353+ """Reboots the host."""
354+ return _power_action("shutdown")
355+
356+
357+@jsonify
358+def host_start(self, arg_dict):
359+ """Starts the host. Currently not feasible, since the host
360+ runs on the same machine as Xen.
361+ """
362+ return _power_action("startup")
363+
364+
365 @jsonify
366 def host_data(self, arg_dict):
367 """Runs the commands on the xenstore host to return the current status
368@@ -217,4 +256,7 @@
369 if __name__ == "__main__":
370 XenAPIPlugin.dispatch(
371 {"host_data": host_data,
372- "set_host_enabled": set_host_enabled})
373+ "set_host_enabled": set_host_enabled,
374+ "host_shutdown": host_shutdown,
375+ "host_reboot": host_reboot,
376+ "host_start": host_start})