Fix substitution mapping assigned value to nodetemplate
[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 copy import deepcopy
18 from toscaparser.common.exception import ExceptionCollector
19 from toscaparser.common.exception import InvalidTemplateVersion
20 from toscaparser.common.exception import MissingRequiredFieldError
21 from toscaparser.common.exception import MissingRequiredParameterError
22 from toscaparser.common.exception import UnknownFieldError
23 from toscaparser.common.exception import ValidationError
24 from toscaparser.elements.entity_type import update_definitions
25 from toscaparser.extensions.exttools import ExtTools
26 import toscaparser.imports
27 from toscaparser.prereq.csar import CSAR
28 from toscaparser.repositories import Repository
29 from toscaparser.topology_template import TopologyTemplate
30 from toscaparser.tpl_relationship_graph import ToscaGraph
31 from toscaparser.utils.gettextutils import _
32 import toscaparser.utils.yamlparser
33
34
35 # TOSCA template key names
36 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
37             TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
38             DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
39             RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
40             CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES, INTERFACE_TYPES,
41             POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
42            ('tosca_definitions_version', 'tosca_default_namespace',
43             'template_name', 'topology_template', 'template_author',
44             'template_version', 'description', 'imports', 'dsl_definitions',
45             'node_types', 'relationship_types', 'relationship_templates',
46             'capability_types', 'artifact_types', 'data_types',
47             'interface_types', 'policy_types', 'group_types', 'repositories')
48 # Sections that are specific to individual template definitions
49 SPECIAL_SECTIONS = (METADATA) = ('metadata')
50
51 log = logging.getLogger("tosca.model")
52
53 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
54
55
56 class ToscaTemplate(object):
57     exttools = ExtTools()
58
59     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0',
60                                'tosca_simple_yaml_1_1']
61
62     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
63
64     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS,
65                            'tosca_simple_yaml_1_1': SPECIAL_SECTIONS}
66
67     ADDITIONAL_SECTIONS.update(exttools.get_sections())
68
69     '''Load the template data.'''
70     def __init__(self, path=None, parsed_params=None, a_file=True,
71                  yaml_dict_tpl=None, sub_mapped_node_template=None,
72                  no_required_paras_check=False,
73                  debug_mode=False):
74         if sub_mapped_node_template is None:
75             ExceptionCollector.start()
76         self.a_file = a_file
77         self.input_path = None
78         self.path = None
79         self.tpl = None
80         self.sub_mapped_node_template = sub_mapped_node_template
81         self.nested_tosca_tpls_with_topology = {}
82         self.nested_tosca_templates_with_topology = []
83         self.no_required_paras_check = no_required_paras_check
84         self.debug_mode = debug_mode
85
86         if path:
87             self.input_path = path
88             self.path = self._get_path(path)
89             if self.path:
90                 self.tpl = YAML_LOADER(self.path, self.a_file)
91             if yaml_dict_tpl:
92                 msg = (_('Both path and yaml_dict_tpl arguments were '
93                          'provided. Using path and ignoring yaml_dict_tpl.'))
94                 log.info(msg)
95                 print(msg)
96         else:
97             if yaml_dict_tpl:
98                 self.tpl = yaml_dict_tpl
99             else:
100                 ExceptionCollector.appendException(
101                     ValueError(_('No path or yaml_dict_tpl was provided. '
102                                  'There is nothing to parse.')))
103
104         if self.tpl:
105             self.parsed_params = parsed_params
106             self._validate_field()
107             self.version = self._tpl_version()
108             self.relationship_types = self._tpl_relationship_types()
109             self.description = self._tpl_description()
110             self.topology_template = self._topology_template()
111             self.repositories = self._tpl_repositories()
112             if self.topology_template.tpl:
113                 self.inputs = self._inputs()
114                 self.relationship_templates = self._relationship_templates()
115                 self.nodetemplates = self._nodetemplates()
116                 self.outputs = self._outputs()
117                 self.policies = self._policies()
118                 self._handle_nested_tosca_templates_with_topology()
119                 self.graph = ToscaGraph(self.nodetemplates)
120
121         if sub_mapped_node_template is None:
122             ExceptionCollector.stop()
123         self.verify_template()
124
125     def _topology_template(self):
126         return TopologyTemplate(self._tpl_topology_template(),
127                                 self._get_all_custom_defs(),
128                                 self.relationship_types,
129                                 self.parsed_params,
130                                 self.sub_mapped_node_template)
131
132     def _inputs(self):
133         return self.topology_template.inputs
134
135     def _nodetemplates(self):
136         return self.topology_template.nodetemplates
137
138     def _relationship_templates(self):
139         return self.topology_template.relationship_templates
140
141     def _outputs(self):
142         return self.topology_template.outputs
143
144     def _tpl_version(self):
145         return self.tpl.get(DEFINITION_VERSION)
146
147     def _tpl_description(self):
148         desc = self.tpl.get(DESCRIPTION)
149         if desc:
150             return desc.rstrip()
151
152     def _tpl_imports(self):
153         return self.tpl.get(IMPORTS)
154
155     def _tpl_repositories(self):
156         repositories = self.tpl.get(REPOSITORIES)
157         reposit = []
158         if repositories:
159             for name, val in repositories.items():
160                 reposits = Repository(name, val)
161                 reposit.append(reposits)
162         return reposit
163
164     def _tpl_relationship_types(self):
165         return self._get_custom_types(RELATIONSHIP_TYPES)
166
167     def _tpl_relationship_templates(self):
168         topology_template = self._tpl_topology_template()
169         return topology_template.get(RELATIONSHIP_TEMPLATES)
170
171     def _tpl_topology_template(self):
172         return self.tpl.get(TOPOLOGY_TEMPLATE)
173
174     def _policies(self):
175         return self.topology_template.policies
176
177     def _get_all_custom_defs(self, imports=None):
178         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
179                  DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
180         custom_defs_final = {}
181         custom_defs = self._get_custom_types(types, imports)
182         if custom_defs:
183             custom_defs_final.update(custom_defs)
184             if custom_defs.get(IMPORTS):
185                 import_defs = self._get_all_custom_defs(
186                     custom_defs.get(IMPORTS))
187                 custom_defs_final.update(import_defs)
188
189         # As imports are not custom_types, removing from the dict
190         custom_defs_final.pop(IMPORTS, None)
191         return custom_defs_final
192
193     def _get_custom_types(self, type_definitions, imports=None):
194         """Handle custom types defined in imported template files
195
196         This method loads the custom type definitions referenced in "imports"
197         section of the TOSCA YAML template.
198         """
199
200         custom_defs = {}
201         type_defs = []
202         if not isinstance(type_definitions, list):
203             type_defs.append(type_definitions)
204         else:
205             type_defs = type_definitions
206
207         if not imports:
208             imports = self._tpl_imports()
209
210         if imports:
211             custom_service = toscaparser.imports.\
212                 ImportsLoader(imports, self.path,
213                               type_defs, self.tpl)
214
215             nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
216             self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
217
218             custom_defs = custom_service.get_custom_defs()
219             if not custom_defs:
220                 return
221
222         # Handle custom types defined in current template file
223         for type_def in type_defs:
224             if type_def != IMPORTS:
225                 inner_custom_types = self.tpl.get(type_def) or {}
226                 if inner_custom_types:
227                     custom_defs.update(inner_custom_types)
228         return custom_defs
229
230     def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
231         for tpl in nested_tosca_tpls:
232             filename, tosca_tpl = list(tpl.items())[0]
233             if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
234                 filename not in list(
235                     self.nested_tosca_tpls_with_topology.keys())):
236                 self.nested_tosca_tpls_with_topology.update(tpl)
237
238     def _handle_nested_tosca_templates_with_topology(self):
239         for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
240             for nodetemplate in self.nodetemplates:
241                 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
242                     parsed_params = self._get_params_for_nested_template(
243                         nodetemplate)
244
245                     cache_exeptions = deepcopy(ExceptionCollector.exceptions)
246                     cache_exeptions_state = \
247                         deepcopy(ExceptionCollector.collecting)
248                     nested_template = None
249                     try:
250                         nrpv = self.no_required_paras_check
251                         nested_template = ToscaTemplate(
252                             path=fname, parsed_params=parsed_params,
253                             sub_mapped_node_template=nodetemplate,
254                             no_required_paras_check=nrpv,
255                             debug_mode=self.debug_mode)
256                     except ValidationError as e:
257                         log.error(e.message)
258                         if self.debug_mode:
259                             print(e.message)
260                         else:
261                             raise e
262
263                     ExceptionCollector.exceptions = deepcopy(cache_exeptions)
264                     ExceptionCollector.collecting = \
265                         deepcopy(cache_exeptions_state)
266
267                     if nested_template and \
268                             nested_template._has_substitution_mappings():
269                         # Record the nested templates in top level template
270                         self.nested_tosca_templates_with_topology.\
271                             append(nested_template)
272                         # Set the substitution toscatemplate for mapped node
273                         nodetemplate.substitution_mapped = \
274                             nested_template
275
276     def _validate_field(self):
277         version = self._tpl_version()
278         if not version:
279             ExceptionCollector.appendException(
280                 MissingRequiredFieldError(what='Template',
281                                           required=DEFINITION_VERSION))
282         else:
283             self._validate_version(version)
284             self.version = version
285
286         for name in self.tpl:
287             if (name not in SECTIONS and
288                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
289                 ExceptionCollector.appendException(
290                     UnknownFieldError(what='Template', field=name))
291
292     def _validate_version(self, version):
293         if version not in self.VALID_TEMPLATE_VERSIONS:
294             ExceptionCollector.appendException(
295                 InvalidTemplateVersion(
296                     what=version,
297                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
298         else:
299             if (version != 'tosca_simple_yaml_1_0' and
300                     version != 'tosca_simple_yaml_1_1'):
301                 update_definitions(version)
302
303     def _get_path(self, path):
304         if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
305             return path
306         elif path.lower().endswith(('.zip', '.csar')):
307             # a CSAR archive
308             csar = CSAR(path, self.a_file)
309             if csar.validate():
310                 csar.decompress()
311                 self.a_file = True  # the file has been decompressed locally
312                 return os.path.join(csar.temp_dir, csar.get_main_template())
313         else:
314             ExceptionCollector.appendException(
315                 ValueError(_('"%(path)s" is not a valid file.')
316                            % {'path': path}))
317
318     def verify_template(self):
319         if ExceptionCollector.exceptionsCaught():
320             if self.no_required_paras_check:
321                 ExceptionCollector.removeException(
322                     MissingRequiredParameterError)
323
324             if self.input_path:
325                 exceptions = ValidationError(
326                     message=(_('\nThe input "%(path)s" failed validation with '
327                                'the following error(s): \n\n\t')
328                              % {'path': self.input_path}) +
329                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
330             else:
331                 exceptions = ValidationError(
332                     message=_('\nThe pre-parsed input failed validation with '
333                               'the following error(s): \n\n\t') +
334                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
335             if not self.debug_mode:
336                 raise exceptions
337             else:
338                 if self.sub_mapped_node_template:
339                     msg = _('======== nested service template ======== ')
340
341                 else:
342                     msg = _('======== main service template ======== ')
343                 print(msg)
344                 print(exceptions.message)
345                 log.error(msg)
346                 log.error(exceptions.message)
347         else:
348             if self.input_path:
349                 msg = (_('The input "%(path)s" successfully passed '
350                          'validation.') % {'path': self.input_path})
351             else:
352                 msg = _('The pre-parsed input successfully passed validation.')
353
354             log.info(msg)
355
356     def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
357         """Return True if the nodetemple is substituted."""
358         if (nodetemplate and not nodetemplate.substitution_mapped and
359                 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
360                 and len(nodetemplate.interfaces) < 1):
361             return True
362         else:
363             return False
364
365     def _get_params_for_nested_template(self, nodetemplate):
366         """Return total params for nested_template."""
367         parsed_params = deepcopy(self.parsed_params) \
368             if self.parsed_params else {}
369         if nodetemplate:
370             for pname in nodetemplate.get_properties():
371                 parsed_params.update({pname:
372                                       nodetemplate.get_property_value(pname)})
373         return parsed_params
374
375     def get_sub_mapping_node_type(self, tosca_tpl):
376         """Return substitution mappings node type."""
377         if tosca_tpl:
378             return TopologyTemplate.get_sub_mapping_node_type(
379                 tosca_tpl.get(TOPOLOGY_TEMPLATE))
380
381     def _has_substitution_mappings(self):
382         """Return True if the template has valid substitution mappings."""
383         return self.topology_template is not None and \
384             self.topology_template.substitution_mappings is not None
385
386     def has_nested_templates(self):
387         """Return True if the tosca template has nested templates."""
388         return self.nested_tosca_templates_with_topology is not None and \
389             len(self.nested_tosca_templates_with_topology) >= 1