summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-08-25 00:39:02 +0000
committerGerrit Code Review <review@openstack.org>2016-08-25 00:39:02 +0000
commit74506411d5ea5c8fdd590b6d2fedad48426d074c (patch)
tree98f404ecbfee466b5b43732605fa7835d973f7e9
parentd86ea51bbddddd7ea5f0c2313a4fe42fe36bc336 (diff)
parent59476f63bc1d5da584d5d49cae44caa630a8efae (diff)
downloadheat-74506411d5ea5c8fdd590b6d2fedad48426d074c.tar.gz
Merge "Support condition for resource"
-rw-r--r--doc/source/template_guide/hot_spec.rst30
-rw-r--r--heat/common/exception.py4
-rw-r--r--heat/engine/cfn/template.py49
-rw-r--r--heat/engine/hot/template.py35
-rw-r--r--heat/engine/rsrc_defn.py16
-rw-r--r--heat/engine/stack.py3
-rw-r--r--heat/engine/template.py14
-rw-r--r--heat/engine/template_common.py33
-rw-r--r--heat/tests/test_stack_update.py56
-rw-r--r--heat/tests/test_template.py44
-rw-r--r--heat_integrationtests/functional/test_conditions.py131
11 files changed, 395 insertions, 20 deletions
diff --git a/doc/source/template_guide/hot_spec.rst b/doc/source/template_guide/hot_spec.rst
index ff9a1e243..e45a0a3a0 100644
--- a/doc/source/template_guide/hot_spec.rst
+++ b/doc/source/template_guide/hot_spec.rst
@@ -635,6 +635,7 @@ the following syntax
update_policy: <update policy>
deletion_policy: <deletion policy>
external_id: <external resource ID>
+ condition: <condition name>
resource ID
A resource ID which must be unique within the ``resources`` section of the
@@ -682,6 +683,13 @@ external_id
update that attribute. Also resource won't be deleted by heat when stack
is deleted.
+condition
+ Condition for the resource. Which decides whether to create the
+ resource or not.
+ This attribute is optional.
+
+ Note: Support ``condition`` for resource is added in the Newton version.
+
Depending on the type of resource, the resource block might include more
resource specific data.
@@ -825,6 +833,28 @@ An example of conditions section definition
cd2: {get_param: param1}
cd3: {equals: [{get_param: param2}, "yes"]}
+The example below shows how to associate condition with resources
+
+.. code-block:: yaml
+
+ parameters:
+ env_type:
+ default: test
+ type: string
+ conditions:
+ create_prod_res: {equals : [{get_param: env_type}, "prod"]
+ resources:
+ volume:
+ type: OS::Cinder::Volume
+ condition: create_prod_res
+ properties:
+ size: 1
+
+The 'create_prod_res' condition evaluates to true if the 'env_type'
+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'.
+
.. _hot_spec_intrinsic_functions:
diff --git a/heat/common/exception.py b/heat/common/exception.py
index 836f3ac26..5ef7fe0f7 100644
--- a/heat/common/exception.py
+++ b/heat/common/exception.py
@@ -138,6 +138,10 @@ class InvalidConditionDefinition(HeatException):
"invalid: %(definition)s")
+class InvalidConditionReference(HeatException):
+ msg_fmt = _('Invalid condition "%(cd)s" (in %(path)s)')
+
+
class ImmutableParameterModified(HeatException):
msg_fmt = _("The following parameters are immutable and may not be "
"updated: %(keys)s")
diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py
index ff69f672f..3ec72b35c 100644
--- a/heat/engine/cfn/template.py
+++ b/heat/engine/cfn/template.py
@@ -50,6 +50,7 @@ class CfnTemplateBase(template_common.CommonTemplate):
'DeletionPolicy', 'UpdatePolicy', 'Description',
)
+ extra_rsrc_defn = ()
functions = {
'Fn::FindInMap': cfn_funcs.FindInMap,
'Fn::GetAZs': cfn_funcs.GetAZs,
@@ -66,6 +67,13 @@ class CfnTemplateBase(template_common.CommonTemplate):
'Snapshot': rsrc_defn.ResourceDefinition.SNAPSHOT
}
+ HOT_TO_CFN_RES_ATTRS = {'type': RES_TYPE,
+ 'properties': RES_PROPERTIES,
+ 'metadata': RES_METADATA,
+ 'depends_on': RES_DEPENDS_ON,
+ 'deletion_policy': RES_DELETION_POLICY,
+ 'update_policy': RES_UPDATE_POLICY}
+
def __getitem__(self, section):
"""Get the relevant section in the template."""
if section not in self.SECTIONS:
@@ -130,25 +138,24 @@ class CfnTemplateBase(template_common.CommonTemplate):
'description': data.get(self.RES_DESCRIPTION) or ''
}
+ for key in self.extra_rsrc_defn:
+ kwargs[key.lower()] = data.get(key)
+
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
return name, defn
- return dict(rsrc_defn_item(name, data)
- for name, data in resources.items())
+ return dict(
+ rsrc_defn_item(name, data)
+ for name, data in resources.items() if self.get_res_condition(
+ stack, data, name))
def add_resource(self, definition, name=None):
if name is None:
name = definition.name
hot_tmpl = definition.render_hot()
- HOT_TO_CFN_ATTRS = {'type': self.RES_TYPE,
- 'properties': self.RES_PROPERTIES,
- 'metadata': self.RES_METADATA,
- 'depends_on': self.RES_DEPENDS_ON,
- 'deletion_policy': self.RES_DELETION_POLICY,
- 'update_policy': self.RES_UPDATE_POLICY}
-
- cfn_tmpl = dict((HOT_TO_CFN_ATTRS[k], v) for k, v in hot_tmpl.items())
+ cfn_tmpl = dict((self.HOT_TO_CFN_RES_ATTRS[k], v)
+ for k, v in hot_tmpl.items())
if len(cfn_tmpl.get(self.RES_DEPENDS_ON, [])) == 1:
cfn_tmpl[self.RES_DEPENDS_ON] = cfn_tmpl[self.RES_DEPENDS_ON][0]
@@ -160,9 +167,16 @@ class CfnTemplateBase(template_common.CommonTemplate):
class CfnTemplate(CfnTemplateBase):
+ CONDITION = 'Condition'
CONDITIONS = 'Conditions'
SECTIONS = CfnTemplateBase.SECTIONS + (CONDITIONS,)
+ RES_CONDITION = CONDITION
+ _RESOURCE_KEYS = CfnTemplateBase._RESOURCE_KEYS + (RES_CONDITION,)
+ HOT_TO_CFN_RES_ATTRS = CfnTemplateBase.HOT_TO_CFN_RES_ATTRS
+ HOT_TO_CFN_RES_ATTRS.update({'condition': RES_CONDITION})
+
+ extra_rsrc_defn = CfnTemplateBase.extra_rsrc_defn + (RES_CONDITION,)
condition_functions = {
'Fn::Equals': hot_funcs.Equals,
'Ref': cfn_funcs.ParamRef,
@@ -175,10 +189,25 @@ class CfnTemplate(CfnTemplateBase):
self._parser_condition_functions = dict(
(n, function.Invalid) for n in self.functions)
self._parser_condition_functions.update(self.condition_functions)
+ self.merge_sections = [self.PARAMETERS, self.CONDITIONS]
def get_condition_definitions(self):
return self[self.CONDITIONS]
+ def has_condition_section(self, snippet):
+ if snippet and self.CONDITION in snippet:
+ return True
+
+ return False
+
+ def validate_resource_definition(self, name, data):
+ super(CfnTemplate, self).validate_resource_definition(name, data)
+
+ self.validate_resource_key_type(
+ self.RES_CONDITION,
+ (six.string_types, bool),
+ 'string or boolean', self._RESOURCE_KEYS, name, data)
+
class HeatTemplate(CfnTemplateBase):
functions = {
diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py
index 2ff931efe..a028511eb 100644
--- a/heat/engine/hot/template.py
+++ b/heat/engine/hot/template.py
@@ -220,9 +220,15 @@ class HOTemplate20130523(template_common.CommonTemplate):
def resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
- parsed_resources = self.parse(stack, resources)
- return dict((name, self.rsrc_defn_from_snippet(name, data))
- for name, data in parsed_resources.items())
+
+ def rsrc_defn_from_snippet(name, snippet):
+ data = self.parse(stack, snippet)
+ return self.rsrc_defn_from_snippet(name, data)
+
+ return dict(
+ (name, rsrc_defn_from_snippet(name, data))
+ for name, data in resources.items() if self.get_res_condition(
+ stack, data, name))
@classmethod
def rsrc_defn_from_snippet(cls, name, data):
@@ -380,6 +386,9 @@ class HOTemplate20160408(HOTemplate20151015):
class HOTemplate20161014(HOTemplate20160408):
+
+ CONDITION = 'condition'
+ RES_CONDITION = CONDITION
CONDITIONS = 'conditions'
SECTIONS = HOTemplate20160408.SECTIONS + (CONDITIONS,)
@@ -387,12 +396,19 @@ class HOTemplate20161014(HOTemplate20160408):
_CFN_TO_HOT_SECTIONS = HOTemplate20160408._CFN_TO_HOT_SECTIONS
_CFN_TO_HOT_SECTIONS.update({
cfn_template.CfnTemplate.CONDITIONS: CONDITIONS})
+
_RESOURCE_KEYS = HOTemplate20160408._RESOURCE_KEYS
_EXT_KEY = (RES_EXTERNAL_ID,) = ('external_id',)
_RESOURCE_KEYS += _EXT_KEY
+ _RESOURCE_KEYS += (RES_CONDITION,)
+
_RESOURCE_HOT_TO_CFN_ATTRS = HOTemplate20160408._RESOURCE_HOT_TO_CFN_ATTRS
_RESOURCE_HOT_TO_CFN_ATTRS.update({RES_EXTERNAL_ID: None})
- extra_rsrc_defn = HOTemplate20160408.extra_rsrc_defn + (RES_EXTERNAL_ID,)
+ _RESOURCE_HOT_TO_CFN_ATTRS.update(
+ {CONDITION: cfn_template.CfnTemplate.CONDITION})
+
+ extra_rsrc_defn = HOTemplate20160408.extra_rsrc_defn + (
+ RES_EXTERNAL_ID, RES_CONDITION,)
deletion_policies = {
'Delete': rsrc_defn.ResourceDefinition.DELETE,
@@ -458,6 +474,7 @@ class HOTemplate20161014(HOTemplate20160408):
else:
self._parser_condition_functions[n] = f
self._parser_condition_functions.update(self.condition_functions)
+ self.merge_sections = [self.PARAMETERS, self.CONDITIONS]
def get_condition_definitions(self):
return self[self.CONDITIONS]
@@ -470,3 +487,13 @@ class HOTemplate20161014(HOTemplate20160408):
self.RES_EXTERNAL_ID,
(six.string_types, function.Function),
'string', self._RESOURCE_KEYS, name, data)
+ self.validate_resource_key_type(
+ self.RES_CONDITION,
+ (six.string_types, bool),
+ 'string or boolean', self._RESOURCE_KEYS, name, data)
+
+ def has_condition_section(self, snippet):
+ if snippet and self.CONDITION in snippet:
+ return True
+
+ return False
diff --git a/heat/engine/rsrc_defn.py b/heat/engine/rsrc_defn.py
index 4e304e190..9b0d9f5cd 100644
--- a/heat/engine/rsrc_defn.py
+++ b/heat/engine/rsrc_defn.py
@@ -70,7 +70,7 @@ class ResourceDefinitionCore(object):
def __init__(self, name, resource_type, properties=None, metadata=None,
depends=None, deletion_policy=None, update_policy=None,
- description=None, external_id=None):
+ description=None, external_id=None, condition=None):
"""Initialise with the parsed definition of a resource.
Any intrinsic functions present in any of the sections should have been
@@ -85,6 +85,7 @@ class ResourceDefinitionCore(object):
:param update_policy: A dictionary of supplied update policies
:param description: A string describing the resource
:param external_id: A uuid of an external resource
+ :param condition: A condition name associated with the resource
"""
self.name = name
self.resource_type = resource_type
@@ -95,6 +96,7 @@ class ResourceDefinitionCore(object):
self._deletion_policy = deletion_policy
self._update_policy = update_policy
self._external_id = external_id
+ self._condition = condition
self._hash = hash(self.resource_type)
self._rendering = None
@@ -132,6 +134,10 @@ class ResourceDefinitionCore(object):
self._hash ^= _hash_data(external_id)
self._deletion_policy = self.RETAIN
+ if condition is not None:
+ assert isinstance(condition, six.string_types)
+ self._hash ^= hash(condition)
+
def freeze(self, **overrides):
"""Return a frozen resource definition, with all functions resolved.
@@ -155,7 +161,7 @@ class ResourceDefinitionCore(object):
args = ('name', 'resource_type', '_properties', '_metadata',
'_depends', '_deletion_policy', '_update_policy',
- 'description', '_external_id')
+ 'description', '_external_id', '_condition')
defn = type(self)(**dict(arg_item(a) for a in args))
defn._frozen = True
@@ -180,7 +186,8 @@ class ResourceDefinitionCore(object):
depends=reparse_snippet(self._depends),
deletion_policy=reparse_snippet(self._deletion_policy),
update_policy=reparse_snippet(self._update_policy),
- external_id=reparse_snippet(self._external_id))
+ external_id=reparse_snippet(self._external_id),
+ condition=self._condition)
def dep_attrs(self, resource_name):
"""Iterate over attributes of a given resource that this references.
@@ -272,6 +279,7 @@ class ResourceDefinitionCore(object):
'update_policy': '_update_policy',
'depends_on': '_depends',
'external_id': '_external_id',
+ 'condition': '_condition'
}
def rawattrs():
@@ -336,7 +344,7 @@ class ResourceDefinitionCore(object):
return '='.join([arg_name, repr(getattr(self, '_%s' % arg_name))])
args = ('properties', 'metadata', 'depends',
- 'deletion_policy', 'update_policy')
+ 'deletion_policy', 'update_policy', 'condition')
data = {
'classname': type(self).__name__,
'name': repr(self.name),
diff --git a/heat/engine/stack.py b/heat/engine/stack.py
index b8a074a02..8c6085b08 100644
--- a/heat/engine/stack.py
+++ b/heat/engine/stack.py
@@ -1456,6 +1456,7 @@ class Stack(collections.Mapping):
updater = scheduler.TaskRunner(update_task)
self.parameters = newstack.parameters
+ self.t._conditions = newstack.t.conditions(newstack)
self.t.files = newstack.t.files
self.t.env = newstack.t.env
self.disable_rollback = newstack.disable_rollback
@@ -1519,8 +1520,10 @@ class Stack(collections.Mapping):
# and new stack resources, we should have user params of both.
existing_params.load(newstack.t.env.user_env_as_dict())
self.t.env = existing_params
+ self.t.merge_snippets(newstack.t)
self.t.store(self.context)
backup_stack.t.env = existing_params
+ backup_stack.t.merge_snippets(newstack.t)
backup_stack.t.store(self.context)
self.store()
diff --git a/heat/engine/template.py b/heat/engine/template.py
index 663693d8c..b245e7e71 100644
--- a/heat/engine/template.py
+++ b/heat/engine/template.py
@@ -117,6 +117,8 @@ class Template(collections.Mapping):
self.files = files or {}
self.maps = self[self.MAPPINGS]
self.env = env or environment.Environment({})
+ self._conditions = None
+ self.merge_sections = [self.PARAMETERS]
self.version = get_version(self.t, _template_classes.keys())
self.t_digest = None
@@ -125,6 +127,14 @@ class Template(collections.Mapping):
return Template(copy.deepcopy(self.t, memo), files=self.files,
env=self.env)
+ def merge_snippets(self, other):
+ for s in self.merge_sections:
+ if s not in other.t:
+ continue
+ if s not in self.t:
+ self.t[s] = {}
+ self.t[s].update(other.t[s])
+
@classmethod
def load(cls, context, template_id, t=None):
"""Retrieve a Template with the given ID from the database."""
@@ -216,6 +226,10 @@ class Template(collections.Mapping):
"""Check conditions section."""
pass
+ def conditions(self, stack):
+ """Return a dictionary of resolved conditions."""
+ return {}
+
@abc.abstractmethod
def resource_definitions(self, stack):
"""Return a dictionary of ResourceDefinition objects."""
diff --git a/heat/engine/template_common.py b/heat/engine/template_common.py
index 9d8ac9295..add12346b 100644
--- a/heat/engine/template_common.py
+++ b/heat/engine/template_common.py
@@ -103,4 +103,37 @@ class CommonTemplate(template.Template):
return result
def get_condition_definitions(self):
+ """Return the condition definitions of template."""
return {}
+
+ def has_condition_section(self, snippet):
+ return False
+
+ def get_res_condition(self, stack, res_data, res_name):
+ """Return the value of condition referenced by resource."""
+
+ path = ''
+ if self.has_condition_section(res_data):
+ path = '.'.join([res_name, self.RES_CONDITION])
+
+ return self.get_condition(res_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
+ if self.has_condition_section(snippet):
+ cd_key = snippet[self.CONDITION]
+ cds = self.conditions(stack)
+ if cd_key not in cds:
+ raise exception.InvalidConditionReference(
+ cd=cd_key, path=path)
+ cd = cds[cd_key]
+ return cd
+
+ return True
+
+ def conditions(self, stack):
+ if self._conditions is None:
+ self._conditions = self.resolve_conditions(stack)
+
+ return self._conditions
diff --git a/heat/tests/test_stack_update.py b/heat/tests/test_stack_update.py
index f414ec773..d78aeb207 100644
--- a/heat/tests/test_stack_update.py
+++ b/heat/tests/test_stack_update.py
@@ -1979,3 +1979,59 @@ class StackUpdateTest(common.HeatTestCase):
# Bres in backup stack should have empty data.
self.assertEqual({}, backup['Bres'].data())
self.assertEqual({'test': '42'}, self.stack['Bres'].data())
+
+ def test_update_failed_with_new_condition(self):
+ create_a_res = {'equals': [{'get_param': 'create_a_res'}, 'yes']}
+ create_b_res = {'equals': [{'get_param': 'create_b_res'}, 'yes']}
+ tmpl = {
+ 'heat_template_version': '2016-10-14',
+ 'parameters': {
+ 'create_a_res': {
+ 'type': 'string',
+ 'default': 'yes'
+ }
+ },
+ 'conditions': {
+ 'create_a': create_a_res
+ },
+ 'resources': {
+ 'Ares': {'type': 'GenericResourceType',
+ 'condition': 'create_a'}
+ }
+ }
+
+ test_stack = stack.Stack(self.ctx,
+ 'test_update_failed_with_new_condition',
+ template.Template(tmpl))
+ test_stack.store()
+ test_stack.create()
+ self.assertEqual((stack.Stack.CREATE, stack.Stack.COMPLETE),
+ test_stack.state)
+ self.assertIn('Ares', test_stack)
+
+ update_tmpl = copy.deepcopy(tmpl)
+ update_tmpl['conditions']['create_b'] = create_b_res
+ update_tmpl['parameters']['create_b_res'] = {
+ 'type': 'string',
+ 'default': 'yes'
+ }
+ update_tmpl['resources']['Bres'] = {
+ 'type': 'OS::Heat::TestResource',
+ 'properties': {
+ 'value': 'res_b',
+ 'fail': True
+ }
+ }
+
+ stack_with_new_resource = stack.Stack(
+ self.ctx,
+ 'test_update_with_new_res_cd',
+ template.Template(update_tmpl))
+ test_stack.update(stack_with_new_resource)
+ self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
+ test_stack.state)
+ self.assertIn('Bres', test_stack)
+ self.assertEqual((resource.Resource.CREATE, resource.Resource.FAILED),
+ test_stack['Bres'].state)
+ self.assertIn('create_b', test_stack.t.t['conditions'])
+ self.assertIn('create_b_res', test_stack.t.t['parameters'])
diff --git a/heat/tests/test_template.py b/heat/tests/test_template.py
index f9fd5f7c7..9f61523b9 100644
--- a/heat/tests/test_template.py
+++ b/heat/tests/test_template.py
@@ -119,10 +119,11 @@ class TemplatePluginFixture(fixtures.Fixture):
class TestTemplatePluginManager(common.HeatTestCase):
def test_template_NEW_good(self):
class NewTemplate(template.Template):
- SECTIONS = (VERSION, MAPPINGS, CONDITIONS) = (
+ SECTIONS = (VERSION, MAPPINGS, CONDITIONS, PARAMETERS) = (
'NEWTemplateFormatVersion',
'__undefined__',
- 'conditions')
+ 'conditions',
+ 'parameters')
RESOURCES = 'thingies'
def param_schemata(self, param_defaults=None):
@@ -282,6 +283,26 @@ class TestTemplateConditionParser(common.HeatTestCase):
def setUp(self):
super(TestTemplateConditionParser, self).setUp()
self.ctx = utils.dummy_context()
+ t = {
+ 'heat_template_version': '2016-10-14',
+ 'parameters': {
+ 'env_type': {
+ 'type': 'string',
+ 'default': 'test'
+ }
+ },
+ 'conditions': {
+ 'prod_env': {
+ 'equals': [{'get_param': 'env_type'}, 'prod']}},
+ 'resources': {
+ 'r1': {
+ 'type': 'GenericResourceType',
+ 'condition': 'prod_env'
+ }
+ }
+ }
+
+ self.tmpl = template.Template(t)
def test_conditions_with_non_supported_functions(self):
t = {
@@ -341,6 +362,25 @@ class TestTemplateConditionParser(common.HeatTestCase):
self.assertIn('The definition of condition (prod_env) is invalid',
six.text_type(ex))
+ def test_get_res_condition_invalid(self):
+ tmpl = copy.deepcopy(self.tmpl)
+ # test condition name is invalid
+ tmpl.t['resources']['r1']['condition'] = 'invalid_cd'
+ stk = stack.Stack(self.ctx, 'test_res_invalid_condition', tmpl)
+ res_snippet = tmpl.t.get('resources')['r1']
+ ex = self.assertRaises(exception.InvalidConditionReference,
+ tmpl.get_condition,
+ res_snippet, stk, 'r1.condition')
+ self.assertIn('Invalid condition "invalid_cd" (in r1.condition)',
+ six.text_type(ex))
+ # test condition name is not string
+ tmpl.t['resources']['r1']['condition'] = 111
+ ex = self.assertRaises(exception.InvalidConditionReference,
+ tmpl.get_condition,
+ res_snippet, stk, 'r1.condition')
+ self.assertIn('Invalid condition "111" (in r1.condition)',
+ six.text_type(ex))
+
class TestTemplateValidate(common.HeatTestCase):
diff --git a/heat_integrationtests/functional/test_conditions.py b/heat_integrationtests/functional/test_conditions.py
new file mode 100644
index 000000000..d0f49a8d8
--- /dev/null
+++ b/heat_integrationtests/functional/test_conditions.py
@@ -0,0 +1,131 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from heat_integrationtests.functional import functional_base
+
+
+cfn_template = '''
+AWSTemplateFormatVersion: 2010-09-09
+Parameters:
+ env_type:
+ Default: test
+ Type: String
+ AllowedValues: [prod, test]
+Conditions:
+ Prod: {"Fn::Equals" : [{Ref: env_type}, "prod"]}
+Resources:
+ test_res:
+ Type: OS::Heat::TestResource
+ Properties:
+ value: test_res
+ prod_res:
+ Type: OS::Heat::TestResource
+ Properties:
+ value: prod_res
+ Condition: Prod
+'''
+
+hot_template = '''
+heat_template_version: 2016-10-14
+parameters:
+ env_type:
+ default: test
+ type: string
+ constraints:
+ - allowed_values: [prod, test]
+conditions:
+ prod: {equals : [{get_param: env_type}, "prod"]}
+resources:
+ test_res:
+ type: OS::Heat::TestResource
+ properties:
+ value: test_res
+ prod_res:
+ type: OS::Heat::TestResource
+ properties:
+ value: prod_res
+ condition: prod
+'''
+
+
+class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):
+
+ def setUp(self):
+ super(CreateUpdateResConditionTest, self).setUp()
+
+ def res_assert_for_prod(self, resources):
+ self.assertEqual(2, len(resources))
+ res_names = [res.resource_name for res in resources]
+ self.assertIn('prod_res', res_names)
+ self.assertIn('test_res', res_names)
+
+ def res_assert_for_test(self, resources):
+ self.assertEqual(1, len(resources))
+ res_names = [res.resource_name for res in resources]
+ self.assertIn('test_res', res_names)
+ self.assertNotIn('prod_res', res_names)
+
+ 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)
+
+ parms = {'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)
+
+ def test_stack_create_update_cfn_template_prod_to_test(self):
+ parms = {'env_type': 'prod'}
+ stack_identifier = self.stack_create(template=cfn_template,
+ parameters=parms)
+ resources = self.client.resources.list(stack_identifier)
+ self.res_assert_for_prod(resources)
+
+ parms = {'env_type': 'test'}
+ self.update_stack(stack_identifier,
+ template=cfn_template,
+ parameters=parms)
+
+ resources = self.client.resources.list(stack_identifier)
+ self.res_assert_for_test(resources)
+
+ 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)
+
+ parms = {'env_type': 'prod'}
+ self.update_stack(stack_identifier,
+ template=hot_template,
+ parameters=parms)
+
+ resources = self.client.resources.list(stack_identifier)
+ self.res_assert_for_prod(resources)
+
+ def test_stack_create_update_hot_template_prod_to_test(self):
+ parms = {'env_type': 'prod'}
+ stack_identifier = self.stack_create(template=hot_template,
+ parameters=parms)
+ resources = self.client.resources.list(stack_identifier)
+ self.res_assert_for_prod(resources)
+
+ parms = {'env_type': 'test'}
+ self.update_stack(stack_identifier,
+ template=hot_template,
+ parameters=parms)
+
+ resources = self.client.resources.list(stack_identifier)
+ self.res_assert_for_test(resources)