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:
Steven Hardy 2015-09-15 18:59:09 +01:00
parent 99670fb1cc
commit 99ae1702ff
5 changed files with 107 additions and 5 deletions

View File

@ -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,

View File

@ -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

View File

@ -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',

View File

@ -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.')}

View File

@ -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',