Merge "Refactor Parameters Schema based on common Schema"

This commit is contained in:
Jenkins 2014-01-28 12:42:27 +00:00 committed by Gerrit Code Review
commit 3d2517b2c7
13 changed files with 402 additions and 431 deletions

View File

@ -15,7 +15,7 @@
from heat.rpc import api
from heat.openstack.common import timeutils
from heat.engine import template
from heat.engine import hot
from heat.engine import constraints as constr
from heat.openstack.common import log as logging
from heat.openstack.common.gettextutils import _
@ -236,35 +236,44 @@ def format_validate_parameter(param):
to be compatible to CFN syntax).
"""
# build basic param schema dictionary; exclude HOT constraints since they
# will be handled differently
res = dict((k, v)
for k, v in param.schema.iteritems() if k != hot.CONSTRAINTS)
# map of Schema object types to API expected types
schema_to_api_types = {
param.schema.STRING: api.PARAM_TYPE_STRING,
param.schema.NUMBER: api.PARAM_TYPE_NUMBER,
param.schema.LIST: api.PARAM_TYPE_COMMA_DELIMITED_LIST,
param.schema.MAP: api.PARAM_TYPE_JSON
}
if not isinstance(param.schema, hot.HOTParamSchema):
return res
res = {
api.PARAM_TYPE: schema_to_api_types.get(param.schema.type,
param.schema.type),
api.PARAM_DESCRIPTION: param.description(),
api.PARAM_NO_ECHO: 'true' if param.hidden() else 'false'
}
# build constraints - formating only necessary for HOT since API is
# currently CFN oriented
constraints = param.schema.get(hot.CONSTRAINTS, [])
for constraint in constraints:
if hot.RANGE in constraint:
const_def = constraint.get(hot.RANGE)
if const_def.get(hot.MIN):
res[api.PARAM_MIN_VALUE] = const_def.get(hot.MIN)
if const_def.get(hot.MAX):
res[api.PARAM_MAX_VALUE] = const_def.get(hot.MAX)
if hot.LENGTH in constraint:
const_def = constraint.get(hot.LENGTH)
if const_def.get(hot.MIN):
res[api.PARAM_MIN_LENGTH] = const_def.get(hot.MIN)
if const_def.get(hot.MAX):
res[api.PARAM_MAX_LENGTH] = const_def.get(hot.MAX)
if hot.ALLOWED_VALUES in constraint:
const_def = constraint.get(hot.ALLOWED_VALUES)
res[api.PARAM_ALLOWED_VALUES] = const_def
if hot.ALLOWED_PATTERN in constraint:
const_def = constraint.get(hot.ALLOWED_PATTERN)
res[api.PARAM_ALLOWED_PATTERN] = const_def
if param.has_default():
res[api.PARAM_DEFAULT] = param.default()
# build constraints
for c in param.schema.constraints:
if isinstance(c, constr.Length):
if c.min is not None:
res[api.PARAM_MIN_LENGTH] = c.min
if c.max is not None:
res[api.PARAM_MAX_LENGTH] = c.max
elif isinstance(c, constr.Range):
if c.min is not None:
res[api.PARAM_MIN_VALUE] = c.min
if c.max is not None:
res[api.PARAM_MAX_VALUE] = c.max
elif isinstance(c, constr.AllowedValues):
res[api.PARAM_ALLOWED_VALUES] = list(c.allowed)
elif isinstance(c, constr.AllowedPattern):
res[api.PARAM_ALLOWED_PATTERN] = c.pattern
return res

View File

@ -123,6 +123,10 @@ class Schema(collections.Mapping):
'%(default)s (%(exc)s)') %
dict(default=self.default, exc=exc))
def set_default(self, default=None):
"""Set the default value for this Schema object."""
self.default = default
def _is_valid_constraint(self, constraint):
valid_types = getattr(constraint, 'valid_types', [])
return any(self.type == getattr(self, t, None) for t in valid_types)
@ -334,7 +338,7 @@ class Length(Range):
}
"""
valid_types = (Schema.STRING_TYPE, Schema.LIST_TYPE,)
valid_types = (Schema.STRING_TYPE, Schema.LIST_TYPE, Schema.MAP_TYPE,)
def __init__(self, min=None, max=None, description=None):
super(Length, self).__init__(min, max, description)
@ -375,7 +379,7 @@ class AllowedValues(Constraint):
"""
valid_types = (Schema.STRING_TYPE, Schema.INTEGER_TYPE, Schema.NUMBER_TYPE,
Schema.BOOLEAN_TYPE,)
Schema.BOOLEAN_TYPE, Schema.LIST_TYPE,)
def __init__(self, allowed, description=None):
super(AllowedValues, self).__init__(description)
@ -393,6 +397,11 @@ class AllowedValues(Constraint):
return '"%s" is not an allowed value %s' % (value, allowed)
def _is_valid(self, value):
# For list values, check if all elements of the list are contained
# in allowed list.
if isinstance(value, list):
return all(v in self.allowed for v in value)
return value in self.allowed
def _constraint(self):

View File

@ -15,6 +15,7 @@
from heat.common import exception
from heat.engine import template
from heat.engine import parameters
from heat.engine import constraints as constr
from heat.openstack.common.gettextutils import _
from heat.openstack.common import log as logging
@ -74,9 +75,6 @@ class HOTemplate(template.Template):
# engine can cope with it.
# This is a shortcut for now and might be changed in the future.
if section == PARAMETERS:
return self._translate_parameters(the_section)
if section == RESOURCES:
return self._translate_resources(the_section)
@ -92,25 +90,6 @@ class HOTemplate(template.Template):
return default
def _translate_parameters(self, parameters):
"""Get the parameters of the template translated into CFN format."""
params = {}
for name, attrs in parameters.iteritems():
param = {}
for key, val in attrs.iteritems():
# Do not translate 'constraints' since we want to handle this
# specifically in HOT and not in common code.
if key != CONSTRAINTS:
key = snake_to_camel(key)
if key == 'Type':
val = snake_to_camel(val)
elif key == 'Hidden':
key = 'NoEcho'
param[key] = val
if len(param) > 0:
params[name] = param
return params
def _translate_resources(self, resources):
"""Get the resources of the template translated into CFN format."""
HOT_TO_CFN_ATTRS = {'type': 'Type',
@ -148,18 +127,18 @@ class HOTemplate(template.Template):
return cfn_outputs
@staticmethod
def resolve_param_refs(s, parameters, transform=None):
def resolve_param_refs(s, params, transform=None):
"""
Resolve constructs of the form { get_param: my_param }
"""
def match_param_ref(key, value):
return (key in ['get_param', 'Ref'] and
value is not None and
value in parameters)
value in params)
def handle_param_ref(ref):
try:
return parameters[ref]
return params[ref]
except (KeyError, ValueError):
raise exception.UserParameterMissing(key=ref)
@ -260,35 +239,63 @@ class HOTemplate(template.Template):
def param_schemata(self):
params = self[PARAMETERS].iteritems()
return dict((name, HOTParamSchema(schema)) for name, schema in params)
return dict((name, HOTParamSchema.from_dict(schema))
for name, schema in params)
class HOTParamSchema(parameters.ParamSchema):
class HOTParamSchema(parameters.Schema):
"""HOT parameter schema."""
def do_check(self, name, value, keys):
# map ParamSchema constraint type to keys used in HOT constraints
constraint_map = {
parameters.ALLOWED_PATTERN: [ALLOWED_PATTERN],
parameters.ALLOWED_VALUES: [ALLOWED_VALUES],
parameters.MIN_LENGTH: [LENGTH, MIN],
parameters.MAX_LENGTH: [LENGTH, MAX],
parameters.MIN_VALUE: [RANGE, MIN],
parameters.MAX_VALUE: [RANGE, MAX]
}
KEYS = (
TYPE, DESCRIPTION, DEFAULT, SCHEMA, CONSTRAINTS,
HIDDEN
) = (
'type', 'description', 'default', 'schema', 'constraints',
'hidden'
)
for const_type in keys:
# get constraint type specific check function
check = self.check(const_type)
# get constraint type specific keys in HOT
const_keys = constraint_map[const_type]
# For Parameters the type name for Schema.LIST is comma_delimited_list
# and the type name for Schema.MAP is json
TYPES = (
STRING, NUMBER, LIST, MAP,
) = (
'string', 'number', 'comma_delimited_list', 'json',
)
for constraint in self.get(CONSTRAINTS, []):
const_descr = constraint.get(DESCRIPTION)
@classmethod
def from_dict(cls, schema_dict):
"""
Return a Parameter Schema object from a legacy schema dictionary.
"""
for const_key in const_keys:
if const_key not in constraint:
break
constraint = constraint[const_key]
else:
check(name, value, constraint, const_descr)
def constraints():
constraints = schema_dict.get(CONSTRAINTS)
if constraints is None:
return
for constraint in constraints:
desc = constraint.get(DESCRIPTION)
if RANGE in constraint:
cdef = constraint.get(RANGE)
yield constr.Range(parameters.Schema.get_num(MIN, cdef),
parameters.Schema.get_num(MAX, cdef),
desc)
if LENGTH in constraint:
cdef = constraint.get(LENGTH)
yield constr.Length(parameters.Schema.get_num(MIN, cdef),
parameters.Schema.get_num(MAX, cdef),
desc)
if ALLOWED_VALUES in constraint:
cdef = constraint.get(ALLOWED_VALUES)
yield constr.AllowedValues(cdef, desc)
if ALLOWED_PATTERN in constraint:
cdef = constraint.get(ALLOWED_PATTERN)
yield constr.AllowedPattern(cdef, desc)
# make update_allowed true by default on TemplateResources
# as the template should deal with this.
return cls(schema_dict[cls.TYPE],
description=schema_dict.get(HOTParamSchema.DESCRIPTION),
default=schema_dict.get(HOTParamSchema.DEFAULT),
constraints=list(constraints()),
hidden=schema_dict.get(HOTParamSchema.HIDDEN, False))

View File

@ -15,7 +15,7 @@
import collections
import json
import re
from heat.engine import constraints as constr
from heat.common import exception
@ -37,103 +37,109 @@ PSEUDO_PARAMETERS = (
)
class ParamSchema(dict):
class Schema(constr.Schema):
'''Parameter schema.'''
KEYS = (
TYPE, DESCRIPTION, DEFAULT, SCHEMA, CONSTRAINTS, HIDDEN
) = (
'Type', 'Description', 'Default', 'Schema', 'Constraints', 'NoEcho'
)
# For Parameters the type name for Schema.LIST is CommaDelimitedList
# and the type name for Schema.MAP is Json
TYPES = (
STRING, NUMBER, COMMA_DELIMITED_LIST, JSON,
STRING, NUMBER, LIST, MAP,
) = (
'String', 'Number', 'CommaDelimitedList', 'Json',
)
def __init__(self, schema):
super(ParamSchema, self).__init__(schema)
def __init__(self, data_type, description=None, default=None, schema=None,
constraints=[], hidden=False):
super(Schema, self).__init__(data_type=data_type,
description=description,
default=default,
schema=schema,
required=default is None,
constraints=constraints)
self.hidden = hidden
def do_check(self, name, value, keys):
for k in keys:
check = self.check(k)
const = self.get(k)
if check is None or const is None:
continue
check(name, value, const)
# Schema class validates default value for lists assuming list type. For
# comma delimited list string supported in paramaters Schema class, the
# default value has to be parsed into a list if necessary so that
# validation works.
def _validate_default(self):
if self.default is not None:
default_value = self.default
if self.type == self.LIST and not isinstance(self.default, list):
try:
default_value = self.default.split(',')
except (KeyError, AttributeError) as err:
raise constr.InvalidSchemaError(_('Default must be a '
'comma-delimited list '
'string: %s') % str(err))
try:
self.validate_constraints(default_value)
except (ValueError, TypeError) as exc:
raise constr.InvalidSchemaError(_('Invalid default '
'%(default)s (%(exc)s)') %
dict(default=self.default,
exc=exc))
def constraints(self):
ptype = self[TYPE]
keys = {
self.STRING: [ALLOWED_VALUES, ALLOWED_PATTERN, MAX_LENGTH,
MIN_LENGTH],
self.NUMBER: [ALLOWED_VALUES, MAX_VALUE, MIN_VALUE],
self.JSON: [MAX_LENGTH, MIN_LENGTH]
}.get(ptype)
list_keys = {
self.COMMA_DELIMITED_LIST: [ALLOWED_VALUES],
self.JSON: [ALLOWED_VALUES]
}.get(ptype)
return (keys, list_keys)
def set_default(self, default=None):
super(Schema, self).set_default(default)
self.required = default is None
@staticmethod
def get_num(key, context):
val = context.get(key)
if val is not None:
val = Schema.str_to_num(val)
return val
@classmethod
def from_dict(cls, schema_dict):
"""
Return a Parameter Schema object from a legacy schema dictionary.
"""
def constraints():
desc = schema_dict.get(CONSTRAINT_DESCRIPTION)
if MIN_VALUE in schema_dict or MAX_VALUE in schema_dict:
yield constr.Range(Schema.get_num(MIN_VALUE, schema_dict),
Schema.get_num(MAX_VALUE, schema_dict),
desc)
if MIN_LENGTH in schema_dict or MAX_LENGTH in schema_dict:
yield constr.Length(Schema.get_num(MIN_LENGTH, schema_dict),
Schema.get_num(MAX_LENGTH, schema_dict),
desc)
if ALLOWED_VALUES in schema_dict:
yield constr.AllowedValues(schema_dict[ALLOWED_VALUES], desc)
if ALLOWED_PATTERN in schema_dict:
yield constr.AllowedPattern(schema_dict[ALLOWED_PATTERN], desc)
# make update_allowed true by default on TemplateResources
# as the template should deal with this.
return cls(schema_dict[TYPE],
description=schema_dict.get(DESCRIPTION),
default=schema_dict.get(DEFAULT),
constraints=list(constraints()),
hidden=str(schema_dict.get(NO_ECHO,
'false')).lower() == 'true')
def validate(self, name, value):
(keys, list_keys) = self.constraints()
if keys:
self.do_check(name, value, keys)
if list_keys:
values = value
for value in values:
self.do_check(name, value, list_keys)
super(Schema, self).validate_constraints(value)
def raise_error(self, name, message, desc=True):
if desc:
message = self.get(CONSTRAINT_DESCRIPTION) or message
raise ValueError('%s %s' % (name, message))
def __getitem__(self, key):
if key == self.TYPE:
return self.type
if key == self.HIDDEN:
return self.hidden
else:
return super(Schema, self).__getitem__(key)
def check_allowed_values(self, name, val, const, desc=None):
vals = list(const)
if val not in vals:
err = '"%s" not in %s "%s"' % (val, ALLOWED_VALUES, vals)
self.raise_error(name, desc or err)
def check_allowed_pattern(self, name, val, p, desc=None):
m = re.match(p, val)
if m is None or m.end() != len(val):
err = '"%s" does not match %s "%s"' % (val, ALLOWED_PATTERN, p)
self.raise_error(name, desc or err)
def check_max_length(self, name, val, const, desc=None):
max_len = int(const)
val_len = len(val)
if val_len > max_len:
err = 'length (%d) overflows %s (%d)' % (val_len,
MAX_LENGTH, max_len)
self.raise_error(name, desc or err)
def check_min_length(self, name, val, const, desc=None):
min_len = int(const)
val_len = len(val)
if val_len < min_len:
err = 'length (%d) underflows %s (%d)' % (val_len,
MIN_LENGTH, min_len)
self.raise_error(name, desc or err)
def check_max_value(self, name, val, const, desc=None):
max_val = float(const)
val = float(val)
if val > max_val:
err = '%d overflows %s %d' % (val, MAX_VALUE, max_val)
self.raise_error(name, desc or err)
def check_min_value(self, name, val, const, desc=None):
min_val = float(const)
val = float(val)
if val < min_val:
err = '%d underflows %s %d' % (val, MIN_VALUE, min_val)
self.raise_error(name, desc or err)
def check(self, const_key):
return {ALLOWED_VALUES: self.check_allowed_values,
ALLOWED_PATTERN: self.check_allowed_pattern,
MAX_LENGTH: self.check_max_length,
MIN_LENGTH: self.check_min_length,
MAX_VALUE: self.check_max_value,
MIN_VALUE: self.check_min_value}.get(const_key)
raise KeyError(key)
class Parameter(object):
@ -144,17 +150,20 @@ class Parameter(object):
if cls is not Parameter:
return super(Parameter, cls).__new__(cls)
param_type = schema[TYPE]
if param_type == schema.STRING:
# Check for fully-fledged Schema objects
if not isinstance(schema, Schema):
schema = Schema.from_dict(schema)
if schema.type == schema.STRING:
ParamClass = StringParam
elif param_type == schema.NUMBER:
elif schema.type == schema.NUMBER:
ParamClass = NumberParam
elif param_type == schema.COMMA_DELIMITED_LIST:
elif schema.type == schema.LIST:
ParamClass = CommaDelimitedListParam
elif param_type == schema.JSON:
elif schema.type == schema.MAP:
ParamClass = JsonParam
else:
raise ValueError(_('Invalid Parameter type "%s"') % param_type)
raise ValueError(_('Invalid Parameter type "%s"') % schema.type)
return ParamClass(name, schema, value, validate_value)
@ -185,29 +194,29 @@ class Parameter(object):
raise KeyError(_('Missing parameter %s') % self.name)
def no_echo(self):
def hidden(self):
'''
Return whether the parameter should be sanitised in any output to
the user.
'''
return str(self.schema.get(NO_ECHO, 'false')).lower() == 'true'
return self.schema.hidden
def description(self):
'''Return the description of the parameter.'''
return self.schema.get(DESCRIPTION, '')
return self.schema.description or ''
def has_default(self):
'''Return whether the parameter has a default value.'''
return DEFAULT in self.schema
return self.schema.default is not None
def default(self):
'''Return the default value of the parameter.'''
return self.schema.get(DEFAULT)
return self.schema.default
def __str__(self):
'''Return a string representation of the parameter'''
value = self.value()
if self.no_echo():
if self.hidden():
return '******'
else:
return str(value)
@ -250,6 +259,10 @@ class CommaDelimitedListParam(Parameter, collections.Sequence):
self.parsed = self.parse(self.user_value or self.default())
def parse(self, value):
# only parse when value is not already a list
if isinstance(value, list):
return value
try:
if value:
return value.split(',')
@ -324,25 +337,24 @@ class Parameters(collections.Mapping):
'''
def parameters():
yield Parameter(PARAM_STACK_ID,
ParamSchema({TYPE: ParamSchema.STRING,
DESCRIPTION: 'Stack ID',
DEFAULT: str(stack_id)}))
Schema(Schema.STRING, _('Stack ID'),
default=str(stack_id)))
if stack_name is not None:
yield Parameter(PARAM_STACK_NAME,
ParamSchema({TYPE: ParamSchema.STRING,
DESCRIPTION: 'Stack Name',
DEFAULT: stack_name}))
Schema(Schema.STRING, _('Stack Name'),
default=stack_name))
yield Parameter(PARAM_REGION,
ParamSchema({TYPE: ParamSchema.STRING,
DEFAULT: 'ap-southeast-1',
ALLOWED_VALUES:
['us-east-1',
'us-west-1',
'us-west-2',
'sa-east-1',
'eu-west-1',
'ap-southeast-1',
'ap-northeast-1']}))
Schema(Schema.STRING,
default='ap-southeast-1',
constraints=
[constr.AllowedValues(['us-east-1',
'us-west-1',
'us-west-2',
'sa-east-1',
'eu-west-1',
'ap-southeast-1',
'ap-northeast-1']
)]))
schemata = self.tmpl.param_schemata().iteritems()
for name, schema in schemata:
@ -382,7 +394,7 @@ class Parameters(collections.Mapping):
'''
Set the AWS::StackId pseudo parameter value
'''
self.params[PARAM_STACK_ID].schema[DEFAULT] = stack_id
self.params[PARAM_STACK_ID].schema.set_default(stack_id)
def _validate(self, user_params):
schemata = self.tmpl.param_schemata()

View File

@ -752,7 +752,7 @@ def resolve_static_data(template, stack, parameters, snippet):
'''
return transform(snippet,
[functools.partial(template.resolve_param_refs,
parameters=parameters),
params=parameters),
functools.partial(template.resolve_availability_zones,
stack=stack),
functools.partial(template.resolve_resource_facade,

View File

@ -18,7 +18,6 @@ import collections
from heat.common import exception
from heat.engine import parameters
from heat.engine import constraints as constr
from heat.engine import hot
SCHEMA_KEYS = (
REQUIRED, IMPLEMENTED, DEFAULT, TYPE, SCHEMA,
@ -101,7 +100,7 @@ class Schema(constr.Schema):
ss = dict((n, cls.from_legacy(sd)) for n, sd in schema_dicts)
else:
raise constr.InvalidSchemaError(_('%(schema)s supplied for '
' for %(type)s %(data)s') %
' %(type)s %(data)s') %
dict(schema=SCHEMA,
type=TYPE,
data=data_type))
@ -120,74 +119,26 @@ class Schema(constr.Schema):
@classmethod
def from_parameter(cls, param):
"""
Return a Property Schema corresponding to a parameter.
Return a Property Schema corresponding to a Parameter Schema.
Convert a parameter schema from a provider template to a property
Schema for the corresponding resource facade.
"""
# map param types to property types
param_type_map = {
parameters.ParamSchema.STRING: Schema.STRING,
parameters.ParamSchema.NUMBER: Schema.NUMBER,
parameters.ParamSchema.COMMA_DELIMITED_LIST: Schema.LIST,
parameters.ParamSchema.JSON: Schema.MAP
param.STRING: cls.STRING,
param.NUMBER: cls.NUMBER,
param.LIST: cls.LIST,
param.MAP: cls.MAP
}
def get_num(key, context=param):
val = context.get(key)
if val is not None:
val = Schema.str_to_num(val)
return val
def constraints():
desc = param.get(parameters.CONSTRAINT_DESCRIPTION)
if parameters.MIN_VALUE in param or parameters.MAX_VALUE in param:
yield constr.Range(get_num(parameters.MIN_VALUE),
get_num(parameters.MAX_VALUE))
if (parameters.MIN_LENGTH in param or
parameters.MAX_LENGTH in param):
yield constr.Length(get_num(parameters.MIN_LENGTH),
get_num(parameters.MAX_LENGTH))
if parameters.ALLOWED_VALUES in param:
yield constr.AllowedValues(param[parameters.ALLOWED_VALUES],
desc)
if parameters.ALLOWED_PATTERN in param:
yield constr.AllowedPattern(param[parameters.ALLOWED_PATTERN],
desc)
def constraints_hot():
constraints = param.get(hot.CONSTRAINTS)
if constraints is None:
return
for constraint in constraints:
desc = constraint.get(hot.DESCRIPTION)
if hot.RANGE in constraint:
const_def = constraint.get(hot.RANGE)
yield constr.Range(get_num(hot.MIN, const_def),
get_num(hot.MAX, const_def), desc)
if hot.LENGTH in constraint:
const_def = constraint.get(hot.LENGTH)
yield constr.Length(get_num(hot.MIN, const_def),
get_num(hot.MAX, const_def), desc)
if hot.ALLOWED_VALUES in constraint:
const_def = constraint.get(hot.ALLOWED_VALUES)
yield constr.AllowedValues(const_def, desc)
if hot.ALLOWED_PATTERN in constraint:
const_def = constraint.get(hot.ALLOWED_PATTERN)
yield constr.AllowedPattern(const_def, desc)
if isinstance(param, hot.HOTParamSchema):
constraint_list = list(constraints_hot())
else:
constraint_list = list(constraints())
# make update_allowed true by default on TemplateResources
# as the template should deal with this.
return cls(param_type_map.get(param[parameters.TYPE], Schema.MAP),
description=param.get(parameters.DESCRIPTION),
required=parameters.DEFAULT not in param,
constraints=constraint_list,
return cls(data_type=param_type_map.get(param.type, cls.MAP),
description=param.description,
required=param.required,
constraints=param.constraints,
update_allowed=True)
def __getitem__(self, key):
@ -395,12 +346,12 @@ class Properties(collections.Mapping):
Return a template parameter definition corresponding to a property.
"""
param_type_map = {
schema.INTEGER: parameters.ParamSchema.NUMBER,
schema.STRING: parameters.ParamSchema.STRING,
schema.NUMBER: parameters.ParamSchema.NUMBER,
schema.BOOLEAN: parameters.ParamSchema.STRING,
schema.MAP: parameters.ParamSchema.JSON,
schema.LIST: parameters.ParamSchema.COMMA_DELIMITED_LIST,
schema.INTEGER: parameters.Schema.NUMBER,
schema.STRING: parameters.Schema.STRING,
schema.NUMBER: parameters.Schema.NUMBER,
schema.BOOLEAN: parameters.Schema.STRING,
schema.MAP: parameters.Schema.MAP,
schema.LIST: parameters.Schema.LIST,
}
def param_items():

