From e9c24bbd85256887df54f1db375d5486617f52d8 Mon Sep 17 00:00:00 2001 From: Anderson Mesquita Date: Mon, 17 Nov 2014 17:56:38 -0500 Subject: [PATCH] Expose resource attributes in the API This adds the necessary API changes to allow the user to view resource's attributes when making calls to resource show. Implements: blueprint detailed-resource-show Change-Id: Id203478dbd067743d36623e99332ac32c6f96d42 --- heat/api/openstack/v1/resources.py | 5 +- heat/rpc/client.py | 12 +++-- heat/tests/test_api_cfn_v1.py | 6 ++- heat/tests/test_api_openstack_v1.py | 73 +++++++++++++++++++++++++---- heat/tests/test_rpc_client.py | 3 +- 5 files changed, 83 insertions(+), 16 deletions(-) diff --git a/heat/api/openstack/v1/resources.py b/heat/api/openstack/v1/resources.py index 1288a7a32..a1c9b1571 100644 --- a/heat/api/openstack/v1/resources.py +++ b/heat/api/openstack/v1/resources.py @@ -96,9 +96,12 @@ class ResourceController(object): Gets detailed information for a resource """ + whitelist = {'with_attr': 'multi'} + params = util.get_allowed_params(req.params, whitelist) res = self.rpc_client.describe_stack_resource(req.context, identity, - resource_name) + resource_name, + **params) return {'resource': format_resource(req, res)} diff --git a/heat/rpc/client.py b/heat/rpc/client.py index d6b199d13..22d2388d5 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -328,16 +328,20 @@ class EngineClient(object): sort_keys=sort_keys, sort_dir=sort_dir)) - def describe_stack_resource(self, ctxt, stack_identity, resource_name): + def describe_stack_resource(self, ctxt, stack_identity, resource_name, + with_attr=None): """ Get detailed resource information about a particular resource. :param ctxt: RPC context. :param stack_identity: Name of the stack. :param resource_name: the Resource. """ - return self.call(ctxt, self.make_msg('describe_stack_resource', - stack_identity=stack_identity, - resource_name=resource_name)) + return self.call(ctxt, + self.make_msg('describe_stack_resource', + stack_identity=stack_identity, + resource_name=resource_name, + with_attr=with_attr), + version='1.2') def find_physical_resource(self, ctxt, physical_resource_id): """ diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/test_api_cfn_v1.py index d17d9daa1..2d660a917 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/test_api_cfn_v1.py @@ -1389,9 +1389,10 @@ class CfnStackControllerTest(common.HeatTestCase): args = { 'stack_identity': identity, 'resource_name': dummy_req.params.get('LogicalResourceId'), + 'with_attr': None, } rpc_client.EngineClient.call( - dummy_req.context, ('describe_stack_resource', args) + dummy_req.context, ('describe_stack_resource', args), version='1.2' ).AndReturn(engine_resp) self.m.ReplayAll() @@ -1453,9 +1454,10 @@ class CfnStackControllerTest(common.HeatTestCase): args = { 'stack_identity': identity, 'resource_name': dummy_req.params.get('LogicalResourceId'), + 'with_attr': None, } rpc_client.EngineClient.call( - dummy_req.context, ('describe_stack_resource', args) + dummy_req.context, ('describe_stack_resource', args), version='1.2' ).AndRaise(heat_exception.ResourceNotFound( resource_name='test', stack_name='test')) diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index 9e41a59b4..5f7af2268 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -2129,7 +2129,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndReturn(engine_resp) self.m.ReplayAll() @@ -2191,7 +2193,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndReturn(engine_resp) self.m.ReplayAll() @@ -2224,7 +2228,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndRaise(to_remote_error(error)) self.m.ReplayAll() @@ -2239,6 +2245,47 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): self.assertEqual('StackNotFound', resp.json['error']['type']) self.m.VerifyAll() + def test_show_with_single_attribute(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'show', True) + res_name = 'WikiDatabase' + stack_identity = identifier.HeatIdentifier(self.tenant, 'foo', '1') + res_identity = identifier.ResourceIdentifier(resource_name=res_name, + **stack_identity) + mock_describe = mock.Mock(return_value={'foo': 'bar'}) + self.controller.rpc_client.describe_stack_resource = mock_describe + + req = self._get(res_identity._tenant_path(), {'with_attr': 'baz'}) + resp = self.controller.show(req, tenant_id=self.tenant, + stack_name=stack_identity.stack_name, + stack_id=stack_identity.stack_id, + resource_name=res_name) + + self.assertEqual({'resource': {'foo': 'bar'}}, resp) + args, kwargs = mock_describe.call_args + self.assertIn('baz', kwargs['with_attr']) + + def test_show_with_multiple_attributes(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'show', True) + res_name = 'WikiDatabase' + stack_identity = identifier.HeatIdentifier(self.tenant, 'foo', '1') + res_identity = identifier.ResourceIdentifier(resource_name=res_name, + **stack_identity) + mock_describe = mock.Mock(return_value={'foo': 'bar'}) + self.controller.rpc_client.describe_stack_resource = mock_describe + + req = self._get(res_identity._tenant_path()) + req.environ['QUERY_STRING'] = 'with_attr=a1&with_attr=a2&with_attr=a3' + resp = self.controller.show(req, tenant_id=self.tenant, + stack_name=stack_identity.stack_name, + stack_id=stack_identity.stack_id, + resource_name=res_name) + + self.assertEqual({'resource': {'foo': 'bar'}}, resp) + args, kwargs = mock_describe.call_args + self.assertIn('a1', kwargs['with_attr']) + self.assertIn('a2', kwargs['with_attr']) + self.assertIn('a3', kwargs['with_attr']) + def test_show_nonexist_resource(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'show', True) res_name = 'Wibble' @@ -2254,7 +2301,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndRaise(to_remote_error(error)) self.m.ReplayAll() @@ -2284,7 +2333,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndRaise(to_remote_error(error)) self.m.ReplayAll() @@ -2347,7 +2398,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndReturn(engine_resp) self.m.ReplayAll() @@ -2376,7 +2429,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndRaise(to_remote_error(error)) self.m.ReplayAll() @@ -2406,7 +2461,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase): rpc_client.EngineClient.call( req.context, ('describe_stack_resource', - {'stack_identity': stack_identity, 'resource_name': res_name}) + {'stack_identity': stack_identity, 'resource_name': res_name, + 'with_attr': None}), + version='1.2' ).AndRaise(to_remote_error(error)) self.m.ReplayAll() diff --git a/heat/tests/test_rpc_client.py b/heat/tests/test_rpc_client.py index 9f763e758..32a23d078 100644 --- a/heat/tests/test_rpc_client.py +++ b/heat/tests/test_rpc_client.py @@ -207,7 +207,8 @@ class EngineRpcAPITestCase(testtools.TestCase): def test_describe_stack_resource(self): self._test_engine_api('describe_stack_resource', 'call', stack_identity=self.identity, - resource_name='LogicalResourceId') + resource_name='LogicalResourceId', + with_attr=None) def test_find_physical_resource(self): self._test_engine_api('find_physical_resource', 'call',