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