Test: Support for ONOS cluster mode startup with --onos-instances option to cord-test.py setup.
Changes to igmpTest to do a rover join test with support for parallel test runs across multiple containers.

Change-Id: If9c01df4c2ff5f2f6961eef9ff6fa865abb29b53
diff --git a/src/test/cli/onosclidriver.py b/src/test/cli/onosclidriver.py
index 2556892..cc3d8c0 100644
--- a/src/test/cli/onosclidriver.py
+++ b/src/test/cli/onosclidriver.py
@@ -49,6 +49,7 @@
         self.home = None
         self.handle = None
         self.controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost'
+        self.controller = self.controller.split(',')[0]
         super( CLI, self ).__init__()
         if connect == True:
             self.connect_cli()
diff --git a/src/test/igmp/__init__.py b/src/test/igmp/__init__.py
index 2864a46..374665c 100644
--- a/src/test/igmp/__init__.py
+++ b/src/test/igmp/__init__.py
@@ -17,4 +17,8 @@
 ##add the python path to lookup the utils
 working_dir = os.path.dirname(os.path.realpath(sys.argv[-1]))
 utils_dir = os.path.join(working_dir, '../utils')
+fsm_dir = os.path.join(working_dir, '../fsm')
+subscriber_dir = os.path.join(working_dir, '../subscriber')
 __path__.append(utils_dir)
+__path__.append(fsm_dir)
+__path__.append(subscriber_dir)
diff --git a/src/test/igmp/igmpTest.py b/src/test/igmp/igmpTest.py
index 88dae5f..b42af1a 100644
--- a/src/test/igmp/igmpTest.py
+++ b/src/test/igmp/igmpTest.py
@@ -77,10 +77,14 @@
     max_packets = 100
     app = 'org.opencord.igmp'
     olt_conf_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../setup/olt_config.json')
+    ROVER_TEST_TIMEOUT = 300 #3600*86
+    ROVER_TIMEOUT = (ROVER_TEST_TIMEOUT - 100)
+    ROVER_JOIN_TIMEOUT = 60
 
     @classmethod
     def setUpClass(cls):
           cls.olt = OltConfig(olt_conf_file = cls.olt_conf_file)
+          cls.port_map, _ = cls.olt.olt_port_map()
           OnosCtrl.cord_olt_config(cls.olt.olt_device_data())
 
     @classmethod
@@ -185,6 +189,17 @@
             ip_range.append(".".join(map(str, temp)))
         return random.choice(ip_range)
 
+    def get_igmp_intf(self):
+        inst = os.getenv('TEST_INSTANCE', None)
+        if not inst:
+            return 'veth0'
+        inst = int(inst) + 1
+        if inst >= self.port_map['uplink']:
+            inst += 1
+        if self.port_map.has_key(inst):
+              return self.port_map[inst]
+        return 'veth0'
+
     def igmp_verify_join(self, igmpStateList):
         sendState, recvState = igmpStateList
         ## check if the send is received for the groups
@@ -237,8 +252,10 @@
         igmpState.update(p.dst, rx = 1, t = recv_time - send_time)
         return 0
 
-    def send_igmp_join(self, groups, src_list = ['1.2.3.4'], record_type=IGMP_V3_GR_TYPE_INCLUDE,ip_pkt = None, iface = 'veth0', delay = 1):
-        #self.onos_ssm_table_load(groups, src_list)
+    def send_igmp_join(self, groups, src_list = ['1.2.3.4'], record_type=IGMP_V3_GR_TYPE_INCLUDE,
+                       ip_pkt = None, iface = 'veth0', ssm_load = False, delay = 1):
+        if ssm_load is True:
+              self.onos_ssm_table_load(groups, src_list)
         igmp = IGMPv3(type = IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30,
                       gaddr=self.IP_DST)
         for g in groups:
@@ -510,7 +527,7 @@
         reactor.callLater(0, igmp_Ngroup_join_latency)
         return df
 
-    def test_igmp_join_rover(self):
+    def test_igmp_join_rover_all(self):
           s = (224 << 24) | 1
           #e = (225 << 24) | (255 << 16) | (255 << 16) | 255
           e = (224 << 24) | 10