View File

@ -19,7 +19,7 @@ import json
from heat.api.aws import utils as aws_utils
from heat.db import api as db_api
from heat.common import exception
from heat.engine.parameters import ParamSchema
from heat.engine import parameters
SECTIONS = (VERSION, DESCRIPTION, MAPPINGS,
PARAMETERS, RESOURCES, OUTPUTS) = \
@ -121,18 +121,18 @@ class Template(collections.Mapping):
return _resolve(match_get_az, handle_get_az, s, transform)
@staticmethod
def resolve_param_refs(s, parameters, transform=None):
def resolve_param_refs(s, params, transform=None):
'''
Resolve constructs of the form { "Ref" : "string" }
'''
def match_param_ref(key, value):
return (key == 'Ref' and
isinstance(value, basestring) and
value in parameters)
value in params)
def handle_param_ref(ref):
try:
return parameters[ref]
return params[ref]
except (KeyError, ValueError):
raise exception.UserParameterMissing(key=ref)
@ -472,8 +472,9 @@ class Template(collections.Mapping):
s, transform)
def param_schemata(self):
parameters = self[PARAMETERS].iteritems()
return dict((name, ParamSchema(schema)) for name, schema in parameters)
params = self[PARAMETERS].iteritems()
return dict((name, parameters.Schema.from_dict(schema))
for name, schema in params)
def _resolve(match, handle, snippet, transform=None):

