diff --git a/.gitignore b/.gitignore index 1399c981..7dfa8ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,6 @@ ChangeLog # Editors *~ -.*.swp \ No newline at end of file +.*.swp +.idea +*.iml \ No newline at end of file diff --git a/translator/toscalib/elements/interfaces.py b/translator/toscalib/elements/interfaces.py index 99a3f252..e9d4b72d 100644 --- a/translator/toscalib/elements/interfaces.py +++ b/translator/toscalib/elements/interfaces.py @@ -14,6 +14,7 @@ # under the License. from translator.toscalib.elements.statefulentitytype import StatefulEntityType +from translator.toscalib.functions import get_function SECTIONS = (LIFECYCLE, CONFIGURE) = \ ('tosca.interfaces.node.Lifecycle', @@ -23,17 +24,17 @@ SECTIONS = (LIFECYCLE, CONFIGURE) = \ class InterfacesDef(StatefulEntityType): '''TOSCA built-in interfaces type.''' - def __init__(self, ntype, interfacetype, - tpl_name=None, name=None, value=None): - self.nodetype = ntype - self.tpl_name = tpl_name + def __init__(self, node_type, interfacetype, + node_template=None, name=None, value=None): + self.ntype = node_type + self.node_template = node_template self.type = interfacetype self.name = name self.value = value self.implementation = None self.input = None self.defs = {} - if ntype: + if node_type: self.defs = self.TOSCA_DEF[interfacetype] if value: if isinstance(self.value, dict): @@ -41,10 +42,19 @@ class InterfacesDef(StatefulEntityType): if i == 'implementation': self.implementation = j if i == 'input': - self.input = j + self.input = self._create_input_functions(j) else: self.implementation = value + def _create_input_functions(self, raw_input): + """Creates input functions if necessary. + :param raw_input: Raw input as dict. + :return: Modified input dict containing template functions. + :rtype: dict + """ + return dict((k, get_function(self.node_template, v)) + for (k, v) in raw_input.items()) + @property def lifecycle_ops(self): if self.defs: diff --git a/translator/toscalib/functions.py b/translator/toscalib/functions.py new file mode 100644 index 00000000..c7e013cf --- /dev/null +++ b/translator/toscalib/functions.py @@ -0,0 +1,111 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import abc + + +GET_PROPERTY = 'get_property' +GET_REF_PROPERTY = 'get_ref_property' + + +class Function(object): + """An abstract type for representing a Tosca template function.""" + + __metaclass__ = abc.ABCMeta + + def __init__(self, node_template, func_name, args): + self.node_template = node_template + self.name = func_name + self.args = args + self.validate() + + @abc.abstractmethod + def result(self): + """Invokes the function and returns its result + + Some methods invocation may only be relevant on runtime (for example, + getting runtime properties) and therefore its the responsibility of + the orchestrator/translator to take care of such functions invocation. + + :return: Function invocation result. + """ + return {self.name: self.args} + + def validate(self): + """Validates function arguments.""" + + +class GetRefProperty(Function): + """Get a property via a reference expressed in the requirements section. + + Arguments: + - Requirement name. + - Capability name. + - Property to get. + + Example: + get_ref_property: [ database_endpoint, database_endpoint, port ] + """ + + def validate(self): + if len(self.args) != 3: + raise ValueError( + 'Expected arguments: requirement, capability, property') + + def result(self): + requires = self.node_template.requirements + name = None + if requires: + requirement = self.args[0] + for r in requires: + for cap, node in r.items(): + if cap == requirement: + name = node + break + if name: + from translator.toscalib.nodetemplate import NodeTemplate + tpl = NodeTemplate( + name, self.node_template.node_templates) + caps = tpl.capabilities + required_cap = self.args[1] + required_property = self.args[2] + for c in caps: + if c.name == required_cap: + return c.properties.get(required_property) + + +function_mappings = { + GET_REF_PROPERTY: GetRefProperty +} + + +def get_function(node_template, raw_function): + """Gets a Function instance representing the provided template function. + + If the format provided raw_function format is not relevant for template + functions or if the function name doesn't exist in function mapping the + method returns the provided raw_function. + + :param node_template: The node template the function is specified for. + :param raw_function: The raw function as dict. + :return: Template function as Function instance or the raw_function if + parsing was unsuccessful. + """ + if isinstance(raw_function, dict) and len(raw_function) == 1: + func_name = list(raw_function.keys())[0] + if func_name in function_mappings: + func = function_mappings[func_name] + func_args = list(raw_function.values())[0] + return func(node_template, func_name, func_args) + return raw_function diff --git a/translator/toscalib/nodetemplate.py b/translator/toscalib/nodetemplate.py index 016aae6c..b0063b4f 100644 --- a/translator/toscalib/nodetemplate.py +++ b/translator/toscalib/nodetemplate.py @@ -78,14 +78,17 @@ class NodeTemplate(object): @property def interfaces(self): interfaces = [] - ifaces = self.node_type.get_value(INTERFACES, self.node_template) - if ifaces: - for i in ifaces: - for name, value in ifaces.items(): - for ops, val in value.items(): - iface = InterfacesDef(None, name, self.name, - ops, val) - interfaces.append(iface) + type_interfaces = self.node_type.get_value(INTERFACES, + self.node_template) + if type_interfaces: + for interface_type, value in type_interfaces.items(): + for op, op_def in value.items(): + iface = InterfacesDef(self.node_type, + interfacetype=interface_type, + node_template=self, + name=op, + value=op_def) + interfaces.append(iface) return interfaces @property diff --git a/translator/toscalib/tests/test_toscatpl.py b/translator/toscalib/tests/test_toscatpl.py index 9e80f2c5..76215cbc 100644 --- a/translator/toscalib/tests/test_toscatpl.py +++ b/translator/toscalib/tests/test_toscatpl.py @@ -14,6 +14,7 @@ # under the License. import os +from translator.toscalib.functions import GetRefProperty from translator.toscalib.tests.base import TestCase from translator.toscalib.tosca_template import ToscaTemplate @@ -104,3 +105,36 @@ class ToscaTemplateTest(TestCase): self.assertEqual( ['website_url'], sorted([output.name for output in self.tosca.outputs])) + + def test_interfaces(self): + wordpress_node = [ + node for node in self.tosca.nodetemplates + if node.name == 'wordpress'][0] + interfaces = wordpress_node.interfaces + self.assertEqual(2, len(interfaces)) + for interface in interfaces: + if interface.name == 'create': + self.assertEqual('tosca.interfaces.node.Lifecycle', + interface.type) + self.assertEqual('wordpress_install.sh', + interface.implementation) + self.assertIsNone(interface.input) + elif interface.name == 'configure': + self.assertEqual('tosca.interfaces.node.Lifecycle', + interface.type) + self.assertEqual('wordpress_configure.sh', + interface.implementation) + self.assertEqual(4, len(interface.input)) + wp_db_port = interface.input['wp_db_port'] + self.assertTrue(isinstance(wp_db_port, GetRefProperty)) + self.assertEqual('get_ref_property', wp_db_port.name) + self.assertEqual(['database_endpoint', + 'database_endpoint', + 'port'], + wp_db_port.args) + result = wp_db_port.result() + self.assertEqual(1, len(result)) + self.assertEqual('db_port', result['get_input']) + else: + raise AssertionError( + 'Unexpected interface: {0}'.format(interface.name))