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
This commit is contained in:
parent
dd92a7b2bb
commit
3ad4614276
@ -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,
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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.'''
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user