From 3ad4614276ff460cd8d034216c078577f148160e Mon Sep 17 00:00:00 2001 From: Peter Razumovsky Date: Wed, 8 Oct 2014 19:19:13 +0400 Subject: [PATCH] Improve StackValidationFailed response in properties In some cases, there is no information about resource and section, where Property error raised. This patch improves StackValidationFailed msg, so this msg look like "Property error : resource_name.section_name.key_name: error_msg", where section_name is section, where Property error raised, e.g. 'update_policy'. Change-Id: Iab2a6acdec254b39983de420ab03f994cff48d89 Closes-bug: #1358512 --- heat/engine/cfn/template.py | 3 + heat/engine/hot/template.py | 19 ++-- heat/engine/properties.py | 96 ++++++++++++------- heat/engine/resource.py | 13 ++- heat/engine/rsrc_defn.py | 6 +- heat/engine/service.py | 11 ++- heat/engine/template.py | 5 + heat/tests/autoscaling/test_launch_config.py | 13 ++- heat/tests/autoscaling/test_scaling_group.py | 6 +- heat/tests/aws/test_volume.py | 8 +- heat/tests/openstack/test_volume.py | 4 +- heat/tests/test_ceilometer_alarm.py | 17 ++-- heat/tests/test_glance_image.py | 16 +++- heat/tests/test_instance.py | 28 +++--- heat/tests/test_nova_keypair.py | 16 ++-- heat/tests/test_properties.py | 38 +++----- heat/tests/test_random_string.py | 2 +- heat/tests/test_resource.py | 11 ++- heat/tests/test_resource_group.py | 2 +- heat/tests/test_sahara_templates.py | 6 +- heat/tests/test_server.py | 25 ++--- heat/tests/test_software_component.py | 4 +- heat/tests/test_template.py | 3 + heat/tests/test_trove_cluster.py | 4 +- heat/tests/test_validate.py | 6 +- .../functional/test_resource_group.py | 2 +- 26 files changed, 217 insertions(+), 147 deletions(-) diff --git a/heat/engine/cfn/template.py b/heat/engine/cfn/template.py index cbde6d744..84e2318f4 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 fdb558002..5d90f5dad 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 e72bae9af..559e80a9f 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 e1174dfa6..49e1d0672 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 80df6d7aa..24fd65381 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 dd352bcdf..ce39c3677 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -812,7 +812,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 @@ -820,9 +820,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 0fbe29f73..97fb074db 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 57f4b4778..be298eb09 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 c26e0d76a..81a052f22 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 5b77fc465..892c7f3f0 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 46e1b68f4..dc73acea1 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 fa1c4fba0..0490c32e9 100644 --- a/heat/tests/test_ceilometer_alarm.py +++ b/heat/tests/test_ceilometer_alarm.py @@ -376,8 +376,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) @@ -391,8 +391,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) @@ -405,7 +406,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', @@ -520,8 +522,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 05bf09a1a..5d94a167f 100644 --- a/heat/tests/test_glance_image.py +++ b/heat/tests/test_glance_image.py @@ -98,7 +98,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): @@ -110,7 +111,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): @@ -134,7 +136,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) @@ -159,8 +163,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 2617a5e5f..fe61b1535 100644 --- a/heat/tests/test_instance.py +++ b/heat/tests/test_instance.py @@ -226,8 +226,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() @@ -321,9 +322,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() @@ -381,9 +383,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() @@ -408,9 +410,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() @@ -432,8 +434,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 e68067c03..c0a1bacfa 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 b90b360b0..5eb7ec92f 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 436ea0521..3dc0d1853 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 5a4e51a76..e8f31690e 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 a7df157f2..e7c23da64 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 caef86d99..f934beae0 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 5dc4df865..78835e6b5 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 1337fc826..7de4f1cca 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 ec77f052a..78cfcc7f6 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 0ca3696dc..b51124e7e 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 20a21b136..56004c3ed 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -1034,7 +1034,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) @@ -1083,7 +1084,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 7da20a1d2..f6157fc33 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)