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
=== added file 'nova/api/openstack/contrib/admin_only.py'
--- nova/api/openstack/contrib/admin_only.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/admin_only.py 2011-08-08 17:37:25 +0000
@@ -0,0 +1,30 @@
1# Copyright (c) 2011 Openstack, LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""Decorator for limiting extensions that should be admin-only."""
17
18from functools import wraps
19from nova import flags
20FLAGS = flags.FLAGS
21
22
23def admin_only(fnc):
24 @wraps(fnc)
25 def _wrapped(self, *args, **kwargs):
26 if FLAGS.allow_admin_api:
27 return fnc(self, *args, **kwargs)
28 return []
29 _wrapped.func_name = fnc.func_name
30 return _wrapped
031
=== modified file 'nova/api/openstack/contrib/hosts.py'
--- nova/api/openstack/contrib/hosts.py 2011-07-06 16:53:08 +0000
+++ nova/api/openstack/contrib/hosts.py 2011-08-08 17:37:25 +0000
@@ -24,6 +24,7 @@
24from nova.api.openstack import common24from nova.api.openstack import common
25from nova.api.openstack import extensions25from nova.api.openstack import extensions
26from nova.api.openstack import faults26from nova.api.openstack import faults
27from nova.api.openstack.contrib import admin_only
27from nova.scheduler import api as scheduler_api28from nova.scheduler import api as scheduler_api
2829
2930
@@ -70,7 +71,7 @@
70 key = raw_key.lower().strip()71 key = raw_key.lower().strip()
71 val = raw_val.lower().strip()72 val = raw_val.lower().strip()
72 # NOTE: (dabo) Right now only 'status' can be set, but other73 # NOTE: (dabo) Right now only 'status' can be set, but other
73 # actions may follow.74 # settings may follow.
74 if key == "status":75 if key == "status":
75 if val[:6] in ("enable", "disabl"):76 if val[:6] in ("enable", "disabl"):
76 return self._set_enabled_status(req, id,77 return self._set_enabled_status(req, id,
@@ -89,8 +90,30 @@
89 LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())90 LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
90 result = self.compute_api.set_host_enabled(context, host=host,91 result = self.compute_api.set_host_enabled(context, host=host,
91 enabled=enabled)92 enabled=enabled)
93 if result not in ("enabled", "disabled"):
94 # An error message was returned
95 raise webob.exc.HTTPBadRequest(explanation=result)
92 return {"host": host, "status": result}96 return {"host": host, "status": result}
9397
98 def _host_power_action(self, req, host, action):
99 """Reboots, shuts down or powers up the host."""
100 context = req.environ['nova.context']
101 try:
102 result = self.compute_api.host_power_action(context, host=host,
103 action=action)
104 except NotImplementedError as e:
105 raise webob.exc.HTTPBadRequest(explanation=e.msg)
106 return {"host": host, "power_action": result}
107
108 def startup(self, req, id):
109 return self._host_power_action(req, host=id, action="startup")
110
111 def shutdown(self, req, id):
112 return self._host_power_action(req, host=id, action="shutdown")
113
114 def reboot(self, req, id):
115 return self._host_power_action(req, host=id, action="reboot")
116
94117
95class Hosts(extensions.ExtensionDescriptor):118class Hosts(extensions.ExtensionDescriptor):
96 def get_name(self):119 def get_name(self):
@@ -108,7 +131,10 @@
108 def get_updated(self):131 def get_updated(self):
109 return "2011-06-29T00:00:00+00:00"132 return "2011-06-29T00:00:00+00:00"
110133
134 @admin_only.admin_only
111 def get_resources(self):135 def get_resources(self):
112 resources = [extensions.ResourceExtension('os-hosts', HostController(),136 resources = [extensions.ResourceExtension('os-hosts',
113 collection_actions={'update': 'PUT'}, member_actions={})]137 HostController(), collection_actions={'update': 'PUT'},
138 member_actions={"startup": "GET", "shutdown": "GET",
139 "reboot": "GET"})]
114 return resources140 return resources
115141
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-08-05 23:41:03 +0000
+++ nova/compute/api.py 2011-08-08 17:37:25 +0000
@@ -997,7 +997,12 @@
997 def set_host_enabled(self, context, host, enabled):997 def set_host_enabled(self, context, host, enabled):
998 """Sets the specified host's ability to accept new instances."""998 """Sets the specified host's ability to accept new instances."""
999 return self._call_compute_message("set_host_enabled", context,999 return self._call_compute_message("set_host_enabled", context,
1000 instance_id=None, host=host, params={"enabled": enabled})1000 host=host, params={"enabled": enabled})
1001
1002 def host_power_action(self, context, host, action):
1003 """Reboots, shuts down or powers up the host."""
1004 return self._call_compute_message("host_power_action", context,
1005 host=host, params={"action": action})
10011006
1002 @scheduler_api.reroute_compute("diagnostics")1007 @scheduler_api.reroute_compute("diagnostics")
1003 def get_diagnostics(self, context, instance_id):1008 def get_diagnostics(self, context, instance_id):
10041009
=== modified file 'nova/compute/manager.py'
--- nova/compute/manager.py 2011-08-05 14:07:48 +0000
+++ nova/compute/manager.py 2011-08-08 17:37:25 +0000
@@ -958,8 +958,12 @@
958 result))958 result))
959959
960 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())960 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
961 def set_host_enabled(self, context, instance_id=None, host=None,961 def host_power_action(self, context, host=None, action=None):
962 enabled=None):962 """Reboots, shuts down or powers up the host."""
963 return self.driver.host_power_action(host, action)
964
965 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
966 def set_host_enabled(self, context, host=None, enabled=None):
963 """Sets the specified host's ability to accept new instances."""967 """Sets the specified host's ability to accept new instances."""
964 return self.driver.set_host_enabled(host, enabled)968 return self.driver.set_host_enabled(host, enabled)
965969
966970
=== modified file 'nova/tests/test_hosts.py'
--- nova/tests/test_hosts.py 2011-07-06 17:14:46 +0000
+++ nova/tests/test_hosts.py 2011-08-08 17:37:25 +0000
@@ -48,6 +48,10 @@
48 return status48 return status
4949
5050
51def stub_host_power_action(context, host, action):
52 return action
53
54
51class FakeRequest(object):55class FakeRequest(object):
52 environ = {"nova.context": context.get_admin_context()}56 environ = {"nova.context": context.get_admin_context()}
5357
@@ -62,6 +66,8 @@
62 self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list)66 self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list)
63 self.stubs.Set(self.controller.compute_api, 'set_host_enabled',67 self.stubs.Set(self.controller.compute_api, 'set_host_enabled',
64 stub_set_host_enabled)68 stub_set_host_enabled)
69 self.stubs.Set(self.controller.compute_api, 'host_power_action',
70 stub_host_power_action)
6571
66 def test_list_hosts(self):72 def test_list_hosts(self):
67 """Verify that the compute hosts are returned."""73 """Verify that the compute hosts are returned."""
@@ -87,6 +93,18 @@
87 result_c2 = self.controller.update(self.req, "host_c2", body=en_body)93 result_c2 = self.controller.update(self.req, "host_c2", body=en_body)
88 self.assertEqual(result_c2["status"], "disabled")94 self.assertEqual(result_c2["status"], "disabled")
8995
96 def test_host_startup(self):
97 result = self.controller.startup(self.req, "host_c1")
98 self.assertEqual(result["power_action"], "startup")
99
100 def test_host_shutdown(self):
101 result = self.controller.shutdown(self.req, "host_c1")
102 self.assertEqual(result["power_action"], "shutdown")
103
104 def test_host_reboot(self):
105 result = self.controller.reboot(self.req, "host_c1")
106 self.assertEqual(result["power_action"], "reboot")
107
90 def test_bad_status_value(self):108 def test_bad_status_value(self):
91 bad_body = {"status": "bad"}109 bad_body = {"status": "bad"}
92 self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,110 self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
93111
=== modified file 'nova/virt/driver.py'
--- nova/virt/driver.py 2011-07-29 21:58:27 +0000
+++ nova/virt/driver.py 2011-08-08 17:37:25 +0000
@@ -282,6 +282,10 @@
282 # TODO(Vek): Need to pass context in for access to auth_token282 # TODO(Vek): Need to pass context in for access to auth_token
283 raise NotImplementedError()283 raise NotImplementedError()
284284
285 def host_power_action(self, host, action):
286 """Reboots, shuts down or powers up the host."""
287 raise NotImplementedError()
288
285 def set_host_enabled(self, host, enabled):289 def set_host_enabled(self, host, enabled):
286 """Sets the specified host's ability to accept new instances."""290 """Sets the specified host's ability to accept new instances."""
287 # TODO(Vek): Need to pass context in for access to auth_token291 # TODO(Vek): Need to pass context in for access to auth_token
288292
=== modified file 'nova/virt/fake.py'
--- nova/virt/fake.py 2011-08-02 21:45:57 +0000
+++ nova/virt/fake.py 2011-08-08 17:37:25 +0000
@@ -512,6 +512,10 @@
512 """Return fake Host Status of ram, disk, network."""512 """Return fake Host Status of ram, disk, network."""
513 return self.host_status513 return self.host_status
514514
515 def host_power_action(self, host, action):
516 """Reboots, shuts down or powers up the host."""
517 pass
518
515 def set_host_enabled(self, host, enabled):519 def set_host_enabled(self, host, enabled):
516 """Sets the specified host's ability to accept new instances."""520 """Sets the specified host's ability to accept new instances."""
517 pass521 pass
518522
=== modified file 'nova/virt/hyperv.py'
--- nova/virt/hyperv.py 2011-07-29 21:58:27 +0000
+++ nova/virt/hyperv.py 2011-08-08 17:37:25 +0000
@@ -499,6 +499,10 @@
499 """See xenapi_conn.py implementation."""499 """See xenapi_conn.py implementation."""
500 pass500 pass
501501
502 def host_power_action(self, host, action):
503 """Reboots, shuts down or powers up the host."""
504 pass
505
502 def set_host_enabled(self, host, enabled):506 def set_host_enabled(self, host, enabled):
503 """Sets the specified host's ability to accept new instances."""507 """Sets the specified host's ability to accept new instances."""
504 pass508 pass
505509
=== modified file 'nova/virt/libvirt/connection.py'
--- nova/virt/libvirt/connection.py 2011-08-05 18:14:26 +0000
+++ nova/virt/libvirt/connection.py 2011-08-08 17:37:25 +0000
@@ -1562,6 +1562,10 @@
1562 """See xenapi_conn.py implementation."""1562 """See xenapi_conn.py implementation."""
1563 pass1563 pass
15641564
1565 def host_power_action(self, host, action):
1566 """Reboots, shuts down or powers up the host."""
1567 pass
1568
1565 def set_host_enabled(self, host, enabled):1569 def set_host_enabled(self, host, enabled):
1566 """Sets the specified host's ability to accept new instances."""1570 """Sets the specified host's ability to accept new instances."""
1567 pass1571 pass
15681572
=== modified file 'nova/virt/vmwareapi_conn.py'
--- nova/virt/vmwareapi_conn.py 2011-07-29 21:58:27 +0000
+++ nova/virt/vmwareapi_conn.py 2011-08-08 17:37:25 +0000
@@ -191,6 +191,10 @@
191 """This method is supported only by libvirt."""191 """This method is supported only by libvirt."""
192 return192 return
193193
194 def host_power_action(self, host, action):
195 """Reboots, shuts down or powers up the host."""
196 pass
197
194 def set_host_enabled(self, host, enabled):198 def set_host_enabled(self, host, enabled):
195 """Sets the specified host's ability to accept new instances."""199 """Sets the specified host's ability to accept new instances."""
196 pass200 pass
197201
=== modified file 'nova/virt/xenapi/vmops.py'
--- nova/virt/xenapi/vmops.py 2011-08-04 21:04:21 +0000
+++ nova/virt/xenapi/vmops.py 2011-08-08 17:37:25 +0000
@@ -1031,11 +1031,23 @@
1031 # TODO: implement this!1031 # TODO: implement this!
1032 return 'http://fakeajaxconsole/fake_url'1032 return 'http://fakeajaxconsole/fake_url'
10331033
1034 def host_power_action(self, host, action):
1035 """Reboots or shuts down the host."""
1036 args = {"action": json.dumps(action)}
1037 methods = {"reboot": "host_reboot", "shutdown": "host_shutdown"}
1038 json_resp = self._call_xenhost(methods[action], args)
1039 resp = json.loads(json_resp)
1040 return resp["power_action"]
1041
1034 def set_host_enabled(self, host, enabled):1042 def set_host_enabled(self, host, enabled):
1035 """Sets the specified host's ability to accept new instances."""1043 """Sets the specified host's ability to accept new instances."""
1036 args = {"enabled": json.dumps(enabled)}1044 args = {"enabled": json.dumps(enabled)}
1037 json_resp = self._call_xenhost("set_host_enabled", args)1045 xenapi_resp = self._call_xenhost("set_host_enabled", args)
1038 resp = json.loads(json_resp)1046 try:
1047 resp = json.loads(xenapi_resp)
1048 except TypeError as e:
1049 # Already logged; return the message
1050 return xenapi_resp.details[-1]
1039 return resp["status"]1051 return resp["status"]
10401052
1041 def _call_xenhost(self, method, arg_dict):1053 def _call_xenhost(self, method, arg_dict):
@@ -1051,7 +1063,7 @@
1051 #args={"params": arg_dict})1063 #args={"params": arg_dict})
1052 ret = self._session.wait_for_task(task, task_id)1064 ret = self._session.wait_for_task(task, task_id)
1053 except self.XenAPI.Failure as e:1065 except self.XenAPI.Failure as e:
1054 ret = None1066 ret = e
1055 LOG.error(_("The call to %(method)s returned an error: %(e)s.")1067 LOG.error(_("The call to %(method)s returned an error: %(e)s.")
1056 % locals())1068 % locals())
1057 return ret1069 return ret
10581070
=== modified file 'nova/virt/xenapi_conn.py'
--- nova/virt/xenapi_conn.py 2011-08-05 14:07:48 +0000
+++ nova/virt/xenapi_conn.py 2011-08-08 17:37:25 +0000
@@ -332,6 +332,19 @@
332 True, run the update first."""332 True, run the update first."""
333 return self.HostState.get_host_stats(refresh=refresh)333 return self.HostState.get_host_stats(refresh=refresh)
334334
335 def host_power_action(self, host, action):
336 """The only valid values for 'action' on XenServer are 'reboot' or
337 'shutdown', even though the API also accepts 'startup'. As this is
338 not technically possible on XenServer, since the host is the same
339 physical machine as the hypervisor, if this is requested, we need to
340 raise an exception.
341 """
342 if action in ("reboot", "shutdown"):
343 return self._vmops.host_power_action(host, action)
344 else:
345 msg = _("Host startup on XenServer is not supported.")
346 raise NotImplementedError(msg)
347
335 def set_host_enabled(self, host, enabled):348 def set_host_enabled(self, host, enabled):
336 """Sets the specified host's ability to accept new instances."""349 """Sets the specified host's ability to accept new instances."""
337 return self._vmops.set_host_enabled(host, enabled)350 return self._vmops.set_host_enabled(host, enabled)
338351
=== modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost'
--- plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost 2011-07-20 15:16:36 +0000
+++ plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost 2011-08-08 17:37:25 +0000
@@ -103,6 +103,45 @@
103 return {"status": status}103 return {"status": status}
104104
105105
106def _power_action(action):
107 host_uuid = _get_host_uuid()
108 # Host must be disabled first
109 result = _run_command("xe host-disable")
110 if result:
111 raise pluginlib.PluginError(result)
112 # All running VMs must be shutdown
113 result = _run_command("xe vm-shutdown --multiple power-state=running")
114 if result:
115 raise pluginlib.PluginError(result)
116 cmds = {"reboot": "xe host-reboot", "startup": "xe host-power-on",
117 "shutdown": "xe host-shutdown"}
118 result = _run_command(cmds[action])
119 # Should be empty string
120 if result:
121 raise pluginlib.PluginError(result)
122 return {"power_action": action}
123
124
125@jsonify
126def host_reboot(self, arg_dict):
127 """Reboots the host."""
128 return _power_action("reboot")
129
130
131@jsonify
132def host_shutdown(self, arg_dict):
133 """Reboots the host."""
134 return _power_action("shutdown")
135
136
137@jsonify
138def host_start(self, arg_dict):
139 """Starts the host. Currently not feasible, since the host
140 runs on the same machine as Xen.
141 """
142 return _power_action("startup")
143
144
106@jsonify145@jsonify
107def host_data(self, arg_dict):146def host_data(self, arg_dict):
108 """Runs the commands on the xenstore host to return the current status147 """Runs the commands on the xenstore host to return the current status
@@ -217,4 +256,7 @@
217if __name__ == "__main__":256if __name__ == "__main__":
218 XenAPIPlugin.dispatch(257 XenAPIPlugin.dispatch(
219 {"host_data": host_data,258 {"host_data": host_data,
220 "set_host_enabled": set_host_enabled})259 "set_host_enabled": set_host_enabled,
260 "host_shutdown": host_shutdown,
261 "host_reboot": host_reboot,
262 "host_start": host_start})