Provide 'or' function

Provides condition function 'or' for templates:
AWSTemplateFormatVersion.2010-09-09
heat_template_version.2016-10-14

Change-Id: I6888453404a2cba5127e5d89f7445c5dfe2b7a37
Blueprint: support-conditions-function
This commit is contained in:
huangtianhua 2016-07-27 18:11:58 +08:00 committed by Zane Bitter
parent 91355034fc
commit bfd8d7b2ab
7 changed files with 253 additions and 7 deletions

View File

@ -448,3 +448,26 @@ Usage
Returns true if the param 'env_type' equals to 'prod' and the param 'zone' is
not equal to 'beijing', otherwise returns false.
------
Fn::Or
------
Acts as an OR operator to evaluate all the specified conditions.
Returns true if any one of the specified conditions evaluate to true,
or returns false if all of the conditions evaluates to false.
Parameters
~~~~~~~~~~
condition:
A condition such as Fn::Equals that evaluates to true or false.
Usage
~~~~~
.. code-block:: yaml
{'Fn::Or': [{'Fn::Equals': [{'Ref': zone}, 'shanghai']},
{'Fn::Equals': [{'Ref': zone}, 'beijing']}]}
Returns true if the param 'zone' equals to 'shanghai' or 'beijing',
otherwise returns false.

View File

@ -244,13 +244,16 @@ for the ``heat_template_version`` key:
This version adds ``equals`` condition function which can be used
to compare whether two values are equal, the ``not`` condition function
which acts as a NOT operator, the ``and`` condition function which acts
as an AND operator to evaluate all the specified conditions. The complete
list of supported condition functions is::
as an AND operator to evaluate all the specified conditions, the ``or``
condition function which acts as an OR operator to evaluate all the
specified conditions. The complete list of supported condition
functions is::
equals
get_param
not
and
or
.. _hot_spec_parameter_groups:
@ -836,6 +839,7 @@ expression
get_param
not
and
or
Note: In condition functions, you can reference a value from an input
parameter, but you cannot reference resource or its attribute.
@ -866,6 +870,14 @@ An example of conditions section definition
equals:
- get_param: zone
- beijing
cd6:
or:
- equals:
- get_param: zone
- shanghai
- equals:
- get_param: zone
- beijing
The example below shows how to associate condition with resources
@ -1640,3 +1652,37 @@ For example
If param 'env_type' equals to 'prod', and param 'zone' is not equal to
'beijing', this function returns true, otherwise returns false.
or
--
The ``or`` function acts as an OR operator to evaluate all the
specified conditions.
The syntax of the ``or`` function is
.. code-block:: yaml
or: [{condition_1}, {condition_2}, ... {condition_n}}]
Note: A condition such as ``equals`` or ``not`` that evaluates to true or
false can be defined in ``or`` function, also we can set a boolean
value as condition.
Returns true if any one of the specified conditions evaluate to true,
or returns false if all of the conditions evaluates to false.
For example
.. code-block:: yaml
or:
- equals:
- get_param: env_type
- prod
- not:
equals:
- get_param: zone
- beijing
If param 'env_type' equals to 'prod', or the param 'zone' is not equal to
'beijing', this function returns true, otherwise returns false.

View File

@ -201,7 +201,8 @@ class CfnTemplate(CfnTemplateBase):
'Ref': cfn_funcs.ParamRef,
'Fn::FindInMap': cfn_funcs.FindInMap,
'Fn::Not': cfn_funcs.Not,
'Fn::And': hot_funcs.And
'Fn::And': hot_funcs.And,
'Fn::Or': hot_funcs.Or
}
def __init__(self, tmpl, template_id=None, files=None, env=None):

View File

@ -1037,3 +1037,39 @@ class And(function.Function):
return False
return True
class Or(function.Function):
"""A function acts as an OR operator to evaluate all the conditions.
Takes the form::
{ "or" : [{condition_1}, {condition_2}, {...}, {condition_n}] }
Returns true if any one of the specified conditions evaluate to true,
or returns false if all of the conditions evaluates to false. The minimum
number of conditions that you can include is 2.
"""
def __init__(self, stack, fn_name, args):
super(Or, self).__init__(stack, fn_name, args)
if (not self.args or
not (isinstance(self.args, collections.Sequence) and
not isinstance(self.args, six.string_types)) or
len(self.args) < 2):
msg = _('Arguments to "%s" must be of the form: '
'[{condition_1}, {condition_2}, {...}, {condition_n}], '
'the minimum number of conditions is 2.')
raise ValueError(msg % self.fn_name)
def result(self):
for cd in self.args:
resolved_value = function.resolve(cd)
if not isinstance(resolved_value, bool):
msg = _('The condition value should be boolean, '
'after resolved the value is: %s')
raise ValueError(msg % resolved_value)
if resolved_value:
return True
return False

View File

@ -466,7 +466,8 @@ class HOTemplate20161014(HOTemplate20160408):
'get_param': hot_funcs.GetParam,
'equals': hot_funcs.Equals,
'not': hot_funcs.Not,
'and': hot_funcs.And
'and': hot_funcs.And,
'or': hot_funcs.Or
}
def __init__(self, tmpl, template_id=None, files=None, env=None):

View File

