Provides 'not' condition function

Support 'not' and 'Fn::Not' for templates:
AWSTemplateFormatVersion.2010-09-09
heat_template_version.2016-10-14

Change-Id: I6a9c89a23160a2cf06c37677871bcfbfab9599be
Blueprint: support-conditions-function
This commit is contained in:
huangtianhua 2016-07-27 13:02:39 +08:00
parent 4c4d790c62
commit aeb15bf818
8 changed files with 237 additions and 4 deletions

View File

@ -393,3 +393,35 @@ Usage
Returns 'value_true' if the condition 'create_prod' evaluates to true,
otherwise returns 'value_false'.
-------
Fn::Not
-------
Acts as a NOT operator.
The syntax of the ``Fn::Not`` function is
.. code-block:: yaml
{'Fn::Not': [condition]}
Returns true for a condition that evaluates to false or returns false
for a condition that evaluates to true.
Parameters
~~~~~~~~~~
condition:
A condition such as ``Fn::Equals`` that evaluates to true or false
can be defined in this function, also we can set a boolean value
as a condition.
Usage
~~~~~
.. code-block:: yaml
{'Fn::Not': [{'Fn::Equals': [{'Ref': env_type'}, 'prod']}]}
Returns false if the param 'env_type' equals to 'prod',
otherwise returns true.

View File

@ -242,11 +242,13 @@ for the ``heat_template_version`` key:
if
This version adds ``equals`` condition function which can be used
to compare whether two values are equal. The complete list of supported
to compare whether two values are equal, the ``not`` condition function
which acts as a NOT operator. The complete list of supported
condition functions is::
equals
get_param
not
.. _hot_spec_parameter_groups:
@ -830,6 +832,7 @@ expression
equals
get_param
not
Note: In condition functions, you can reference a value from an input
parameter, but you cannot reference resource or its attribute.
@ -840,8 +843,17 @@ An example of conditions section definition
conditions:
cd1: True
cd2: {get_param: param1}
cd3: {equals: [{get_param: param2}, "yes"]}
cd2:
get_param: param1
cd3:
equals:
- get_param: param2
- yes
cd4:
not:
equals:
- get_param: param3
- yes
The example below shows how to associate condition with resources
@ -1545,3 +1557,40 @@ Note: You define all conditions in the ``conditions`` section of a
template except for ``if`` conditions. You can use the ``if`` condition
in the property values in the ``resources`` section and ``outputs`` sections
of a template.
not
---
The ``not`` function acts as a NOT operator.
The syntax of the ``not`` function is
.. code-block:: yaml
not: condition
Note: A condition such as ``equals`` that evaluates to true or false
can be defined in ``not`` function, also we can set a boolean
value as condition.
Returns true for a condition that evaluates to false or
returns false for a condition that evaluates to true.
For example
.. code-block:: yaml
not:
equals:
- get_param: env_type
- prod
If param 'env_type' equals to 'prod', this function returns false,
otherwise returns true.
Another example
.. code-block:: yaml
not: True
This function returns false.

View File

@ -574,3 +574,38 @@ class ResourceFacade(function.Function):
return function.resolve(up)
elif attr == self.DELETION_POLICY:
return self.stack.parent_resource.t.deletion_policy()
class Not(function.Function):
"""A function acts as a NOT operator.
Takes the form::
{ "Fn::Not" : [condition] }
Returns true for a condition that evaluates to false or
returns false for a condition that evaluates to true.
"""
def __init__(self, stack, fn_name, args):
super(Not, self).__init__(stack, fn_name, args)
try:
if (not self.args or
not isinstance(self.args, collections.Sequence) or
isinstance(self.args, six.string_types)):
raise ValueError()
if len(self.args) != 1:
raise ValueError()
self.condition = self.args[0]
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'[condition]')
raise ValueError(msg % self.fn_name)
def result(self):
resolved_value = function.resolve(self.condition)
if not isinstance(resolved_value, bool):
msg = _('The condition value should be boolean, '
'after resolved the value is: %s')
raise ValueError(msg % resolved_value)
return not resolved_value

View File

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

View File

@ -968,3 +968,34 @@ class If(function.Macro):
raise KeyError(_('Invalid condition name "%s"') % cd_name)
return conditions[cd_name]
class Not(function.Function):
"""A function acts as a NOT operator.
Takes the form::
{ "not" : condition }
Returns true for a condition that evaluates to false or
returns false for a condition that evaluates to true.
"""
def __init__(self, stack, fn_name, args):
super(Not, self).__init__(stack, fn_name, args)
try:
if not self.args:
raise ValueError()
self.condition = self.args
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'condition')
raise ValueError(msg % self.fn_name)
def result(self):
resolved_value = function.resolve(self.condition)
if not isinstance(resolved_value, bool):
msg = _('The condition value should be boolean, '
'after resolved the value is: %s')
raise ValueError(msg % resolved_value)
return not resolved_value

View File

@ -465,6 +465,7 @@ class HOTemplate20161014(HOTemplate20160408):
condition_functions = {
'get_param': hot_funcs.GetParam,
'equals': hot_funcs.Equals,
'not': hot_funcs.Not
}
def __init__(self, tmpl, template_id=None, files=None, env=None):

View File

@ -950,6 +950,55 @@ class TemplateTest(common.HeatTestCase):
self.resolve_condition, snippet, tmpl)
self.assertIn(error_msg, six.text_type(exc))
def test_not(self):
tpl = template_format.parse('''
AWSTemplateFormatVersion: 2010-09-09
Parameters:
env_type:
Type: String
Default: 'test'
''')
snippet = {'Fn::Not': [{'Fn::Equals': [{'Ref': 'env_type'}, 'prod']}]}
# when param 'env_type' is 'test', not function resolve to true
tmpl = template.Template(tpl)
stk = stack.Stack(utils.dummy_context(),
'test_not_true', tmpl)
resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertTrue(resolved)
# when param 'env_type' is 'prod', not function resolve to false
tmpl = template.Template(tpl,
env=environment.Environment(
{'env_type': 'prod'}))
stk = stack.Stack(utils.dummy_context(),
'test_not_false', tmpl)
resolved = self.resolve_condition(snippet, tmpl, stk)
self.assertFalse(resolved)
def test_not_invalid_args(self):
tmpl = template.Template(aws_empty_template)
snippet = {'Fn::Not': ['invalid_arg']}
exc = self.assertRaises(ValueError,
self.resolve_condition, snippet, tmpl)
error_msg = ('The condition value should be boolean, '
'after resolved the value is: invalid_arg')
self.assertIn(error_msg, six.text_type(exc))
# test invalid type
snippet = {'Fn::Not': 'invalid'}
exc = self.assertRaises(exception.StackValidationFailed,
self.resolve_condition, snippet, tmpl)
error_msg = ('.Fn::Not: Arguments to "Fn::Not" must be '
'of the form: [condition]')
self.assertIn(error_msg, six.text_type(exc))
snippet = {'Fn::Not': ['cd1', 'cd2']}
exc = self.assertRaises(exception.StackValidationFailed,
self.resolve_condition, snippet, tmpl)
error_msg = ('.Fn::Not: Arguments to "Fn::Not" must be '
'of the form: [condition]')
self.assertIn(error_msg, six.text_type(exc))
def test_join(self):
tmpl = template.Template(empty_template)
join = {"Fn::Join": [" ", ["foo", "bar"]]}

View File

@ -22,6 +22,11 @@ Parameters:
AllowedValues: [prod, test]
Conditions:
Prod: {"Fn::Equals" : [{Ref: env_type}, "prod"]}
Test:
Fn::Not:
- Fn::Equals:
- Ref: env_type
- prod
Resources:
test_res:
Type: OS::Heat::TestResource
@ -32,6 +37,11 @@ Resources:
Properties:
value: prod_res
Condition: Prod
test_res1:
Type: OS::Heat::TestResource
Properties:
value: just in test env
Condition: Test
Outputs:
res_value:
Value: {"Fn::GetAtt": [prod_res, output]}
@ -40,6 +50,9 @@ Outputs:
Value: {"Fn::GetAtt": [test_res, output]}
prod_resource:
Value: {"Fn::If": [Prod, {Ref: prod_res}, 'no_prod_res']}
test_res1_value:
Value: {"Fn::If": [Test, {"Fn::GetAtt": [test_res1, output]},
'no_test_res1']}
'''
hot_template = '''
@ -52,6 +65,11 @@ parameters:
- allowed_values: [prod, test]
conditions:
prod: {equals : [{get_param: env_type}, "prod"]}
test:
not:
equals:
- get_param: env_type
- prod
resources:
test_res:
type: OS::Heat::TestResource
@ -62,6 +80,11 @@ resources:
properties:
value: prod_res
condition: prod
test_res1:
type: OS::Heat::TestResource
properties:
value: just in test env
condition: test
outputs:
res_value:
value: {get_attr: [prod_res, output]}
@ -70,6 +93,8 @@ outputs:
value: {get_attr: [test_res, output]}
prod_resource:
value: {if: [prod, {get_resource: prod_res}, 'no_prod_res']}
test_res1_value:
value: {if: [test, {get_attr: [test_res1, output]}, 'no_test_res1']}
'''
@ -85,9 +110,10 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.assertIn('test_res', res_names)
def res_assert_for_test(self, resources):
self.assertEqual(1, len(resources))
self.assertEqual(2, len(resources))
res_names = [res.resource_name for res in resources]
self.assertIn('test_res', res_names)
self.assertIn('test_res1', res_names)
self.assertNotIn('prod_res', res_names)
def output_assert_for_prod(self, stack_id):
@ -103,6 +129,10 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
stack_id, 'prod_resource')['output']
self.assertNotEqual('no_prod_res', prod_resource['output_value'])
test_res_output = self.client.stacks.output_show(
stack_id, 'test_res1_value')['output']
self.assertEqual('no_test_res1', test_res_output['output_value'])
def output_assert_for_test(self, stack_id):
output = self.client.stacks.output_show(stack_id,
'res_value')['output']
@ -116,6 +146,11 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
stack_id, 'prod_resource')['output']
self.assertEqual('no_prod_res', prod_resource['output_value'])
test_res_output = self.client.stacks.output_show(
stack_id, 'test_res1_value')['output']
self.assertEqual('just in test env',
test_res_output['output_value'])
def test_stack_create_update_cfn_template_test_to_prod(self):
stack_identifier = self.stack_create(template=cfn_template)
resources = self.client.resources.list(stack_identifier)