From d072f6e7f195d6488d70b5246e67fdba1a5f5286 Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Fri, 22 Jul 2016 18:04:27 +0800 Subject: [PATCH] Add validating of section 'conditions' Validate section 'conditions' when stack validate. Change-Id: I0cf1abcc4656c573afbbd41e02b573ada63c1ae6 Blueprint: support-conditions-function --- heat/common/exception.py | 5 +++ heat/engine/cfn/template.py | 3 ++ heat/engine/hot/template.py | 3 ++ heat/engine/stack.py | 3 ++ heat/engine/template.py | 5 +++ heat/engine/template_common.py | 30 +++++++++++++++ heat/tests/test_template.py | 68 ++++++++++++++++++++++++++++++++++ 7 files changed, 117 insertions(+) diff --git a/heat/common/exception.py b/heat/common/exception.py index 881171885b..157204f077 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -133,6 +133,11 @@ class InvalidConditionFunction(HeatException): msg_fmt = _("The function is not supported in condition: %(func)s") +class InvalidConditionDefinition(HeatException): + msg_fmt = _("The definition of condition (%(cd)s) is " + "invalid: %(definition)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 f91a39d5bf..ff69f672fa 100644 --- a/heat/engine/cfn/template.py +++ b/heat/engine/cfn/template.py @@ -176,6 +176,9 @@ class CfnTemplate(CfnTemplateBase): (n, function.Invalid) for n in self.functions) self._parser_condition_functions.update(self.condition_functions) + def get_condition_definitions(self): + return self[self.CONDITIONS] + class HeatTemplate(CfnTemplateBase): functions = { diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index 7ff875a38a..025bfe4713 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -511,3 +511,6 @@ class HOTemplate20161014(HOTemplate20160408): else: self._parser_condition_functions[n] = f self._parser_condition_functions.update(self.condition_functions) + + def get_condition_definitions(self): + return self[self.CONDITIONS] diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 354a4c50c2..f9542bad8a 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -779,6 +779,9 @@ class Stack(collections.Mapping): parameter_groups = param_groups.ParameterGroups(self.t) parameter_groups.validate() + # Validate condition definition of conditions section + self.t.validate_condition_definitions(self) + # Validate types of sections in ResourceDefinitions self.t.validate_resource_definitions(self) diff --git a/heat/engine/template.py b/heat/engine/template.py index 7ded1ec087..663693d8ca 100644 --- a/heat/engine/template.py +++ b/heat/engine/template.py @@ -211,6 +211,11 @@ class Template(collections.Mapping): """Check section's type of ResourceDefinitions.""" pass + @abc.abstractmethod + def validate_condition_definitions(self, stack): + """Check conditions section.""" + pass + @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 3c1fb0e54e..9d8ac9295d 100644 --- a/heat/engine/template_common.py +++ b/heat/engine/template_common.py @@ -74,3 +74,33 @@ class CommonTemplate(template.Template): self.validate_resource_definition(name, data) except (TypeError, ValueError, KeyError) as ex: raise exception.StackValidationFailed(message=six.text_type(ex)) + + def validate_condition_definitions(self, stack): + """Check conditions section.""" + + resolved_cds = self.resolve_conditions(stack) + if resolved_cds: + for cd_key, cd_value in six.iteritems(resolved_cds): + if not isinstance(cd_value, bool): + raise exception.InvalidConditionDefinition( + cd=cd_key, + definition=cd_value) + + def resolve_conditions(self, stack): + cd_snippet = self.get_condition_definitions() + result = {} + if cd_snippet: + for cd_key, cd_value in six.iteritems(cd_snippet): + # hasn't been resolved yet + if not isinstance(cd_value, bool): + condition_func = self.parse_condition( + stack, cd_value) + resolved_cd_value = function.resolve(condition_func) + result[cd_key] = resolved_cd_value + else: + result[cd_key] = cd_value + + return result + + def get_condition_definitions(self): + return {} diff --git a/heat/tests/test_template.py b/heat/tests/test_template.py index a998dc0412..f9fd5f7c74 100644 --- a/heat/tests/test_template.py +++ b/heat/tests/test_template.py @@ -138,6 +138,9 @@ class TestTemplatePluginManager(common.HeatTestCase): def validate_resource_definitions(self, stack): pass + def validate_condition_definitions(self, stack): + pass + def resource_definitions(self, stack): pass @@ -274,6 +277,71 @@ class ParserTest(common.HeatTestCase): self.assertIsNot(raw, parsed) +class TestTemplateConditionParser(common.HeatTestCase): + + def setUp(self): + super(TestTemplateConditionParser, self).setUp() + self.ctx = utils.dummy_context() + + def test_conditions_with_non_supported_functions(self): + t = { + 'heat_template_version': '2016-10-14', + 'parameters': { + 'env_type': { + 'type': 'string', + 'default': 'test' + } + }, + 'conditions': { + 'prod_env': { + 'equals': [{'get_param': 'env_type'}, + {'get_attr': [None, 'att']}]}}} + # test with get_attr in equals + tmpl = template.Template(t) + stk = stack.Stack(self.ctx, 'test_condition_with_get_attr_func', tmpl) + ex = self.assertRaises(exception.InvalidConditionFunction, + tmpl.resolve_conditions, stk) + self.assertIn('The function is not supported in condition: get_attr', + six.text_type(ex)) + + # test with get_resource in top level of a condition + tmpl.t['conditions']['prod_env'] = {'get_resource': 'R1'} + stk = stack.Stack(self.ctx, 'test_condition_with_get_attr_func', tmpl) + ex = self.assertRaises(exception.InvalidConditionFunction, + tmpl.resolve_conditions, stk) + self.assertIn('The function is not supported in condition: ' + 'get_resource', six.text_type(ex)) + + # test with get_attr in top level of a condition + tmpl.t['conditions']['prod_env'] = {'get_attr': [None, 'att']} + stk = stack.Stack(self.ctx, 'test_condition_with_get_attr_func', tmpl) + ex = self.assertRaises(exception.InvalidConditionFunction, + tmpl.resolve_conditions, stk) + self.assertIn('The function is not supported in condition: get_attr', + six.text_type(ex)) + + def test_condition_resolved_not_boolean(self): + t = { + 'heat_template_version': '2016-10-14', + 'parameters': { + 'env_type': { + 'type': 'string', + 'default': 'test' + } + }, + 'conditions': { + 'prod_env': {'get_param': 'env_type'}}} + + # test with get_attr in equals + tmpl = template.Template(t) + stk = stack.Stack(self.ctx, 'test_condition_not_boolean', tmpl) + + ex = self.assertRaises(exception.InvalidConditionDefinition, + tmpl.validate_condition_definitions, stk) + self.assertIn('The definition of condition (prod_env) is invalid', + six.text_type(ex)) + + class TestTemplateValidate(common.HeatTestCase): def test_template_validate_cfn_check_t_digest(self):