From b90991e00b4e0029748c28c5258607f266f1bf85 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 20 Feb 2017 15:12:49 -0500 Subject: [PATCH] Pass on outputs errors to parent stacks If getting an output from a child stack fails with an error, we didn't pass on the error message to the parent stack that was requesting it but instead reported essentially that the given output did not exist. Change-Id: I5653baf310a29dc4829ad570c769cf67ce12695e Partial-Bug: #1599114 --- heat/common/exception.py | 4 ++++ heat/engine/resources/stack_resource.py | 22 +++++++++++++++------- heat/tests/test_provider_template.py | 2 +- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/heat/common/exception.py b/heat/common/exception.py index e01d453927..c842dc61a0 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -158,6 +158,10 @@ class InvalidTemplateReference(HeatException): ' is incorrect.') +class TemplateOutputError(HeatException): + msg_fmt = _('Error in %(resource)s output %(attribute)s: %(message)s') + + class InvalidExternalResourceDependency(HeatException): msg_fmt = _("Invalid dependency with external %(resource_type)s " "resource: %(external_id)s") diff --git a/heat/engine/resources/stack_resource.py b/heat/engine/resources/stack_resource.py index e0bc85b12a..9b27f1fe1a 100644 --- a/heat/engine/resources/stack_resource.py +++ b/heat/engine/resources/stack_resource.py @@ -585,8 +585,10 @@ class StackResource(resource.Resource): particular exception, not KeyError, being raised if the key does not exist.) """ - if self._outputs is None or self._outputs.get(op, - NotImplemented) is None: + if (self._outputs is None or + (op in self._outputs and + rpc_api.OUTPUT_ERROR not in self._outputs[op] and + self._outputs[op].get(rpc_api.OUTPUT_VALUE) is None)): stack_identity = self.nested_identifier() if stack_identity is None: return @@ -595,14 +597,20 @@ class StackResource(resource.Resource): if not stack: return outputs = stack[0].get(rpc_api.STACK_OUTPUTS) or {} - self._outputs = {o[rpc_api.OUTPUT_KEY]: o[rpc_api.OUTPUT_VALUE] - for o in outputs if rpc_api.OUTPUT_ERROR not in o} + self._outputs = {o[rpc_api.OUTPUT_KEY]: o for o in outputs} - try: - return self._outputs[op] - except KeyError: + if op not in self._outputs: raise exception.InvalidTemplateAttribute(resource=self.name, key=op) + output_data = self._outputs[op] + if rpc_api.OUTPUT_ERROR in output_data: + raise exception.TemplateOutputError( + resource=self.name, + attribute=op, + message=output_data[rpc_api.OUTPUT_ERROR]) + + return output_data[rpc_api.OUTPUT_VALUE] + def _resolve_attribute(self, name): return self.get_output(name) diff --git a/heat/tests/test_provider_template.py b/heat/tests/test_provider_template.py index 6df378a276..5e17bd5638 100644 --- a/heat/tests/test_provider_template.py +++ b/heat/tests/test_provider_template.py @@ -319,7 +319,7 @@ class ProviderTemplateTest(common.HeatTestCase): output = {'outputs': [{'output_key': 'Foo', 'output_value': None, 'output_error': 'it is all bad'}]} temp_res._rpc_client.show_stack.return_value = [output] - self.assertRaises(exception.InvalidTemplateAttribute, + self.assertRaises(exception.TemplateOutputError, temp_res.FnGetAtt, 'Foo') def test_properties_normal(self):