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
18 from toscaparser.common.exception import ExceptionCollector
19 from toscaparser.common.exception import UnknownInputError
20 from toscaparser.dataentity import DataEntity
21 from toscaparser.elements.constraints import Schema
22 from toscaparser.elements.datatype import DataType
23 from toscaparser.elements.entity_type import EntityType
24 from toscaparser.elements.relationshiptype import RelationshipType
25 from toscaparser.utils.gettextutils import _
28 GET_PROPERTY = 'get_property'
29 GET_ATTRIBUTE = 'get_attribute'
30 GET_INPUT = 'get_input'
39 HOSTED_ON = 'tosca.relationships.HostedOn'
42 @six.add_metaclass(abc.ABCMeta)
43 class Function(object):
44 """An abstract type for representing a Tosca template function."""
46 def __init__(self, tosca_tpl, context, name, args):
47 self.tosca_tpl = tosca_tpl
48 self.context = context
55 """Invokes the function and returns its result
57 Some methods invocation may only be relevant on runtime (for example,
58 getting runtime properties) and therefore its the responsibility of
59 the orchestrator/translator to take care of such functions invocation.
61 :return: Function invocation result.
63 return {self.name: self.args}
67 """Validates function arguments."""
71 class GetInput(Function):
72 """Get a property value declared within the input of the service template.
84 if len(self.args) != 1:
85 ExceptionCollector.appendException(
87 'Expected one argument for function "get_input" but '
88 'received "%s".') % self.args))
89 inputs = [input.name for input in self.tosca_tpl.inputs]
90 if self.args[0] not in inputs:
91 ExceptionCollector.appendException(
92 UnknownInputError(input_name=self.args[0]))
95 if self.tosca_tpl.parsed_params and \
96 self.input_name in self.tosca_tpl.parsed_params:
97 return DataEntity.validate_datatype(
98 self.tosca_tpl.tpl['inputs'][self.input_name]['type'],
99 self.tosca_tpl.parsed_params[self.input_name])
101 input = [input_def for input_def in self.tosca_tpl.inputs
102 if self.input_name == input_def.name][0]
106 def input_name(self):
110 class GetAttribute(Function):
111 """Get an attribute value of an entity defined in the service template
113 Node template attributes values are set in runtime and therefore its the
114 responsibility of the Tosca engine to implement the evaluation of
115 get_attribute functions.
119 * Node template name | HOST.
122 If the HOST keyword is passed as the node template name argument the
123 function will search each node template along the HostedOn relationship
124 chain until a node which contains the attribute is found.
128 * { get_attribute: [ server, private_address ] }
129 * { get_attribute: [ HOST, private_address ] }
130 * { get_attribute: [ HOST, private_address, 0 ] }
131 * { get_attribute: [ HOST, private_address, 0, some_prop] }
135 if len(self.args) < 2:
136 ExceptionCollector.appendException(
137 ValueError(_('Illegal arguments for function "{0}". Expected '
138 'arguments: "node-template-name", "req-or-cap"'
139 '(optional), "property name"'
140 ).format(GET_ATTRIBUTE)))
142 elif len(self.args) == 2:
143 self._find_node_template_containing_attribute()
145 node_tpl = self._find_node_template(self.args[0])
147 attrs = node_tpl.type_definition.get_attributes_def()
148 found = [attrs[self.args[1]]] if self.args[1] in attrs else []
153 # then check the req or caps
154 attr = self._find_req_or_cap_attribute(self.args[1],
157 value_type = attr.schema['type']
158 if len(self.args) > index:
159 for elem in self.args[index:]:
160 if value_type == "list":
161 if not isinstance(elem, int):
162 ExceptionCollector.appendException(
163 ValueError(_('Illegal arguments for function'
164 ' "{0}". "{1}" Expected positive'
166 ).format(GET_ATTRIBUTE, elem)))
167 value_type = attr.schema['entry_schema']['type']
168 elif value_type == "map":
169 value_type = attr.schema['entry_schema']['type']
170 elif value_type in Schema.PROPERTY_TYPES:
171 ExceptionCollector.appendException(
172 ValueError(_('Illegal arguments for function'
173 ' "{0}". Unexpected attribute/'
175 ).format(GET_ATTRIBUTE, elem)))
177 else: # It is a complex type
178 data_type = DataType(value_type)
179 props = data_type.get_all_properties()
180 found = [props[elem]] if elem in props else []
183 value_type = prop.schema['type']
185 ExceptionCollector.appendException(
186 KeyError(_('Illegal arguments for function'
187 ' "{0}". Attribute name "{1}" not'
189 ).format(GET_ATTRIBUTE,
196 def get_referenced_node_template(self):
197 """Gets the NodeTemplate instance the get_attribute function refers to.
199 If HOST keyword was used as the node template argument, the node
200 template which contains the attribute along the HostedOn relationship
201 chain will be returned.
203 return self._find_node_template_containing_attribute()
205 def _find_node_template_containing_attribute(self):
206 node_tpl = self._find_node_template(self.args[0])
208 not self._attribute_exists_in_type(node_tpl.type_definition):
209 ExceptionCollector.appendException(
210 KeyError(_('Attribute "%(att)s" was not found in node '
211 'template "%(ntpl)s".') %
212 {'att': self.attribute_name,
213 'ntpl': node_tpl.name}))
216 def _attribute_exists_in_type(self, type_definition):
217 attrs_def = type_definition.get_attributes_def()
218 found = [attrs_def[self.attribute_name]] \
219 if self.attribute_name in attrs_def else []
220 return len(found) == 1
222 def _find_host_containing_attribute(self, node_template_name=SELF):
223 node_template = self._find_node_template(node_template_name)
225 hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
226 for r in node_template.requirements:
227 for requirement, target_name in r.items():
228 target_node = self._find_node_template(target_name)
229 target_type = target_node.type_definition
230 for capability in target_type.get_capabilities_objects():
231 if capability.type in \
232 hosted_on_rel['valid_target_types']:
233 if self._attribute_exists_in_type(target_type):
235 return self._find_host_containing_attribute(
238 def _find_node_template(self, node_template_name):
239 if node_template_name == HOST:
240 # Currently this is the only way to tell whether the function
241 # is used within the outputs section of the TOSCA template.
242 if isinstance(self.context, list):
243 ExceptionCollector.appendException(
245 '"get_attribute: [ HOST, ... ]" is not allowed in '
246 '"outputs" section of the TOSCA template.')))
248 node_tpl = self._find_host_containing_attribute()
250 ExceptionCollector.appendException(
252 '"get_attribute: [ HOST, ... ]" was used in node '
253 'template "{0}" but "{1}" was not found in '
254 'the relationship chain.').format(self.context.name,
258 if node_template_name == TARGET:
259 if not isinstance(self.context.type_definition, RelationshipType):
260 ExceptionCollector.appendException(
261 KeyError(_('"TARGET" keyword can only be used in context'
262 ' to "Relationships" target node')))
264 return self.context.target
265 if node_template_name == SOURCE:
266 if not isinstance(self.context.type_definition, RelationshipType):
267 ExceptionCollector.appendException(
268 KeyError(_('"SOURCE" keyword can only be used in context'
269 ' to "Relationships" source node')))
271 return self.context.source
272 name = self.context.name \
273 if node_template_name == SELF and \
274 not isinstance(self.context, list) \
275 else node_template_name
276 for node_template in self.tosca_tpl.nodetemplates:
277 if node_template.name == name:
279 ExceptionCollector.appendException(
281 'Node template "{0}" was not found.'
282 ).format(node_template_name)))
284 def _find_req_or_cap_attribute(self, req_or_cap, attr_name):
285 node_tpl = self._find_node_template(self.args[0])
286 # Find attribute in node template's requirements
287 for r in node_tpl.requirements:
288 for req, node_name in r.items():
289 if req == req_or_cap:
290 node_template = self._find_node_template(node_name)
291 return self._get_capability_attribute(
295 # If requirement was not found, look in node template's capabilities
296 return self._get_capability_attribute(node_tpl,
300 def _get_capability_attribute(self,
304 """Gets a node template capability attribute."""
305 caps = node_template.get_capabilities()
306 if caps and capability_name in caps.keys():
307 cap = caps[capability_name]
309 attrs = cap.definition.get_attributes_def()
310 if attrs and attr_name in attrs.keys():
311 attribute = attrs[attr_name]
313 ExceptionCollector.appendException(
314 KeyError(_('Attribute "%(attr)s" was not found in '
315 'capability "%(cap)s" of node template '
316 '"%(ntpl1)s" referenced from node template '
317 '"%(ntpl2)s".') % {'attr': attr_name,
318 'cap': capability_name,
319 'ntpl1': node_template.name,
320 'ntpl2': self.context.name}))
322 msg = _('Requirement/Capability "{0}" referenced from node template '
323 '"{1}" was not found in node template "{2}".').format(
327 ExceptionCollector.appendException(KeyError(msg))
330 def node_template_name(self):
334 def attribute_name(self):
338 class GetProperty(Function):
339 """Get a property value of an entity defined in the same service template.
343 * Node template name | SELF | HOST | SOURCE | TARGET.
344 * Requirement or capability name (optional).
347 If requirement or capability name is specified, the behavior is as follows:
348 The req or cap name is first looked up in the specified node template's
350 If found, it would search for a matching capability
351 of an other node template and get its property as specified in function
353 Otherwise, the req or cap name would be looked up in the specified
354 node template's capabilities and if found, it would return the property of
355 the capability as specified in function arguments.
359 * { get_property: [ mysql_server, port ] }
360 * { get_property: [ SELF, db_port ] }
361 * { get_property: [ SELF, database_endpoint, port ] }
362 * { get_property: [ SELF, database_endpoint, port, 1 ] }
366 if len(self.args) < 2:
367 ExceptionCollector.appendException(
369 'Expected arguments: "node-template-name", "req-or-cap" '
370 '(optional), "property name".')))
372 if len(self.args) == 2:
373 found_prop = self._find_property(self.args[1])
376 prop = found_prop.value
377 if not isinstance(prop, Function):
378 get_function(self.tosca_tpl, self.context, prop)
379 elif len(self.args) >= 3:
380 # do not use _find_property to avoid raise KeyError
381 # if the prop is not found
382 # First check if there is property with this name
383 node_tpl = self._find_node_template(self.args[0])
384 props = node_tpl.get_properties() if node_tpl else []
386 found = [props[self.args[1]]] if self.args[1] in props else []
388 property_value = found[0].value
391 # then check the req or caps
392 property_value = self._find_req_or_cap_property(self.args[1],
394 if len(self.args) > index:
395 for elem in self.args[index:]:
396 if isinstance(property_value, list):
398 property_value = self._get_index_value(property_value,
401 property_value = self._get_attribute_value(
405 def _find_req_or_cap_property(self, req_or_cap, property_name):
406 node_tpl = self._find_node_template(self.args[0])
407 # Find property in node template's requirements
408 for r in node_tpl.requirements:
409 for req, node_name in r.items():
410 if req == req_or_cap:
411 node_template = self._find_node_template(node_name)
412 return self._get_capability_property(
416 # If requirement was not found, look in node template's capabilities
417 return self._get_capability_property(node_tpl,
421 def _get_capability_property(self,
425 """Gets a node template capability property."""
426 caps = node_template.get_capabilities()
427 if caps and capability_name in caps.keys():
428 cap = caps[capability_name]
430 props = cap.get_properties()
431 if props and property_name in props.keys():
432 property = props[property_name].value
434 ExceptionCollector.appendException(
435 KeyError(_('Property "%(prop)s" was not found in '
436 'capability "%(cap)s" of node template '
437 '"%(ntpl1)s" referenced from node template '
438 '"%(ntpl2)s".') % {'prop': property_name,
439 'cap': capability_name,
440 'ntpl1': node_template.name,
441 'ntpl2': self.context.name}))
443 msg = _('Requirement/Capability "{0}" referenced from node template '
444 '"{1}" was not found in node template "{2}".').format(
448 ExceptionCollector.appendException(KeyError(msg))
450 def _find_property(self, property_name):
451 node_tpl = self._find_node_template(self.args[0])
454 props = node_tpl.get_properties()
455 found = [props[property_name]] if property_name in props else []
457 ExceptionCollector.appendException(
458 KeyError(_('Property "%(prop)s" was not found in node '
459 'template "%(ntpl)s".') %
460 {'prop': property_name,
461 'ntpl': node_tpl.name}))
465 def _find_node_template(self, node_template_name):
466 if node_template_name == SELF:
468 # enable the HOST value in the function
469 if node_template_name == HOST:
470 return self._find_host_containing_property()
471 if node_template_name == TARGET:
472 if not isinstance(self.context.type_definition, RelationshipType):
473 ExceptionCollector.appendException(
474 KeyError(_('"TARGET" keyword can only be used in context'
475 ' to "Relationships" target node')))
477 return self.context.target
478 if node_template_name == SOURCE:
479 if not isinstance(self.context.type_definition, RelationshipType):
480 ExceptionCollector.appendException(
481 KeyError(_('"SOURCE" keyword can only be used in context'
482 ' to "Relationships" source node')))
484 return self.context.source
485 if not hasattr(self.tosca_tpl, 'nodetemplates'):
487 for node_template in self.tosca_tpl.nodetemplates:
488 if node_template.name == node_template_name:
490 ExceptionCollector.appendException(
492 'Node template "{0}" was not found.'
493 ).format(node_template_name)))
495 def _get_index_value(self, value, index):
496 if isinstance(value, list):
497 if index < len(value):
500 ExceptionCollector.appendException(
502 "Property '{0}' found in capability '{1}'"
503 " referenced from node template {2}"
504 " must have an element with index {3}.").
510 ExceptionCollector.appendException(
512 "Property '{0}' found in capability '{1}'"
513 " referenced from node template {2}"
514 " must be a list.").format(self.args[2],
518 def _get_attribute_value(self, value, attibute):
519 if isinstance(value, dict):
520 if attibute in value:
521 return value[attibute]
523 ExceptionCollector.appendException(
525 "Property '{0}' found in capability '{1}'"
526 " referenced from node template {2}"
527 " must have an attribute named {3}.").
533 ExceptionCollector.appendException(
535 "Property '{0}' found in capability '{1}'"
536 " referenced from node template {2}"
537 " must be a dict.").format(self.args[2],
541 # Add this functions similar to get_attribute case
542 def _find_host_containing_property(self, node_template_name=SELF):
543 node_template = self._find_node_template(node_template_name)
544 hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
545 for r in node_template.requirements:
546 for requirement, target_name in r.items():
547 target_node = self._find_node_template(target_name)
548 target_type = target_node.type_definition
549 for capability in target_type.get_capabilities_objects():
550 if capability.type in hosted_on_rel['valid_target_types']:
551 if self._property_exists_in_type(target_type):
553 return self._find_host_containing_property(
557 def _property_exists_in_type(self, type_definition):
558 props_def = type_definition.get_properties_def()
559 found = [props_def[self.args[1]]] \
560 if self.args[1] in props_def else []
561 return len(found) == 1
564 if len(self.args) >= 3:
565 # First check if there is property with this name
566 node_tpl = self._find_node_template(self.args[0])
567 props = node_tpl.get_properties() if node_tpl else []
569 found = [props[self.args[1]]] if self.args[1] in props else []
571 property_value = found[0].value
574 # then check the req or caps
575 property_value = self._find_req_or_cap_property(self.args[1],
577 if len(self.args) > index:
578 for elem in self.args[index:]:
579 if isinstance(property_value, list):
581 property_value = self._get_index_value(property_value,
584 property_value = self._get_attribute_value(
588 property_value = self._find_property(self.args[1]).value
589 if isinstance(property_value, Function):
590 return property_value.result()
591 return get_function(self.tosca_tpl,
596 def node_template_name(self):
600 def property_name(self):
601 if len(self.args) > 2:
606 def req_or_cap(self):
607 if len(self.args) > 2:
612 class Concat(Function):
613 """Validate the function and provide an instance of the function
615 Concatenation of values are supposed to be produced at runtime and
616 therefore its the responsibility of the TOSCA engine to implement the
617 evaluation of Concat functions.
621 * List of strings that needs to be concatenated
626 get_attribute: [ server, public_address ],
628 get_attribute: [ server, port ] ]
632 if len(self.args) < 1:
633 ExceptionCollector.appendException(
634 ValueError(_('Invalid arguments for function "{0}". Expected '
635 'at least one arguments.').format(CONCAT)))
641 class Token(Function):
642 """Validate the function and provide an instance of the function
644 The token function is used within a TOSCA service template on a string to
645 parse out (tokenize) substrings separated by one or more token characters
646 within a larger string.
651 * The composite string that contains one or more substrings separated by
653 * The string that contains one or more token characters that separate
654 substrings within the composite string.
655 * The integer indicates the index of the substring to return from the
656 composite string. Note that the first substring is denoted by using
657 the '0' (zero) integer value.
661 [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ]
666 if len(self.args) < 3:
667 ExceptionCollector.appendException(
668 ValueError(_('Invalid arguments for function "{0}". Expected '
669 'at least three arguments.').format(TOKEN)))
671 if not isinstance(self.args[1], str) or len(self.args[1]) != 1:
672 ExceptionCollector.appendException(
673 ValueError(_('Invalid arguments for function "{0}". '
674 'Expected single char value as second '
675 'argument.').format(TOKEN)))
677 if not isinstance(self.args[2], int):
678 ExceptionCollector.appendException(
679 ValueError(_('Invalid arguments for function "{0}". '
680 'Expected integer value as third '
681 'argument.').format(TOKEN)))
686 function_mappings = {
687 GET_PROPERTY: GetProperty,
689 GET_ATTRIBUTE: GetAttribute,
695 def is_function(function):
696 """Returns True if the provided function is a Tosca intrinsic function.
700 * "{ get_property: { SELF, port } }"
701 * "{ get_input: db_name }"
704 :param function: Function as string or a Function instance.
705 :return: True if function is a Tosca intrinsic function, otherwise False.
707 if isinstance(function, dict) and len(function) == 1:
708 func_name = list(function.keys())[0]
709 return func_name in function_mappings
710 return isinstance(function, Function)
713 def get_function(tosca_tpl, node_template, raw_function):
714 """Gets a Function instance representing the provided template function.
716 If the format provided raw_function format is not relevant for template
717 functions or if the function name doesn't exist in function mapping the
718 method returns the provided raw_function.
720 :param tosca_tpl: The tosca template.
721 :param node_template: The node template the function is specified for.
722 :param raw_function: The raw function as dict.
723 :return: Template function as Function instance or the raw_function if
724 parsing was unsuccessful.
726 if is_function(raw_function):
727 func_name = list(raw_function.keys())[0]
728 if func_name in function_mappings:
729 func = function_mappings[func_name]
730 func_args = list(raw_function.values())[0]
731 if not isinstance(func_args, list):
732 func_args = [func_args]
733 return func(tosca_tpl, node_template, func_name, func_args)