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