From ed2f6006158e120159f4422bc626cc0d5fe5cecf Mon Sep 17 00:00:00 2001 From: shangxdy Date: Thu, 21 Jul 2016 20:02:51 +0800 Subject: tosca-parser support the semantic of substitution mapping 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 --- .../tosca-parser/toscaparser/nodetemplate.py | 1 + .../toscaparser/substitution_mappings.py | 155 +++++++++++++++++++++ .../toscaparser/tests/test_topology_template.py | 2 +- .../tosca-parser/toscaparser/topology_template.py | 29 +++- .../tosca-parser/toscaparser/tosca_template.py | 68 +++++++-- 5 files changed, 235 insertions(+), 20 deletions(-) create mode 100644 tosca2heat/tosca-parser/toscaparser/substitution_mappings.py (limited to 'tosca2heat/tosca-parser/toscaparser') diff --git a/tosca2heat/tosca-parser/toscaparser/nodetemplate.py b/tosca2heat/tosca-parser/toscaparser/nodetemplate.py index d969d51..d90b73a 100644 --- a/tosca2heat/tosca-parser/toscaparser/nodetemplate.py +++ b/tosca2heat/tosca-parser/toscaparser/nodetemplate.py @@ -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 index 0000000..40ff3ca --- /dev/null +++ b/tosca2heat/tosca-parser/toscaparser/substitution_mappings.py @@ -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)) diff --git a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py index 14f2496..f66396e 100644 --- a/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tests/test_topology_template.py @@ -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) diff --git a/tosca2heat/tosca-parser/toscaparser/topology_template.py b/tosca2heat/tosca-parser/toscaparser/topology_template.py index 6cf4f31..70bc7d6 100644 --- a/tosca2heat/tosca-parser/toscaparser/topology_template.py +++ b/tosca2heat/tosca-parser/toscaparser/topology_template.py @@ -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) diff --git a/tosca2heat/tosca-parser/toscaparser/tosca_template.py b/tosca2heat/tosca-parser/toscaparser/tosca_template.py index 5da34d6..fa97572 100644 --- a/tosca2heat/tosca-parser/toscaparser/tosca_template.py +++ b/tosca2heat/tosca-parser/toscaparser/tosca_template.py @@ -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 -- cgit 1.2.3-korg