Convert validate_template validation path
Currently the validation code for template-validate is different to that used for create/preview/update, which can lead to inconsistent results as the implementations have diverged. So instead align more closely with the actual validation, which should also enable easier validation of nested stacks which is currently not possible. Change-Id: Ibf93a170ab381a42a46ea414c3b134cbe0c3f232 Closes-Bug: #1467573
This commit is contained in:
parent
54116830fc
commit
ed64822d4d
@ -559,6 +559,17 @@ class EngineService(service.Service):
|
||||
raise exception.MissingCredentialError(required='X-Auth-Key')
|
||||
|
||||
def _validate_new_stack(self, cnxt, stack_name, parsed_template):
|
||||
if stack_object.Stack.get_by_name(cnxt, stack_name):
|
||||
raise exception.StackExists(stack_name=stack_name)
|
||||
|
||||
tenant_limit = cfg.CONF.max_stacks_per_tenant
|
||||
if stack_object.Stack.count_all(cnxt) >= tenant_limit:
|
||||
message = _("You have reached the maximum stacks per tenant, "
|
||||
"%d. Please delete some stacks.") % tenant_limit
|
||||
raise exception.RequestLimitExceeded(message=message)
|
||||
self._validate_template(cnxt, parsed_template)
|
||||
|
||||
def _validate_template(self, cnxt, parsed_template):
|
||||
try:
|
||||
parsed_template.validate()
|
||||
except AssertionError:
|
||||
@ -566,15 +577,6 @@ class EngineService(service.Service):
|
||||
except Exception as ex:
|
||||
raise exception.StackValidationFailed(message=six.text_type(ex))
|
||||
|
||||
if stack_object.Stack.get_by_name(cnxt, stack_name):
|
||||
raise exception.StackExists(stack_name=stack_name)
|
||||
|
||||
tenant_limit = cfg.CONF.max_stacks_per_tenant
|
||||
if stack_object.Stack.count_all(cnxt) >= tenant_limit:
|
||||
message = _("You have reached the maximum stacks per tenant, %d."
|
||||
" Please delete some stacks.") % tenant_limit
|
||||
raise exception.RequestLimitExceeded(message=message)
|
||||
|
||||
max_resources = cfg.CONF.max_resources_per_stack
|
||||
if max_resources == -1:
|
||||
return
|
||||
@ -973,58 +975,33 @@ class EngineService(service.Service):
|
||||
msg = _("No Template provided.")
|
||||
return webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
tmpl = templatem.Template(template, files=files)
|
||||
|
||||
# validate overall template
|
||||
env = environment.Environment(params)
|
||||
tmpl = templatem.Template(template, files=files, env=env)
|
||||
try:
|
||||
tmpl.validate()
|
||||
self._validate_template(cnxt, tmpl)
|
||||
except Exception as ex:
|
||||
return {'Error': six.text_type(ex)}
|
||||
|
||||
# validate resource classes
|
||||
tmpl_resources = tmpl[tmpl.RESOURCES]
|
||||
stack_name = 'dummy'
|
||||
stack = parser.Stack(cnxt, stack_name, tmpl, strict_validate=False)
|
||||
stack.resource_validate = False
|
||||
try:
|
||||
stack.validate()
|
||||
except exception.StackValidationFailed as ex:
|
||||
return {'Error': six.text_type(ex)}
|
||||
|
||||
env = environment.Environment(params)
|
||||
def filter_parameter(p):
|
||||
return p.name not in stack.parameters.PSEUDO_PARAMETERS
|
||||
|
||||
for name, res in six.iteritems(tmpl_resources):
|
||||
ResourceClass = env.get_class(res['Type'])
|
||||
if ResourceClass == resources.template_resource.TemplateResource:
|
||||
# we can't validate a TemplateResource unless we instantiate
|
||||
# it as we need to download the template and convert the
|
||||
# parameters into properties_schema.
|
||||
continue
|
||||
|
||||
if not ResourceClass.is_service_available(cnxt):
|
||||
raise exception.ResourceTypeUnavailable(
|
||||
service_name=ResourceClass.default_client_name,
|
||||
resource_type=res['Type']
|
||||
)
|
||||
|
||||
props = properties.Properties(
|
||||
ResourceClass.properties_schema,
|
||||
res.get('Properties', {}),
|
||||
parent_name=six.text_type(name),
|
||||
context=cnxt,
|
||||
section='Properties')
|
||||
deletion_policy = res.get('DeletionPolicy', 'Delete')
|
||||
try:
|
||||
ResourceClass.validate_deletion_policy(deletion_policy)
|
||||
props.validate(with_value=False)
|
||||
except Exception as ex:
|
||||
return {'Error': six.text_type(ex)}
|
||||
|
||||
# validate parameters
|
||||
tmpl_params = tmpl.parameters(None, user_params=env.params)
|
||||
tmpl_params.validate(validate_value=False, context=cnxt)
|
||||
is_real_param = lambda p: p.name not in tmpl_params.PSEUDO_PARAMETERS
|
||||
params = tmpl_params.map(api.format_validate_parameter, is_real_param)
|
||||
param_groups = parameter_groups.ParameterGroups(tmpl)
|
||||
params = stack.parameters.map(api.format_validate_parameter,
|
||||
filter_func=filter_parameter)
|
||||
|
||||
result = {
|
||||
'Description': tmpl.get('Description', ''),
|
||||
'Parameters': params,
|
||||
'Parameters': params
|
||||
}
|
||||
|
||||
param_groups = parameter_groups.ParameterGroups(tmpl)
|
||||
if param_groups.parameter_groups:
|
||||
result['ParameterGroups'] = param_groups.parameter_groups
|
||||
|
||||
|
@ -940,7 +940,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
def test_validate_ref_valid(self):
|
||||
t = template_format.parse(test_template_ref % 'WikiDatabase')
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual('test.', res['Description'])
|
||||
|
||||
def test_validate_with_environment(self):
|
||||
@ -950,7 +950,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
t = template_format.parse(test_template)
|
||||
engine = service.EngineService('a', 't')
|
||||
params = {'resource_registry': {'My::Instance': 'AWS::EC2::Instance'}}
|
||||
res = dict(engine.validate_template(None, t, params))
|
||||
res = dict(engine.validate_template(self.ctx, t, params))
|
||||
self.assertEqual('test.', res['Description'])
|
||||
|
||||
def test_validate_hot_valid(self):
|
||||
@ -963,31 +963,31 @@ class ValidateTest(common.HeatTestCase):
|
||||
type: AWS::EC2::Instance
|
||||
""")
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual('test.', res['Description'])
|
||||
|
||||
def test_validate_ref_invalid(self):
|
||||
t = template_format.parse(test_template_ref % 'WikiDatabasez')
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertNotEqual(res['Description'], 'Successfully validated')
|
||||
|
||||
def test_validate_findinmap_valid(self):
|
||||
t = template_format.parse(test_template_findinmap_valid)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual('test.', res['Description'])
|
||||
|
||||
def test_validate_findinmap_invalid(self):
|
||||
t = template_format.parse(test_template_findinmap_invalid)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertNotEqual(res['Description'], 'Successfully validated')
|
||||
|
||||
def test_validate_parameters(self):
|
||||
t = template_format.parse(test_template_ref % 'WikiDatabase')
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
# Note: the assertion below does not expect a CFN dict of the parameter
|
||||
# but a dict of the parameters.Schema object.
|
||||
# For API CFN backward compatibility, formating to CFN is done in the
|
||||
@ -1003,7 +1003,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
t = template_format.parse(test_template_default_override)
|
||||
env_params = {'net_name': 'betternetname'}
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, env_params))
|
||||
res = dict(engine.validate_template(self.ctx, t, env_params))
|
||||
self.assertEqual('defaultnet',
|
||||
res['Parameters']['net_name']['Default'])
|
||||
self.assertEqual('betternetname',
|
||||
@ -1013,7 +1013,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
t = template_format.parse(test_template_no_default)
|
||||
env_params = {'net_name': 'betternetname'}
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, env_params))
|
||||
res = dict(engine.validate_template(self.ctx, t, env_params))
|
||||
self.assertEqual('betternetname',
|
||||
res['Parameters']['net_name']['Value'])
|
||||
self.assertNotIn('Default', res['Parameters']['net_name'])
|
||||
@ -1029,13 +1029,13 @@ class ValidateTest(common.HeatTestCase):
|
||||
type: AWS::EC2::Instance
|
||||
""")
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual({}, res['Parameters'])
|
||||
|
||||
def test_validate_hot_parameter_label(self):
|
||||
t = template_format.parse(test_template_hot_parameter_label)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
parameters = res['Parameters']
|
||||
|
||||
expected = {'KeyName': {
|
||||
@ -1049,7 +1049,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
def test_validate_hot_no_parameter_label(self):
|
||||
t = template_format.parse(test_template_hot_no_parameter_label)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
parameters = res['Parameters']
|
||||
|
||||
expected = {'KeyName': {
|
||||
@ -1063,7 +1063,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
def test_validate_cfn_parameter_label(self):
|
||||
t = template_format.parse(test_template_cfn_parameter_label)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
parameters = res['Parameters']
|
||||
|
||||
expected = {'KeyName': {
|
||||
@ -1090,7 +1090,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
type: boolean
|
||||
""")
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
parameters = res['Parameters']
|
||||
# make sure all the types are reported correctly
|
||||
self.assertEqual('String', parameters["param1"]["Type"])
|
||||
@ -1107,7 +1107,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
resources:
|
||||
""")
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
expected = {"Description": "test.",
|
||||
"Parameters": {}}
|
||||
self.assertEqual(expected, res)
|
||||
@ -1120,7 +1120,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
outputs:
|
||||
""")
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
expected = {"Description": "test.",
|
||||
"Parameters": {}}
|
||||
self.assertEqual(expected, res)
|
||||
@ -1128,14 +1128,15 @@ class ValidateTest(common.HeatTestCase):
|
||||
def test_validate_properties(self):
|
||||
t = template_format.parse(test_template_invalid_property)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
self.assertEqual({'Error': 'Property error: WikiDatabase.Properties: '
|
||||
'Unknown Property UnknownProperty'}, res)
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual(
|
||||
{'Error': 'Property error: Resources.WikiDatabase.Properties: '
|
||||
'Unknown Property UnknownProperty'}, res)
|
||||
|
||||
def test_invalid_resources(self):
|
||||
t = template_format.parse(test_template_invalid_resources)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual({'Error': 'Resources must contain Resource. '
|
||||
'Found a [%s] instead' % six.text_type},
|
||||
res)
|
||||
@ -1155,7 +1156,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
""")
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t))
|
||||
res = dict(engine.validate_template(self.ctx, t))
|
||||
self.assertEqual({'Error': 'The template section is invalid: Output'},
|
||||
res)
|
||||
|
||||
@ -1170,36 +1171,36 @@ class ValidateTest(common.HeatTestCase):
|
||||
""")
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t))
|
||||
res = dict(engine.validate_template(self.ctx, t))
|
||||
self.assertEqual({'Error': 'The template section is invalid: output'},
|
||||
res)
|
||||
|
||||
def test_unimplemented_property(self):
|
||||
t = template_format.parse(test_template_unimplemented_property)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual(
|
||||
{'Error': 'Property error: WikiDatabase.Properties: '
|
||||
{'Error': 'Property error: Resources.WikiDatabase.Properties: '
|
||||
'Property SourceDestCheck not implemented yet'},
|
||||
res)
|
||||
|
||||
def test_invalid_deletion_policy(self):
|
||||
t = template_format.parse(test_template_invalid_deletion_policy)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual({'Error': 'Invalid deletion policy "Destroy"'}, res)
|
||||
|
||||
def test_snapshot_deletion_policy(self):
|
||||
t = template_format.parse(test_template_snapshot_deletion_policy)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual(
|
||||
{'Error': '"Snapshot" deletion policy not supported'}, res)
|
||||
|
||||
def test_volume_snapshot_deletion_policy(self):
|
||||
t = template_format.parse(test_template_volume_snapshot)
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, t, {}))
|
||||
res = dict(engine.validate_template(self.ctx, t, {}))
|
||||
self.assertEqual({'Description': u'test.', 'Parameters': {}}, res)
|
||||
|
||||
def test_validate_template_without_resources(self):
|
||||
@ -1208,7 +1209,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
expected = {'Description': 'No description', 'Parameters': {}}
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
@ -1229,7 +1230,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"Type" is not a valid keyword '
|
||||
'inside a resource definition'}, res)
|
||||
|
||||
@ -1250,7 +1251,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"Properties" is not a valid keyword '
|
||||
'inside a resource definition'}, res)
|
||||
|
||||
@ -1271,7 +1272,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"Metadata" is not a valid keyword '
|
||||
'inside a resource definition'}, res)
|
||||
|
||||
@ -1292,7 +1293,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"DependsOn" is not a valid keyword '
|
||||
'inside a resource definition'}, res)
|
||||
|
||||
@ -1313,7 +1314,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"DeletionPolicy" is not a valid '
|
||||
'keyword inside a resource definition'},
|
||||
res)
|
||||
@ -1335,7 +1336,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
''')
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
res = dict(engine.validate_template(self.ctx, hot_tpl, {}))
|
||||
self.assertEqual({'Error': '"UpdatePolicy" is not a valid '
|
||||
'keyword inside a resource definition'},
|
||||
res)
|
||||
@ -1678,7 +1679,7 @@ class ValidateTest(common.HeatTestCase):
|
||||
self.mock_is_service_available.return_value = False
|
||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
||||
engine.validate_template,
|
||||
None,
|
||||
self.ctx,
|
||||
t,
|
||||
{})
|
||||
self.assertEqual(exception.ResourceTypeUnavailable, ex.exc_info[0])
|
||||
|
Loading…
x
Reference in New Issue
Block a user