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],
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'
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/'
180 ).format(GET_ATTRIBUTE, elem)))
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 []
188 value_type = prop.schema['type']
190 ExceptionCollector.appendException(
191 KeyError(_('Illegal arguments for function'
192 ' "{0}". Attribute name "{1}" not'
194 ).format(GET_ATTRIBUTE,
201 def get_referenced_node_template(self):
202 """Gets the NodeTemplate instance the get_attribute function refers to.
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.
208 return self._find_node_template_containing_attribute()
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])
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}))
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
230 def _find_host_containing_attribute(self, node_template_name=SELF):
231 node_template = self._find_node_template(node_template_name)
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):
243 return self._find_host_containing_attribute(
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(
253 '"get_attribute: [ HOST, ... ]" is not allowed in '
254 '"outputs" section of the TOSCA template.')))
256 node_tpl = self._find_host_containing_attribute()
258 ExceptionCollector.appendException(
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,
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')))
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')))
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:
287 ExceptionCollector.appendException(
289 'Node template "{0}" was not found.'
290 ).format(node_template_name)))
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(
303 # If requirement was not found, look in node template's capabilities
304 return self._get_capability_attribute(node_tpl,
308 def _get_capability_attribute(self,
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]
317 attrs = cap.definition.get_attributes_def()
318 if attrs and attr_name in attrs.keys():
319 attribute = attrs[attr_name]
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}))
330 msg = _('Requirement/Capability "{0}" referenced from node template '
331 '"{1}" was not found in node template "{2}".').format(
335 ExceptionCollector.appendException(KeyError(msg))
338 def node_template_name(self):
342 def attribute_name(self):
346 class GetProperty(Function):
347 """Get a property value of an entity defined in the same service template.
351 * Node template name | SELF | HOST | SOURCE | TARGET.
352 * Requirement or capability name (optional).
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
358 If found, it would search for a matching capability
359 of an other node template and get its property as specified in function
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.
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 ] }
374 if len(self.args) < 2:
375 ExceptionCollector.appendException(
377 'Expected arguments: "node-template-name", "req-or-cap" '
378 '(optional), "property name".')))
380 if len(self.args) == 2:
381 found_prop = self._find_property(self.args[1])
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 []
394 found = [props[self.args[1]]] if self.args[1] in props else []
396 property_value = found[0].value
399 # then check the req or caps
400 property_value = self._find_req_or_cap_property(self.args[1],
402 if len(self.args) > index:
403 for elem in self.args[index:]:
404 if isinstance(property_value, list):
406 property_value = self._get_index_value(property_value,
409 property_value = self._get_attribute_value(
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(
424 # If requirement was not found, look in node template's capabilities
425 return self._get_capability_property(node_tpl,
429 def _get_capability_property(self,
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]
438 props = cap.get_properties()
439 if props and property_name in props.keys():
440 property = props[property_name].value
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}))
451 msg = _('Requirement/Capability "{0}" referenced from node template '
452 '"{1}" was not found in node template "{2}".').format(
456 ExceptionCollector.appendException(KeyError(msg))
458 def _find_property(self, property_name):
459 node_tpl = self._find_node_template(self.args[0])
462 props = node_tpl.get_properties()
463 found = [props[property_name]] if property_name in props else []
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}))
473 def _find_node_template(self, node_template_name):
474 if node_template_name == SELF:
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')))
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')))
492 return self.context.source
493 if not hasattr(self.tosca_tpl, 'nodetemplates'):
495 for node_template in self.tosca_tpl.nodetemplates:
496 if node_template.name == node_template_name:
498 ExceptionCollector.appendException(
500 'Node template "{0}" was not found.'
501 ).format(node_template_name)))
503 def _get_index_value(self, value, index):
504 if isinstance(value, list):
505 if index < len(value):
508 ExceptionCollector.appendException(
510 "Property '{0}' found in capability '{1}'"
511 " referenced from node template {2}"
512 " must have an element with index {3}.").
518 ExceptionCollector.appendException(
520 "Property '{0}' found in capability '{1}'"
521 " referenced from node template {2}"
522 " must be a list.").format(self.args[2],
526 def _get_attribute_value(self, value, attibute):
527 if isinstance(value, dict):
528 if attibute in value:
529 return value[attibute]
531 ExceptionCollector.appendException(
533 "Property '{0}' found in capability '{1}'"
534 " referenced from node template {2}"
535 " must have an attribute named {3}.").
541 ExceptionCollector.appendException(
543 "Property '{0}' found in capability '{1}'"
544 " referenced from node template {2}"
545 " must be a dict.").format(self.args[2],
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):
561 return self._find_host_containing_property(
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
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 []
577 found = [props[self.args[1]]] if self.args[1] in props else []
579 property_value = found[0].value
582 # then check the req or caps
583 property_value = self._find_req_or_cap_property(self.args[1],
585 if len(self.args) > index:
586 for elem in self.args[index:]:
587 if isinstance(property_value, list):
589 property_value = self._get_index_value(property_value,
592 property_value = self._get_attribute_value(
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,
604 def node_template_name(self):
608 def property_name(self):
609 if len(self.args) > 2:
614 def req_or_cap(self):
615 if len(self.args) > 2:
620 class GetOperationOutput(Function):
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])
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)))
634 def _find_interface_name(self, interface_name):
635 if interface_name in toscaparser.elements.interfaces.SECTIONS:
636 return interface_name
638 ExceptionCollector.appendException(
639 ValueError(_('Enter a valid interface name'
640 ).format(GET_OPERATION_OUTPUT)))
643 def _find_operation_name(self, interface_name, operation_name):
644 if(interface_name == 'Configure' or
645 interface_name == 'tosca.interfaces.node.relationship.Configure'):
648 interfaces_relationship_configure_operations):
649 return operation_name
651 ExceptionCollector.appendException(
652 ValueError(_('Enter an operation of Configure interface'
653 ).format(GET_OPERATION_OUTPUT)))
655 elif(interface_name == 'Standard' or
656 interface_name == 'tosca.interfaces.node.lifecycle.Standard'):
658 StatefulEntityType.interfaces_node_lifecycle_operations):
659 return operation_name
661 ExceptionCollector.appendException(
662 ValueError(_('Enter an operation of Standard interface'
663 ).format(GET_OPERATION_OUTPUT)))
666 ExceptionCollector.appendException(
667 ValueError(_('Enter a valid operation name'
668 ).format(GET_OPERATION_OUTPUT)))
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')))
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')))
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:
693 ExceptionCollector.appendException(
695 'Node template "{0}" was not found.'
696 ).format(node_template_name)))
702 class Concat(Function):
703 """Validate the function and provide an instance of the function
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.
711 * List of strings that needs to be concatenated
716 get_attribute: [ server, public_address ],
718 get_attribute: [ server, port ] ]
722 if len(self.args) < 1:
723 ExceptionCollector.appendException(
724 ValueError(_('Invalid arguments for function "{0}". Expected '
725 'at least one arguments.').format(CONCAT)))
731 class Token(Function):
732 """Validate the function and provide an instance of the function
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.
741 * The composite string that contains one or more substrings separated by
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.
751 [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ]
756 if len(self.args) < 3:
757 ExceptionCollector.appendException(
758 ValueError(_('Invalid arguments for function "{0}". Expected '
759 'at least three arguments.').format(TOKEN)))
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)))
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)))
776 function_mappings = {
777 GET_PROPERTY: GetProperty,
779 GET_ATTRIBUTE: GetAttribute,
780 GET_OPERATION_OUTPUT: GetOperationOutput,
786 def is_function(function):
787 """Returns True if the provided function is a Tosca intrinsic function.
791 * "{ get_property: { SELF, port } }"
792 * "{ get_input: db_name }"
795 :param function: Function as string or a Function instance.
796 :return: True if function is a Tosca intrinsic function, otherwise False.
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)
804 def get_function(tosca_tpl, node_template, raw_function):
805 """Gets a Function instance representing the provided template function.
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.
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.
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)