@@ -519,6 +536,44 @@
                       ip = '%d.%d.%d.%d'%((i>>24)&0xff, (i>>16)&0xff, (i>>8)&0xff, i&0xff)
                 self.send_igmp_join([ip], delay = 0)
 
+    @deferred(timeout=ROVER_TEST_TIMEOUT)
+    def test_igmp_join_rover(self):
+          df = defer.Deferred()
+          iface = self.get_igmp_intf()
+          self.df = df
+          self.count = 0
+          self.timeout = 0
+          self.complete = False
+          def igmp_join_timer():
+                self.timeout += self.ROVER_JOIN_TIMEOUT
+                log.info('IGMP joins sent: %d' %self.count)
+                did = OnosCtrl.get_device_id()
+                if self.timeout >= self.ROVER_TIMEOUT:
+                      self.complete = True
+                reactor.callLater(self.ROVER_JOIN_TIMEOUT, igmp_join_timer)
+
+          reactor.callLater(self.ROVER_JOIN_TIMEOUT, igmp_join_timer)
+          self.start_channel = s = (224 << 24) | 1
+          self.end_channel = e = (224 << 24) | 200 #(225 << 24) | (255 << 16) | (255 << 16) | 255
+          self.current_channel = self.start_channel
+          def igmp_join_rover(self):
+                #e = (224 << 24) | 10
+                chan = self.current_channel
+                self.current_channel += 1
+                if self.current_channel >= self.end_channel:
+                      chan = self.current_channel = self.start_channel
+                if chan&0xff:
+                      ip = '%d.%d.%d.%d'%((chan>>24)&0xff, (chan>>16)&0xff, (chan>>8)&0xff, chan&0xff)
+                      self.send_igmp_join([ip], delay = 0, ssm_load = False, iface = iface)
+                      self.count += 1
+                if self.complete == True:
+                      log.info('%d IGMP joins sent in %d seconds over %s' %(self.count, self.timeout, iface))
+                      self.df.callback(0)
+                else:
+                      reactor.callLater(0, igmp_join_rover, self)
+          reactor.callLater(0, igmp_join_rover, self)
+          return df
+
     @deferred(timeout=IGMP_QUERY_TIMEOUT + 10)
     def test_igmp_query(self):
         groups = ['224.0.0.1'] ##igmp query group
diff --git a/src/test/setup/cord-test.py b/src/test/setup/cord-test.py
index 6b995ec..df0e26d 100755
--- a/src/test/setup/cord-test.py
+++ b/src/test/setup/cord-test.py
@@ -107,7 +107,7 @@
                 return
             CordTester.switch_on_olt = True
             ovs_cmd += ' {0}'.format(self.ctlr_ip)
-            print('Starting OVS on the host')
+            print('Starting OVS on the host with controller: %s' %(self.ctlr_ip))
         else:
             print('Starting OVS on test container %s' %self.name)
         self.execute_switch(ovs_cmd)
@@ -457,6 +457,9 @@
         args.keep = True
     port_num = 0
     num_tests = len(tests_parallel)
+    if num_tests > 0 and num_tests < args.num_containers:
+        tests_parallel *= args.num_containers/num_tests
+        num_tests = len(tests_parallel)
     tests_per_container = max(1, num_tests/args.num_containers)
     test_slice_start = 0
     test_slice_end = test_slice_start + tests_per_container
@@ -591,11 +594,26 @@
     Onos.IMAGE = onos_cnt['image']
     Onos.PREFIX = args.prefix
     Onos.TAG = onos_cnt['tag']
+    cluster_mode = True if args.onos_instances > 1 else False
+    onos = None
     if onos_ip is None:
-        onos = Onos(image = Onos.IMAGE, tag = Onos.TAG, boot_delay = 60)
+        onos = Onos(image = Onos.IMAGE, tag = Onos.TAG, boot_delay = 60, cluster = cluster_mode)
         onos_ip = onos.ip()
 
