diff --git a/heat/engine/properties.py b/heat/engine/properties.py index c342d3b94..e72bae9af 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -13,6 +13,7 @@ import collections +from oslo_serialization import jsonutils import six from heat.common import exception @@ -58,13 +59,15 @@ class Schema(constr.Schema): implemented=True, update_allowed=False, immutable=False, - support_status=support.SupportStatus()): + support_status=support.SupportStatus(), + allow_conversion=False): super(Schema, self).__init__(data_type, description, default, schema, required, constraints) self.implemented = implemented self.update_allowed = update_allowed self.immutable = immutable self.support_status = support_status + self.allow_conversion = allow_conversion # validate structural correctness of schema itself self.validate() @@ -146,6 +149,13 @@ class Schema(constr.Schema): param.BOOLEAN: cls.BOOLEAN } + # allow_conversion allows slightly more flexible type conversion + # where property->parameter types don't align, primarily when + # a json parameter value is passed via a Map property, which requires + # some coercion to pass strings or lists (which are both valid for + # Json parameters but not for Map properties). + allow_conversion = param.type == param.MAP + # make update_allowed true by default on TemplateResources # as the template should deal with this. return cls(data_type=param_type_map.get(param.type, cls.MAP), @@ -153,7 +163,8 @@ class Schema(constr.Schema): required=param.required, constraints=param.constraints, update_allowed=True, - immutable=False) + immutable=False, + allow_conversion=allow_conversion) def allowed_param_prop_type(self): """ @@ -269,6 +280,14 @@ class Property(object): if value is None: value = self.has_default() and self.default() or {} if not isinstance(value, collections.Mapping): + # This is to handle passing Lists via Json parameters exposed + # via a provider resource, in particular lists-of-dicts which + # cannot be handled correctly via comma_delimited_list + if self.schema.allow_conversion: + if isinstance(value, six.string_types): + return value + elif isinstance(value, collections.Sequence): + return jsonutils.dumps(value) raise TypeError(_('"%s" is not a map') % value) return dict(self._get_children(six.iteritems(value), diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index 8bd385772..4d816086b 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_serialization import jsonutils import six import testtools @@ -468,6 +469,7 @@ class PropertySchemaTest(testtools.TestCase): self.assertTrue(schema.required) self.assertIsNone(schema.default) self.assertEqual(0, len(schema.constraints)) + self.assertFalse(schema.allow_conversion) def test_from_number_param_min(self): default = "42" @@ -546,6 +548,7 @@ class PropertySchemaTest(testtools.TestCase): self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) + self.assertFalse(schema.allow_conversion) allowed_constraint = schema.constraints[0] @@ -563,6 +566,7 @@ class PropertySchemaTest(testtools.TestCase): self.assertEqual(properties.Schema.LIST, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) + self.assertFalse(schema.allow_conversion) def test_from_json_param(self): param = parameters.Schema.from_dict('name', { @@ -575,6 +579,7 @@ class PropertySchemaTest(testtools.TestCase): self.assertEqual(properties.Schema.MAP, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) + self.assertTrue(schema.allow_conversion) class PropertyTest(testtools.TestCase): @@ -862,6 +867,12 @@ class PropertyTest(testtools.TestCase): p = properties.Property({'Type': 'Map'}) self.assertRaises(TypeError, p.get_value, ['foo']) + def test_map_allow_conversion(self): + p = properties.Property({'Type': 'Map'}) + p.schema.allow_conversion = True + self.assertEqual('foo', p.get_value('foo')) + self.assertEqual(jsonutils.dumps(['foo']), p.get_value(['foo'])) + def test_map_schema_good(self): map_schema = {'valid': {'Type': 'Boolean'}} p = properties.Property({'Type': 'Map', 'Schema': map_schema})