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