fix package init name for nfv-toscaparser
[parser.git] / tosca2heat / tosca-parser / toscaparser / functions.py
index 011efde..b64f1bc 100644 (file)
 
 import abc
 import six
+import toscaparser.elements.interfaces
 
 from toscaparser.common.exception import ExceptionCollector
 from toscaparser.common.exception import UnknownInputError
 from toscaparser.dataentity import DataEntity
+from toscaparser.elements.constraints import Schema
+from toscaparser.elements.datatype import DataType
 from toscaparser.elements.entity_type import EntityType
 from toscaparser.elements.relationshiptype import RelationshipType
+from toscaparser.elements.statefulentitytype import StatefulEntityType
 from toscaparser.utils.gettextutils import _
 
 
 GET_PROPERTY = 'get_property'
 GET_ATTRIBUTE = 'get_attribute'
 GET_INPUT = 'get_input'
+GET_OPERATION_OUTPUT = 'get_operation_output'
 CONCAT = 'concat'
+TOKEN = 'token'
 
 SELF = 'SELF'
 HOST = 'HOST'
@@ -125,30 +131,71 @@ class GetAttribute(Function):
     * { get_attribute: [ server, private_address ] }
     * { get_attribute: [ HOST, private_address ] }
     * { get_attribute: [ HOST, private_address, 0 ] }
+    * { get_attribute: [ HOST, private_address, 0, some_prop] }
     """
 
     def validate(self):
-        if len(self.args) != 2 and len(self.args) != 3:
+        if len(self.args) < 2:
             ExceptionCollector.appendException(
                 ValueError(_('Illegal arguments for function "{0}". Expected '
-                             'arguments: "node-template-name", '
-                             '"attribute-name"').format(GET_ATTRIBUTE)))
-        node_tpl = self._find_node_template_containing_attribute()
-        if len(self.args) > 2:
-            # Currently we only check the first level
-            attrs_def = node_tpl.type_definition.get_attributes_def()
-            attr_def = attrs_def[self.attribute_name]
-            if attr_def.schema['type'] == "list":
-                if not isinstance(self.args[2], int):
-                    ExceptionCollector.appendException(
-                        ValueError(_('Illegal arguments for function "{0}". '
-                                     'Third argument must be a positive'
-                                     ' integer') .format(GET_ATTRIBUTE)))
-            elif attr_def.schema['type'] != "map":
-                ExceptionCollector.appendException(
-                    ValueError(_('Illegal arguments for function "{0}". '
-                                 'Expected arguments: "node-template-name", '
-                                 '"attribute-name"').format(GET_ATTRIBUTE)))
+                             'arguments: "node-template-name", "req-or-cap"'
+                             '(optional), "property name"'
+                             ).format(GET_ATTRIBUTE)))
+            return
+        elif len(self.args) == 2:
+            self._find_node_template_containing_attribute()
+        else:
+            node_tpl = self._find_node_template(self.args[0])
+            if node_tpl is None:
+                return
+            index = 2
+            attrs = node_tpl.type_definition.get_attributes_def()
+            found = [attrs[self.args[1]]] if self.args[1] in attrs else []
+            if found:
+                attr = found[0]
+            else:
+                index = 3
+                # then check the req or caps
+                attr = self._find_req_or_cap_attribute(self.args[1],
+                                                       self.args[2])
+                if not attr:
+                    return
+
+            value_type = attr.schema['type']
+            if len(self.args) > index:
+                for elem in self.args[index:]:
+                    if value_type == "list":
+                        if not isinstance(elem, int):
+                            ExceptionCollector.appendException(
+                                ValueError(_('Illegal arguments for function'
+                                             ' "{0}". "{1}" Expected positive'
+                                             ' integer argument'
+                                             ).format(GET_ATTRIBUTE, elem)))
+                        value_type = attr.schema['entry_schema']['type']
+                    elif value_type == "map":
+                        value_type = attr.schema['entry_schema']['type']
+                    elif value_type in Schema.PROPERTY_TYPES:
+                        ExceptionCollector.appendException(
+                            ValueError(_('Illegal arguments for function'
+                                         ' "{0}". Unexpected attribute/'
+                                         'index value "{1}"'
+                                         ).format(GET_ATTRIBUTE, elem)))
+                        return
+                    else:  # It is a complex type
+                        data_type = DataType(value_type)
+                        props = data_type.get_all_properties()
+                        found = [props[elem]] if elem in props else []
+                        if found:
+                            prop = found[0]
+                            value_type = prop.schema['type']
+                        else:
+                            ExceptionCollector.appendException(
+                                KeyError(_('Illegal arguments for function'
+                                           ' "{0}". Attribute name "{1}" not'
+                                           ' found in "{2}"'
+                                           ).format(GET_ATTRIBUTE,
+                                                    elem,
+                                                    value_type)))
 
     def result(self):
         return self
@@ -162,28 +209,13 @@ class GetAttribute(Function):
         """
         return self._find_node_template_containing_attribute()
 
