Support condition for output
Provides output condition for cfn/hot templates, if the condition of output evaluates to false, will set output value to None. Change-Id: I0398e39541a4176ef5699331c10536c59f1cb3e7 Blueprint: support-conditions-function
This commit is contained in:
parent
74506411d5
commit
4552e08767
@ -766,6 +766,7 @@ according to the following syntax
|
|||||||
<parameter name>:
|
<parameter name>:
|
||||||
description: <description>
|
description: <description>
|
||||||
value: <parameter value>
|
value: <parameter value>
|
||||||
|
condition: <condition name>
|
||||||
|
|
||||||
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``
|
||||||
@ -781,6 +782,13 @@ parameter value
|
|||||||
the functions.
|
the functions.
|
||||||
This attribute is required.
|
This attribute is required.
|
||||||
|
|
||||||
|
condition
|
||||||
|
To conditionally define an output value. None value will be shown if the
|
||||||
|
condition is False.
|
||||||
|
This attribute is optional.
|
||||||
|
|
||||||
|
Note: Support ``condition`` for output is added in the Newton version.
|
||||||
|
|
||||||
The example below shows how the IP address of a compute resource can
|
The example below shows how the IP address of a compute resource can
|
||||||
be defined as an output parameter
|
be defined as an output parameter
|
||||||
|
|
||||||
@ -855,6 +863,20 @@ parameter is equal to 'prod'. In the above sample template, the 'volume'
|
|||||||
resource is associated with the 'create_prod_res' condition. Therefore,
|
resource is associated with the 'create_prod_res' condition. Therefore,
|
||||||
the 'volume' resource is created only if the 'env_type' is equal to 'prod'.
|
the 'volume' resource is created only if the 'env_type' is equal to 'prod'.
|
||||||
|
|
||||||
|
The example below shows how to conditionally define an output
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
vol_size:
|
||||||
|
value: {get_attr: [my_volume, size]}
|
||||||
|
condition: create_prod_res
|
||||||
|
|
||||||
|
In the above sample template, the 'vol_size' output is associated with
|
||||||
|
the 'create_prod_res' condition. Therefore, the 'vol_size' output is
|
||||||
|
given corresponding value only if the 'env_type' is equal to 'prod',
|
||||||
|
otherwise the value of the output is None.
|
||||||
|
|
||||||
|
|
||||||
.. _hot_spec_intrinsic_functions:
|
.. _hot_spec_intrinsic_functions:
|
||||||
|
|
||||||
|
@ -177,6 +177,10 @@ class CfnTemplate(CfnTemplateBase):
|
|||||||
HOT_TO_CFN_RES_ATTRS.update({'condition': RES_CONDITION})
|
HOT_TO_CFN_RES_ATTRS.update({'condition': RES_CONDITION})
|
||||||
|
|
||||||
extra_rsrc_defn = CfnTemplateBase.extra_rsrc_defn + (RES_CONDITION,)
|
extra_rsrc_defn = CfnTemplateBase.extra_rsrc_defn + (RES_CONDITION,)
|
||||||
|
|
||||||
|
OUTPUT_CONDITION = CONDITION
|
||||||
|
OUTPUT_KEYS = CfnTemplateBase.OUTPUT_KEYS + (OUTPUT_CONDITION,)
|
||||||
|
|
||||||
condition_functions = {
|
condition_functions = {
|
||||||
'Fn::Equals': hot_funcs.Equals,
|
'Fn::Equals': hot_funcs.Equals,
|
||||||
'Ref': cfn_funcs.ParamRef,
|
'Ref': cfn_funcs.ParamRef,
|
||||||
|
@ -410,6 +410,9 @@ class HOTemplate20161014(HOTemplate20160408):
|
|||||||
extra_rsrc_defn = HOTemplate20160408.extra_rsrc_defn + (
|
extra_rsrc_defn = HOTemplate20160408.extra_rsrc_defn + (
|
||||||
RES_EXTERNAL_ID, RES_CONDITION,)
|
RES_EXTERNAL_ID, RES_CONDITION,)
|
||||||
|
|
||||||
|
OUTPUT_CONDITION = CONDITION
|
||||||
|
OUTPUT_KEYS = HOTemplate20160408.OUTPUT_KEYS + (OUTPUT_CONDITION,)
|
||||||
|
|
||||||
deletion_policies = {
|
deletion_policies = {
|
||||||
'Delete': rsrc_defn.ResourceDefinition.DELETE,
|
'Delete': rsrc_defn.ResourceDefinition.DELETE,
|
||||||
'Retain': rsrc_defn.ResourceDefinition.RETAIN,
|
'Retain': rsrc_defn.ResourceDefinition.RETAIN,
|
||||||
|
@ -1314,12 +1314,12 @@ class EngineService(service.Service):
|
|||||||
if output_key not in outputs:
|
if output_key not in outputs:
|
||||||
raise exception.NotFound(_('Specified output key %s not '
|
raise exception.NotFound(_('Specified output key %s not '
|
||||||
'found.') % output_key)
|
'found.') % output_key)
|
||||||
output = stack.resolve_static_data(outputs[output_key])
|
output = stack.resolve_outputs_data({output_key: outputs[output_key]})
|
||||||
|
|
||||||
if not stack.outputs:
|
if not stack.outputs:
|
||||||
stack.outputs.update({output_key: output})
|
stack.outputs.update(output)
|
||||||
|
|
||||||
return api.format_stack_output(stack, {output_key: output}, output_key)
|
return api.format_stack_output(stack, output, output_key)
|
||||||
|
|
||||||
def _remote_call(self, cnxt, lock_engine_id, call, **kwargs):
|
def _remote_call(self, cnxt, lock_engine_id, call, **kwargs):
|
||||||
timeout = cfg.CONF.engine_life_check_timeout
|
timeout = cfg.CONF.engine_life_check_timeout
|
||||||
|
@ -228,7 +228,7 @@ class Stack(collections.Mapping):
|
|||||||
self._set_param_stackid()
|
self._set_param_stackid()
|
||||||
|
|
||||||
if resolve_data:
|
if resolve_data:
|
||||||
self.outputs = self.resolve_static_data(
|
self.outputs = self.resolve_outputs_data(
|
||||||
self.t[self.t.OUTPUTS], path=self.t.OUTPUTS)
|
self.t[self.t.OUTPUTS], path=self.t.OUTPUTS)
|
||||||
else:
|
else:
|
||||||
self.outputs = {}
|
self.outputs = {}
|
||||||
@ -1503,7 +1503,7 @@ class Stack(collections.Mapping):
|
|||||||
previous_template_id = self.t.id
|
previous_template_id = self.t.id
|
||||||
self.t = newstack.t
|
self.t = newstack.t
|
||||||
template_outputs = self.t[self.t.OUTPUTS]
|
template_outputs = self.t[self.t.OUTPUTS]
|
||||||
self.outputs = self.resolve_static_data(
|
self.outputs = self.resolve_outputs_data(
|
||||||
template_outputs, path=self.t.OUTPUTS)
|
template_outputs, path=self.t.OUTPUTS)
|
||||||
finally:
|
finally:
|
||||||
if should_rollback:
|
if should_rollback:
|
||||||
@ -1970,8 +1970,17 @@ class Stack(collections.Mapping):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def resolve_static_data(self, snippet, path=''):
|
def resolve_static_data(self, snippet, path=''):
|
||||||
|
warnings.warn('Stack.resolve_static_data() is deprecated and '
|
||||||
|
'will be removed in the Ocata release. Use the '
|
||||||
|
'Stack.resolve_outputs_data() instead.',
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
return self.t.parse(self, snippet, path=path)
|
return self.t.parse(self, snippet, path=path)
|
||||||
|
|
||||||
|
def resolve_outputs_data(self, outputs, path=''):
|
||||||
|
resolve_outputs = self.t.parse_outputs_conditions(outputs, self)
|
||||||
|
return self.t.parse(self, resolve_outputs, path=path)
|
||||||
|
|
||||||
def reset_resource_attributes(self):
|
def reset_resource_attributes(self):
|
||||||
# nothing is cached if no resources exist
|
# nothing is cached if no resources exist
|
||||||
if not self._resources:
|
if not self._resources:
|
||||||
|
@ -135,6 +135,10 @@ class Template(collections.Mapping):
|
|||||||
self.t[s] = {}
|
self.t[s] = {}
|
||||||
self.t[s].update(other.t[s])
|
self.t[s].update(other.t[s])
|
||||||
|
|
||||||
|
def parse_outputs_conditions(self, outputs, stack):
|
||||||
|
"""Return a dictionary of outputs data which resolved conditions."""
|
||||||
|
return outputs
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, context, template_id, t=None):
|
def load(cls, context, template_id, t=None):
|
||||||
"""Retrieve a Template with the given ID from the database."""
|
"""Retrieve a Template with the given ID from the database."""
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import copy
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@ -118,6 +119,11 @@ class CommonTemplate(template.Template):
|
|||||||
|
|
||||||
return self.get_condition(res_data, stack, path)
|
return self.get_condition(res_data, stack, path)
|
||||||
|
|
||||||
|
def get_output_condition(self, stack, o_data, o_key):
|
||||||
|
path = '.'.join([self.OUTPUTS, o_key, self.OUTPUT_CONDITION])
|
||||||
|
|
||||||
|
return self.get_condition(o_data, stack, path)
|
||||||
|
|
||||||
def get_condition(self, snippet, stack, path=''):
|
def get_condition(self, snippet, stack, path=''):
|
||||||
# if specify condition return the resolved condition value,
|
# if specify condition return the resolved condition value,
|
||||||
# true or false if don't specify condition, return true
|
# true or false if don't specify condition, return true
|
||||||
@ -137,3 +143,14 @@ class CommonTemplate(template.Template):
|
|||||||
self._conditions = self.resolve_conditions(stack)
|
self._conditions = self.resolve_conditions(stack)
|
||||||
|
|
||||||
return self._conditions
|
return self._conditions
|
||||||
|
|
||||||
|
def parse_outputs_conditions(self, outputs, stack):
|
||||||
|
copy_outputs = copy.deepcopy(outputs)
|
||||||
|
for key, snippet in six.iteritems(copy_outputs):
|
||||||
|
if self.has_condition_section(snippet):
|
||||||
|
cd = self.get_output_condition(stack, snippet, key)
|
||||||
|
snippet[self.OUTPUT_CONDITION] = cd
|
||||||
|
if not cd:
|
||||||
|
snippet[self.OUTPUT_VALUE] = None
|
||||||
|
|
||||||
|
return copy_outputs
|
||||||
|
@ -2730,7 +2730,7 @@ class StackTest(common.HeatTestCase):
|
|||||||
stc = stack.Stack(self.ctx, utils.random_name(),
|
stc = stack.Stack(self.ctx, utils.random_name(),
|
||||||
tmpl, resolve_data=False)
|
tmpl, resolve_data=False)
|
||||||
expected_exception = self.assertRaises(AssertionError,
|
expected_exception = self.assertRaises(AssertionError,
|
||||||
stc.resolve_static_data,
|
stc.resolve_outputs_data,
|
||||||
None)
|
None)
|
||||||
self.assertEqual(expected_message, six.text_type(expected_exception))
|
self.assertEqual(expected_message, six.text_type(expected_exception))
|
||||||
|
|
||||||
|
@ -299,6 +299,12 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
|||||||
'type': 'GenericResourceType',
|
'type': 'GenericResourceType',
|
||||||
'condition': 'prod_env'
|
'condition': 'prod_env'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'outputs': {
|
||||||
|
'foo': {
|
||||||
|
'condition': 'prod_env',
|
||||||
|
'value': {'get_attr': ['r1', 'foo']}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,6 +387,27 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
|||||||
self.assertIn('Invalid condition "111" (in r1.condition)',
|
self.assertIn('Invalid condition "111" (in r1.condition)',
|
||||||
six.text_type(ex))
|
six.text_type(ex))
|
||||||
|
|
||||||
|
def test_parse_output_condition_invalid(self):
|
||||||
|
stk = stack.Stack(self.ctx,
|
||||||
|
'test_output_invalid_condition',
|
||||||
|
self.tmpl)
|
||||||
|
|
||||||
|
# test condition name is invalid
|
||||||
|
stk.outputs['foo']['condition'] = 'invalid_cd'
|
||||||
|
ex = self.assertRaises(exception.InvalidConditionReference,
|
||||||
|
self.tmpl.parse_outputs_conditions,
|
||||||
|
stk.outputs, stk)
|
||||||
|
self.assertIn('Invalid condition "invalid_cd" '
|
||||||
|
'(in outputs.foo.condition)',
|
||||||
|
six.text_type(ex))
|
||||||
|
# test condition name is not string
|
||||||
|
stk.outputs['foo']['condition'] = 222
|
||||||
|
ex = self.assertRaises(exception.InvalidConditionReference,
|
||||||
|
self.tmpl.parse_outputs_conditions,
|
||||||
|
stk.outputs, stk)
|
||||||
|
self.assertIn('Invalid condition "222" (in outputs.foo.condition)',
|
||||||
|
six.text_type(ex))
|
||||||
|
|
||||||
|
|
||||||
class TestTemplateValidate(common.HeatTestCase):
|
class TestTemplateValidate(common.HeatTestCase):
|
||||||
|
|
||||||
|
@ -32,6 +32,10 @@ Resources:
|
|||||||
Properties:
|
Properties:
|
||||||
value: prod_res
|
value: prod_res
|
||||||
Condition: Prod
|
Condition: Prod
|
||||||
|
Outputs:
|
||||||
|
res_value:
|
||||||
|
Value: {"Fn::GetAtt": [prod_res, output]}
|
||||||
|
Condition: Prod
|
||||||
'''
|
'''
|
||||||
|
|
||||||
hot_template = '''
|
hot_template = '''
|
||||||
@ -54,6 +58,10 @@ resources:
|
|||||||
properties:
|
properties:
|
||||||
value: prod_res
|
value: prod_res
|
||||||
condition: prod
|
condition: prod
|
||||||
|
outputs:
|
||||||
|
res_value:
|
||||||
|
value: {get_attr: [prod_res, output]}
|
||||||
|
condition: prod
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@ -74,10 +82,21 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
self.assertIn('test_res', res_names)
|
self.assertIn('test_res', res_names)
|
||||||
self.assertNotIn('prod_res', res_names)
|
self.assertNotIn('prod_res', res_names)
|
||||||
|
|
||||||
|
def output_assert_for_prod(self, stack_id):
|
||||||
|
output = self.client.stacks.output_show(stack_id,
|
||||||
|
'res_value')['output']
|
||||||
|
self.assertEqual('prod_res', output['output_value'])
|
||||||
|
|
||||||
|
def output_assert_for_test(self, stack_id):
|
||||||
|
output = self.client.stacks.output_show(stack_id,
|
||||||
|
'res_value')['output']
|
||||||
|
self.assertIsNone(output['output_value'])
|
||||||
|
|
||||||
def test_stack_create_update_cfn_template_test_to_prod(self):
|
def test_stack_create_update_cfn_template_test_to_prod(self):
|
||||||
stack_identifier = self.stack_create(template=cfn_template)
|
stack_identifier = self.stack_create(template=cfn_template)
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_test(resources)
|
self.res_assert_for_test(resources)
|
||||||
|
self.output_assert_for_test(stack_identifier)
|
||||||
|
|
||||||
parms = {'env_type': 'prod'}
|
parms = {'env_type': 'prod'}
|
||||||
self.update_stack(stack_identifier,
|
self.update_stack(stack_identifier,
|
||||||
@ -86,6 +105,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
|
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_prod(resources)
|
self.res_assert_for_prod(resources)
|
||||||
|
self.output_assert_for_prod(stack_identifier)
|
||||||
|
|
||||||
def test_stack_create_update_cfn_template_prod_to_test(self):
|
def test_stack_create_update_cfn_template_prod_to_test(self):
|
||||||
parms = {'env_type': 'prod'}
|
parms = {'env_type': 'prod'}
|
||||||
@ -93,6 +113,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
parameters=parms)
|
parameters=parms)
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_prod(resources)
|
self.res_assert_for_prod(resources)
|
||||||
|
self.output_assert_for_prod(stack_identifier)
|
||||||
|
|
||||||
parms = {'env_type': 'test'}
|
parms = {'env_type': 'test'}
|
||||||
self.update_stack(stack_identifier,
|
self.update_stack(stack_identifier,
|
||||||
@ -101,11 +122,13 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
|
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_test(resources)
|
self.res_assert_for_test(resources)
|
||||||
|
self.output_assert_for_test(stack_identifier)
|
||||||
|
|
||||||
def test_stack_create_update_hot_template_test_to_prod(self):
|
def test_stack_create_update_hot_template_test_to_prod(self):
|
||||||
stack_identifier = self.stack_create(template=hot_template)
|
stack_identifier = self.stack_create(template=hot_template)
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_test(resources)
|
self.res_assert_for_test(resources)
|
||||||
|
self.output_assert_for_test(stack_identifier)
|
||||||
|
|
||||||
parms = {'env_type': 'prod'}
|
parms = {'env_type': 'prod'}
|
||||||
self.update_stack(stack_identifier,
|
self.update_stack(stack_identifier,
|
||||||
@ -114,6 +137,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
|
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_prod(resources)
|
self.res_assert_for_prod(resources)
|
||||||
|
self.output_assert_for_prod(stack_identifier)
|
||||||
|
|
||||||
def test_stack_create_update_hot_template_prod_to_test(self):
|
def test_stack_create_update_hot_template_prod_to_test(self):
|
||||||
parms = {'env_type': 'prod'}
|
parms = {'env_type': 'prod'}
|
||||||
@ -121,6 +145,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
parameters=parms)
|
parameters=parms)
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_prod(resources)
|
self.res_assert_for_prod(resources)
|
||||||
|
self.output_assert_for_prod(stack_identifier)
|
||||||
|
|
||||||
parms = {'env_type': 'test'}
|
parms = {'env_type': 'test'}
|
||||||
self.update_stack(stack_identifier,
|
self.update_stack(stack_identifier,
|
||||||
@ -129,3 +154,4 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
|||||||
|
|
||||||
resources = self.client.resources.list(stack_identifier)
|
resources = self.client.resources.list(stack_identifier)
|
||||||
self.res_assert_for_test(resources)
|
self.res_assert_for_test(resources)
|
||||||
|
self.output_assert_for_test(stack_identifier)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user