tosca-parser support the semantic of substitution mapping 29/17329/5
authorshangxdy <shang.xiaodong@zte.com.cn>
Thu, 21 Jul 2016 12:02:51 +0000 (20:02 +0800)
committershangxdy <shang.xiaodong@zte.com.cn>
Fri, 29 Jul 2016 14:03:30 +0000 (22:03 +0800)
As a template designer,
I want to using node template substitution for model composition or
chaining subsystems.
So firstly tosca-paser should support the substitution mappings
analysis.

Change-Id: I44371795504415ba8cf5a15f7e1d046e3ff00ade
JIRA: PARSER-43
Signed-off-by: shangxdy <shang.xiaodong@zte.com.cn>
tosca2heat/tosca-parser/toscaparser/nodetemplate.py
tosca2heat/tosca-parser/toscaparser/substitution_mappings.py [new file with mode: 0644]
tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py
tosca2heat/tosca-parser/toscaparser/topology_template.py
tosca2heat/tosca-parser/toscaparser/tosca_template.py

index d969d51..d90b73a 100644 (file)
@@ -48,6 +48,7 @@ class NodeTemplate(EntityTemplate):
         self.available_rel_tpls = available_rel_tpls
         self.available_rel_types = available_rel_types
         self._relationships = {}
+        self.substitution_mapped = False
 
     @property
     def relationships(self):
diff --git a/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py
new file mode 100644 (file)
index 0000000..40ff3ca
--- /dev/null
@@ -0,0 +1,155 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+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 UnknownFieldError
+# from toscaparser.common.exception import ValidationError
+# from toscaparser.utils.gettextutils import _
+# from toscaparser.utils import validateutils
+# from toscaparser.nodetemplate import NodeTemplate
+# from toscaparser.elements.nodetype import NodeType
+# from toscaparser.parameters import Input
+# from toscaparser.parameters import Output
+# from toscaparser.groups import Group
+# from toscaparser.policy import Policy
+
+
+log = logging.getLogger('tosca')
+
+
+class Substitution_mappings(object):
+    '''Substitution_mappings class for declaration that
+
+    exports the topology template as an implementation of a Node type.
+    '''
+
+    SECTIONS = (NODE_TYPE, CAPABILITIES, REQUIREMENTS) = \
+               ('node_type', 'requirements', 'capabilities')
+
+    def __init__(self, submap_def, nodetemplates, inputs, outputs,
+                 submaped_node_template, custom_defs):
+        self.nodetemplates = nodetemplates
+        self.submap_def = submap_def
+        self.inputs = inputs or []
+        self.outputs = outputs or []
+        self.submaped_node_template = submaped_node_template
+        self.custom_defs = custom_defs or {}
+        self._validate()
+
+        self._capabilities = None
+        self._requirements = None
+
+        self.submaped_node_template.substitution_mapped = True
+
+    @classmethod
+    def get_node_type(cls, submap_tpl):
+        if isinstance(submap_tpl, dict):
+            return submap_tpl.get(cls.NODE_TYPE)
+
+    @property
+    def node_type(self):
+        return self.submap_def.get(self.NODE_TYPE)
+
+    @property
+    def capabilities(self):
+        return self.submap_def.get(self.CAPABILITIES)
+
+    @property
+    def requirements(self):
+        return self.submap_def.get(self.REQUIREMENTS)
+
+    def _validate(self):
+        self._validate_keys()
+        self._validate_type()
+        self._validate_inputs()
+        self._validate_capabilities()
+        self._validate_requirements()
+        self._validate_outputs()
+
+    def _validate_keys(self):
+        """validate the keys of substitution mappings."""
+        for key in self.submap_def.keys():
+            if key not in self.SECTIONS:
+                ExceptionCollector.appendException(
+                    UnknownFieldError(what='Substitution_mappings',
+                                      field=key))
+
+    def _validate_type(self):
+        """validate the node_type of substitution mappings."""
+        node_type = self.submap_def.get(self.NODE_TYPE)
+        if not node_type:
+            ExceptionCollector.appendException(
+                MissingRequiredFieldError(
+                    what=_('Substitution_mappings used in topology_template'),
+                    required=self.NODE_TYPE))
+
+        node_type_def = self.custom_defs.get(node_type)
+        if not node_type_def:
+            ExceptionCollector.appendException(
+                InvalidNodeTypeError(what=node_type_def))
+
+    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.submaped_node_template
+                                .get_properties().keys())
+        for name in inputs_names:
+            if name not in [input.name for input in self.inputs]:
+                ExceptionCollector.appendException(
+                    UnknownFieldError(what='Substitution_mappings',
+                                      field=name))
+
+    def _validate_capabilities(self):
+        """validate the capabilities of substitution mappings."""
+
+        # The capabilites must be in node template wchich be mapped.
+        tpls_capabilities = self.submap_def.get(self.CAPABILITIES)
+        node_capabiliteys = self.submaped_node_template.get_capabilities()
+        for cap in node_capabiliteys.keys() if node_capabiliteys else []:
+            if (tpls_capabilities and
+                    cap not in list(tpls_capabilities.keys())):
+                pass
+                # ExceptionCollector.appendException(
+                #    UnknownFieldError(what='Substitution_mappings',
+                #                      field=cap))
+
+    def _validate_requirements(self):
+        """validate the requirements of substitution mappings."""
+
+        # The requirements must be in node template wchich be mapped.
+        tpls_requirements = self.submap_def.get(self.REQUIREMENTS)
+        node_requirements = self.submaped_node_template.requirements
+        for req in node_requirements if node_requirements else []:
+            if (tpls_requirements and
+                    req not in list(tpls_requirements.keys())):
+                pass
+                # ExceptionCollector.appendException(
+                #    UnknownFieldError(what='Substitution_mappings',
+                #                      field=req))
+
+    def _validate_outputs(self):
+        """validate the outputs of substitution mappings."""
+        pass
+        # The outputs in service template which defines substutition mappings
+        # must be in atrributes of node template wchich be mapped.
+        # outputs_names = self.submaped_node_template.get_properties().keys()
+        # for name in outputs_names:
+        #    if name not in [output.name for input in self.outputs]:
+        #        ExceptionCollector.appendException(
+        #            UnknownFieldError(what='Substitution_mappings',
+        #                              field=name))
index 14f2496..f66396e 100644 (file)
@@ -159,4 +159,4 @@ class TopologyTemplateTest(TestCase):
             "data/topology_template/system.yaml")
         system_tosca_template = ToscaTemplate(tpl_path)
         self.assertIsNotNone(system_tosca_template)
