Merge "Validate TemplateResource schema when based on other templates"

This commit is contained in:
Jenkins 2015-09-16 11:02:59 +00:00 committed by Gerrit Code Review
commit cff9dda9c3
4 changed files with 117 additions and 19 deletions

View File

@ -128,7 +128,7 @@ class ClassResourceInfo(ResourceInfo):
"""Store the mapping of resource name to python class implementation."""
description = 'Plugin'
def get_class(self):
def get_class(self, files=None):
return self.value
@ -144,12 +144,12 @@ class TemplateResourceInfo(ResourceInfo):
self.template_name = value
self.value = self.template_name
def get_class(self):
def get_class(self, files=None):
from heat.engine.resources import template_resource
env = self.registry.environment
return template_resource.generate_class(str(self.name),
self.template_name,
env)
env, files=files)
class MapResourceInfo(ResourceInfo):
@ -159,7 +159,7 @@ class MapResourceInfo(ResourceInfo):
"""
description = 'Mapping'
def get_class(self):
def get_class(self, files=None):
return None
def get_resource_info(self, resource_type=None, resource_name=None):
@ -363,7 +363,7 @@ class ResourceRegistry(object):
yield self._registry[pattern]
def get_resource_info(self, resource_type, resource_name=None,
registry_type=None):
registry_type=None, ignore=None):
"""Find possible matches to the resource type and name.
chain the results from the global and user registry to find
a match.
@ -395,6 +395,8 @@ class ResourceRegistry(object):
match = info.get_resource_info(resource_type,
resource_name)
if registry_type is None or isinstance(match, registry_type):
if ignore is not None and match == ignore:
continue
# NOTE(prazumovsky): if resource_type defined in outer env
# there is a risk to lose it due to h-eng restarting, so
# store it to local env (exclude ClassResourceInfo because it
@ -403,10 +405,10 @@ class ResourceRegistry(object):
if (match and not match.user_resource and
not isinstance(info, (TemplateResourceInfo,
ClassResourceInfo))):
self._register_info([resource_type], info)
self._register_info([resource_type], info)
return match
def get_class(self, resource_type, resource_name=None):
def get_class(self, resource_type, resource_name=None, files=None):
if resource_type == "":
msg = _('Resource "%s" has no type') % resource_name
raise exception.InvalidResourceType(message=msg)
@ -422,7 +424,7 @@ class ResourceRegistry(object):
resource_name=resource_name)
if info is None:
raise exception.ResourceTypeNotFound(type_name=resource_type)
return info.get_class()
return info.get_class(files=files)
def as_dict(self):
"""Return user resources in a dict format."""
@ -564,8 +566,9 @@ class Environment(object):
self.stack_lifecycle_plugins.append((stack_lifecycle_name,
stack_lifecycle_class))
def get_class(self, resource_type, resource_name=None):
return self.registry.get_class(resource_type, resource_name)
def get_class(self, resource_type, resource_name=None, files=None):
return self.registry.get_class(resource_type, resource_name,
files=files)
def get_types(self,
cnxt=None,
@ -578,9 +581,9 @@ class Environment(object):
version=version)
def get_resource_info(self, resource_type, resource_name=None,
registry_type=None):
registry_type=None, ignore=None):
return self.registry.get_resource_info(resource_type, resource_name,
registry_type)
registry_type, ignore=ignore)
def get_constraint(self, name):
return self.constraints.get(name)

View File

@ -133,7 +133,8 @@ class Resource(object):
registry = stack.env.registry
try:
ResourceClass = registry.get_class(definition.resource_type,
resource_name=name)
resource_name=name,
files=stack.t.files)
except exception.TemplateNotFound:
ResourceClass = template_resource.TemplateResource

View File

@ -27,8 +27,12 @@ from heat.engine.resources import stack_resource
from heat.engine import template
def generate_class(name, template_name, env):
data = TemplateResource.get_template_file(template_name, ('file',))
def generate_class(name, template_name, env, files=None):
data = None
if files is not None:
data = files.get(template_name)
if data is None:
data = TemplateResource.get_template_file(template_name, ('file',))
tmpl = template.Template(template_format.parse(data))
props, attrs = TemplateResource.get_schemas(tmpl, env.param_defaults)
cls = type(name, (TemplateResource,),
@ -255,15 +259,15 @@ class TemplateResource(stack_resource.StackResource):
except ValueError as ex:
msg = _("Failed to retrieve template data: %s") % ex
raise exception.StackValidationFailed(message=msg)
cri = self.stack.env.get_resource_info(
fri = self.stack.env.get_resource_info(
self.type(),
resource_name=self.name,
registry_type=environment.ClassResourceInfo)
ignore=self.resource_info)
# If we're using an existing resource type as a facade for this
# template, check for compatibility between the interfaces.
if cri is not None and not isinstance(self, cri.get_class()):
facade_cls = cri.get_class()
if fri is not None:
facade_cls = fri.get_class(files=self.stack.t.files)
self._validate_against_facade(facade_cls)
return super(TemplateResource, self).validate()

View File

@ -12,6 +12,8 @@
import json
from heatclient import exc as heat_exceptions
import six
import yaml
from heat_integrationtests.common import test
@ -713,3 +715,91 @@ resources:
self.stack_suspend(stack_identifier=stack_identifier)
self.stack_resume(stack_identifier=stack_identifier)
class ValidateFacadeTest(test.HeatIntegrationTest):
"""Prove that nested stack errors don't suck."""
template = '''
heat_template_version: 2015-10-15
resources:
thisone:
type: OS::Thingy
properties:
one: pre
two: post
outputs:
one:
value: {get_attr: [thisone, here-it-is]}
'''
templ_facade = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
outputs:
here-it-is:
value: noop
'''
env = '''
resource_registry:
OS::Thingy: facade.yaml
resources:
thisone:
OS::Thingy: concrete.yaml
'''
def setUp(self):
super(ValidateFacadeTest, self).setUp()
self.client = self.orchestration_client
def test_missing_param(self):
templ_missing_parameter = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
resources:
str:
type: OS::Heat::RandomString
outputs:
here-it-is:
value:
not-important
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_parameter},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Required property two for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))
def test_missing_output(self):
templ_missing_output = '''
heat_template_version: 2015-04-30
parameters:
one:
type: string
two:
type: string
resources:
str:
type: OS::Heat::RandomString
'''
try:
self.stack_create(
template=self.template,
environment=self.env,
files={'facade.yaml': self.templ_facade,
'concrete.yaml': templ_missing_output},
expected_status='CREATE_FAILED')
except heat_exceptions.HTTPBadRequest as exc:
exp = ('ERROR: Attribute here-it-is for facade '
'OS::Thingy missing in provider')
self.assertEqual(exp, six.text_type(exc))