Allow selectively disabling resource validation
For template-validate to use the normal stack.validate code instead of reimplementing different validation logic, we need to disable the resource plugin validate() methods, because in nearly all cases these refer to resource property values, which don't exist at template-validate time, where parameters aren't mandatory. We do however want to ensure structural/syntax issues in the resource definition are caught as early as possible, so add a stack resource_validate attribute (defaulted to True) which enables selecting only the resource.Resource superclass validate logic, so we can defer the full value validation until create without maintaining completely separate logic. Change-Id: I501c8c36e923baa8d9b3f0395aaee41f7585ebb1 Partial-Bug: #1467573
This commit is contained in:
parent
99670fb1cc
commit
99ae1702ff
@ -1080,8 +1080,24 @@ class Resource(object):
|
||||
return name[0:2] + '-' + name[-postfix_length:]
|
||||
|
||||
def validate(self):
|
||||
LOG.info(_LI('Validating %s'), six.text_type(self))
|
||||
'''
|
||||
Validate the resource.
|
||||
|
||||
This may be overridden by resource plugins to add extra
|
||||
validation logic specific to the resource implementation.
|
||||
'''
|
||||
LOG.info(_LI('Validating %s'), six.text_type(self))
|
||||
return self.validate_template()
|
||||
|
||||
def validate_template(self):
|
||||
'''
|
||||
Validate strucural/syntax aspects of the resource definition.
|
||||
|
||||
Resource plugins should not override this, because this interface
|
||||
is expected to be called pre-create so things normally valid
|
||||
in an overridden validate() such as accessing properties
|
||||
may not work.
|
||||
'''
|
||||
function.validate(self.t)
|
||||
self.validate_deletion_policy(self.t.deletion_policy())
|
||||
self.t.update_policy(self.update_policy_schema,
|
||||
|
@ -96,7 +96,7 @@ class Stack(collections.Mapping):
|
||||
use_stored_context=False, username=None,
|
||||
nested_depth=0, strict_validate=True, convergence=False,
|
||||
current_traversal=None, tags=None, prev_raw_template_id=None,
|
||||
current_deps=None, cache_data=None):
|
||||
current_deps=None, cache_data=None, resource_validate=True):
|
||||
|
||||
'''
|
||||
Initialise from a context, name, Template object and (optionally)
|
||||
@ -141,7 +141,6 @@ class Stack(collections.Mapping):
|
||||
self.updated_time = updated_time
|
||||
self.user_creds_id = user_creds_id
|
||||
self.nested_depth = nested_depth
|
||||
self.strict_validate = strict_validate
|
||||
self.convergence = convergence
|
||||
self.current_traversal = current_traversal
|
||||
self.tags = tags
|
||||
@ -151,6 +150,19 @@ class Stack(collections.Mapping):
|
||||
self._worker_client = None
|
||||
self._convg_deps = None
|
||||
|
||||
# strict_validate can be used to disable value validation
|
||||
# in the resource properties schema, this is useful when
|
||||
# performing validation when properties reference attributes
|
||||
# for not-yet-created resources (which return None)
|
||||
self.strict_validate = strict_validate
|
||||
|
||||
# resource_validate can be used to disable resource plugin subclass
|
||||
# validate methods, which is useful when you want to validate
|
||||
# template integrity but some parameters may not be provided
|
||||
# at all, thus we can't yet reference property values such as is
|
||||
# commonly done in plugin validate() methods
|
||||
self.resource_validate = resource_validate
|
||||
|
||||
if use_stored_context:
|
||||
self.context = self.stored_context()
|
||||
|
||||
@ -605,7 +617,7 @@ class Stack(collections.Mapping):
|
||||
@profiler.trace('Stack.validate', hide_args=False)
|
||||
def validate(self):
|
||||
'''
|
||||
Validates the template.
|
||||
Validates the stack.
|
||||
'''
|
||||
# TODO(sdake) Should return line number of invalid reference
|
||||
|
||||
@ -634,7 +646,10 @@ class Stack(collections.Mapping):
|
||||
|
||||
for res in self.dependencies:
|
||||
try:
|
||||
result = res.validate()
|
||||
if self.resource_validate:
|
||||
result = res.validate()
|
||||
else:
|
||||
result = res.validate_template()
|
||||
except exception.HeatException as ex:
|
||||
LOG.debug('%s', ex)
|
||||
raise ex
|
||||
|
@ -145,6 +145,9 @@ class HeatTestCase(testscenarios.WithScenarios,
|
||||
generic_rsrc.ResourceWithProps)
|
||||
resource._register_class('ResourceWithPropsRefPropOnDelete',
|
||||
generic_rsrc.ResourceWithPropsRefPropOnDelete)
|
||||
resource._register_class(
|
||||
'ResourceWithPropsRefPropOnValidate',
|
||||
generic_rsrc.ResourceWithPropsRefPropOnValidate)
|
||||
resource._register_class('StackUserResourceType',
|
||||
generic_rsrc.StackUserResource)
|
||||
resource._register_class('ResourceWithResourceIDType',
|
||||
|
@ -102,6 +102,12 @@ class ResourceWithPropsRefPropOnDelete(ResourceWithProps):
|
||||
return self.properties['FooInt'] is not None
|
||||
|
||||
|
||||
class ResourceWithPropsRefPropOnValidate(ResourceWithProps):
|
||||
def validate(self):
|
||||
super(ResourceWithPropsRefPropOnValidate, self).validate()
|
||||
self.properties['FooInt'] is not None
|
||||
|
||||
|
||||
class ResourceWithPropsAndAttrs(ResourceWithProps):
|
||||
attributes_schema = {'Bar': attributes.Schema('Something.')}
|
||||
|
||||
|
@ -1803,6 +1803,68 @@ class StackTest(common.HeatTestCase):
|
||||
self.stack.strict_validate = False
|
||||
self.assertIsNone(self.stack.validate())
|
||||
|
||||
def test_disable_validate_required_param(self):
|
||||
tmpl = template_format.parse("""
|
||||
heat_template_version: 2013-05-23
|
||||
parameters:
|
||||
aparam:
|
||||
type: number
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsRefPropOnValidate
|
||||
properties:
|
||||
FooInt: {get_param: aparam}
|
||||
""")
|
||||
self.stack = stack.Stack(self.ctx, 'stack_with_reqd_param',
|
||||
template.Template(tmpl))
|
||||
|
||||
ex = self.assertRaises(exception.UserParameterMissing,
|
||||
self.stack.validate)
|
||||
self.assertIn("The Parameter (aparam) was not provided",
|
||||
six.text_type(ex))
|
||||
|
||||
self.stack.strict_validate = False
|
||||
ex = self.assertRaises(exception.StackValidationFailed,
|
||||
self.stack.validate)
|
||||
self.assertIn("The Parameter (aparam) was not provided",
|
||||
six.text_type(ex))
|
||||
|
||||
self.stack.resource_validate = False
|
||||
self.assertIsNone(self.stack.validate())
|
||||
|
||||
def test_nodisable_validate_tmpl_err(self):
|
||||
tmpl = template_format.parse("""
|
||||
heat_template_version: 2013-05-23
|
||||
resources:
|
||||
AResource:
|
||||
type: ResourceWithPropsRefPropOnValidate
|
||||
depends_on: noexist
|
||||
properties:
|
||||
FooInt: 123
|
||||
""")
|
||||
self.stack = stack.Stack(self.ctx, 'stack_with_tmpl_err',
|
||||
template.Template(tmpl))
|
||||
|
||||
ex = self.assertRaises(exception.InvalidTemplateReference,
|
||||
self.stack.validate)
|
||||
self.assertIn(
|
||||
"The specified reference \"noexist\" (in AResource) is incorrect",
|
||||
six.text_type(ex))
|
||||
|
||||
self.stack.strict_validate = False
|
||||
ex = self.assertRaises(exception.InvalidTemplateReference,
|
||||
self.stack.validate)
|
||||
self.assertIn(
|
||||
"The specified reference \"noexist\" (in AResource) is incorrect",
|
||||
six.text_type(ex))
|
||||
|
||||
self.stack.resource_validate = False
|
||||
ex = self.assertRaises(exception.InvalidTemplateReference,
|
||||
self.stack.validate)
|
||||
self.assertIn(
|
||||
"The specified reference \"noexist\" (in AResource) is incorrect",
|
||||
six.text_type(ex))
|
||||
|
||||
def test_validate_property_getatt(self):
|
||||
tmpl = {
|
||||
'HeatTemplateFormatVersion': '2012-12-12',
|
||||
|
Loading…
x
Reference in New Issue
Block a user