-        self.assertEqual(len(system_tosca_template.nested_tosca_template), 0)
+        self.assertEqual(len(system_tosca_template.nested_tosca_templates), 1)
index 6cf4f31..70bc7d6 100644 (file)
@@ -22,6 +22,7 @@ from toscaparser.parameters import Input
 from toscaparser.parameters import Output
 from toscaparser.policy import Policy
 from toscaparser.relationship_template import RelationshipTemplate
+from toscaparser.substitution_mappings import Substitution_mappings
 from toscaparser.tpl_relationship_graph import ToscaGraph
 from toscaparser.utils.gettextutils import _
 
@@ -41,8 +42,10 @@ class TopologyTemplate(object):
 
     '''Load the template data.'''
     def __init__(self, template, custom_defs,
-                 rel_types=None, parsed_params=None):
+                 rel_types=None, parsed_params=None,
+                 submaped_node_template=None):
         self.tpl = template
+        self.submaped_node_template = submaped_node_template
         if self.tpl:
             self.custom_defs = custom_defs
             self.rel_types = rel_types
@@ -106,7 +109,14 @@ class TopologyTemplate(object):
         return outputs
 
     def _substitution_mappings(self):
-        pass
+        tpl_substitution_mapping = self._tpl_substitution_mappings()
+        if tpl_substitution_mapping and self.submaped_node_template:
+            return Substitution_mappings(tpl_substitution_mapping,
+                                         self.nodetemplates,
+                                         self.inputs,
+                                         self.outputs,
+                                         self.submaped_node_template,
+                                         self.custom_defs)
 
     def _policies(self):
         policies = []
@@ -178,13 +188,16 @@ class TopologyTemplate(object):
     # topology template can act like node template
     # it is exposed by substitution_mappings.
     def nodetype(self):
-        pass
+        return (self.substitution_mappings.node_type
+                if self.substitution_mappings else None)
 
     def capabilities(self):
-        pass
+        return (self.substitution_mappings.capabilities
+                if self.substitution_mappings else None)
 
     def requirements(self):
-        pass
+        return (self.substitution_mappings.requirements
+                if self.substitution_mappings else None)
 
     def _tpl_description(self):
         description = self.tpl.get(DESCRIPTION)
@@ -279,3 +292,9 @@ class TopologyTemplate(object):
             func = functions.get_function(self, self.outputs, output.value)
             if isinstance(func, functions.GetAttribute):
                 output.attrs[output.VALUE] = func
+
+    @classmethod
+    def get_submaped_node_type(cls, topo_tpl):
+        if topo_tpl and isinstance(topo_tpl, dict):
+            submap_tpl = topo_tpl.get(SUBSTITUION_MAPPINGS)
+            return Substitution_mappings.get_node_type(submap_tpl)
index 5da34d6..fa97572 100644 (file)
@@ -64,13 +64,15 @@ class ToscaTemplate(object):
 
     '''Load the template data.'''
     def __init__(self, path=None, parsed_params=None, a_file=True,
-                 yaml_dict_tpl=None):
+                 yaml_dict_tpl=None, submaped_node_template=None):
         ExceptionCollector.start()
         self.a_file = a_file
         self.input_path = None
         self.path = None
         self.tpl = None
