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:
Thomas Spatzier 2014-03-29 22:15:13 +01:00
parent 3a61debdec
commit 73a9dce9ba
4 changed files with 75 additions and 42 deletions

View File

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

View File

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

View File

@ -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': {},
}

View File

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