Support 'conditions' section for templates

This changes:
1. Support 'Conditions' for AWSTemplateFormatVersion: 2010-09-09
2. Support 'conditions' for heat_template_version: 2016-10-14
3. There is no need to add a new HeatTemplateFormatVersion template,
   because we decide to support conditions in AWSTemplateFormatVersion
   and heat_template_version: 2016-10-14, so remove the
   HeatTemplateFormatVersion.2016-10-14
4. Move the definition of function 'equals' to hot/functions.py
5. Mark 'equals' as condition function which supported in conditions.

Change-Id: I2e7bdfa1c2052e75f35f2bd0003cdc170188d8b8
Blueprint: support-conditions-function
This commit is contained in:
huangtianhua 2016-07-22 15:50:20 +08:00
parent 6c9f33dade
commit 70c4ab3fcf
12 changed files with 224 additions and 92 deletions

View File

@ -43,7 +43,7 @@ HOT templates are defined in YAML and follow the structure outlined below.
.. code-block:: yaml .. code-block:: yaml
heat_template_version: 2015-04-30 heat_template_version: 2016-10-14
description: description:
# a description of the template # a description of the template
@ -60,6 +60,9 @@ HOT templates are defined in YAML and follow the structure outlined below.
outputs: outputs:
# declaration of output parameters # declaration of output parameters
conditions:
# declaration of conditions
heat_template_version heat_template_version
This key with value ``2013-05-23`` (or a later date) indicates that the This key with value ``2013-05-23`` (or a later date) indicates that the
YAML document is a HOT template of the specified version. YAML document is a HOT template of the specified version.
@ -89,6 +92,15 @@ outputs
once the template has been instantiated. This section is optional and can once the template has been instantiated. This section is optional and can
be omitted when no output values are required. be omitted when no output values are required.
conditions
This optional section includes statements which can be used to restrict
when a resource is created or when a property is defined. They can be
associated with resources and resource properties in the
``resources`` section, also can be associated with outputs in the
``outputs`` sections of a template.
Note: Support for this section is added in the Newton version.
.. _hot_spec_template_version: .. _hot_spec_template_version:
@ -209,10 +221,9 @@ for the ``heat_template_version`` key:
The key with value ``2016-10-14`` or ``newton`` indicates that the YAML The key with value ``2016-10-14`` or ``newton`` indicates that the YAML
document is a HOT template and it may contain features added and/or removed document is a HOT template and it may contain features added and/or removed
up until the Newton release. This version adds the ``yaql`` function which up until the Newton release. This version adds the ``yaql`` function which
can be used for evaluation of complex expressions, and also adds ``equals`` can be used for evaluation of complex expressions, and the ``map_replace``
function which can be used to compare whether two values are equal, and function that can do key/value replacements on a mapping. The complete list
the ``map_replace`` function that can do key/value replacements on a mapping. of supported functions is::
The complete list of supported functions is::
digest digest
get_attr get_attr
@ -227,7 +238,13 @@ for the ``heat_template_version`` key:
str_replace str_replace
str_split str_split
yaql yaql
This version also adds ``equals`` condition function which can be used
to compare whether two values are equal. The complete list of supported
condition functions is::
equals equals
get_param
.. _hot_spec_parameter_groups: .. _hot_spec_parameter_groups:
@ -756,6 +773,48 @@ be defined as an output parameter
value: { get_attr: [my_instance, first_address] } value: { get_attr: [my_instance, first_address] }
Conditions section
~~~~~~~~~~~~~~~~~~
The ``conditions`` section defines one or more conditions which are evaluated
based on input parameter values provided when a user creates or updates a
stack. The condition can be associated with resources, resource properties and
outputs. For example, based on the result of a condition, user can
conditionally create resources, user can conditionally set different values
of properties, and user can conditionally give outputs of a stack.
The ``conditions`` section is defined with the following syntax
.. code-block:: yaml
conditions:
<condition name1>: {expression1}
<condition name2>: {expression2}
...
condition name
The condition name, which must be unique within the ``conditions``
section of a template.
expression
The expression which is expected to return True or False. Usually,
the condition functions can be used as expression to define conditions::
equals
get_param
Note: In condition functions, you can reference a value from an input
parameter, but you cannot reference resource or its attribute.
An example of conditions section definition
.. code-block:: yaml
conditions:
cd1: True
cd2: {get_param: param1}
cd3: {equals: [{get_param: param2}, "yes"]}
.. _hot_spec_intrinsic_functions: .. _hot_spec_intrinsic_functions:
Intrinsic functions Intrinsic functions