@ -1066,6 +1066,69 @@ class TemplateTest(common.HeatTestCase):
'after resolved the value is: cd1')
self.assertIn(error_msg, six.text_type(exc))
def test_or(self):
tpl = template_format.parse('''
AWSTemplateFormatVersion: 2010-09-09
Parameters:
zone:
Type: String
Default: 'guangzhou'
''')
snippet = {
'Fn::Or': [
{'Fn::Equals': [{'Ref': 'zone'}, 'shanghai']},
{'Fn::Equals': [{'Ref': 'zone'}, 'beijing']}]}
# when param 'zone' is neither equal to 'shanghai' nor 'beijing',
# the 'or' function resolve to false
tmpl = template.Template(tpl)
stk = stack.Stack(utils.dummy_context(),
'test_or_false', tmpl)
resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertFalse(resolved)
# when param 'zone' equals to 'shanghai' or 'beijing',
# the 'or' function resolve to true
tmpl = template.Template(tpl,
env=environment.Environment(
{'zone': 'beijing'}))
stk = stack.Stack(utils.dummy_context(),
'test_or_true', tmpl)
resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertTrue(resolved)
tmpl = template.Template(tpl,
env=environment.Environment(
{'zone': 'shanghai'}))
stk = stack.Stack(utils.dummy_context(),
'test_or_true', tmpl)
resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertTrue(resolved)
def test_or_invalid_args(self):
tmpl = template.Template(aws_empty_template)
snippet = {'Fn::Or': ['invalid_arg']}
exc = self.assertRaises(exception.StackValidationFailed,
self.resolve_condition, snippet, tmpl)
error_msg = ('.Fn::Or: Arguments to "Fn::Or" must be '
'of the form: [{condition_1}, {condition_2}, {...}, '
'{condition_n}]')
self.assertIn(error_msg, six.text_type(exc))
# test invalid type
snippet = {'Fn::Or': 'invalid'}
exc = self.assertRaises(exception.StackValidationFailed,
self.resolve_condition, snippet, tmpl)
self.assertIn(error_msg, six.text_type(exc))
snippet = {'Fn::Or': ['cd1', True]}
exc = self.assertRaises(ValueError,
self.resolve_condition, snippet, tmpl)
error_msg = ('The condition value should be boolean, '
'after resolved the value is: cd1')
self.assertIn(error_msg, six.text_type(exc))
def test_join(self):
tmpl = template.Template(empty_template)
join = {"Fn::Join": [" ", ["foo", "bar"]]}

View File

@ -38,6 +38,14 @@ Conditions:
- Fn::Equals:
- Ref: zone
- beijing
Fujian_Zone:
Fn::Or:
- Fn::Equals:
- Ref: zone
- fuzhou
- Fn::Equals:
- Ref: zone
- xiamen
Resources:
test_res:
Type: OS::Heat::TestResource
@ -58,6 +66,11 @@ Resources:
Properties:
value: beijing_prod_res
Condition: Beijing_Prod
fujian_res:
Type: OS::Heat::TestResource
Condition: Fujian_Zone
Properties:
value: fujian_res
Outputs:
res_value:
Value: {"Fn::GetAtt": [prod_res, output]}
@ -99,6 +112,14 @@ conditions:
- equals:
- get_param: env_type
- prod
fujian_zone:
or:
- equals:
- get_param: zone
- fuzhou
- equals:
- get_param: zone
- xiamen
resources:
test_res:
type: OS::Heat::TestResource
@ -119,6 +140,11 @@ resources:
properties:
value: beijing_prod_res
condition: beijing_prod
fujian_res:
type: OS::Heat::TestResource
condition: fujian_zone
properties:
value: fujian_res
outputs:
res_value:
value: {get_attr: [prod_res, output]}
@ -140,19 +166,30 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
def setUp(self):
super(CreateUpdateResConditionTest, self).setUp()
def res_assert_for_prod(self, resources, bj_prod=True):
def res_assert_for_prod(self, resources, bj_prod=True, fj_zone=False):
res_names = [res.resource_name for res in resources]
if bj_prod:
self.assertEqual(3, len(resources))
self.assertIn('beijing_prod_res', res_names)
elif fj_zone:
self.assertEqual(3, len(resources))
self.assertIn('fujian_res', res_names)
self.assertNotIn('beijing_prod_res', res_names)
else:
self.assertEqual(2, len(resources))
self.assertIn('prod_res', res_names)
self.assertIn('test_res', res_names)
def res_assert_for_test(self, resources):
self.assertEqual(2, len(resources))
def res_assert_for_test(self, resources, fj_zone=False):
res_names = [res.resource_name for res in resources]
if fj_zone:
self.assertEqual(3, len(resources))
self.assertIn('fujian_res', res_names)
else:
self.assertEqual(2, len(resources))
self.assertNotIn('fujian_res', res_names)
self.assertIn('test_res', res_names)
self.assertIn('test_res1', res_names)
self.assertNotIn('prod_res', res_names)
@ -210,6 +247,15 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'fuzhou'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
@ -229,6 +275,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_prod(resources, False)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'prod',
'zone': 'xiamen'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, False)
def test_stack_create_update_cfn_template_prod_to_test(self):
parms = {'env_type': 'prod'}
stack_identifier = self.stack_create(template=cfn_template,
@ -237,6 +293,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'zone': 'xiamen',
'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'env_type': 'test'}
self.update_stack(stack_identifier,
template=cfn_template,
@ -246,6 +312,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'fuzhou'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
def test_stack_create_update_hot_template_test_to_prod(self):
stack_identifier = self.stack_create(template=hot_template)
resources = self.client.resources.list(stack_identifier)