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
=== added file 'nova/api/openstack/contrib/instanceactions.py'
--- nova/api/openstack/contrib/instanceactions.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/instanceactions.py 2011-09-20 18:37:25 +0000
@@ -0,0 +1,86 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License
16
17import json
18
19from webob import exc
20import webob
21
22from nova import compute
23from nova import exception
24import nova.image
25from nova import log as logging
26from nova import network
27from nova import rpc
28from nova.api.openstack import faults
29from nova.api.openstack import extensions
30from nova.api.openstack import wsgi
31
32LOG = logging.getLogger('nova.api.openstack.contrib.instanceactions')
33
34class InstanceActionsController(object):
35 def __init__(self):
36 self.compute_api = compute.API()
37
38 def create(self, req, server_id, body=None):
39 if not body:
40 return faults.Fault(exc.HTTPUnprocessableEntity())
41 context = req.environ['nova.context']
42 try:
43 server = self.compute_api.routing_get(context, server_id)
44 except exception.NotFound:
45 explanation = _("Server not found.")
46 return faults.Fault(exc.HTTPNotFound(explanation=explanation))
47
48 action_type = body['server']['action']
49 if action_type == 'poweron':
50 self.compute_api.power_on_instance(context, server_id)
51 else:
52 fault_message = "power_on is the only supported action"
53 return faults.Fault(exc.HTTPUnprocessableEntity(
54 explanation=fault_message))
55
56 return exc.HTTPNoContent()
57
58
59class Instanceactions(extensions.ExtensionDescriptor):
60 def __init__(self):
61 super(Instanceactions, self).__init__()
62
63 def get_name(self):
64 return "InstanceActions"
65
66 def get_alias(self):
67 return "OS-INST-ACT"
68
69 def get_description(self):
70 return "Extra Instance Actions support"
71
72 def get_namespace(self):
73 return "http://docs.openstack.org/ext/instance_actions/api/v1.1"
74
75 def get_updated(self):
76 return "2011-09-18T00:00:00+00:00"
77
78 def get_resources(self):
79 return self._instance_extension_controller()
80
81 def _instance_extension_controller(self):
82 res = extensions.ResourceExtension(
83 'os-instance-actions',
84 controller=InstanceActionsController(),
85 parent=dict(member_name='server', collection_name='servers'))
86 return [res]
087
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-09-19 21:53:17 +0000
+++ nova/compute/api.py 2011-09-20 18:37:25 +0000
@@ -1264,6 +1264,12 @@
1264 context,1264 context,
1265 instance_id)1265 instance_id)
12661266
1267 def power_on_instance(self, context, instance_id):
1268 """Turns the instance on if it was turned off previously"""
1269 return self._call_compute_message("power_on_instance",
1270 context,
1271 instance_id)
1272
1267 def get_actions(self, context, instance_id):1273 def get_actions(self, context, instance_id):
1268 """Retrieve actions for the given instance."""1274 """Retrieve actions for the given instance."""
1269 return self.db.instance_get_actions(context, instance_id)1275 return self.db.instance_get_actions(context, instance_id)
12701276
=== modified file 'nova/compute/manager.py'
--- nova/compute/manager.py 2011-09-19 15:25:00 +0000
+++ nova/compute/manager.py 2011-09-20 18:37:25 +0000
@@ -1115,6 +1115,12 @@
1115 return self.driver.get_diagnostics(instance_ref)1115 return self.driver.get_diagnostics(instance_ref)
11161116
1117 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())1117 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1118 def power_on_instance(self, context, instance_id):
1119 """Turns the instance on if it was turned off previously"""
1120 instance_ref = self.db.instance_get(context, instance_id)
1121 return self.driver.power_on(instance_ref)
1122
1123 @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
1118 @checks_instance_lock1124 @checks_instance_lock
1119 def suspend_instance(self, context, instance_id):1125 def suspend_instance(self, context, instance_id):
1120 """Suspend the given instance."""1126 """Suspend the given instance."""
11211127
=== added file 'nova/tests/api/openstack/contrib/test_instance_actions.py'
--- nova/tests/api/openstack/contrib/test_instance_actions.py 1970-01-01 00:00:00 +0000
+++ nova/tests/api/openstack/contrib/test_instance_actions.py 2011-09-20 18:37:25 +0000
@@ -0,0 +1,92 @@
1# Copyright 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
16import json
17import webob
18
19from nova import compute
20from nova import exception
21from nova import test
22from nova.api.openstack.contrib.instanceactions import \
23 InstanceActionsController
24from nova.tests.api.openstack import fakes
25
26
27class InstanceActionTest(test.TestCase):
28 def test_power_on(self):
29 self.called = False
30
31 def fake_compute_get(*args, **kwargs):
32 return {}
33
34 def fake_compute_power_on(*args, **kwargs):
35 self.called = True
36
37 self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
38 self.stubs.Set(compute.api.API, 'power_on_instance',
39 fake_compute_power_on)
40 req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
41 req.headers['Accept'] = 'application/json'
42 req.headers['Content-Type'] = 'application/json'
43 req.method = 'POST'
44 body = {'server': {'action': 'poweron'}}
45 req.body = json.dumps(body)
46 res = req.get_response(fakes.wsgi_app())
47 self.assertEqual(res.status_int, 204)
48 self.assertEqual(self.called, True)
49
50 def test_action_bad_server_id_fails(self):
51 self.called = False
52
53 def fake_compute_get(*args, **kwargs):
54 raise exception.NotFound()
55
56 def fake_compute_power_on(*args, **kwargs):
57 self.called = True
58
59 self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
60 self.stubs.Set(compute.api.API, 'power_on_instance',
61 fake_compute_power_on)
62 req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
63 req.headers['Accept'] = 'application/json'
64 req.headers['Content-Type'] = 'application/json'
65 req.method = 'POST'
66 body = {'server': {'action': 'poweron'}}
67 req.body = json.dumps(body)
68 res = req.get_response(fakes.wsgi_app())
69 self.assertEqual(res.status_int, 404)
70 self.assertEqual(self.called, False)
71
72 def test_bad_action_fails(self):
73 self.called = False
74
75 def fake_compute_get(*args, **kwargs):
76 return {}
77
78 def fake_compute_power_on(*args, **kwargs):
79 self.called = True
80
81 self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get)
82 self.stubs.Set(compute.api.API, 'power_on_instance',
83 fake_compute_power_on)
84 req = webob.Request.blank('/v1.1/openstack/servers/50/os-instance-actions')
85 req.headers['Accept'] = 'application/json'
86 req.headers['Content-Type'] = 'application/json'
87 req.method = 'POST'
88 body = {'server': {'action': 'derp'}}
89 req.body = json.dumps(body)
90 res = req.get_response(fakes.wsgi_app())
91 self.assertEqual(res.status_int, 422)
92 self.assertEqual(self.called, False)
093
=== modified file 'nova/tests/api/openstack/test_extensions.py'
--- nova/tests/api/openstack/test_extensions.py 2011-09-14 15:54:56 +0000
+++ nova/tests/api/openstack/test_extensions.py 2011-09-20 18:37:25 +0000
@@ -91,6 +91,7 @@
91 "Floating_ips",91 "Floating_ips",
92 "Fox In Socks",92 "Fox In Socks",
93 "Hosts",93 "Hosts",
94 "InstanceActions",
94 "Keypairs",95 "Keypairs",
95 "Multinic",96 "Multinic",
96 "Quotas",97 "Quotas",
9798
=== modified file 'nova/virt/driver.py'
--- nova/virt/driver.py 2011-09-15 18:44:49 +0000
+++ nova/virt/driver.py 2011-09-20 18:37:25 +0000
@@ -196,6 +196,10 @@
196 # TODO(Vek): Need to pass context in for access to auth_token196 # TODO(Vek): Need to pass context in for access to auth_token
197 raise NotImplementedError()197 raise NotImplementedError()
198198
199 def power_on(self, instance):
200 """Turns the instance on if it was previously turned off"""
201 raise NotImplementedError()
202
199 def get_host_ip_addr(self):203 def get_host_ip_addr(self):
200 """204 """
201 Retrieves the IP address of the dom0205 Retrieves the IP address of the dom0
202206
=== modified file 'nova/virt/xenapi/vmops.py'
--- nova/virt/xenapi/vmops.py 2011-09-19 15:25:00 +0000
+++ nova/virt/xenapi/vmops.py 2011-09-20 18:37:25 +0000
@@ -1074,6 +1074,10 @@
1074 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)1074 vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
1075 return VMHelper.compile_diagnostics(self._session, vm_rec)1075 return VMHelper.compile_diagnostics(self._session, vm_rec)
10761076
1077 def power_on(self, instance):
1078 """Turns the instance on if it was previously turned off"""
1079 return self._start(instance)
1080
1077 def get_console_output(self, instance):1081 def get_console_output(self, instance):
1078 """Return snapshot of console."""1082 """Return snapshot of console."""
1079 # TODO: implement this to fix pylint!1083 # TODO: implement this to fix pylint!
10801084
=== modified file 'nova/virt/xenapi_conn.py'
--- nova/virt/xenapi_conn.py 2011-09-15 18:44:49 +0000
+++ nova/virt/xenapi_conn.py 2011-09-20 18:37:25 +0000
@@ -277,6 +277,10 @@
277 """Return data about VM diagnostics"""277 """Return data about VM diagnostics"""
278 return self._vmops.get_diagnostics(instance)278 return self._vmops.get_diagnostics(instance)
279279
280 def power_on(self, instance):
281 """Turns the instance on if it was previously turned off"""
282 return self._vmops.power_on(instance)
283
280 def get_console_output(self, instance):284 def get_console_output(self, instance):
281 """Return snapshot of console"""285 """Return snapshot of console"""
282 return self._vmops.get_console_output(instance)286 return self._vmops.get_console_output(instance)