Add input validation in substitution_mapping class 21/18621/4
authorshangxdy <shang.xiaodong@zte.com.cn>
Sat, 13 Aug 2016 18:16:28 +0000 (02:16 +0800)
committershangxdy <shang.xiaodong@zte.com.cn>
Sun, 14 Aug 2016 15:30:24 +0000 (23:30 +0800)
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 <shang.xiaodong@zte.com.cn>
tosca2heat/tosca-parser/toscaparser/common/exception.py
tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/data/vRNC/Definitions/vRNC.yaml
tosca2heat/tosca-parser/toscaparser/extensions/nfv/tests/test_tosca_vRNC.py
tosca2heat/tosca-parser/toscaparser/substitution_mappings.py
tosca2heat/tosca-parser/toscaparser/tests/data/topology_template/databasesubsystem.yaml
tosca2heat/tosca-parser/toscaparser/tosca_template.py

index bd0ed9c..34abe77 100644 (file)
@@ -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.')
 
index 21f79e2..7068c7a 100644 (file)
@@ -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
index c839626..a0ffc21 100644 (file)
@@ -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(
index 83ff590..f644808 100644 (file)
@@ -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))
index 22eb259..ebf1856 100644 (file)
@@ -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:
index 80cb1cb..2e815fd 100644 (file)
@@ -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: