Add resource_definitions check to Stack.validate

We should check resource attributes' types in validate method
for preventing stack creation with incorrect resources. So
this patch splits resource_definitions() methods on
validate_resource_definitions() and resource_definitions()
methods and add validate_resource_definitions() to Stack.validate
method.

Change-Id: I16b35fc105154d9391f64285e90c396537fe3bb7
Partial-bug: #1397969
This commit is contained in:
Peter Razumovsky 2014-12-10 17:30:15 +03:00
parent 69a3599767
commit 3f16eebd06
6 changed files with 178 additions and 115 deletions

View File

@ -15,6 +15,7 @@ import collections
import six
from heat.common import exception
from heat.common.i18n import _
from heat.engine.cfn import functions as cfn_funcs
from heat.engine import function
@ -87,72 +88,77 @@ class CfnTemplate(template.Template):
user_params=user_params,
param_defaults=param_defaults)
def validate_resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
allowed_keys = set(_RESOURCE_KEYS)
try:
for name, snippet in resources.items():
data = self.parse(stack, snippet)
if not self.validate_resource_key_type(RES_TYPE,
six.string_types,
'string',
allowed_keys,
name, data):
args = {'name': name, 'type_key': RES_TYPE}
msg = _('Resource %(name)s is missing '
'"%(type_key)s"') % args
raise KeyError(msg)
self.validate_resource_key_type(
RES_PROPERTIES,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
self.validate_resource_key_type(
RES_METADATA,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
self.validate_resource_key_type(
RES_DEPENDS_ON,
collections.Sequence,
'list or string', allowed_keys, name, data)
self.validate_resource_key_type(
RES_DELETION_POLICY,
six.string_types,
'string', allowed_keys, name, data)
self.validate_resource_key_type(
RES_UPDATE_POLICY,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
self.validate_resource_key_type(
RES_DESCRIPTION,
six.string_types,
'string', allowed_keys, name, data)
except TypeError as ex:
raise exception.StackValidationFailed(message=six.text_type(ex))
def resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
def rsrc_defn_item(name, snippet):
data = self.parse(stack, snippet)
def get_check_type(key, valid_types, typename, default=None):
if key in data:
field = data[key]
if not isinstance(field, valid_types):
args = {'name': name, 'key': key, 'typename': typename}
msg = _('Resource %(name)s %(key)s type '
'must be %(typename)s') % args
raise TypeError(msg)
return field
else:
return default
resource_type = get_check_type(RES_TYPE,
six.string_types,
'string')
if resource_type is None:
args = {'name': name, 'type_key': RES_TYPE}
msg = _('Resource %(name)s is missing "%(type_key)s"') % args
raise KeyError(msg)
properties = get_check_type(RES_PROPERTIES,
(collections.Mapping,
function.Function),
'object')
metadata = get_check_type(RES_METADATA,
(collections.Mapping,
function.Function),
'object')
depends = get_check_type(RES_DEPENDS_ON,
collections.Sequence,
'list or string',
default=[])
if isinstance(depends, six.string_types):
depends = data.get(RES_DEPENDS_ON)
if not depends:
depends = []
elif isinstance(depends, six.string_types):
depends = [depends]
deletion_policy = get_check_type(RES_DELETION_POLICY,
six.string_types,
'string')
kwargs = {
'resource_type': data.get(RES_TYPE),
'properties': data.get(RES_PROPERTIES),
'metadata': data.get(RES_METADATA),
'depends': depends,
'deletion_policy': data.get(RES_DELETION_POLICY),
'update_policy': data.get(RES_UPDATE_POLICY),
'description': data.get(RES_DESCRIPTION) or ''
}
update_policy = get_check_type(RES_UPDATE_POLICY,
(collections.Mapping,
function.Function),
'object')
description = get_check_type(RES_DESCRIPTION,
six.string_types,
'string',
default='')
defn = rsrc_defn.ResourceDefinition(
name, resource_type,
properties=properties,
metadata=metadata,
depends=depends,
deletion_policy=deletion_policy,
update_policy=update_policy,
description=description)
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
return name, defn
resources = self.t.get(self.RESOURCES) or {}
return dict(rsrc_defn_item(name, data)
for name, data in resources.items())

View File

@ -191,74 +191,72 @@ class HOTemplate20130523(template.Template):
user_params=user_params,
param_defaults=param_defaults)
def resource_definitions(self, stack):
def validate_resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
allowed_keys = set(_RESOURCE_KEYS)
try:
for name, snippet in resources.items():
data = self.parse(stack, snippet)
if not self.validate_resource_key_type(RES_TYPE,
six.string_types,
'string',
allowed_keys,
name, data):
args = {'name': name, 'type_key': RES_TYPE}
msg = _('Resource %(name)s is missing '
'"%(type_key)s"') % args
raise KeyError(msg)
self.validate_resource_key_type(
RES_PROPERTIES,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
self.validate_resource_key_type(
RES_METADATA,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
self.validate_resource_key_type(
RES_DEPENDS_ON,
collections.Sequence,
'list or string', allowed_keys, name, data)
self.validate_resource_key_type(
RES_DELETION_POLICY,
six.string_types,
'string', allowed_keys, name, data)
self.validate_resource_key_type(
RES_UPDATE_POLICY,
(collections.Mapping, function.Function),
'object', allowed_keys, name, data)
except (TypeError, ValueError) as ex:
raise exception.StackValidationFailed(message=six.text_type(ex))
def resource_definitions(self, stack):
resources = self.t.get(self.RESOURCES) or {}
def rsrc_defn_item(name, snippet):
data = self.parse(stack, snippet)
def get_check_type(key, valid_types, typename, default=None):
if key in data:
field = data[key]
if not isinstance(field, valid_types):
args = {'name': name, 'key': key, 'typename': typename}
msg = _('Resource %(name)s %(key)s type '
'must be %(typename)s') % args
raise TypeError(msg)
return field
else:
return default
resource_type = get_check_type(RES_TYPE,
six.string_types,
'string')
if resource_type is None:
args = {'name': name, 'type_key': RES_TYPE}
msg = _('Resource %(name)s is missing "%(type_key)s"') % args
raise KeyError(msg)
properties = get_check_type(RES_PROPERTIES,
(collections.Mapping,
function.Function),
'object')
metadata = get_check_type(RES_METADATA,
(collections.Mapping,
function.Function),
'object')
depends = get_check_type(RES_DEPENDS_ON,
collections.Sequence,
'list or string',
default=[])
if isinstance(depends, six.string_types):
depends = data.get(RES_DEPENDS_ON)
if not depends:
depends = []
elif isinstance(depends, six.string_types):
depends = [depends]
deletion_policy = get_check_type(RES_DELETION_POLICY,
six.string_types,
'string')
kwargs = {
'resource_type': data.get(RES_TYPE),
'properties': data.get(RES_PROPERTIES),
'metadata': data.get(RES_METADATA),
'depends': depends,
'deletion_policy': data.get(RES_DELETION_POLICY),
'update_policy': data.get(RES_UPDATE_POLICY),
'description': None
}
update_policy = get_check_type(RES_UPDATE_POLICY,
(collections.Mapping,
function.Function),
'object')
for key in data:
if key not in allowed_keys:
raise ValueError(_('"%s" is not a valid keyword '
'inside a resource definition') % key)
defn = rsrc_defn.ResourceDefinition(
name, resource_type,
properties=properties,
metadata=metadata,
depends=depends,
deletion_policy=deletion_policy,
update_policy=update_policy,
description=None)
defn = rsrc_defn.ResourceDefinition(name, **kwargs)
return name, defn
resources = self.t.get(self.RESOURCES) or {}
return dict(rsrc_defn_item(name, data)
for name, data in resources.items())

View File

@ -469,6 +469,9 @@ class Stack(collections.Mapping):
parameter_groups = param_groups.ParameterGroups(self.t)
parameter_groups.validate()
# Validate types of sections in ResourceDefinitions
self.t.validate_resource_definitions(self)
# Check duplicate names between parameters and resources
dup_names = set(self.parameters.keys()) & set(self.keys())

View File

@ -158,6 +158,34 @@ class Template(collections.Mapping):
'''Return a parameters.Parameters object for the stack.'''
pass
@classmethod
def validate_resource_key_type(cls, key, valid_types, typename,
allowed_keys, rsrc_name, rsrc_data):
"""Validation type of the specific resource key.
Used in validate_resource_definition and check correctness of
key's type.
"""
if key not in allowed_keys:
raise ValueError(_('"%s" is not a valid '
'keyword inside a resource '
'definition') % key)
if key in rsrc_data:
if not isinstance(rsrc_data.get(key), valid_types):
args = {'name': rsrc_name, 'key': key,
'typename': typename}
message = _('Resource %(name)s %(key)s type '
'must be %(typename)s') % args
raise TypeError(message)
return True
else:
return False
@abc.abstractmethod
def validate_resource_definitions(self, stack):
"""Check section's type of ResourceDefinitions."""
pass
@abc.abstractmethod
def resource_definitions(self, stack):
'''Return a dictionary of ResourceDefinition objects.'''

View File

@ -59,6 +59,9 @@ class TestTemplatePluginManager(common.HeatTestCase):
def parameters(self, stack_identifier, user_params):
pass
def validate_resource_definitions(self, stack):
pass
def resource_definitions(self, stack):
pass

View File

@ -1468,3 +1468,28 @@ class validateTest(common.HeatTestCase):
error_message = ('Arguments to "get_attr" must be of the form '
'[resource_name, attribute, (path), ...]')
self.assertEqual(error_message, six.text_type(err))
def test_validate_resource_attr_invalid_type(self):
t = template_format.parse("""
heat_template_version: 2013-05-23
resources:
resource:
type: 123
""")
template = parser.Template(t)
stack = parser.Stack(self.ctx, 'test_stack', template)
ex = self.assertRaises(exception.StackValidationFailed, stack.validate)
self.assertEqual('Resource resource type type must be string',
six.text_type(ex))
def test_validate_resource_attr_invalid_type_cfn(self):
t = template_format.parse("""
HeatTemplateFormatVersion: '2012-12-12'
Resources:
Resource:
Type: [Wrong, Type]
""")
stack = parser.Stack(self.ctx, 'test_stack', parser.Template(t))
ex = self.assertRaises(exception.StackValidationFailed, stack.validate)
self.assertEqual('Resource Resource Type type must be string',
six.text_type(ex))