From a1a0609e8ed456ac2bcfb1924ace0add1bf58b5e Mon Sep 17 00:00:00 2001 From: Saravanan KR Date: Thu, 21 Sep 2017 17:03:47 +0530 Subject: [PATCH] Added tags attribute to the template parameter In some scenarios, it is required to categorize the parameter. Associating tags with the parameter, it is will be easy to categorize the parameter and add simple validations to users. This patch introduces a new attribute 'tags' with the parameters, which is a list of strings. Change-Id: I20fc95d606b0b8a08d3b46bf33f4860bff49c74f --- doc/source/template_guide/hot_spec.rst | 6 ++ heat/engine/api.py | 3 + heat/engine/hot/parameters.py | 62 +++++++++++---- heat/engine/hot/template.py | 2 + heat/engine/parameters.py | 17 ++++- heat/rpc/api.py | 4 +- heat/tests/test_validate.py | 76 +++++++++++++++++++ .../parameter-tags-148ef065616f92fc.yaml | 5 ++ 8 files changed, 154 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/parameter-tags-148ef065616f92fc.yaml diff --git a/doc/source/template_guide/hot_spec.rst b/doc/source/template_guide/hot_spec.rst index 80925f92ea..d01864d38e 100644 --- a/doc/source/template_guide/hot_spec.rst +++ b/doc/source/template_guide/hot_spec.rst @@ -437,6 +437,7 @@ default value defined as nested elements. constraints: immutable: + tags: param name The name of the parameter. @@ -478,6 +479,11 @@ immutable set to ``true`` and the parameter value is changed. This attribute is optional and defaults to ``false``. +tags + A list of strings to specify the category of a parameter. This value is + used to categorize a parameter so that users can group the parameters. + This attribute is optional. + The table below describes all currently supported types with examples: +----------------------+-------------------------------+------------------+ diff --git a/heat/engine/api.py b/heat/engine/api.py index c19b4f9f53..8cf958ff5e 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -524,6 +524,9 @@ def format_validate_parameter(param): if param.user_value: res[rpc_api.PARAM_VALUE] = param.user_value + if param.tags(): + res[rpc_api.PARAM_TAG] = param.tags() + _build_parameter_constraints(res, param) return res diff --git a/heat/engine/hot/parameters.py b/heat/engine/hot/parameters.py index 1438288924..309c5e6b75 100644 --- a/heat/engine/hot/parameters.py +++ b/heat/engine/hot/parameters.py @@ -79,6 +79,22 @@ class HOTParamSchema(parameters.Schema): raise exception.InvalidSchemaError( message=_("No constraint expressed")) + @classmethod + def _constraints(cls, param_name, schema_dict): + constraints = schema_dict.get(cls.CONSTRAINTS) + if constraints is None: + return + + if not isinstance(constraints, list): + raise exception.InvalidSchemaError( + message=_("Invalid parameter constraints for parameter " + "%s, expected a list") % param_name) + + for constraint in constraints: + cls._check_dict(constraint, PARAM_CONSTRAINTS, + 'parameter constraints') + yield cls._constraint_from_def(constraint) + @classmethod def from_dict(cls, param_name, schema_dict): """Return a Parameter Schema object from a legacy schema dictionary. @@ -89,27 +105,12 @@ class HOTParamSchema(parameters.Schema): """ cls._validate_dict(param_name, schema_dict) - def constraints(): - constraints = schema_dict.get(cls.CONSTRAINTS) - if constraints is None: - return - - if not isinstance(constraints, list): - raise exception.InvalidSchemaError( - message=_("Invalid parameter constraints for parameter " - "%s, expected a list") % param_name) - - for constraint in constraints: - cls._check_dict(constraint, PARAM_CONSTRAINTS, - 'parameter constraints') - yield cls._constraint_from_def(constraint) - # 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()), + constraints=list(cls._constraints(param_name, schema_dict)), hidden=schema_dict.get(HOTParamSchema.HIDDEN, False), label=schema_dict.get(HOTParamSchema.LABEL), immutable=schema_dict.get(HOTParamSchema.IMMUTABLE, False)) @@ -131,6 +132,35 @@ class HOTParamSchema20170224(HOTParamSchema): constraint) +class HOTParamSchema20180302(HOTParamSchema20170224): + + KEYS_20180302 = (TAGS,) = ('tags',) + KEYS = HOTParamSchema20170224.KEYS + KEYS_20180302 + + PARAMETER_KEYS = KEYS + + @classmethod + def from_dict(cls, param_name, schema_dict): + """Return a Parameter Schema object from a legacy schema dictionary. + + :param param_name: name of the parameter owning the schema; used + for more verbose logging + :type param_name: str + """ + cls._validate_dict(param_name, schema_dict) + + # 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(cls._constraints(param_name, schema_dict)), + hidden=schema_dict.get(HOTParamSchema.HIDDEN, False), + label=schema_dict.get(HOTParamSchema.LABEL), + immutable=schema_dict.get(HOTParamSchema.IMMUTABLE, False), + tags=schema_dict.get(HOTParamSchema20180302.TAGS)) + + class HOTParameters(parameters.Parameters): PSEUDO_PARAMETERS = ( PARAM_STACK_ID, PARAM_STACK_NAME, PARAM_REGION, PARAM_PROJECT_ID diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index 0a18f4f8bd..4a54615c24 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -695,3 +695,5 @@ class HOTemplate20180302(HOTemplate20170901): 'yaql': hot_funcs.Yaql, 'contains': hot_funcs.Contains } + + param_schema_class = parameters.HOTParamSchema20180302 diff --git a/heat/engine/parameters.py b/heat/engine/parameters.py index 8a4d19a403..2222c6034e 100644 --- a/heat/engine/parameters.py +++ b/heat/engine/parameters.py @@ -42,10 +42,10 @@ class Schema(constr.Schema): KEYS = ( TYPE, DESCRIPTION, DEFAULT, SCHEMA, CONSTRAINTS, HIDDEN, - LABEL, IMMUTABLE + LABEL, IMMUTABLE, TAGS, ) = ( 'Type', 'Description', 'Default', 'Schema', 'Constraints', 'NoEcho', - 'Label', 'Immutable' + 'Label', 'Immutable', 'Tags', ) PARAMETER_KEYS = PARAMETER_KEYS @@ -59,7 +59,8 @@ class Schema(constr.Schema): ) def __init__(self, data_type, description=None, default=None, schema=None, - constraints=None, hidden=False, label=None, immutable=False): + constraints=None, hidden=False, label=None, immutable=False, + tags=None): super(Schema, self).__init__(data_type=data_type, description=description, default=default, @@ -69,6 +70,7 @@ class Schema(constr.Schema): label=label, immutable=immutable) self.hidden = hidden + self.tags = tags # Schema class validates default value for lists assuming list type. For # comma delimited list string supported in parameters Schema class, the @@ -129,6 +131,11 @@ class Schema(constr.Schema): message=_("Missing parameter type for parameter: %s") % param_name) + if not isinstance(schema_dict.get(cls.TAGS, []), list): + raise exception.InvalidSchemaError( + message=_("Tags property should be a list for parameter: %s") % + param_name) + @classmethod def from_dict(cls, param_name, schema_dict): """Return a Parameter Schema object from a legacy schema dictionary. @@ -276,6 +283,10 @@ class Parameter(object): """Return the label or param name.""" return self.schema.label or self.name + def tags(self): + """Return the tags associated with the parameter""" + return self.schema.tags or [] + def has_default(self): """Return whether the parameter has a default value.""" return (self.schema.default is not None or diff --git a/heat/rpc/api.py b/heat/rpc/api.py index bffe783d73..90cdc81776 100644 --- a/heat/rpc/api.py +++ b/heat/rpc/api.py @@ -193,13 +193,13 @@ VALIDATE_PARAM_KEYS = ( PARAM_MIN_LENGTH, PARAM_MAX_VALUE, PARAM_MIN_VALUE, PARAM_STEP, PARAM_OFFSET, PARAM_DESCRIPTION, PARAM_CONSTRAINT_DESCRIPTION, PARAM_LABEL, - PARAM_CUSTOM_CONSTRAINT, PARAM_VALUE + PARAM_CUSTOM_CONSTRAINT, PARAM_VALUE, PARAM_TAG ) = ( 'Type', 'Default', 'NoEcho', 'AllowedValues', 'AllowedPattern', 'MaxLength', 'MinLength', 'MaxValue', 'MinValue', 'Step', 'Offset', 'Description', 'ConstraintDescription', 'Label', - 'CustomConstraint', 'Value' + 'CustomConstraint', 'Value', 'Tags' ) VALIDATE_PARAM_TYPES = ( diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index 128e093f32..2db962150a 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -924,6 +924,46 @@ resources: external_id: foobar ''' +test_template_hot_parameter_tags_older = ''' +heat_template_version: 2013-05-23 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: + - feature1 + - feature2 + +''' + +test_template_hot_parameter_tags_pass = ''' +heat_template_version: 2018-03-02 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: + - feature1 + - feature2 + +''' + +test_template_hot_parameter_tags_fail = ''' +heat_template_version: 2018-03-02 + +parameters: + KeyName: + type: string + description: Name of an existing key pair to use for the instance + label: Nova KeyPair Name + tags: feature + +''' + class ValidateTest(common.HeatTestCase): def setUp(self): @@ -1859,3 +1899,39 @@ parameter_groups: self.ctx, t, {}) self.assertEqual(dependencies.CircularDependencyException, exc.exc_info[0]) + + def test_validate_hot_parameter_tags_older(self): + t = template_format.parse(test_template_hot_parameter_tags_older) + + exc = self.assertRaises(dispatcher.ExpectedException, + self.engine.validate_template, + self.ctx, t, {}) + self.assertEqual(exception.InvalidSchemaError, + exc.exc_info[0]) + + def test_validate_hot_parameter_tags_pass(self): + t = template_format.parse(test_template_hot_parameter_tags_pass) + + res = dict(self.engine.validate_template(self.ctx, t, {})) + parameters = res['Parameters'] + + expected = {'KeyName': { + 'Description': 'Name of an existing key pair to use for the ' + 'instance', + 'NoEcho': 'false', + 'Label': 'Nova KeyPair Name', + 'Type': 'String', + 'Tags': [ + 'feature1', + 'feature2' + ]}} + self.assertEqual(expected, parameters) + + def test_validate_hot_parameter_tags_fail(self): + t = template_format.parse(test_template_hot_parameter_tags_fail) + + exc = self.assertRaises(dispatcher.ExpectedException, + self.engine.validate_template, + self.ctx, t, {}) + self.assertEqual(exception.InvalidSchemaError, + exc.exc_info[0]) diff --git a/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml b/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml new file mode 100644 index 0000000000..87f67ba3c5 --- /dev/null +++ b/releasenotes/notes/parameter-tags-148ef065616f92fc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added a new schema property tags, to parameters, to categorize + parameters based on features.