55485f07dbddd97a9474f146932e7942c5dc0424
[sdc/sdc-distribution-client.git] /
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 from org.openecomp.sdc.toscaparser.jython import JyToscaTemplate
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(JyToscaTemplate):
57     exttools = ExtTools()
58
59     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
60
61     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
62
63     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
64
65     ADDITIONAL_SECTIONS.update(exttools.get_sections())
66
67     '''Load the template data.'''
68     def __init__(self, path=None, parsed_params=None, a_file=True,
69                  yaml_dict_tpl=None):
70
71         ExceptionCollector.start()
72         self.a_file = a_file
73         self.input_path = None
74         self.path = None
75         self.tpl = None
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._handle_nested_tosca_templates_with_topology()
110                 self.graph = ToscaGraph(self.nodetemplates)
111
112         ExceptionCollector.stop()
113         self.verify_template()
114         
115     def getJyVersion(self):
116         return self.version       
117      
118     def getJyDescription(self):
119         return self.description        
120      
121     def getJyTopologyTemplate(self):
122         return self.topology_template        
123
124     def _topology_template(self):
125         return TopologyTemplate(self._tpl_topology_template(),
126                                 self._get_all_custom_defs(),
127                                 self.relationship_types,
128                                 self.parsed_params,
129                                 None)
130
131     def _inputs(self):
132         return self.topology_template.inputs
133
134     def _nodetemplates(self):
135         return self.topology_template.nodetemplates
136
137     def _relationship_templates(self):
138         return self.topology_template.relationship_templates
139
140     def _outputs(self):
141         return self.topology_template.outputs
142
143     def _tpl_version(self):
144         return self.tpl.get(DEFINITION_VERSION)
145
146     def _tpl_description(self):
147         desc = self.tpl.get(DESCRIPTION)
148         if desc:
149             return desc.rstrip()
150
151     def _tpl_imports(self):
152         return self.tpl.get(IMPORTS)
153
154     def _tpl_repositories(self):
155         repositories = self.tpl.get(REPOSITORIES)
156         reposit = []
157         if repositories:
158             for name, val in repositories.items():
159                 reposits = Repository(name, val)
160                 reposit.append(reposits)
161         return reposit
162
163     def _tpl_relationship_types(self):
164         return self._get_custom_types(RELATIONSHIP_TYPES)
165
166     def _tpl_relationship_templates(self):
167         topology_template = self._tpl_topology_template()
168         return topology_template.get(RELATIONSHIP_TEMPLATES)
169
170     def _tpl_topology_template(self):
171         return self.tpl.get(TOPOLOGY_TEMPLATE)
172
173     def _get_all_custom_defs(self, imports=None):
174         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
175                  DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
176         custom_defs_final = {}
177         custom_defs = self._get_custom_types(types, imports)
178         if custom_defs:
179             custom_defs_final.update(custom_defs)
180             if custom_defs.get(IMPORTS):
181                 import_defs = self._get_all_custom_defs(
182                     custom_defs.get(IMPORTS))
183                 custom_defs_final.update(import_defs)
184
185         # As imports are not custom_types, removing from the dict
186         custom_defs_final.pop(IMPORTS, None)
187         return custom_defs_final
188
189     def _get_custom_types(self, type_definitions, imports=None):
190         """Handle custom types defined in imported template files
191
192         This method loads the custom type definitions referenced in "imports"
193         section of the TOSCA YAML template.
194         """
195
196         custom_defs = {}
197         type_defs = []
198         if not isinstance(type_definitions, list):
199             type_defs.append(type_definitions)
200         else:
201             type_defs = type_definitions
202
203         if not imports:
204             imports = self._tpl_imports()
205
206         if imports:
207             custom_service = toscaparser.imports.\
208                 ImportsLoader(imports, self.path,
209                               type_defs, self.tpl)
210
211             nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
212             self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
213
214             custom_defs = custom_service.get_custom_defs()
215             if not custom_defs:
216                 return
217
218         # Handle custom types defined in current template file
219         for type_def in type_defs:
220             if type_def != IMPORTS:
221                 inner_custom_types = self.tpl.get(type_def) or {}
222                 if inner_custom_types:
223                     custom_defs.update(inner_custom_types)
224         return custom_defs
225
226     def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
227         for tpl in nested_tosca_tpls:
228             filename, tosca_tpl = list(tpl.items())[0]
229             if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
230                 filename not in list(
231                     self.nested_tosca_tpls_with_topology.keys())):
232                 self.nested_tosca_tpls_with_topology.update(tpl)
233
234     def _handle_nested_tosca_templates_with_topology(self):
235         for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
236             for nodetemplate in self.nodetemplates:
237                 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
238                     parsed_params = self._get_params_for_nested_template(
239                         nodetemplate)
240                     topology_tpl = tosca_tpl.get(TOPOLOGY_TEMPLATE)
241                     topology_with_sub_mapping = TopologyTemplate(
242                         topology_tpl,
243                         self._get_all_custom_defs(),
244                         self.relationship_types,
245                         parsed_params,
246                         nodetemplate)
247                     if topology_with_sub_mapping.substitution_mappings:
248                         # Record nested topo templates in top level template
249                         self.nested_tosca_templates_with_topology.\
250                             append(topology_with_sub_mapping)
251                         # Set substitution mapping object for mapped node
252                         nodetemplate.sub_mapping_tosca_template = \
253                             topology_with_sub_mapping.substitution_mappings
254
255     def _validate_field(self):
256         version = self._tpl_version()
257         if not version:
258             ExceptionCollector.appendException(
259                 MissingRequiredFieldError(what='Template',
260                                           required=DEFINITION_VERSION))
261         else:
262             self._validate_version(version)
263             self.version = version
264
265         for name in self.tpl:
266             if (name not in SECTIONS and
267                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
268                 ExceptionCollector.appendException(
269                     UnknownFieldError(what='Template', field=name))
270
271     def _validate_version(self, version):
272         if version not in self.VALID_TEMPLATE_VERSIONS:
273             ExceptionCollector.appendException(
274                 InvalidTemplateVersion(
275                     what=version,
276                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
277         else:
278             if version != 'tosca_simple_yaml_1_0':
279                 update_definitions(version)
280
281     def _get_path(self, path):
282         if path.lower().endswith('.yaml'):
283             return path
284         elif path.lower().endswith(('.zip', '.csar')):
285             # a CSAR archive
286             csar = CSAR(path, self.a_file)
287             if csar.validate():
288                 csar.decompress()
289                 self.a_file = True  # the file has been decompressed locally
290                 return os.path.join(csar.temp_dir, csar.get_main_template())
291         else:
292             ExceptionCollector.appendException(
293                 ValueError(_('"%(path)s" is not a valid file.')
294                            % {'path': path}))
295
296     def verify_template(self):
297         if ExceptionCollector.exceptionsCaught():
298             if self.input_path:
299                 raise ValidationError(
300                     message=(_('\nThe input "%(path)s" failed validation with '
301                                'the following error(s): \n\n\t')
302                              % {'path': self.input_path}) +
303                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
304             else:
305                 raise ValidationError(
306                     message=_('\nThe pre-parsed input failed validation with '
307                               'the following error(s): \n\n\t') +
308                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
309         else:
310             if self.input_path:
311                 msg = (_('The input "%(path)s" successfully passed '
312                          'validation.') % {'path': self.input_path})
313             else:
314                 msg = _('The pre-parsed input successfully passed validation.')
315
316             log.info(msg)
317
318     def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
319         """Return True if the nodetemple is substituted."""
320         if (nodetemplate and not nodetemplate.sub_mapping_tosca_template and
321                 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
322                 and len(nodetemplate.interfaces) < 1):
323             return True
324         else:
325             return False
326
327     def _get_params_for_nested_template(self, nodetemplate):
328         """Return total params for nested_template."""
329         parsed_params = deepcopy(self.parsed_params) \
330             if self.parsed_params else {}
331         if nodetemplate:
332             for pname in nodetemplate.get_properties():
333                 parsed_params.update({pname:
334                                       nodetemplate.get_property_value(pname)})
335         return parsed_params
336
337     def get_sub_mapping_node_type(self, tosca_tpl):
338         """Return substitution mappings node type."""
339         if tosca_tpl:
340             return TopologyTemplate.get_sub_mapping_node_type(
341                 tosca_tpl.get(TOPOLOGY_TEMPLATE))
342
343     def _has_substitution_mappings(self):
344         """Return True if the template has valid substitution mappings."""
345         return self.topology_template is not None and \
346             self.topology_template.substitution_mappings is not None
347
348     def has_nested_templates(self):
349         """Return True if the tosca template has nested templates."""
350         return self.nested_tosca_templates_with_topology is not None and \
351             len(self.nested_tosca_templates_with_topology) >= 1