Correct list_join function

Heat supports 'list_join' function to join one or more
lists of strings in Liberty release. But when we resolve
the function, we pop some arguments, this introduced
errors if resolve the snippet again when updating a resource.

This patch correct the behavior of 'list_join' function,
also refactor some codes for the functions of Kilo/Liberty.

Change-Id: Ia67a0171261ca1a864fe11f7d61c3bf5ef801040
Closes-Bug: #1513060
This commit is contained in:
huangtianhua 2015-11-06 17:15:25 +08:00
parent ba532e6530
commit db11ae6d42
3 changed files with 79 additions and 36 deletions

View File

@ -303,7 +303,7 @@ class Join(function.Function):
fmt_data = {'fn_name': self.fn_name,
'example': example}
if isinstance(self.args, (six.string_types, collections.Mapping)):
if not isinstance(self.args, list):
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
'should be: %(example)s') % fmt_data)
@ -331,7 +331,8 @@ class Join(function.Function):
return ''
if not isinstance(s, six.string_types):
raise TypeError(
_('Items to join must be strings %s') % (repr(s)[:200]))
_('Items to join must be strings not %s'
) % (repr(s)[:200]))
return s
return delim.join(ensure_string(s) for s in strings)

View File

@ -357,7 +357,7 @@ class Join(cfn_funcs.Join):
class JoinMultiple(function.Function):
"""A function for joining strings.
"""A function for joining one or more lists of strings.
Takes the form::
@ -376,31 +376,31 @@ class JoinMultiple(function.Function):
fmt_data = {'fn_name': fn_name,
'example': example}
if isinstance(args, (six.string_types, collections.Mapping)):
if not isinstance(args, list):
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
'should be: %(example)s') % fmt_data)
try:
self._delim = args.pop(0)
self._joinlist = args.pop(0)
except IndexError:
self._delim = args[0]
self._joinlists = args[1:]
if len(self._joinlists) < 1:
raise ValueError
except (IndexError, ValueError):
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
'should be: %(example)s') % fmt_data)
# Optionally allow additional lists, which are appended
for l in args:
try:
self._joinlist += l
except (AttributeError, TypeError):
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
'should be: %(example)s') % fmt_data)
def result(self):
strings = function.resolve(self._joinlist)
if strings is None:
strings = []
if (isinstance(strings, six.string_types) or
not isinstance(strings, collections.Sequence)):
raise TypeError(_('"%s" must operate on a list') % self.fn_name)
r_joinlists = function.resolve(self._joinlists)
strings = []
for jl in r_joinlists:
if jl:
if (isinstance(jl, six.string_types) or
not isinstance(jl, collections.Sequence)):
raise TypeError(_('"%s" must operate on '
'a list') % self.fn_name)
strings += jl
delim = function.resolve(self._delim)
if not isinstance(delim, six.string_types):
@ -421,9 +421,7 @@ class JoinMultiple(function.Function):
msg = _('Items to join must be string, map or list. '
'%s failed json serialization'
) % (repr(s)[:200])
else:
msg = _('Items to join must be string, map or list not %s'
) % (repr(s)[:200])
raise TypeError(msg)
return delim.join(ensure_string(s) for s in strings)

View File

@ -496,6 +496,17 @@ class HOTemplateTest(common.HeatTestCase):
stack = parser.Stack(utils.dummy_context(), 'test_stack', tmpl)
snippet = {'list_join': ["\n", {'get_attr': ['rg', 'name']}]}
self.assertEqual('', self.resolve(snippet, tmpl, stack))
# test list_join for liberty template
hot_tpl['heat_template_version'] = '2015-10-15'
tmpl = template.Template(hot_tpl)
stack = parser.Stack(utils.dummy_context(), 'test_stack', tmpl)
snippet = {'list_join': ["\n", {'get_attr': ['rg', 'name']}]}
self.assertEqual('', self.resolve(snippet, tmpl, stack))
# test list join again and update to multiple lists
snippet = {'list_join': ["\n",
{'get_attr': ['rg', 'name']},
{'get_attr': ['rg', 'name']}]}
self.assertEqual('', self.resolve(snippet, tmpl, stack))
def test_str_replace(self):
"""Test str_replace function."""
@ -669,25 +680,41 @@ class HOTemplateTest(common.HeatTestCase):
tmpl = template.Template(hot_liberty_tpl_empty)
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
def test_list_join_empty_list(self):
snippet = {'list_join': [',', []]}
snippet_resolved = ''
k_tmpl = template.Template(hot_kilo_tpl_empty)
self.assertEqual(snippet_resolved, self.resolve(snippet, k_tmpl))
l_tmpl = template.Template(hot_liberty_tpl_empty)
self.assertEqual(snippet_resolved, self.resolve(snippet, l_tmpl))
def test_join_json(self):
snippet = {'list_join': [',', [{'foo': 'json'}, {'foo2': 'json2'}]]}
snippet_resolved = '{"foo": "json"},{"foo2": "json2"}'
tmpl = template.Template(hot_liberty_tpl_empty)
self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl))
l_tmpl = template.Template(hot_liberty_tpl_empty)
self.assertEqual(snippet_resolved, self.resolve(snippet, l_tmpl))
# old versions before liberty don't support to join json
k_tmpl = template.Template(hot_kilo_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, k_tmpl)
self.assertEqual("Items to join must be strings not {'foo': 'json'}",
six.text_type(exc))
def test_join_type_fail(self):
def test_join_object_type_fail(self):
not_serializable = object
snippet = {'list_join': [',', [not_serializable]]}
tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
l_tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, l_tmpl)
self.assertIn('Items to join must be string, map or list not',
six.text_type(exc))
k_tmpl = template.Template(hot_kilo_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, k_tmpl)
self.assertIn("Items to join must be strings", six.text_type(exc))
def test_join_json_fail(self):
not_serializable = object
snippet = {'list_join': [',', [{'foo': not_serializable}]]}
tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
l_tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, l_tmpl)
self.assertIn('Items to join must be string, map or list',
six.text_type(exc))
self.assertIn("failed json serialization",
@ -695,22 +722,39 @@ class HOTemplateTest(common.HeatTestCase):
def test_join_invalid(self):
snippet = {'list_join': 'bad'}
tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
l_tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, l_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc))
k_tmpl = template.Template(hot_kilo_tpl_empty)
exc1 = self.assertRaises(TypeError, self.resolve, snippet, k_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc1))
def test_join_int_invalid(self):
snippet = {'list_join': 5}
l_tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, l_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc))
k_tmpl = template.Template(hot_kilo_tpl_empty)
exc1 = self.assertRaises(TypeError, self.resolve, snippet, k_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc1))
def test_join_invalid_value(self):
snippet = {'list_join': [',']}
tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl)
l_tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(ValueError, self.resolve, snippet, l_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc))
k_tmpl = template.Template(hot_kilo_tpl_empty)
exc1 = self.assertRaises(ValueError, self.resolve, snippet, k_tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc1))
def test_join_invalid_multiple(self):
snippet = {'list_join': [',', 'bad', ['foo']]}
tmpl = template.Template(hot_liberty_tpl_empty)
exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl)
self.assertIn('Incorrect arguments', six.text_type(exc))
self.assertIn('must operate on a list', six.text_type(exc))
def test_merge(self):
snippet = {'map_merge': [{'f1': 'b1', 'f2': 'b2'}, {'f1': 'b2'}]}