Allows condition name using boolean or function

This change supports:
1. Allow boolean value or condition function as
   condition name of 'if' function:
   resources:
     r1:
        ...
	properties:
	  a: {if: [true, 'value_true', 'value_false']}
     r2:
       ...
       properties:
         b: {if: [{equals: [...]}, 'value_true', 'value_false']}
2. Allow boolean value or condition function as
   condtiion name in resource definition:
   resources:
     r1:
       ...
       condition: false
     r2:
       ...
       condition: {and: [cd1, cd2]}
3. Allow boolean value or condition function as
   condition name in outputs:
   outputs:
     output1:
       value: ...
       condition: true
     output2:
       value: ...
       condition: {not: cd3}

Change-Id: I2bf9bb0b608788c928d12425cbfdaf658df9e880
Co-Authored-By: Zane Bitter <zbitter@redhat.com>
Blueprint: support-conditions-function
This commit is contained in:
huangtianhua 2016-09-09 17:08:00 -04:00 committed by Zane Bitter
parent b67605de24
commit 4a92678f18
9 changed files with 102 additions and 25 deletions

View File

@ -644,7 +644,7 @@ the following syntax
update_policy: <update policy>
deletion_policy: <deletion policy>
external_id: <external resource ID>
condition: <condition name>
condition: <condition name or expression or boolean>
resource ID
A resource ID which must be unique within the ``resources`` section of the
@ -775,7 +775,7 @@ according to the following syntax
<parameter name>:
description: <description>
value: <parameter value>
condition: <condition name>
condition: <condition name or expression or boolean>
parameter name
The output parameter name, which must be unique within the ``outputs``

View File

@ -11,11 +11,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import six
from heat.common import exception
from heat.common.i18n import _
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
from heat.engine import template_common
@ -121,7 +124,7 @@ class CfnTemplateBase(template_common.CommonTemplate):
raise exception.StackValidationFailed(message=msg)
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
cond_name = defn.condition_name()
cond_name = defn.condition()
if cond_name is not None:
try:
@ -205,14 +208,14 @@ class CfnTemplate(CfnTemplateBase):
for arg in super(CfnTemplate, self)._rsrc_defn_args(stack, name, data):
yield arg
def no_parse(field, path):
return field
parse_cond = functools.partial(self.parse_condition, stack)
yield ('condition',
self._parse_resource_field(self.RES_CONDITION,
(six.string_types, bool),
(six.string_types, bool,
function.Function),
'string or boolean',
name, data, no_parse))
name, data, parse_cond))
class HeatTemplate(CfnTemplateBase):

View File

@ -53,6 +53,9 @@ class Conditions(object):
if condition_name is None:
return True
if isinstance(condition_name, bool):
return condition_name
if not (isinstance(condition_name, six.string_types) and
condition_name in self._conditions):
raise ValueError(_('Invalid condition "%s"') % condition_name)

View File

@ -1149,17 +1149,22 @@ class If(function.Macro):
not isinstance(self.args, collections.Sequence) or
isinstance(self.args, six.string_types)):
raise ValueError()
cd_name, value_if_true, value_if_false = self.args
condition, value_if_true, value_if_false = self.args
except ValueError:
msg = _('Arguments to "%s" must be of the form: '
'[condition_name, value_if_true, value_if_false]')
raise ValueError(msg % self.fn_name)
cd = self._get_condition(cd_name)
cond = self.template.parse_condition(self.stack, condition,
self.fn_name)
cd = self._get_condition(function.resolve(cond))
return parse_func(value_if_true if cd else value_if_false)
def _get_condition(self, cd_name):
return self.template.conditions(self.stack).is_enabled(cd_name)
def _get_condition(self, cond):
if isinstance(cond, bool):
return cond
return self.template.conditions(self.stack).is_enabled(cond)
class ConditionBoolean(function.Function):

View File

@ -241,7 +241,7 @@ class HOTemplate20130523(template_common.CommonTemplate):
raise exception.StackValidationFailed(message=msg)
defn = rsrc_defn.ResourceDefinition(name, **defn_data)
cond_name = defn.condition_name()
cond_name = defn.condition()
if cond_name is not None:
try:
@ -494,9 +494,7 @@ class HOTemplate20161014(HOTemplate20160408):
yield arg
parse = functools.partial(self.parse, stack)
def no_parse(field, path):
return field
parse_cond = functools.partial(self.parse_condition, stack)
yield ('external_id',
self._parse_resource_field(self.RES_EXTERNAL_ID,
@ -507,6 +505,7 @@ class HOTemplate20161014(HOTemplate20160408):
yield ('condition',
self._parse_resource_field(self.RES_CONDITION,
(six.string_types, bool),
'string or boolean',
name, data, no_parse))
(six.string_types, bool,
function.Function),
'string_or_boolean',
name, data, parse_cond))

View File

