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
6 # http://www.apache.org/licenses/LICENSE-2.0
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
17 import toscaparser.elements.interfaces
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 _
30 GET_PROPERTY = 'get_property'
31 GET_ATTRIBUTE = 'get_attribute'
32 GET_INPUT = 'get_input'
33 GET_OPERATION_OUTPUT = 'get_operation_output'
42 HOSTED_ON = 'tosca.relationships.HostedOn'
45 @six.add_metaclass(abc.ABCMeta)
46 class Function(object):
47 """An abstract type for representing a Tosca template function."""
49 def __init__(self, tosca_tpl, context, name, args):
50 self.tosca_tpl = tosca_tpl
51 self.context = context
58 """Invokes the function and returns its result
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.
64 :return: Function invocation result.
66 return {self.name: self.args}
70 """Validates function arguments."""
74 class GetInput(Function):
75 """Get a property value declared within the input of the service template.
87 if len(self.args) != 1:
88 ExceptionCollector.appendException(
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]))
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])
104 input = [input_def for input_def in self.tosca_tpl.inputs
105 if self.input_name == input_def.name][0]
109 def input_name(self):
113 class GetAttribute(Function):
114 """Get an attribute value of an entity defined in the service template
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.
122 * Node template name | HOST.
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.
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] }
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)))
145 elif len(self.args) == 2:
146 self._find_node_template_containing_attribute()
148 node_tpl = self._find_node_template(self.args[0])
152 attrs = node_tpl.type_definition.get_attributes_def()
153 found = [attrs[self.args[1]]] if self.args[1] in attrs else []
158 # then check the req or caps
159 attr = self._find_req_or_cap_attribute(self.args[1],
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'
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/'
182 ).format(GET_ATTRIBUTE, elem)))
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 []
190 value_type = prop.schema['type']
192 ExceptionCollector.appendException(
193 KeyError(_('Illegal arguments for function'
194 ' "{0}". Attribute name "{1}" not'
196 ).format(GET_ATTRIBUTE,
203 def get_referenced_node_template(self):
204 """Gets the NodeTemplate instance the get_attribute function refers to.
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.
210 return self._find_node_template_containing_attribute()
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])
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}))
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
232 def _find_host_containing_attribute(self, node_template_name=SELF):
233 node_template = self._find_node_template(node_template_name)
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):
245 return self._find_host_containing_attribute(
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(
255 '"get_attribute: [ HOST, ... ]" is not allowed in '
256 '"outputs" section of the TOSCA template.')))
258 node_tpl = self._find_host_containing_attribute()
260 ExceptionCollector.appendException(
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,
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')))
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')))
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:
289 ExceptionCollector.appendException(
291 'Node template "{0}" was not found.'
292 ).format(node_template_name)))
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(
305 # If requirement was not found, look in node template's capabilities
306 return self._get_capability_attribute(node_tpl,
310 def _get_capability_attribute(self,
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]
319 attrs = cap.definition.get_attributes_def()
320 if attrs and attr_name in attrs.keys():
321 attribute = attrs[attr_name]
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}))
332 msg = _('Requirement/Capability "{0}" referenced from node template '
333 '"{1}" was not found in node template "{2}".').format(
337 ExceptionCollector.appendException(KeyError(msg))
340 def node_template_name(self):
344 def attribute_name(self):
348 class GetProperty(Function):
349 """Get a property value of an entity defined in the same service template.
353 * Node template name | SELF | HOST | SOURCE | TARGET.
354 * Requirement or capability name (optional).
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
360 If found, it would search for a matching capability
361 of an other node template and get its property as specified in function
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.
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 ] }
376 if len(self.args) < 2:
377 ExceptionCollector.appendException(
379 'Expected arguments: "node-template-name", "req-or-cap" '
380 '(optional), "property name".')))
382 if len(self.args) == 2:
383 found_prop = self._find_property(self.args[1])
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 []
396 found = [props[self.args[1]]] if self.args[1] in props else []
398 property_value = found[0].value
401 # then check the req or caps
402 property_value = self._find_req_or_cap_property(self.args[1],
404 if len(self.args) > index:
405 for elem in self.args[index:]:
406 if isinstance(property_value, list):
408 property_value = self._get_index_value(property_value,
411 property_value = self._get_attribute_value(
415 def _find_req_or_cap_property(self, req_or_cap, property_name):
416 node_tpl = self._find_node_template(self.args[0])
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(
428 # If requirement was not found, look in node template's capabilities
429 return self._get_capability_property(node_tpl,
433 def _get_capability_property(self,
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]
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}))
457 msg = _('Requirement/Capability "{0}" referenced from '
458 'node template "{1}" was not found in node template'
459 ' "{2}".').format(capability_name,
462 ExceptionCollector.appendException(KeyError(msg))
466 def _find_property(self, property_name):
467 node_tpl = self._find_node_template(self.args[0])
470 props = node_tpl.get_properties()
471 found = [props[property_name]] if property_name in props else []
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}))
481 def _find_node_template(self, node_template_name):
482 if node_template_name == SELF:
484 # enable the HOST value in the function
485 if node_template_name == HOST:
486 node = self._find_host_containing_property()
488 ExceptionCollector.appendException(
490 "Property '{0}' not found in capability/requirement"
491 " '{1}' referenced from node template {2}").
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')))
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')))
511 return self.context.source
512 if not hasattr(self.tosca_tpl, 'nodetemplates'):
514 for node_template in self.tosca_tpl.nodetemplates:
515 if node_template.name == node_template_name:
517 ExceptionCollector.appendException(
519 'Node template "{0}" was not found.'
520 ' referenced from node template {1}'
521 ).format(node_template_name,
524 def _get_index_value(self, value, index):
525 if isinstance(value, list):
526 if index < len(value):
529 ExceptionCollector.appendException(
531 "Property '{0}' found in capability '{1}'"
532 " referenced from node template {2}"
533 " must have an element with index {3}.").
539 ExceptionCollector.appendException(
541 "Property '{0}' found in capability '{1}'"
542 " referenced from node template {2}"
543 " must be a list.").format(self.args[2],
547 def _get_attribute_value(self, value, attibute):
548 if isinstance(value, dict):
549 if attibute in value:
550 return value[attibute]
552 ExceptionCollector.appendException(
554 "Property '{0}' found in capability '{1}'"
555 " referenced from node template {2}"
556 " must have an attribute named {3}.").
562 ExceptionCollector.appendException(
564 "Property '{0}' found in capability '{1}'"
565 " referenced from node template {2}"
566 " must be a dict.").format(self.args[2],
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):
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,
592 return self._find_host_containing_property(
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
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 []
608 found = [props[self.args[1]]] if self.args[1] in props else []
610 property_value = found[0].value
613 # then check the req or caps
614 property_value = self._find_req_or_cap_property(self.args[1],
616 if len(self.args) > index:
617 for elem in self.args[index:]:
618 if isinstance(property_value, list):
620 property_value = self._get_index_value(property_value,
623 property_value = self._get_attribute_value(
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,
635 def node_template_name(self):
639 def property_name(self):
640 if len(self.args) > 2:
645 def req_or_cap(self):
646 if len(self.args) > 2:
651 class GetOperationOutput(Function):
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])
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)))
665 def _find_interface_name(self, interface_name):
666 if interface_name in toscaparser.elements.interfaces.SECTIONS:
667 return interface_name
669 ExceptionCollector.appendException(
670 ValueError(_('Enter a valid interface name'
671 ).format(GET_OPERATION_OUTPUT)))
674 def _find_operation_name(self, interface_name, operation_name):
675 if(interface_name == 'Configure' or
676 interface_name == 'tosca.interfaces.node.relationship.Configure'):
679 interfaces_relationship_configure_operations):
680 return operation_name
682 ExceptionCollector.appendException(
683 ValueError(_('Enter an operation of Configure interface'
684 ).format(GET_OPERATION_OUTPUT)))
686 elif(interface_name == 'Standard' or
687 interface_name == 'tosca.interfaces.node.lifecycle.Standard'):
689 StatefulEntityType.interfaces_node_lifecycle_operations):
690 return operation_name
692 ExceptionCollector.appendException(
693 ValueError(_('Enter an operation of Standard interface'
694 ).format(GET_OPERATION_OUTPUT)))
697 ExceptionCollector.appendException(
698 ValueError(_('Enter a valid operation name'
699 ).format(GET_OPERATION_OUTPUT)))
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')))
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')))
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:
724 ExceptionCollector.appendException(
726 'Node template "{0}" was not found.'
727 ).format(node_template_name)))
733 class Concat(Function):
734 """Validate the function and provide an instance of the function
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.
742 * List of strings that needs to be concatenated
747 get_attribute: [ server, public_address ],
749 get_attribute: [ server, port ] ]
753 if len(self.args) < 1:
754 ExceptionCollector.appendException(
755 ValueError(_('Invalid arguments for function "{0}". Expected '
756 'at least one arguments.').format(CONCAT)))
762 class Token(Function):
763 """Validate the function and provide an instance of the function
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.
772 * The composite string that contains one or more substrings separated by
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.
782 [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ]
787 if len(self.args) < 3:
788 ExceptionCollector.appendException(
789 ValueError(_('Invalid arguments for function "{0}". Expected '
790 'at least three arguments.').format(TOKEN)))
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)))
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)))
807 function_mappings = {
808 GET_PROPERTY: GetProperty,
810 GET_ATTRIBUTE: GetAttribute,
811 GET_OPERATION_OUTPUT: GetOperationOutput,
817 def is_function(function):
818 """Returns True if the provided function is a Tosca intrinsic function.
822 * "{ get_property: { SELF, port } }"
823 * "{ get_input: db_name }"
826 :param function: Function as string or a Function instance.
827 :return: True if function is a Tosca intrinsic function, otherwise False.
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)
835 def get_function(tosca_tpl, node_template, raw_function):
836 """Gets a Function instance representing the provided template function.
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.
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.
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)