Improve validation for some functions
Move validation code to __init__ method for these list of functions: Removed, Repeat, Yaql, so now validation error will be found during template.parse() and returned with a path to the function in template. Don't change validation for GetAttr, because it resolves some arguments and we need to parse template before we can use resolve(). Also, remove check for Removed function in translation, because now we can't have Removed in properties. Change-Id: I4f2e1ebee37e82badcdca9110cadc649aa01be8c
This commit is contained in:
parent
e660cea228
commit
36bf170f34
|
@ -518,10 +518,9 @@ class Removed(function.Function):
|
|||
|
||||
Check the HOT guide for an equivalent native function.
|
||||
"""
|
||||
|
||||
def validate(self):
|
||||
def __init__(self, stack, fn_name, args):
|
||||
exp = (_("The function %s is not supported in this version of HOT.") %
|
||||
self.fn_name)
|
||||
fn_name)
|
||||
raise exception.InvalidTemplateVersion(explanation=exp)
|
||||
|
||||
def result(self):
|
||||
|
@ -561,10 +560,9 @@ class Repeat(function.Function):
|
|||
for_each:
|
||||
%var%: ['a', 'b', 'c']''')
|
||||
raise KeyError(_('"repeat" syntax should be %s') % example)
|
||||
self.validate_args()
|
||||
|
||||
def validate(self):
|
||||
super(Repeat, self).validate()
|
||||
|
||||
def validate_args(self):
|
||||
if not isinstance(self._for_each, function.Function):
|
||||
if not isinstance(self._for_each, collections.Mapping):
|
||||
raise TypeError(_('The "for_each" argument to "%s" must '
|
||||
|
@ -762,6 +760,7 @@ class Yaql(function.Function):
|
|||
var1: [3, 2, 1]''') % self.fn_name
|
||||
raise KeyError(_('"%(name)s" syntax should be %(example)s') % {
|
||||
'name': self.fn_name, 'example': example})
|
||||
self.validate_args()
|
||||
|
||||
def validate_expression(self, expression):
|
||||
try:
|
||||
|
@ -769,8 +768,7 @@ class Yaql(function.Function):
|
|||
except exceptions.YaqlException as yex:
|
||||
raise ValueError(_('Bad expression %s.') % yex)
|
||||
|
||||
def validate(self):
|
||||
super(Yaql, self).validate()
|
||||
def validate_args(self):
|
||||
if not isinstance(self._data,
|
||||
(collections.Mapping, function.Function)):
|
||||
raise TypeError(_('The "data" argument to "%s" must contain '
|
||||
|
|
|
@ -340,7 +340,8 @@ def parse(functions, stack, snippet, path=''):
|
|||
try:
|
||||
path = '.'.join([path, fn_name])
|
||||
return Func(stack, fn_name, recurse(args, path))
|
||||
except (ValueError, TypeError, KeyError) as e:
|
||||
except (ValueError, TypeError, KeyError,
|
||||
exception.InvalidTemplateVersion) as e:
|
||||
raise exception.StackValidationFailed(
|
||||
path=path,
|
||||
message=six.text_type(e))
|
||||
|
|
|
@ -185,8 +185,6 @@ class TranslationRule(object):
|
|||
# NOTE(prazumovsky): If property uses removed in HOT function,
|
||||
# we should not translate it for correct validating and raising
|
||||
# validation error.
|
||||
if isinstance(param, hot_funcs.Removed):
|
||||
raise AttributeError(_('Property uses removed function.'))
|
||||
if isinstance(param, (hot_funcs.GetParam, cfn_funcs.ParamRef)):
|
||||
try:
|
||||
return function.resolve(param)
|
||||
|
|
|
@ -914,8 +914,9 @@ class HOTemplateTest(common.HeatTestCase):
|
|||
snippet = {'yaql': {'expression': '$.data.var1.sum()',
|
||||
'data': 'mustbeamap'}}
|
||||
tmpl = template.Template(hot_newton_tpl_empty)
|
||||
msg = 'The "data" argument to "yaql" must contain a map.'
|
||||
self.assertRaisesRegexp(TypeError, msg, self.resolve, snippet, tmpl)
|
||||
msg = '.yaql: The "data" argument to "yaql" must contain a map.'
|
||||
self.assertRaisesRegexp(exception.StackValidationFailed,
|
||||
msg, self.resolve, snippet, tmpl)
|
||||
|
||||
def test_yaql_bogus_keys(self):
|
||||
snippet = {'yaql': {'expression': '1 + 3',
|
||||
|
@ -943,8 +944,8 @@ class HOTemplateTest(common.HeatTestCase):
|
|||
snippet = {'yaql': {'expression': 'invalid(',
|
||||
'data': {'var1': [1, 2, 3, 4]}}}
|
||||
tmpl = template.Template(hot_newton_tpl_empty)
|
||||
yaql = tmpl.parse(None, snippet)
|
||||
self.assertRaises(ValueError, function.validate, yaql)
|
||||
self.assertRaises(exception.StackValidationFailed,
|
||||
tmpl.parse, None, snippet)
|
||||
|
||||
def test_yaql_data_as_function(self):
|
||||
snippet = {'yaql': {'expression': '$.data.var1.len()',
|
||||
|
@ -1086,7 +1087,8 @@ class HOTemplateTest(common.HeatTestCase):
|
|||
# value given to for_each entry is not a list
|
||||
snippet = {'repeat': {'template': 'this is %var%',
|
||||
'for_each': {'%var%': 'a'}}}
|
||||
self.assertRaises(TypeError, self.resolve, snippet, tmpl)
|
||||
self.assertRaises(exception.StackValidationFailed,
|
||||
self.resolve, snippet, tmpl)
|
||||
|
||||
# misspelled template
|
||||
snippet = {'repeat': {'templte': 'this is %var%',
|
||||
|
@ -1100,8 +1102,8 @@ class HOTemplateTest(common.HeatTestCase):
|
|||
# for_each is not a map
|
||||
snippet = {'repeat': {'template': 'this is %var%',
|
||||
'for_each': '%var%'}}
|
||||
repeat = tmpl.parse(None, snippet)
|
||||
self.assertRaises(TypeError, function.validate, repeat)
|
||||
self.assertRaises(exception.StackValidationFailed,
|
||||
tmpl.parse, None, snippet)
|
||||
|
||||
def test_digest(self):
|
||||
snippet = {'digest': ['md5', 'foobar']}
|
||||
|
@ -1334,9 +1336,8 @@ class HOTemplateTest(common.HeatTestCase):
|
|||
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))
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
stack.t.parse, stack, snippet)
|
||||
self.assertIn(next(iter(snippet)), six.text_type(error))
|
||||
|
||||
def test_add_resource(self):
|
||||
|
|
|
@ -807,50 +807,6 @@ class TestTranslationRule(common.HeatTestCase):
|
|||
self.assertEqual(['white', 'roses', 'chrysanthemums'],
|
||||
props.get('far'))
|
||||
|
||||
def test_property_no_translation_removed_function(self):
|
||||
"""Test case when list property with sub-schema takes json param."""
|
||||
schema = {
|
||||
'far': properties.Schema(properties.Schema.LIST,
|
||||
schema=properties.Schema(
|
||||
properties.Schema.MAP,
|
||||
schema={
|
||||
'bar': properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
),
|
||||
'dar': properties.Schema(
|
||||
properties.Schema.STRING
|
||||
)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
class DummyStack(dict):
|
||||
@property
|
||||
def parameters(self):
|
||||
return mock.Mock()
|
||||
|
||||
param = hot_funcs.Removed(DummyStack(json_far='json_far'),
|
||||
'Ref',
|
||||
'json_far')
|
||||
param.parameters = {
|
||||
'json_far': parameters.JsonParam(
|
||||
'json_far',
|
||||
{'Type': 'Json'},
|
||||
'{"dar": "rad"}').value()}
|
||||
data = {'far': [param]}
|
||||
|
||||
props = properties.Properties(schema, data)
|
||||
|
||||
rule = translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.REPLACE,
|
||||
['far', 'bar'],
|
||||
value_name='dar')
|
||||
|
||||
rule.execute_rule()
|
||||
|
||||
self.assertEqual([param], props.data.get('far'))
|
||||
|
||||
def test_property_no_translation_if_user_parameter_missing(self):
|
||||
"""Test translation in the case of missing parameter"""
|
||||
schema = {
|
||||
|
|
Loading…
Reference in New Issue