From f019fb002e84bc6b3dc621560b7bab863b7c220b Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Thu, 21 Jul 2016 18:53:16 +0100 Subject: [PATCH] Fix some map_replace issues While testing I discovered a couple of corner cases not previously handled: - If you provide values/keys via a get_attr reference it's possible for them to be None during validation - If the input map has an unhashable value, it breaks the values replacement so we need to tolerate a failure to lookup an unhashable key in the values data. Change-Id: I14d92056e0a07816a216aba752711887e8ac0aa5 --- doc/source/template_guide/hot_spec.rst | 4 ++++ heat/engine/hot/functions.py | 10 +++++++--- heat/tests/test_hot.py | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) 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'}}]}