Merge "Allows condition name using boolean or function"

This commit is contained in:
Jenkins 2016-09-15 05:11:02 +00:00 committed by Gerrit Code Review
commit 9df7ee3a09
9 changed files with 102 additions and 25 deletions

View File

@ -644,7 +644,7 @@ the following syntax
update_policy: <update policy> update_policy: <update policy>
deletion_policy: <deletion policy> deletion_policy: <deletion policy>
external_id: <external resource ID> external_id: <external resource ID>
condition: <condition name> condition: <condition name or expression or boolean>
resource ID resource ID
A resource ID which must be unique within the ``resources`` section of the A resource ID which must be unique within the ``resources`` section of the
@ -775,7 +775,7 @@ according to the following syntax
<parameter name>: <parameter name>:
description: <description> description: <description>
value: <parameter value> value: <parameter value>
condition: <condition name> condition: <condition name or expression or boolean>
parameter name parameter name
The output parameter name, which must be unique within the ``outputs`` 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools
import six import six
from heat.common import exception 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 parameters from heat.engine import parameters
from heat.engine import rsrc_defn from heat.engine import rsrc_defn
from heat.engine import template_common from heat.engine import template_common
@ -121,7 +124,7 @@ class CfnTemplateBase(template_common.CommonTemplate):
raise exception.StackValidationFailed(message=msg) raise exception.StackValidationFailed(message=msg)
defn = rsrc_defn.ResourceDefinition(name, **defn_data) defn = rsrc_defn.ResourceDefinition(name, **defn_data)
cond_name = defn.condition_name() cond_name = defn.condition()
if cond_name is not None: if cond_name is not None:
try: try:
@ -205,14 +208,14 @@ class CfnTemplate(CfnTemplateBase):
for arg in super(CfnTemplate, self)._rsrc_defn_args(stack, name, data): for arg in super(CfnTemplate, self)._rsrc_defn_args(stack, name, data):
yield arg yield arg
def no_parse(field, path): parse_cond = functools.partial(self.parse_condition, stack)
return field
yield ('condition', yield ('condition',
self._parse_resource_field(self.RES_CONDITION, self._parse_resource_field(self.RES_CONDITION,
(six.string_types, bool), (six.string_types, bool,
function.Function),
'string or boolean', 'string or boolean',
name, data, no_parse)) name, data, parse_cond))
class HeatTemplate(CfnTemplateBase): class HeatTemplate(CfnTemplateBase):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1207,6 +1207,35 @@ class HOTemplateTest(common.HeatTestCase):
resolved = self.resolve(snippet, tmpl, stack) resolved = self.resolve(snippet, tmpl, stack)
self.assertEqual('value_if_false', resolved) 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): def test_if_invalid_args(self):
snippet = {'if': ['create_prod', 'one_value']} snippet = {'if': ['create_prod', 'one_value']}
tmpl = template.Template(hot_newton_tpl_empty) tmpl = template.Template(hot_newton_tpl_empty)

View File

@ -297,7 +297,7 @@ class TestTemplateConditionParser(common.HeatTestCase):
'outputs': { 'outputs': {
'foo': { 'foo': {
'condition': 'prod_env', '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) ex = self.assertRaises(ValueError, conds.is_enabled, 111)
self.assertIn('Invalid condition "111"', six.text_type(ex)) 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): def test_parse_output_condition_invalid(self):
stk = stack.Stack(self.ctx, stk = stack.Stack(self.ctx,
'test_output_invalid_condition', 'test_output_invalid_condition',
@ -441,6 +450,31 @@ class TestTemplateConditionParser(common.HeatTestCase):
self.assertIn('Circular definition for condition "first_cond"', self.assertIn('Circular definition for condition "first_cond"',
six.text_type(ex)) 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): class TestTemplateValidate(common.HeatTestCase):