From b480a1167858eea7c0212800fdaa38090463b522 Mon Sep 17 00:00:00 2001
From: Miguel Caballer <micafer1@upv.es>
Date: Wed, 9 Mar 2016 12:53:23 +0100
Subject: [PATCH] Add support for SOURCE and TARGET in get_property and
 get_attribute functions

Change-Id: I17181c8adb23f7e55c49c780d6bfaba9c26fc985
Related-Bug: 1554507
---
 toscaparser/functions.py                      | 33 +++++++++++++-
 toscaparser/nodetemplate.py                   | 11 +++--
 toscaparser/relationship_template.py          |  5 ++-
 ..._get_attribute_source_target_keywords.yaml | 30 +++++++++++++
 ...t_get_property_source_target_keywords.yaml | 35 +++++++++++++++
 toscaparser/tests/test_functions.py           | 43 +++++++++++++++++++
 6 files changed, 151 insertions(+), 6 deletions(-)
 create mode 100644 toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml
 create mode 100644 toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml

diff --git a/toscaparser/functions.py b/toscaparser/functions.py
index 8261b213..62b2fc9f 100644
--- a/toscaparser/functions.py
+++ b/toscaparser/functions.py
@@ -19,6 +19,7 @@ from toscaparser.common.exception import ExceptionCollector
 from toscaparser.common.exception import UnknownInputError
 from toscaparser.dataentity import DataEntity
 from toscaparser.elements.entity_type import EntityType
+from toscaparser.elements.relationshiptype import RelationshipType
 from toscaparser.utils.gettextutils import _
 
 
@@ -28,6 +29,8 @@ GET_INPUT = 'get_input'
 
 SELF = 'SELF'
 HOST = 'HOST'
+TARGET = 'TARGET'
+SOURCE = 'SOURCE'
 
 HOSTED_ON = 'tosca.relationships.HostedOn'
 
@@ -210,6 +213,20 @@ class GetAttribute(Function):
                                 target_name)
 
     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) \
@@ -236,7 +253,7 @@ class GetProperty(Function):
 
     Arguments:
 
-    * Node template name.
+    * Node template name | SELF | HOST | SOURCE | TARGET.
     * Requirement or capability name (optional).
     * Property name.
 
@@ -364,6 +381,20 @@ class GetProperty(Function):
         # enable the HOST value in the function
         if node_template_name == HOST:
             return self._find_host_containing_property()
+        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
         if not hasattr(self.tosca_tpl, 'nodetemplates'):
             return
         for node_template in self.tosca_tpl.nodetemplates:
diff --git a/toscaparser/nodetemplate.py b/toscaparser/nodetemplate.py
index 3bb93155..d969d51b 100644
--- a/toscaparser/nodetemplate.py
+++ b/toscaparser/nodetemplate.py
@@ -114,6 +114,8 @@ class NodeTemplate(EntityTemplate):
                             rtype = RelationshipType(tpl.type, None,
                                                      self.custom_def)
                             explicit_relation[rtype] = related_tpl
+                            tpl.target = related_tpl
+                            tpl.source = self
                             self.relationship_tpl.append(tpl)
                             found_relationship_tpl = True
                 # create relationship template object.
@@ -137,7 +139,8 @@ class NodeTemplate(EntityTemplate):
                         if rtype.type == relationship:
                             explicit_relation[rtype] = related_tpl
                             related_tpl._add_relationship_template(req,
-                                                                   rtype.type)
+                                                                   rtype.type,
+                                                                   self)
                         elif self.available_rel_types:
                             if relationship in self.available_rel_types.keys():
                                 rel_type_def = self.available_rel_types.\
@@ -151,13 +154,13 @@ class NodeTemplate(EntityTemplate):
                                         explicit_relation[rtype] = related_tpl
                                         related_tpl.\
                                             _add_relationship_template(
-                                                req, rtype.type)
+                                                req, rtype.type, self)
         return explicit_relation
 
-    def _add_relationship_template(self, requirement, rtype):
+    def _add_relationship_template(self, requirement, rtype, source):
         req = requirement.copy()
         req['type'] = rtype
-        tpl = RelationshipTemplate(req, rtype, self.custom_def)
+        tpl = RelationshipTemplate(req, rtype, self.custom_def, self, source)
         self.relationship_tpl.append(tpl)
 
     def get_relationship_template(self):
diff --git a/toscaparser/relationship_template.py b/toscaparser/relationship_template.py
index d405b9c7..b9656d41 100644
--- a/toscaparser/relationship_template.py
+++ b/toscaparser/relationship_template.py
@@ -26,12 +26,15 @@ log = logging.getLogger('tosca')
 
 class RelationshipTemplate(EntityTemplate):
     '''Relationship template.'''
-    def __init__(self, relationship_template, name, custom_def=None):
+    def __init__(self, relationship_template, name, custom_def=None,
+                 target=None, source=None):
         super(RelationshipTemplate, self).__init__(name,
                                                    relationship_template,
                                                    'relationship_type',
                                                    custom_def)
         self.name = name.lower()
