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
This commit is contained in:
Anderson Mesquita 2014-11-17 17:56:38 -05:00
parent 6d8a5cb35c
commit e9c24bbd85
5 changed files with 83 additions and 16 deletions

View File

@ -96,9 +96,12 @@ class ResourceController(object):
Gets detailed information for a resource 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, res = self.rpc_client.describe_stack_resource(req.context,
identity, identity,
resource_name) resource_name,
**params)
return {'resource': format_resource(req, res)} return {'resource': format_resource(req, res)}

View File

@ -328,16 +328,20 @@ class EngineClient(object):
sort_keys=sort_keys, sort_keys=sort_keys,
sort_dir=sort_dir)) 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. Get detailed resource information about a particular resource.
:param ctxt: RPC context. :param ctxt: RPC context.
:param stack_identity: Name of the stack. :param stack_identity: Name of the stack.
:param resource_name: the Resource. :param resource_name: the Resource.
""" """
return self.call(ctxt, self.make_msg('describe_stack_resource', return self.call(ctxt,
stack_identity=stack_identity, self.make_msg('describe_stack_resource',
resource_name=resource_name)) stack_identity=stack_identity,
resource_name=resource_name,
with_attr=with_attr),
version='1.2')
def find_physical_resource(self, ctxt, physical_resource_id): def find_physical_resource(self, ctxt, physical_resource_id):
""" """

View File

@ -1389,9 +1389,10 @@ class CfnStackControllerTest(common.HeatTestCase):
args = { args = {
'stack_identity': identity, 'stack_identity': identity,
'resource_name': dummy_req.params.get('LogicalResourceId'), 'resource_name': dummy_req.params.get('LogicalResourceId'),
'with_attr': None,
} }
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
dummy_req.context, ('describe_stack_resource', args) dummy_req.context, ('describe_stack_resource', args), version='1.2'
).AndReturn(engine_resp) ).AndReturn(engine_resp)
self.m.ReplayAll() self.m.ReplayAll()
@ -1453,9 +1454,10 @@ class CfnStackControllerTest(common.HeatTestCase):
args = { args = {
'stack_identity': identity, 'stack_identity': identity,
'resource_name': dummy_req.params.get('LogicalResourceId'), 'resource_name': dummy_req.params.get('LogicalResourceId'),
'with_attr': None,
} }
rpc_client.EngineClient.call( 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( ).AndRaise(heat_exception.ResourceNotFound(
resource_name='test', stack_name='test')) resource_name='test', stack_name='test'))

View File

@ -2129,7 +2129,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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) ).AndReturn(engine_resp)
self.m.ReplayAll() self.m.ReplayAll()
@ -2191,7 +2193,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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) ).AndReturn(engine_resp)
self.m.ReplayAll() self.m.ReplayAll()
@ -2224,7 +2228,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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)) ).AndRaise(to_remote_error(error))
self.m.ReplayAll() self.m.ReplayAll()
@ -2239,6 +2245,47 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual('StackNotFound', resp.json['error']['type']) self.assertEqual('StackNotFound', resp.json['error']['type'])
self.m.VerifyAll() 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): def test_show_nonexist_resource(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'show', True) self._mock_enforce_setup(mock_enforce, 'show', True)
res_name = 'Wibble' res_name = 'Wibble'
@ -2254,7 +2301,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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)) ).AndRaise(to_remote_error(error))
self.m.ReplayAll() self.m.ReplayAll()
@ -2284,7 +2333,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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)) ).AndRaise(to_remote_error(error))
self.m.ReplayAll() self.m.ReplayAll()
@ -2347,7 +2398,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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) ).AndReturn(engine_resp)
self.m.ReplayAll() self.m.ReplayAll()
@ -2376,7 +2429,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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)) ).AndRaise(to_remote_error(error))
self.m.ReplayAll() self.m.ReplayAll()
@ -2406,7 +2461,9 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
rpc_client.EngineClient.call( rpc_client.EngineClient.call(
req.context, req.context,
('describe_stack_resource', ('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)) ).AndRaise(to_remote_error(error))
self.m.ReplayAll() self.m.ReplayAll()

View File

@ -207,7 +207,8 @@ class EngineRpcAPITestCase(testtools.TestCase):
def test_describe_stack_resource(self): def test_describe_stack_resource(self):
self._test_engine_api('describe_stack_resource', 'call', self._test_engine_api('describe_stack_resource', 'call',
stack_identity=self.identity, stack_identity=self.identity,
resource_name='LogicalResourceId') resource_name='LogicalResourceId',
with_attr=None)
def test_find_physical_resource(self): def test_find_physical_resource(self):
self._test_engine_api('find_physical_resource', 'call', self._test_engine_api('find_physical_resource', 'call',