-        self.nested_tosca_template = []
+        self.submaped_node_template = submaped_node_template
+        self.nested_tosca_tpls = {}
+        self.nested_tosca_templates = []
         if path:
             self.input_path = path
             self.path = self._get_path(path)
@@ -102,6 +104,7 @@ class ToscaTemplate(object):
                 self.relationship_templates = self._relationship_templates()
                 self.nodetemplates = self._nodetemplates()
                 self.outputs = self._outputs()
+                self._handle_nested_topo_templates()
                 self.graph = ToscaGraph(self.nodetemplates)
 
         ExceptionCollector.stop()
@@ -111,7 +114,8 @@ class ToscaTemplate(object):
         return TopologyTemplate(self._tpl_topology_template(),
                                 self._get_all_custom_defs(),
                                 self.relationship_types,
-                                self.parsed_params)
+                                self.parsed_params,
+                                self.submaped_node_template)
 
     def _inputs(self):
         return self.topology_template.inputs
@@ -189,12 +193,12 @@ class ToscaTemplate(object):
             imports = self._tpl_imports()
 
         if imports:
-            custom_service = toscaparser.imports.\
-                ImportsLoader(imports, self.path,
-                              type_defs, self.tpl)
+            custom_service = \
+                toscaparser.imports.ImportsLoader(imports, self.path,
+                                                  type_defs, self.tpl)
 
             nested_topo_tpls = custom_service.get_nested_topo_tpls()
-            self._handle_nested_topo_tpls(nested_topo_tpls)
+            self._update_nested_topo_tpls(nested_topo_tpls)
 
             custom_defs = custom_service.get_custom_defs()
             if not custom_defs:
@@ -208,15 +212,26 @@ class ToscaTemplate(object):
                     custom_defs.update(inner_custom_types)
         return custom_defs
 
-    def _handle_nested_topo_tpls(self, nested_topo_tpls):
+    def _update_nested_topo_tpls(self, nested_topo_tpls):
         for tpl in nested_topo_tpls:
             filename, tosca_tpl = list(tpl.items())[0]
-            if tosca_tpl.get(TOPOLOGY_TEMPLATE):
-                nested_template = ToscaTemplate(
-                    path=filename, parsed_params=self.parsed_params,
-                    yaml_dict_tpl=tosca_tpl)
-                if nested_template.topology_template.substitution_mappings:
-                    self.nested_tosca_template.append(nested_template)
+            if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
+               filename not in list(self.nested_tosca_tpls.keys())):
+                self.nested_tosca_tpls.update(tpl)
+
+    def _handle_nested_topo_templates(self):
+        for filename, tosca_tpl in self.nested_tosca_tpls.items():
+            for nodetemplate in self.nodetemplates:
+                if self._is_substitution_mapped_node(nodetemplate, tosca_tpl):
+                    nested_template = ToscaTemplate(
+                        path=filename, parsed_params=self.parsed_params,
+                        yaml_dict_tpl=tosca_tpl,
+                        submaped_node_template=nodetemplate)
+                    if nested_template.has_substitution_mappings():
+                        filenames = [tpl.path for tpl in
+                                     self.nested_tosca_templates]
+                        if filename not in filenames:
+                            self.nested_tosca_templates.append(nested_template)
 
     def _validate_field(self):
         version = self._tpl_version()
@@ -280,3 +295,28 @@ class ToscaTemplate(object):
                 msg = _('The pre-parsed input successfully passed validation.')
 
             log.info(msg)
+
+    def _is_substitution_mapped_node(self, nodetemplate, tosca_tpl):
+        """Return True if the nodetemple is substituted."""
+        if (nodetemplate and not nodetemplate.substitution_mapped and
+                self.get_submaped_node_type(tosca_tpl) == nodetemplate.type and
+                len(nodetemplate.interfaces) < 1):
+            return True
+        else:
+            return False
+
+    def get_submaped_node_type(self, tosca_tpl):
+        """Return substitution mappings node type."""
+        if tosca_tpl:
+            return TopologyTemplate.get_submaped_node_type(
+                tosca_tpl.get(TOPOLOGY_TEMPLATE))
+
+    def has_substitution_mappings(self):
+        """Return True if the template has valid substitution mappings."""
+        return self.topology_template is not None and \
+            self.topology_template.substitution_mappings is not None
+
+    def has_nested_templates(self):
+        """Return True if the tosca template has nested templates."""
+        return self.nested_tosca_templates is not None and \
+            len(self.nested_tosca_templates) >= 1