View File

@ -129,6 +129,10 @@ class InvalidTemplateSection(HeatException):
msg_fmt = _("The template section is invalid: %(section)s") msg_fmt = _("The template section is invalid: %(section)s")
class InvalidConditionFunction(HeatException):
msg_fmt = _("The function is not supported in condition: %(func)s")
class ImmutableParameterModified(HeatException): class ImmutableParameterModified(HeatException):
msg_fmt = _("The following parameters are immutable and may not be " msg_fmt = _("The following parameters are immutable and may not be "
"updated: %(keys)s") "updated: %(keys)s")

View File

@ -48,36 +48,6 @@ class FindInMap(function.Function):
return mapping[key][value] return mapping[key][value]
class Equals(function.Function):
"""A function for comparing whether two values are equal.
Takes the form::
{ "Fn::Equals" : ["value_1", "value_2"] }
The value to be any type that you want to compare. Returns true
if the two values are equal or false if they aren't.
"""
def __init__(self, stack, fn_name, args):
super(Equals, self).__init__(stack, fn_name, args)
try:
if (not self.args or
not isinstance(self.args, list)):
raise ValueError()
self.value1, self.value2 = self.args
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'[value_1, value_2]')
raise ValueError(msg % self.fn_name)
def result(self):
resolved_v1 = function.resolve(self.value1)
resolved_v2 = function.resolve(self.value2)
return resolved_v1 == resolved_v2
class GetAZs(function.Function): class GetAZs(function.Function):
"""A function for retrieving the availability zones. """A function for retrieving the availability zones.

View File

@ -19,20 +19,21 @@ from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine.cfn import functions as cfn_funcs from heat.engine.cfn import functions as cfn_funcs
from heat.engine import function from heat.engine import function
from heat.engine.hot import functions as hot_funcs
from heat.engine import parameters from heat.engine import parameters
from heat.engine import rsrc_defn from heat.engine import rsrc_defn
from heat.engine import template from heat.engine import template
class CfnTemplate(template.Template): class CfnTemplateBase(template.Template):
"""A stack template.""" """The base implementation of cfn template."""
SECTIONS = ( SECTIONS = (
VERSION, ALTERNATE_VERSION, VERSION, ALTERNATE_VERSION,
DESCRIPTION, MAPPINGS, PARAMETERS, RESOURCES, OUTPUTS DESCRIPTION, MAPPINGS, PARAMETERS, RESOURCES, OUTPUTS,
) = ( ) = (
'AWSTemplateFormatVersion', 'HeatTemplateFormatVersion', 'AWSTemplateFormatVersion', 'HeatTemplateFormatVersion',
'Description', 'Mappings', 'Parameters', 'Resources', 'Outputs' 'Description', 'Mappings', 'Parameters', 'Resources', 'Outputs',
) )
OUTPUT_KEYS = ( OUTPUT_KEYS = (
@ -206,7 +207,26 @@ class CfnTemplate(template.Template):
self.t[self.RESOURCES][name] = cfn_tmpl self.t[self.RESOURCES][name] = cfn_tmpl
class HeatTemplate(CfnTemplate): class CfnTemplate(CfnTemplateBase):
CONDITIONS = 'Conditions'
SECTIONS = CfnTemplateBase.SECTIONS + (CONDITIONS,)
condition_functions = {
'Fn::Equals': hot_funcs.Equals,
'Ref': cfn_funcs.ParamRef,
'Fn::FindInMap': cfn_funcs.FindInMap,
}
def __init__(self, tmpl, template_id=None, files=None, env=None):
super(CfnTemplate, self).__init__(tmpl, template_id, files, env)
self._parser_condition_functions = dict(
(n, function.Invalid) for n in self.functions)
self._parser_condition_functions.update(self.condition_functions)
class HeatTemplate(CfnTemplateBase):
functions = { functions = {
'Fn::FindInMap': cfn_funcs.FindInMap, 'Fn::FindInMap': cfn_funcs.FindInMap,
'Fn::GetAZs': cfn_funcs.GetAZs, 'Fn::GetAZs': cfn_funcs.GetAZs,
@ -220,21 +240,3 @@ class HeatTemplate(CfnTemplate):
'Fn::MemberListToMap': cfn_funcs.MemberListToMap, 'Fn::MemberListToMap': cfn_funcs.MemberListToMap,
'Fn::ResourceFacade': cfn_funcs.ResourceFacade, 'Fn::ResourceFacade': cfn_funcs.ResourceFacade,
} }
class HeatTemplate20161014(HeatTemplate):
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,
# supports Fn::Equals in Newton
'Fn::Equals': cfn_funcs.Equals,
}

View File

@ -18,6 +18,8 @@ import weakref
import six import six
from heat.common import exception
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class Function(object): class Function(object):
@ -203,3 +205,17 @@ def dep_attrs(snippet, resource_name):
attrs = (dep_attrs(value, resource_name) for value in snippet) attrs = (dep_attrs(value, resource_name) for value in snippet)
return itertools.chain.from_iterable(attrs) return itertools.chain.from_iterable(attrs)
return [] return []
class Invalid(Function):
"""A function for checking condition functions and to force failures.
This function is used to force failures for functions that are not
supported in condition definition.
"""
def __init__(self, stack, fn_name, args):
raise exception.InvalidConditionFunction(func=fn_name)
def result(self):
return super(Invalid, self).result()

View File

@ -872,3 +872,33 @@ class Yaql(function.Function):
self._expression = function.resolve(self._expression) self._expression = function.resolve(self._expression)
self.validate_expression(self._expression) self.validate_expression(self._expression)
return self.parser(self._expression).evaluate(context=self.context) return self.parser(self._expression).evaluate(context=self.context)
class Equals(function.Function):
"""A function for comparing whether two values are equal.
Takes the form::
{ "equals" : ["value_1", "value_2"] }
The value can be any type that you want to compare. Returns true
if the two values are equal or false if they aren't.
"""
def __init__(self, stack, fn_name, args):
super(Equals, self).__init__(stack, fn_name, args)
try:
if (not self.args or
not isinstance(self.args, list)):
raise ValueError()
self.value1, self.value2 = self.args
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'[value_1, value_2]')
raise ValueError(msg % self.fn_name)
def result(self):
resolved_v1 = function.resolve(self.value1)
resolved_v2 = function.resolve(self.value2)
return resolved_v1 == resolved_v2