+    # Attributes can be explicitly created as part of the type definition
+    # or a property name can be implicitly used as an attribute name
     def _find_node_template_containing_attribute(self):
-        if self.node_template_name == HOST:
-            # Currently this is the only way to tell whether the function
-            # is used within the outputs section of the TOSCA template.
-            if isinstance(self.context, list):
-                ExceptionCollector.appendException(
-                    ValueError(_(
-                        '"get_attribute: [ HOST, ... ]" is not allowed in '
-                        '"outputs" section of the TOSCA template.')))
-                return
-            node_tpl = self._find_host_containing_attribute()
-            if not node_tpl:
-                ExceptionCollector.appendException(
-                    ValueError(_(
-                        '"get_attribute: [ HOST, ... ]" was used in node '
-                        'template "{0}" but "{1}" was not found in '
-                        'the relationship chain.').format(self.context.name,
-                                                          HOSTED_ON)))
-        else:
-            node_tpl = self._find_node_template(self.args[0])
+        node_tpl = self._find_node_template(self.args[0])
         if node_tpl and \
-            not self._attribute_exists_in_type(node_tpl.type_definition):
+                not self._attribute_exists_in_type(node_tpl.type_definition) \
+                and self.attribute_name not in node_tpl.get_properties():
             ExceptionCollector.appendException(
                 KeyError(_('Attribute "%(att)s" was not found in node '
                            'template "%(ntpl)s".') %
@@ -206,14 +238,33 @@ class GetAttribute(Function):
                     target_node = self._find_node_template(target_name)
                     target_type = target_node.type_definition
                     for capability in target_type.get_capabilities_objects():
-                        if capability.type in \
-                            hosted_on_rel['valid_target_types']:
+                        if capability.inherits_from(
+                                hosted_on_rel['valid_target_types']):
                             if self._attribute_exists_in_type(target_type):
                                 return target_node
                             return self._find_host_containing_attribute(
                                 target_name)
 
     def _find_node_template(self, node_template_name):
+        if node_template_name == HOST:
+            # Currently this is the only way to tell whether the function
+            # is used within the outputs section of the TOSCA template.
+            if isinstance(self.context, list):
+                ExceptionCollector.appendException(
+                    ValueError(_(
+                        '"get_attribute: [ HOST, ... ]" is not allowed in '
+                        '"outputs" section of the TOSCA template.')))
+                return
+            node_tpl = self._find_host_containing_attribute()
+            if not node_tpl:
+                ExceptionCollector.appendException(
+                    ValueError(_(
+                        '"get_attribute: [ HOST, ... ]" was used in node '
+                        'template "{0}" but "{1}" was not found in '
+                        'the relationship chain.').format(self.context.name,
+                                                          HOSTED_ON)))
+                return
+            return node_tpl
         if node_template_name == TARGET:
             if not isinstance(self.context.type_definition, RelationshipType):
                 ExceptionCollector.appendException(
@@ -240,6 +291,51 @@ class GetAttribute(Function):
                 'Node template "{0}" was not found.'
                 ).format(node_template_name)))
 
+    def _find_req_or_cap_attribute(self, req_or_cap, attr_name):
+        node_tpl = self._find_node_template(self.args[0])
+        # Find attribute in node template's requirements
+        for r in node_tpl.requirements:
+            for req, node_name in r.items():
+                if req == req_or_cap:
+                    node_template = self._find_node_template(node_name)
+                    return self._get_capability_attribute(
+                        node_template,
+                        req,
+                        attr_name)
+        # If requirement was not found, look in node template's capabilities
+        return self._get_capability_attribute(node_tpl,
+                                              req_or_cap,
+                                              attr_name)
+
+    def _get_capability_attribute(self,
+                                  node_template,
+                                  capability_name,
+                                  attr_name):
+        """Gets a node template capability attribute."""
+        caps = node_template.get_capabilities()
+        if caps and capability_name in caps.keys():
+            cap = caps[capability_name]
+            attribute = None
+            attrs = cap.definition.get_attributes_def()
+            if attrs and attr_name in attrs.keys():
+                attribute = attrs[attr_name]
+            if not attribute:
+                ExceptionCollector.appendException(
+                    KeyError(_('Attribute "%(attr)s" was not found in '
+                               'capability "%(cap)s" of node template '
+                               '"%(ntpl1)s" referenced from node template '
+                               '"%(ntpl2)s".') % {'attr': attr_name,
+                                                  'cap': capability_name,
+                                                  'ntpl1': node_template.name,
+                                                  'ntpl2': self.context.name}))
+            return attribute
+        msg = _('Requirement/Capability "{0}" referenced from node template '
+                '"{1}" was not found in node template "{2}".').format(
+                    capability_name,
+                    self.context.name,
+                    node_template.name)
+        ExceptionCollector.appendException(KeyError(msg))
+
     @property
     def node_template_name(self):
         return self.args[0]
@@ -318,6 +414,8 @@ class GetProperty(Function):
 
     def _find_req_or_cap_property(self, req_or_cap, property_name):
         node_tpl = self._find_node_template(self.args[0])
+        if node_tpl is None:
+            return None
         # Find property in node template's requirements
         for r in node_tpl.requirements:
             for req, node_name in r.items():
@@ -335,7 +433,8 @@ class GetProperty(Function):
     def _get_capability_property(self,
                                  node_template,
                                  capability_name,
-                                 property_name):
+                                 property_name,
+                                 throw_errors=True):
         """Gets a node template capability property."""
         caps = node_template.get_capabilities()
         if caps and capability_name in caps.keys():
@@ -344,7 +443,7 @@ class GetProperty(Function):
             props = cap.get_properties()
             if props and property_name in props.keys():
                 property = props[property_name].value
-            if not property:
+            if property is None and throw_errors:
                 ExceptionCollector.appendException(
                     KeyError(_('Property "%(prop)s" was not found in '
                                'capability "%(cap)s" of node template '
@@ -354,12 +453,15 @@ class GetProperty(Function):
                                                   'ntpl1': node_template.name,
                                                   'ntpl2': self.context.name}))
             return property
-        msg = _('Requirement/Capability "{0}" referenced from node template '
-                '"{1}" was not found in node template "{2}".').format(
-                    capability_name,
-                    self.context.name,
-                    node_template.name)
-        ExceptionCollector.appendException(KeyError(msg))
+        if throw_errors:
+            msg = _('Requirement/Capability "{0}" referenced from '
+                    'node template "{1}" was not found in node template'
+                    ' "{2}".').format(capability_name,
+                                      self.context.name,
+                                      node_template.name)
+            ExceptionCollector.appendException(KeyError(msg))
+        else:
+            return None
 
     def _find_property(self, property_name):
         node_tpl = self._find_node_template(self.args[0])
@@ -381,7 +483,18 @@ class GetProperty(Function):
             return self.context
         # enable the HOST value in the function
         if node_template_name == HOST:
-            return self._find_host_containing_property()
+            node = self._find_host_containing_property()
+            if node is None:
+                ExceptionCollector.appendException(
+                    KeyError(_(
+                        "Property '{0}' not found in capability/requirement"
+                        " '{1}' referenced from node template {2}").
+                        format(self.args[2],
+                               self.args[1],
+                               self.context.name)))
+                return None
+            else:
+                return node
         if node_template_name == TARGET:
             if not isinstance(self.context.type_definition, RelationshipType):
                 ExceptionCollector.appendException(
@@ -404,7 +517,9 @@ class GetProperty(Function):
         ExceptionCollector.appendException(
             KeyError(_(
                 'Node template "{0}" was not found.'
-                ).format(node_template_name)))
+                ' referenced from node template {1}'
+                ).format(node_template_name,
+                         self.context.name)))
 
     def _get_index_value(self, value, index):
         if isinstance(value, list):
@@ -461,9 +576,19 @@ class GetProperty(Function):
                 target_node = self._find_node_template(target_name)
                 target_type = target_node.type_definition
                 for capability in target_type.get_capabilities_objects():
-                    if capability.type in hosted_on_rel['valid_target_types']:
+                    if capability.inherits_from(
+                            hosted_on_rel['valid_target_types']):
                         if self._property_exists_in_type(target_type):
                             return target_node
+                        # If requirement was not found, look in node
+                        # template's capabilities
+                        if (len(self.args) > 2 and
+                                self._get_capability_property(target_node,
+                                                              self.args[1],
+                                                              self.args[2],
+                                                              False)
+                                is not None):
+                            return target_node
                         return self._find_host_containing_property(
                             target_name)
         return None
@@ -523,6 +648,88 @@ class GetProperty(Function):
         return None
 
 
+class GetOperationOutput(Function):
+    def validate(self):
+        if len(self.args) == 4:
+            self._find_node_template(self.args[0])
+            interface_name = self._find_interface_name(self.args[1])
+            self._find_operation_name(interface_name, self.args[2])
+        else:
+            ExceptionCollector.appendException(
+                ValueError(_('Illegal arguments for function "{0}". Expected '
+                             'arguments: "template_name","interface_name",'
+                             '"operation_name","output_variable_name"'
+                             ).format(GET_OPERATION_OUTPUT)))
+            return
+
+    def _find_interface_name(self, interface_name):
+        if interface_name in toscaparser.elements.interfaces.SECTIONS:
+            return interface_name
+        else:
+            ExceptionCollector.appendException(
+                ValueError(_('Enter a valid interface name'
+                             ).format(GET_OPERATION_OUTPUT)))
+            return
+
+    def _find_operation_name(self, interface_name, operation_name):
+        if(interface_name == 'Configure' or
+           interface_name == 'tosca.interfaces.node.relationship.Configure'):
+            if(operation_name in
+               StatefulEntityType.
+               interfaces_relationship_configure_operations):
+                return operation_name
+            else:
+                ExceptionCollector.appendException(
+                    ValueError(_('Enter an operation of Configure interface'
+                                 ).format(GET_OPERATION_OUTPUT)))
+                return
+        elif(interface_name == 'Standard' or
+             interface_name == 'tosca.interfaces.node.lifecycle.Standard'):
+            if(operation_name in
+               StatefulEntityType.interfaces_node_lifecycle_operations):
+                return operation_name
+            else:
+                ExceptionCollector.appendException(
+                    ValueError(_('Enter an operation of Standard interface'
+                                 ).format(GET_OPERATION_OUTPUT)))
+                return
+        else:
+            ExceptionCollector.appendException(
+                ValueError(_('Enter a valid operation name'
+                             ).format(GET_OPERATION_OUTPUT)))
+            return
+
+    def _find_node_template(self, node_template_name):
+        if node_template_name == TARGET:
+            if not isinstance(self.context.type_definition, RelationshipType):
+                ExceptionCollector.appendException(
+                    KeyError(_('"TARGET" keyword can only be used in context'
+                               ' to "Relationships" target node')))
+                return
+            return self.context.target
+        if node_template_name == SOURCE:
+            if not isinstance(self.context.type_definition, RelationshipType):
+                ExceptionCollector.appendException(
+                    KeyError(_('"SOURCE" keyword can only be used in context'
+                               ' to "Relationships" source node')))
+                return
+            return self.context.source
+        name = self.context.name \
+            if node_template_name == SELF and \
+            not isinstance(self.context, list) \
+            else node_template_name
+        for node_template in self.tosca_tpl.nodetemplates:
+            if node_template.name == name:
+                return node_template
+        ExceptionCollector.appendException(
+            KeyError(_(
+                'Node template "{0}" was not found.'
+                ).format(node_template_name)))
+
+    def result(self):
+        return self
+
+
 class Concat(Function):
     """Validate the function and provide an instance of the function
 
@@ -551,11 +758,59 @@ class Concat(Function):
     def result(self):
         return self
 
+
+class Token(Function):
+    """Validate the function and provide an instance of the function
+
+    The token function is used within a TOSCA service template on a string to
+    parse out (tokenize) substrings separated by one or more token characters
+    within a larger string.
+
+
+    Arguments:
+
+    * The composite string that contains one or more substrings separated by
+      token characters.
+    * The string that contains one or more token characters that separate
+      substrings within the composite string.
+    * The integer indicates the index of the substring to return from the
+      composite string.  Note that the first substring is denoted by using
+      the '0' (zero) integer value.
+
+    Example:
+
+     [ get_attribute: [ my_server, data_endpoint, ip_address ], ':', 1 ]
+
+    """
+
+    def validate(self):
+        if len(self.args) < 3:
+            ExceptionCollector.appendException(
+                ValueError(_('Invalid arguments for function "{0}". Expected '
+                             'at least three arguments.').format(TOKEN)))
+        else:
+            if not isinstance(self.args[1], str) or len(self.args[1]) != 1:
+                ExceptionCollector.appendException(
+                    ValueError(_('Invalid arguments for function "{0}". '
+                                 'Expected single char value as second '
+                                 'argument.').format(TOKEN)))
+
+            if not isinstance(self.args[2], int):
+                ExceptionCollector.appendException(
+                    ValueError(_('Invalid arguments for function "{0}". '
+                                 'Expected integer value as third '
+                                 'argument.').format(TOKEN)))
+
+    def result(self):
+        return self
+
 function_mappings = {
     GET_PROPERTY: GetProperty,
     GET_INPUT: GetInput,
     GET_ATTRIBUTE: GetAttribute,
-    CONCAT: Concat
+    GET_OPERATION_OUTPUT: GetOperationOutput,
+    CONCAT: Concat,
+    TOKEN: Token
 }
 
 
@@ -591,11 +846,12 @@ def get_function(tosca_tpl, node_template, raw_function):
      parsing was unsuccessful.
     """
     if is_function(raw_function):
-        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]
-            if not isinstance(func_args, list):
-                func_args = [func_args]
-            return func(tosca_tpl, node_template, func_name, func_args)
+        if isinstance(raw_function, dict):
+            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]
+                if not isinstance(func_args, list):
+                    func_args = [func_args]
+                return func(tosca_tpl, node_template, func_name, func_args)
     return raw_function