Test: Implement setup --cord mode for cord-tester.
This would make the cord-tester listen for ONOS restart requests on the cord.
The restart is implemented using docker-compose when running the test agent on the ONOS compute node.
This is required because the tests restart ONOS with different configurations and the cord-tester agent
restarts bind the config volume to the xos/onos container before restarting ONOS.

Also implement fetching the device id properly when running tester under OLT configuration.
One can also override with OLT_DEVICE_ID env in the test container when multiple devices are connected to ONOS.

This is used by the subscriber test to override the pmc-olt driver for the device id when running
single-channel N subscriber tests.

Change-Id: I1fa27dd21ccacec35f38030443ad298b59718f4b
diff --git a/src/test/setup/cord-test.py b/src/test/setup/cord-test.py
index 5dabe1c..ac81fa7 100755
--- a/src/test/setup/cord-test.py
+++ b/src/test/setup/cord-test.py
@@ -363,6 +363,15 @@
 
     onos_ip = None
     radius_ip = None
+    onos_cord_loc = args.onos_cord
+    if onos_cord_loc:
+        if onos_cord_loc.find(os.path.sep) < 0:
+            onos_cord_loc = os.path.join(os.getenv('HOME'), onos_cord_loc)
+        if not os.access(onos_cord_loc, os.F_OK):
+            print('ONOS cord config location %s is not accessible' %onos_cord_loc)
+            sys.exit(1)
+        #Disable test container provisioning on the ONOS compute node
+        args.dont_provision = True
 
     ##If onos/radius was already started
     if args.test_controller:
@@ -373,6 +382,14 @@
         else:
             radius_ip = None
 
+    onos_cord = None
+    if onos_cord_loc:
+        if not args.test_controller:
+            ##Unexpected case. Specify the external controller ip when running on cord node
+            print('Specify ONOS ip using \"-e\" option when running the cord-tester on cord node')
+            sys.exit(1)
+        onos_cord = OnosCord(onos_cord_loc)
+
     #don't spawn onos if the user had started it externally
     onos_cnt['image'] = args.onos.split(':')[0]
     if args.onos.find(':') >= 0:
@@ -385,7 +402,6 @@
     print('Onos IP %s' %onos_ip)
     print('Installing ONOS cord apps')
     Onos.install_cord_apps(onos_ip = onos_ip)
-
     print('Installing cord tester ONOS app %s' %onos_app_file)
     OnosCtrl.install_app(args.app, onos_ip = onos_ip)
 
@@ -435,7 +451,8 @@
         print('Test container %s started and provisioned to run tests using nosetests' %(test_cnt.name))
 
     #Finally start the test server and daemonize
-    cord_test_server_start(daemonize = True, cord_test_host = ip, cord_test_port = port)
+    cord_test_server_start(daemonize = True, cord_test_host = ip, cord_test_port = port,
+                           onos_cord = onos_cord)
 
 def cleanupTests(args):
     test_container = '{}:latest'.format(CordTester.IMAGE)
@@ -499,6 +516,8 @@
     parser_setup.add_argument('-d', '--dont-provision', action='store_true', help='Dont start test container.')
     parser_setup.add_argument('-p', '--olt', action='store_true', help='Use OLT config')
     parser_setup.add_argument('-s', '--start-switch', action='store_true', help='Start OVS when running under OLT config')
+    parser_setup.add_argument('-c', '--onos-cord', default='', type=str,
+                              help='Specify cord location for ONOS cord when running on podd')
     parser_setup.set_defaults(func=setupCordTester)
 
     parser_list = subparser.add_parser('list', help='List test cases')
diff --git a/src/test/subscriberMultiTable/subscriberMultiTableTest.py b/src/test/subscriberMultiTable/subscriberMultiTableTest.py
index a6eec2a..75c9392 100644
--- a/src/test/subscriberMultiTable/subscriberMultiTableTest.py
+++ b/src/test/subscriberMultiTable/subscriberMultiTableTest.py
@@ -179,15 +179,6 @@
       olt_conf_file = os.path.join(test_path, '..', 'setup/olt_config_multitable.json')
       cpqd_path = os.path.join(test_path, '..', 'setup')
       ovs_path = cpqd_path
