From 5559b0c06930deb1c7831efe599a1036574f50b3 Mon Sep 17 00:00:00 2001 From: shangxdy Date: Sun, 14 Aug 2016 02:16:28 +0800 Subject: [PATCH] Add input validation in substitution_mapping class Add input validation in class of substitution_mapping according to specification of http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/ TOSCA-Simple-Profile-YAML-v1.0.html: 1) The properties of substituted node template which be mapped must be in the inputs of nested service template which defines substutition mappings; 2) The inputs of nested service template which are not in properties of the substituted node template must have default values. 3) If the properties of node_type is required and no default value, must provide inputs for them; 4) Property names and the input names must be the same. JIRA:PARSER-79 Change-Id: Ie4664fe17c8279ad531ac9acec057f98d4e9281a Signed-off-by: shangxdy --- .../tosca-parser/toscaparser/common/exception.py | 10 ++++ .../nfv/tests/data/vRNC/Definitions/vRNC.yaml | 12 ++++ .../extensions/nfv/tests/test_tosca_vRNC.py | 6 +- .../toscaparser/substitution_mappings.py | 67 +++++++++++++++++----- .../data/topology_template/databasesubsystem.yaml | 12 ++-- .../tosca-parser/toscaparser/tosca_template.py | 15 ++++- 6 files changed, 99 insertions(+), 23 deletions(-) diff --git a/tosca2heat/tosca-parser/toscaparser/common/exception.py b/tosca2heat/tosca-parser/toscaparser/common/exception.py index bd0ed9c..34abe77 100644 --- a/tosca2heat/tosca-parser/toscaparser/common/exception.py +++ b/tosca2heat/tosca-parser/toscaparser/common/exception.py @@ -109,6 +109,16 @@ class UnknownInputError(TOSCAException): msg_fmt = _('Unknown input "%(input_name)s".') +class MissingRequiredInputError(TOSCAException): + msg_fmt = _('%(what)s is missing required input definition ' + ' with name: "%(input_name)s".') + + +class MissingRequiredParameterError(TOSCAException): + msg_fmt = _('%(what)s is missing required parameter for input: ' + '"%(input_name)s".') + + class InvalidPropertyValueError(TOSCAException): msg_fmt = _('Value of property "%(what)s" is invalid.') diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/vRNC/Definitions/vRNC.yaml b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/vRNC/Definitions/vRNC.yaml index 21f79e2..7068c7a 100644 --- a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/vRNC/Definitions/vRNC.yaml +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/vRNC/Definitions/vRNC.yaml @@ -71,6 +71,18 @@ topology_template: description: mm additional block storage size constraints: - in_range: [ 1, 200 ] + id: + type: string + description: ID of this VNF + default: UMTS + vendor: + type: string + description: name of the vendor who generate this VNF + default: ZTE + version: + type: version + description: version of the software for this VNF + default: 1.0 substitution_mappings: node_type: rnc.nodes.VNF diff --git a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_vRNC.py b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_vRNC.py index c839626..a0ffc21 100644 --- a/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_vRNC.py +++ b/tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_vRNC.py @@ -29,8 +29,10 @@ class ToscaVRNCTemplateTest(TestCase): "tosca_simple_profile_for_nfv_1_0_0") def test_input(self): - first_input_name = "mm_storage_size" - self.assertEqual(self.tosca.inputs[0].name, first_input_name) + input_names = sorted(["mm_storage_size", "id", + "vendor", "version"]) + self.assertEqual(sorted([i.name for i in self.tosca.inputs]), + input_names) def test_nodetemplates(self): expected_node_list = sorted( diff --git a/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py index 83ff590..f644808 100644 --- a/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py +++ b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py @@ -15,7 +15,11 @@ import logging from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import InvalidNodeTypeError from toscaparser.common.exception import MissingRequiredFieldError +from toscaparser.common.exception import MissingRequiredInputError +from toscaparser.common.exception import MissingRequiredParameterError from toscaparser.common.exception import UnknownFieldError +from toscaparser.elements.nodetype import NodeType + log = logging.getLogger('tosca') @@ -60,9 +64,16 @@ class SubstitutionMappings(object): def requirements(self): return self.sub_mapping_def.get(self.REQUIREMENTS) + @property + def node_definition(self): + return NodeType(self.node_type, self.custom_defs) + def _validate(self): + # basic valiation self._validate_keys() self._validate_type() + + # SubstitutionMapping class syntax validation self._validate_inputs() self._validate_capabilities() self._validate_requirements() @@ -73,7 +84,7 @@ class SubstitutionMappings(object): for key in self.sub_mapping_def.keys(): if key not in self.SECTIONS: ExceptionCollector.appendException( - UnknownFieldError(what='Substitution_mappings', + UnknownFieldError(what='SubstitutionMappings', field=key)) def _validate_type(self): @@ -82,7 +93,7 @@ class SubstitutionMappings(object): if not node_type: ExceptionCollector.appendException( MissingRequiredFieldError( - what=_('Substitution_mappings used in topology_template'), + what=_('SubstitutionMappings used in topology_template'), required=self.NODE_TYPE)) node_type_def = self.custom_defs.get(node_type) @@ -93,16 +104,44 @@ class SubstitutionMappings(object): def _validate_inputs(self): """validate the inputs of substitution mappings.""" - # The inputs in service template which defines substutition mappings - # must be in properties of node template wchich be mapped. - inputs_names = list(self.sub_mapped_node_template - .get_properties().keys() - if self.sub_mapped_node_template else []) - for name in inputs_names: - if name not in [input.name for input in self.inputs]: + # The inputs in service template which provides substutition mappings + # must be in properties of node template which is mapped or provide + # defualt value. Currently the input.name is not restrict to be the + # same as property name in specification, but they should be equal + # for current implementation. + + # Must provide parameters for required properties of node_type + # This checking is internal(inside SubstitutionMappings) + for propery in self.node_definition.get_properties_def_objects(): + # Check property which is 'required' and has no 'default' value + if propery.required and propery.default is None and \ + propery.name not in [input.name for input in self.inputs]: ExceptionCollector.appendException( - UnknownFieldError(what='SubstitutionMappings', - field=name)) + MissingRequiredInputError( + what='SubstitutionMappings with node_type:' + + self.node_type, + input_name=propery.name)) + + # Get property names from substituted node tempalte + property_names = list(self.sub_mapped_node_template + .get_properties().keys() + if self.sub_mapped_node_template else []) + # Sub_mapped_node_template is None(deploy standaolone), will check + # according to node_type + if 0 == len(property_names): + property_names = list(self.node_definition + .get_properties_def().keys()) + # Provide default value for parameter which is not property of + # node with the type node_type, this may not be mandatory for + # current implematation, but the specification express it mandatory. + # This checking is external(outside SubstitutionMappings) + for input in self.inputs: + if input.name not in property_names and input.default is None: + ExceptionCollector.appendException( + MissingRequiredParameterError( + what='SubstitutionMappings with node_type:' + + self.node_type, + input_name=input.name)) def _validate_capabilities(self): """validate the capabilities of substitution mappings.""" @@ -116,7 +155,7 @@ class SubstitutionMappings(object): cap not in list(tpls_capabilities.keys())): pass # ExceptionCollector.appendException( - # UnknownFieldError(what='Substitution_mappings', + # UnknownFieldError(what='SubstitutionMappings', # field=cap)) def _validate_requirements(self): @@ -131,7 +170,7 @@ class SubstitutionMappings(object): req not in list(tpls_requirements.keys())): pass # ExceptionCollector.appendException( - # UnknownFieldError(what='Substitution_mappings', + # UnknownFieldError(what='SubstitutionMappings', # field=req)) def _validate_outputs(self): @@ -144,5 +183,5 @@ class SubstitutionMappings(object): # for name in outputs_names: # if name not in [output.name for input in self.outputs]: # ExceptionCollector.appendException( - # UnknownFieldError(what='Substitution_mappings', + # UnknownFieldError(what='SubstitutionMappings', # field=name)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/databasesubsystem.yaml b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/databasesubsystem.yaml index 22eb259..ebf1856 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/databasesubsystem.yaml +++ b/tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/databasesubsystem.yaml @@ -12,15 +12,15 @@ topology_template: description: Template of a database including its hosting stack. inputs: - db_user: + user: type: string description: the user name of database. default: test - db_port: + port: type: integer description: the port of database. default: 3306 - db_name: + name: type: string description: the name of database. default: test @@ -40,9 +40,9 @@ topology_template: db_app: type: tosca.nodes.Database properties: - user: { get_input: db_user } - port: { get_input: db_port } - name: { get_input: db_name } + user: { get_input: user } + port: { get_input: port } + name: { get_input: name } capabilities: database_endpoint: properties: diff --git a/tosca2heat/tosca-parser/toscaparser/tosca_template.py b/tosca2heat/tosca-parser/toscaparser/tosca_template.py index 80cb1cb..2e815fd 100644 --- a/tosca2heat/tosca-parser/toscaparser/tosca_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tosca_template.py @@ -14,6 +14,7 @@ import logging import os +from copy import deepcopy from toscaparser.common.exception import ExceptionCollector from toscaparser.common.exception import InvalidTemplateVersion from toscaparser.common.exception import MissingRequiredFieldError @@ -226,8 +227,10 @@ class ToscaTemplate(object): for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items(): for nodetemplate in self.nodetemplates: if self._is_sub_mapped_node(nodetemplate, tosca_tpl): + parsed_params = self._get_params_for_nested_template( + nodetemplate) nested_template = ToscaTemplate( - path=fname, parsed_params=self.parsed_params, + path=fname, parsed_params=parsed_params, yaml_dict_tpl=tosca_tpl, sub_mapped_node_template=nodetemplate) if nested_template.has_substitution_mappings(): @@ -310,6 +313,16 @@ class ToscaTemplate(object): else: return False + def _get_params_for_nested_template(self, nodetemplate): + """Return total params for nested_template.""" + parsed_params = deepcopy(self.parsed_params) \ + if self.parsed_params else {} + if nodetemplate: + for pname in nodetemplate.get_properties(): + parsed_params.update({pname: + nodetemplate.get_property_value(pname)}) + return parsed_params + def get_sub_mapping_node_type(self, tosca_tpl): """Return substitution mappings node type.""" if tosca_tpl: -- 2.16.6