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