From 6c3fa4e25d3e35adf53ad1e80d5455bf9f7a924a Mon Sep 17 00:00:00 2001 From: Peter Razumovsky Date: Mon, 14 Sep 2015 17:43:49 +0300 Subject: [PATCH] Fix translating for props with get_param value If some map or list type properties specified with json-type or commadelimitedlist parameters, error raised in case of wrong properties data parsing. Fix this case by adding check if data is GetParam instance, resolve it. Other function can be safely replaced without resolve. Change-Id: I0c9a6af29b56b629cbdad2acb868c3033e38b5ef Closes-bug: #1494364 (cherry picked from commit aea59ecdac6ed6635125eb2064554140e3e645fc) --- heat/engine/properties.py | 58 ++++++++---- heat/tests/test_properties.py | 166 ++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 17 deletions(-) diff --git a/heat/engine/properties.py b/heat/engine/properties.py index 1147005ef9..7d77a3519d 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -18,8 +18,10 @@ import six from heat.common import exception from heat.common.i18n import _ +from heat.engine.cfn import functions as cfn_funcs from heat.engine import constraints as constr from heat.engine import function +from heat.engine.hot import functions as hot_funcs from heat.engine.hot import parameters as hot_param from heat.engine import parameters from heat.engine import support @@ -659,17 +661,20 @@ class TranslationRule(object): raise ValueError(_('value must be list type when rule is Add.')) def execute_rule(self): - (source_key, source_data) = self.get_data_from_source_path( - self.source_path) - if self.value_path: - (value_key, value_data) = self.get_data_from_source_path( - self.value_path) - value = (value_data[value_key] - if value_data and value_data.get(value_key) - else self.value) - else: - (value_key, value_data) = None, None - value = self.value + try: + (source_key, source_data) = self.get_data_from_source_path( + self.source_path) + if self.value_path: + (value_key, value_data) = self.get_data_from_source_path( + self.value_path) + value = (value_data[value_key] + if value_data and value_data.get(value_key) + else self.value) + else: + (value_key, value_data) = None, None + value = self.value + except AttributeError: + return if (source_data is None or (self.rule != self.DELETE and (value is None and @@ -728,16 +733,37 @@ class TranslationRule(object): for k, s in schemata.items()) return props + def resolve_param(param): + """Check whether if given item is param and resolve, if it is.""" + # NOTE(prazumovsky): If property uses removed in HOT function, + # we should not translate it for correct validating and raising + # validation error. + if isinstance(param, hot_funcs.Removed): + raise AttributeError(_('Property uses removed function.')) + if isinstance(param, (hot_funcs.GetParam, cfn_funcs.ParamRef)): + return function.resolve(param) + elif isinstance(param, list): + return [resolve_param(param_item) for param_item in param] + else: + return param + source_key = path[0] data = self.properties.data props = self.properties.props for key in path: if isinstance(data, list): source_key = key - elif data.get(key) is not None and isinstance(data.get(key), - (list, dict)): - data = data.get(key) - props = get_props(props, key) + elif data.get(key) is not None: + # NOTE(prazumovsky): There's no need to resolve other functions + # because we can translate all function to another path. But if + # list or map type property equals to get_param function, need + # to resolve it for correct translating. + data[key] = resolve_param(data[key]) + if isinstance(data[key], (dict, list)): + data = data[key] + props = get_props(props, key) + else: + source_key = key elif data.get(key) is None: if (self.rule == TranslationRule.DELETE or (self.rule == TranslationRule.REPLACE and @@ -752,6 +778,4 @@ class TranslationRule(object): continue data = data.get(key) props = get_props(props, key) - else: - source_key = key return source_key, data diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index 898161b526..effff21fc3 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -18,6 +18,7 @@ import six from heat.common import exception from heat.engine.cfn import functions as cfn_funcs from heat.engine import constraints +from heat.engine.hot import functions as hot_funcs from heat.engine.hot import parameters as hot_param from heat.engine import parameters from heat.engine import plugin_manager @@ -2289,3 +2290,168 @@ class TestTranslationRule(common.HeatTestCase): rule.execute_rule() self.assertIsNone(props.get('far')) + + def test_property_json_param_correct_translation(self): + """Test case when property with sub-schema takes json param.""" + schema = { + 'far': properties.Schema(properties.Schema.MAP, + schema={ + 'bar': properties.Schema( + properties.Schema.STRING, + ), + 'dar': properties.Schema( + properties.Schema.STRING + ) + }) + } + + class DummyStack(dict): + @property + def parameters(self): + return mock.Mock() + + param = hot_funcs.GetParam(DummyStack(json_far='json_far'), + 'get_param', + 'json_far') + param.parameters = { + 'json_far': parameters.JsonParam( + 'json_far', + {'Type': 'Json'}, + '{"dar": "rad"}').value()} + data = {'far': param} + + props = properties.Properties(schema, data) + + rule = properties.TranslationRule(props, + properties.TranslationRule.REPLACE, + ['far', 'bar'], + value_path=['far', 'dar']) + + rule.execute_rule() + + self.assertEqual('rad', props.get('far').get('bar')) + + def test_property_json_param_to_list_correct_translation(self): + """Test case when list property with sub-schema takes json param.""" + schema = { + 'far': properties.Schema(properties.Schema.LIST, + schema=properties.Schema( + properties.Schema.MAP, + schema={ + 'bar': properties.Schema( + properties.Schema.STRING, + ), + 'dar': properties.Schema( + properties.Schema.STRING + ) + } + )) + } + + class DummyStack(dict): + @property + def parameters(self): + return mock.Mock() + + param = hot_funcs.GetParam(DummyStack(json_far='json_far'), + 'get_param', + 'json_far') + param.parameters = { + 'json_far': parameters.JsonParam( + 'json_far', + {'Type': 'Json'}, + '{"dar": "rad"}').value()} + data = {'far': [param]} + + props = properties.Properties(schema, data) + + rule = properties.TranslationRule(props, + properties.TranslationRule.REPLACE, + ['far', 'bar'], + value_name='dar') + + rule.execute_rule() + + self.assertEqual([{'dar': None, 'bar': 'rad'}], props.get('far')) + + def test_property_commadelimitedlist_param_correct_translation(self): + """Test when property with sub-schema takes comma_delimited_list.""" + schema = { + 'far': properties.Schema( + properties.Schema.LIST, + schema=properties.Schema( + properties.Schema.STRING, + ) + ), + 'boo': properties.Schema( + properties.Schema.STRING + )} + + class DummyStack(dict): + @property + def parameters(self): + return mock.Mock() + + param = hot_funcs.GetParam(DummyStack(list_far='list_far'), + 'get_param', + 'list_far') + param.parameters = { + 'list_far': parameters.CommaDelimitedListParam( + 'list_far', + {'Type': 'CommaDelimitedList'}, + "white,roses").value()} + data = {'far': param, 'boo': 'chrysanthemums'} + + props = properties.Properties(schema, data) + + rule = properties.TranslationRule(props, + properties.TranslationRule.ADD, + ['far'], + [props.get('boo')]) + rule.execute_rule() + + self.assertEqual(['white', 'roses', 'chrysanthemums'], + props.get('far')) + + def test_property_no_translation_removed_function(self): + """Test case when list property with sub-schema takes json param.""" + schema = { + 'far': properties.Schema(properties.Schema.LIST, + schema=properties.Schema( + properties.Schema.MAP, + schema={ + 'bar': properties.Schema( + properties.Schema.STRING, + ), + 'dar': properties.Schema( + properties.Schema.STRING + ) + } + )) + } + + class DummyStack(dict): + @property + def parameters(self): + return mock.Mock() + + param = hot_funcs.Removed(DummyStack(json_far='json_far'), + 'Ref', + 'json_far') + param.parameters = { + 'json_far': parameters.JsonParam( + 'json_far', + {'Type': 'Json'}, + '{"dar": "rad"}').value()} + data = {'far': [param]} + + props = properties.Properties(schema, data) + + rule = properties.TranslationRule(props, + properties.TranslationRule.REPLACE, + ['far', 'bar'], + value_name='dar') + + rule.execute_rule() + + self.assertEqual([param], props.data.get('far'))