-    print('Onos IP %s' %onos_ip)
+    num_onos_instances = args.onos_instances
+    onos_ips = [ onos_ip ]
+    if num_onos_instances > 1 and onos is not None:
+        onos_instances = []
+        onos_instances.append(onos)
+        for i in range(1, num_onos_instances):
+            name = '{}-{}'.format(Onos.NAME, i+1)
+            onos = Onos(name = name, image = Onos.IMAGE, tag = Onos.TAG, boot_delay = 60, cluster = cluster_mode)
+            onos_instances.append(onos)
+            onos_ips.append(onos.ipaddr)
+        Onos.setup_cluster(onos_instances)
+
+    ctlr_addr = ','.join(onos_ips)
+    print('Onos IP %s' %ctlr_addr)
     if use_manifest or args.test_controller:
         print('Installing ONOS cord apps')
         try:
@@ -604,7 +622,8 @@
 
     print('Installing cord tester ONOS app %s' %onos_app_file)
     try:
-        OnosCtrl.install_app(args.app, onos_ip = onos_ip)
+        for ip in onos_ips:
+            OnosCtrl.install_app(args.app, onos_ip = ip)
     except: pass
 
     ##Start Radius container if not started
@@ -634,7 +653,7 @@
 
     #provision the test container
     if not args.dont_provision:
