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 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: .. _hot_spec_resources:
----------------- -----------------

View File

@ -22,10 +22,13 @@ from heat.openstack.common import log as logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
PARAM_CONSTRAINTS = (CONSTRAINTS, DESCRIPTION, LENGTH, RANGE, PARAM_CONSTRAINTS = (
MIN, MAX, ALLOWED_VALUES, ALLOWED_PATTERN) = \ CONSTRAINTS, DESCRIPTION, LENGTH, RANGE, MIN, MAX,
('constraints', 'description', 'length', 'range', ALLOWED_VALUES, ALLOWED_PATTERN, CUSTOM_CONSTRAINT,
'min', 'max', 'allowed_values', 'allowed_pattern') ) = (
'constraints', 'description', 'length', 'range', 'min', 'max',
'allowed_values', 'allowed_pattern', 'custom_constraint',
)
def snake_to_camel(name): def snake_to_camel(name):
@ -319,9 +322,10 @@ class HOTemplate(template.Template):
return dict((name, HOTParamSchema.from_dict(schema)) return dict((name, HOTParamSchema.from_dict(schema))
for name, schema in params) 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, return HOTParameters(stack_identifier, self, user_params=user_params,
validate_value=validate_value) validate_value=validate_value, context=context)
class HOTParamSchema(parameters.Schema): class HOTParamSchema(parameters.Schema):
@ -372,6 +376,9 @@ class HOTParamSchema(parameters.Schema):
if ALLOWED_PATTERN in constraint: if ALLOWED_PATTERN in constraint:
cdef = constraint.get(ALLOWED_PATTERN) cdef = constraint.get(ALLOWED_PATTERN)
yield constr.AllowedPattern(cdef, desc) 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 # make update_allowed true by default on TemplateResources
# as the template should deal with this. # 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, 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, super(Schema, self).__init__(data_type=data_type,
description=description, description=description,
default=default, default=default,
@ -59,6 +59,7 @@ class Schema(constr.Schema):
required=default is None, required=default is None,
constraints=constraints) constraints=constraints)
self.hidden = hidden self.hidden = hidden
self.context = context
# Schema class validates default value for lists assuming list type. For # Schema class validates default value for lists assuming list type. For
# comma delimited list string supported in paramaters Schema class, the # comma delimited list string supported in paramaters Schema class, the
@ -125,7 +126,7 @@ class Schema(constr.Schema):
'false')).lower() == 'true') 'false')).lower() == 'true')
def validate(self, name, value): def validate(self, name, value):
super(Schema, self).validate_constraints(value) super(Schema, self).validate_constraints(value, self.context)
def __getitem__(self, key): def __getitem__(self, key):
if key == self.TYPE: if key == self.TYPE:
@ -333,13 +334,14 @@ class Parameters(collections.Mapping):
) )
def __init__(self, stack_identifier, tmpl, user_params={}, 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 Create the parameter container for a stack from the stack name and
template, optionally setting the user-supplied parameter values. template, optionally setting the user-supplied parameter values.
''' '''
def user_parameter(schema_item): def user_parameter(schema_item):
name, schema = schema_item name, schema = schema_item
schema.context = context
return Parameter(name, schema, return Parameter(name, schema,
user_params.get(name), user_params.get(name),
validate_value) validate_value)

View File

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

View File

@ -493,10 +493,12 @@ class Template(collections.Mapping):
return dict((name, parameters.Schema.from_dict(schema)) return dict((name, parameters.Schema.from_dict(schema))
for name, schema in params) 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, return parameters.Parameters(stack_identifier, self,
user_params=user_params, user_params=user_params,
validate_value=validate_value) validate_value=validate_value,
context=context)
def _resolve(match, handle, snippet, transform=None): 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 parser
from heat.engine import resource from heat.engine import resource
from heat.engine import hot from heat.engine import hot
from heat.engine import resources
from heat.engine import template from heat.engine import template
from heat.engine import constraints from heat.engine import constraints
@ -764,6 +765,41 @@ class HOTParamValidatorTest(HeatTestCase):
value = 50000 value = 50000
self.assertTrue(v(value)) 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): def test_range_constraint_invalid_default(self):
range_desc = 'Value must be between 30000 and 50000' range_desc = 'Value must be between 30000 and 50000'
param = { param = {