From 5e883e94d73cb7fd6737c262f25cd7c6be56ffe5 Mon Sep 17 00:00:00 2001 From: Sergey Kraynev Date: Fri, 10 Jan 2014 09:04:21 -0500 Subject: [PATCH] Showing member list for nested resources New section 'members' was added for nested resource description. This section contain physical_resource_id of nested stack resources. Closes-bug: #1249484 Change-Id: Iff5647b92abcd9d6fc3b6e3f3e7379c3f5f95e79 --- heat/engine/api.py | 4 ++ heat/rpc/api.py | 4 +- heat/tests/test_api_openstack_v1.py | 65 ++++++++++++++++++++ heat/tests/test_engine_service.py | 92 +++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 2 deletions(-) diff --git a/heat/engine/api.py b/heat/engine/api.py index 824bb632f3..a545b56a66 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -119,6 +119,10 @@ def format_stack_resource(resource, detail=True): res[api.RES_DESCRIPTION] = resource.parsed_template('Description', '') res[api.RES_METADATA] = resource.metadata + if getattr(resource, 'nested', None) is not None: + res[api.RES_MEMBERS] = [r.resource_id for r in + resource.nested().resources.itervalues()] + return res diff --git a/heat/rpc/api.py b/heat/rpc/api.py index b5e742377d..bbf72702b4 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -51,13 +51,13 @@ RES_KEYS = ( RES_NAME, RES_PHYSICAL_ID, RES_METADATA, RES_ACTION, RES_STATUS, RES_STATUS_DATA, RES_TYPE, RES_ID, RES_STACK_ID, RES_STACK_NAME, - RES_REQUIRED_BY, + RES_REQUIRED_BY, RES_MEMBERS, ) = ( 'description', 'updated_time', 'resource_name', 'physical_resource_id', 'metadata', 'resource_action', 'resource_status', 'resource_status_reason', 'resource_type', 'resource_identity', STACK_ID, STACK_NAME, - 'required_by', + 'required_by', 'members', ) RES_SCHEMA_KEYS = ( diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/test_api_openstack_v1.py index ef850ad7cb..6887ef194c 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/test_api_openstack_v1.py @@ -1923,6 +1923,71 @@ class ResourceControllerTest(ControllerTest, HeatTestCase): self.assertEqual(403, resp.status_int) self.assertIn('403 Forbidden', str(resp)) + def test_show_nested(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'show', True) + res_name = 'ServerGroup' + stack_identity = identifier.HeatIdentifier(self.tenant, + 'nested_resource', '6') + res_identity = identifier.ResourceIdentifier(resource_name=res_name, + **stack_identity) + + req = self._get(stack_identity._tenant_path()) + + engine_resp = { + u'description': u'', + u'resource_identity': dict(res_identity), + u'stack_name': stack_identity.stack_name, + u'resource_name': res_name, + u'resource_status_reason': None, + u'updated_time': u'2012-07-23T13:06:00Z', + u'stack_identity': dict(stack_identity), + u'resource_action': u'CREATE', + u'resource_status': u'COMPLETE', + u'physical_resource_id': + u'a3455d8c-9f88-404d-a85b-5315293e67de', + u'resource_type': u'OS::Heat::ResourceGroup', + u'metadata': {u'ensureRunning': u'true'}, + u'members': [u'2bf47h48-45u4-4z47-371h-j2k4v236l562', + u'a3455d8c-9f88-404d-a85b-5315293e67de'] + } + self.m.StubOutWithMock(rpc, 'call') + rpc.call(req.context, self.topic, + {'namespace': None, + 'method': 'describe_stack_resource', + 'args': {'stack_identity': stack_identity, + 'resource_name': res_name}, + 'version': self.api_version}, + None).AndReturn(engine_resp) + self.m.ReplayAll() + + result = self.controller.show(req, tenant_id=self.tenant, + stack_name=stack_identity.stack_name, + stack_id=stack_identity.stack_id, + resource_name=res_name) + + expected = { + 'resource': { + 'links': [ + {'href': self._url(res_identity), 'rel': 'self'}, + {'href': self._url(stack_identity), 'rel': 'stack'}, + ], + u'description': u'', + u'resource_name': res_name, + u'logical_resource_id': res_name, + u'resource_status_reason': None, + u'updated_time': u'2012-07-23T13:06:00Z', + u'resource_status': u'CREATE_COMPLETE', + u'physical_resource_id': + u'a3455d8c-9f88-404d-a85b-5315293e67de', + u'resource_type': u'OS::Heat::ResourceGroup', + u'members': [u'2bf47h48-45u4-4z47-371h-j2k4v236l562', + u'a3455d8c-9f88-404d-a85b-5315293e67de'] + } + } + + self.assertEqual(result, expected) + self.m.VerifyAll() + def test_metadata_show(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'metadata', True) res_name = 'WikiDatabase' diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 10b55c74ce..1f97e6ce5b 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -161,6 +161,31 @@ user_policy_template = ''' } ''' +nested_template = ''' +{ + "AWSTemplateFormatVersion" : "2010-09-09", + "Description" : "Nested resource", + "Parameters" : {}, + "Resources" : { + "ServerGroup": { + "Type": "OS::Heat::ResourceGroup", + "Properties": { + "count": 2, + "resource_def": { + "type": "AWS::EC2::Instance", + "properties": { + "KeyName" : "test", + "ImageId" : "F17-x86_64-gold", + "InstanceType" : "m1.large", + "UserData" : "wordpress" + } + } + } + } + } +} +''' + def get_wordpress_stack(stack_name, ctx): t = template_format.parse(wp_template) @@ -1508,6 +1533,73 @@ class StackServiceTest(HeatTestCase): self.m.VerifyAll() + def test_stack_resource_describe_nested(self): + stack = get_stack('service_stack_resource_describe_nested_test_stack', + self.ctx, + nested_template) + self.stack = stack + stack.store() + + fc = fakes.FakeClient() + self.m.StubOutWithMock(instances.Instance, 'nova') + instances.Instance.nova().MultipleTimes().AndReturn(fc) + + patcher = mock.patch.object( + nova_utils, 'build_userdata', return_value=None) + patcher.start() + self.m.StubOutWithMock(fc.servers, 'create') + fc.servers.create(image=744, flavor=3, key_name='test', + name=utils.PhysName( + utils.PhysName(stack.name, 'ServerGroup'), + '0', 53), + security_groups=None, + userdata=None, scheduler_hints=None, + meta=None, nics=None, + availability_zone=None).InAnyOrder().AndReturn( + fc.servers.list()[1]) + fc.servers.create(image=744, flavor=3, key_name='test', + name=utils.PhysName( + utils.PhysName(stack.name, 'ServerGroup'), + '1', 53), + security_groups=None, + userdata=None, scheduler_hints=None, + meta=None, nics=None, + availability_zone=None).InAnyOrder().AndReturn( + fc.servers.list()[1]) + + self.m.ReplayAll() + stack.create() + + self.m.StubOutWithMock(parser.Stack, 'load') + parser.Stack.load(self.ctx, + stack=mox.IgnoreArg()).AndReturn(self.stack) + self.m.ReplayAll() + + r = self.eng.describe_stack_resource(self.ctx, self.stack.identifier(), + 'ServerGroup') + + self.assertIn('resource_identity', r) + self.assertIn('description', r) + self.assertIn('updated_time', r) + self.assertIn('stack_identity', r) + self.assertIsNotNone(r['stack_identity']) + self.assertIn('stack_name', r) + self.assertEqual(self.stack.name, r['stack_name']) + self.assertIn('metadata', r) + self.assertIn('resource_status', r) + self.assertIn('resource_status_reason', r) + self.assertIn('resource_type', r) + self.assertIn('physical_resource_id', r) + self.assertIn('resource_name', r) + self.assertIn('members', r) + self.assertEqual([5678, 5678], r['members']) + self.assertEqual('ServerGroup', r['resource_name']) + + self.m.VerifyAll() + self.m.UnsetStubs() + patcher.stop() + self.stack.delete() + @stack_context('service_resources_describe_test_stack') def test_stack_resources_describe(self): self.m.StubOutWithMock(parser.Stack, 'load')