View File

@ -171,3 +171,11 @@ VALIDATE_PARAM_KEYS = (
'MinLength', 'MaxValue', 'MinValue',
'Description', 'ConstraintDescription'
)
VALIDATE_PARAM_TYPES = (
PARAM_TYPE_STRING, PARAM_TYPE_NUMBER, PARAM_TYPE_COMMA_DELIMITED_LIST,
PARAM_TYPE_JSON
) = (
'String', 'Number', 'CommaDelimitedList',
'Json'
)

View File

@ -216,7 +216,8 @@ class FormatValidateParameterTest(HeatTestCase):
''',
expected={
'Type': 'String',
'Description': 'Name of SSH key pair'
'Description': 'Name of SSH key pair',
'NoEcho': 'false'
})
),
('default',
@ -232,7 +233,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'Default': 'dummy'
'Default': 'dummy',
'NoEcho': 'false'
})
),
('min_length_constraint',
@ -248,7 +250,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'MinLength': 4
'MinLength': 4,
'NoEcho': 'false'
})
),
('max_length_constraint',
@ -264,7 +267,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'MaxLength': 10
'MaxLength': 10,
'NoEcho': 'false'
})
),
('min_max_length_constraint',
@ -282,7 +286,8 @@ class FormatValidateParameterTest(HeatTestCase):
'Type': 'String',
'Description': 'Name of SSH key pair',
'MinLength': 4,
'MaxLength': 10
'MaxLength': 10,
'NoEcho': 'false'
})
),
('min_value_constraint',
@ -298,7 +303,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'Number',
'Description': 'A number',
'MinValue': 4
'MinValue': 4,
'NoEcho': 'false'
})
),
('max_value_constraint',
@ -314,7 +320,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'Number',
'Description': 'A number',
'MaxValue': 10
'MaxValue': 10,
'NoEcho': 'false'
})
),
('min_max_value_constraint',
@ -332,7 +339,8 @@ class FormatValidateParameterTest(HeatTestCase):
'Type': 'Number',
'Description': 'A number',
'MinValue': 4,
'MaxValue': 10
'MaxValue': 10,
'NoEcho': 'false'
})
),
('allowed_values_constraint',
@ -348,7 +356,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'AllowedValues': ['foo', 'bar', 'blub']
'AllowedValues': ['foo', 'bar', 'blub'],
'NoEcho': 'false'
})
),
('allowed_pattern_constraint',
@ -364,7 +373,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'AllowedPattern': "[a-zA-Z0-9]+"
'AllowedPattern': "[a-zA-Z0-9]+",
'NoEcho': 'false'
})
),
('multiple_constraints',
@ -388,7 +398,8 @@ class FormatValidateParameterTest(HeatTestCase):
'MinLength': 4,
'MaxLength': 10,
'AllowedValues': ['foo', 'bar', 'blub'],
'AllowedPattern': "[a-zA-Z0-9]+"
'AllowedPattern': "[a-zA-Z0-9]+",
'NoEcho': 'false'
})
),
('simple_hot',
@ -403,6 +414,7 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'NoEcho': 'false'
})
),
('default_hot',
@ -418,7 +430,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'Default': 'dummy'
'Default': 'dummy',
'NoEcho': 'false'
})
),
('min_length_constraint_hot',
@ -436,7 +449,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'MinLength': 4
'MinLength': 4,
'NoEcho': 'false'
})
),
('max_length_constraint_hot',
@ -454,7 +468,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'MaxLength': 10
'MaxLength': 10,
'NoEcho': 'false'
})
),
('min_max_length_constraint_hot',
@ -473,7 +488,8 @@ class FormatValidateParameterTest(HeatTestCase):
'Type': 'String',
'Description': 'Name of SSH key pair',
'MinLength': 4,
'MaxLength': 10
'MaxLength': 10,
'NoEcho': 'false'
})
),
('min_value_constraint_hot',
@ -491,7 +507,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'Number',
'Description': 'A number',
'MinValue': 4
'MinValue': 4,
'NoEcho': 'false'
})
),
('max_value_constraint_hot',
@ -509,7 +526,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'Number',
'Description': 'A number',
'MaxValue': 10
'MaxValue': 10,
'NoEcho': 'false'
})
),
('min_max_value_constraint_hot',
@ -528,7 +546,8 @@ class FormatValidateParameterTest(HeatTestCase):
'Type': 'Number',
'Description': 'A number',
'MinValue': 4,
'MaxValue': 10
'MaxValue': 10,
'NoEcho': 'false'
})
),
('allowed_values_constraint_hot',
@ -549,7 +568,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'AllowedValues': ['foo', 'bar', 'blub']
'AllowedValues': ['foo', 'bar', 'blub'],
'NoEcho': 'false'
})
),
('allowed_pattern_constraint_hot',
@ -567,7 +587,8 @@ class FormatValidateParameterTest(HeatTestCase):
expected={
'Type': 'String',
'Description': 'Name of SSH key pair',
'AllowedPattern': "[a-zA-Z0-9]+"
'AllowedPattern': "[a-zA-Z0-9]+",
'NoEcho': 'false'
})
),
('multiple_constraints_hot',
@ -593,7 +614,8 @@ class FormatValidateParameterTest(HeatTestCase):
'MinLength': 4,
'MaxLength': 10,
'AllowedValues': ['foo', 'bar', 'blub'],
'AllowedPattern': "[a-zA-Z0-9]+"
'AllowedPattern': "[a-zA-Z0-9]+",
'NoEcho': 'false'
})
),
]

View File

@ -17,6 +17,7 @@ from heat.engine import parser
from heat.engine import hot
from heat.engine import parameters
from heat.engine import template
from heat.engine import constraints
from heat.tests.common import HeatTestCase
from heat.tests import test_parser
@ -47,67 +48,6 @@ class HOTemplateTest(HeatTestCase):
self.assertEqual({}, tmpl[hot.RESOURCES])
self.assertEqual({}, tmpl[hot.OUTPUTS])
def test_translate_parameters(self):
"""Test translation of parameters into internal engine format."""
hot_tpl = template_format.parse('''
heat_template_version: 2013-05-23
parameters:
param1:
description: foo
type: string
default: boo
''')
expected = {'param1': {'Description': 'foo',
'Type': 'String',
'Default': 'boo'}}
tmpl = parser.Template(hot_tpl)
self.assertEqual(expected, tmpl[hot.PARAMETERS])
def test_translate_parameters_unsupported_type(self):
"""Test translation of parameters into internal engine format
This tests if parameters with a type not yet supported by engine
are also parsed.
"""
hot_tpl = template_format.parse('''
heat_template_version: 2013-05-23
parameters:
param1:
description: foo
type: unsupported_type
''')
expected = {'param1': {'Description': 'foo',
'Type': 'UnsupportedType'}}
tmpl = parser.Template(hot_tpl)
self.assertEqual(expected, tmpl[hot.PARAMETERS])
def test_translate_parameters_hidden(self):
hot_tpl = template_format.parse('''
heat_template_version: 2013-05-23
parameters:
user_roles:
description: User roles
type: comma_delimited_list
default: guest,newhire
hidden: TRUE
''')
expected = {
'user_roles': {
'Description': 'User roles',
'Type': 'CommaDelimitedList',
'Default': 'guest,newhire',
'NoEcho': True
}}
tmpl = parser.Template(hot_tpl)
self.assertEqual(expected, tmpl[hot.PARAMETERS])
def test_translate_resources(self):
"""Test translation of resources into internal engine format."""
@ -329,9 +269,9 @@ class HOTParamValidatorTest(HeatTestCase):
pattern_desc2 = 'Value must start with a lowercase character'
param = {
'db_name': {
'Description': 'The WordPress database name',
'Type': 'String',
'Default': 'wordpress',
'description': 'The WordPress database name',
'type': 'string',
'default': 'wordpress',
'constraints': [
{'length': {'min': 6, 'max': 16},
'description': len_desc},
@ -344,7 +284,7 @@ class HOTParamValidatorTest(HeatTestCase):
schema = param['db_name']
def v(value):
hot.HOTParamSchema(schema).validate(name, value)
hot.HOTParamSchema.from_dict(schema).validate(name, value)
return True
value = 'wp'
@ -420,9 +360,9 @@ class HOTParamValidatorTest(HeatTestCase):
range_desc = 'Value must be between 30000 and 50000'
param = {
'db_port': {
'Description': 'The database port',
'Type': 'Number',
'Default': 15,
'description': 'The database port',
'type': 'number',
'default': 31000,
'constraints': [
{'range': {'min': 30000, 'max': 50000},
'description': range_desc}]}}
@ -431,7 +371,7 @@ class HOTParamValidatorTest(HeatTestCase):
schema = param['db_port']
def v(value):
hot.HOTParamSchema(schema).validate(name, value)
hot.HOTParamSchema.from_dict(schema).validate(name, value)
return True
value = 29999
@ -450,3 +390,20 @@ class HOTParamValidatorTest(HeatTestCase):
value = 50000
self.assertTrue(v(value))
def test_range_constraint_invalid_default(self):
range_desc = 'Value must be between 30000 and 50000'
param = {
'db_port': {
'description': 'The database port',
'type': 'number',
'default': 15,
'constraints': [
{'range': {'min': 30000, 'max': 50000},
'description': range_desc}]}}
schema = param['db_port']
err = self.assertRaises(constraints.InvalidSchemaError,
hot.HOTParamSchema.from_dict, schema)
self.assertIn(range_desc, str(err))

View File

@ -19,6 +19,7 @@ import json
from heat.common import exception
from heat.engine import parameters
from heat.engine import template
from heat.engine import constraints as constr
class ParameterTest(testtools.TestCase):
@ -49,8 +50,8 @@ class ParameterTest(testtools.TestCase):
self.assertIsInstance(p, parameters.JsonParam)
def test_new_bad_type(self):
self.assertRaises(ValueError, self.new_parameter, 'p',
{'Type': 'List'})
self.assertRaises(constr.InvalidSchemaError, self.new_parameter, 'p',
{'Type': 'List'}, validate_value=False)
def test_new_no_type(self):
self.assertRaises(KeyError, self.new_parameter,
@ -77,7 +78,7 @@ class ParameterTest(testtools.TestCase):
'AllowedValues': ['foo'],
'ConstraintDescription': 'wibble',
'Default': 'bar'}
err = self.assertRaises(ValueError,
err = self.assertRaises(constr.InvalidSchemaError,
self.new_parameter, 'p', schema, 'foo')
self.assertIn('wibble', str(err))
@ -86,7 +87,7 @@ class ParameterTest(testtools.TestCase):
{'Type': 'String',
'NoEcho': 'true'},
'wibble')
self.assertTrue(p.no_echo())
self.assertTrue(p.hidden())
self.assertNotEqual(str(p), 'wibble')
def test_no_echo_true_caps(self):
@ -94,7 +95,7 @@ class ParameterTest(testtools.TestCase):
{'Type': 'String',
'NoEcho': 'TrUe'},
'wibble')
self.assertTrue(p.no_echo())
self.assertTrue(p.hidden())
self.assertNotEqual(str(p), 'wibble')
def test_no_echo_false(self):
@ -102,7 +103,7 @@ class ParameterTest(testtools.TestCase):
{'Type': 'String',
'NoEcho': 'false'},
'wibble')
self.assertFalse(p.no_echo())
self.assertFalse(p.hidden())
self.assertEqual('wibble', str(p))
def test_description(self):
@ -274,25 +275,6 @@ class ParameterTest(testtools.TestCase):
self.new_parameter, 'p', schema, val)
self.assertIn('Value must be valid JSON', str(err))
def test_map_values_good(self):
'''Happy path for map keys.'''
schema = {'Type': 'Json',
'AllowedValues': ["foo", "bar", "baz"]}
val = {"foo": "bar", "baz": [1, 2, 3]}
val_s = json.dumps(val)
p = self.new_parameter('p', schema, val_s)
self.assertEqual(val, p.value())
self.assertEqual(val, p.parsed)
def test_map_values_bad(self):
'''Test failure of invalid map keys.'''
schema = {'Type': 'Json',
'AllowedValues': ["foo", "bar", "baz"]}
val = {"foo": "bar", "items": [1, 2, 3]}
err = self.assertRaises(ValueError,
self.new_parameter, 'p', schema, val)
self.assertIn("items", str(err))
def test_map_underrun(self):
'''Test map length under MIN_LEN.'''
schema = {'Type': 'Json',
@ -300,7 +282,7 @@ class ParameterTest(testtools.TestCase):
val = {"foo": "bar", "items": [1, 2, 3]}
err = self.assertRaises(ValueError,
self.new_parameter, 'p', schema, val)
self.assertIn('underflows', str(err))
self.assertIn('out of range', str(err))
def test_map_overrun(self):
'''Test map length over MAX_LEN.'''
@ -309,7 +291,7 @@ class ParameterTest(testtools.TestCase):
val = {"foo": "bar", "items": [1, 2, 3]}
err = self.assertRaises(ValueError,
self.new_parameter, 'p', schema, val)
self.assertIn('overflows', str(err))
self.assertIn('out of range', str(err))
def test_missing_param(self):
'''Test missing user parameter.'''

View File

@ -320,7 +320,7 @@ class PropertySchemaTest(testtools.TestCase):
"m2.xlarge", "m2.2xlarge", "m2.4xlarge",
"c1.medium", "c1.xlarge", "cc1.4xlarge"]
constraint_desc = "Must be a valid EC2 instance type."
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "String",
"Description": description,
"Default": "m1.large",
@ -343,9 +343,9 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_string_allowed_pattern(self):
description = "WebServer EC2 instance type"
allowed_pattern = "[A-Za-z0-9]*"
allowed_pattern = "[A-Za-z0-9.]*"
constraint_desc = "Must contain only alphanumeric characters."
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "String",
"Description": description,
"Default": "m1.large",
@ -368,9 +368,9 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_string_multi_constraints(self):
description = "WebServer EC2 instance type"
allowed_pattern = "[A-Za-z0-9]*"
allowed_pattern = "[A-Za-z0-9.]*"
constraint_desc = "Must contain only alphanumeric characters."
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "String",
"Description": description,
"Default": "m1.large",
@ -396,7 +396,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertEqual(constraint_desc, allowed_constraint.description)
def test_from_param_string_min_len(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Description": "WebServer EC2 instance type",
"Type": "String",
"Default": "m1.large",
@ -413,7 +413,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertIsNone(len_constraint.max)
def test_from_param_string_max_len(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Description": "WebServer EC2 instance type",
"Type": "String",
"Default": "m1.large",
@ -430,7 +430,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertEqual(11, len_constraint.max)
def test_from_param_string_min_max_len(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Description": "WebServer EC2 instance type",
"Type": "String",
"Default": "m1.large",
@ -448,7 +448,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertEqual(11, len_constraint.max)
def test_from_param_no_default(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Description": "WebServer EC2 instance type",
"Type": "String",
})
@ -460,7 +460,7 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_number_param_min(self):
default = "42"
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "Number",
"Default": default,
"MinValue": "10",
@ -480,7 +480,7 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_number_param_max(self):
default = "42"
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "Number",
"Default": default,
"MaxValue": "100",
@ -500,7 +500,7 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_number_param_min_max(self):
default = "42"
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "Number",
"Default": default,
"MinValue": "10",
@ -522,7 +522,7 @@ class PropertySchemaTest(testtools.TestCase):
def test_from_number_param_allowed_vals(self):
default = "42"
constraint_desc = "The quick brown fox jumps over the lazy dog."
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "Number",
"Default": default,
"AllowedValues": ["10", "42", "100"],
@ -542,7 +542,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertEqual(constraint_desc, allowed_constraint.description)
def test_from_list_param(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "CommaDelimitedList",
"Default": "foo,bar,baz"
})
@ -554,7 +554,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertFalse(schema.required)
def test_from_json_param(self):
param = parameters.ParamSchema({
param = parameters.Schema.from_dict({
"Type": "Json",
"Default": {"foo": "bar", "blarg": "wibble"}
})
@ -1097,7 +1097,9 @@ class PropertiesTest(testtools.TestCase):
"required": False,
'update_allowed': True,
"constraints": [
{"length": {"min": 1, "max": 16}},
{"length": {"min": 1, "max": 16},
"description": "must begin with a letter and contain "
"only alphanumeric characters."},
{"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
"description": "must begin with a letter and contain "
"only alphanumeric characters."},
@ -1138,7 +1140,9 @@ class PropertiesTest(testtools.TestCase):
"required": False,
'update_allowed': True,
"constraints": [
{"length": {"min": 1, "max": 41}},
{"length": {"min": 1, "max": 41},
"description": "must contain only alphanumeric "
"characters."},
{"allowed_pattern": "[a-zA-Z0-9]*",
"description": "must contain only alphanumeric "
"characters."},
@ -1157,7 +1161,9 @@ class PropertiesTest(testtools.TestCase):
"required": False,
'update_allowed': True,
"constraints": [
{"length": {"min": 1, "max": 41}},
{"length": {"min": 1, "max": 41},
"description": "must contain only alphanumeric "
"characters."},
{"allowed_pattern": "[a-zA-Z0-9]*",
"description": "must contain only alphanumeric "
"characters."},
@ -1169,14 +1175,16 @@ class PropertiesTest(testtools.TestCase):
"required": False,
'update_allowed': True,
"constraints": [
{"length": {"min": 1, "max": 64}},
{"length": {"min": 1, "max": 64},
"description": "must begin with a letter and contain "
"only alphanumeric characters."},
{"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*",
"description": "must begin with a letter and contain "
"only alphanumeric characters."},
]
},
}
params = dict((n, parameters.ParamSchema(s)) for n, s
params = dict((n, parameters.Schema.from_dict(s)) for n, s
in params_snippet.items())
props_schemata = properties.Properties.schema_from_params(params)
@ -1186,14 +1194,14 @@ class PropertiesTest(testtools.TestCase):
def test_schema_from_hot_params(self):
params_snippet = {
"KeyName": {
"Type": "String",
"Description": ("Name of an existing EC2 KeyPair to enable "
"type": "string",
"description": ("Name of an existing EC2 KeyPair to enable "
"SSH access to the instances")
},
"InstanceType": {
"Default": "m1.large",
"Type": "String",
"Description": "WebServer EC2 instance type",
"default": "m1.large",
"type": "string",
"description": "WebServer EC2 instance type",
"constraints": [
{"allowed_values": ["t1.micro", "m1.small", "m1.large",
"m1.xlarge", "m2.xlarge", "m2.2xlarge",
@ -1203,9 +1211,9 @@ class PropertiesTest(testtools.TestCase):
]
},
"LinuxDistribution": {
"Default": "F17",
"Type": "String",
"Description": "Distribution of choice",
"default": "F17",
"type": "string",
"description": "Distribution of choice",
"constraints": [
{"allowed_values": ["F18", "F17", "U10", "RHEL-6.1",
"RHEL-6.2", "RHEL-6.3"],
@ -1213,9 +1221,9 @@ class PropertiesTest(testtools.TestCase):
]
},
"DBName": {
"Type": "String",
"Description": "The WordPress database name",
"Default": "wordpress",
"type": "string",
"description": "The WordPress database name",
"default": "wordpress",
"constraints": [
{"length": {"min": 1, "max": 64},
"description": "Length must be between 1 and 64"},
@ -1225,10 +1233,10 @@ class PropertiesTest(testtools.TestCase):
]
},
"DBUsername": {
"Type": "String",
"Description": "The WordPress database admin account username",
"Default": "admin",
"NoEcho": "true",
"type": "string",
"description": "The WordPress database admin account username",
"default": "admin",
"hidden": "true",
"constraints": [
{"length": {"min": 1, "max": 16},
"description": "Length must be between 1 and 16"},
@ -1238,10 +1246,10 @@ class PropertiesTest(testtools.TestCase):
]
},
"DBPassword": {
"Type": "String",
"Description": "The WordPress database admin account password",
"Default": "admin",
"NoEcho": "true",
"type": "string",
"description": "The WordPress database admin account password",
"default": "admin",
"hidden": "true",
"constraints": [
{"length": {"min": 1, "max": 41},
"description": "Length must be between 1 and 41"},
@ -1251,10 +1259,10 @@ class PropertiesTest(testtools.TestCase):
]
},
"DBRootPassword": {
"Type": "String",
"Description": "Root password for MySQL",
"Default": "admin",
"NoEcho": "true",
"type": "string",
"description": "Root password for MySQL",
"default": "admin",
"hidden": "true",
"constraints": [
{"length": {"min": 1, "max": 41},
"description": "Length must be between 1 and 41"},
@ -1349,7 +1357,7 @@ class PropertiesTest(testtools.TestCase):
]
}
}
params = dict((n, hot.HOTParamSchema(s)) for n, s
params = dict((n, hot.HOTParamSchema.from_dict(s)) for n, s
in params_snippet.items())
props_schemata = properties.Properties.schema_from_params(params)

View File

@ -644,10 +644,15 @@ class validateTest(HeatTestCase):
engine = service.EngineService('a', 't')
res = dict(engine.validate_template(None, t))
# Note: the assertion below does not expect a CFN dict of the parameter
# but a dict of the parameters.Schema object.
# For API CFN backward compatibility, formating to CFN is done in the
# API layer in heat.engine.api.format_validate_parameter.
expected = {'KeyName': {
'Type': 'String',
'Description': 'Name of an existing EC2KeyPair to enable SSH '
'access to the instances'}}
'access to the instances',
'NoEcho': 'false'}}
self.assertEqual(expected, res['Parameters'])
def test_validate_properties(self):