diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py index cbde6d7448..84e2318f4d 100644 --- a/heat/engine/cfn/template.py +++ b/heat/engine/cfn/template.py @@ -83,6 +83,9 @@ class CfnTemplate(template.Template): return dict((name, parameters.Schema.from_dict(name, schema)) for name, schema in six.iteritems(params)) + def get_section_name(self, section): + return section + def parameters(self, stack_identifier, user_params, param_defaults=None): return parameters.Parameters(stack_identifier, self, user_params=user_params, diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index fdb5580021..5d90f5dad6 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -56,6 +56,12 @@ class HOTemplate20130523(template.Template): cfn_template.CfnTemplate.RESOURCES: RESOURCES, cfn_template.CfnTemplate.OUTPUTS: OUTPUTS} + _RESOURCE_HOT_TO_CFN_ATTRS = {'type': 'Type', + 'properties': 'Properties', + 'metadata': 'Metadata', + 'depends_on': 'DependsOn', + 'deletion_policy': 'DeletionPolicy', + 'update_policy': 'UpdatePolicy'} functions = { 'Fn::GetAZs': cfn_funcs.GetAZs, 'get_param': hot_funcs.GetParam, @@ -157,15 +163,14 @@ class HOTemplate20130523(template.Template): def _translate_resources(self, resources): """Get the resources of the template translated into CFN format.""" - HOT_TO_CFN_ATTRS = {'type': 'Type', - 'properties': 'Properties', - 'metadata': 'Metadata', - 'depends_on': 'DependsOn', - 'deletion_policy': 'DeletionPolicy', - 'update_policy': 'UpdatePolicy'} return self._translate_section('resources', 'type', resources, - HOT_TO_CFN_ATTRS) + self._RESOURCE_HOT_TO_CFN_ATTRS) + + def get_section_name(self, section): + cfn_to_hot_attrs = dict(zip(self._RESOURCE_HOT_TO_CFN_ATTRS.values(), + self._RESOURCE_HOT_TO_CFN_ATTRS.keys())) + return cfn_to_hot_attrs.get(section, section) def _translate_outputs(self, outputs): """Get the outputs of the template translated into CFN format.""" diff --git a/heat/engine/properties.py b/heat/engine/properties.py index e72bae9afc..559e80a9f2 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -267,7 +267,6 @@ class Property(object): keys = list(self.schema.schema) schemata = dict((k, self.schema.schema[k]) for k in keys) properties = Properties(schemata, dict(child_values), - parent_name=self.name, context=self.context) if validate: properties.validate() @@ -343,15 +342,16 @@ class Property(object): class Properties(collections.Mapping): def __init__(self, schema, data, resolver=lambda d: d, parent_name=None, - context=None): + context=None, section=None): self.props = dict((k, Property(s, k, context)) for k, s in schema.items()) self.resolve = resolver self.data = data - if parent_name is None: - self.error_prefix = '' - else: - self.error_prefix = '%s: ' % parent_name + self.error_prefix = [] + if parent_name is not None: + self.error_prefix.append(parent_name) + if section is not None: + self.error_prefix.append(section) self.context = context @staticmethod @@ -369,38 +369,58 @@ class Properties(collections.Mapping): return {} def validate(self, with_value=True): - for (key, prop) in self.props.items(): - # check that update_allowed and immutable - # do not contradict each other - if prop.update_allowed() and prop.immutable(): - msg = _("Property %(prop)s: %(ua)s and %(im)s " - "cannot both be True") % { - 'prop': key, - 'ua': prop.schema.UPDATE_ALLOWED, - 'im': prop.schema.IMMUTABLE} - raise exception.InvalidSchemaError(message=msg) - - if with_value: - try: - self._get_property_value(key, validate=True) - except ValueError as e: - msg = _("Property error : %s") % e + try: + for key in self.data: + if key not in self.props: + msg = _("Unknown Property %s") % key raise exception.StackValidationFailed(message=msg) - # are there unimplemented Properties - if not prop.implemented() and key in self.data: - msg = _("Property %s not implemented yet") % key - raise exception.StackValidationFailed(message=msg) + for (key, prop) in self.props.items(): + # check that update_allowed and immutable + # do not contradict each other + if prop.update_allowed() and prop.immutable(): + msg = _("Property %(prop)s: %(ua)s and %(im)s " + "cannot both be True") % { + 'prop': key, + 'ua': prop.schema.UPDATE_ALLOWED, + 'im': prop.schema.IMMUTABLE} + raise exception.InvalidSchemaError(message=msg) - for key in self.data: - if key not in self.props: - msg = _("Unknown Property %s") % key - raise exception.StackValidationFailed(message=msg) + if with_value: + try: + self._get_property_value(key, validate=True) + except exception.StackValidationFailed as ex: + path = [key] + path.extend(ex.path) + raise exception.StackValidationFailed( + path=path, message=ex.error_message) + except ValueError as e: + if prop.required() and key not in self.data: + path = [] + else: + path = [key] + raise exception.StackValidationFailed( + path=path, message=six.text_type(e)) + + # are there unimplemented Properties + if not prop.implemented() and key in self.data: + msg = _("Property %s not implemented yet") % key + raise exception.StackValidationFailed(message=msg) + except exception.StackValidationFailed as ex: + # NOTE(prazumovsky): should reraise exception for adding specific + # error name and error_prefix to path for correct error message + # building. + path = self.error_prefix + path.extend(ex.path) + raise exception.StackValidationFailed( + error=ex.error or 'Property error', + path=path, + message=ex.error_message + ) def _get_property_value(self, key, validate=False): if key not in self: - raise KeyError(_('%(prefix)sInvalid Property %(key)s') % - {'prefix': self.error_prefix, 'key': key}) + raise KeyError(_('Invalid Property %s') % key) prop = self.props[key] @@ -414,16 +434,20 @@ class Properties(collections.Mapping): value = self.resolve(unresolved_value) return prop.get_value(value, validate) + # Children can raise StackValidationFailed with unique path which + # is necessary for further use in StackValidationFailed exception. + # So we need to handle this exception in this method. + except exception.StackValidationFailed as e: + raise exception.StackValidationFailed(path=e.path, + message=e.error_message) # the resolver function could raise any number of exceptions, # so handle this generically except Exception as e: - raise ValueError('%s%s %s' % (self.error_prefix, key, - six.text_type(e))) + raise ValueError(six.text_type(e)) elif prop.has_default(): return prop.get_value(None, validate) elif prop.required(): - raise ValueError(_('%(prefix)sProperty %(key)s not assigned') % - {'prefix': self.error_prefix, 'key': key}) + raise ValueError(_('Property %s not assigned') % key) def __getitem__(self, key): return self._get_property_value(key) diff --git a/heat/engine/resource.py b/heat/engine/resource.py index e1174dfa63..49e1d06722 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -874,7 +874,18 @@ class Resource(object): function.validate(self.t) self.validate_deletion_policy(self.t.deletion_policy()) - return self.properties.validate(with_value=self.stack.strict_validate) + try: + validate = self.properties.validate( + with_value=self.stack.strict_validate) + except exception.StackValidationFailed as ex: + path = [self.stack.t.RESOURCES, ex.path[0], + self.stack.t.get_section_name(ex.path[1])] + path.extend(ex.path[2:]) + raise exception.StackValidationFailed( + error=ex.error, + path=path, + message=ex.error_message) + return validate @classmethod def validate_deletion_policy(cls, policy): diff --git a/heat/engine/rsrc_defn.py b/heat/engine/rsrc_defn.py index 80df6d7aab..24fd653814 100644 --- a/heat/engine/rsrc_defn.py +++ b/heat/engine/rsrc_defn.py @@ -183,7 +183,8 @@ class ResourceDefinitionCore(object): require a context to validate constraints. """ return properties.Properties(schema, self._properties or {}, - function.resolve, self.name, context) + function.resolve, self.name, context, + section=PROPERTIES) def deletion_policy(self): """ @@ -201,7 +202,8 @@ class ResourceDefinitionCore(object): require a context to validate constraints. """ return properties.Properties(schema, self._update_policy or {}, - function.resolve, self.name, context) + function.resolve, self.name, context, + section=UPDATE_POLICY) def metadata(self): """ diff --git a/heat/engine/service.py b/heat/engine/service.py index f6ccbee2d6..4d48f7e17b 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -816,7 +816,7 @@ class EngineService(service.Service): env = environment.Environment(params) - for res in tmpl_resources.values(): + for name, res in six.iteritems(tmpl_resources): ResourceClass = env.get_class(res['Type']) if ResourceClass == resources.template_resource.TemplateResource: # we can't validate a TemplateResource unless we instantiate @@ -824,9 +824,12 @@ class EngineService(service.Service): # parameters into properties_schema. continue - props = properties.Properties(ResourceClass.properties_schema, - res.get('Properties', {}), - context=cnxt) + props = properties.Properties( + ResourceClass.properties_schema, + res.get('Properties', {}), + parent_name=six.text_type(name), + context=cnxt, + section='Properties') deletion_policy = res.get('DeletionPolicy', 'Delete') try: ResourceClass.validate_deletion_policy(deletion_policy) diff --git a/heat/engine/template.py b/heat/engine/template.py index 0fbe29f733..97fb074db1 100644 --- a/heat/engine/template.py +++ b/heat/engine/template.py @@ -158,6 +158,11 @@ class Template(collections.Mapping): '''Return a dict of parameters.Schema objects for the parameters.''' pass + @abc.abstractmethod + def get_section_name(self, section): + """Return a correct section name.""" + pass + @abc.abstractmethod def parameters(self, stack_identifier, user_params, param_defaults=None): '''Return a parameters.Parameters object for the stack.''' diff --git a/heat/tests/autoscaling/test_launch_config.py b/heat/tests/autoscaling/test_launch_config.py index 57f4b4778d..be298eb09f 100644 --- a/heat/tests/autoscaling/test_launch_config.py +++ b/heat/tests/autoscaling/test_launch_config.py @@ -133,8 +133,10 @@ class LaunchConfigurationTest(common.HeatTestCase): self.patchobject(nova.NovaClientPlugin, 'get_server', side_effect=exception.ServerNotFound(server='5678')) - msg = ("Property error : LaunchConfig: InstanceId Error validating " - "value '5678': The server (5678) could not be found.") + msg = ("Property error : " + "Resources.LaunchConfig.Properties.InstanceId: " + "Error validating value '5678': The server (5678) " + "could not be found.") exc = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertIn(msg, six.text_type(exc)) @@ -193,9 +195,10 @@ class LaunchConfigurationTest(common.HeatTestCase): self.validate_launch_config, t, stack, 'LaunchConfig') - excepted_error = ('Property error : LaunchConfig: BlockDeviceMappings ' - 'Property error : BlockDeviceMappings: 0 Property ' - 'error : 0: Property DeviceName not assigned') + excepted_error = ( + 'Property error : ' + 'Resources.LaunchConfig.Properties.BlockDeviceMappings[0]: ' + 'Property DeviceName not assigned') self.assertIn(excepted_error, six.text_type(e)) self.m.VerifyAll() diff --git a/heat/tests/autoscaling/test_scaling_group.py b/heat/tests/autoscaling/test_scaling_group.py index c26e0d76a3..81a052f220 100644 --- a/heat/tests/autoscaling/test_scaling_group.py +++ b/heat/tests/autoscaling/test_scaling_group.py @@ -220,8 +220,10 @@ class TestAutoScalingGroupValidation(common.HeatTestCase): rsrc = stack['WebServerGroup'] self._stub_nova_server_get(not_found=True) self.m.ReplayAll() - msg = ("Property error : WebServerGroup: InstanceId Error validating " - "value '5678': The server (5678) could not be found") + msg = ("Property error : " + "Resources.WebServerGroup.Properties.InstanceId: " + "Error validating value '5678': The server (5678) could " + "not be found.") exc = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertIn(msg, six.text_type(exc)) diff --git a/heat/tests/aws/test_volume.py b/heat/tests/aws/test_volume.py index 5b77fc4657..892c7f3f04 100644 --- a/heat/tests/aws/test_volume.py +++ b/heat/tests/aws/test_volume.py @@ -203,7 +203,9 @@ class VolumeTest(vt_base.BaseVolumeTest): ex = self.assertRaises(exception.StackValidationFailed, self.create_volume, self.t, stack, 'DataVolume') - self.assertIn('Tags Property error', six.text_type(ex)) + self.assertEqual("Property error : " + "Resources.DataVolume.Properties.Tags[0]: " + "Unknown Property Foo", six.text_type(ex)) self.m.VerifyAll() @@ -664,8 +666,8 @@ class VolumeTest(vt_base.BaseVolumeTest): self.create_volume, self.t, stack, 'DataVolume') self.assertEqual( - "Property error : DataVolume: Size 0 is out of " - "range (min: 1, max: None)", six.text_type(error)) + "Property error : Resources.DataVolume.Properties.Size: " + "0 is out of range (min: 1, max: None)", six.text_type(error)) def test_volume_attachment_updates_not_supported(self): self.m.StubOutWithMock(nova.NovaClientPlugin, 'get_server') diff --git a/heat/tests/openstack/test_volume.py b/heat/tests/openstack/test_volume.py index 46e1b68f4f..dc73acea15 100644 --- a/heat/tests/openstack/test_volume.py +++ b/heat/tests/openstack/test_volume.py @@ -103,8 +103,8 @@ class CinderVolumeTest(vt_base.BaseVolumeTest): self.create_volume, self.t, stack, 'volume') self.assertEqual( - "Property error : volume: size 0 is out of " - "range (min: 1, max: None)", six.text_type(error)) + "Property error : resources.volume.properties.size: " + "0 is out of range (min: 1, max: None)", six.text_type(error)) def test_cinder_create(self): fv = vt_base.FakeVolume('creating') diff --git a/heat/tests/test_ceilometer_alarm.py b/heat/tests/test_ceilometer_alarm.py index 5a60d7dc7a..8b50bb7822 100644 --- a/heat/tests/test_ceilometer_alarm.py +++ b/heat/tests/test_ceilometer_alarm.py @@ -377,8 +377,8 @@ class CeilometerAlarmTest(common.HeatTestCase): error = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertEqual( - "Property error : MEMAlarmHigh: %s Value '60a' is not an " - "integer" % p, six.text_type(error)) + "Property error : Resources.MEMAlarmHigh.Properties.%s: " + "Value '60a' is not an integer" % p, six.text_type(error)) def test_mem_alarm_high_not_integer_parameters(self): snippet = template_format.parse(not_string_alarm_template) @@ -392,8 +392,9 @@ class CeilometerAlarmTest(common.HeatTestCase): error = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertEqual( - "Property error : MEMAlarmHigh: %s int() argument must be " - "a string or a number, not 'list'" % p, six.text_type(error)) + "Property error : Resources.MEMAlarmHigh.Properties.%s: " + "int() argument must be a string or a number, not " + "'list'" % p, six.text_type(error)) def test_mem_alarm_high_check_not_required_parameters(self): snippet = template_format.parse(not_string_alarm_template) @@ -406,7 +407,8 @@ class CeilometerAlarmTest(common.HeatTestCase): error = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertEqual( - "Property error : MEMAlarmHigh: Property meter_name not assigned", + "Property error : Resources.MEMAlarmHigh.Properties: " + "Property meter_name not assigned", six.text_type(error)) for p in ('period', 'evaluation_periods', 'statistic', @@ -521,8 +523,9 @@ class CombinationAlarmTest(common.HeatTestCase): error = self.assertRaises(exception.StackValidationFailed, rsrc.validate) self.assertEqual( - "Property error : CombinAlarm: alarm_ids length (0) is out of " - "range (min: 1, max: None)", six.text_type(error)) + "Property error : Resources.CombinAlarm.Properties.alarm_ids: " + "length (0) is out of range (min: 1, max: None)", + six.text_type(error)) def test_update(self): rsrc = self.create_alarm() diff --git a/heat/tests/test_glance_image.py b/heat/tests/test_glance_image.py index 75b7f82d9b..5ca65816ab 100644 --- a/heat/tests/test_glance_image.py +++ b/heat/tests/test_glance_image.py @@ -99,7 +99,8 @@ class GlanceImageTest(common.HeatTestCase): ) image = stack['image'] image.t['Properties']['min_disk'] = -1 - error_msg = 'min_disk -1 is out of range (min: 0, max: None)' + error_msg = ('Property error : resources.image.properties.min_disk: ' + '-1 is out of range (min: 0, max: None)') self._test_validate(image, error_msg) def test_invalid_min_ram(self): @@ -111,7 +112,8 @@ class GlanceImageTest(common.HeatTestCase): ) image = stack['image'] image.t['Properties']['min_ram'] = -1 - error_msg = 'min_ram -1 is out of range (min: 0, max: None)' + error_msg = ('Property error : resources.image.properties.min_ram: ' + '-1 is out of range (min: 0, max: None)') self._test_validate(image, error_msg) def test_miss_disk_format(self): @@ -135,7 +137,9 @@ class GlanceImageTest(common.HeatTestCase): ) image = stack['image'] image.t['Properties']['disk_format'] = 'incorrect_format' - error_msg = ('disk_format "incorrect_format" is not an allowed value ' + error_msg = ('Property error : ' + 'resources.image.properties.disk_format: ' + '"incorrect_format" is not an allowed value ' '[ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, iso]') self._test_validate(image, error_msg) @@ -160,8 +164,10 @@ class GlanceImageTest(common.HeatTestCase): ) image = stack['image'] image.t['Properties']['container_format'] = 'incorrect_format' - error_msg = ('container_format "incorrect_format" is not an ' - 'allowed value [ami, ari, aki, bare, ova, ovf]') + error_msg = ('Property error : ' + 'resources.image.properties.container_format: ' + '"incorrect_format" is not an allowed value ' + '[ami, ari, aki, bare, ova, ovf]') self._test_validate(image, error_msg) def test_miss_location(self): diff --git a/heat/tests/test_instance.py b/heat/tests/test_instance.py index 6ceaaa1e9f..15107751cd 100644 --- a/heat/tests/test_instance.py +++ b/heat/tests/test_instance.py @@ -227,8 +227,9 @@ class InstancesTest(common.HeatTestCase): exc = self.assertRaises(exception.StackValidationFailed, instance.validate) - self.assertIn("VolumeId Error validating value '1234': " - "The Volume (1234) could not be found.", + self.assertIn("WebServer.Properties.Volumes[0].VolumeId: " + "Error validating value '1234': The Volume " + "(1234) could not be found.", six.text_type(exc)) self.m.VerifyAll() @@ -322,9 +323,10 @@ class InstancesTest(common.HeatTestCase): exc = self.assertRaises(exception.StackValidationFailed, instance.validate) - excepted_error = ('Property error : WebServer: BlockDeviceMappings ' - 'Property error : BlockDeviceMappings: 0 Property ' - 'error : 0: Property DeviceName not assigned') + excepted_error = ( + 'Property error : ' + 'Resources.WebServer.Properties.BlockDeviceMappings[0]: ' + 'Property DeviceName not assigned') self.assertIn(excepted_error, six.text_type(exc)) self.m.VerifyAll() @@ -382,9 +384,9 @@ class InstancesTest(common.HeatTestCase): create = scheduler.TaskRunner(instance.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - "StackValidationFailed: Property error : WebServer: " - "ImageId Error validating value 'Slackware': " - "The Image (Slackware) could not be found.", + "StackValidationFailed: Property error : " + "WebServer.Properties.ImageId: Error validating value " + "'Slackware': The Image (Slackware) could not be found.", six.text_type(error)) self.m.VerifyAll() @@ -409,9 +411,9 @@ class InstancesTest(common.HeatTestCase): create = scheduler.TaskRunner(instance.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - 'StackValidationFailed: Property error : WebServer: ' - 'ImageId Multiple physical resources were ' - 'found with name (CentOS 5.2).', + 'StackValidationFailed: Property error : ' + 'WebServer.Properties.ImageId: Multiple physical ' + 'resources were found with name (CentOS 5.2).', six.text_type(error)) self.m.VerifyAll() @@ -433,8 +435,8 @@ class InstancesTest(common.HeatTestCase): create = scheduler.TaskRunner(instance.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - 'StackValidationFailed: Property error : WebServer: ' - 'ImageId 404 (HTTP 404)', + 'StackValidationFailed: Property error : ' + 'WebServer.Properties.ImageId: 404 (HTTP 404)', six.text_type(error)) self.m.VerifyAll() diff --git a/heat/tests/test_nova_keypair.py b/heat/tests/test_nova_keypair.py index e68067c031..c0a1bacfa4 100644 --- a/heat/tests/test_nova_keypair.py +++ b/heat/tests/test_nova_keypair.py @@ -103,11 +103,11 @@ class NovaKeyPairTest(common.HeatTestCase): definition = stack.t.resource_definitions(stack)['kp'] kp_res = nova_keypair.KeyPair('kp', definition, stack) self.m.ReplayAll() - create = scheduler.TaskRunner(kp_res.create) - error = self.assertRaises(exception.ResourceFailure, create) + error = self.assertRaises(exception.StackValidationFailed, + kp_res.validate) self.assertIn("Property error", six.text_type(error)) - self.assertIn("name length (0) is out of range (min: 1, max: 255)", - six.text_type(error)) + self.assertIn("kp.properties.name: length (0) is out of " + "range (min: 1, max: 255)", six.text_type(error)) self.m.VerifyAll() def test_create_key_excess_name_length(self): @@ -119,11 +119,11 @@ class NovaKeyPairTest(common.HeatTestCase): definition = stack.t.resource_definitions(stack)['kp'] kp_res = nova_keypair.KeyPair('kp', definition, stack) self.m.ReplayAll() - create = scheduler.TaskRunner(kp_res.create) - error = self.assertRaises(exception.ResourceFailure, create) + error = self.assertRaises(exception.StackValidationFailed, + kp_res.validate) self.assertIn("Property error", six.text_type(error)) - self.assertIn("name length (256) is out of range (min: 1, max: 255)", - six.text_type(error)) + self.assertIn("kp.properties.name: length (256) is out of " + "range (min: 1, max: 255)", six.text_type(error)) self.m.VerifyAll() def test_delete_key(self): diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index b90b360b0b..5eb7ec92f2 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -882,8 +882,8 @@ class PropertyTest(common.HeatTestCase): p = properties.Property({'Type': 'Map', 'Schema': map_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, {'valid': 'fish'}, True) - self.assertEqual('Property error : valid "fish" is not ' - 'a valid boolean', six.text_type(ex)) + self.assertEqual('Property error : valid: "fish" is not a ' + 'valid boolean', six.text_type(ex)) def test_map_schema_missing_data(self): map_schema = {'valid': {'Type': 'Boolean'}} @@ -914,8 +914,8 @@ class PropertyTest(common.HeatTestCase): ex = self.assertRaises(exception.StackValidationFailed, p.get_value, [{'valid': 'True'}, {'valid': 'fish'}], True) - self.assertEqual('Property error : 1 Property error : 1: valid ' - '"fish" is not a valid boolean', six.text_type(ex)) + self.assertEqual('Property error : [1].valid: "fish" is not ' + 'a valid boolean', six.text_type(ex)) def test_list_schema_int_good(self): list_schema = {'Type': 'Integer'} @@ -927,7 +927,7 @@ class PropertyTest(common.HeatTestCase): p = properties.Property({'Type': 'List', 'Schema': list_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, [42, 'fish'], True) - self.assertEqual("Property error : 1 Value 'fish' is not " + self.assertEqual("Property error : [1]: Value 'fish' is not " "an integer", six.text_type(ex)) @@ -1056,16 +1056,6 @@ class PropertiesTest(common.HeatTestCase): props = properties.Properties(schema, {'foo': None}) self.assertEqual(['one', 'two'], props['foo']) - def test_bad_resolver(self): - schema = {'foo': {'Type': 'String', 'Default': 'bar'}} - - def bad_resolver(prop): - raise Exception('resolution failed!') - - props = properties.Properties(schema, {'foo': 'baz'}, bad_resolver) - err = self.assertRaises(ValueError, props.get, 'foo') - self.assertEqual('foo resolution failed!', six.text_type(err)) - def test_resolve_returns_none(self): schema = {'foo': {'Type': 'String', "MinLength": "5"}} @@ -1543,14 +1533,14 @@ class PropertiesValidationTest(common.HeatTestCase): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': ['foo', 'bar']}) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Value must be a string', + self.assertEqual('Property error : foo: Value must be a string', six.text_type(ex)) def test_dict_instead_string(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': {'foo': 'bar'}}) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Value must be a string', + self.assertEqual('Property error : foo: Value must be a string', six.text_type(ex)) def test_none_string(self): @@ -1717,8 +1707,8 @@ class PropertiesValidationTest(common.HeatTestCase): props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Property error : foo: 0 ' - 'Unknown Property bar', six.text_type(ex)) + self.assertEqual('Property error : foo[0]: Unknown Property bar', + six.text_type(ex)) def test_nested_properties_schema_invalid_property_in_map(self): child_schema = {'Key': {'Type': 'String', @@ -1736,8 +1726,8 @@ class PropertiesValidationTest(common.HeatTestCase): props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Property error : foo: boo ' - 'Unknown Property bar', six.text_type(ex)) + self.assertEqual('Property error : foo.boo: Unknown Property bar', + six.text_type(ex)) def test_more_nested_properties_schema_invalid_property_in_list(self): nested_child_schema = {'Key': {'Type': 'String', @@ -1754,8 +1744,7 @@ class PropertiesValidationTest(common.HeatTestCase): props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Property error : foo: 0 ' - 'Property error : 0: doo Unknown Property bar', + self.assertEqual('Property error : foo[0].doo: Unknown Property bar', six.text_type(ex)) def test_more_nested_properties_schema_invalid_property_in_map(self): @@ -1773,8 +1762,7 @@ class PropertiesValidationTest(common.HeatTestCase): props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) - self.assertEqual('Property error : foo Property error : foo: boo ' - 'Property error : boo: doo Unknown Property bar', + self.assertEqual('Property error : foo.boo.doo: Unknown Property bar', six.text_type(ex)) def test_schema_to_template_empty_schema(self): diff --git a/heat/tests/test_random_string.py b/heat/tests/test_random_string.py index 6453d4b2bd..cec5b497ec 100644 --- a/heat/tests/test_random_string.py +++ b/heat/tests/test_random_string.py @@ -223,7 +223,7 @@ Resources: ''' exc = self.assertRaises(exception.StackValidationFailed, self.create_stack, template_random_string) - self.assertIn('length 513 is out of range (min: 1, max: 512)', + self.assertIn('513 is out of range (min: 1, max: 512)', six.text_type(exc)) diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 9c6b4b5881..edf2fe5674 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -547,7 +547,8 @@ class ResourceTest(common.HeatTestCase): tmpl = rsrc_defn.ResourceDefinition(rname, 'Foo', {}) res = generic_rsrc.ResourceWithRequiredProps(rname, tmpl, self.stack) - estr = 'Property error : test_resource: Property Foo not assigned' + estr = ('Property error : test_resource.Properties: ' + 'Property Foo not assigned') create = scheduler.TaskRunner(res.create) err = self.assertRaises(exception.ResourceFailure, create) self.assertIn(estr, six.text_type(err)) @@ -559,7 +560,8 @@ class ResourceTest(common.HeatTestCase): {'Food': 'abc'}) res = generic_rsrc.ResourceWithProps(rname, tmpl, self.stack) - estr = 'StackValidationFailed: Unknown Property Food' + estr = ('StackValidationFailed: Property error : ' + 'test_resource.Properties: Unknown Property Food') create = scheduler.TaskRunner(res.create) err = self.assertRaises(exception.ResourceFailure, create) self.assertIn(estr, six.text_type(err)) @@ -1506,8 +1508,9 @@ class ResourceDependenciesTest(common.HeatTestCase): stack = parser.Stack(utils.dummy_context(), 'test', tmpl) ex = self.assertRaises(exception.StackValidationFailed, stack.validate) - expected = "FooInt Value 'notanint' is not an integer" - self.assertIn(expected, six.text_type(ex)) + self.assertIn("Property error : resources.bar.properties.FooInt: " + "Value 'notanint' is not an integer", + six.text_type(ex)) # You can turn off value validation via strict_validate stack_novalidate = parser.Stack(utils.dummy_context(), 'test', tmpl, diff --git a/heat/tests/test_resource_group.py b/heat/tests/test_resource_group.py index a7df157f2d..e7c23da642 100644 --- a/heat/tests/test_resource_group.py +++ b/heat/tests/test_resource_group.py @@ -328,7 +328,7 @@ class ResourceGroupTest(common.HeatTestCase): resg = resource_group.ResourceGroup('test', snip, stack) exc = self.assertRaises(exception.StackValidationFailed, resg.validate) - errstr = "removal_policies \"'notallowed'\" is not a list" + errstr = "removal_policies: \"'notallowed'\" is not a list" self.assertIn(errstr, six.text_type(exc)) def test_invalid_removal_policies_nomap(self): diff --git a/heat/tests/test_sahara_templates.py b/heat/tests/test_sahara_templates.py index caef86d99d..f934beae09 100644 --- a/heat/tests/test_sahara_templates.py +++ b/heat/tests/test_sahara_templates.py @@ -188,8 +188,10 @@ class SaharaNodeGroupTemplateTest(common.HeatTestCase): self.patchobject(ngt, 'is_using_neutron').return_value = False ex = self.assertRaises(exception.StackValidationFailed, ngt.validate) - self.assertEqual(u"Property error : node-group: flavor Error " - u"validating value u'm1.large'", six.text_type(ex)) + self.assertEqual(u"Property error : " + u"resources.node-group.properties.flavor: " + u"Error validating value u'm1.large'", + six.text_type(ex)) class SaharaClusterTemplateTest(common.HeatTestCase): diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py index d30bcaf15a..84b780f159 100644 --- a/heat/tests/test_server.py +++ b/heat/tests/test_server.py @@ -425,8 +425,8 @@ class ServersTest(common.HeatTestCase): create = scheduler.TaskRunner(server.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - "StackValidationFailed: Property error : WebServer: " - "image Error validating value 'Slackware': " + "StackValidationFailed: Property error : " + "WebServer.Properties.image: Error validating value 'Slackware': " "The Image (Slackware) could not be found.", six.text_type(error)) @@ -450,9 +450,9 @@ class ServersTest(common.HeatTestCase): create = scheduler.TaskRunner(server.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - 'StackValidationFailed: Property error : WebServer: ' - 'image Multiple physical resources were ' - 'found with name (CentOS 5.2).', + 'StackValidationFailed: Property error : ' + 'WebServer.Properties.image: Multiple physical ' + 'resources were found with name (CentOS 5.2).', six.text_type(error)) self.m.VerifyAll() @@ -474,8 +474,8 @@ class ServersTest(common.HeatTestCase): create = scheduler.TaskRunner(server.create) error = self.assertRaises(exception.ResourceFailure, create) self.assertEqual( - "StackValidationFailed: Property error : WebServer: " - "image Error validating value '1': " + "StackValidationFailed: Property error : " + "WebServer.Properties.image: Error validating value '1': " "The Image (1) could not be found.", six.text_type(error)) @@ -1049,9 +1049,9 @@ class ServersTest(common.HeatTestCase): error = self.assertRaises(exception.StackValidationFailed, server.validate) self.assertEqual( - "Property error : WebServer: key_name Error validating " - "value 'test2': The Key (test2) could not be found.", - six.text_type(error)) + "Property error : Resources.WebServer.Properties.key_name: " + "Error validating value 'test2': The Key (test2) could not " + "be found.", six.text_type(error)) self.m.VerifyAll() def test_server_validate_with_networks(self): @@ -3020,8 +3020,9 @@ class ServersTest(common.HeatTestCase): # update updater = scheduler.TaskRunner(server.update, update_template) err = self.assertRaises(exception.ResourceFailure, updater) - self.assertEqual('StackValidationFailed: Property error : WebServer: ' - 'image The Image (Update Image) could not be found.', + self.assertEqual('StackValidationFailed: Property error : ' + 'WebServer.Properties.image: The Image ' + '(Update Image) could not be found.', six.text_type(err)) self.m.VerifyAll() diff --git a/heat/tests/test_software_component.py b/heat/tests/test_software_component.py index 1337fc8262..7de4f1cca9 100644 --- a/heat/tests/test_software_component.py +++ b/heat/tests/test_software_component.py @@ -217,8 +217,8 @@ class SoftwareComponentValidationTest(common.HeatTestCase): tool: script ''', err=exc.StackValidationFailed, - err_msg='actions length (0) is out of range ' - '(min: 1, max: None)') + err_msg='component.properties.configs[0].actions: ' + 'length (0) is out of range (min: 1, max: None)') ), ( 'multiple_configs_per_action_single', diff --git a/heat/tests/test_template.py b/heat/tests/test_template.py index ec77f052a5..78cfcc7f6b 100644 --- a/heat/tests/test_template.py +++ b/heat/tests/test_template.py @@ -124,6 +124,9 @@ class TestTemplatePluginManager(common.HeatTestCase): def param_schemata(self): pass + def get_section_name(self, section): + pass + def parameters(self, stack_identifier, user_params): pass diff --git a/heat/tests/test_trove_cluster.py b/heat/tests/test_trove_cluster.py index 0ca3696dc6..b51124e7ee 100644 --- a/heat/tests/test_trove_cluster.py +++ b/heat/tests/test_trove_cluster.py @@ -160,8 +160,8 @@ class TroveClusterTest(common.HeatTestCase): self.rsrc_defn['Properties']['instances'][0]['flavor'] = 'm1.small' tc = trove_cluster.TroveCluster('cluster', self.rsrc_defn, self.stack) ex = self.assertRaises(exception.StackValidationFailed, tc.validate) - error_msg = ("Property error : cluster: instances Property error : " - "instances: 0 Property error : 0: flavor " + error_msg = ("Property error : " + "resources.cluster.properties.instances[0].flavor: " "Error validating value 'm1.small': " "The Flavor ID (m1.small) could not be found.") self.assertEqual(error_msg, six.text_type(ex)) diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index de7ca3b7b6..bf3ea7c8cc 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -1035,7 +1035,8 @@ class validateTest(common.HeatTestCase): t = template_format.parse(test_template_invalid_property) engine = service.EngineService('a', 't') res = dict(engine.validate_template(None, t, {})) - self.assertEqual({'Error': 'Unknown Property UnknownProperty'}, res) + self.assertEqual({'Error': 'Property error : WikiDatabase.Properties: ' + 'Unknown Property UnknownProperty'}, res) def test_invalid_resources(self): t = template_format.parse(test_template_invalid_resources) @@ -1084,7 +1085,8 @@ class validateTest(common.HeatTestCase): engine = service.EngineService('a', 't') res = dict(engine.validate_template(None, t, {})) self.assertEqual( - {'Error': 'Property SourceDestCheck not implemented yet'}, + {'Error': 'Property error : WikiDatabase.Properties: ' + 'Property SourceDestCheck not implemented yet'}, res) def test_invalid_deletion_policy(self): diff --git a/heat_integrationtests/functional/test_resource_group.py b/heat_integrationtests/functional/test_resource_group.py index 7da20a1d29..f6157fc33f 100644 --- a/heat_integrationtests/functional/test_resource_group.py +++ b/heat_integrationtests/functional/test_resource_group.py @@ -98,7 +98,7 @@ resources: # Prove validation works for non-zero create/update template_two_nested = self.template.replace("count: 0", "count: 2") - expected_err = "length Value 'BAD' is not an integer" + expected_err = "Value 'BAD' is not an integer" ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack, stack_identifier, template_two_nested, environment=env, files=files)