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