-      device_id = 'of:' + get_mac('ovsbr0')
-      device_dict = { "devices" : {
-                  "{}".format(device_id) : {
-                        "basic" : {
-                              "driver" : "pmc-olt"
-                              }
-                        }
-                  },
-              }
       test_services = ('IGMP', 'TRAFFIC')
       num_joins = 0
       num_subscribers = 0
@@ -195,10 +186,39 @@
       recv_timeout = False
 
       @classmethod
+      def load_device_id(cls):
+            '''If running under olt, we get the first switch connected to onos'''
+            olt = os.getenv('OLT_CONFIG', None)
+            if olt:
+                  devices = OnosCtrl.get_devices()
+                  if devices:
+                        dids = map(lambda d: d['id'], devices)
+                        if len(dids) == 1:
+                              did = dids[0]
+                        else:
+                              ###If we have more than 1, then check for env before using first one
+                              did = os.getenv('OLT_DEVICE_ID', dids[0])
+            else:
+                  did = 'of:' + get_mac('ovsbr0')
+
+            #Set the default config
+            cls.device_id = did
+            cls.device_dict = { "devices" : {
+                        "{}".format(did) : {
+                              "basic" : {
+                                    "driver" : "pmc-olt"
+                                    }
+                              }
+                        },
+                  }
+            return did
+
+      @classmethod
       def setUpClass(cls):
           '''Load the OLT config and activate relevant apps'''