-        test_cnt_env = { 'ONOS_CONTROLLER_IP' : onos_ip,
+        test_cnt_env = { 'ONOS_CONTROLLER_IP' : ctlr_addr,
                          'ONOS_AAA_IP' : radius_ip,
                          'QUAGGA_IP': ip,
                          'CORD_TEST_HOST' : ip,
@@ -652,7 +671,7 @@
             test_cnt_env['OLT_CONFIG'] = olt_conf_test_loc
 
         test_cnt = CordTester((),
-                              ctlr_ip = onos_ip,
+                              ctlr_ip = ctlr_addr,
                               image = nose_cnt['image'],
                               prefix = Container.IMAGE_PREFIX,
                               tag = nose_cnt['tag'],
@@ -677,6 +696,7 @@
     return 0
 
 def cleanupTests(args):
+    image_name = args.onos
     prefix = args.prefix
     if prefix:
         prefix += '/'
@@ -686,6 +706,13 @@
     if args.olt:
         print('Cleaning up test container OLT configuration')
         CordTester.cleanup_intfs()
+
+    onos_list = [ c['Names'][0][1:] for c in Container.dckr.containers() if c['Image'] == image_name ]
+    if len(onos_list) > 1:
+        for onos in onos_list:
+            Container.dckr.kill(onos)
+            Container.dckr.remove_container(onos, force=True)
+
     return 0
 
 def listTests(args):
@@ -866,6 +893,8 @@
     parser_setup.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
     parser_setup.add_argument('-i', '--identity-file', default=identity_file_default,
                               type=str, help='ssh identity file to access compute nodes from test container')
+    parser_setup.add_argument('-n', '--onos-instances', default=1, type=int,
+                            help='Specify number of test onos instances to spawn')
     parser_setup.set_defaults(func=setupCordTester)
 
     parser_list = subparser.add_parser('list', help='List test cases')
@@ -893,6 +922,8 @@
     parser_cleanup = subparser.add_parser('cleanup', help='Cleanup test containers')
     parser_cleanup.add_argument('-p', '--prefix', default='', type=str, help='Provide container image prefix')
     parser_cleanup.add_argument('-l', '--olt', action = 'store_true', help = 'Cleanup OLT config')
+    parser_cleanup.add_argument('-o', '--onos', default=onos_image_default, type=str,
+                                help='ONOS container image to cleanup')
     parser_cleanup.set_defaults(func=cleanupTests)
 
     c = Client(**(kwargs_from_env()))
diff --git a/src/test/setup/of-bridge-local.sh b/src/test/setup/of-bridge-local.sh
index 5bd3ba7..6abfae6 100755
--- a/src/test/setup/of-bridge-local.sh
+++ b/src/test/setup/of-bridge-local.sh
@@ -5,7 +5,7 @@
   bridge="ovsbr0"
 fi
 if [ x"$controller" = "x" ]; then
-  controller=$ONOS_CONTROLLER_IP
+  controller="$ONOS_CONTROLLER_IP"
 fi
 pkill -9 ofdatapath
 pkill -9 ofprotocol
@@ -13,9 +13,11 @@
 echo "Configuring ovs bridge $bridge"
 ovs-vsctl del-br $bridge
 ovs-vsctl add-br $bridge
-my_ip=`ifconfig docker0 | grep "inet addr" | tr -s ' ' | cut -d":" -f2 |cut -d" " -f1`
-#ovs-vsctl set-controller $bridge ptcp:6653:$my_ip tcp:$controller:6633
-ovs-vsctl set-controller $bridge tcp:$controller:6653
+ctlr=""
+for ip in `echo $controller | tr ',' '\n'`; do
+  ctlr="$ctlr tcp:$ip:6653"
+done
+ovs-vsctl set-controller $bridge $ctlr
 ovs-vsctl set controller $bridge max_backoff=1000
 ovs-vsctl set bridge $bridge protocols=OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13
 ovs-vsctl show
diff --git a/src/test/setup/of-bridge.sh b/src/test/setup/of-bridge.sh
index 2957160..609756a 100755
--- a/src/test/setup/of-bridge.sh
+++ b/src/test/setup/of-bridge.sh
@@ -5,7 +5,7 @@
   bridge="ovsbr0"
 fi
 if [ x"$controller" = "x" ]; then
-  controller=$ONOS_CONTROLLER_IP
+  controller="$ONOS_CONTROLLER_IP"
 fi
 pkill -9 ofdatapath
 pkill -9 ofprotocol
@@ -28,9 +28,11 @@
 for i in $(seq 1 2 $ports); do
   ovs-vsctl add-port $bridge veth$i
 done
-my_ip=`ifconfig eth0 | grep "inet addr" | tr -s ' ' | cut -d":" -f2 |cut -d" " -f1`
-#ovs-vsctl set-controller $bridge ptcp:6653:$my_ip tcp:$controller:6633
-ovs-vsctl set-controller $bridge tcp:$controller:6653
+ctlr=""
+for ip in `echo $controller | tr ',' '\n'`; do
+  ctlr="$ctlr tcp:$ip:6653"
+done
+ovs-vsctl set-controller $bridge $ctlr
 ovs-vsctl set controller $bridge max_backoff=1000
 ovs-vsctl set bridge $bridge protocols=OpenFlow10,OpenFlow11,OpenFlow12,OpenFlow13
 ovs-vsctl show
diff --git a/src/test/setup/onos-form-cluster b/src/test/setup/onos-form-cluster
new file mode 100755
index 0000000..578a443
--- /dev/null
+++ b/src/test/setup/onos-form-cluster
@@ -0,0 +1,41 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+# Forms ONOS cluster using REST API of each separate instance.
+# -----------------------------------------------------------------------------
+
+[ $# -lt 2 ] && echo "usage: $(basename $0) ip1 ip2..." && exit 1
+
+# Scan arguments for user/password or other options...
+while getopts u:p: o; do
+    case "$o" in
+        u) user=$OPTARG;;
+        p) password=$OPTARG;;
+    esac
+done
+ONOS_WEB_USER=${ONOS_WEB_USER:-onos} # ONOS WEB User defaults to 'onos'
+ONOS_WEB_PASS=${ONOS_WEB_PASS:-rocks} # ONOS WEB Password defaults to 'rocks'
+user=${user:-$ONOS_WEB_USER}
+password=${password:-$ONOS_WEB_PASS}
+let OPC=$OPTIND-1
+shift $OPC
+
+ip=$1
+shift
+nodes=$*
+
+ipPrefix=${ip%.*}
+
+aux=/tmp/${ipPrefix}.cluster.json
+trap "rm -f $aux" EXIT
+
+echo "{ \"nodes\": [ { \"ip\": \"$ip\" }" > $aux
+for node in $nodes; do
+    echo ", { \"ip\": \"$node\" }" >> $aux
+done
+echo "], \"ipPrefix\": \"$ipPrefix.*\" }" >> $aux
+
+for node in $ip $nodes; do
+    echo "Forming cluster on $node..."
+    curl --user $user:$password -X POST \
+        http://$node:8181/onos/v1/cluster/configuration -d @$aux
+done
diff --git a/src/test/setup/radius-config/freeradius/clients.conf b/src/test/setup/radius-config/freeradius/clients.conf
index 1786abe..be2369e 100644
--- a/src/test/setup/radius-config/freeradius/clients.conf
+++ b/src/test/setup/radius-config/freeradius/clients.conf
@@ -1374,6 +1374,27 @@
 	ipv6addr = ::
 	secret = radius_password
 }
+client 0.0.0.0/0{
+	secret = radius_password
+}
+client ipv6{
+	ipv6addr = ::
+	secret = radius_password
+}
+client 0.0.0.0/0{
+	secret = radius_password
+}
+client ipv6{
+	ipv6addr = ::
+	secret = radius_password
+}
+client 0.0.0.0/0{
+	secret = radius_password
+}
+client ipv6{
+	ipv6addr = ::
+	secret = radius_password
+}
 client localhost {
 	#  Allowed values are:
 	#	dotted quad (1.2.3.4)
diff --git a/src/test/setup/radius-config/freeradius/mods-available/sql b/src/test/setup/radius-config/freeradius/mods-available/sql
index 4273142..93e85c1 100644
--- a/src/test/setup/radius-config/freeradius/mods-available/sql
+++ b/src/test/setup/radius-config/freeradius/mods-available/sql
@@ -813,6 +813,18 @@
 	sqlite {
 		filename = "/opt/db/radius.sqlite3"
 	}
+ 
+	sqlite {
+		filename = "/opt/db/radius.sqlite3"
+	}
+ 
+	sqlite {
+		filename = "/opt/db/radius.sqlite3"
+	}
+ 
+	sqlite {
+		filename = "/opt/db/radius.sqlite3"
+	}
 
 #
 #	Several drivers accept specific options, to set them, a
diff --git a/src/test/utils/ACL.py b/src/test/utils/ACL.py
index 1492868..ccae5d5 100644
--- a/src/test/utils/ACL.py
+++ b/src/test/utils/ACL.py
@@ -26,7 +26,7 @@
 class ACLTest:
 
     auth = ('karaf', 'karaf')
-    controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost'   
+    controller = OnosCtrl.get_controller()
     add_acl_rule_url = 'http://%s:8181/onos/v1/acl/rules' %(controller)
     remove_acl_rule_url = 'http://%s:8181/onos/v1/acl/rules/%s' %(controller, id)
     clear_all_acl_rule_url = 'http://%s:8181/onos/v1/acl/rules' %(controller)
@@ -51,7 +51,7 @@
     def adding_acl_rule(self, ipv4Prefix, srcIp, dstIp, ipProto ='null', dstTpPort='null', action= 'include'):
         '''This function is generating ACL json file and post to ONOS for creating a ACL rule'''
         if ipv4Prefix is 'v4':
-           acl_dict = {} 
+           acl_dict = {}
            if srcIp and dstIp and action:
               acl_dict['srcIp'] = '{}'.format(srcIp)
               acl_dict['dstIp'] = '{}'.format(dstIp)
@@ -78,7 +78,7 @@
            remove_acl_rule_url = 'http://%s:8181/onos/v1/acl/rules/%s' %(cls.controller, id)
         resp = requests.delete(remove_acl_rule_url, auth = cls.auth)
         return resp.ok, resp.status_code
-  
+
     def generate_onos_interface_config(self,iface_num = 4, iface_name = 'null',iface_count = 1,iface_ip = '198.162.10.1'):
         '''This function is generate interface config data in json format and post to ONOS for creating it '''
         ''' To add interfaces on ONOS to test acl with trffic'''
diff --git a/src/test/utils/CordContainer.py b/src/test/utils/CordContainer.py
index 63443b9..6f8e037 100644
--- a/src/test/utils/CordContainer.py
+++ b/src/test/utils/CordContainer.py
@@ -117,10 +117,18 @@
         return self.image_name in [ctn['RepoTags'][0] if ctn['RepoTags'] else '' for ctn in self.dckr.images()]
 
     def ip(self):
-        cnt_list = filter(lambda c: c['Image'] == self.image_name, self.dckr.containers())
+        cnt_list = filter(lambda c: c['Names'][0] == '/{}'.format(self.name), self.dckr.containers())
+        #if not cnt_list:
+        #    cnt_list = filter(lambda c: c['Image'] == self.image_name, self.dckr.containers())
         cnt_settings = cnt_list.pop()
         return cnt_settings['NetworkSettings']['Networks']['bridge']['IPAddress']
 
+    @classmethod
+    def ips(cls, image_name):
+        cnt_list = filter(lambda c: c['Image'] == image_name, cls.dckr.containers())
+        ips = [ cnt['NetworkSettings']['Networks']['bridge']['IPAddress'] for cnt in cnt_list ]
+        return ips
+
     def kill(self, remove = True):
         self.dckr.kill(self.name)
         self.dckr.remove_container(self.name, force=True)
@@ -287,8 +295,12 @@
     host_config_dir = os.path.join(setup_dir, 'onos-config')
     guest_config_dir = '/root/onos/config'
     onos_gen_partitions = os.path.join(setup_dir, 'onos-gen-partitions')
+    onos_form_cluster = os.path.join(setup_dir, 'onos-form-cluster')
     cord_apps_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'apps')
     host_guest_map = ( (host_config_dir, guest_config_dir), )
+    cluster_cfg = os.path.join(host_config_dir, 'cluster.json')
+    cluster_mode = False
+    cluster_instances = []
     NAME = 'cord-onos'
     ##the ip of ONOS in default cluster.json in setup/onos-config
     CLUSTER_CFG_IP = '172.17.0.2'
@@ -297,14 +309,26 @@
     PREFIX = ''
 
     @classmethod
-    def onos_generate_cluster_cfg(cls, ip):
+    def generate_cluster_cfg(cls, ip):
+        if type(ip) in [ list, tuple ]:
+            ips = ' '.join(ip)
+        else:
+            ips = ip
         try:
-            cmd = '{} {}/cluster.json {}'.format(cls.onos_gen_partitions, cls.host_config_dir, ip)
+            cmd = '{} {} {}'.format(cls.onos_gen_partitions, cls.cluster_cfg, ips)
+            os.system(cmd)
+        except: pass
+
+    @classmethod
+    def form_cluster(cls, ips):
+        nodes = ' '.join(ips)
+        try:
+            cmd = '{} {}'.format(cls.onos_form_cluster, nodes)
             os.system(cmd)
         except: pass
 
     def __init__(self, name = NAME, image = 'onosproject/onos', prefix = '', tag = 'latest',
-                 boot_delay = 60, restart = False, network_cfg = None):
+                 boot_delay = 60, restart = False, network_cfg = None, cluster = False):
         if restart is True:
             ##Find the right image to restart
             running_image = filter(lambda c: c['Names'][0] == '/{}'.format(name), self.dckr.containers())
