diff --git a/heat/engine/cfn/functions.py b/heat/engine/cfn/functions.py index 4161183e18..1a3753e8ec 100644 --- a/heat/engine/cfn/functions.py +++ b/heat/engine/cfn/functions.py @@ -557,35 +557,3 @@ class ResourceFacade(function.Function): elif attr == self.DELETION_POLICY: dp = self.stack.parent_resource.t.deletion_policy() return function.resolve(dp) - - -def function_mapping(version_key, version): - if version_key == 'AWSTemplateFormatVersion': - return { - 'Fn::FindInMap': FindInMap, - 'Fn::GetAZs': GetAZs, - 'Ref': Ref, - 'Fn::GetAtt': GetAtt, - 'Fn::Select': Select, - 'Fn::Join': Join, - 'Fn::Base64': Base64, - } - elif version_key != 'HeatTemplateFormatVersion': - return {} - - if version == '2012-12-12': - return { - 'Fn::FindInMap': FindInMap, - 'Fn::GetAZs': GetAZs, - 'Ref': Ref, - 'Fn::GetAtt': GetAtt, - 'Fn::Select': Select, - 'Fn::Join': Join, - 'Fn::Split': Split, - 'Fn::Replace': Replace, - 'Fn::Base64': Base64, - 'Fn::MemberListToMap': MemberListToMap, - 'Fn::ResourceFacade': ResourceFacade, - } - - return {} diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py index 4106560832..7ee4fee427 100644 --- a/heat/engine/cfn/template.py +++ b/heat/engine/cfn/template.py @@ -15,6 +15,7 @@ import collections import six +from heat.engine.cfn import functions as cfn_funcs from heat.engine import function from heat.engine import parameters from heat.engine import rsrc_defn @@ -41,6 +42,16 @@ class CfnTemplate(template.Template): SECTIONS_NO_DIRECT_ACCESS = set([PARAMETERS, VERSION, ALTERNATE_VERSION]) + functions = { + 'Fn::FindInMap': cfn_funcs.FindInMap, + 'Fn::GetAZs': cfn_funcs.GetAZs, + 'Ref': cfn_funcs.Ref, + 'Fn::GetAtt': cfn_funcs.GetAtt, + 'Fn::Select': cfn_funcs.Select, + 'Fn::Join': cfn_funcs.Join, + 'Fn::Base64': cfn_funcs.Base64, + } + def __getitem__(self, section): '''Get the relevant section in the template.''' if section not in self.SECTIONS: @@ -152,3 +163,19 @@ class CfnTemplate(template.Template): if self.t.get(self.RESOURCES) is None: self.t[self.RESOURCES] = {} self.t[self.RESOURCES][name] = cfn_tmpl + + +class HeatTemplate(CfnTemplate): + functions = { + 'Fn::FindInMap': cfn_funcs.FindInMap, + 'Fn::GetAZs': cfn_funcs.GetAZs, + 'Ref': cfn_funcs.Ref, + 'Fn::GetAtt': cfn_funcs.GetAtt, + 'Fn::Select': cfn_funcs.Select, + 'Fn::Join': cfn_funcs.Join, + 'Fn::Split': cfn_funcs.Split, + 'Fn::Replace': cfn_funcs.Replace, + 'Fn::Base64': cfn_funcs.Base64, + 'Fn::MemberListToMap': cfn_funcs.MemberListToMap, + 'Fn::ResourceFacade': cfn_funcs.ResourceFacade, + } diff --git a/heat/engine/hot/functions.py b/heat/engine/hot/functions.py index 23c333bb7e..599f7b3d2c 100644 --- a/heat/engine/hot/functions.py +++ b/heat/engine/hot/functions.py @@ -261,50 +261,3 @@ class Removed(function.Function): def result(self): return super(Removed, self).result() - - -def function_mapping(version_key, version): - if version_key != 'heat_template_version': - return {} - - if version == '2013-05-23': - return { - 'Fn::GetAZs': cfn_funcs.GetAZs, - 'get_param': GetParam, - 'get_resource': cfn_funcs.ResourceRef, - 'Ref': cfn_funcs.Ref, - 'get_attr': GetAttThenSelect, - 'Fn::Select': cfn_funcs.Select, - 'Fn::Join': cfn_funcs.Join, - 'Fn::Split': cfn_funcs.Split, - 'str_replace': Replace, - 'Fn::Replace': cfn_funcs.Replace, - 'Fn::Base64': cfn_funcs.Base64, - 'Fn::MemberListToMap': cfn_funcs.MemberListToMap, - 'resource_facade': ResourceFacade, - 'Fn::ResourceFacade': cfn_funcs.ResourceFacade, - 'get_file': GetFile, - } - if version == '2014-10-16': - return { - 'get_param': GetParam, - 'get_resource': cfn_funcs.ResourceRef, - 'get_attr': GetAtt, - 'list_join': Join, - 'str_replace': Replace, - 'resource_facade': ResourceFacade, - 'get_file': GetFile, - - 'Fn::Select': cfn_funcs.Select, - - 'Fn::GetAZs': Removed, - 'Ref': Removed, - 'Fn::Join': Removed, - 'Fn::Split': Removed, - 'Fn::Replace': Removed, - 'Fn::Base64': Removed, - 'Fn::MemberListToMap': Removed, - 'Fn::ResourceFacade': Removed, - } - - return {} diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index 7a313d4930..eacdf2a38b 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -13,8 +13,10 @@ import collections import six +from heat.engine.cfn import functions as cfn_funcs from heat.engine.cfn import template as cfn_template from heat.engine import function +from heat.engine.hot import functions as hot_funcs from heat.engine.hot import parameters from heat.engine import rsrc_defn from heat.engine import template @@ -30,7 +32,7 @@ _RESOURCE_KEYS = ( ) -class HOTemplate(template.Template): +class HOTemplate20130523(template.Template): """ A Heat Orchestration Template format stack template. """ @@ -49,13 +51,32 @@ class HOTemplate(template.Template): cfn_template.CfnTemplate.RESOURCES: RESOURCES, cfn_template.CfnTemplate.OUTPUTS: OUTPUTS} + functions = { + 'Fn::GetAZs': cfn_funcs.GetAZs, + 'get_param': hot_funcs.GetParam, + 'get_resource': cfn_funcs.ResourceRef, + 'Ref': cfn_funcs.Ref, + 'get_attr': hot_funcs.GetAttThenSelect, + 'Fn::Select': cfn_funcs.Select, + 'Fn::Join': cfn_funcs.Join, + 'list_join': hot_funcs.Join, + 'Fn::Split': cfn_funcs.Split, + 'str_replace': hot_funcs.Replace, + 'Fn::Replace': cfn_funcs.Replace, + 'Fn::Base64': cfn_funcs.Base64, + 'Fn::MemberListToMap': cfn_funcs.MemberListToMap, + 'resource_facade': hot_funcs.ResourceFacade, + 'Fn::ResourceFacade': cfn_funcs.ResourceFacade, + 'get_file': hot_funcs.GetFile, + } + def __getitem__(self, section): """"Get the relevant section in the template.""" #first translate from CFN into HOT terminology if necessary if section not in self.SECTIONS: - section = HOTemplate._translate(section, self._CFN_TO_HOT_SECTIONS, - _('"%s" is not a valid template ' - 'section')) + section = HOTemplate20130523._translate( + section, self._CFN_TO_HOT_SECTIONS, + _('"%s" is not a valid template section')) if section not in self.SECTIONS: raise KeyError(_('"%s" is not a valid template section') % section) @@ -226,3 +247,27 @@ class HOTemplate(template.Template): if self.t.get(self.RESOURCES) is None: self.t[self.RESOURCES] = {} self.t[self.RESOURCES][name] = definition.render_hot() + + +class HOTemplate20141016(HOTemplate20130523): + functions = { + 'get_attr': hot_funcs.GetAtt, + 'get_file': hot_funcs.GetFile, + 'get_param': hot_funcs.GetParam, + 'get_resource': cfn_funcs.ResourceRef, + 'list_join': hot_funcs.Join, + 'resource_facade': hot_funcs.ResourceFacade, + 'str_replace': hot_funcs.Replace, + + 'Fn::Select': cfn_funcs.Select, + + # functions removed from 20130523 + 'Fn::GetAZs': hot_funcs.Removed, + 'Fn::Join': hot_funcs.Removed, + 'Fn::Split': hot_funcs.Removed, + 'Fn::Replace': hot_funcs.Removed, + 'Fn::Base64': hot_funcs.Removed, + 'Fn::MemberListToMap': hot_funcs.Removed, + 'Fn::ResourceFacade': hot_funcs.Removed, + 'Ref': hot_funcs.Removed, + } diff --git a/heat/engine/template.py b/heat/engine/template.py index 4952db3459..ee2638edcd 100644 --- a/heat/engine/template.py +++ b/heat/engine/template.py @@ -20,7 +20,6 @@ from stevedore import extension from heat.common import exception from heat.db import api as db_api -from heat.engine import plugin_manager from heat.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -31,31 +30,6 @@ __all__ = ['Template'] _template_classes = None -class TemplatePluginManager(object): - '''A Descriptor class for caching PluginManagers. - - Keeps a cache of PluginManagers with the search directories corresponding - to the package containing the owner class. - ''' - - def __init__(self): - self.plugin_managers = {} - - @staticmethod - def package_name(obj_class): - '''Return the package containing the given class.''' - module_name = obj_class.__module__ - return module_name.rsplit('.', 1)[0] - - def __get__(self, obj, obj_class): - '''Get a PluginManager for a class.''' - pkg = self.package_name(obj_class) - if pkg not in self.plugin_managers: - self.plugin_managers[pkg] = plugin_manager.PluginManager(pkg) - - return self.plugin_managers[pkg] - - def get_version(template_data, available_versions): version_keys = set(key for key, version in available_versions) candidate_keys = set(k for k, v in six.iteritems(template_data) if @@ -74,14 +48,18 @@ def get_version(template_data, available_versions): return version_key, template_data[version_key] -def get_template_class(plugin_mgr, template_data): +def _get_template_extension_manager(): + return extension.ExtensionManager( + namespace='heat.templates', + invoke_on_load=False, + verify_requirements=True) + + +def get_template_class(template_data): global _template_classes if _template_classes is None: - mgr = extension.ExtensionManager( - namespace='heat.templates', - invoke_on_load=False, - verify_requirements=True) + mgr = _get_template_extension_manager() _template_classes = dict((tuple(name.split('.')), mgr[name].plugin) for name in mgr.names()) @@ -108,16 +86,13 @@ def get_template_class(plugin_mgr, template_data): class Template(collections.Mapping): '''A stack template.''' - _plugins = TemplatePluginManager() - _functionmaps = {} - def __new__(cls, template, *args, **kwargs): '''Create a new Template of the appropriate class.''' if cls != Template: TemplateClass = cls else: - TemplateClass = get_template_class(cls._plugins, template) + TemplateClass = get_template_class(template) return super(Template, cls).__new__(TemplateClass) @@ -191,17 +166,8 @@ class Template(collections.Mapping): '''Remove a resource from the template.''' self.t.get(self.RESOURCES, {}).pop(name) - def functions(self): - '''Return a dict of template functions keyed by name.''' - if self.version not in self._functionmaps: - mappings = plugin_manager.PluginMapping('function', *self.version) - funcs = dict(mappings.load_all(self._plugins)) - self._functionmaps[self.version] = funcs - - return self._functionmaps[self.version] - def parse(self, stack, snippet): - return parse(self.functions(), stack, snippet) + return parse(self.functions, stack, snippet) def validate(self): '''Validate the template. diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index 2c58ccdd53..c2c341b070 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -99,7 +99,7 @@ class HOTemplateTest(HeatTestCase): tmpl = parser.Template(hot_tpl_empty) # check if we get the right class - self.assertIsInstance(tmpl, hot_template.HOTemplate) + self.assertIsInstance(tmpl, hot_template.HOTemplate20130523) # test getting an invalid section self.assertNotIn('foobar', tmpl) @@ -113,7 +113,7 @@ class HOTemplateTest(HeatTestCase): tmpl = parser.Template(hot_tpl_empty_sections) # check if we get the right class - self.assertIsInstance(tmpl, hot_template.HOTemplate) + self.assertIsInstance(tmpl, hot_template.HOTemplate20130523) # test getting an invalid section self.assertNotIn('foobar', tmpl) diff --git a/heat/tests/test_parser.py b/heat/tests/test_parser.py index de7bcebc6a..c054c48de0 100644 --- a/heat/tests/test_parser.py +++ b/heat/tests/test_parser.py @@ -212,8 +212,8 @@ class TemplateTest(HeatTestCase): "heat_template_version" : "2012-12-12", }''') versions = { - ('heat_template_version', '2013-05-23'): hot_t.HOTemplate, - ('heat_template_version', '2013-06-23'): hot_t.HOTemplate + ('heat_template_version', '2013-05-23'): hot_t.HOTemplate20130523, + ('heat_template_version', '2013-06-23'): hot_t.HOTemplate20130523 } temp_copy = copy.deepcopy(template._template_classes) diff --git a/heat/tests/test_template.py b/heat/tests/test_template.py index 06af963317..b1a408979b 100644 --- a/heat/tests/test_template.py +++ b/heat/tests/test_template.py @@ -12,38 +12,80 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures +from oslotest import mockpatch import six +from stevedore import extension from heat.common import exception from heat.common import template_format -from heat.engine.cfn.template import CfnTemplate -from heat.engine import plugin_manager +from heat.engine import function from heat.engine import template -from heat.tests.common import HeatTestCase +from heat.tests import common -class TestTemplatePluginManager(HeatTestCase): +class TemplatePluginFixture(fixtures.Fixture): + def __init__(self, templates={}): + super(TemplatePluginFixture, self).__init__() + self.templates = [extension.Extension(k, None, v, None) + for (k, v) in templates.items()] - def test_pkg_name(self): - cfn_tmpl_pkg = template.TemplatePluginManager.package_name(CfnTemplate) - self.assertEqual('heat.engine.cfn', cfn_tmpl_pkg) + def _get_template_extension_manager(self): + return extension.ExtensionManager.make_test_instance(self.templates) - def test_get(self): + def setUp(self): + super(TemplatePluginFixture, self).setUp() - tpm = template.TemplatePluginManager() + def clear_template_classes(): + template._template_classes = None - self.assertFalse(tpm.plugin_managers) - - class Test(object): - plugins = tpm - - test_pm = Test().plugins - - self.assertTrue(isinstance(test_pm, plugin_manager.PluginManager)) - self.assertEqual(tpm.plugin_managers['heat.tests'], test_pm) + clear_template_classes() + self.useFixture(mockpatch.PatchObject( + template, + '_get_template_extension_manager', + new=self._get_template_extension_manager)) + self.addCleanup(clear_template_classes) -class TestTemplateVersion(HeatTestCase): +class TestTemplatePluginManager(common.HeatTestCase): + def test_template_NEW_good(self): + class NewTemplate(template.Template): + SECTIONS = (VERSION, MAPPINGS) = ('NEWTemplateFormatVersion', + '__undefined__') + RESOURCES = 'thingies' + + def param_schemata(self): + pass + + def parameters(self, stack_identifier, user_params): + pass + + def resource_definitions(self, stack): + pass + + def add_resource(self, definition, name=None): + pass + + def __getitem__(self, section): + return {} + + def functions(self): + return {} + + class NewTemplatePrint(function.Function): + def result(self): + return 'always this' + + self.useFixture(TemplatePluginFixture( + {'NEWTemplateFormatVersion.2345-01-01': NewTemplate})) + + t = {'NEWTemplateFormatVersion': '2345-01-01'} + tmpl = template.Template(t) + err = tmpl.validate() + self.assertIsNone(err) + + +class TestTemplateVersion(common.HeatTestCase): versions = (('heat_template_version', '2013-05-23'), ('HeatTemplateFormatVersion', '2012-12-12'), @@ -97,7 +139,7 @@ class TestTemplateVersion(HeatTestCase): template.get_version, tmpl, self.versions) -class TestTemplateValidate(HeatTestCase): +class TestTemplateValidate(common.HeatTestCase): def test_template_validate_cfn_good(self): t = { diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index 3078c8ae6a..28a33bcf81 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -19,7 +19,7 @@ from heat.common import template_format from heat.engine.clients.os import glance from heat.engine.clients.os import nova from heat.engine import environment -from heat.engine.hot.template import HOTemplate +from heat.engine.hot.template import HOTemplate20130523 from heat.engine import parser from heat.engine import resources from heat.engine import service @@ -1331,7 +1331,7 @@ class validateTest(HeatTestCase): def test_validate_duplicate_parameters_in_group(self): t = template_format.parse(test_template_duplicate_parameters) - template = HOTemplate(t) + template = HOTemplate20130523(t) stack = parser.Stack(self.ctx, 'test_stack', template, environment.Environment({ 'KeyName': 'test', @@ -1346,7 +1346,7 @@ class validateTest(HeatTestCase): def test_validate_invalid_parameter_in_group(self): t = template_format.parse(test_template_invalid_parameter_name) - template = HOTemplate(t) + template = HOTemplate20130523(t) stack = parser.Stack(self.ctx, 'test_stack', template, environment.Environment({ 'KeyName': 'test', @@ -1362,7 +1362,7 @@ class validateTest(HeatTestCase): def test_validate_no_parameters_in_group(self): t = template_format.parse(test_template_no_parameters) - template = HOTemplate(t) + template = HOTemplate20130523(t) stack = parser.Stack(self.ctx, 'test_stack', template) exc = self.assertRaises(exception.StackValidationFailed, stack.validate) diff --git a/setup.cfg b/setup.cfg index 1db0ec2fdf..7ffb952a10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,9 +62,9 @@ heat.constraints = heat.stack_lifecycle_plugins = heat.templates = - heat_template_version.2013-05-23 = heat.engine.hot.template:HOTemplate - heat_template_version.2014-10-16 = heat.engine.hot.template:HOTemplate - HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:CfnTemplate + heat_template_version.2013-05-23 = heat.engine.hot.template:HOTemplate20130523 + heat_template_version.2014-10-16 = heat.engine.hot.template:HOTemplate20141016 + HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:HeatTemplate AWSTemplateFormatVersion.2010-09-09 = heat.engine.cfn.template:CfnTemplate # These are for backwards compat with Icehouse notification_driver configuration values