diff --git a/doc/source/template_guide/hot_spec.rst b/doc/source/template_guide/hot_spec.rst index 934f4b0618..5ae935892c 100644 --- a/doc/source/template_guide/hot_spec.rst +++ b/doc/source/template_guide/hot_spec.rst @@ -1329,6 +1329,10 @@ The keys/values mappings are optional, either or both may be specified. Note that an error is raised if a replacement defined in "keys" results in a collision with an existing keys in the input or output map. +Also note that while unhashable values (e.g lists) in the input map are valid, +they will be ignored by the values replacement, because no key can be defined +in the values mapping to define their replacement. + yaql ---- The ``yaql`` evaluates yaql expression on a given data. diff --git a/heat/engine/hot/functions.py b/heat/engine/hot/functions.py index 2797c5bd6b..5565b1c1ac 100644 --- a/heat/engine/hot/functions.py +++ b/heat/engine/hot/functions.py @@ -542,8 +542,8 @@ class MapReplace(function.Function): raise ValueError(_('Incorrect arguments to "%(fn_name)s" ' 'should be: %(example)s') % self.fmt_data) - repl_keys = repl_map.get('keys', {}) - repl_values = repl_map.get('values', {}) + repl_keys = ensure_map(repl_map.get('keys', {})) + repl_values = ensure_map(repl_map.get('values', {})) ret_map = {} for k, v in six.iteritems(in_map): key = repl_keys.get(k) @@ -559,7 +559,11 @@ class MapReplace(function.Function): msg = _('key replacement %s collides with ' 'a key in the output map') raise ValueError(msg % key) - value = repl_values.get(v, v) + try: + value = repl_values.get(v, v) + except TypeError: + # If the value is unhashable, we get here + value = v ret_map[key] = value return ret_map diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index 4aa9601306..f50b96d8b5 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -926,6 +926,30 @@ class HOTemplateTest(common.HeatTestCase): self.assertEqual({'f1': 'b1', 'F2': 'b2'}, resolved) + def test_map_replace_none_values(self): + snippet = {'map_replace': [{'f1': 'b1', 'f2': 'b2'}, + {'values': None}]} + tmpl = template.Template(hot_newton_tpl_empty) + resolved = self.resolve(snippet, tmpl) + self.assertEqual({'f1': 'b1', 'f2': 'b2'}, + resolved) + + def test_map_replace_none_keys(self): + snippet = {'map_replace': [{'f1': 'b1', 'f2': 'b2'}, + {'keys': None}]} + tmpl = template.Template(hot_newton_tpl_empty) + resolved = self.resolve(snippet, tmpl) + self.assertEqual({'f1': 'b1', 'f2': 'b2'}, + resolved) + + def test_map_replace_unhashable_value(self): + snippet = {'map_replace': [{'f1': 'b1', 'f2': []}, + {'values': {}}]} + tmpl = template.Template(hot_newton_tpl_empty) + resolved = self.resolve(snippet, tmpl) + self.assertEqual({'f1': 'b1', 'f2': []}, + resolved) + def test_map_replace_keys_collide(self): snippet = {'map_replace': [{'f1': 'b1', 'f2': 'b2'}, {'keys': {'f2': 'f1'}}]}