@@ -316,40 +340,145 @@
                 except: pass
 
         super(Onos, self).__init__(name, image, prefix = prefix, tag = tag, quagga_config = self.quagga_config)
+        self.boot_delay = boot_delay
+        if cluster is True:
+            self.ports = []
+            if os.access(self.cluster_cfg, os.F_OK):
+                try:
+                    os.unlink(self.cluster_cfg)
+                except: pass
+
+        self.host_config = self.create_host_config(port_list = self.ports,
+                                                   host_guest_map = self.host_guest_map)
+        self.volumes = []
+        for _,g in self.host_guest_map:
+            self.volumes.append(g)
+
         if restart is True and self.exists():
             self.kill()
+
         if not self.exists():
             self.remove_container(name, force=True)
-            host_config = self.create_host_config(port_list = self.ports,
-                                                  host_guest_map = self.host_guest_map)
-            volumes = []
-            for _,g in self.host_guest_map:
-                volumes.append(g)
             if network_cfg is not None:
                 json_data = json.dumps(network_cfg, indent=4)
                 with open('{}/network-cfg.json'.format(self.host_config_dir), 'w') as f:
                     f.write(json_data)
             print('Starting ONOS container %s' %self.name)
             self.start(ports = self.ports, environment = self.env,
-                       host_config = host_config, volumes = volumes, tty = True)
+                       host_config = self.host_config, volumes = self.volumes, tty = True)
             if not restart:
                 ##wait a bit before fetching IP to regenerate cluster cfg
                 time.sleep(5)
                 ip = self.ip()
                 ##Just a quick hack/check to ensure we don't regenerate in the common case.
                 ##As ONOS is usually the first test container that is started
