From f7a52b5005ef03228c39c2cd3ffe72ba31b7c0c3 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Mon, 3 Mar 2014 15:28:29 -0500 Subject: [PATCH] Give cfn a separate Template class Change-Id: Iddeeef7e0303fcbf09fc936a96ec68dbccd5489e --- heat/engine/cfn/template.py | 67 +++++++++++++++++++++++++++++++ heat/engine/hot/__init__.py | 17 ++++---- heat/engine/template.py | 61 ++++++++-------------------- heat/tests/test_engine_service.py | 2 +- heat/tests/test_parser.py | 2 +- heat/tests/test_stack_resource.py | 2 +- 6 files changed, 96 insertions(+), 55 deletions(-) create mode 100644 heat/engine/cfn/template.py diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py new file mode 100644 index 0000000000..a1a641a332 --- /dev/null +++ b/heat/engine/cfn/template.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from heat.engine import parameters +from heat.engine import template +from heat.engine.cfn import functions + + +class CfnTemplate(template.Template): + '''A stack template.''' + + SECTIONS = (VERSION, DESCRIPTION, MAPPINGS, + PARAMETERS, RESOURCES, OUTPUTS) = \ + ('AWSTemplateFormatVersion', 'Description', 'Mappings', + 'Parameters', 'Resources', 'Outputs') + + SECTIONS_NO_DIRECT_ACCESS = set([PARAMETERS, VERSION]) + + def __getitem__(self, section): + '''Get the relevant section in the template.''' + if section not in self.SECTIONS: + raise KeyError(_('"%s" is not a valid template section') % section) + if section in self.SECTIONS_NO_DIRECT_ACCESS: + raise KeyError( + _('Section %s can not be accessed directly.') % section) + + if section == self.DESCRIPTION: + default = 'No description' + else: + default = {} + + return self.t.get(section, default) + + def version(self): + for key in ('HeatTemplateFormatVersion', 'AWSTemplateFormatVersion'): + if key in self.t: + return key, self.t[key] + + # All user templates are forced to include a version string. This is + # just a convenient default for unit tests. + return 'HeatTemplateFormatVersion', '2012-12-12' + + def param_schemata(self): + params = self.t.get(self.PARAMETERS, {}).iteritems() + return dict((name, parameters.Schema.from_dict(schema)) + for name, schema in params) + + def parameters(self, stack_identifier, user_params, validate_value=True, + context=None): + return parameters.Parameters(stack_identifier, self, + user_params=user_params, + validate_value=validate_value, + context=context) + + def functions(self): + return functions.function_mapping(*self.version()) diff --git a/heat/engine/hot/__init__.py b/heat/engine/hot/__init__.py index 0932129da8..76d5532833 100644 --- a/heat/engine/hot/__init__.py +++ b/heat/engine/hot/__init__.py @@ -14,6 +14,7 @@ from heat.common import exception from heat.engine import template +from heat.engine.cfn import template as cfn_template from heat.engine import parameters from heat.engine import constraints as constr from heat.openstack.common.gettextutils import _ @@ -41,7 +42,7 @@ class HOTemplate(template.Template): """ SECTIONS = (VERSION, DESCRIPTION, PARAMETER_GROUPS, PARAMETERS, - RESOURCES, OUTPUTS, UNDEFINED) = \ + RESOURCES, OUTPUTS, MAPPINGS) = \ ('heat_template_version', 'description', 'parameter_groups', 'parameters', 'resources', 'outputs', '__undefined__') @@ -49,12 +50,12 @@ class HOTemplate(template.Template): VERSIONS = ('2013-05-23',) - _CFN_TO_HOT_SECTIONS = {template.Template.VERSION: VERSION, - template.Template.DESCRIPTION: DESCRIPTION, - template.Template.PARAMETERS: PARAMETERS, - template.Template.MAPPINGS: UNDEFINED, - template.Template.RESOURCES: RESOURCES, - template.Template.OUTPUTS: OUTPUTS} + _CFN_TO_HOT_SECTIONS = {cfn_template.CfnTemplate.VERSION: VERSION, + cfn_template.CfnTemplate.DESCRIPTION: DESCRIPTION, + cfn_template.CfnTemplate.PARAMETERS: PARAMETERS, + cfn_template.CfnTemplate.MAPPINGS: MAPPINGS, + cfn_template.CfnTemplate.RESOURCES: RESOURCES, + cfn_template.CfnTemplate.OUTPUTS: OUTPUTS} def __init__(self, template, *args, **kwargs): version = template[self.VERSION] @@ -79,7 +80,7 @@ class HOTemplate(template.Template): raise KeyError( _('Section %s can not be accessed directly.') % section) - if section == self.UNDEFINED: + if section == self.MAPPINGS: return {} if section == self.DESCRIPTION: diff --git a/heat/engine/template.py b/heat/engine/template.py index b3efa20581..e530a72460 100644 --- a/heat/engine/template.py +++ b/heat/engine/template.py @@ -13,36 +13,27 @@ # License for the specific language governing permissions and limitations # under the License. +import abc import collections import functools from heat.db import api as db_api -from heat.engine import parameters -from heat.engine.cfn import functions -from heat.openstack.common.gettextutils import _ class Template(collections.Mapping): '''A stack template.''' - SECTIONS = (VERSION, DESCRIPTION, MAPPINGS, - PARAMETERS, RESOURCES, OUTPUTS) = \ - ('AWSTemplateFormatVersion', 'Description', 'Mappings', - 'Parameters', 'Resources', 'Outputs') - - SECTIONS_NO_DIRECT_ACCESS = set([PARAMETERS, VERSION]) - def __new__(cls, template, *args, **kwargs): '''Create a new Template of the appropriate class.''' if cls == Template: + # deferred module imports to avoid circular dependency if 'heat_template_version' in template: - - # defer import of HOT module to avoid circular dependency - # at load time from heat.engine import hot - return hot.HOTemplate(template, *args, **kwargs) + else: + from heat.engine.cfn import template as cfn + return cfn.CfnTemplate(template, *args, **kwargs) return super(Template, cls).__new__(cls) @@ -72,21 +63,6 @@ class Template(collections.Mapping): self.id = new_rt.id return self.id - def __getitem__(self, section): - '''Get the relevant section in the template.''' - if section not in self.SECTIONS: - raise KeyError(_('"%s" is not a valid template section') % section) - if section in self.SECTIONS_NO_DIRECT_ACCESS: - raise KeyError( - _('Section %s can not be accessed directly.') % section) - - if section == self.DESCRIPTION: - default = 'No description' - else: - default = {} - - return self.t.get(section, default) - def __iter__(self): '''Return an iterator over the section names.''' return (s for s in self.SECTIONS @@ -96,29 +72,26 @@ class Template(collections.Mapping): '''Return the number of sections.''' return len(self.SECTIONS) - len(self.SECTIONS_NO_DIRECT_ACCESS) + @abc.abstractmethod def version(self): - for key in ('HeatTemplateFormatVersion', 'AWSTemplateFormatVersion'): - if key in self.t: - return key, self.t[key] - - # All user templates are forced to include a version string. This is - # just a convenient default for unit tests. - return 'HeatTemplateFormatVersion', '2012-12-12' + '''Return a (versionkey, version) tuple for the template.''' + pass + @abc.abstractmethod def param_schemata(self): - params = self.t.get(self.PARAMETERS, {}).iteritems() - return dict((name, parameters.Schema.from_dict(schema)) - for name, schema in params) + '''Return a dict of parameters.Schema objects for the parameters.''' + pass + @abc.abstractmethod def parameters(self, stack_identifier, user_params, validate_value=True, context=None): - return parameters.Parameters(stack_identifier, self, - user_params=user_params, - validate_value=validate_value, - context=context) + '''Return a parameters.Parameters object for the stack.''' + pass + @abc.abstractmethod def functions(self): - return functions.function_mapping(*self.version()) + '''Return a dict of template functions keyed by name.''' + pass def parse(self, stack, snippet): parse = functools.partial(self.parse, stack) diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index f9d4fa4c3e..a039ce3232 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -2608,7 +2608,7 @@ class StackServiceTest(HeatTestCase): def test_validate_new_stack_checks_resource_limit(self): cfg.CONF.set_override('max_resources_per_stack', 5) - template = {service.parser.Template.RESOURCES: [1, 2, 3, 4, 5, 6]} + template = {'Resources': [1, 2, 3, 4, 5, 6]} parsed_template = service.parser.Template(template) self.assertRaises(exception.RequestLimitExceeded, self.eng._validate_new_stack, diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index e1b43b45c8..77b5edffb7 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -1473,7 +1473,7 @@ class StackTest(HeatTestCase): self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE), self.stack.state) self.assertEqual('BTemplate', - self.stack.t[template.Template.DESCRIPTION]) + self.stack.t[self.stack.t.DESCRIPTION]) @utils.stack_delete_after def test_update_modify_ok_replace(self): diff --git a/heat/tests/test_stack_resource.py b/heat/tests/test_stack_resource.py index 546e670505..890f159d26 100644 --- a/heat/tests/test_stack_resource.py +++ b/heat/tests/test_stack_resource.py @@ -177,7 +177,7 @@ class StackResourceTest(HeatTestCase): def test__validate_nested_resources_checks_num_of_resources(self): stack_resource.cfg.CONF.set_override('max_resources_per_stack', 2) - tmpl = {stack_resource.parser.Template.RESOURCES: [1]} + tmpl = {'Resources': [1]} template = stack_resource.parser.Template(tmpl) root_resources = mock.Mock(return_value=2) self.parent_resource.stack.root_stack.total_resources = root_resources