Convert functions into a fixed part of the template

http://www.mail-archive.com/openstack-dev@lists.openstack.org/msg28987.html
part of blueprint stevedore-plugins
Change-Id: Iabfa077077fa2170f5da8e7752e05e00db91a692
This commit is contained in:
Angus Salkeld 2014-09-09 11:13:43 +10:00
parent b3cb23e5b3
commit b49f283a4d
10 changed files with 160 additions and 159 deletions

View File

@ -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 {}

View File

@ -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,
}

View File

@ -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 {}

View File

@ -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,
}

View File

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

View File

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

View File

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

View File

@ -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 = {

View File

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

View File

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