-                if ip != self.CLUSTER_CFG_IP:
-                    print('Regenerating ONOS cluster cfg for ip %s' %ip)
-                    self.onos_generate_cluster_cfg(ip)
-                    self.kill()
-                    self.remove_container(self.name, force=True)
-                    print('Restarting ONOS container %s' %self.name)
-                    self.start(ports = self.ports, environment = self.env,
-                               host_config = host_config, volumes = volumes, tty = True)
+                if cluster is False:
+                    if ip != self.CLUSTER_CFG_IP or not os.access(self.cluster_cfg, os.F_OK):
+                        print('Regenerating ONOS cluster cfg for ip %s' %ip)
+                        self.generate_cluster_cfg(ip)
+                        self.kill()
+                        self.remove_container(self.name, force=True)
+                        print('Restarting ONOS container %s' %self.name)
+                        self.start(ports = self.ports, environment = self.env,
+                                   host_config = self.host_config, volumes = self.volumes, tty = True)
             print('Waiting %d seconds for ONOS to boot' %(boot_delay))
             time.sleep(boot_delay)
+        self.ipaddr = self.ip()
+        if cluster is False:
+            self.install_cord_apps(self.ipaddr)
 
-        self.install_cord_apps()
+    @classmethod
+    def setup_cluster_deprecated(cls, onos_instances, image_name = None):
+        if not onos_instances or len(onos_instances) < 2:
+            return
+        ips = []
+        if image_name is not None:
+            ips = Container.ips(image_name)
+        else:
+            for onos in onos_instances:
+                ips.append(onos.ipaddr)
+        Onos.cluster_instances = onos_instances
+        Onos.cluster_mode = True
+        ##regenerate the cluster json with the 3 instance ips before restarting them back
+        print('Generating cluster cfg for ONOS instances with ips %s' %ips)
+        Onos.generate_cluster_cfg(ips)
+        for onos in onos_instances:
+            onos.kill()
+            onos.remove_container(onos.name, force=True)
+            print('Restarting ONOS container %s for forming cluster' %onos.name)
+            onos.start(ports = onos.ports, environment = onos.env,
+                       host_config = onos.host_config, volumes = onos.volumes, tty = True)
+            print('Waiting %d seconds for ONOS %s to boot' %(onos.boot_delay, onos.name))
+            time.sleep(onos.boot_delay)
+            onos.ipaddr = onos.ip()
+            onos.install_cord_apps(onos.ipaddr)
+
+    @classmethod
+    def setup_cluster(cls, onos_instances, image_name = None):
+        if not onos_instances or len(onos_instances) < 2:
+            return
+        ips = []
+        if image_name is not None:
+            ips = Container.ips(image_name)
+        else:
+            for onos in onos_instances:
+                ips.append(onos.ipaddr)
+        Onos.cluster_instances = onos_instances
+        Onos.cluster_mode = True
+        ##regenerate the cluster json with the 3 instance ips before restarting them back
+        print('Forming cluster for ONOS instances with ips %s' %ips)
+        Onos.form_cluster(ips)
+        ##wait for the cluster to be formed
+        print('Waiting for the cluster to be formed')
+        time.sleep(60)
+        for onos in onos_instances:
+            onos.install_cord_apps(onos.ipaddr)
+
+    @classmethod
+    def restart_cluster(cls, network_cfg = None):
+        if cls.cluster_mode is False:
+            return
+        if not cls.cluster_instances:
+            return
+
+        if network_cfg is not None:
+            json_data = json.dumps(network_cfg, indent=4)
+            with open('{}/network-cfg.json'.format(cls.host_config_dir), 'w') as f:
+                f.write(json_data)
+
+        for onos in cls.cluster_instances:
+            if onos.exists():
+                onos.kill()
+            onos.remove_container(onos.name, force=True)
+            print('Restarting ONOS container %s' %onos.name)
+            onos.start(ports = onos.ports, environment = onos.env,
+                       host_config = onos.host_config, volumes = onos.volumes, tty = True)
+            print('Waiting %d seconds for ONOS %s to boot' %(onos.boot_delay, onos.name))
+            time.sleep(onos.boot_delay)
+            onos.ipaddr = onos.ip()
+
+        ##form the cluster
+        cls.setup_cluster(cls.cluster_instances)
+
+    @classmethod
+    def cluster_ips(cls):
+        if cls.cluster_mode is False:
+            return []
+        if not cls.cluster_instances:
+            return []
+        ips = [ onos.ipaddr for onos in cls.cluster_instances ]
+        return ips
+
+    @classmethod
+    def cleanup_cluster(cls):
+        if cls.cluster_mode is False:
+            return
+        if not cls.cluster_instances:
+            return
+        for onos in cls.cluster_instances:
+            if onos.exists():
+                onos.kill()
+            onos.remove_container(onos.name, force=True)
 
     @classmethod
     def install_cord_apps(cls, onos_ip = None):
