Merge "Allow reference conditions by name"

This commit is contained in:
Jenkins 2016-09-07 10:10:59 +00:00 committed by Gerrit Code Review
commit 45bbec711e
5 changed files with 297 additions and 72 deletions

View File

@ -842,7 +842,8 @@ expression
or
Note: In condition functions, you can reference a value from an input
parameter, but you cannot reference resource or its attribute.
parameter, but you cannot reference resource or its attribute. We support
referencing other conditions (by condition name) in condition functions.
An example of conditions section definition
@ -878,6 +879,12 @@ An example of conditions section definition
- equals:
- get_param: zone
- beijing
cd7:
not: cd4
cd8:
and:
- cd1
- cd2
The example below shows how to associate condition with resources
@ -1592,9 +1599,9 @@ The syntax of the ``not`` function is
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.
Note: A condition can be an expression such as ``equals``, ``or`` and ``and``
that evaluates to true or false, can be a boolean, and can be other condition
name defined in ``conditions`` section of template.
Returns true for a condition that evaluates to false or
returns false for a condition that evaluates to true.
@ -1611,7 +1618,7 @@ For example
If param 'env_type' equals to 'prod', this function returns false,
otherwise returns true.
Another example
Another example with boolean value definition
.. code-block:: yaml
@ -1619,6 +1626,15 @@ Another example
This function returns false.
Another example reference other condition name
.. code-block:: yaml
not: my_other_condition
This function returns false if my_other_condition evaluates to true,
otherwise returns true.
and
---
The ``and`` function acts as an AND operator to evaluate all the
@ -1630,9 +1646,9 @@ The syntax of the ``and`` function is
and: [{condition_1}, {condition_2}, ... {condition_n}]
Note: A condition such as ``equals`` or ``not`` that evaluates to true or
false can be defined in ``and`` function, also we can set a boolean
value as condition.
Note: A condition can be an expression such as ``equals``, ``or`` and ``not``
that evaluates to true or false, can be a boolean, and can be other condition
names defined in ``conditions`` section of template.
Returns true if all the specified conditions evaluate to true, or returns
false if any one of the conditions evaluates to false.
@ -1653,6 +1669,17 @@ For example
If param 'env_type' equals to 'prod', and param 'zone' is not equal to
'beijing', this function returns true, otherwise returns false.
Another example reference with other conditions
.. code-block:: yaml
and:
- other_condition_1
- other_condition_2
This function returns true if other_condition_1 and other_condition_2
evaluate to true both, otherwise returns false.
or
--
The ``or`` function acts as an OR operator to evaluate all the
@ -1664,9 +1691,9 @@ The syntax of the ``or`` function is
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.
Note: A condition can be an expression such as ``equals``, ``and`` and ``not``
that evaluates to true or false, can be a boolean, and can be other condition
names defined in ``conditions`` section of template.
Returns true if any one of the specified conditions evaluate to true,
or returns false if all of the conditions evaluates to false.
@ -1686,3 +1713,14 @@ For example
If param 'env_type' equals to 'prod', or the param 'zone' is not equal to
'beijing', this function returns true, otherwise returns false.
Another example reference other conditions
.. code-block:: yaml
or:
- other_condition_1
- other_condition_2
This function returns true if any one of other_condition_1 or
other_condition_2 evaluate to true, otherwise returns false.

View File

@ -576,7 +576,7 @@ class ResourceFacade(function.Function):
return self.stack.parent_resource.t.deletion_policy()
class Not(function.Function):
class Not(function.Macro):
"""A function acts as a NOT operator.
Takes the form::
@ -587,8 +587,7 @@ class Not(function.Function):
returns false for a condition that evaluates to true.
"""
def __init__(self, stack, fn_name, args):
super(Not, self).__init__(stack, fn_name, args)
def parse_args(self, parse_func):
try:
if (not self.args or
not isinstance(self.args, collections.Sequence) or
@ -596,14 +595,21 @@ class Not(function.Function):
raise ValueError()
if len(self.args) != 1:
raise ValueError()
self.condition = self.args[0]
condition = self.args[0]
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'[condition]')
raise ValueError(msg % self.fn_name)
if isinstance(condition, six.string_types):
cd_snippets = self.template.get_condition_definitions()
if condition in cd_snippets:
condition = cd_snippets[condition]
return parse_func(condition)
def result(self):
resolved_value = function.resolve(self.condition)
resolved_value = function.resolve(self.parsed)
if not isinstance(resolved_value, bool):
msg = _('The condition value should be boolean, '
'after resolved the value is: %s')

