Complete overhaul of the implementation of ONOS cord instance wrapper for cord-tester.
It now patches the onos-cord compose instance in place and restores it back on cleanup.
A sample manifest-cord.json illustrates the usage if one wants to wrap the existing onos-cord running instance
in CiaB head-node (called prod)
This is required because of the manner in which the onos-cord instance networks are created automatically matching the docker networks that already exist for the onos instances.
The service profile spec allows one to synchronize the configuration/apps for the onos-cord running instance.

Change-Id: Icd066d8b953eccb7dcd7330775b548d36a7fb33e
diff --git a/src/test/utils/CordContainer.py b/src/test/utils/CordContainer.py
index 1a7656b..5f1ea2c 100644
--- a/src/test/utils/CordContainer.py
+++ b/src/test/utils/CordContainer.py
@@ -25,7 +25,7 @@
 from nsenter import Namespace
 from docker import Client
 from docker import utils as dockerutils
-from shutil import rmtree
+import shutil
 from OnosCtrl import OnosCtrl
 from OnosLog import OnosLog
 from threadPool import ThreadPool
@@ -292,132 +292,122 @@
 
 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')
-    onos_cfg_save_loc = os.path.join(onos_cord_dir, 'network-cfg.json.saved')
 
-    def __init__(self, onos_ip, conf, boot_delay = 60):
+    def __init__(self, onos_ip, conf, service_profile, synchronizer, start = True, boot_delay = 60):
+        if not os.access(conf, os.F_OK):
+            raise Exception('ONOS cord configuration location %s is invalid' %conf)
+        if not os.access(service_profile, os.F_OK):
+            raise Exception('ONOS cord service profile location is not accessible' %service_profile)
         self.onos_ip = onos_ip
-        self.cord_conf_dir = conf
+        self.onos_cord_dir = conf
         self.boot_delay = boot_delay
-        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)
+        self.synchronizer = synchronizer
+        self.service_profile = service_profile
+        self.docker_yaml = os.path.join(conf, 'docker-compose.yml')
+        self.docker_yaml_saved = os.path.join(conf, 'docker-compose.yml.saved')
+        self.onos_config_dir = os.path.join(conf, 'config')
+        self.onos_cfg_save_loc = os.path.join(conf, 'network-cfg.json.saved')
+        instance_active = False
+        #if we have a wrapper onos instance already active, back out
+        if os.access(self.onos_config_dir, os.F_OK) or os.access(self.docker_yaml_saved, os.F_OK):
+            instance_active = True
+        else:
+            if start is True:
+                os.mkdir(self.onos_config_dir)
+                shutil.copy(self.docker_yaml, self.docker_yaml_saved)
 
+        self.start_wrapper = instance_active is False and start is True
         ##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)
+            cord_conf_dir_basename = os.path.basename(self.onos_cord_dir.replace('-', ''))
+            xos_onos_name = '{}_{}_1'.format(cord_conf_dir_basename, 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)
+                if self.start_wrapper:
+                    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 = '')
-        #fetch the current config of onos cord instance
-        try:
-            self.last_cfg = OnosCtrl.get_config(controller = onos_ip)
-        except:
-            self.last_cfg = None
+        self.xos_onos = Container(xos_onos_name, image, tag = '')
+        self.last_cfg = None
+        if self.start_wrapper:
+            #fetch the current config of onos cord instance and save it
+            try:
+                self.last_cfg = OnosCtrl.get_config(controller = onos_ip)
+                json_data = json.dumps(self.last_cfg, indent=4)
+                with open(self.onos_cfg_save_loc, 'w') as f:
+                    f.write(json_data)
+            except:
+                pass
+            #start the container back with the shared onos config volume
+            self.start()
 
     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():
-                if self.last_cfg is not None:
-                    #save the current network config of onos cord instance
-                    json_data = json.dumps(self.last_cfg, indent=4)
-                    with open(self.onos_cfg_save_loc, 'w') as f:
-                        f.write(json_data)
-                print('Killing container %s' %self.xos_onos.name)
-                self.xos_onos.kill()
-
-        if network_cfg is not None:
+        if network_cfg:
             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)
