From 3a96fd7e257633d4078d65bca02bfc261f074e18 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Wed, 31 Jan 2018 11:39:03 -0500 Subject: [PATCH] Improve error reporting for missing nested template When the template for a TemplateResource was missing from the files dict and not otherwise available, we provided misleading feedback when validating the parent template. First, we log that we are fetching the template from a URL, even though we would not actually attempt to do so if it is a file:/// URL. Move the log to after the allowed scheme check, and log debug to indicate when we cannot find a template file. While an exception would be generated in TemplateResource._generate_schema() at resource initialisation time, it is suppressed and saved for later. An empty template is used to generate the properties schema. The exception is re-raised when validate() is called, but during a template validation we only call validate_template(), not validate(), so the first error we run into will be a mismatch of property names. (If no property values were passed in the parent template, we may not even get an error even though we won't be able to create a stack from the given data.) Also re-raise the stored exception at the beginning of validate_template() so that users will see the true source of the error. Change-Id: I1bc100684e1b84fc9ac54ef523d798b317e4dc51 Story: #1739447 Task: 22219 --- heat/common/urlfetch.py | 4 ++-- heat/engine/resources/template_resource.py | 18 ++++++++++++++---- .../functional/test_template_resource.py | 4 +++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/heat/common/urlfetch.py b/heat/common/urlfetch.py index 04e0be1f1a..1ef22b40c4 100644 --- a/heat/common/urlfetch.py +++ b/heat/common/urlfetch.py @@ -39,13 +39,13 @@ def get(url, allowed_schemes=('http', 'https')): the allowed_schemes argument. Raise an IOError if getting the data fails. """ - LOG.info('Fetching data from %s', url) - components = urllib.parse.urlparse(url) if components.scheme not in allowed_schemes: raise URLFetchError(_('Invalid URL scheme %s') % components.scheme) + LOG.info('Fetching data from %s', url) + if components.scheme == 'file': try: return urllib.request.urlopen(url).read() diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py index 7abf6d2876..ba5e58ade6 100644 --- a/heat/engine/resources/template_resource.py +++ b/heat/engine/resources/template_resource.py @@ -195,6 +195,10 @@ class TemplateResource(stack_resource.StackResource): reported_excp = None t_data = self.stack.t.files.get(self.template_url) stored_t_data = t_data + + if t_data is None: + LOG.debug('TemplateResource data file "%s" not found in files.', + self.template_url) if not t_data and self.template_url.endswith((".yaml", ".template")): try: t_data = self.get_template_file(self.template_url, @@ -257,9 +261,8 @@ class TemplateResource(stack_resource.StackResource): raise exception.StackValidationFailed(message=msg) def validate(self): - if self.validation_exception is not None: - msg = six.text_type(self.validation_exception) - raise exception.StackValidationFailed(message=msg) + # Calls validate_template() + result = super(TemplateResource, self).validate() try: self.template_data() @@ -280,7 +283,14 @@ class TemplateResource(stack_resource.StackResource): facade_cls = fri.get_class(files=self.stack.t.files) self._validate_against_facade(facade_cls) - return super(TemplateResource, self).validate() + return result + + def validate_template(self): + if self.validation_exception is not None: + msg = six.text_type(self.validation_exception) + raise exception.StackValidationFailed(message=msg) + + return super(TemplateResource, self).validate_template() def handle_adopt(self, resource_data=None): return self.create_with_template(self.child_template(), diff --git a/heat_integrationtests/functional/test_template_resource.py b/heat_integrationtests/functional/test_template_resource.py index c05912c4ce..21a4ec061a 100644 --- a/heat_integrationtests/functional/test_template_resource.py +++ b/heat_integrationtests/functional/test_template_resource.py @@ -792,9 +792,11 @@ outputs: value: not-important ''' + template = yaml.safe_load(self.template) + del template['resources']['thisone']['properties']['two'] try: self.stack_create( - template=self.template, + template=yaml.safe_dump(template), environment=self.env, files={'facade.yaml': self.templ_facade, 'concrete.yaml': templ_missing_parameter},