Merge lp:~cerberus/nova/lp853573 into lp:~hudson-openstack/nova/trunk

Proposed by Matt Dietz
Status: Needs review
Proposed branch: lp:~cerberus/nova/lp853573
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 278 lines (+203/-0)
8 files modified
nova/api/openstack/contrib/instanceactions.py (+86/-0)
nova/compute/api.py (+6/-0)
nova/compute/manager.py (+6/-0)
nova/tests/api/openstack/contrib/test_instance_actions.py (+92/-0)
nova/tests/api/openstack/test_extensions.py (+1/-0)
nova/virt/driver.py (+4/-0)
nova/virt/xenapi/vmops.py (+4/-0)
nova/virt/xenapi_conn.py (+4/-0)
To merge this branch: bzr merge lp:~cerberus/nova/lp853573
Reviewer Review Type Date Requested Status
Sandy Walsh (community) Needs Information
Review via email: mp+76272@code.launchpad.net

Description of the change

Fixes lp853573 for XenServer only

Implements an OS API extension that allows a user to power back on an instance that was shutdown through other means.

To post a comment you must log in.
lp:~cerberus/nova/lp853573 updated
1593. By Matt Dietz

Failed the inane extension list test

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Shouldn't HyperV be pass'ed as well?

+47, you're using routing_get(), but don't have any decorators for redirecting the call the child zones (hmm, actually, haven't tried /server/XXX/action or extensions with rerouting, so it could be a fun investigation). So, either it should use .get() or use the decorators.

That get_updated() call seems like a recipe for debugging down the road. I know I'll never remember to update that method on a bug fix. I know, not your problem.

Otherwise, lgtm

review: Needs Fixing
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Oh, should this be an admin-only operation? Or do you need admin context to perform it?

review: Needs Information

Unmerged revisions

1593. By Matt Dietz

Failed the inane extension list test

1592. By Matt Dietz

Merge from trunk

1591. By Matt Dietz

Merge from remote

1590. By Matt Dietz

Power on action extension with passing unit tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'nova/api/openstack/contrib/instanceactions.py'
2--- nova/api/openstack/contrib/instanceactions.py 1970-01-01 00:00:00 +0000
3+++ nova/api/openstack/contrib/instanceactions.py 2011-09-20 18:37:25 +0000
4@@ -0,0 +1,86 @@
5+# vim: tabstop=4 shiftwidth=4 softtabstop=4
6+
7+# Copyright 2011 OpenStack LLC.
8+#
9+# Licensed under the Apache License, Version 2.0 (the "License"); you may
10+# not use this file except in compliance with the License. You may obtain
11+# a copy of the License at
12+#
13+# http://www.apache.org/licenses/LICENSE-2.0
14+#
15+# Unless required by applicable law or agreed to in writing, software
16+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18+# License for the specific language governing permissions and limitations
19+# under the License
20+
21+import json
22+
23+from webob import exc
24+import webob
25+
26+from nova import compute
27+from nova import exception
28+import nova.image
29+from nova import log as logging
30+from nova import network
31+from nova import rpc
32+from nova.api.openstack import faults
33+from nova.api.openstack import extensions
34+from nova.api.openstack import wsgi
35+
36+LOG = logging.getLogger('nova.api.openstack.contrib.instanceactions')
37+
38+class InstanceActionsController(object):
39+ def __init__(self):
40+ self.compute_api = compute.API()
41+
42+ def create(self, req, server_id, body=None):
43+ if not body:
44+ return faults.Fault(exc.HTTPUnprocessableEntity())
45+ context = req.environ['nova.context']
46+ try:
47+ server = self.compute_api.routing_get(context, server_id)
48+ except exception.NotFound:
49+ explanation = _("Server not found.")
50+ return faults.Fault(exc.HTTPNotFound(explanation=explanation))
51+
52+ action_type = body['server']['action']
53+ if action_type == 'poweron':
54+ self.compute_api.power_on_instance(context, server_id)
55+ else:
56+ fault_message = "power_on is the only supported action"
57+ return faults.Fault(exc.HTTPUnprocessableEntity(
58+ explanation=fault_message))
59+
60+ return exc.HTTPNoContent()
61+
62+
63+class Instanceactions(extensions.ExtensionDescriptor):
64+ def __init__(self):
65+ super(Instanceactions, self).__init__()
66+
67+ def get_name(self):
68+ return "InstanceActions"
69+
70+ def get_alias(self):
71+ return "OS-INST-ACT"
72+
73+ def get_description(self):
74+ return "Extra Instance Actions support"
75+
76+ def get_namespace(self):
77+ return "http://docs.openstack.org/ext/instance_actions/api/v1.1"
78+
79+ def get_updated(self):
80+ return "2011-09-18T00:00:00+00:00"
81+
82+ def get_resources(self):
83+ return self._instance_extension_controller()
84+
85+ def _instance_extension_controller(self):
86+ res = extensions.ResourceExtension(
87+ 'os-instance-actions',
88+ controller=InstanceActionsController(),
89+ parent=dict(member_name='server', collection_name='servers'))
90+ return [res]
91
92=== modified file 'nova/compute/api.py'
93--- nova/compute/api.py 2011-09-19 21:53:17 +0000
94+++ nova/compute/api.py 2011-09-20 18:37:25 +0000
95@@ -1264,6 +1264,12 @@
96 context,
97 instance_id)
98
99+ def power_on_instance(self, context, instance_id):
100+ """Turns the instance on if it was turned off previously"""
101+ return self._call_compute_message("power_on_instance",
102+ context,
103+ instance_id)
104+
105 def get_actions(self, context, instance_id):
106 """Retrieve actions for the given instance."""
107 return self.db.instance_get_actions(context, instance_id)
108
109=== modified file 'nova/compute/manager.py'
110--- nova/compute/manager.py 2011-09-19 15:25:00 +0000
111+++ nova/compute/manager.py 2011-09-20 18:37:25 +0000
112@@ -1115,6 +1115,12 @@
113 return self.driver.get_diagnostics(instance_ref)
114
115 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
116+ def power_on_instance(self, context, instance_id):
117+ """Turns the instance on if it was turned off previously"""
118+ instance_ref = self.db.instance_get(context, instance_id)
119+ return self.driver.power_on(instance_ref)
120+
121+ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
122 @checks_instance_lock
123 def suspend_instance(self, context, instance_id):
124 """Suspend the given instance."""
125
126=== added file 'nova/tests/api/openstack/contrib/test_instance_actions.py'
127--- nova/tests/api/openstack/contrib/test_instance_actions.py 1970-01-01 00:00:00 +0000
128+++ nova/tests/api/openstack/contrib/test_instance_actions.py 2011-09-20 18:37:25 +0000
129@@ -0,0 +1,92 @@
130+# Copyright 2011 OpenStack LLC.
131+# All Rights Reserved.
132+#
133+# Licensed under the Apache License, Version 2.0 (the "License"); you may
134+# not use this file except in compliance with the License. You may obtain
135+# a copy of the License at
136+#
137+# http://www.apache.org/licenses/LICENSE-2.0
138+#
139+# Unless required by applicable law or agreed to in writing, software
140+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
141+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
142+# License for the specific language governing permissions and limitations
143+# under the License.
144+
145+import json
146+import webob
147+
148+from nova import compute
149+from nova import exception
150+from nova import test
151+from nova.api.openstack.contrib.instanceactions import \
152+ InstanceActionsController
153+from nova.tests.api.openstack import fakes
154+
155+
156+class InstanceActionTest(test.TestCase):
157+ def test_power_on(self):
158+ self.called = False
159+
160+ def fake_compute_get(*args, **kwargs):
161+ return {}
162+
163+ def fake_compute_power_on(*args, **kwargs):
164+ self.called = True
165+
166+ self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
167+ self.stubs.Set(compute.api.API, 'power_on_instance',
168+ fake_compute_power_on)
169+ req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
170+ req.headers['Accept'] = 'application/json'
171+ req.headers['Content-Type'] = 'application/json'
172+ req.method = 'POST'
173+ body = {'server': {'action': 'poweron'}}
174+ req.body = json.dumps(body)
175+ res = req.get_response(fakes.wsgi_app())
176+ self.assertEqual(res.status_int, 204)
177+ self.assertEqual(self.called, True)
178+
179+ def test_action_bad_server_id_fails(self):
180+ self.called = False
181+
182+ def fake_compute_get(*args, **kwargs):
183+ raise exception.NotFound()
184+
185+ def fake_compute_power_on(*args, **kwargs):
186+ self.called = True
187+
188+ self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
189+ self.stubs.Set(compute.api.API, 'power_on_instance',
190+ fake_compute_power_on)
191+ req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
192+ req.headers['Accept'] = 'application/json'
193+ req.headers['Content-Type'] = 'application/json'
194+ req.method = 'POST'
195+ body = {'server': {'action': 'poweron'}}
196+ req.body = json.dumps(body)
197+ res = req.get_response(fakes.wsgi_app())
198+ self.assertEqual(res.status_int, 404)
199+ self.assertEqual(self.called, False)
200+
201+ def test_bad_action_fails(self):
202+ self.called = False
203+
204+ def fake_compute_get(*args, **kwargs):
205+ return {}
206+
207+ def fake_compute_power_on(*args, **kwargs):
208+ self.called = True
209+
210+ self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
211+ self.stubs.Set(compute.api.API, 'power_on_instance',
212+ fake_compute_power_on)
213+ req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
214+ req.headers['Accept'] = 'application/json'
215+ req.headers['Content-Type'] = 'application/json'
216+ req.method = 'POST'
217+ body = {'server': {'action': 'derp'}}
218+ req.body = json.dumps(body)
219+ res = req.get_response(fakes.wsgi_app())
220+ self.assertEqual(res.status_int, 422)
221+ self.assertEqual(self.called, False)
222
223=== modified file 'nova/tests/api/openstack/test_extensions.py'
224--- nova/tests/api/openstack/test_extensions.py 2011-09-14 15:54:56 +0000
225+++ nova/tests/api/openstack/test_extensions.py 2011-09-20 18:37:25 +0000
226@@ -91,6 +91,7 @@
227 "Floating_ips",
228 "Fox In Socks",
229 "Hosts",
230+ "InstanceActions",
231 "Keypairs",
232 "Multinic",
233 "Quotas",
234
235=== modified file 'nova/virt/driver.py'
236--- nova/virt/driver.py 2011-09-15 18:44:49 +0000
237+++ nova/virt/driver.py 2011-09-20 18:37:25 +0000
238@@ -196,6 +196,10 @@
239 # TODO(Vek): Need to pass context in for access to auth_token
240 raise NotImplementedError()
241
242+ def power_on(self, instance):
243+ """Turns the instance on if it was previously turned off"""
244+ raise NotImplementedError()
245+
246 def get_host_ip_addr(self):
247 """
248 Retrieves the IP address of the dom0
249
250=== modified file 'nova/virt/xenapi/vmops.py'
251--- nova/virt/xenapi/vmops.py 2011-09-19 15:25:00 +0000
252+++ nova/virt/xenapi/vmops.py 2011-09-20 18:37:25 +0000
253@@ -1074,6 +1074,10 @@
254 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
255 return VMHelper.compile_diagnostics(self._session, vm_rec)
256
257+ def power_on(self, instance):
258+ """Turns the instance on if it was previously turned off"""
259+ return self._start(instance)
260+
261 def get_console_output(self, instance):
262 """Return snapshot of console."""
263 # TODO: implement this to fix pylint!
264
265=== modified file 'nova/virt/xenapi_conn.py'
266--- nova/virt/xenapi_conn.py 2011-09-15 18:44:49 +0000
267+++ nova/virt/xenapi_conn.py 2011-09-20 18:37:25 +0000
268@@ -277,6 +277,10 @@
269 """Return data about VM diagnostics"""
270 return self._vmops.get_diagnostics(instance)
271
272+ def power_on(self, instance):
273+ """Turns the instance on if it was previously turned off"""
274+ return self._vmops.power_on(instance)
275+
276 def get_console_output(self, instance):
277 """Return snapshot of console"""
278 return self._vmops.get_console_output(instance)