-        #Delay to make sure ONOS fully boots
+        if restart is False:
+            #stop and start and synchronize the services before installing tester cord apps
+            cmds = [ 'cd {} && docker-compose down'.format(self.onos_cord_dir),
+                     'cd {} && docker-compose up -d'.format(self.onos_cord_dir),
+                     'sleep 45',
+                     'cd {} && make {}'.format(self.service_profile, self.synchronizer)
+            ]
+            for cmd in cmds:
+                try:
+                    print(cmd)
+                    os.system(cmd)
+                except:pass
+            Onos.install_cord_apps(onos_ip = self.onos_ip)
+        else:
+            cmd = 'cd {} && docker-compose restart'.format(self.onos_cord_dir)
+            try:
+                os.system(cmd)
+            except: pass
+        print('Waiting %d seconds for ONOS instance to start' %self.boot_delay)
         time.sleep(self.boot_delay)
-        Onos.install_cord_apps(onos_ip = self.onos_ip)
 
     def build_image(self):
         build_cmd = 'cd {} && docker-compose build'.format(self.onos_cord_dir)
         os.system(build_cmd)
 
-    @classmethod
-    def cleanup(cls):
-        if not os.access(cls.onos_cord_dir, os.F_OK):
+    def restore(self, force = False):
+        restore = self.start_wrapper is True or force is True
+        if not restore:
             return
-        cmd = 'cd {} && docker-compose down'.format(cls.onos_cord_dir)
-        try:
-            os.system(cmd)
-        except: pass
-
-        print('Cleaning up the ONOS cord wrapper directory at %s' %(cls.onos_cord_dir))
-        try:
-            os.system('rm -rf {}'.format(cls.onos_cord_dir))
-        except:
-            pass
-
-    @classmethod
-    def restore_onos_cord(cls, onos_cord, onos_ip):
-        #bring down the onos cord wrapper container
-        #if there is no saved config, there is nothing to restore as it was never restarted
-        if not os.access(cls.onos_cfg_save_loc, os.F_OK):
-            return False
-        if not onos_cord or not os.access(onos_cord, os.F_OK):
-            return False
-
-        print('Stopping the existing ONOS cord wrapper instance at %s' %(cls.onos_cord_dir))
-        cmd = 'cd {} && docker-compose down'.format(cls.onos_cord_dir)
-        try:
-            os.system(cmd)
-        except:pass
-
-        print('Starting the ONOS cord instance at %s' %(onos_cord))
-        #bring back up the onos cord container
-        cmd = 'cd {} && docker-compose up -d'.format(onos_cord)
-        try:
-            os.system(cmd)
-            time.sleep(30)
-        except:
-            pass
-
-        #now restore back the old config
-        print('Restoring back the saved ONOS cord config at %s for ONOS cord instance' %(cls.onos_cfg_save_loc))
-        with open(cls.onos_cfg_save_loc, 'r') as f:
-            config = json.load(f)
+        #restore the config files back. The synchronizer restore should bring the last config back
+        cmds = ['cd {} && docker-compose down'.format(self.onos_cord_dir),
+                'rm -rf {}'.format(self.onos_config_dir),
+                'mv {} {}'.format(self.docker_yaml_saved, self.docker_yaml),
+                'cd {} && docker-compose up -d'.format(self.onos_cord_dir),
+                'sleep 45',
+                'cd {} && make {}'.format(self.service_profile, self.synchronizer)
+        ]
+        for cmd in cmds:
             try:
-                OnosCtrl.config(config, controller = onos_ip)
-                os.unlink(cls.onos_cfg_save_loc)
+                print(cmd)
+                os.system(cmd)
             except: pass
 
-        cls.cleanup()
-        return True
+        #We may not have to restore the config but still it should match synchronizer last config
+        if os.access(self.onos_cfg_save_loc, os.F_OK):
+            with open(self.onos_cfg_save_loc, 'r') as f:
+                cfg = json.load(f)
+                try:
+                    OnosCtrl.config(cfg, controller = self.onos_ip)
+                    os.unlink(self.onos_cfg_save_loc)
+                except:
+                    pass
 
 class OnosCordStopWrapper(Container):
     onos_cord_dir = os.path.join(os.getenv('HOME'), 'cord-tester-cord')
@@ -508,7 +498,7 @@
     def remove_data_map(cls, host_volume, guest_volume_dir):
         host_volume_dir = os.path.join(cls.setup_dir, os.path.basename(host_volume))
         if os.path.exists(host_volume_dir):
-            rmtree(host_volume_dir)
+            shutil.rmtree(host_volume_dir)
 
     def remove_data_volume(self):
         if self.data_map is not None: