Return error with path in function validate

Error with path to the function is returned now in the
case of failed validation.

Closes-Bug: #1625723
Change-Id: Ib96a944c751543f3eb7cb5f8ba09911fc5cae586
This commit is contained in:
Oleksii Chuprykov 2016-09-20 18:18:03 +03:00
parent e03a063084
commit ec45f0e47e
7 changed files with 63 additions and 44 deletions

View File

@ -18,6 +18,7 @@ import weakref
import six
from heat.common import exception
from heat.common.i18n import _
@ -198,16 +199,29 @@ def resolve(snippet):
return snippet
def validate(snippet):
def validate(snippet, path=''):
if isinstance(snippet, Function):
snippet.validate()
try:
snippet.validate()
except AssertionError:
raise
except Exception as e:
path = '.'.join([path, snippet.fn_name])
raise exception.StackValidationFailed(
path=path, message=six.text_type(e))
elif isinstance(snippet, collections.Mapping):
for v in six.itervalues(snippet):
validate(v)
def mkpath(key):
return '.'.join([path, key])
for k, v in six.iteritems(snippet):
validate(v, mkpath(k))
elif (not isinstance(snippet, six.string_types) and
isinstance(snippet, collections.Iterable)):
for v in snippet:
validate(v)
def mkpath(indx):
return '.'.join([path, '[%d]' % indx])
for i, v in enumerate(snippet):
validate(v, mkpath(i))
def dependencies(snippet, path=''):

View File

@ -25,9 +25,9 @@ class OutputDefinition(object):
self._resolved_value = None
self._description = description
def validate(self):
def validate(self, path=''):
"""Validate the output value without resolving it."""
function.validate(self._value)
function.validate(self._value, path)
def dep_attrs(self, resource_name):
"""Iterate over attributes of a given resource that this references.

View File

@ -1501,8 +1501,8 @@ class Resource(object):
self.stack.context,
self.t.resource_type
)
function.validate(self.t)
path = '.'.join([self.stack.t.RESOURCES, self.name])
function.validate(self.t, path)
self.validate_deletion_policy(self.t.deletion_policy())
self.t.update_policy(self.update_policy_schema,
self.context).validate()

View File

@ -838,15 +838,13 @@ class Stack(collections.Mapping):
for op_name, output in six.iteritems(self.outputs):
try:
output.validate()
except exception.StackValidationFailed as ex:
path = '.'.join([self.t.OUTPUTS, op_name,
self.t.OUTPUT_VALUE])
output.validate(path)
except exception.StackValidationFailed:
raise
except AssertionError:
raise
except Exception as ex:
raise exception.StackValidationFailed(
error='Validation error in output "%s"' % op_name,
message=six.text_type(ex))
def requires_deferred_auth(self):
"""Determine whether to perform API requests with deferred auth.

View File

@ -178,8 +178,9 @@ class ValidateTest(common.HeatTestCase):
def test_validate_func(self):
self.assertIsNone(function.validate(self.func))
self.func = TestFunction(None, 'foo', ['bar'])
ex = self.assertRaises(TypeError, function.validate, self.func)
self.assertEqual('Need more arguments', six.text_type(ex))
self.assertRaisesRegexp(exception.StackValidationFailed,
'.foo: Need more arguments',
function.validate, self.func)
def test_validate_dict(self):
snippet = {'foo': 'bar', 'blarg': self.func}
@ -187,8 +188,9 @@ class ValidateTest(common.HeatTestCase):
self.func = TestFunction(None, 'foo', ['bar'])
snippet = {'foo': 'bar', 'blarg': self.func}
ex = self.assertRaises(TypeError, function.validate, snippet)
self.assertEqual('Need more arguments', six.text_type(ex))
self.assertRaisesRegexp(exception.StackValidationFailed,
'.blarg.foo: Need more arguments',
function.validate, snippet)
def test_validate_list(self):
snippet = ['foo', 'bar', 'baz', 'blarg', self.func]
@ -196,8 +198,9 @@ class ValidateTest(common.HeatTestCase):
self.func = TestFunction(None, 'foo', ['bar'])
snippet = {'foo': 'bar', 'blarg': self.func}
ex = self.assertRaises(TypeError, function.validate, snippet)
self.assertEqual('Need more arguments', six.text_type(ex))
self.assertRaisesRegexp(exception.StackValidationFailed,
'.blarg.foo: Need more arguments',
function.validate, snippet)
def test_validate_all(self):
snippet = ['foo', {'bar': ['baz', {'blarg': self.func}]}]
@ -205,8 +208,9 @@ class ValidateTest(common.HeatTestCase):
self.func = TestFunction(None, 'foo', ['bar'])
snippet = {'foo': 'bar', 'blarg': self.func}
ex = self.assertRaises(TypeError, function.validate, snippet)
self.assertEqual('Need more arguments', six.text_type(ex))
self.assertRaisesRegexp(exception.StackValidationFailed,
'.blarg.foo: Need more arguments',
function.validate, snippet)
class DependenciesTest(common.HeatTestCase):

View File

@ -1118,7 +1118,10 @@ class HOTemplateTest(common.HeatTestCase):
'data': {'var1': [1, 2, 3, 4]}}}
tmpl = template.Template(hot_newton_tpl_empty)
yaql = tmpl.parse(None, snippet)
self.assertRaises(ValueError, function.validate, yaql)
regxp = ('.yaql: Bad expression Parse error: unexpected end '
'of statement.')
self.assertRaisesRegexp(exception.StackValidationFailed, regxp,
function.validate, yaql)
def test_yaql_data_as_function(self):
snippet = {'yaql': {'expression': '$.data.var1.len()',
@ -1381,7 +1384,10 @@ conditions:
snippet = {'repeat': {'template': 'this is %var%',
'for_each': '%var%'}}
repeat = tmpl.parse(None, snippet)
self.assertRaises(TypeError, function.validate, repeat)
regxp = ('.repeat: The "for_each" argument to "repeat" '
'must contain a map')
self.assertRaisesRegexp(exception.StackValidationFailed, regxp,
function.validate, repeat)
def test_digest(self):
snippet = {'digest': ['md5', 'foobar']}
@ -1624,10 +1630,11 @@ conditions:
snippet = {'Fn::GetAZs': ''}
stack = parser.Stack(utils.dummy_context(), 'test_stack',
template.Template(hot_juno_tpl_empty))
error = self.assertRaises(exception.InvalidTemplateVersion,
function.validate,
stack.t.parse(stack, snippet))
self.assertIn(next(iter(snippet)), six.text_type(error))
regxp = '.Fn::GetAZs: The template version is invalid'
self.assertRaisesRegexp(exception.StackValidationFailed,
regxp,
function.validate,
stack.t.parse(stack, snippet))
def test_add_resource(self):
hot_tpl = template_format.parse('''

View File

@ -1846,13 +1846,11 @@ class StackTest(common.HeatTestCase):
self.stack = stack.Stack(self.ctx, 'stack_with_correct_outputs',
template.Template(tmpl))
ex = self.assertRaises(exception.StackValidationFailed,
self.stack.validate)
self.assertEqual('Validation error in output "Resource_attr": '
'The Referenced Attribute '
'(AResource Bar) is incorrect.',
six.text_type(ex))
self.assertRaisesRegexp(
exception.StackValidationFailed,
('Outputs.Resource_attr.Value.Fn::GetAtt: The Referenced '
'Attribute \(AResource Bar\) is incorrect.'),
self.stack.validate)
def test_incorrect_outputs_cfn_incorrect_reference(self):
tmpl = template_format.parse("""
@ -2212,13 +2210,11 @@ class StackTest(common.HeatTestCase):
self.stack = stack.Stack(self.ctx, 'stack_with_correct_outputs',
template.Template(tmpl))
ex = self.assertRaises(exception.StackValidationFailed,
self.stack.validate)
self.assertEqual('Validation error in output "resource_attr": '
'The Referenced Attribute '
'(AResource Bar) is incorrect.',
six.text_type(ex))
self.assertRaisesRegexp(
exception.StackValidationFailed,
('outputs.resource_attr.value.get_attr: The Referenced Attribute '
'\(AResource Bar\) is incorrect.'),
self.stack.validate)
def test_snapshot_save_called_first(self):
def snapshotting_called_first(stack, action, status, reason):