@ -135,8 +135,9 @@ class ResourceDefinitionCore(object):
self._deletion_policy = self.RETAIN
if condition is not None:
assert isinstance(condition, six.string_types)
self._hash ^= hash(condition)
assert isinstance(condition, (six.string_types, bool,
function.Function))
self._hash ^= _hash_data(condition)
def freeze(self, **overrides):
"""Return a frozen resource definition, with all functions resolved.
@ -268,12 +269,12 @@ class ResourceDefinitionCore(object):
"""Return the external resource id."""
return function.resolve(self._external_id)
def condition_name(self):
def condition(self):
"""Return the name of the conditional inclusion rule, if any.
Returns None if the resource is included unconditionally.
"""
return self._condition
return function.resolve(self._condition)
def render_hot(self):
"""Return a HOT snippet for the resource definition."""

View File

@ -179,9 +179,12 @@ class CommonTemplate(template.Template):
description = val.get(self.OUTPUT_DESCRIPTION)
if hasattr(self, 'OUTPUT_CONDITION'):
cond_name = val.get(self.OUTPUT_CONDITION)
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
cond = self.parse_condition(stack,
val.get(self.OUTPUT_CONDITION),
'.'.join(path))
try:
enabled = conds.is_enabled(cond_name)
enabled = conds.is_enabled(function.resolve(cond))
except ValueError as exc:
path = [self.OUTPUTS, key, self.OUTPUT_CONDITION]
message = six.text_type(exc)

View File

@ -1180,6 +1180,35 @@ class HOTemplateTest(common.HeatTestCase):
resolved = self.resolve(snippet, tmpl, stack)
self.assertEqual('value_if_false', resolved)
def test_if_using_boolean_condition(self):
snippet = {'if': [True, 'value_if_true', 'value_if_false']}
# when condition is true, if function resolve to value_if_true
tmpl = template.Template(hot_newton_tpl_empty)
stack = parser.Stack(utils.dummy_context(),
'test_if_using_boolean_condition', tmpl)
resolved = self.resolve(snippet, tmpl, stack)
self.assertEqual('value_if_true', resolved)
# when condition is false, if function resolve to value_if_false
snippet = {'if': [False, 'value_if_true', 'value_if_false']}
resolved = self.resolve(snippet, tmpl, stack)
self.assertEqual('value_if_false', resolved)
def test_if_using_condition_function(self):
tmpl_with_conditions = template_format.parse('''
heat_template_version: 2016-10-14
conditions:
create_prod: False
''')
snippet = {'if': [{'not': 'create_prod'},
'value_if_true', 'value_if_false']}
tmpl = template.Template(tmpl_with_conditions)
stack = parser.Stack(utils.dummy_context(),
'test_if_using_condition_function', tmpl)
resolved = self.resolve(snippet, tmpl, stack)
self.assertEqual('value_if_true', resolved)
def test_if_invalid_args(self):
snippet = {'if': ['create_prod', 'one_value']}
tmpl = template.Template(hot_newton_tpl_empty)

View File

@ -297,7 +297,7 @@ class TestTemplateConditionParser(common.HeatTestCase):
'outputs': {
'foo': {
'condition': 'prod_env',
'value': {'get_attr': ['r1', 'foo']}
'value': 'show me'
}
}
}
@ -400,6 +400,15 @@ class TestTemplateConditionParser(common.HeatTestCase):
ex = self.assertRaises(ValueError, conds.is_enabled, 111)
self.assertIn('Invalid condition "111"', six.text_type(ex))
def test_res_condition_using_boolean(self):
tmpl = copy.deepcopy(self.tmpl)
# test condition name is boolean
stk = stack.Stack(self.ctx, 'test_res_cd_boolean', tmpl)
conds = tmpl.conditions(stk)
self.assertTrue(conds.is_enabled(True))
self.assertFalse(conds.is_enabled(False))
def test_parse_output_condition_invalid(self):
stk = stack.Stack(self.ctx,
'test_output_invalid_condition',
@ -441,6 +450,31 @@ class TestTemplateConditionParser(common.HeatTestCase):
self.assertIn('Circular definition for condition "first_cond"',
six.text_type(ex))
def test_parse_output_condition_boolean(self):
t = copy.deepcopy(self.tmpl.t)
t['outputs']['foo']['condition'] = True
stk = stack.Stack(self.ctx,
'test_output_cd_boolean',
template.Template(t))
self.assertEqual('show me', stk.outputs['foo'].get_value())
t = copy.deepcopy(self.tmpl.t)
t['outputs']['foo']['condition'] = False
stk = stack.Stack(self.ctx,
'test_output_cd_boolean',
template.Template(t))
self.assertIsNone(stk.outputs['foo'].get_value())
def test_parse_output_condition_function(self):
t = copy.deepcopy(self.tmpl.t)
t['outputs']['foo']['condition'] = {'not': 'prod_env'}
stk = stack.Stack(self.ctx,
'test_output_cd_function',
template.Template(t))
self.assertEqual('show me', stk.outputs['foo'].get_value())
class TestTemplateValidate(common.HeatTestCase):