View File

@ -30,10 +30,10 @@ class HOTemplate20130523(template.Template):
SECTIONS = ( SECTIONS = (
VERSION, DESCRIPTION, PARAMETER_GROUPS, VERSION, DESCRIPTION, PARAMETER_GROUPS,
PARAMETERS, RESOURCES, OUTPUTS, MAPPINGS PARAMETERS, RESOURCES, OUTPUTS, MAPPINGS,
) = ( ) = (
'heat_template_version', 'description', 'parameter_groups', 'heat_template_version', 'description', 'parameter_groups',
'parameters', 'resources', 'outputs', '__undefined__' 'parameters', 'resources', 'outputs', '__undefined__',
) )
OUTPUT_KEYS = ( OUTPUT_KEYS = (
@ -394,6 +394,15 @@ class HOTemplate20160408(HOTemplate20151015):
class HOTemplate20161014(HOTemplate20160408): class HOTemplate20161014(HOTemplate20160408):
CONDITIONS = 'conditions'
SECTIONS = HOTemplate20160408.SECTIONS + (CONDITIONS,)
_CFN_TO_HOT_SECTIONS = HOTemplate20160408._CFN_TO_HOT_SECTIONS
_CFN_TO_HOT_SECTIONS.update({
cfn_template.CfnTemplate.CONDITIONS: CONDITIONS})
deletion_policies = { deletion_policies = {
'Delete': rsrc_defn.ResourceDefinition.DELETE, 'Delete': rsrc_defn.ResourceDefinition.DELETE,
'Retain': rsrc_defn.ResourceDefinition.RETAIN, 'Retain': rsrc_defn.ResourceDefinition.RETAIN,
@ -426,7 +435,6 @@ class HOTemplate20161014(HOTemplate20160408):
# functions added in 2016-10-14 # functions added in 2016-10-14
'yaql': hot_funcs.Yaql, 'yaql': hot_funcs.Yaql,
'equals': cfn_funcs.Equals,
'map_replace': hot_funcs.MapReplace, 'map_replace': hot_funcs.MapReplace,
# functions removed from 2015-10-15 # functions removed from 2015-10-15
@ -442,3 +450,20 @@ class HOTemplate20161014(HOTemplate20160408):
'Fn::ResourceFacade': hot_funcs.Removed, 'Fn::ResourceFacade': hot_funcs.Removed,
'Ref': hot_funcs.Removed, 'Ref': hot_funcs.Removed,
} }
condition_functions = {
'get_param': hot_funcs.GetParam,
'equals': hot_funcs.Equals,
}
def __init__(self, tmpl, template_id=None, files=None, env=None):
super(HOTemplate20161014, self).__init__(
tmpl, template_id, files, env)
self._parser_condition_functions = {}
for n, f in six.iteritems(self.functions):
if not isinstance(f, hot_funcs.Removed):
self._parser_condition_functions[n] = function.Invalid
else:
self._parser_condition_functions[n] = f
self._parser_condition_functions.update(self.condition_functions)

View File

@ -90,6 +90,10 @@ def get_template_class(template_data):
class Template(collections.Mapping): class Template(collections.Mapping):
"""A stack template.""" """A stack template."""
condition_functions = {}
_parser_condition_functions = {}
functions = {}
def __new__(cls, template, *args, **kwargs): def __new__(cls, template, *args, **kwargs):
"""Create a new Template of the appropriate class.""" """Create a new Template of the appropriate class."""
global _template_classes global _template_classes
@ -260,6 +264,9 @@ class Template(collections.Mapping):
def parse(self, stack, snippet, path=''): def parse(self, stack, snippet, path=''):
return parse(self.functions, stack, snippet, path) return parse(self.functions, stack, snippet, path)
def parse_condition(self, stack, snippet):
return parse(self._parser_condition_functions, stack, snippet)
def validate(self): def validate(self):
"""Validate the template. """Validate the template.

View File

@ -110,7 +110,7 @@ class TestContainer(common.HeatTestCase):
self.stack = utils.parse_stack(tmpl) self.stack = utils.parse_stack(tmpl)
else: else:
self.stack = stack self.stack = stack
resource_defns = self.stack.t.resource_definitions(stack) resource_defns = self.stack.t.resource_definitions(self.stack)
if snippet is None: if snippet is None:
snippet = resource_defns['container'] snippet = resource_defns['container']
res_class = container.resource_mapping()[tmpl_name] res_class = container.resource_mapping()[tmpl_name]

View File

@ -156,6 +156,10 @@ class HOTemplateTest(common.HeatTestCase):
def resolve(snippet, template, stack=None): def resolve(snippet, template, stack=None):
return function.resolve(template.parse(stack, snippet)) return function.resolve(template.parse(stack, snippet))
@staticmethod
def resolve_condition(snippet, template, stack=None):
return function.resolve(template.parse_condition(stack, snippet))
def test_defaults(self): def test_defaults(self):
"""Test default content behavior of HOT template.""" """Test default content behavior of HOT template."""
@ -1073,7 +1077,7 @@ class HOTemplateTest(common.HeatTestCase):
tmpl = template.Template(hot_tpl) tmpl = template.Template(hot_tpl)
stack = parser.Stack(utils.dummy_context(), stack = parser.Stack(utils.dummy_context(),
'test_equals_false', tmpl) 'test_equals_false', tmpl)
resolved = self.resolve(snippet, tmpl, stack) resolved = self.resolve_condition(snippet, tmpl, stack)
self.assertFalse(resolved) self.assertFalse(resolved)
# when param 'env_type' is 'prod', equals function resolve to true # when param 'env_type' is 'prod', equals function resolve to true
tmpl = template.Template(hot_tpl, tmpl = template.Template(hot_tpl,
@ -1081,7 +1085,7 @@ class HOTemplateTest(common.HeatTestCase):
{'env_type': 'prod'})) {'env_type': 'prod'}))
stack = parser.Stack(utils.dummy_context(), stack = parser.Stack(utils.dummy_context(),
'test_equals_true', tmpl) 'test_equals_true', tmpl)
resolved = self.resolve(snippet, tmpl, stack) resolved = self.resolve_condition(snippet, tmpl, stack)
self.assertTrue(resolved) self.assertTrue(resolved)
def test_equals_invalid_args(self): def test_equals_invalid_args(self):
@ -1089,15 +1093,27 @@ class HOTemplateTest(common.HeatTestCase):
snippet = {'equals': ['test', 'prod', 'invalid']} snippet = {'equals': ['test', 'prod', 'invalid']}
exc = self.assertRaises(exception.StackValidationFailed, exc = self.assertRaises(exception.StackValidationFailed,
self.resolve, snippet, tmpl) self.resolve_condition, snippet, tmpl)
self.assertIn('.equals: Arguments to "equals" must be of the form: '
'[value_1, value_2]', six.text_type(exc)) error_msg = ('.equals: Arguments to "equals" must be '
'of the form: [value_1, value_2]')
self.assertIn(error_msg, six.text_type(exc))
snippet = {'equals': "invalid condition"} snippet = {'equals': "invalid condition"}
exc = self.assertRaises(exception.StackValidationFailed, exc = self.assertRaises(exception.StackValidationFailed,
self.resolve, snippet, tmpl) self.resolve_condition, snippet, tmpl)
self.assertIn('.equals: Arguments to "equals" must be of the form: ' self.assertIn(error_msg, six.text_type(exc))
'[value_1, value_2]', six.text_type(exc))
def test_equals_with_non_supported_function(self):
tmpl = template.Template(hot_newton_tpl_empty)
snippet = {'equals': [{'get_attr': [None, 'att1']},
{'get_attr': [None, 'att2']}]}
exc = self.assertRaises(exception.InvalidConditionFunction,
self.resolve_condition, snippet, tmpl)
error_msg = 'The function is not supported in condition: get_attr'
self.assertIn(error_msg, six.text_type(exc))
def test_repeat(self): def test_repeat(self):
"""Test repeat function.""" """Test repeat function."""