diff --git a/src/test/utils/CordTestServer.py b/src/test/utils/CordTestServer.py
index 47b84e3..7b60620 100644
--- a/src/test/utils/CordTestServer.py
+++ b/src/test/utils/CordTestServer.py
@@ -53,7 +53,10 @@
         if self.onos_cord:
             self.onos_cord.start(restart = True, network_cfg = config)
         else:
-            Onos(restart = True, network_cfg = config, image = Onos.IMAGE, tag = Onos.TAG)
+            if Onos.cluster_mode is True:
+                Onos.restart_cluster(network_cfg = config)
+            else:
+                Onos(restart = True, network_cfg = config, image = Onos.IMAGE, tag = Onos.TAG)
         return 'DONE'
 
     def restart_onos(self, kwargs):
diff --git a/src/test/utils/EapTLS.py b/src/test/utils/EapTLS.py
index 8f31509..f4e7346 100644
--- a/src/test/utils/EapTLS.py
+++ b/src/test/utils/EapTLS.py
@@ -33,7 +33,11 @@
 log.setLevel('INFO')
 
 def bytes_to_num(data):
-    return int(data.encode('hex'), 16)
+    try:
+        return int(data.encode('hex'), 16)
+    except:
+        print('Exception')
+        return -1
 
 class TLSAuthTest(EapolPacket, CordTester):
 
