diff --git a/heat/engine/api.py b/heat/engine/api.py index 47bffd5ad..46b9b5beb 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -17,6 +17,7 @@ from oslo_log import log as logging from oslo_utils import timeutils import six +from heat.common import exception from heat.common.i18n import _ from heat.common.i18n import _LE from heat.common import param_utils @@ -200,9 +201,13 @@ def format_stack_resource(resource, detail=True, with_props=False, rpc_api.RES_REQUIRED_BY: resource.required_by(), } - if (hasattr(resource, 'nested') and callable(resource.nested) and - resource.nested() is not None): - res[rpc_api.RES_NESTED_STACK_ID] = dict(resource.nested().identifier()) + try: + if (hasattr(resource, 'nested') and callable(resource.nested) and + resource.nested() is not None): + res[rpc_api.RES_NESTED_STACK_ID] = dict( + resource.nested().identifier()) + except exception.NotFound: + pass if resource.stack.parent_resource_name: res[rpc_api.RES_PARENT_RESOURCE] = resource.stack.parent_resource_name diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 39e51c010..b03c68e65 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -19,6 +19,7 @@ import mock from oslo_utils import timeutils import six +from heat.common import exception from heat.common import identifier from heat.common import template_format from heat.engine import api @@ -205,6 +206,28 @@ class FormatTest(common.HeatTestCase): formatted = api.format_stack_resource(res, False) self.assertEqual(resource_keys, set(six.iterkeys(formatted))) + def test_format_stack_resource_with_nested_stack_not_found(self): + res = self.stack['generic1'] + res.nested = mock.Mock() + res.nested.side_effect = exception.NotFound() + + resource_keys = set(( + rpc_api.RES_CREATION_TIME, + rpc_api.RES_UPDATED_TIME, + rpc_api.RES_NAME, + rpc_api.RES_PHYSICAL_ID, + rpc_api.RES_ACTION, + rpc_api.RES_STATUS, + rpc_api.RES_STATUS_DATA, + rpc_api.RES_TYPE, + rpc_api.RES_ID, + rpc_api.RES_STACK_ID, + rpc_api.RES_STACK_NAME, + rpc_api.RES_REQUIRED_BY)) + + formatted = api.format_stack_resource(res, False) + self.assertEqual(resource_keys, set(six.iterkeys(formatted))) + def test_format_stack_resource_with_nested_stack_empty(self): res = self.stack['generic1'] nested_id = {'foo': 'bar'} diff --git a/heat_integrationtests/functional/test_resource_group.py b/heat_integrationtests/functional/test_resource_group.py index a41f84106..ab3b355b3 100644 --- a/heat_integrationtests/functional/test_resource_group.py +++ b/heat_integrationtests/functional/test_resource_group.py @@ -472,3 +472,43 @@ outputs: stack = self.client.stacks.get(stack_identifier) self.assertEqual('goopie', self._stack_output(stack, 'test0')) self.assertEqual('different', self._stack_output(stack, 'test1')) + + +class ResourceGroupErrorResourceTest(test.HeatIntegrationTest): + template = ''' +heat_template_version: "2013-05-23" +resources: + group1: + type: OS::Heat::ResourceGroup + properties: + count: 2 + resource_def: + type: fail.yaml +''' + nested_templ = ''' +heat_template_version: "2013-05-23" +resources: + oops: + type: OS::Heat::TestResource + properties: + fail: true + wait_secs: 2 +''' + + def setUp(self): + super(ResourceGroupErrorResourceTest, self).setUp() + self.client = self.orchestration_client + + def test_fail(self): + stack_identifier = self.stack_create( + template=self.template, + files={'fail.yaml': self.nested_templ}, + expected_status='CREATE_FAILED', + enable_cleanup=False) + stack = self.client.stacks.get(stack_identifier) + + self.assertEqual('CREATE_FAILED', stack.stack_status) + self.client.stacks.delete(stack_identifier) + self._wait_for_stack_status( + stack_identifier, 'DELETE_COMPLETE', + success_on_not_found=True)