Enable usage of custom constraint in parameters

Add a new custom_constraint keyword type in HOT parameters to allow
validation of custom parameters.

blueprint param-constraints
Co-Authored-By: cedric.soulas@cloudwatt.com
Change-Id: Ic52475ea8a39541f7b1643038a2bcba15f0d7471
This commit is contained in:
Thomas Herve 2014-02-12 10:51:13 +01:00
parent 7100cfa147
commit 84c93280ed
6 changed files with 89 additions and 12 deletions

View File

@ -350,6 +350,35 @@ For example:
description: User name must start with an uppercase character
custom_constraint
~~~~~~~~~~~~~~~~~
The *custom_constraint* constraint adds an extra step of validation, generally
to check that the specified resource exists in the backend. Custom constraints
get implemented by plug-ins and can provide any kind of advanced constraint
validation logic.
The syntax of the custom_constraint constraint is:
::
custom_constraint: <name>
The *name* specifies the concrete type of custom constraint. It corresponds to
the name under which the respective validation plugin has been registered with
the Heat engine.
For example:
::
parameters:
key_name
type: string
description: SSH key pair
constraints:
- custom_constraint: nova.keypair
.. _hot_spec_resources:
-----------------

View File

@ -22,10 +22,13 @@ from heat.openstack.common import log as logging
logger = logging.getLogger(__name__)
PARAM_CONSTRAINTS = (CONSTRAINTS, DESCRIPTION, LENGTH, RANGE,
MIN, MAX, ALLOWED_VALUES, ALLOWED_PATTERN) = \
('constraints', 'description', 'length', 'range',
'min', 'max', 'allowed_values', 'allowed_pattern')
PARAM_CONSTRAINTS = (
CONSTRAINTS, DESCRIPTION, LENGTH, RANGE, MIN, MAX,
ALLOWED_VALUES, ALLOWED_PATTERN, CUSTOM_CONSTRAINT,
) = (
'constraints', 'description', 'length', 'range', 'min', 'max',
'allowed_values', 'allowed_pattern', 'custom_constraint',
)
def snake_to_camel(name):
@ -319,9 +322,10 @@ class HOTemplate(template.Template):
return dict((name, HOTParamSchema.from_dict(schema))
for name, schema in params)
def parameters(self, stack_identifier, user_params, validate_value=True):
def parameters(self, stack_identifier, user_params, validate_value=True,
context=None):
return HOTParameters(stack_identifier, self, user_params=user_params,
validate_value=validate_value)
validate_value=validate_value, context=context)
class HOTParamSchema(parameters.Schema):
@ -372,6 +376,9 @@ class HOTParamSchema(parameters.Schema):
if ALLOWED_PATTERN in constraint:
cdef = constraint.get(ALLOWED_PATTERN)
yield constr.AllowedPattern(cdef, desc)
if CUSTOM_CONSTRAINT in constraint:
cdef = constraint.get(CUSTOM_CONSTRAINT)
yield constr.CustomConstraint(cdef, desc)
# make update_allowed true by default on TemplateResources
# as the template should deal with this.

View File

@ -51,7 +51,7 @@ class Schema(constr.Schema):
)
def __init__(self, data_type, description=None, default=None, schema=None,
constraints=[], hidden=False):
constraints=[], hidden=False, context=None):
super(Schema, self).__init__(data_type=data_type,
description=description,
default=default,
@ -59,6 +59,7 @@ class Schema(constr.Schema):
required=default is None,
constraints=constraints)
self.hidden = hidden
self.context = context
# Schema class validates default value for lists assuming list type. For
# comma delimited list string supported in paramaters Schema class, the
@ -125,7 +126,7 @@ class Schema(constr.Schema):
'false')).lower() == 'true')
def validate(self, name, value):
super(Schema, self).validate_constraints(value)
super(Schema, self).validate_constraints(value, self.context)
def __getitem__(self, key):
if key == self.TYPE:
@ -333,13 +334,14 @@ class Parameters(collections.Mapping):
)
def __init__(self, stack_identifier, tmpl, user_params={},
validate_value=True):
validate_value=True, context=None):
'''
Create the parameter container for a stack from the stack name and
template, optionally setting the user-supplied parameter values.
'''
def user_parameter(schema_item):
name, schema = schema_item
schema.context = context
return Parameter(name, schema,
user_params.get(name),
validate_value)

View File

@ -103,7 +103,8 @@ class Stack(collections.Mapping):
self.env = env or environment.Environment({})
self.parameters = self.t.parameters(self.identifier(),
user_params=self.env.params)
user_params=self.env.params,
context=context)
self._set_param_stackid()

View File

@ -493,10 +493,12 @@ class Template(collections.Mapping):
return dict((name, parameters.Schema.from_dict(schema))
for name, schema in params)
def parameters(self, stack_identifier, user_params, validate_value=True):
def parameters(self, stack_identifier, user_params, validate_value=True,
context=None):
return parameters.Parameters(stack_identifier, self,
user_params=user_params,
validate_value=validate_value)
validate_value=validate_value,
context=context)
def _resolve(match, handle, snippet, transform=None):

View File

@ -17,6 +17,7 @@ from heat.common import identifier
from heat.engine import parser
from heat.engine import resource
from heat.engine import hot
from heat.engine import resources
from heat.engine import template
from heat.engine import constraints
@ -764,6 +765,41 @@ class HOTParamValidatorTest(HeatTestCase):
value = 50000
self.assertTrue(v(value))
def test_custom_constraint(self):
class ZeroConstraint(object):
def validate(self, value, context):
return value == "0"
env = resources.global_env()
env.register_constraint("zero", ZeroConstraint)
self.addCleanup(env.constraints.pop, "zero")
desc = 'Value must be zero'
param = {
'param1': {
'type': 'string',
'constraints': [
{'custom_constraint': 'zero',
'description': desc}]}}
name = 'param1'
schema = param['param1']
def v(value):
hot.HOTParamSchema.from_dict(schema).validate(name, value)
return True
value = "1"
err = self.assertRaises(ValueError, v, value)
self.assertEqual(desc, str(err))
value = "2"
err = self.assertRaises(ValueError, v, value)
self.assertEqual(desc, str(err))
value = "0"
self.assertTrue(v(value))
def test_range_constraint_invalid_default(self):
range_desc = 'Value must be between 30000 and 50000'
param = {