CORD-1341: Extend IR with reverse link information

Change-Id: I582eda399f2e43e06dcd9766ecb2d5a6b1f2524b
diff --git a/xos/genx/targets/dot.xtarget b/xos/genx/targets/dot.xtarget
index d0e0369..a51ccb8 100644
--- a/xos/genx/targets/dot.xtarget
+++ b/xos/genx/targets/dot.xtarget
@@ -1,6 +1,7 @@
+digraph {
 {% for m in proto.messages %}
   {%- for l in m.links %}
-  {{ l.src_port }} -> {{ l.dst_port }};
+  {{ m.name }} -> {{ l.peer }};
   {%- endfor %}
-}
 {% endfor %}
+}
diff --git a/xos/genx/tool/tests/rlinks_test.py b/xos/genx/tool/tests/rlinks_test.py
new file mode 100644
index 0000000..649fbe2
--- /dev/null
+++ b/xos/genx/tool/tests/rlinks_test.py
@@ -0,0 +1,45 @@
+from xproto_test_base import *
+
+class XProtoRlinkTests(XProtoTest):
+    def test_proto_generator(self):
+        xproto = \
+"""
+message VRouterPort (PlCoreBase){
+     optional string name = 1 [help_text = "port friendly name", max_length = 20, null = True, db_index = False, blank = True];
+     required string openflow_id = 2 [help_text = "port identifier in ONOS", max_length = 21, null = False, db_index = False, blank = False];
+     required manytoone vrouter_device->VRouterDevice:ports = 3 [db_index = True, null = False, blank = False];
+     required manytoone vrouter_service->VRouterService:device_ports = 4 [db_index = True, null = False, blank = False];
+}
+
+message VRouterService (Service) {
+     optional string rest_hostname = 1 [db_index = False, max_length = 255, null = True, content_type = "stripped", blank = True];
+     required int32 rest_port = 2 [default = 8181, null = False, db_index = False, blank = False];
+     required string rest_user = 3 [default = "onos", max_length = 255, content_type = "stripped", blank = False, null = False, db_index = False];
+     required string rest_pass = 4 [default = "rocks", max_length = 255, content_type = "stripped", blank = False, null = False, db_index = False];
+}
+
+message VRouterDevice (PlCoreBase){
+     optional string name = 1 [help_text = "device friendly name", max_length = 20, null = True, db_index = False, blank = True];
+     required string openflow_id = 2 [help_text = "device identifier in ONOS", max_length = 20, null = False, db_index = False, blank = False];
+     required string config_key = 3 [default = "basic", max_length = 32, blank = False, help_text = "configuration key", null = False, db_index = False];
+     required string driver = 4 [help_text = "driver type", max_length = 32, null = False, db_index = False, blank = False];
+     required manytoone vrouter_service->VRouterService:devices = 5 [db_index = True, null = False, blank = False];
+}
+"""
+	target = \
+"""
+{% for m in proto.messages %}
+   {% for r in m.rlinks %}
+       {{ r }}
+   {% endfor %}
+{% endfor %} 
+"""
+
+        self.generate(xproto = xproto, target = target)
+        self.assertIn("'src_port': 'device_ports'", self.get_output())
+        self.assertIn("'src_port': 'ports'", self.get_output())
+
+if __name__ == '__main__':
+    unittest.main()
+
+
diff --git a/xos/genx/tool/xos2jinja.py b/xos/genx/tool/xos2jinja.py
index d7004ad..2a20612 100644
--- a/xos/genx/tool/xos2jinja.py
+++ b/xos/genx/tool/xos2jinja.py
@@ -6,6 +6,7 @@
 import sys
 import jinja2
 import os
+import copy
 
 def count_messages(body):
     count = 0
@@ -21,6 +22,36 @@
             count+=1
     return count
 
+def compute_rlinks(messages):
+    rev_links = {}
+
+    link_opposite = {
+            'manytomany': 'manytomany',
+            'manytoone' : 'onetomany',
+            'onetoone'  : 'onetoone',
+            'onetomany' : 'manytoone'
+    }
+
+    for m in messages:
+        for l in m['links']:
+            rlink = copy.deepcopy(l)
+            rlink['_type'] = 'rlink' # An implicit link, not declared in the model
+            rlink['src_port'] = l['dst_port']
+            rlink['dst_port'] = l['src_port']
+            rlink['peer'] = m['name']
+            rlink['link_type'] = link_opposite[l['link_type']]
+
+            try:
+                rev_links[l['peer']].append(rlink)
+            except KeyError:
+                rev_links[l['peer']] = [rlink]
+
+    for m in messages:
+        try:
+            m['rlinks'] = rev_links[m['name']]
+        except KeyError:
+            pass
+
 class Stack(list):
     def push(self,x):
         self.append(x)
@@ -243,6 +274,7 @@
 
             messages.insert(0,m)
 
+        compute_rlinks(messages)
         self.messages = messages
         return True