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.entity_type import EntityType
22 from toscaparser.elements.relationshiptype import RelationshipType
23 from toscaparser.utils.gettextutils import _
26 GET_PROPERTY = 'get_property'
27 GET_ATTRIBUTE = 'get_attribute'
28 GET_INPUT = 'get_input'
36 HOSTED_ON = 'tosca.relationships.HostedOn'
39 @six.add_metaclass(abc.ABCMeta)
40 class Function(object):
41 """An abstract type for representing a Tosca template function."""
43 def __init__(self, tosca_tpl, context, name, args):
44 self.tosca_tpl = tosca_tpl
45 self.context = context
52 """Invokes the function and returns its result
54 Some methods invocation may only be relevant on runtime (for example,
55 getting runtime properties) and therefore its the responsibility of
56 the orchestrator/translator to take care of such functions invocation.
58 :return: Function invocation result.
60 return {self.name: self.args}
64 """Validates function arguments."""
68 class GetInput(Function):
69 """Get a property value declared within the input of the service template.
81 if len(self.args) != 1:
82 ExceptionCollector.appendException(
84 'Expected one argument for function "get_input" but '
85 'received "%s".') % self.args))
86 inputs = [input.name for input in self.tosca_tpl.inputs]
87 if self.args[0] not in inputs:
88 ExceptionCollector.appendException(
89 UnknownInputError(input_name=self.args[0]))
92 if self.tosca_tpl.parsed_params and \
93 self.input_name in self.tosca_tpl.parsed_params:
94 return DataEntity.validate_datatype(
95 self.tosca_tpl.tpl['inputs'][self.input_name]['type'],
96 self.tosca_tpl.parsed_params[self.input_name])
98 input = [input_def for input_def in self.tosca_tpl.inputs
99 if self.input_name == input_def.name][0]
103 def input_name(self):
107 class GetAttribute(Function):
108 """Get an attribute value of an entity defined in the service template
110 Node template attributes values are set in runtime and therefore its the
111 responsibility of the Tosca engine to implement the evaluation of
112 get_attribute functions.
116 * Node template name | HOST.
119 If the HOST keyword is passed as the node template name argument the
120 function will search each node template along the HostedOn relationship
121 chain until a node which contains the attribute is found.
125 * { get_attribute: [ server, private_address ] }
126 * { get_attribute: [ HOST, private_address ] }
127 * { get_attribute: [ HOST, private_address, 0 ] }
131 if len(self.args) != 2 and len(self.args) != 3:
132 ExceptionCollector.appendException(
133 ValueError(_('Illegal arguments for function "{0}". Expected '
134 'arguments: "node-template-name", '
135 '"attribute-name"').format(GET_ATTRIBUTE)))
136 node_tpl = self._find_node_template_containing_attribute()
137 if len(self.args) > 2:
138 # Currently we only check the first level
139 attrs_def = node_tpl.type_definition.get_attributes_def()
140 attr_def = attrs_def[self.attribute_name]
141 if attr_def.schema['type'] == "list":
142 if not isinstance(self.args[2], int):
143 ExceptionCollector.appendException(
144 ValueError(_('Illegal arguments for function "{0}". '
145 'Third argument must be a positive'
146 ' integer') .format(GET_ATTRIBUTE)))
147 elif attr_def.schema['type'] != "map":
148 ExceptionCollector.appendException(
149 ValueError(_('Illegal arguments for function "{0}". '
150 'Expected arguments: "node-template-name", '
151 '"attribute-name"').format(GET_ATTRIBUTE)))
156 def get_referenced_node_template(self):
157 """Gets the NodeTemplate instance the get_attribute function refers to.
159 If HOST keyword was used as the node template argument, the node
160 template which contains the attribute along the HostedOn relationship
161 chain will be returned.
163 return self._find_node_template_containing_attribute()
165 def _find_node_template_containing_attribute(self):
166 if self.node_template_name == HOST:
167 # Currently this is the only way to tell whether the function
168 # is used within the outputs section of the TOSCA template.
169 if isinstance(self.context, list):
170 ExceptionCollector.appendException(
172 '"get_attribute: [ HOST, ... ]" is not allowed in '
173 '"outputs" section of the TOSCA template.')))
175 node_tpl = self._find_host_containing_attribute()
177 ExceptionCollector.appendException(
179 '"get_attribute: [ HOST, ... ]" was used in node '
180 'template "{0}" but "{1}" was not found in '
181 'the relationship chain.').format(self.context.name,
184 node_tpl = self._find_node_template(self.args[0])
186 not self._attribute_exists_in_type(node_tpl.type_definition):
187 ExceptionCollector.appendException(
188 KeyError(_('Attribute "%(att)s" was not found in node '
189 'template "%(ntpl)s".') %
190 {'att': self.attribute_name,
191 'ntpl': node_tpl.name}))
194 def _attribute_exists_in_type(self, type_definition):
195 attrs_def = type_definition.get_attributes_def()
196 found = [attrs_def[self.attribute_name]] \
197 if self.attribute_name in attrs_def else []
198 return len(found) == 1
200 def _find_host_containing_attribute(self, node_template_name=SELF):
201 node_template = self._find_node_template(node_template_name)
203 hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
204 for r in node_template.requirements:
205 for requirement, target_name in r.items():
206 target_node = self._find_node_template(target_name)
207 target_type = target_node.type_definition
208 for capability in target_type.get_capabilities_objects():
209 if capability.type in \
210 hosted_on_rel['valid_target_types']:
211 if self._attribute_exists_in_type(target_type):
213 return self._find_host_containing_attribute(
216 def _find_node_template(self, node_template_name):
217 if node_template_name == TARGET:
218 if not isinstance(self.context.type_definition, RelationshipType):
219 ExceptionCollector.appendException(
220 KeyError(_('"TARGET" keyword can only be used in context'
221 ' to "Relationships" target node')))
223 return self.context.target
224 if node_template_name == SOURCE:
225 if not isinstance(self.context.type_definition, RelationshipType):
226 ExceptionCollector.appendException(
227 KeyError(_('"SOURCE" keyword can only be used in context'
228 ' to "Relationships" source node')))
230 return self.context.source
231 name = self.context.name \
232 if node_template_name == SELF and \
233 not isinstance(self.context, list) \
234 else node_template_name
235 for node_template in self.tosca_tpl.nodetemplates:
236 if node_template.name == name:
238 ExceptionCollector.appendException(
240 'Node template "{0}" was not found.'
241 ).format(node_template_name)))
244 def node_template_name(self):
248 def attribute_name(self):
252 class GetProperty(Function):
253 """Get a property value of an entity defined in the same service template.
257 * Node template name | SELF | HOST | SOURCE | TARGET.
258 * Requirement or capability name (optional).
261 If requirement or capability name is specified, the behavior is as follows:
262 The req or cap name is first looked up in the specified node template's
264 If found, it would search for a matching capability
265 of an other node template and get its property as specified in function
267 Otherwise, the req or cap name would be looked up in the specified
268 node template's capabilities and if found, it would return the property of
269 the capability as specified in function arguments.
273 * { get_property: [ mysql_server, port ] }
274 * { get_property: [ SELF, db_port ] }
275 * { get_property: [ SELF, database_endpoint, port ] }
276 * { get_property: [ SELF, database_endpoint, port, 1 ] }
280 if len(self.args) < 2:
281 ExceptionCollector.appendException(
283 'Expected arguments: "node-template-name", "req-or-cap" '
284 '(optional), "property name".')))
286 if len(self.args) == 2:
287 found_prop = self._find_property(self.args[1])
290 prop = found_prop.value
291 if not isinstance(prop, Function):
292 get_function(self.tosca_tpl, self.context, prop)
293 elif len(self.args) >= 3:
294 # do not use _find_property to avoid raise KeyError
295 # if the prop is not found
296 # First check if there is property with this name
297 node_tpl = self._find_node_template(self.args[0])
298 props = node_tpl.get_properties() if node_tpl else []
300 found = [props[self.args[1]]] if self.args[1] in props else []
302 property_value = found[0].value
305 # then check the req or caps
306 property_value = self._find_req_or_cap_property(self.args[1],
308 if len(self.args) > index:
309 for elem in self.args[index:]:
310 if isinstance(property_value, list):
312 property_value = self._get_index_value(property_value,
315 property_value = self._get_attribute_value(
319 def _find_req_or_cap_property(self, req_or_cap, property_name):
320 node_tpl = self._find_node_template(self.args[0])
321 # Find property in node template's requirements
322 for r in node_tpl.requirements:
323 for req, node_name in r.items():
324 if req == req_or_cap:
325 node_template = self._find_node_template(node_name)
326 return self._get_capability_property(
330 # If requirement was not found, look in node template's capabilities
331 return self._get_capability_property(node_tpl,
335 def _get_capability_property(self,
339 """Gets a node template capability property."""
340 caps = node_template.get_capabilities()
341 if caps and capability_name in caps.keys():
342 cap = caps[capability_name]
344 props = cap.get_properties()
345 if props and property_name in props.keys():
346 property = props[property_name].value
348 ExceptionCollector.appendException(
349 KeyError(_('Property "%(prop)s" was not found in '
350 'capability "%(cap)s" of node template '
351 '"%(ntpl1)s" referenced from node template '
352 '"%(ntpl2)s".') % {'prop': property_name,
353 'cap': capability_name,
354 'ntpl1': node_template.name,
355 'ntpl2': self.context.name}))
357 msg = _('Requirement/Capability "{0}" referenced from node template '
358 '"{1}" was not found in node template "{2}".').format(
362 ExceptionCollector.appendException(KeyError(msg))
364 def _find_property(self, property_name):
365 node_tpl = self._find_node_template(self.args[0])
368 props = node_tpl.get_properties()
369 found = [props[property_name]] if property_name in props else []
371 ExceptionCollector.appendException(
372 KeyError(_('Property "%(prop)s" was not found in node '
373 'template "%(ntpl)s".') %
374 {'prop': property_name,
375 'ntpl': node_tpl.name}))
379 def _find_node_template(self, node_template_name):
380 if node_template_name == SELF:
382 # enable the HOST value in the function
383 if node_template_name == HOST:
384 return self._find_host_containing_property()
385 if node_template_name == TARGET:
386 if not isinstance(self.context.type_definition, RelationshipType):
387 ExceptionCollector.appendException(
388 KeyError(_('"TARGET" keyword can only be used in context'
389 ' to "Relationships" target node')))
391 return self.context.target
392 if node_template_name == SOURCE:
393 if not isinstance(self.context.type_definition, RelationshipType):
394 ExceptionCollector.appendException(
395 KeyError(_('"SOURCE" keyword can only be used in context'
396 ' to "Relationships" source node')))
398 return self.context.source
399 if not hasattr(self.tosca_tpl, 'nodetemplates'):
401 for node_template in self.tosca_tpl.nodetemplates:
402 if node_template.name == node_template_name:
404 ExceptionCollector.appendException(
406 'Node template "{0}" was not found.'
407 ).format(node_template_name)))
409 def _get_index_value(self, value, index):
410 if isinstance(value, list):
411 if index < len(value):
414 ExceptionCollector.appendException(
416 "Property '{0}' found in capability '{1}'"
417 " referenced from node template {2}"
418 " must have an element with index {3}.").
424 ExceptionCollector.appendException(
426 "Property '{0}' found in capability '{1}'"
427 " referenced from node template {2}"
428 " must be a list.").format(self.args[2],
432 def _get_attribute_value(self, value, attibute):
433 if isinstance(value, dict):
434 if attibute in value:
435 return value[attibute]
437 ExceptionCollector.appendException(
439 "Property '{0}' found in capability '{1}'"
440 " referenced from node template {2}"
441 " must have an attribute named {3}.").
447 ExceptionCollector.appendException(
449 "Property '{0}' found in capability '{1}'"
450 " referenced from node template {2}"
451 " must be a dict.").format(self.args[2],
455 # Add this functions similar to get_attribute case
456 def _find_host_containing_property(self, node_template_name=SELF):
457 node_template = self._find_node_template(node_template_name)
458 hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
459 for r in node_template.requirements:
460 for requirement, target_name in r.items():
461 target_node = self._find_node_template(target_name)
462 target_type = target_node.type_definition
463 for capability in target_type.get_capabilities_objects():
464 if capability.type in hosted_on_rel['valid_target_types']:
465 if self._property_exists_in_type(target_type):
467 return self._find_host_containing_property(
471 def _property_exists_in_type(self, type_definition):
472 props_def = type_definition.get_properties_def()
473 found = [props_def[self.args[1]]] \
474 if self.args[1] in props_def else []
475 return len(found) == 1
478 if len(self.args) >= 3:
479 # First check if there is property with this name
480 node_tpl = self._find_node_template(self.args[0])
481 props = node_tpl.get_properties() if node_tpl else []
483 found = [props[self.args[1]]] if self.args[1] in props else []
485 property_value = found[0].value
488 # then check the req or caps
489 property_value = self._find_req_or_cap_property(self.args[1],
491 if len(self.args) > index:
492 for elem in self.args[index:]:
493 if isinstance(property_value, list):
495 property_value = self._get_index_value(property_value,
498 property_value = self._get_attribute_value(
502 property_value = self._find_property(self.args[1]).value
503 if isinstance(property_value, Function):
504 return property_value.result()
505 return get_function(self.tosca_tpl,
510 def node_template_name(self):
514 def property_name(self):
515 if len(self.args) > 2:
520 def req_or_cap(self):
521 if len(self.args) > 2:
526 class Concat(Function):
527 """Validate the function and provide an instance of the function
529 Concatenation of values are supposed to be produced at runtime and
530 therefore its the responsibility of the TOSCA engine to implement the
531 evaluation of Concat functions.
535 * List of strings that needs to be concatenated
540 get_attribute: [ server, public_address ],
542 get_attribute: [ server, port ] ]
546 if len(self.args) < 1:
547 ExceptionCollector.appendException(
548 ValueError(_('Invalid arguments for function "{0}". Expected '
549 'at least one arguments.').format(CONCAT)))
554 function_mappings = {
555 GET_PROPERTY: GetProperty,
557 GET_ATTRIBUTE: GetAttribute,
562 def is_function(function):
563 """Returns True if the provided function is a Tosca intrinsic function.
567 * "{ get_property: { SELF, port } }"
568 * "{ get_input: db_name }"
571 :param function: Function as string or a Function instance.
572 :return: True if function is a Tosca intrinsic function, otherwise False.
574 if isinstance(function, dict) and len(function) == 1:
575 func_name = list(function.keys())[0]
576 return func_name in function_mappings
577 return isinstance(function, Function)
580 def get_function(tosca_tpl, node_template, raw_function):
581 """Gets a Function instance representing the provided template function.
583 If the format provided raw_function format is not relevant for template
584 functions or if the function name doesn't exist in function mapping the
585 method returns the provided raw_function.
587 :param tosca_tpl: The tosca template.
588 :param node_template: The node template the function is specified for.
589 :param raw_function: The raw function as dict.
590 :return: Template function as Function instance or the raw_function if
591 parsing was unsuccessful.
593 if is_function(raw_function):
594 func_name = list(raw_function.keys())[0]
595 if func_name in function_mappings:
596 func = function_mappings[func_name]
597 func_args = list(raw_function.values())[0]
598 if not isinstance(func_args, list):
599 func_args = [func_args]
600 return func(tosca_tpl, node_template, func_name, func_args)