Merge "Release D doc update"
[parser.git] / tosca2heat / tosca-parser / 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 import toscaparser.elements.interfaces
18
19 from toscaparser.common.exception import ExceptionCollector
20 from toscaparser.common.exception import UnknownInputError
21 from toscaparser.dataentity import DataEntity
22 from toscaparser.elements.constraints import Schema
23 from toscaparser.elements.datatype import DataType
24 from toscaparser.elements.entity_type import EntityType
25 from toscaparser.elements.relationshiptype import RelationshipType
26 from toscaparser.elements.statefulentitytype import StatefulEntityType
27 from toscaparser.utils.gettextutils import _
28
29
30 GET_PROPERTY = 'get_property'
31 GET_ATTRIBUTE = 'get_attribute'
32 GET_INPUT = 'get_input'
33 GET_OPERATION_OUTPUT = 'get_operation_output'
34 CONCAT = 'concat'
35 TOKEN = 'token'
36
37 SELF = 'SELF'
38 HOST = 'HOST'
39 TARGET = 'TARGET'
40 SOURCE = 'SOURCE'
41
42 HOSTED_ON = 'tosca.relationships.HostedOn'
43
44
45 @six.add_metaclass(abc.ABCMeta)
46 class Function(object):
47     """An abstract type for representing a Tosca template function."""
48
49     def __init__(self, tosca_tpl, context, name, args):
50         self.tosca_tpl = tosca_tpl
51         self.context = context
52         self.name = name
53         self.args = args
54         self.validate()
55
56     @abc.abstractmethod
57     def result(self):
58         """Invokes the function and returns its result
59
60         Some methods invocation may only be relevant on runtime (for example,
61         getting runtime properties) and therefore its the responsibility of
62         the orchestrator/translator to take care of such functions invocation.
63
64         :return: Function invocation result.
65         """
66         return {self.name: self.args}
67
68     @abc.abstractmethod
69     def validate(self):
70         """Validates function arguments."""
71         pass
72
73
74 class GetInput(Function):
75     """Get a property value declared within the input of the service template.
76
77     Arguments:
78
79     * Input name.
80
81     Example:
82
83     * get_input: port
84     """
85
86     def validate(self):
87         if len(self.args) != 1:
88             ExceptionCollector.appendException(
89                 ValueError(_(
90                     'Expected one argument for function "get_input" but '
91                     'received "%s".') % self.args))
92         inputs = [input.name for input in self.tosca_tpl.inputs]
93         if self.args[0] not in inputs:
94             ExceptionCollector.appendException(
95                 UnknownInputError(input_name=self.args[0]))
96
97     def result(self):
98         if self.tosca_tpl.parsed_params and \
99            self.input_name in self.tosca_tpl.parsed_params:
100             return DataEntity.validate_datatype(
101                 self.tosca_tpl.tpl['inputs'][self.input_name]['type'],
102                 self.tosca_tpl.parsed_params[self.input_name])
103
104         input = [input_def for input_def in self.tosca_tpl.inputs
105                  if self.input_name == input_def.name][0]
106         return input.default
107
108     @property
109     def input_name(self):
110         return self.args[0]
111
112
113 class GetAttribute(Function):
114     """Get an attribute value of an entity defined in the service template
115
116     Node template attributes values are set in runtime and therefore its the
117     responsibility of the Tosca engine to implement the evaluation of
118     get_attribute functions.
119
120     Arguments:
121
122     * Node template name | HOST.
123     * Attribute name.
124
125     If the HOST keyword is passed as the node template name argument the
126     function will search each node template along the HostedOn relationship
127     chain until a node which contains the attribute is found.
128
129     Examples:
130
131     * { get_attribute: [ server, private_address ] }
132     * { get_attribute: [ HOST, private_address ] }
133     * { get_attribute: [ HOST, private_address, 0 ] }
134     * { get_attribute: [ HOST, private_address, 0, some_prop] }
135     """
136
137     def validate(self):
138         if len(self.args) < 2:
139             ExceptionCollector.appendException(
140                 ValueError(_('Illegal arguments for function "{0}". Expected '
141                              'arguments: "node-template-name", "req-or-cap"'
142                              '(optional), "property name"'
143                              ).format(GET_ATTRIBUTE)))
144             return
145         elif len(self.args) == 2:
146             self._find_node_template_containing_attribute()
147         else:
148             node_tpl = self._find_node_template(self.args[0])
149             if node_tpl is None:
150                 return
151             index = 2
152             attrs = node_tpl.type_definition.get_attributes_def()
153             found = [attrs[self.args[1]]] if self.args[1] in attrs else []
154             if found:
155                 attr = found[0]
156             else:
157                 index = 3
158                 # then check the req or caps
159                 attr = self._find_req_or_cap_attribute(self.args[1],
160                                                        self.args[2])
161
162             value_type = attr.schema['type']
163             if len(self.args) > index:
164                 for elem in self.args[index:]:
165                     if value_type == "list":
166                         if not isinstance(elem, int):
167                             ExceptionCollector.appendException(
168                                 ValueError(_('Illegal arguments for function'
169                                              ' "{0}". "{1}" Expected positive'
170                                              ' integer argument'
171                                              ).format(GET_ATTRIBUTE, elem)))
172                         value_type = attr.schema['entry_schema']['type']
173                     elif value_type == "map":
174                         value_type = attr.schema['entry_schema']['type']
175                     elif value_type in Schema.PROPERTY_TYPES:
176                         ExceptionCollector.appendException(
177                             ValueError(_('Illegal arguments for function'
178                                          ' "{0}". Unexpected attribute/'
179                                          'index value "{1}"'
180                                          ).format(GET_ATTRIBUTE, elem)))
181                         return
182                     else:  # It is a complex type
183                         data_type = DataType(value_type)
184                         props = data_type.get_all_properties()
185                         found = [props[elem]] if elem in props else []
186                         if found:
187                             prop = found[0]
188                             value_type = prop.schema['type']
189                         else:
190                             ExceptionCollector.appendException(
191                                 KeyError(_('Illegal arguments for function'
192                                            ' "{0}". Attribute name "{1}" not'
193                                            ' found in "{2}"'
194                                            ).format(GET_ATTRIBUTE,
195                                                     elem,
196                                                     value_type)))
197
198     def result(self):
199         return self
200
201     def get_referenced_node_template(self):
202         """Gets the NodeTemplate instance the get_attribute function refers to.
203
204         If HOST keyword was used as the node template argument, the node
205         template which contains the attribute along the HostedOn relationship
206         chain will be returned.
207         """
208         return self._find_node_template_containing_attribute()
209
210     # Attributes can be explicitly created as part of the type definition
211     # or a property name can be implicitly used as an attribute name
212     def _find_node_template_containing_attribute(self):
213         node_tpl = self._find_node_template(self.args[0])
214         if node_tpl and \
215                 not self._attribute_exists_in_type(node_tpl.type_definition) \
216                 and self.attribute_name not in node_tpl.get_properties():
217             ExceptionCollector.appendException(
218                 KeyError(_('Attribute "%(att)s" was not found in node '
219                            'template "%(ntpl)s".') %
220                          {'att': self.attribute_name,
221                           'ntpl': node_tpl.name}))
222         return node_tpl
223
224     def _attribute_exists_in_type(self, type_definition):
225         attrs_def = type_definition.get_attributes_def()
226         found = [attrs_def[self.attribute_name]] \
227             if self.attribute_name in attrs_def else []
228         return len(found) == 1
229
230     def _find_host_containing_attribute(self, node_template_name=SELF):
231         node_template = self._find_node_template(node_template_name)
232         if node_template:
233             hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
234             for r in node_template.requirements:
235                 for requirement, target_name in r.items():
236                     target_node = self._find_node_template(target_name)
237                     target_type = target_node.type_definition
238                     for capability in target_type.get_capabilities_objects():
239                         if capability.type in \
240                                 hosted_on_rel['valid_target_types']:
241                             if self._attribute_exists_in_type(target_type):
242                                 return target_node
243                             return self._find_host_containing_attribute(
244                                 target_name)
245
246     def _find_node_template(self, node_template_name):
247         if node_template_name == HOST:
248             # Currently this is the only way to tell whether the function
249             # is used within the outputs section of the TOSCA template.
250             if isinstance(self.context, list):
251                 ExceptionCollector.appendException(
252                     ValueError(_(
253                         '"get_attribute: [ HOST, ... ]" is not allowed in '
254                         '"outputs" section of the TOSCA template.')))
255                 return
256             node_tpl = self._find_host_containing_attribute()
257             if not node_tpl:
258                 ExceptionCollector.appendException(
259                     ValueError(_(
260                         '"get_attribute: [ HOST, ... ]" was used in node '
261                         'template "{0}" but "{1}" was not found in '
262                         'the relationship chain.').format(self.context.name,
263                                                           HOSTED_ON)))
264                 return
265             return node_tpl
266         if node_template_name == TARGET:
267             if not isinstance(self.context.type_definition, RelationshipType):
268                 ExceptionCollector.appendException(
269                     KeyError(_('"TARGET" keyword can only be used in context'
270                                ' to "Relationships" target node')))
271                 return
272             return self.context.target
273         if node_template_name == SOURCE:
274             if not isinstance(self.context.type_definition, RelationshipType):
275                 ExceptionCollector.appendException(
276                     KeyError(_('"SOURCE" keyword can only be used in context'
277                                ' to "Relationships" source node')))
278                 return
279             return self.context.source
280         name = self.context.name \
281             if node_template_name == SELF and \
282             not isinstance(self.context, list) \
283             else node_template_name
284         for node_template in self.tosca_tpl.nodetemplates:
285             if node_template.name == name:
286                 return node_template
287         ExceptionCollector.appendException(
288             KeyError(_(
289                 'Node template "{0}" was not found.'
290                 ).format(node_template_name)))
291
292     def _find_req_or_cap_attribute(self, req_or_cap, attr_name):
293         node_tpl = self._find_node_template(self.args[0])
294         # Find attribute in node template's requirements
295         for r in node_tpl.requirements:
296             for req, node_name in r.items():
297                 if req == req_or_cap:
298                     node_template = self._find_node_template(node_name)
299                     return self._get_capability_attribute(
300                         node_template,
301                         req,
302                         attr_name)
303         # If requirement was not found, look in node template's capabilities
304         return self._get_capability_attribute(node_tpl,
305                                               req_or_cap,
306                                               attr_name)
307
308     def _get_capability_attribute(self,
309                                   node_template,
310                                   capability_name,
311                                   attr_name):
312         """Gets a node template capability attribute."""
313         caps = node_template.get_capabilities()
314         if caps and capability_name in caps.keys():
315             cap = caps[capability_name]
316             attribute = None
317             attrs = cap.definition.get_attributes_def()
318             if attrs and attr_name in attrs.keys():
319                 attribute = attrs[attr_name]
320             if not attribute:
321                 ExceptionCollector.appendException(
322                     KeyError(_('Attribute "%(attr)s" was not found in '
323                                'capability "%(cap)s" of node template '
324                                '"%(ntpl1)s" referenced from node template '
325                                '"%(ntpl2)s".') % {'attr': attr_name,
326                                                   'cap': capability_name,
327                                                   'ntpl1': node_template.name,
328                                                   'ntpl2': self.context.name}))
329             return attribute
330         msg = _('Requirement/Capability "{0}" referenced from node template '
331                 '"{1}" was not found in node template "{2}".').format(
332                     capability_name,
333                     self.context.name,
334                     node_template.name)
335         ExceptionCollector.appendException(KeyError(msg))
336
337     @property
338     def node_template_name(self):
339         return self.args[0]
340
341     @property
342     def attribute_name(self):
343         return self.args[1]
344
345
346 class GetProperty(Function):
347     """Get a property value of an entity defined in the same service template.
348
349     Arguments:
350
351     * Node template name | SELF | HOST | SOURCE | TARGET.
352     * Requirement or capability name (optional).
353     * Property name.
354
355     If requirement or capability name is specified, the behavior is as follows:
356     The req or cap name is first looked up in the specified node template's
357     requirements.
358     If found, it would search for a matching capability
359     of an other node template and get its property as specified in function
360     arguments.
361     Otherwise, the req or cap name would be looked up in the specified
362     node template's capabilities and if found, it would return  the property of
363     the capability as specified in function arguments.
364
365     Examples:
366
367     * { get_property: [ mysql_server, port ] }
368     * { get_property: [ SELF, db_port ] }
369     * { get_property: [ SELF, database_endpoint, port ] }
370     * { get_property: [ SELF, database_endpoint, port, 1 ] }
371     """
372
373     def validate(self):
374         if len(self.args) < 2:
375             ExceptionCollector.appendException(
376                 ValueError(_(
377                     'Expected arguments: "node-template-name", "req-or-cap" '
378                     '(optional), "property name".')))
379             return
380         if len(self.args) == 2:
381             found_prop = self._find_property(self.args[1])
382             if not found_prop:
383                 return
384             prop = found_prop.value
385             if not isinstance(prop, Function):
386                 get_function(self.tosca_tpl, self.context, prop)
387         elif len(self.args) >= 3:
388             # do not use _find_property to avoid raise KeyError
389             # if the prop is not found
390             # First check if there is property with this name
391             node_tpl = self._find_node_template(self.args[0])
392             props = node_tpl.get_properties() if node_tpl else []
393             index = 2
394             found = [props[self.args[1]]] if self.args[1] in props else []
395             if found:
396                 property_value = found[0].value
397             else:
398                 index = 3
399                 # then check the req or caps
400                 property_value = self._find_req_or_cap_property(self.args[1],
401                                                                 self.args[2])
402             if len(self.args) > index:
403                 for elem in self.args[index:]:
404                     if isinstance(property_value, list):
405                         int_elem = int(elem)
406                         property_value = self._get_index_value(property_value,
407                                                                int_elem)
408                     else:
409                         property_value = self._get_attribute_value(
410                             property_value,
411                             elem)
412
413     def _find_req_or_cap_property(self, req_or_cap, property_name):
414         node_tpl = self._find_node_template(self.args[0])
415         # Find property in node template's requirements
416         for r in node_tpl.requirements:
417             for req, node_name in r.items():
418                 if req == req_or_cap:
419                     node_template = self._find_node_template(node_name)
420                     return self._get_capability_property(
421                         node_template,
422                         req,
423                         property_name)
424         # If requirement was not found, look in node template's capabilities
425         return self._get_capability_property(node_tpl,
426                                              req_or_cap,
427                                              property_name)
428
429     def _get_capability_property(self,
430                                  node_template,
431                                  capability_name,
432                                  property_name):
433         """Gets a node template capability property."""
434         caps = node_template.get_capabilities()
435         if caps and capability_name in caps.keys():
436             cap = caps[capability_name]
437             property = None
438             props = cap.get_properties()
439             if props and property_name in props.keys():
440                 property = props[property_name].value
441             if not property:
442                 ExceptionCollector.appendException(
443                     KeyError(_('Property "%(prop)s" was not found in '
444                                'capability "%(cap)s" of node template '
445                                '"%(ntpl1)s" referenced from node template '
446                                '"%(ntpl2)s".') % {'prop': property_name,
447                                                   'cap': capability_name,
448                                                   'ntpl1': node_template.name,
449                                                   'ntpl2': self.context.name}))
450             return property
451         msg = _('Requirement/Capability "{0}" referenced from node template '
452                 '"{1}" was not found in node template "{2}".').format(
453                     capability_name,
454                     self.context.name,
455                     node_template.name)
456         ExceptionCollector.appendException(KeyError(msg))
457
458     def _find_property(self, property_name):
459         node_tpl = self._find_node_template(self.args[0])
460         if not node_tpl:
461             return
462         props = node_tpl.get_properties()
463         found = [props[property_name]] if property_name in props else []
464         if len(found) == 0:
465             ExceptionCollector.appendException(
466                 KeyError(_('Property "%(prop)s" was not found in node '
467                            'template "%(ntpl)s".') %
468                          {'prop': property_name,
469                           'ntpl': node_tpl.name}))
470             return None
471         return found[0]
472
473     def _find_node_template(self, node_template_name):
474         if node_template_name == SELF:
475             return self.context
476         # enable the HOST value in the function
477         if node_template_name == HOST:
478             return self._find_host_containing_property()
479         if node_template_name == TARGET:
480             if not isinstance(self.context.type_definition, RelationshipType):
481                 ExceptionCollector.appendException(
482                     KeyError(_('"TARGET" keyword can only be used in context'
483                                ' to "Relationships" target node')))
484                 return
485             return self.context.target
486         if node_template_name == SOURCE:
487             if not isinstance(self.context.type_definition, RelationshipType):
488                 ExceptionCollector.appendException(
489                     KeyError(_('"SOURCE" keyword can only be used in context'
490                                ' to "Relationships" source node')))
491                 return
492             return self.context.source
493         if not hasattr(self.tosca_tpl, 'nodetemplates'):
494             return
495         for node_template in self.tosca_tpl.nodetemplates:
496             if node_template.name == node_template_name:
497                 return node_template
498         ExceptionCollector.appendException(
499             KeyError(_(
500                 'Node template "{0}" was not found.'
501                 ).format(node_template_name)))
502
503     def _get_index_value(self, value, index):
504         if isinstance(value, list):
505             if index < len(value):
506                 return value[index]
507             else:
508                 ExceptionCollector.appendException(
509                     KeyError(_(
510                         "Property '{0}' found in capability '{1}'"
511                         " referenced from node template {2}"
512                         " must have an element with index {3}.").
513                         format(self.args[2],
514                                self.args[1],
515                                self.context.name,
516                                index)))
517         else:
518             ExceptionCollector.appendException(
519                 KeyError(_(
520                     "Property '{0}' found in capability '{1}'"
521                     " referenced from node template {2}"
522                     " must be a list.").format(self.args[2],
523                                                self.args[1],
524                                                self.context.name)))
525
526     def _get_attribute_value(self, value, attibute):
527         if isinstance(value, dict):
528             if attibute in value:
529                 return value[attibute]
530             else:
531                 ExceptionCollector.appendException(
532                     KeyError(_(
533                         "Property '{0}' found in capability '{1}'"
534                         " referenced from node template {2}"
535                         " must have an attribute named {3}.").
536                         format(self.args[2],
537                                self.args[1],
538                                self.context.name,
539                                attibute)))
540         else:
541             ExceptionCollector.appendException(
542                 KeyError(_(
543                     "Property '{0}' found in capability '{1}'"
544                     " referenced from node template {2}"
545                     " must be a dict.").format(self.args[2],
546                                                self.args[1],
547                                                self.context.name)))
548
549     # Add this functions similar to get_attribute case
550     def _find_host_containing_property(self, node_template_name=SELF):
551         node_template = self._find_node_template(node_template_name)
552         hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
553         for r in node_template.requirements:
554             for requirement, target_name in r.items():
555                 target_node = self._find_node_template(target_name)
556                 target_type = target_node.type_definition
557                 for capability in target_type.get_capabilities_objects():
558                     if capability.type in hosted_on_rel['valid_target_types']:
559                         if self._property_exists_in_type(target_type):
560                             return target_node
561                         return self._find_host_containing_property(
562                             target_name)
563         return None
564
565     def _property_exists_in_type(self, type_definition):
566         props_def = type_definition.get_properties_def()
567         found = [props_def[self.args[1]]] \
568             if self.args[1] in props_def else []
569         return len(found) == 1
570
571     def result(self):
572         if len(self.args) >= 3:
573             # First check if there is property with this name
574             node_tpl = self._find_node_template(self.args[0])
575             props = node_tpl.get_properties() if node_tpl else []
576             index = 2
577             found = [props[self.args[1]]] if self.args[1] in props else []
578             if found:
579                 property_value = found[0].value
580             else:
581                 index = 3
582                 # then check the req or caps
583                 property_value = self._find_req_or_cap_property(self.args[1],
584                                                                 self.args[2])
585             if len(self.args) > index:
586                 for elem in self.args[index:]:
587                     if isinstance(property_value, list):
588                         int_elem = int(elem)
589                         property_value = self._get_index_value(property_value,
590                                                                int_elem)
591                     else:
592                         property_value = self._get_attribute_value(
593                             property_value,
594                             elem)
595         else:
596             property_value = self._find_property(self.args[1]).value
597         if isinstance(property_value, Function):
598             return property_value.result()
599         return get_function(self.tosca_tpl,
600                             self.context,
601                             property_value)
602
603     @property
604     def node_template_name(self):
605         return self.args[0]
606
607     @property
608     def property_name(self):
609         if len(self.args) > 2:
610             return self.args[2]
611         return self.args[1]
612
613     @property
614     def req_or_cap(self):
615         if len(self.args) > 2:
616             return self.args[1]
617         return None
618
619
620 class GetOperationOutput(Function):
621     def validate(self):
622         if len(self.args) == 4:
623             self._find_node_template(self.args[0])
624             interface_name = self._find_interface_name(self.args[1])
625             self._find_operation_name(interface_name, self.args[2])
626         else:
627             ExceptionCollector.appendException(
628                 ValueError(_('Illegal arguments for function "{0}". Expected '
629                              'arguments: "template_name","interface_name",'
630                              '"operation_name","output_variable_name"'
631                              ).format(GET_OPERATION_OUTPUT)))
632             return
633
634     def _find_interface_name(self, interface_name):
635         if interface_name in toscaparser.elements.interfaces.SECTIONS:
636             return interface_name
637         else:
638             ExceptionCollector.appendException(
639                 ValueError(_('Enter a valid interface name'
640                              ).format(GET_OPERATION_OUTPUT)))
641             return
642
643     def _find_operation_name(self, interface_name, operation_name):
644         if(interface_name == 'Configure' or
645            interface_name == 'tosca.interfaces.node.relationship.Configure'):
646             if(operation_name in
647                StatefulEntityType.
648                interfaces_relationship_configure_operations):
649                 return operation_name
650             else:
651                 ExceptionCollector.appendException(
652                     ValueError(_('Enter an operation of Configure interface'
653                                  ).format(GET_OPERATION_OUTPUT)))
654                 return
655         elif(interface_name == 'Standard' or
656              interface_name == 'tosca.interfaces.node.lifecycle.Standard'):
657             if(operation_name in
658                StatefulEntityType.interfaces_node_lifecycle_operations):
659                 return operation_name
660             else:
661                 ExceptionCollector.appendException(
662                     ValueError(_('Enter an operation of Standard interface'
663                                  ).format(GET_OPERATION_OUTPUT)))
664                 return
665         else:
666             ExceptionCollector.appendException(
667                 ValueError(_('Enter a valid operation name'
668                              ).format(GET_OPERATION_OUTPUT)))
669             return
670
671     def _find_node_template(self, node_template_name):
672         if node_template_name == TARGET:
673             if not isinstance(self.context.type_definition, RelationshipType):
674                 ExceptionCollector.appendException(
675                     KeyError(_('"TARGET" keyword can only be used in context'
676                                ' to "Relationships" target node')))
677                 return
678             return self.context.target
679         if node_template_name == SOURCE:
680             if not isinstance(self.context.type_definition, RelationshipType):
681                 ExceptionCollector.appendException(
682                     KeyError(_('"SOURCE" keyword can only be used in context'
683                                ' to "Relationships" source node')))
684                 return
685             return self.context.source
686         name = self.context.name \
687             if node_template_name == SELF and \
688             not isinstance(self.context, list) \
689             else node_template_name
690         for node_template in self.tosca_tpl.nodetemplates:
691             if node_template.name == name:
692                 return node_template
693         ExceptionCollector.appendException(
694             KeyError(_(
695                 'Node template "{0}" was not found.'
696                 ).format(node_template_name)))
697
698     def result(self):
699         return self
700
701
702 class Concat(Function):
703     """Validate the function and provide an instance of the function
704
705     Concatenation of values are supposed to be produced at runtime and
706     therefore its the responsibility of the TOSCA engine to implement the
707     evaluation of Concat functions.
708
709     Arguments:
710
711     * List of strings that needs to be concatenated
712
713     Example:
714
715       [ 'http://',
716         get_attribute: [ server, public_address ],
717         ':' ,
718         get_attribute: [ server, port ] ]
719     """
720
721     def validate(self):
722         if len(self.args) < 1:
723             ExceptionCollector.appendException(
724                 ValueError(_('Invalid arguments for function "{0}". Expected '
725                              'at least one arguments.').format(CONCAT)))
726
727     def result(self):
728         return self
729
730
731 class Token(Function):
732     """Validate the function and provide an instance of the function
733
734     The token function is used within a TOSCA service template on a string to
735     parse out (tokenize) substrings separated by one or more token characters
736     within a larger string.
737
738
739     Arguments:
740
741     * The composite string that contains one or more substrings separated by
742       token characters.
743     * The string that contains one or more token characters that separate
744       substrings within the composite string.
745     * The integer indicates the index of the substring to return from the
746       composite string.  Note that the first substring is denoted by using
747       the '0' (zero) integer value.
748
749     Example:
750
751      [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ]
752
753     """
754
755     def validate(self):
756         if len(self.args) < 3:
757             ExceptionCollector.appendException(
758                 ValueError(_('Invalid arguments for function "{0}". Expected '
759                              'at least three arguments.').format(TOKEN)))
760         else:
761             if not isinstance(self.args[1], str) or len(self.args[1]) != 1:
762                 ExceptionCollector.appendException(
763                     ValueError(_('Invalid arguments for function "{0}". '
764                                  'Expected single char value as second '
765                                  'argument.').format(TOKEN)))
766
767             if not isinstance(self.args[2], int):
768                 ExceptionCollector.appendException(
769                     ValueError(_('Invalid arguments for function "{0}". '
770                                  'Expected integer value as third '
771                                  'argument.').format(TOKEN)))
772
773     def result(self):
774         return self
775
776 function_mappings = {
777     GET_PROPERTY: GetProperty,
778     GET_INPUT: GetInput,
779     GET_ATTRIBUTE: GetAttribute,
780     GET_OPERATION_OUTPUT: GetOperationOutput,
781     CONCAT: Concat,
782     TOKEN: Token
783 }
784
785
786 def is_function(function):
787     """Returns True if the provided function is a Tosca intrinsic function.
788
789     Examples:
790
791     * "{ get_property: { SELF, port } }"
792     * "{ get_input: db_name }"
793     * Function instance
794
795     :param function: Function as string or a Function instance.
796     :return: True if function is a Tosca intrinsic function, otherwise False.
797     """
798     if isinstance(function, dict) and len(function) == 1:
799         func_name = list(function.keys())[0]
800         return func_name in function_mappings
801     return isinstance(function, Function)
802
803
804 def get_function(tosca_tpl, node_template, raw_function):
805     """Gets a Function instance representing the provided template function.
806
807     If the format provided raw_function format is not relevant for template
808     functions or if the function name doesn't exist in function mapping the
809     method returns the provided raw_function.
810
811     :param tosca_tpl: The tosca template.
812     :param node_template: The node template the function is specified for.
813     :param raw_function: The raw function as dict.
814     :return: Template function as Function instance or the raw_function if
815      parsing was unsuccessful.
816     """
817     if is_function(raw_function):
818         if isinstance(raw_function, dict):
819             func_name = list(raw_function.keys())[0]
820             if func_name in function_mappings:
821                 func = function_mappings[func_name]
822                 func_args = list(raw_function.values())[0]
823                 if not isinstance(func_args, list):
824                     func_args = [func_args]
825                 return func(tosca_tpl, node_template, func_name, func_args)
826     return raw_function