+          did = cls.load_device_id()
           network_cfg = { "devices" : {
-                  "{}".format(cls.device_id) : {
+                  "{}".format(did) : {
                         "basic" : {
                               "driver" : "pmc-olt"
                               }
diff --git a/src/test/utils/CordContainer.py b/src/test/utils/CordContainer.py
index e5f7526..da99655 100644
--- a/src/test/utils/CordContainer.py
+++ b/src/test/utils/CordContainer.py
@@ -16,6 +16,7 @@
 import os,time
 import io
 import json
+import yaml
 from pyroute2 import IPRoute
 from itertools import chain
 from nsenter import Namespace
@@ -51,7 +52,10 @@
         self.name = name
         self.image = image
         self.tag = tag
-        self.image_name = image + ':' + tag
+        if tag:
+            self.image_name = image + ':' + tag
+        else:
+            self.image_name = image
         self.id = None
         self.command = command
         self.quagga_config = quagga_config
@@ -191,6 +195,68 @@
         mem = min(mem, 16)
         return str(mem) + 'G'
 
+class OnosCord(Container):
+    """Use this when running the cord tester agent on the onos compute node"""
+    onos_cord_dir = os.path.join(os.getenv('HOME'), 'cord-tester-cord')
+    onos_config_dir_guest = '/root/onos/config'
+    onos_config_dir = os.path.join(onos_cord_dir, 'config')
+    docker_yaml = os.path.join(onos_cord_dir, 'docker-compose.yml')
+
+    def __init__(self, conf):
+        self.cord_conf_dir = conf
+        if os.access(self.cord_conf_dir, os.F_OK) and not os.access(self.onos_cord_dir, os.F_OK):
+            os.mkdir(self.onos_cord_dir)
+            os.mkdir(self.onos_config_dir)
+            ##copy the config file from cord-tester-config
+            cmd = 'cp {}/* {}'.format(self.cord_conf_dir, self.onos_cord_dir)
+            os.system(cmd)
+
+        ##update the docker yaml with the config volume
+        with open(self.docker_yaml, 'r') as f:
+            yaml_config = yaml.load(f)
+            image = yaml_config['services'].keys()[0]
+            name = 'cordtestercord_{}_1'.format(image)
+            volumes = yaml_config['services'][image]['volumes']
+            config_volumes = filter(lambda e: e.find(self.onos_config_dir_guest) >= 0, volumes)
+            if not config_volumes:
+                config_volume = '{}:{}'.format(self.onos_config_dir, self.onos_config_dir_guest)
+                volumes.append(config_volume)
+                docker_yaml_changed = '{}-changed'.format(self.docker_yaml)
+                with open(docker_yaml_changed, 'w') as wf:
+                    yaml.dump(yaml_config, wf)
+
+                os.rename(docker_yaml_changed, self.docker_yaml)
+            self.volumes = volumes
+
+        super(OnosCord, self).__init__(name, image, tag = '')
+        cord_conf_dir_basename = os.path.basename(self.cord_conf_dir.replace('-', ''))
+        self.xos_onos_name = '{}_{}_1'.format(cord_conf_dir_basename, image)
+        ##Create an container instance of xos onos
+        self.xos_onos = Container(self.xos_onos_name, image, tag = '')
+
+    def start(self, restart = False, network_cfg = None):
+        if restart is True:
+            if self.exists():
+                ##Kill the existing instance
+                print('Killing container %s' %self.name)
+                self.kill()
+            if self.xos_onos.exists():
+                print('Killing container %s' %self.xos_onos.name)
+                self.xos_onos.kill()
+
+        if network_cfg is not None:
+            json_data = json.dumps(network_cfg, indent=4)
+            with open('{}/network-cfg.json'.format(self.onos_config_dir), 'w') as f:
+                f.write(json_data)
+
+        #start the container using docker-compose
+        cmd = 'cd {} && docker-compose up -d'.format(self.onos_cord_dir)
+        os.system(cmd)
+
+    def build_image(self):
+        build_cmd = 'cd {} && docker-compose build'.format(self.onos_cord_dir)
+        os.system(build_cmd)
+
 class Onos(Container):
 
     quagga_config = ( { 'bridge' : 'quagga-br', 'ip': '10.10.0.4', 'mask' : 16 }, )
@@ -240,6 +306,8 @@
             print('Waiting %d seconds for ONOS to boot' %(boot_delay))
             time.sleep(boot_delay)
 
+        self.install_cord_apps()
+
     @classmethod
     def install_cord_apps(cls, onos_ip = None):
         for app, version in cls.onos_cord_apps:
diff --git a/src/test/utils/CordTestServer.py b/src/test/utils/CordTestServer.py
index e263939..a77b977 100644
--- a/src/test/utils/CordTestServer.py
+++ b/src/test/utils/CordTestServer.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-from CordContainer import Container, Onos, Quagga, Radius, reinitContainerClients
+from CordContainer import Container, Onos, OnosCord, Quagga, Radius, reinitContainerClients
 from nose.tools import nottest
 from SimpleXMLRPCServer import SimpleXMLRPCServer
 import daemon
@@ -28,6 +28,7 @@
 
 CORD_TEST_HOST = '172.17.0.1'
 CORD_TEST_PORT = 25000
+g_onos_cord = None
 
 class QuaggaStopWrapper(Container):
     def __init__(self, name = Quagga.NAME, image = Quagga.IMAGE, tag = 'latest'):
@@ -38,14 +39,20 @@
 class CordTestServer(object):
 
     def __restart_onos(self, config = None):
-        onos_config = '{}/network-cfg.json'.format(Onos.host_config_dir)
+        if g_onos_cord:
+            onos_config = '{}/network-cfg.json'.format(OnosCord.onos_config_dir)
+        else:
+            onos_config = '{}/network-cfg.json'.format(Onos.host_config_dir)
         if config is None:
             try:
                 os.unlink(onos_config)
             except:
                 pass
         print('Restarting ONOS')
-        Onos(restart = True, network_cfg = config)
+        if g_onos_cord:
+            g_onos_cord.start(restart = True, network_cfg = config)
+        else:
+            Onos(restart = True, network_cfg = config)
         return 'DONE'
 
     def restart_onos(self, kwargs):
@@ -90,9 +97,12 @@
         return 'DONE'
 
 @nottest
-def cord_test_server_start(daemonize = True, cord_test_host = CORD_TEST_HOST, cord_test_port = CORD_TEST_PORT):
+def cord_test_server_start(daemonize = True, cord_test_host = CORD_TEST_HOST,
+                           cord_test_port = CORD_TEST_PORT, onos_cord = None):
+    global g_onos_cord
     server = SimpleXMLRPCServer( (cord_test_host, cord_test_port) )
     server.register_instance(CordTestServer())
+    g_onos_cord = onos_cord
     if daemonize is True:
         d = daemon.DaemonContext(files_preserve = [server],
                                  detach_process = True)