Merge "Refactor Parameters Schema based on common Schema"
This commit is contained in:
commit
3d2517b2c7
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
|
@ -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'
|
||||
})
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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.'''
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue