tosca-parser support the semantic of substitution mapping
[parser.git] / tosca2heat / tosca-parser / toscaparser / tosca_template.py
1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
2 #    not use this file except in compliance with the License. You may obtain
3 #    a copy of the License at
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
7 #    Unless required by applicable law or agreed to in writing, software
8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 #    License for the specific language governing permissions and limitations
11 #    under the License.
12
13
14 import logging
15 import os
16
17 from toscaparser.common.exception import ExceptionCollector
18 from toscaparser.common.exception import InvalidTemplateVersion
19 from toscaparser.common.exception import MissingRequiredFieldError
20 from toscaparser.common.exception import UnknownFieldError
21 from toscaparser.common.exception import ValidationError
22 from toscaparser.elements.entity_type import update_definitions
23 from toscaparser.extensions.exttools import ExtTools
24 import toscaparser.imports
25 from toscaparser.prereq.csar import CSAR
26 from toscaparser.repositories import Repository
27 from toscaparser.topology_template import TopologyTemplate
28 from toscaparser.tpl_relationship_graph import ToscaGraph
29 from toscaparser.utils.gettextutils import _
30 import toscaparser.utils.yamlparser
31
32
33 # TOSCA template key names
34 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
35             TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
36             DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
37             RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
38             CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES,
39             POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
40            ('tosca_definitions_version', 'tosca_default_namespace',
41             'template_name', 'topology_template', 'template_author',
42             'template_version', 'description', 'imports', 'dsl_definitions',
43             'node_types', 'relationship_types', 'relationship_templates',
44             'capability_types', 'artifact_types', 'data_types',
45             'policy_types', 'group_types', 'repositories')
46 # Sections that are specific to individual template definitions
47 SPECIAL_SECTIONS = (METADATA) = ('metadata')
48
49 log = logging.getLogger("tosca.model")
50
51 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
52
53
54 class ToscaTemplate(object):
55     exttools = ExtTools()
56
57     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
58
59     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
60
61     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
62
63     ADDITIONAL_SECTIONS.update(exttools.get_sections())
64
65     '''Load the template data.'''
66     def __init__(self, path=None, parsed_params=None, a_file=True,
67                  yaml_dict_tpl=None, submaped_node_template=None):
68         ExceptionCollector.start()
69         self.a_file = a_file
70         self.input_path = None
71         self.path = None
72         self.tpl = None
73         self.submaped_node_template = submaped_node_template
74         self.nested_tosca_tpls = {}
75         self.nested_tosca_templates = []
76         if path:
77             self.input_path = path
78             self.path = self._get_path(path)
79             if self.path:
80                 self.tpl = YAML_LOADER(self.path, self.a_file)
81             if yaml_dict_tpl:
82                 msg = (_('Both path and yaml_dict_tpl arguments were '
83                          'provided. Using path and ignoring yaml_dict_tpl.'))
84                 log.info(msg)
85                 print(msg)
86         else:
87             if yaml_dict_tpl:
88                 self.tpl = yaml_dict_tpl
89             else:
90                 ExceptionCollector.appendException(
91                     ValueError(_('No path or yaml_dict_tpl was provided. '
92                                  'There is nothing to parse.')))
93
94         if self.tpl:
95             self.parsed_params = parsed_params
96             self._validate_field()
97             self.version = self._tpl_version()
98             self.relationship_types = self._tpl_relationship_types()
99             self.description = self._tpl_description()
100             self.topology_template = self._topology_template()
101             self.repositories = self._tpl_repositories()
102             if self.topology_template.tpl:
103                 self.inputs = self._inputs()
104                 self.relationship_templates = self._relationship_templates()
105                 self.nodetemplates = self._nodetemplates()
106                 self.outputs = self._outputs()
107                 self._handle_nested_topo_templates()
108                 self.graph = ToscaGraph(self.nodetemplates)
109
110         ExceptionCollector.stop()
111         self.verify_template()
112
113     def _topology_template(self):
114         return TopologyTemplate(self._tpl_topology_template(),
115                                 self._get_all_custom_defs(),
116                                 self.relationship_types,
117                                 self.parsed_params,
118                                 self.submaped_node_template)
119
120     def _inputs(self):
121         return self.topology_template.inputs
122
123     def _nodetemplates(self):
124         return self.topology_template.nodetemplates
125
126     def _relationship_templates(self):
127         return self.topology_template.relationship_templates
128
129     def _outputs(self):
130         return self.topology_template.outputs
131
132     def _tpl_version(self):
133         return self.tpl.get(DEFINITION_VERSION)
134
135     def _tpl_description(self):
136         desc = self.tpl.get(DESCRIPTION)
137         if desc:
138             return desc.rstrip()
139
140     def _tpl_imports(self):
141         return self.tpl.get(IMPORTS)
142
143     def _tpl_repositories(self):
144         repositories = self.tpl.get(REPOSITORIES)
145         reposit = []
146         if repositories:
147             for name, val in repositories.items():
148                 reposits = Repository(name, val)
149                 reposit.append(reposits)
150         return reposit
151
152     def _tpl_relationship_types(self):
153         return self._get_custom_types(RELATIONSHIP_TYPES)
154
155     def _tpl_relationship_templates(self):
156         topology_template = self._tpl_topology_template()
157         return topology_template.get(RELATIONSHIP_TEMPLATES)
158
159     def _tpl_topology_template(self):
160         return self.tpl.get(TOPOLOGY_TEMPLATE)
161
162     def _get_all_custom_defs(self, imports=None):
163         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
164                  DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
165         custom_defs_final = {}
166         custom_defs = self._get_custom_types(types, imports)
167         if custom_defs:
168             custom_defs_final.update(custom_defs)
169             if custom_defs.get(IMPORTS):
170                 import_defs = self._get_all_custom_defs(
171                     custom_defs.get(IMPORTS))
172                 custom_defs_final.update(import_defs)
173
174         # As imports are not custom_types, removing from the dict
175         custom_defs_final.pop(IMPORTS, None)
176         return custom_defs_final
177
178     def _get_custom_types(self, type_definitions, imports=None):
179         """Handle custom types defined in imported template files
180
181         This method loads the custom type definitions referenced in "imports"
182         section of the TOSCA YAML template.
183         """
184
185         custom_defs = {}
186         type_defs = []
187         if not isinstance(type_definitions, list):
188             type_defs.append(type_definitions)
189         else:
190             type_defs = type_definitions
191
192         if not imports:
193             imports = self._tpl_imports()
194
195         if imports:
196             custom_service = \
197                 toscaparser.imports.ImportsLoader(imports, self.path,
198                                                   type_defs, self.tpl)
199
200             nested_topo_tpls = custom_service.get_nested_topo_tpls()
201             self._update_nested_topo_tpls(nested_topo_tpls)
202
203             custom_defs = custom_service.get_custom_defs()
204             if not custom_defs:
205                 return
206
207         # Handle custom types defined in current template file
208         for type_def in type_defs:
209             if type_def != IMPORTS:
210                 inner_custom_types = self.tpl.get(type_def) or {}
211                 if inner_custom_types:
212                     custom_defs.update(inner_custom_types)
213         return custom_defs
214
215     def _update_nested_topo_tpls(self, nested_topo_tpls):
216         for tpl in nested_topo_tpls:
217             filename, tosca_tpl = list(tpl.items())[0]
218             if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
219                filename not in list(self.nested_tosca_tpls.keys())):
220                 self.nested_tosca_tpls.update(tpl)
221
222     def _handle_nested_topo_templates(self):
223         for filename, tosca_tpl in self.nested_tosca_tpls.items():
224             for nodetemplate in self.nodetemplates:
225                 if self._is_substitution_mapped_node(nodetemplate, tosca_tpl):
226                     nested_template = ToscaTemplate(
227                         path=filename, parsed_params=self.parsed_params,
228                         yaml_dict_tpl=tosca_tpl,
229                         submaped_node_template=nodetemplate)
230                     if nested_template.has_substitution_mappings():
231                         filenames = [tpl.path for tpl in
232                                      self.nested_tosca_templates]
233                         if filename not in filenames:
234                             self.nested_tosca_templates.append(nested_template)
235
236     def _validate_field(self):
237         version = self._tpl_version()
238         if not version:
239             ExceptionCollector.appendException(
240                 MissingRequiredFieldError(what='Template',
241                                           required=DEFINITION_VERSION))
242         else:
243             self._validate_version(version)
244             self.version = version
245
246         for name in self.tpl:
247             if (name not in SECTIONS and
248                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
249                 ExceptionCollector.appendException(
250                     UnknownFieldError(what='Template', field=name))
251
252     def _validate_version(self, version):
253         if version not in self.VALID_TEMPLATE_VERSIONS:
254             ExceptionCollector.appendException(
255                 InvalidTemplateVersion(
256                     what=version,
257                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
258         else:
259             if version != 'tosca_simple_yaml_1_0':
260                 update_definitions(version)
261
262     def _get_path(self, path):
263         if path.lower().endswith('.yaml'):
264             return path
265         elif path.lower().endswith(('.zip', '.csar')):
266             # a CSAR archive
267             csar = CSAR(path, self.a_file)
268             if csar.validate():
269                 csar.decompress()
270                 self.a_file = True  # the file has been decompressed locally
271                 return os.path.join(csar.temp_dir, csar.get_main_template())
272         else:
273             ExceptionCollector.appendException(
274                 ValueError(_('"%(path)s" is not a valid file.')
275                            % {'path': path}))
276
277     def verify_template(self):
278         if ExceptionCollector.exceptionsCaught():
279             if self.input_path:
280                 raise ValidationError(
281                     message=(_('\nThe input "%(path)s" failed validation with '
282                                'the following error(s): \n\n\t')
283                              % {'path': self.input_path}) +
284                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
285             else:
286                 raise ValidationError(
287                     message=_('\nThe pre-parsed input failed validation with '
288                               'the following error(s): \n\n\t') +
289                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
290         else:
291             if self.input_path:
292                 msg = (_('The input "%(path)s" successfully passed '
293                          'validation.') % {'path': self.input_path})
294             else:
295                 msg = _('The pre-parsed input successfully passed validation.')
296
297             log.info(msg)
298
299     def _is_substitution_mapped_node(self, nodetemplate, tosca_tpl):
300         """Return True if the nodetemple is substituted."""
301         if (nodetemplate and not nodetemplate.substitution_mapped and
302                 self.get_submaped_node_type(tosca_tpl) == nodetemplate.type and
303                 len(nodetemplate.interfaces) < 1):
304             return True
305         else:
306             return False
307
308     def get_submaped_node_type(self, tosca_tpl):
309         """Return substitution mappings node type."""
310         if tosca_tpl:
311             return TopologyTemplate.get_submaped_node_type(
312                 tosca_tpl.get(TOPOLOGY_TEMPLATE))
313
314     def has_substitution_mappings(self):
315         """Return True if the template has valid substitution mappings."""
316         return self.topology_template is not None and \
317             self.topology_template.substitution_mappings is not None
318
319     def has_nested_templates(self):
320         """Return True if the tosca template has nested templates."""
321         return self.nested_tosca_templates is not None and \
322             len(self.nested_tosca_templates) >= 1