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.utils.gettextutils import _
24 GET_PROPERTY = 'get_property'
25 GET_ATTRIBUTE = 'get_attribute'
26 GET_INPUT = 'get_input'
31 HOSTED_ON = 'tosca.relationships.HostedOn'
34 @six.add_metaclass(abc.ABCMeta)
35 class Function(object):
36 """An abstract type for representing a Tosca template function."""
38 def __init__(self, tosca_tpl, context, name, args):
39 self.tosca_tpl = tosca_tpl
40 self.context = context
47 """Invokes the function and returns its result
49 Some methods invocation may only be relevant on runtime (for example,
50 getting runtime properties) and therefore its the responsibility of
51 the orchestrator/translator to take care of such functions invocation.
53 :return: Function invocation result.
55 return {self.name: self.args}
59 """Validates function arguments."""
63 class GetInput(Function):
64 """Get a property value declared within the input of the service template.
76 if len(self.args) != 1:
77 ExceptionCollector.appendException(
79 'Expected one argument for function "get_input" but '
80 'received "%s".') % self.args))
81 inputs = [input.name for input in self.tosca_tpl.inputs]
82 if self.args[0] not in inputs:
83 ExceptionCollector.appendException(
84 UnknownInputError(input_name=self.args[0]))
87 if self.tosca_tpl.parsed_params and \
88 self.input_name in self.tosca_tpl.parsed_params:
89 return DataEntity.validate_datatype(
90 self.tosca_tpl.tpl['inputs'][self.input_name]['type'],
91 self.tosca_tpl.parsed_params[self.input_name])
93 input = [input_def for input_def in self.tosca_tpl.inputs
94 if self.input_name == input_def.name][0]
102 class GetAttribute(Function):
103 """Get an attribute value of an entity defined in the service template
105 Node template attributes values are set in runtime and therefore its the
106 responsibility of the Tosca engine to implement the evaluation of
107 get_attribute functions.
111 * Node template name | HOST.
114 If the HOST keyword is passed as the node template name argument the
115 function will search each node template along the HostedOn relationship
116 chain until a node which contains the attribute is found.
120 * { get_attribute: [ server, private_address ] }
121 * { get_attribute: [ HOST, private_address ] }
125 if len(self.args) != 2:
126 ExceptionCollector.appendException(
127 ValueError(_('Illegal arguments for function "{0}". Expected '
128 'arguments: "node-template-name", '
129 '"attribute-name"').format(GET_ATTRIBUTE)))
130 self._find_node_template_containing_attribute()
135 def get_referenced_node_template(self):
136 """Gets the NodeTemplate instance the get_attribute function refers to.
138 If HOST keyword was used as the node template argument, the node
139 template which contains the attribute along the HostedOn relationship
140 chain will be returned.
142 return self._find_node_template_containing_attribute()
144 def _find_node_template_containing_attribute(self):
145 if self.node_template_name == HOST:
146 # Currently this is the only way to tell whether the function
147 # is used within the outputs section of the TOSCA template.
148 if isinstance(self.context, list):
149 ExceptionCollector.appendException(
151 '"get_attribute: [ HOST, ... ]" is not allowed in '
152 '"outputs" section of the TOSCA template.')))
154 node_tpl = self._find_host_containing_attribute()
156 ExceptionCollector.appendException(
158 '"get_attribute: [ HOST, ... ]" was used in node '
159 'template "{0}" but "{1}" was not found in '
160 'the relationship chain.').format(self.context.name,
163 node_tpl = self._find_node_template(self.args[0])
165 not self._attribute_exists_in_type(node_tpl.type_definition):
166 ExceptionCollector.appendException(
167 KeyError(_('Attribute "%(att)s" was not found in node '
168 'template "%(ntpl)s".') %
169 {'att': self.attribute_name,
170 'ntpl': node_tpl.name}))
173 def _attribute_exists_in_type(self, type_definition):
174 attrs_def = type_definition.get_attributes_def()
175 found = [attrs_def[self.attribute_name]] \
176 if self.attribute_name in attrs_def else []
177 return len(found) == 1
179 def _find_host_containing_attribute(self, node_template_name=SELF):
180 node_template = self._find_node_template(node_template_name)
182 from toscaparser.elements.entity_type import EntityType
183 hosted_on_rel = EntityType.TOSCA_DEF[HOSTED_ON]
184 for r in node_template.requirements:
185 for requirement, target_name in r.items():
186 target_node = self._find_node_template(target_name)
187 target_type = target_node.type_definition
188 for capability in target_type.get_capabilities_objects():
189 if capability.type in \
190 hosted_on_rel['valid_target_types']:
191 if self._attribute_exists_in_type(target_type):
193 return self._find_host_containing_attribute(
196 def _find_node_template(self, node_template_name):
197 name = self.context.name \
198 if node_template_name == SELF and \
199 not isinstance(self.context, list) \
200 else node_template_name
201 for node_template in self.tosca_tpl.nodetemplates:
202 if node_template.name == name:
204 ExceptionCollector.appendException(
206 'Node template "{0}" was not found.'
207 ).format(node_template_name)))
210 def node_template_name(self):
214 def attribute_name(self):
218 class GetProperty(Function):
219 """Get a property value of an entity defined in the same service template.
223 * Node template name.
224 * Requirement or capability name (optional).
227 If requirement or capability name is specified, the behavior is as follows:
228 The req or cap name is first looked up in the specified node template's
230 If found, it would search for a matching capability
231 of an other node template and get its property as specified in function
233 Otherwise, the req or cap name would be looked up in the specified
234 node template's capabilities and if found, it would return the property of
235 the capability as specified in function arguments.
239 * { get_property: [ mysql_server, port ] }
240 * { get_property: [ SELF, db_port ] }
241 * { get_property: [ SELF, database_endpoint, port ] }
245 if len(self.args) < 2 or len(self.args) > 3:
246 ExceptionCollector.appendException(
248 'Expected arguments: "node-template-name", "req-or-cap" '
249 '(optional), "property name".')))
251 if len(self.args) == 2:
252 found_prop = self._find_property(self.args[1])
255 prop = found_prop.value
256 if not isinstance(prop, Function):
257 get_function(self.tosca_tpl, self.context, prop)
258 elif len(self.args) == 3:
259 get_function(self.tosca_tpl,
261 self._find_req_or_cap_property(self.args[1],
264 ExceptionCollector.appendException(
265 NotImplementedError(_(
266 'Nested properties are not supported.')))
268 def _find_req_or_cap_property(self, req_or_cap, property_name):
269 node_tpl = self._find_node_template(self.args[0])
270 # Find property in node template's requirements
271 for r in node_tpl.requirements:
272 for req, node_name in r.items():
273 if req == req_or_cap:
274 node_template = self._find_node_template(node_name)
275 return self._get_capability_property(
279 # If requirement was not found, look in node template's capabilities
280 return self._get_capability_property(node_tpl,
284 def _get_capability_property(self,
288 """Gets a node template capability property."""
289 caps = node_template.get_capabilities()
290 if caps and capability_name in caps.keys():
291 cap = caps[capability_name]
293 props = cap.get_properties()
294 if props and property_name in props.keys():
295 property = props[property_name].value
297 ExceptionCollector.appendException(
298 KeyError(_('Property "%(prop)s" was not found in '
299 'capability "%(cap)s" of node template '
300 '"%(ntpl1)s" referenced from node template '
301 '"%(ntpl2)s".') % {'prop': property_name,
302 'cap': capability_name,
303 'ntpl1': node_template.name,
304 'ntpl2': self.context.name}))
306 msg = _('Requirement/Capability "{0}" referenced from node template '
307 '"{1}" was not found in node template "{2}".').format(
311 ExceptionCollector.appendException(KeyError(msg))
313 def _find_property(self, property_name):
314 node_tpl = self._find_node_template(self.args[0])
317 props = node_tpl.get_properties()
318 found = [props[property_name]] if property_name in props else []
320 ExceptionCollector.appendException(
321 KeyError(_('Property "%(prop)s" was not found in node '
322 'template "%(ntpl)s".') %
323 {'prop': property_name,
324 'ntpl': node_tpl.name}))
328 def _find_node_template(self, node_template_name):
329 if node_template_name == SELF:
331 if not hasattr(self.tosca_tpl, 'nodetemplates'):
333 for node_template in self.tosca_tpl.nodetemplates:
334 if node_template.name == node_template_name:
336 ExceptionCollector.appendException(
338 'Node template "{0}" was not found.'
339 ).format(node_template_name)))
342 if len(self.args) == 3:
343 property_value = self._find_req_or_cap_property(self.args[1],
346 property_value = self._find_property(self.args[1]).value
347 if isinstance(property_value, Function):
348 return property_value.result()
349 return get_function(self.tosca_tpl,
354 def node_template_name(self):
358 def property_name(self):
359 if len(self.args) > 2:
364 def req_or_cap(self):
365 if len(self.args) > 2:
370 function_mappings = {
371 GET_PROPERTY: GetProperty,
373 GET_ATTRIBUTE: GetAttribute
377 def is_function(function):
378 """Returns True if the provided function is a Tosca intrinsic function.
382 * "{ get_property: { SELF, port } }"
383 * "{ get_input: db_name }"
386 :param function: Function as string or a Function instance.
387 :return: True if function is a Tosca intrinsic function, otherwise False.
389 if isinstance(function, dict) and len(function) == 1:
390 func_name = list(function.keys())[0]
391 return func_name in function_mappings
392 return isinstance(function, Function)
395 def get_function(tosca_tpl, node_template, raw_function):
396 """Gets a Function instance representing the provided template function.
398 If the format provided raw_function format is not relevant for template
399 functions or if the function name doesn't exist in function mapping the
400 method returns the provided raw_function.
402 :param tosca_tpl: The tosca template.
403 :param node_template: The node template the function is specified for.
404 :param raw_function: The raw function as dict.
405 :return: Template function as Function instance or the raw_function if
406 parsing was unsuccessful.
408 if is_function(raw_function):
409 func_name = list(raw_function.keys())[0]
410 if func_name in function_mappings:
411 func = function_mappings[func_name]
412 func_args = list(raw_function.values())[0]
413 if not isinstance(func_args, list):
414 func_args = [func_args]
415 return func(tosca_tpl, node_template, func_name, func_args)