View File

@ -55,8 +55,8 @@ empty_template = template_format.parse('''{
"HeatTemplateFormatVersion" : "2012-12-12", "HeatTemplateFormatVersion" : "2012-12-12",
}''') }''')
empty_template20161014 = template_format.parse('''{ aws_empty_template = template_format.parse('''{
"HeatTemplateFormatVersion" : "2016-10-14", "AWSTemplateFormatVersion" : "2010-09-09",
}''') }''')
parameter_template = template_format.parse('''{ parameter_template = template_format.parse('''{
@ -119,17 +119,20 @@ class TemplatePluginFixture(fixtures.Fixture):
class TestTemplatePluginManager(common.HeatTestCase): class TestTemplatePluginManager(common.HeatTestCase):
def test_template_NEW_good(self): def test_template_NEW_good(self):
class NewTemplate(template.Template): class NewTemplate(template.Template):
SECTIONS = (VERSION, MAPPINGS) = ('NEWTemplateFormatVersion', SECTIONS = (VERSION, MAPPINGS, CONDITIONS) = (
'__undefined__') 'NEWTemplateFormatVersion',
'__undefined__',
'conditions')
RESOURCES = 'thingies' RESOURCES = 'thingies'
def param_schemata(self): def param_schemata(self, param_defaults=None):
pass pass
def get_section_name(self, section): def get_section_name(self, section):
pass pass
def parameters(self, stack_identifier, user_params): def parameters(self, stack_identifier, user_params,
param_defaults=None):
pass pass
def validate_resource_definitions(self, stack): def validate_resource_definitions(self, stack):
@ -144,9 +147,6 @@ class TestTemplatePluginManager(common.HeatTestCase):
def __getitem__(self, section): def __getitem__(self, section):
return {} return {}
def functions(self):
return {}
class NewTemplatePrint(function.Function): class NewTemplatePrint(function.Function):
def result(self): def result(self):
return 'always this' return 'always this'
@ -495,6 +495,10 @@ class TemplateTest(common.HeatTestCase):
def resolve(snippet, template, stack=None): def resolve(snippet, template, stack=None):
return function.resolve(template.parse(stack, snippet)) return function.resolve(template.parse(stack, snippet))
@staticmethod
def resolve_condition(snippet, template, stack=None):
return function.resolve(template.parse_condition(stack, snippet))
def test_defaults(self): def test_defaults(self):
empty = template.Template(empty_template) empty = template.Template(empty_template)
self.assertNotIn('AWSTemplateFormatVersion', empty) self.assertNotIn('AWSTemplateFormatVersion', empty)
@ -593,8 +597,7 @@ class TemplateTest(common.HeatTestCase):
invalid_heat_version_tmp) invalid_heat_version_tmp)
ex_error_msg = ('The template version is invalid: ' ex_error_msg = ('The template version is invalid: '
'"HeatTemplateFormatVersion: 2010-09-09". ' '"HeatTemplateFormatVersion: 2010-09-09". '
'"HeatTemplateFormatVersion" should be one of: ' '"HeatTemplateFormatVersion" should be: 2012-12-12')
'2012-12-12, 2016-10-14')
self.assertEqual(ex_error_msg, six.text_type(init_ex)) self.assertEqual(ex_error_msg, six.text_type(init_ex))
def test_invalid_version_not_in_heat_versions(self): def test_invalid_version_not_in_heat_versions(self):
@ -771,7 +774,7 @@ class TemplateTest(common.HeatTestCase):
def test_equals(self): def test_equals(self):
tpl = template_format.parse(''' tpl = template_format.parse('''
HeatTemplateFormatVersion: 2016-10-14 AWSTemplateFormatVersion: 2010-09-09
Parameters: Parameters:
env_type: env_type:
Type: String Type: String
@ -782,7 +785,7 @@ class TemplateTest(common.HeatTestCase):
tmpl = template.Template(tpl) tmpl = template.Template(tpl)
stk = stack.Stack(utils.dummy_context(), stk = stack.Stack(utils.dummy_context(),
'test_equals_false', tmpl) 'test_equals_false', tmpl)
resolved = self.resolve(snippet, tmpl, stk) resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertFalse(resolved) self.assertFalse(resolved)
# when param 'env_type' is 'prod', equals function resolve to true # when param 'env_type' is 'prod', equals function resolve to true
tmpl = template.Template(tpl, tmpl = template.Template(tpl,
@ -790,23 +793,24 @@ class TemplateTest(common.HeatTestCase):
{'env_type': 'prod'})) {'env_type': 'prod'}))
stk = stack.Stack(utils.dummy_context(), stk = stack.Stack(utils.dummy_context(),
'test_equals_true', tmpl) 'test_equals_true', tmpl)
resolved = self.resolve(snippet, tmpl, stk) resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertTrue(resolved) self.assertTrue(resolved)
def test_equals_invalid_args(self): def test_equals_invalid_args(self):
tmpl = template.Template(empty_template20161014) tmpl = template.Template(aws_empty_template)
snippet = {'Fn::Equals': ['test', 'prod', 'invalid']} snippet = {'Fn::Equals': ['test', 'prod', 'invalid']}
exc = self.assertRaises(exception.StackValidationFailed, exc = self.assertRaises(exception.StackValidationFailed,
self.resolve, snippet, tmpl) self.resolve_condition, snippet, tmpl)
self.assertIn('.Fn::Equals: Arguments to "Fn::Equals" must be of '
'the form: [value_1, value_2]', six.text_type(exc)) error_msg = ('.Fn::Equals: Arguments to "Fn::Equals" must be '
'of the form: [value_1, value_2]')
self.assertIn(error_msg, six.text_type(exc))
# test invalid type # test invalid type
snippet = {'Fn::Equals': {"equal": False}} snippet = {'Fn::Equals': {"equal": False}}
exc = self.assertRaises(exception.StackValidationFailed, exc = self.assertRaises(exception.StackValidationFailed,
self.resolve, snippet, tmpl) self.resolve_condition, snippet, tmpl)
self.assertIn('.Fn::Equals: Arguments to "Fn::Equals" must be of ' self.assertIn(error_msg, six.text_type(exc))
'the form: [value_1, value_2]', six.text_type(exc))
def test_join(self): def test_join(self):
tmpl = template.Template(empty_template) tmpl = template.Template(empty_template)

View File

@ -155,7 +155,6 @@ heat.templates =
heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408 heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408
heat_template_version.2016-10-14 = heat.engine.hot.template:HOTemplate20161014 heat_template_version.2016-10-14 = heat.engine.hot.template:HOTemplate20161014
heat_template_version.newton = heat.engine.hot.template:HOTemplate20161014 heat_template_version.newton = heat.engine.hot.template:HOTemplate20161014
HeatTemplateFormatVersion.2016-10-14 = heat.engine.cfn.template:HeatTemplate20161014
[global] [global]
setup-hooks = setup-hooks =