Move some template validation to Template class
This patch moves some template validation code to the Template class' validate() method which has been introduced in change https://review.openstack.org/#/c/83758 . Going forward, this should make it easy to perform specific validtion in Template sub-classes (HOT, CFN). Change-Id: Iaf86f48d462231a7aa51adb642ea8920f9f43892
This commit is contained in:
parent
3a61debdec
commit
73a9dce9ba
|
@ -597,33 +597,19 @@ class EngineService(service.Service):
|
|||
return webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
tmpl = parser.Template(template)
|
||||
|
||||
# validate overall template
|
||||
try:
|
||||
tmpl_resources = tmpl['Resources']
|
||||
except KeyError as ex:
|
||||
tmpl.validate(allow_empty=False)
|
||||
except Exception as ex:
|
||||
return {'Error': six.text_type(ex)}
|
||||
|
||||
# validate overall template (top-level structure)
|
||||
tmpl.validate()
|
||||
|
||||
if not tmpl_resources:
|
||||
return {'Error': 'At least one Resources member must be defined.'}
|
||||
# validate resource classes
|
||||
tmpl_resources = tmpl[tmpl.RESOURCES]
|
||||
|
||||
env = environment.Environment(params)
|
||||
|
||||
for res in tmpl_resources.values():
|
||||
try:
|
||||
if not res.get('Type'):
|
||||
return {'Error':
|
||||
'Every Resource object must '
|
||||
'contain a Type member.'}
|
||||
except AttributeError:
|
||||
type_res = type(res)
|
||||
if isinstance(res, unicode):
|
||||
type_res = "string"
|
||||
return {'Error':
|
||||
'Resources must contain Resource. '
|
||||
'Found a [%s] instead' % type_res}
|
||||
|
||||
ResourceClass = env.get_class(res['Type'])
|
||||
if ResourceClass == resources.template_resource.TemplateResource:
|
||||
# we can't validate a TemplateResource unless we instantiate
|
||||
|
@ -640,6 +626,7 @@ class EngineService(service.Service):
|
|||
except Exception as ex:
|
||||
return {'Error': six.text_type(ex)}
|
||||
|
||||
# validate parameters
|
||||
tmpl_params = tmpl.parameters(None, {}, validate_value=False)
|
||||
is_real_param = lambda p: p.name not in tmpl_params.PSEUDO_PARAMETERS
|
||||
params = tmpl_params.map(api.format_validate_parameter, is_real_param)
|
||||
|
|
|
@ -164,18 +164,44 @@ class Template(collections.Mapping):
|
|||
def parse(self, stack, snippet):
|
||||
return parse(self.functions(), stack, snippet)
|
||||
|
||||
def validate(self):
|
||||
def validate(self, allow_empty=True):
|
||||
'''Validate the template.
|
||||
|
||||
Only validates the top-level sections of the template. Syntax inside
|
||||
sections is not checked here but in code parts that are responsible
|
||||
for working with the respective sections.
|
||||
Validates the top-level sections of the template as well as syntax
|
||||
inside select sections. Some sections are not checked here but in
|
||||
code parts that are responsible for working with the respective
|
||||
sections (e.g. parameters are check by parameters schema class).
|
||||
|
||||
:param allow_empty: whether to allow an empty resources section
|
||||
'''
|
||||
|
||||
# check top-level sections
|
||||
for k in self.t.keys():
|
||||
if k not in self.SECTIONS:
|
||||
raise exception.InvalidTemplateSection(section=k)
|
||||
|
||||
# check resources
|
||||
tmpl_resources = self[self.RESOURCES]
|
||||
|
||||
if not allow_empty and not tmpl_resources:
|
||||
message = _('The template is invalid. A Resources section with at '
|
||||
'least one resource must be defined.')
|
||||
raise exception.StackValidationFailed(message=message)
|
||||
|
||||
for res in tmpl_resources.values():
|
||||
try:
|
||||
if not res.get('Type'):
|
||||
message = _('Every Resource object must '
|
||||
'contain a Type member.')
|
||||
raise exception.StackValidationFailed(message=message)
|
||||
except AttributeError:
|
||||
type_res = type(res)
|
||||
if isinstance(res, unicode):
|
||||
type_res = "string"
|
||||
message = _('Resources must contain Resource. '
|
||||
'Found a [%s] instead') % type_res
|
||||
raise exception.StackValidationFailed(message=message)
|
||||
|
||||
|
||||
def parse(functions, stack, snippet):
|
||||
recurse = functools.partial(parse, functions, stack)
|
||||
|
|
|
@ -100,7 +100,11 @@ class TestTemplateValidate(HeatTestCase):
|
|||
'Description': 'foo',
|
||||
'Parameters': {},
|
||||
'Mappings': {},
|
||||
'Resources': {},
|
||||
'Resources': {
|
||||
'server': {
|
||||
'Type': 'OS::Nova::Server'
|
||||
}
|
||||
},
|
||||
'Outputs': {},
|
||||
}
|
||||
|
||||
|
@ -114,7 +118,11 @@ class TestTemplateValidate(HeatTestCase):
|
|||
'Description': 'foo',
|
||||
'Parameters': {},
|
||||
'Mappings': {},
|
||||
'Resources': {},
|
||||
'Resources': {
|
||||
'server': {
|
||||
'Type': 'OS::Nova::Server'
|
||||
}
|
||||
},
|
||||
'Outputs': {},
|
||||
}
|
||||
|
||||
|
@ -128,7 +136,11 @@ class TestTemplateValidate(HeatTestCase):
|
|||
'Description': 'foo',
|
||||
'Parameteers': {},
|
||||
'Mappings': {},
|
||||
'Resources': {},
|
||||
'Resources': {
|
||||
'server': {
|
||||
'Type': 'OS::Nova::Server'
|
||||
}
|
||||
},
|
||||
'Outputs': {},
|
||||
}
|
||||
|
||||
|
@ -142,7 +154,11 @@ class TestTemplateValidate(HeatTestCase):
|
|||
'heat_template_version': '2013-05-23',
|
||||
'description': 'foo',
|
||||
'parameters': {},
|
||||
'resources': {},
|
||||
'resources': {
|
||||
'server': {
|
||||
'type': 'OS::Nova::Server'
|
||||
}
|
||||
},
|
||||
'outputs': {},
|
||||
}
|
||||
|
||||
|
@ -155,7 +171,11 @@ class TestTemplateValidate(HeatTestCase):
|
|||
'heat_template_version': '2013-05-23',
|
||||
'description': 'foo',
|
||||
'parameteers': {},
|
||||
'resources': {},
|
||||
'resources': {
|
||||
'server': {
|
||||
'type': 'OS::Nova::Server'
|
||||
}
|
||||
},
|
||||
'outputs': {},
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ from heat.engine import resources
|
|||
from heat.engine.resources import instance as instances
|
||||
from heat.engine import service
|
||||
from heat.openstack.common.importutils import try_import
|
||||
from heat.openstack.common.rpc import common as rpc_common
|
||||
from heat.tests.common import HeatTestCase
|
||||
from heat.tests import utils
|
||||
from heat.tests.v1_1 import fakes
|
||||
|
@ -769,6 +768,10 @@ parameter_groups:
|
|||
description: A group of parameters for the server
|
||||
- label: Database Group
|
||||
description: A group of parameters for the database
|
||||
|
||||
resources:
|
||||
server:
|
||||
type: OS::Nova::Server
|
||||
'''
|
||||
|
||||
|
||||
|
@ -992,11 +995,9 @@ class validateTest(HeatTestCase):
|
|||
self.m.ReplayAll()
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
ex = self.assertRaises(rpc_common.ClientException,
|
||||
engine.validate_template, None, t)
|
||||
self.assertEqual(ex._exc_info[0], exception.InvalidTemplateSection)
|
||||
self.assertEqual('The template section is invalid: Output',
|
||||
str(ex._exc_info[1]))
|
||||
res = dict(engine.validate_template(None, t))
|
||||
self.assertEqual({'Error': 'The template section is invalid: Output'},
|
||||
res)
|
||||
|
||||
def test_invalid_section_hot(self):
|
||||
t = template_format.parse(
|
||||
|
@ -1015,11 +1016,9 @@ class validateTest(HeatTestCase):
|
|||
self.m.ReplayAll()
|
||||
|
||||
engine = service.EngineService('a', 't')
|
||||
ex = self.assertRaises(rpc_common.ClientException,
|
||||
engine.validate_template, None, t)
|
||||
self.assertEqual(ex._exc_info[0], exception.InvalidTemplateSection)
|
||||
self.assertEqual('The template section is invalid: output',
|
||||
str(ex._exc_info[1]))
|
||||
res = dict(engine.validate_template(None, t))
|
||||
self.assertEqual({'Error': 'The template section is invalid: output'},
|
||||
res)
|
||||
|
||||
def test_unimplemented_property(self):
|
||||
t = template_format.parse(test_template_unimplemented_property)
|
||||
|
@ -1077,8 +1076,9 @@ class validateTest(HeatTestCase):
|
|||
|
||||
engine = service.EngineService('a', 't')
|
||||
res = dict(engine.validate_template(None, hot_tpl, {}))
|
||||
self.assertEqual({'Error': 'At least one Resources member '
|
||||
'must be defined.'}, res)
|
||||
self.assertEqual({'Error': 'The template is invalid. '
|
||||
'A Resources section with at least one resource '
|
||||
'must be defined.'}, res)
|
||||
|
||||
def test_validate_template_with_invalid_resource_type(self):
|
||||
hot_tpl = template_format.parse('''
|
||||
|
|
Loading…
Reference in New Issue