View File

@ -957,7 +957,41 @@ class If(function.Macro):
return conditions[cd_name]
class Not(function.Function):
class ConditionMacro(function.Macro):
"""The base function of condition."""
def _parse_arg(self, arg):
if isinstance(arg, six.string_types):
cd_snippets = self.template.get_condition_definitions()
if arg in cd_snippets:
arg = cd_snippets[arg]
return arg
def parse_args(self, parse_func):
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)
parsed_args = []
for arg in self.args:
parsed_args.append(self._parse_arg(arg))
return parse_func(parsed_args)
def _validate_resolved_value(self, resolved_value):
if not isinstance(resolved_value, bool):
msg = _('The condition value should be boolean, '
'after resolved the value is: %s')
raise ValueError(msg % resolved_value)
class Not(ConditionMacro):
"""A function acts as a NOT operator.
Takes the form::
@ -968,27 +1002,27 @@ class Not(function.Function):
returns false for a condition that evaluates to true.
"""
def __init__(self, stack, fn_name, args):
super(Not, self).__init__(stack, fn_name, args)
def parse_args(self, parse_func):
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)
self.args = self._parse_arg(self.args)
return parse_func(self.args)
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)
resolved_value = function.resolve(self.parsed)
self._validate_resolved_value(resolved_value)
return not resolved_value
class And(function.Function):
class And(ConditionMacro):
"""A function acts as an AND operator.
Takes the form::
@ -1000,31 +1034,19 @@ class And(function.Function):
of conditions that you can include is 2.
"""
def __init__(self, stack, fn_name, args):
super(And, 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:
_result = True
for cd in self.parsed:
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)
self._validate_resolved_value(resolved_value)
if not resolved_value:
return False
_result = False
return True
return _result
class Or(function.Function):
class Or(ConditionMacro):
"""A function acts as an OR operator to evaluate all the conditions.
Takes the form::
@ -1036,24 +1058,11 @@ class Or(function.Function):
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:
for cd in self.parsed:
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)
self._validate_resolved_value(resolved_value)
if resolved_value:
return True

View File

@ -1113,11 +1113,11 @@ class TemplateTest(common.HeatTestCase):
self.assertIn(error_msg, six.text_type(exc))
snippet = {'Fn::Or': ['cd1', True]}
snippet = {'Fn::Or': ['invalid_cd', True]}
exc = self.assertRaises(ValueError,
self.resolve_condition, snippet, tmpl)
error_msg = ('The condition value should be boolean, '
'after resolved the value is: cd1')
'after resolved the value is: invalid_cd')
self.assertIn(error_msg, six.text_type(exc))
def test_join(self):

View File

@ -38,6 +38,14 @@ Conditions:
- Fn::Equals:
- Ref: zone
- beijing
Xian_Zone:
Fn::Equals:
- Ref: zone
- xian
Xianyang_Zone:
Fn::Equals:
- Ref: zone
- xianyang
Fujian_Zone:
Fn::Or:
- Fn::Equals:
@ -46,6 +54,16 @@ Conditions:
- Fn::Equals:
- Ref: zone
- xiamen
Fujian_Prod:
Fn::And:
- Fujian_Zone
- Prod
Shannxi_Provice:
Fn::Or:
- Xian_Zone
- Xianyang_Zone
Not_Shannxi:
Fn::Not: [Shannxi_Provice]
Resources:
test_res:
Type: OS::Heat::TestResource
@ -71,6 +89,21 @@ Resources:
Condition: Fujian_Zone
Properties:
value: fujian_res
fujian_prod_res:
Type: OS::Heat::TestResource
Condition: Fujian_Prod
Properties:
value: fujian_prod_res
shannxi_res:
Type: OS::Heat::TestResource
Condition: Shannxi_Provice
Properties:
value: shannxi_res
not_shannxi_res:
Type: OS::Heat::TestResource
Condition: Not_Shannxi
Properties:
value: not_shannxi_res
Outputs:
res_value:
Value: {"Fn::GetAtt": [prod_res, output]}
@ -112,6 +145,14 @@ conditions:
- equals:
- get_param: env_type
- prod
xian_zone:
equals:
- get_param: zone
- xian
xianyang_zone:
equals:
- get_param: zone
- xianyang
fujian_zone:
or:
- equals:
@ -120,6 +161,16 @@ conditions:
- equals:
- get_param: zone
- xiamen
fujian_prod:
and:
- fujian_zone
- prod
shannxi_provice:
or:
- xian_zone
- xianyang_zone
not_shannxi:
not: shannxi_provice
resources:
test_res:
type: OS::Heat::TestResource
@ -145,6 +196,21 @@ resources:
condition: fujian_zone
properties:
value: fujian_res
fujian_prod_res:
type: OS::Heat::TestResource
condition: fujian_prod
properties:
value: fujian_prod_res
shannxi_res:
type: OS::Heat::TestResource
condition: shannxi_provice
properties:
value: shannxi_res
not_shannxi_res:
type: OS::Heat::TestResource
condition: not_shannxi
properties:
value: not_shannxi_res
outputs:
res_value:
value: {get_attr: [prod_res, output]}
@ -166,30 +232,43 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
def setUp(self):
super(CreateUpdateResConditionTest, self).setUp()
def res_assert_for_prod(self, resources, bj_prod=True, fj_zone=False):
def res_assert_for_prod(self, resources, bj_prod=True, fj_zone=False,
shannxi_provice=False):
res_names = [res.resource_name for res in resources]
if bj_prod:
self.assertEqual(3, len(resources))
self.assertEqual(4, len(resources))
self.assertIn('beijing_prod_res', res_names)
self.assertIn('not_shannxi_res', res_names)
elif fj_zone:
self.assertEqual(3, len(resources))
self.assertEqual(5, len(resources))
self.assertIn('fujian_res', res_names)
self.assertNotIn('beijing_prod_res', res_names)
self.assertIn('not_shannxi_res', res_names)
self.assertIn('fujian_prod_res', res_names)
elif shannxi_provice:
self.assertEqual(3, len(resources))
self.assertIn('shannxi_res', res_names)
else:
self.assertEqual(2, len(resources))
self.assertEqual(3, len(resources))
self.assertIn('not_shannxi_res', res_names)
self.assertIn('prod_res', res_names)
self.assertIn('test_res', res_names)
def res_assert_for_test(self, resources, fj_zone=False):
def res_assert_for_test(self, resources, fj_zone=False,
shannxi_provice=False):
res_names = [res.resource_name for res in resources]
if fj_zone:
self.assertEqual(3, len(resources))
self.assertEqual(4, len(resources))
self.assertIn('fujian_res', res_names)
else:
self.assertEqual(2, len(resources))
self.assertIn('not_shannxi_res', res_names)
elif shannxi_provice:
self.assertEqual(3, len(resources))
self.assertNotIn('fujian_res', res_names)
self.assertIn('shannxi_res', res_names)
else:
self.assertEqual(3, len(resources))
self.assertIn('not_shannxi_res', res_names)
self.assertIn('test_res', res_names)
self.assertIn('test_res1', res_names)
self.assertNotIn('prod_res', res_names)
@ -256,6 +335,15 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=cfn_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'prod'}
self.update_stack(stack_identifier,
template=cfn_template,
@ -285,6 +373,17 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'prod',
'zone': 'xianyang'}
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=False,
shannxi_provice=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,
@ -303,6 +402,28 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_prod(resources, bj_prod=False, fj_zone=True)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'zone': 'xianyang',
'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=False,
shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'zone': 'shanghai',
'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=False,
shannxi_provice=False)
self.output_assert_for_prod(stack_identifier, bj_prod=False)
parms = {'env_type': 'test'}
self.update_stack(stack_identifier,
template=cfn_template,
@ -322,12 +443,32 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_test(resources, fj_zone=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'xianyang'}
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=False,
shannxi_provice=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)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, shannxi_provice=True)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'prod'}
self.update_stack(stack_identifier,
template=hot_template,
@ -347,6 +488,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': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False, shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, False)
def test_stack_create_update_hot_template_prod_to_test(self):
parms = {'env_type': 'prod'}
stack_identifier = self.stack_create(template=hot_template,
@ -355,6 +506,16 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
self.res_assert_for_prod(resources)
self.output_assert_for_prod(stack_identifier)
parms = {'env_type': 'prod',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_prod(resources, False, shannxi_provice=True)
self.output_assert_for_prod(stack_identifier, False)
parms = {'env_type': 'test'}
self.update_stack(stack_identifier,
template=hot_template,
@ -363,3 +524,14 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources)
self.output_assert_for_test(stack_identifier)
parms = {'env_type': 'test',
'zone': 'xianyang'}
self.update_stack(stack_identifier,
template=hot_template,
parameters=parms)
resources = self.client.resources.list(stack_identifier)
self.res_assert_for_test(resources, fj_zone=False,
shannxi_provice=True)
self.output_assert_for_test(stack_identifier)