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:
parent
b3cb23e5b3
commit
b49f283a4d
@ -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 {}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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 {}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user