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>:
|
||||
description: <description>
|
||||
value: <parameter value>
|
||||
condition: <condition name>
|
||||
|
||||
parameter name
|
||||
The output parameter name, which must be unique within the ``outputs``
|
||||
@ -781,6 +782,13 @@ parameter value
|
||||
the functions.
|
||||
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
|
||||
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,
|
||||
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:
|
||||
|
||||
|
@ -177,6 +177,10 @@ class CfnTemplate(CfnTemplateBase):
|
||||
HOT_TO_CFN_RES_ATTRS.update({'condition': RES_CONDITION})
|
||||
|
||||
extra_rsrc_defn = CfnTemplateBase.extra_rsrc_defn + (RES_CONDITION,)
|
||||
|
||||
OUTPUT_CONDITION = CONDITION
|
||||
OUTPUT_KEYS = CfnTemplateBase.OUTPUT_KEYS + (OUTPUT_CONDITION,)
|
||||
|
||||
condition_functions = {
|
||||
'Fn::Equals': hot_funcs.Equals,
|
||||
'Ref': cfn_funcs.ParamRef,
|
||||
|
@ -410,6 +410,9 @@ class HOTemplate20161014(HOTemplate20160408):
|
||||
extra_rsrc_defn = HOTemplate20160408.extra_rsrc_defn + (
|
||||
RES_EXTERNAL_ID, RES_CONDITION,)
|
||||
|
||||
OUTPUT_CONDITION = CONDITION
|
||||
OUTPUT_KEYS = HOTemplate20160408.OUTPUT_KEYS + (OUTPUT_CONDITION,)
|
||||
|
||||
deletion_policies = {
|
||||
'Delete': rsrc_defn.ResourceDefinition.DELETE,
|
||||
'Retain': rsrc_defn.ResourceDefinition.RETAIN,
|
||||
|
@ -1314,12 +1314,12 @@ class EngineService(service.Service):
|
||||
if output_key not in outputs:
|
||||
raise exception.NotFound(_('Specified output key %s not '
|
||||
'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:
|
||||
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):
|
||||
timeout = cfg.CONF.engine_life_check_timeout
|
||||
|
@ -228,7 +228,7 @@ class Stack(collections.Mapping):
|
||||
self._set_param_stackid()
|
||||
|
||||
if resolve_data:
|
||||
self.outputs = self.resolve_static_data(
|
||||
self.outputs = self.resolve_outputs_data(
|
||||
self.t[self.t.OUTPUTS], path=self.t.OUTPUTS)
|
||||
else:
|
||||
self.outputs = {}
|
||||
@ -1503,7 +1503,7 @@ class Stack(collections.Mapping):
|
||||
previous_template_id = self.t.id
|
||||
self.t = newstack.t
|
||||
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)
|
||||
finally:
|
||||
if should_rollback:
|
||||
@ -1970,8 +1970,17 @@ class Stack(collections.Mapping):
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
# nothing is cached if no resources exist
|
||||
if not self._resources:
|
||||
|
@ -135,6 +135,10 @@ class Template(collections.Mapping):
|
||||
self.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
|
||||
def load(cls, context, template_id, t=None):
|
||||
"""Retrieve a Template with the given ID from the database."""
|
||||
|
@ -12,6 +12,7 @@
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import copy
|
||||
|
||||
import six
|
||||
|
||||
@ -118,6 +119,11 @@ class CommonTemplate(template.Template):
|
||||
|
||||
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=''):
|
||||
# if specify condition return the resolved condition value,
|
||||
# true or false if don't specify condition, return true
|
||||
@ -137,3 +143,14 @@ class CommonTemplate(template.Template):
|
||||
self._conditions = self.resolve_conditions(stack)
|
||||
|
||||
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(),
|
||||
tmpl, resolve_data=False)
|
||||
expected_exception = self.assertRaises(AssertionError,
|
||||
stc.resolve_static_data,
|
||||
stc.resolve_outputs_data,
|
||||
None)
|
||||
self.assertEqual(expected_message, six.text_type(expected_exception))
|
||||
|
||||
|
@ -299,6 +299,12 @@ class TestTemplateConditionParser(common.HeatTestCase):
|
||||
'type': 'GenericResourceType',
|
||||
'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)',
|
||||
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):
|
||||
|
||||
|
@ -32,6 +32,10 @@ Resources:
|
||||
Properties:
|
||||
value: prod_res
|
||||
Condition: Prod
|
||||
Outputs:
|
||||
res_value:
|
||||
Value: {"Fn::GetAtt": [prod_res, output]}
|
||||
Condition: Prod
|
||||
'''
|
||||
|
||||
hot_template = '''
|
||||
@ -54,6 +58,10 @@ resources:
|
||||
properties:
|
||||
value: prod_res
|
||||
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.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):
|
||||
stack_identifier = self.stack_create(template=cfn_template)
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_test(resources)
|
||||
self.output_assert_for_test(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'prod'}
|
||||
self.update_stack(stack_identifier,
|
||||
@ -86,6 +105,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
def test_stack_create_update_cfn_template_prod_to_test(self):
|
||||
parms = {'env_type': 'prod'}
|
||||
@ -93,6 +113,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
||||
parameters=parms)
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'test'}
|
||||
self.update_stack(stack_identifier,
|
||||
@ -101,11 +122,13 @@ 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)
|
||||
|
||||
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 = {'env_type': 'prod'}
|
||||
self.update_stack(stack_identifier,
|
||||
@ -114,6 +137,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
||||
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
def test_stack_create_update_hot_template_prod_to_test(self):
|
||||
parms = {'env_type': 'prod'}
|
||||
@ -121,6 +145,7 @@ class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
|
||||
parameters=parms)
|
||||
resources = self.client.resources.list(stack_identifier)
|
||||
self.res_assert_for_prod(resources)
|
||||
self.output_assert_for_prod(stack_identifier)
|
||||
|
||||
parms = {'env_type': 'test'}
|
||||
self.update_stack(stack_identifier,
|
||||
@ -129,3 +154,4 @@ 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)
|
||||
|
Loading…
Reference in New Issue
Block a user