Merge "Validate TemplateResource schema when based on other templates"
This commit is contained in:
commit
cff9dda9c3
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user