@@ -239,10 +243,14 @@
             else:
                 self.pkt_update(self.pkt_last, tls_data)
                 self.pending_bytes -= len(tls_data)
-
+        print('Offset: %d, pkt : %d, pending %d\n' %(offset, len(pkt), self.pending_bytes))
         while self.pending_bytes == 0 and offset < len(pkt):
             tls_data = r[offset:]
+            hexdump(tls_data)
             self.pending_bytes = bytes_to_num(tls_data[3:5])
+            if self.pending_bytes < 0:
+                self.pending_bytes = 0
+                return
             if tls_data[0] == self.HANDSHAKE:
                 pkt_type = tls_data[5]
                 if pkt_type in [ self.CERTIFICATE_REQUEST ]:
diff --git a/src/test/utils/OnosCtrl.py b/src/test/utils/OnosCtrl.py
index 6083d85..29f9080 100644
--- a/src/test/utils/OnosCtrl.py
+++ b/src/test/utils/OnosCtrl.py
@@ -33,10 +33,15 @@
         sep = ':'
     return '0'*pad + sep.join(['%02x' %ord(char) for char in info[18:24]])
 
+def get_controller():
+    controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost'
+    controller = controller.split(',')[0]
+    return controller
+
 class OnosCtrl:
 
     auth = ('karaf', 'karaf')
-    controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost'
+    controller = get_controller()
     cfg_url = 'http://%s:8181/onos/v1/network/configuration/' %(controller)
     maven_repo = 'http://central.maven.org/maven2/org/onosproject'
     applications_url = 'http://%s:8181/onos/v1/applications' %(controller)
diff --git a/src/test/utils/OnosFlowCtrl.py b/src/test/utils/OnosFlowCtrl.py
index 295a7ab..5b2da52 100644
--- a/src/test/utils/OnosFlowCtrl.py
+++ b/src/test/utils/OnosFlowCtrl.py
@@ -23,7 +23,7 @@
 class OnosFlowCtrl:
 
     auth = ('karaf', 'karaf')
-    controller = os.getenv('ONOS_CONTROLLER_IP') or 'localhost'
+    controller = OnosCtrl.get_controller()
     cfg_url = 'http://%s:8181/onos/v1/flows/' %(controller)
 
     def __init__( self,