+        self.target = target
+        self.source = source
 
     def get_properties_objects(self):
         '''Return properties objects for this template.'''
diff --git a/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml b/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml
new file mode 100644
index 00000000..047387fe
--- /dev/null
+++ b/toscaparser/tests/data/functions/test_get_attribute_source_target_keywords.yaml
@@ -0,0 +1,30 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: >
+  TOSCA template for testing get_attribute with TARGET ans SOURCE keywords.
+
+topology_template:
+
+  node_templates:
+
+    mysql:
+      type: tosca.nodes.DBMS
+      properties:
+        root_password: rootpw
+        port: 3306
+      requirements:
+        - host:
+            node: db_server
+            relationship:
+              type: tosca.relationships.HostedOn
+              interfaces:
+                Configure:
+                  pre_configure_source:
+                    implementation: some_script.sh
+                    inputs:
+                      target_test: { get_attribute: [ TARGET, public_address ] }
+                      source_port: { get_attribute: [ SOURCE, tosca_name ] }
+
+    db_server:
+      type: tosca.nodes.Compute
+
diff --git a/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml b/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml
new file mode 100644
index 00000000..c4602577
--- /dev/null
+++ b/toscaparser/tests/data/functions/test_get_property_source_target_keywords.yaml
@@ -0,0 +1,35 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: >
+  TOSCA template for testing get_property with TARGET ans SOURCE keywords.
+
+imports:
+  - ../custom_types/compute_with_prop.yaml
+
+topology_template:
+
+  node_templates:
+
+    mysql:
+      type: tosca.nodes.DBMS
+      properties:
+        root_password: rootpw
+        port: 3306
+      requirements:
+        - host:
+            node: db_server
+            relationship:
+              type: tosca.relationships.HostedOn
+              interfaces:
+                Configure:
+                  pre_configure_source:
+                    implementation: some_script.sh
+                    inputs:
+                      target_test: { get_property: [ TARGET, test ] }
+                      source_port: { get_property: [ SOURCE, port ] }
+
+    db_server:
+      type: tosca.nodes.ComputeWithProp
+      properties:
+        test: 1
+
diff --git a/toscaparser/tests/test_functions.py b/toscaparser/tests/test_functions.py
index d5671c0d..7062df4a 100644
--- a/toscaparser/tests/test_functions.py
+++ b/toscaparser/tests/test_functions.py
@@ -157,6 +157,26 @@ class IntrinsicFunctionsTest(TestCase):
         self.assertTrue(isinstance(some_input, functions.GetProperty))
         self.assertEqual('someval', some_input.result())
 
+    def test_get_property_source_target_keywords(self):
+        tosca_tpl = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            "data/functions/test_get_property_source_target_keywords.yaml")
+        tosca = ToscaTemplate(tosca_tpl)
+
+        for node in tosca.nodetemplates:
+            for relationship, trgt in node.relationships.items():
+                rel_template = trgt.get_relationship_template()[0]
+                break
+
+        operation = self._get_operation(rel_template.interfaces,
+                                        'pre_configure_source')
+        target_test = operation.inputs['target_test']
+        self.assertTrue(isinstance(target_test, functions.GetProperty))
+        self.assertEqual(1, target_test.result())
+        source_port = operation.inputs['source_port']
+        self.assertTrue(isinstance(source_port, functions.GetProperty))
+        self.assertEqual(3306, source_port.result())
+
 
 class GetAttributeTest(TestCase):
 
@@ -166,6 +186,11 @@ class GetAttributeTest(TestCase):
             'data',
             filename))
 
+    def _get_operation(self, interfaces, operation):
+        return [
+            interface for interface in interfaces
+            if interface.name == operation][0]
+
     def test_get_attribute_in_outputs(self):
         tpl = self._load_template('tosca_single_instance_wordpress.yaml')
         website_url_output = [
@@ -256,3 +281,21 @@ class GetAttributeTest(TestCase):
             ValueError,
             _('Illegal arguments for function "get_attribute". '
               'Expected arguments: "node-template-name", "attribute-name"'))
+
+    def test_get_attribute_source_target_keywords(self):
+        tosca_tpl = os.path.join(
+            os.path.dirname(os.path.abspath(__file__)),
+            "data/functions/test_get_attribute_source_target_keywords.yaml")
+        tosca = ToscaTemplate(tosca_tpl)
+
+        for node in tosca.nodetemplates:
+            for relationship, trgt in node.relationships.items():
+                rel_template = trgt.get_relationship_template()[0]
+                break
+
+        operation = self._get_operation(rel_template.interfaces,
+                                        'pre_configure_source')
+        target_test = operation.inputs['target_test']
+        self.assertTrue(isinstance(target_test, functions.GetAttribute))
+        source_port = operation.inputs['source_port']
+        self.assertTrue(isinstance(source_port, functions.GetAttribute))