Merge lp:~ed-leafe/nova/powerstate into lp:~hudson-openstack/nova/trunk
- powerstate
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
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.
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_
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(
For reboot, yeah, probably. Since I'm not using libvert, though, I left that for others to code and test. :)
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?
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.
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_
These are somewhat drastic suggestions though..
I watched you demonstrate this and the code looks alright otherwise.
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_
Trey Morris (tr3buchet) wrote : | # |
I definitely like this better. The admin_only decorator is way better than checking FLAGS.allow_
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.
Ed Leafe (ed-leafe) wrote : | # |
> I definitely like this better. The admin_only decorator is way better than
> checking FLAGS.allow_
> 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.
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
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.
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
> 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.
Trey Morris (tr3buchet) wrote : | # |
alrighty, good to go on my end!
Josh Kearney (jk0) wrote : | # |
Everything looks good except for one little thing:
275 + except TypeError as e:
There are two spaces before as.
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_
> 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_
do stuff
In that case,
110 + return self._call_
111 + instance_id=None, host=host, params={"action": action})
would become:
return self._call_
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?
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_
> > 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.
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_
> 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.
Preview Diff
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}) |
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? "sudo", "shutdown", "-r", "now")
Also, if i understand the code correctly, implementing it for libvirt it should be as simple as utils.execute(