From 9b7d33f22781ade62ac712a1285d0c0729bfebc1 Mon Sep 17 00:00:00 2001 From: ivan-zhu Date: Fri, 28 Jun 2013 14:12:31 +0800 Subject: [PATCH] Port instance_actions API to v3 Part 2 This patch contains the changes required to adapt the agent extension and the corresponding unittest to the v3 framework Partially implements: bp v3-api-extension-versioning Change-Id: Ibf0784a455942185799d40395fab5e8eb112e2d8 --- etc/nova/policy.json | 2 + .../compute/plugins/v3/instance_actions.py | 41 ++++++---- .../plugins/v3/test_instance_actions.py | 74 +++++++++++-------- nova/tests/fake_policy.py | 2 + setup.cfg | 1 + 5 files changed, 74 insertions(+), 46 deletions(-) diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 995881eb44f7..6e4fb3b043af 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -97,7 +97,9 @@ "compute_extension:v3:os-image-metadata": "", "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", + "compute_extension:v3:os-instance-actions": "", "compute_extension:instance_actions:events": "rule:admin_api", + "compute_extension:v3:os-instance-actions:events": "rule:admin_api", "compute_extension:instance_usage_audit_log": "rule:admin_api", "compute_extension:v3:os-instance-usage-audit-log": "rule:admin_api", "compute_extension:v3:ips:discoverable": "", diff --git a/nova/api/openstack/compute/plugins/v3/instance_actions.py b/nova/api/openstack/compute/plugins/v3/instance_actions.py index 3b15de2bac68..94497c5e4884 100644 --- a/nova/api/openstack/compute/plugins/v3/instance_actions.py +++ b/nova/api/openstack/compute/plugins/v3/instance_actions.py @@ -21,10 +21,11 @@ from nova.api.openstack import xmlutil from nova import compute from nova import exception +ALIAS = "os-instance-actions" authorize_actions = extensions.extension_authorizer('compute', - 'instance_actions') + 'v3:' + ALIAS) authorize_events = extensions.soft_extension_authorizer('compute', - 'instance_actions:events') + 'v3:' + ALIAS + ':events') ACTION_KEYS = ['action', 'instance_uuid', 'request_id', 'user_id', 'project_id', 'start_time', 'message'] @@ -47,17 +48,17 @@ def make_action(elem): class InstanceActionsTemplate(xmlutil.TemplateBuilder): def construct(self): - root = xmlutil.TemplateElement('instanceActions') - elem = xmlutil.SubTemplateElement(root, 'instanceAction', - selector='instanceActions') + root = xmlutil.TemplateElement('instance_actions') + elem = xmlutil.SubTemplateElement(root, 'instance_action', + selector='instance_actions') make_actions(elem) return xmlutil.MasterTemplate(root, 1) class InstanceActionTemplate(xmlutil.TemplateBuilder): def construct(self): - root = xmlutil.TemplateElement('instanceAction', - selector='instanceAction') + root = xmlutil.TemplateElement('instance_action', + selector='instance_action') make_action(root) return xmlutil.MasterTemplate(root, 1) @@ -92,18 +93,22 @@ class InstanceActionsController(wsgi.Controller): authorize_actions(context, target=instance) actions_raw = self.action_api.actions_get(context, instance) actions = [self._format_action(action) for action in actions_raw] - return {'instanceActions': actions} + return {'instance_actions': actions} @wsgi.serializers(xml=InstanceActionTemplate) def show(self, req, server_id, id): """Return data about the given instance action.""" context = req.environ['nova.context'] - instance = self.compute_api.get(context, server_id) + try: + instance = self.compute_api.get(context, server_id) + except exception.InstanceNotFound as err: + raise exc.HTTPNotFound(explanation=err.format_message()) authorize_actions(context, target=instance) action = self.action_api.action_get_by_request_id(context, instance, id) if action is None: - raise exc.HTTPNotFound() + msg = _("Action %s not found") % id + raise exc.HTTPNotFound(msg) action_id = action['id'] action = self._format_action(action) @@ -111,17 +116,17 @@ class InstanceActionsController(wsgi.Controller): events_raw = self.action_api.action_events_get(context, instance, action_id) action['events'] = [self._format_event(evt) for evt in events_raw] - return {'instanceAction': action} + return {'instance_action': action} -class Instance_actions(extensions.ExtensionDescriptor): +class InstanceActions(extensions.V3APIExtensionBase): """View a log of actions and events taken on an instance.""" name = "InstanceActions" - alias = "os-instance-actions" + alias = ALIAS namespace = ("http://docs.openstack.org/compute/ext/" - "instance-actions/api/v1.1") - updated = "2013-02-08T00:00:00+00:00" + "instance-actions/api/v3") + version = 1 def get_resources(self): ext = extensions.ResourceExtension('os-instance-actions', @@ -130,3 +135,9 @@ class Instance_actions(extensions.ExtensionDescriptor): member_name='server', collection_name='servers')) return [ext] + + def get_controller_extensions(self): + """It's an abstract function V3APIExtensionBase and the extension + will not be loaded without it. + """ + return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_instance_actions.py b/nova/tests/api/openstack/compute/plugins/v3/test_instance_actions.py index 12c3fab0ea6b..af1472fbd4cb 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_instance_actions.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_instance_actions.py @@ -19,7 +19,7 @@ import uuid from lxml import etree from webob import exc -from nova.api.openstack.compute.contrib import instance_actions +from nova.api.openstack.compute.plugins.v3 import instance_actions from nova.compute import api as compute_api from nova import db from nova.db.sqlalchemy import models @@ -57,7 +57,7 @@ class InstanceActionsPolicyTest(test.TestCase): def test_list_actions_restricted_by_project(self): rules = policy.Rules({'compute:get': policy.parse_rule(''), - 'compute_extension:instance_actions': + 'compute_extension:v3:os-instance-actions': policy.parse_rule('project_id:%(project_id)s')}) policy.set_rules(rules) @@ -68,13 +68,13 @@ class InstanceActionsPolicyTest(test.TestCase): context.project_id}) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) - req = fakes.HTTPRequest.blank('/v2/123/servers/12/os-instance-actions') + req = fakes.HTTPRequestV3.blank('/servers/12/os-instance-actions') self.assertRaises(exception.NotAuthorized, self.controller.index, req, str(uuid.uuid4())) def test_get_action_restricted_by_project(self): rules = policy.Rules({'compute:get': policy.parse_rule(''), - 'compute_extension:instance_actions': + 'compute_extension:v3:os-instance-actions': policy.parse_rule('project_id:%(project_id)s')}) policy.set_rules(rules) @@ -85,8 +85,8 @@ class InstanceActionsPolicyTest(test.TestCase): context.project_id}) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) - req = fakes.HTTPRequest.blank( - '/v2/123/servers/12/os-instance-actions/1') + req = fakes.HTTPRequestV3.blank( + '/servers/12/os-instance-actions/1') self.assertRaises(exception.NotAuthorized, self.controller.show, req, str(uuid.uuid4()), '1') @@ -101,8 +101,11 @@ class InstanceActionsTest(test.TestCase): def fake_get(self, context, instance_uuid): return {'uuid': instance_uuid} - def fake_instance_get_by_uuid(context, instance_id): - return {'name': 'fake', 'project_id': context.project_id} + def fake_instance_get_by_uuid(context, instance_id, + columns_to_join=None): + return fake_instance.fake_db_instance( + **{'name': 'fake', 'project_id': '%s_unequal' % + context.project_id}) self.stubs.Set(compute_api.API, 'get', fake_get) self.stubs.Set(db, 'instance_get_by_uuid', fake_instance_get_by_uuid) @@ -117,9 +120,9 @@ class InstanceActionsTest(test.TestCase): return actions self.stubs.Set(db, 'actions_get', fake_get_actions) - req = fakes.HTTPRequest.blank('/v2/123/servers/12/os-instance-actions') + req = fakes.HTTPRequestV3.blank('/servers/12/os-instance-actions') res_dict = self.controller.index(req, FAKE_UUID) - for res in res_dict['instanceActions']: + for res in res_dict['instance_actions']: fake_action = self.fake_actions[FAKE_UUID][res['request_id']] fake_action = format_action(fake_action) self.assertEqual(fake_action, res) @@ -140,8 +143,8 @@ class InstanceActionsTest(test.TestCase): self.stubs.Set(db, 'action_get_by_request_id', fake_get_action) self.stubs.Set(db, 'action_events_get', fake_get_events) - req = fakes.HTTPRequest.blank( - '/v2/123/servers/12/os-instance-actions/1', + req = fakes.HTTPRequestV3.blank( + '/servers/12/os-instance-actions/1', use_admin_context=True) res_dict = self.controller.show(req, FAKE_UUID, FAKE_REQUEST_ID) fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID] @@ -149,7 +152,7 @@ class InstanceActionsTest(test.TestCase): fake_events = [format_event(event) for event in fake_events] fake_action = format_action(fake_action) fake_action['events'] = fake_events - self.assertEqual(fake_action, res_dict['instanceAction']) + self.assertEqual(fake_action, res_dict['instance_action']) def test_get_action_with_events_not_allowed(self): def fake_get_action(context, uuid, request_id): @@ -160,26 +163,27 @@ class InstanceActionsTest(test.TestCase): self.stubs.Set(db, 'action_get_by_request_id', fake_get_action) self.stubs.Set(db, 'action_events_get', fake_get_events) - rules = policy.Rules({'compute:get': policy.parse_rule(''), - 'compute_extension:instance_actions': - policy.parse_rule(''), - 'compute_extension:instance_actions:events': - policy.parse_rule('is_admin:True')}) + rules = policy.Rules({ + 'compute:get': policy.parse_rule(''), + 'compute_extension:v3:os-instance-actions': + policy.parse_rule(''), + 'compute_extension:v3:os-instance-actions:events': + policy.parse_rule('is_admin:True')}) policy.set_rules(rules) - req = fakes.HTTPRequest.blank( - '/v2/123/servers/12/os-instance-actions/1') + req = fakes.HTTPRequestV3.blank( + '/servers/12/os-instance-actions/1') res_dict = self.controller.show(req, FAKE_UUID, FAKE_REQUEST_ID) fake_action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID] fake_action = format_action(fake_action) - self.assertEqual(fake_action, res_dict['instanceAction']) + self.assertEqual(fake_action, res_dict['instance_action']) def test_action_not_found(self): def fake_no_action(context, uuid, action_id): return None self.stubs.Set(db, 'action_get_by_request_id', fake_no_action) - req = fakes.HTTPRequest.blank( - '/v2/123/servers/12/os-instance-actions/1') + req = fakes.HTTPRequestV3.blank( + '/servers/12/os-instance-actions/1') self.assertRaises(exc.HTTPNotFound, self.controller.show, req, FAKE_UUID, FAKE_REQUEST_ID) @@ -187,10 +191,18 @@ class InstanceActionsTest(test.TestCase): def fake_get(self, context, instance_uuid): raise exception.InstanceNotFound(instance_id=instance_uuid) self.stubs.Set(compute_api.API, 'get', fake_get) - req = fakes.HTTPRequest.blank('/v2/123/servers/12/os-instance-actions') + req = fakes.HTTPRequestV3.blank('/servers/12/os-instance-actions') self.assertRaises(exc.HTTPNotFound, self.controller.index, req, FAKE_UUID) + def test_instance_not_found_in_show(self): + def fake_get(self, context, instance_uuid): + raise exception.InstanceNotFound(instance_id=instance_uuid) + self.stubs.Set(compute_api.API, 'get', fake_get) + req = fakes.HTTPRequestV3.blank('/servers/12/os-instance-actions/1') + self.assertRaises(exc.HTTPNotFound, self.controller.show, req, + FAKE_UUID, FAKE_REQUEST_ID) + class InstanceActionsSerializerTest(test.TestCase): def setUp(self): @@ -212,11 +224,11 @@ class InstanceActionsSerializerTest(test.TestCase): def test_instance_action_serializer(self): serializer = instance_actions.InstanceActionTemplate() action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID] - text = serializer.serialize({'instanceAction': action}) + text = serializer.serialize({'instance_action': action}) tree = etree.fromstring(text) action = format_action(action) - self.assertEqual('instanceAction', tree.tag) + self.assertEqual('instance_action', tree.tag) self._verify_instance_action_attachment(action, tree) found_events = False for child in tree: @@ -229,11 +241,11 @@ class InstanceActionsSerializerTest(test.TestCase): action = self.fake_actions[FAKE_UUID][FAKE_REQUEST_ID] event = self.fake_events[action['id']][0] action['events'] = [event, event] - text = serializer.serialize({'instanceAction': action}) + text = serializer.serialize({'instance_action': action}) tree = etree.fromstring(text) action = format_action(action) - self.assertEqual('instanceAction', tree.tag) + self.assertEqual('instance_action', tree.tag) self._verify_instance_action_attachment(action, tree) event = format_event(event) @@ -248,14 +260,14 @@ class InstanceActionsSerializerTest(test.TestCase): def test_instance_actions_serializer(self): serializer = instance_actions.InstanceActionsTemplate() action_list = self.fake_actions[FAKE_UUID].values() - text = serializer.serialize({'instanceActions': action_list}) + text = serializer.serialize({'instance_actions': action_list}) tree = etree.fromstring(text) action_list = [format_action(action) for action in action_list] - self.assertEqual('instanceActions', tree.tag) + self.assertEqual('instance_actions', tree.tag) self.assertEqual(len(action_list), len(tree)) for idx, child in enumerate(tree): - self.assertEqual('instanceAction', child.tag) + self.assertEqual('instance_action', child.tag) request_id = child.get('request_id') self._verify_instance_action_attachment( self.fake_actions[FAKE_UUID][request_id], diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 86f6ddc9a712..b670c232f4bb 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -174,7 +174,9 @@ policy_data = """ "compute_extension:v3:os-image-metadata": "", "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", + "compute_extension:v3:os-instance-actions": "", "compute_extension:instance_actions:events": "is_admin:True", + "compute_extension:v3:os-instance-actions:events": "is_admin:True", "compute_extension:instance_usage_audit_log": "", "compute_extension:v3:os-instance-usage-audit-log": "", "compute_extension:keypairs": "", diff --git a/setup.cfg b/setup.cfg index f1eb12e59710..228dbe968cf8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -77,6 +77,7 @@ nova.api.v3.extensions = hypervisors = nova.api.openstack.compute.plugins.v3.hypervisors:Hypervisors image_metadata = nova.api.openstack.compute.plugins.v3.image_metadata:ImageMetadata images = nova.api.openstack.compute.plugins.v3.images:Images + instance_actions = nova.api.openstack.compute.plugins.v3.instance_actions:InstanceActions ips = nova.api.openstack.compute.plugins.v3.ips:IPs instance_usage_audit_log = nova.api.openstack.compute.plugins.v3.instance_usage_audit_log:InstanceUsageAuditLog keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs