docs: fix issues
[parser.git] / tosca2heat / tosca-parser-0.3.0 / toscaparser / functions.py
1 #
2 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
3 #    not use this file except in compliance with the License. You may obtain
4 #    a copy of the License at
5 #
6 #         http://www.apache.org/licenses/LICENSE-2.0
7 #
8 #    Unless required by applicable law or agreed to in writing, software
9 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 #    License for the specific language governing permissions and limitations
12 #    under the License.
13
14
15 import abc
16 import six
17
18 from toscaparser.common.exception import ExceptionCollector
19 from toscaparser.common.exception import UnknownInputError
20 from toscaparser.dataentity import DataEntity
21 from toscaparser.utils.gettextutils import _
22
23
24 GET_PROPERTY = 'get_property'
25 GET_ATTRIBUTE = 'get_attribute'
26 GET_INPUT = 'get_input'
27
28 SELF = 'SELF'
29 HOST = 'HOST'
30
31 HOSTED_ON = 'tosca.relationships.HostedOn'
32
33
34 @six.add_metaclass(abc.ABCMeta)
35 class Function(object):
36     """An abstract type for representing a Tosca template function."""
37
38     def __init__(self, tosca_tpl, context, name, args):
39         self.tosca_tpl = tosca_tpl
40         self.context = context
41         self.name = name
42         self.args = args
43         self.validate()
44
45     @abc.abstractmethod
46     def result(self):
47         """Invokes the function and returns its result
48
49         Some methods invocation may only be relevant on runtime (for example,
50         getting runtime properties) and therefore its the responsibility of
51         the orchestrator/translator to take care of such functions invocation.
52
53         :return: Function invocation result.
54         """
55         return {self.name: self.args}
56
57     @abc.abstractmethod
58     def validate(self):
59         """Validates function arguments."""
60         pass
61
62
63 class GetInput(Function):
64     """Get a property value declared within the input of the service template.
65
66     Arguments:
67
68     * Input name.
69
70     Example:
71
72     * get_input: port
73     """
74
75     def validate(self):
76         if len(self.args) != 1:
77             ExceptionCollector.appendException(
78                 ValueError(_(
79                     'Expected one argument for function "get_input" but '
80                     'received "%s".') % self.args))
81         inputs = [input.name for input in self.tosca_tpl.inputs]
82         if self.args[0] not in inputs:
83             ExceptionCollector.appendException(
84                 UnknownInputError(input_name=self.args[0]))
85
86     def result(self):
87         if self.tosca_tpl.parsed_params and \
88            self.input_name in self.tosca_tpl.parsed_params:
89             return DataEntity.validate_datatype(
90                 self.tosca_tpl.tpl['inputs'][self.input_name]['type'],
91                 self.tosca_tpl.parsed_params[self.input_name])
92
93         input = [input_def for input_def in self.tosca_tpl.inputs
94                  if self.input_name == input_def.name][0]
95         return input.default
96
97     @property
98     def input_name(self):
99         return self.args[0]
100
101
102 class GetAttribute(Function):
103     """Get an attribute value of an entity defined in the service template
104
105     Node template attributes values are set in runtime and therefore its the
106     responsibility of the Tosca engine to implement the evaluation of
107     get_attribute functions.
108
109     Arguments:
110
111     * Node template name | HOST.
112     * Attribute name.
113
114     If the HOST keyword is passed as the node template name argument the
115     function will search each node template along the HostedOn relationship
116     chain until a node which contains the attribute is found.
117
118     Examples:
119
120     * { get_attribute: [ server, private_address ] }
121     * { get_attribute: [ HOST, private_address ] }
122     """
123
124     def validate(self):
125         if len(self.args) != 2:
126             ExceptionCollector.appendException(
127                 ValueError(_('Illegal arguments for function "{0}". Expected '
128                              'arguments: "node-template-name", '
129                              '"attribute-name"').format(GET_ATTRIBUTE)))
130         self._find_node_template_containing_attribute()
131
132     def result(self):
133         return self.args
134
135     def get_referenced_node_template(self):
136         """Gets the NodeTemplate instance the get_attribute function refers to.
137
138         If HOST keyword was used as the node template argument, the node
139         template which contains the attribute along the HostedOn relationship
140         chain will be returned.
141         """
142         return self._find_node_template_containing_attribute()
143
144     def _find_node_template_containing_attribute(self):
145         if self.node_template_name == HOST:
146             # Currently this is the only way to tell whether the function
147             # is used within the outputs section of the TOSCA template.
148             if isinstance(self.context, list):
149                 ExceptionCollector.appendException(
150                     ValueError(_(
151                         '"get_attribute: [ HOST, ... ]" is not allowed in '
152                         '"outputs" section of the TOSCA template.')))
153                 return
154             node_tpl = self._find_host_containing_attribute()
155             if not node_tpl:
156                 ExceptionCollector.appendException(
157                     ValueError(_(
158                         '"get_attribute: [ HOST, ... ]" was used in node '
159                         'template "{0}" but "{1}" was not found in '
160                         'the relationship chain.').format(self.context.name,
161                                                           HOSTED_ON)))
162         else:
163             node_tpl = self._find_node_template(self.args[0])
164         if node_tpl and \
165             not self._attribute_exists_in_type(node_tpl.type_definition):
166             ExceptionCollector.appendException(
167                 KeyError(_('Attribute "%(att)s" was not found in node '
168                            'template "%(ntpl)s".') %
169                          {'att': self.attribute_name,
170                           'ntpl': node_tpl.name}))
171         return node_tpl
172
173     def _attribute_exists_in_type(self, type_definition):
174         attrs_def = type_definition.get_attributes_def()
175         found = [attrs_def[self.attribute_name]] \
176             if self.attribute_name in attrs_def else []
177         return len(found) == 1
178
179     def _find_host_containing_attribute(self, node_template_name=SELF):
180         node_template = self._find_node_template(node_template_name)
181         if node_template:
182             from toscaparser.elements.entity_type import EntityType
183             hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
184             for r in node_template.requirements:
185                 for requirement, target_name in r.items():
186                     target_node = self._find_node_template(target_name)
187                     target_type = target_node.type_definition
188                     for capability in target_type.get_capabilities_objects():
189                         if capability.type in \
190                             hosted_on_rel['valid_target_types']:
191                             if self._attribute_exists_in_type(target_type):
192                                 return target_node
193                             return self._find_host_containing_attribute(
194                                 target_name)
195
196     def _find_node_template(self, node_template_name):
197         name = self.context.name \
198             if node_template_name == SELF and \
199             not isinstance(self.context, list) \
200             else node_template_name
201         for node_template in self.tosca_tpl.nodetemplates:
202             if node_template.name == name:
203                 return node_template
204         ExceptionCollector.appendException(
205             KeyError(_(
206                 'Node template "{0}" was not found.'
207                 ).format(node_template_name)))
208
209     @property
210     def node_template_name(self):
211         return self.args[0]
212
213     @property
214     def attribute_name(self):
215         return self.args[1]
216
217
218 class GetProperty(Function):
219     """Get a property value of an entity defined in the same service template.
220
221     Arguments:
222
223     * Node template name.
224     * Requirement or capability name (optional).
225     * Property name.
226
227     If requirement or capability name is specified, the behavior is as follows:
228     The req or cap name is first looked up in the specified node template's
229     requirements.
230     If found, it would search for a matching capability
231     of an other node template and get its property as specified in function
232     arguments.
233     Otherwise, the req or cap name would be looked up in the specified
234     node template's capabilities and if found, it would return  the property of
235     the capability as specified in function arguments.
236
237     Examples:
238
239     * { get_property: [ mysql_server, port ] }
240     * { get_property: [ SELF, db_port ] }
241     * { get_property: [ SELF, database_endpoint, port ] }
242     """
243
244     def validate(self):
245         if len(self.args) < 2 or len(self.args) > 3:
246             ExceptionCollector.appendException(
247                 ValueError(_(
248                     'Expected arguments: "node-template-name", "req-or-cap" '
249                     '(optional), "property name".')))
250             return
251         if len(self.args) == 2:
252             found_prop = self._find_property(self.args[1])
253             if not found_prop:
254                 return
255             prop = found_prop.value
256             if not isinstance(prop, Function):
257                 get_function(self.tosca_tpl, self.context, prop)
258         elif len(self.args) == 3:
259             get_function(self.tosca_tpl,
260                          self.context,
261                          self._find_req_or_cap_property(self.args[1],
262                                                         self.args[2]))
263         else:
264             ExceptionCollector.appendException(
265                 NotImplementedError(_(
266                     'Nested properties are not supported.')))
267
268     def _find_req_or_cap_property(self, req_or_cap, property_name):
269         node_tpl = self._find_node_template(self.args[0])
270         # Find property in node template's requirements
271         for r in node_tpl.requirements:
272             for req, node_name in r.items():
273                 if req == req_or_cap:
274                     node_template = self._find_node_template(node_name)
275                     return self._get_capability_property(
276                         node_template,
277                         req,
278                         property_name)
279         # If requirement was not found, look in node template's capabilities
280         return self._get_capability_property(node_tpl,
281                                              req_or_cap,
282                                              property_name)
283
284     def _get_capability_property(self,
285                                  node_template,
286                                  capability_name,
287                                  property_name):
288         """Gets a node template capability property."""
289         caps = node_template.get_capabilities()
290         if caps and capability_name in caps.keys():
291             cap = caps[capability_name]
292             property = None
293             props = cap.get_properties()
294             if props and property_name in props.keys():
295                 property = props[property_name].value
296             if not property:
297                 ExceptionCollector.appendException(
298                     KeyError(_('Property "%(prop)s" was not found in '
299                                'capability "%(cap)s" of node template '
300                                '"%(ntpl1)s" referenced from node template '
301                                '"%(ntpl2)s".') % {'prop': property_name,
302                                                   'cap': capability_name,
303                                                   'ntpl1': node_template.name,
304                                                   'ntpl2': self.context.name}))
305             return property
306         msg = _('Requirement/Capability "{0}" referenced from node template '
307                 '"{1}" was not found in node template "{2}".').format(
308                     capability_name,
309                     self.context.name,
310                     node_template.name)
311         ExceptionCollector.appendException(KeyError(msg))
312
313     def _find_property(self, property_name):
314         node_tpl = self._find_node_template(self.args[0])
315         if not node_tpl:
316             return
317         props = node_tpl.get_properties()
318         found = [props[property_name]] if property_name in props else []
319         if len(found) == 0:
320             ExceptionCollector.appendException(
321                 KeyError(_('Property "%(prop)s" was not found in node '
322                            'template "%(ntpl)s".') %
323                          {'prop': property_name,
324                           'ntpl': node_tpl.name}))
325             return None
326         return found[0]
327
328     def _find_node_template(self, node_template_name):
329         if node_template_name == SELF:
330             return self.context
331         if not hasattr(self.tosca_tpl, 'nodetemplates'):
332             return
333         for node_template in self.tosca_tpl.nodetemplates:
334             if node_template.name == node_template_name:
335                 return node_template
336         ExceptionCollector.appendException(
337             KeyError(_(
338                 'Node template "{0}" was not found.'
339                 ).format(node_template_name)))
340
341     def result(self):
342         if len(self.args) == 3:
343             property_value = self._find_req_or_cap_property(self.args[1],
344                                                             self.args[2])
345         else:
346             property_value = self._find_property(self.args[1]).value
347         if isinstance(property_value, Function):
348             return property_value.result()
349         return get_function(self.tosca_tpl,
350                             self.context,
351                             property_value)
352
353     @property
354     def node_template_name(self):
355         return self.args[0]
356
357     @property
358     def property_name(self):
359         if len(self.args) > 2:
360             return self.args[2]
361         return self.args[1]
362
363     @property
364     def req_or_cap(self):
365         if len(self.args) > 2:
366             return self.args[1]
367         return None
368
369
370 function_mappings = {
371     GET_PROPERTY: GetProperty,
372     GET_INPUT: GetInput,
373     GET_ATTRIBUTE: GetAttribute
374 }
375
376
377 def is_function(function):
378     """Returns True if the provided function is a Tosca intrinsic function.
379
380     Examples:
381
382     * "{ get_property: { SELF, port } }"
383     * "{ get_input: db_name }"
384     * Function instance
385
386     :param function: Function as string or a Function instance.
387     :return: True if function is a Tosca intrinsic function, otherwise False.
388     """
389     if isinstance(function, dict) and len(function) == 1:
390         func_name = list(function.keys())[0]
391         return func_name in function_mappings
392     return isinstance(function, Function)
393
394
395 def get_function(tosca_tpl, node_template, raw_function):
396     """Gets a Function instance representing the provided template function.
397
398     If the format provided raw_function format is not relevant for template
399     functions or if the function name doesn't exist in function mapping the
400     method returns the provided raw_function.
401
402     :param tosca_tpl: The tosca template.
403     :param node_template: The node template the function is specified for.
404     :param raw_function: The raw function as dict.
405     :return: Template function as Function instance or the raw_function if
406      parsing was unsuccessful.
407     """
408     if is_function(raw_function):
409         func_name = list(raw_function.keys())[0]
410         if func_name in function_mappings:
411             func = function_mappings[func_name]
412             func_args = list(raw_function.values())[0]
413             if not isinstance(func_args, list):
414                 func_args = [func_args]
415             return func(tosca_tpl, node_template, func_name, func_args)
416     return raw_function