diff --git a/heat/engine/api.py b/heat/engine/api.py index 05257c8e3..8923c42d4 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -180,8 +180,8 @@ def format_stack_resource(resource, detail=True, with_props=False, resource.nested() is not None): res[rpc_api.RES_NESTED_STACK_ID] = dict(resource.nested().identifier()) - if resource.stack.parent_resource: - res[rpc_api.RES_PARENT_RESOURCE] = resource.stack.parent_resource.name + if resource.stack.parent_resource_name: + res[rpc_api.RES_PARENT_RESOURCE] = resource.stack.parent_resource_name if detail: res[rpc_api.RES_DESCRIPTION] = resource.t.description diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 115ec0fc0..2ae30dbf4 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -107,7 +107,8 @@ class Stack(collections.Mapping): self.status_reason = status_reason self.timeout_mins = timeout_mins self.disable_rollback = disable_rollback - self.parent_resource = parent_resource + self.parent_resource_name = parent_resource + self._parent_resource = None self._resources = None self._dependencies = None self._access_allowed_handlers = {} @@ -145,6 +146,26 @@ class Stack(collections.Mapping): else: self.outputs = {} + @property + def parent_resource(self): + """Dynamically load up the parent_resource. + + Note: this should only be used by "Fn::ResourceFacade" + """ + if self._parent_resource is not None: + return self._parent_resource + + # we need both parent name and owner id. + if self.parent_resource_name is None or self.owner_id is None: + return None + + try: + owner = self.load(self.context, stack_id=self.owner_id) + except exception.NotFound: + return None + self._parent_resource = owner[self.parent_resource_name] + return self._parent_resource + def stored_context(self): if self.user_creds_id: creds = db_api.user_creds_get(self.user_creds_id) diff --git a/heat/engine/stack_resource.py b/heat/engine/stack_resource.py index c52968b0c..7531ed3ff 100644 --- a/heat/engine/stack_resource.py +++ b/heat/engine/stack_resource.py @@ -57,8 +57,9 @@ class StackResource(resource.Resource): def validate_nested_stack(self): try: + name = "%s-%s" % (self.stack.name, self.name) nested_stack = self._parse_nested_stack( - self.stack.name, + name, self.child_template(), self.child_params()) nested_stack.strict_validate = False @@ -95,7 +96,7 @@ class StackResource(resource.Resource): if self._nested is None and self.resource_id is not None: self._nested = parser.Stack.load(self.context, self.resource_id, - parent_resource=self, + parent_resource=self.name, show_deleted=show_deleted, force_reload=force_reload) @@ -191,7 +192,7 @@ class StackResource(resource.Resource): env=child_env, timeout_mins=timeout_mins, disable_rollback=True, - parent_resource=self, + parent_resource=self.name, owner_id=self.stack.id, user_creds_id=self.stack.user_creds_id, stack_user_project_id=stack_user_project_id, diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 10ef09540..db0f5b28a 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -228,8 +228,7 @@ class FormatTest(common.HeatTestCase): def test_format_stack_resource_with_parent_stack(self): res = self.stack['generic1'] - res.stack.parent_resource = mock.Mock() - res.stack.parent_resource.name = 'foobar' + res.stack.parent_resource_name = 'foobar' formatted = api.format_stack_resource(res, False) self.assertEqual('foobar', formatted[rpc_api.RES_PARENT_RESOURCE]) diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index 2c5d06e38..21283337a 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -753,7 +753,8 @@ class HOTemplateTest(common.HeatTestCase): parser.Template(hot_tpl_empty)) stack = parser.Stack(utils.dummy_context(), 'test_stack', parser.Template(hot_tpl_empty), - parent_resource=parent_resource) + parent_resource='parent') + stack._parent_resource = parent_resource self.assertEqual({"foo": "bar"}, self.resolve(metadata_snippet, stack.t, stack)) self.assertEqual('Retain', @@ -778,7 +779,8 @@ class HOTemplateTest(common.HeatTestCase): stack = parser.Stack(utils.dummy_context(), 'test_stack', parser.Template(hot_tpl_empty), - parent_resource=parent_resource) + parent_resource='parent') + stack._parent_resource = parent_resource self.assertEqual('Retain', self.resolve(deletion_policy_snippet, stack.t, stack)) @@ -803,7 +805,8 @@ class HOTemplateTest(common.HeatTestCase): parser.Template(hot_tpl_empty)) stack = parser.Stack(utils.dummy_context(), 'test_stack', parser.Template(hot_tpl_empty), - parent_resource=parent_resource) + parent_resource='parent') + stack._parent_resource = parent_resource self.assertEqual('Delete', self.resolve(snippet, stack.t, stack)) def test_removed_function(self): diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index 0ff544c91..c9478f319 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -559,7 +559,8 @@ Mappings: parser.Template(empty_template)) stack = parser.Stack(self.ctx, 'test_stack', parser.Template(empty_template), - parent_resource=parent_resource) + parent_resource='parent', owner_id=45) + stack._parent_resource = parent_resource self.assertEqual({"foo": "bar"}, self.resolve(metadata_snippet, stack.t, stack)) self.assertEqual('Retain', @@ -582,7 +583,8 @@ Mappings: stack = parser.Stack(self.ctx, 'test_stack', parser.Template(empty_template), - parent_resource=parent_resource) + parent_resource='parent') + stack._parent_resource = parent_resource self.assertEqual('Retain', self.resolve(deletion_policy_snippet, stack.t, stack)) @@ -607,7 +609,8 @@ Mappings: parser.Template(empty_template)) stack = parser.Stack(self.ctx, 'test_stack', parser.Template(empty_template), - parent_resource=parent_resource) + parent_resource='parent', owner_id=78) + stack._parent_resource = parent_resource self.assertEqual('Delete', self.resolve(snippet, stack.t, stack)) def test_prevent_parameters_access(self): @@ -1107,8 +1110,8 @@ class StackTest(common.HeatTestCase): stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl), status_reason='blarg') - stack.parent_resource = mock.Mock() - stack.parent_resource.stack = None + stack._parent_resource = mock.Mock() + stack._parent_resource.stack = None self.assertEqual(stack, stack.root_stack) def test_root_stack_with_parent(self): @@ -1118,8 +1121,8 @@ class StackTest(common.HeatTestCase): stack = parser.Stack(self.ctx, 'test_stack', parser.Template(tpl), status_reason='blarg') - stack.parent_resource = mock.Mock() - stack.parent_resource.stack.root_stack = 'test value' + stack._parent_resource = mock.Mock() + stack._parent_resource.stack.root_stack = 'test value' self.assertEqual('test value', stack.root_stack) def test_load_parent_resource(self): diff --git a/heat/tests/test_software_deployment.py b/heat/tests/test_software_deployment.py index e69581e08..c1d1a5a88 100644 --- a/heat/tests/test_software_deployment.py +++ b/heat/tests/test_software_deployment.py @@ -1081,5 +1081,5 @@ class SoftwareDeploymentsTest(common.HeatTestCase): def test_validate(self): stack = utils.parse_stack(self.template) snip = stack.t.resource_definitions(stack)['deploy_mysql'] - resg = sd.SoftwareDeployments('test', snip, stack) + resg = sd.SoftwareDeployments('deploy_mysql', snip, stack) self.assertIsNone(resg.validate()) diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index ba3c7649f..d82a3f6b7 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -248,7 +248,7 @@ class StackResourceTest(common.HeatTestCase): env='environment', timeout_mins=None, disable_rollback=True, - parent_resource=parent_resource, + parent_resource=parent_resource.name, owner_id=self.parent_stack.id, user_creds_id=self.parent_stack.user_creds_id, stack_user_project_id=self.parent_stack.stack_user_project_id, @@ -288,7 +288,7 @@ class StackResourceTest(common.HeatTestCase): env='environment', timeout_mins=None, disable_rollback=True, - parent_resource=parent_resource, + parent_resource=parent_resource.name, owner_id=self.parent_stack.id, user_creds_id=self.parent_stack.user_creds_id, stack_user_project_id=self.parent_stack.stack_user_project_id, @@ -436,10 +436,7 @@ class StackResourceTest(common.HeatTestCase): def test_update_with_template_validates(self): """Updating a stack with a template validates the created stack.""" - create_result = self.parent_resource.create_with_template( - self.simple_template, {}) - while not create_result.step(): - pass + self.parent_resource._nested = mock.MagicMock() template = self.simple_template.copy() template['Parameters']['WebServer'] = {'Type': 'String'} @@ -454,10 +451,11 @@ class StackResourceTest(common.HeatTestCase): self.stack = self.parent_resource.nested() self.parent_resource._nested = None + self.parent_resource.resource_id = 319 self.m.StubOutWithMock(parser.Stack, 'load') parser.Stack.load(self.parent_resource.context, self.parent_resource.resource_id, - parent_resource=self.parent_resource, + parent_resource=self.parent_resource.name, show_deleted=False, force_reload=False).AndReturn('s') self.m.ReplayAll() @@ -475,7 +473,7 @@ class StackResourceTest(common.HeatTestCase): stack = parser.Stack.load( self.parent_resource.context, self.parent_resource.resource_id, - parent_resource=self.parent_resource, + parent_resource=self.parent_resource.name, show_deleted=False) stack.state_set(parser.Stack.CREATE, parser.Stack.FAILED, "foo") self.assertEqual(expected_state, self.parent_resource.nested().state) @@ -506,7 +504,7 @@ class StackResourceTest(common.HeatTestCase): self.m.StubOutWithMock(parser.Stack, 'load') parser.Stack.load(self.parent_resource.context, self.parent_resource.resource_id, - parent_resource=self.parent_resource, + parent_resource=self.parent_resource.name, show_deleted=False, force_reload=False) self.m.ReplayAll() @@ -514,14 +512,18 @@ class StackResourceTest(common.HeatTestCase): self.assertRaises(exception.NotFound, self.parent_resource.nested) self.m.VerifyAll() - def test_delete_nested_ok(self): - nested = self.m.CreateMockAnything() - self.m.StubOutWithMock(stack_resource.StackResource, 'nested') - stack_resource.StackResource.nested().AndReturn(nested) - nested.delete() + def test_load_nested_force_reload_none(self): + self.parent_resource._nested = mock.MagicMock() + self.parent_resource.resource_id = '90-8' + self.m.StubOutWithMock(parser.Stack, 'load') + parser.Stack.load(self.parent_resource.context, + self.parent_resource.resource_id, + parent_resource=self.parent_resource.name, + show_deleted=False, + force_reload=True).AndReturn(None) self.m.ReplayAll() - - self.parent_resource.delete_nested() + self.assertRaises(exception.NotFound, self.parent_resource.nested, + force_reload=True) self.m.VerifyAll() def test_delete_nested_not_found_nested_stack(self): @@ -534,7 +536,7 @@ class StackResourceTest(common.HeatTestCase): parser.Stack.load( self.parent_resource.context, self.parent_resource.resource_id, - parent_resource=self.parent_resource, + parent_resource=self.parent_resource.name, show_deleted=False, force_reload=False ).AndRaise(exception.NotFound('')) self.m.ReplayAll() @@ -706,8 +708,9 @@ class StackResourceAttrTest(common.HeatTestCase): nested.validate().AndReturn(True) self.m.StubOutWithMock(stack_resource.StackResource, '_parse_nested_stack') + name = '%s-%s' % (self.parent_stack.name, self.parent_resource.name) stack_resource.StackResource._parse_nested_stack( - self.parent_stack.name, 'foo', {}).AndReturn(nested) + name, 'foo', {}).AndReturn(nested) self.m.ReplayAll() self.parent_resource.validate_nested_stack()