Test : Multiple scenarios for verification of Proxy ARP.

Change-Id: I58fd0d23d4ceaf9481f0651cee8a13d2d0236d8f
diff --git a/src/test/cli/onosclidriver.py b/src/test/cli/onosclidriver.py
index 252fc73..2556892 100644
--- a/src/test/cli/onosclidriver.py
+++ b/src/test/cli/onosclidriver.py
@@ -1,12 +1,12 @@
-# 
+#
 # Copyright 2016-present Ciena Corporation
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# 
+#
 # http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -4589,6 +4589,28 @@
             main.cleanup()
             main.exit()
 
+    def host_remove( self, hostid ):
+	try:
+	    cmdStr = "host-remove" + " " + hostid
+	    handle = self.sendline( cmdStr )
+	    assert "Command not found:" not in handle, handle
+	    return handle
+	except AssertionError:
+	    main.log.exception( "" )
+	    return None
+	except TypeError:
+	    main.log.exception( self.name + ": Object not as expected" )
+	    return None
+        except pexpect.EOF:
+	    main.log.error( self.name + ": EOF exception found" )
+	    main.log.error( self.name + ":    " + self.handle.before )
+	    main.cleanup()
+	    main.exit()
+	except Exception:
+	    main.log.exception( self.name + ": Uncaught exception!" )
+	    main.cleanup()
+	    main.exit()
+
 if __name__ == '__main__':
   onos_cli = OnosCliDriver(connect = False)
   name = 'onos_cli'
@@ -4607,4 +4629,4 @@
   flows_json = onos_cli.flows(state = "ADDED")
   print('Flows %s' %flows_json)
   onos_cli.disconnect()
-  
+
diff --git a/src/test/proxyarp/__init__.py b/src/test/proxyarp/__init__.py
new file mode 100644
index 0000000..c38f621
--- /dev/null
+++ b/src/test/proxyarp/__init__.py
@@ -0,0 +1,26 @@
+#
+# Copyright 2016-present Ciena Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os,sys
+##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')
+cli_dir = os.path.join(working_dir, '../cli')
+subscriber_dir = os.path.join(working_dir, '../subscriber')
+__path__.append(utils_dir)
+__path__.append(fsm_dir)
+__path__.append(cli_dir)
+__path__.append(subscriber_dir)
diff --git a/src/test/proxyarp/proxyarpTest.py b/src/test/proxyarp/proxyarpTest.py
new file mode 100644
index 0000000..a9083b9
--- /dev/null
+++ b/src/test/proxyarp/proxyarpTest.py
@@ -0,0 +1,439 @@
+
+# Copyright 2016-present Ciena Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import unittest
+from nose.tools import *
+from scapy.all import *
+from OnosCtrl import OnosCtrl
+from OltConfig import OltConfig
+from OnosFlowCtrl import OnosFlowCtrl, get_mac
+from onosclidriver import OnosCliDriver
+from CordContainer import Container, Onos, Quagga
+from CordTestServer import cord_test_onos_restart, cord_test_quagga_restart
+from portmaps import g_subscriber_port_map
+import threading
+from threading import current_thread
+import time
+import os
+import json
+log.setLevel('INFO')
+
+
+class proxyarp_exchange(unittest.TestCase):
+
+    apps = ('org.onosproject.vrouter','org.onosproject.proxyarp')
+    device_id = 'of:' + get_mac('ovsbr0')
+    device_dict = { "devices" : {
+                "{}".format(device_id) : {
+                    "basic" : {
+                        "driver" : "softrouter"
+                    }
+                }
+             },
+          }
+    test_path = os.path.dirname(os.path.realpath(__file__))
+    onos_config_path = os.path.join(test_path, '..', 'setup/onos-config')
+    GATEWAY = '192.168.10.50'
+    INGRESS_PORT = 1
+    EGRESS_PORT = 2
+    MAX_PORTS = 100
+    hosts_list = [ ('192.168.10.1', '00:00:00:00:00:01'), ('192.168.11.1', '00:00:00:00:02:01'), ]
+
+    @classmethod
+    def setUpClass(cls):
+        cls.olt = OltConfig()
+        cls.port_map = cls.olt.olt_port_map()
+        if not cls.port_map:
+            cls.port_map = g_subscriber_port_map
+        time.sleep(3)
+
+    @classmethod
+    def tearDownClass(cls):
+        '''Deactivate the vrouter apps'''
+        #cls.vrouter_host_unload()
+
+    def cliEnter(self):
+        retries = 0
+        while retries < 3:
+            self.cli = OnosCliDriver(connect = True)
+            if self.cli.handle:
+                break
+            else:
+                retries += 1
+                time.sleep(2)
+    def cliExit(self):
+        self.cli.disconnect()
+
+    @classmethod
+    def proxyarp_host_unload(cls):
+        index = 1
+        for host,_ in cls.hosts_list:
+            iface = cls.port_map[index]
+            index += 1
+            config_cmds = ('ifconfig {} 0'.format(iface), )
+            for cmd in config_cmds:
+		log.info('host unload command %s' % cmd)
+                os.system(cmd)
+
+    @classmethod
+    def interface_config_load(cls, interface_cfg = None):
+  	if type(interface_cfg) is tuple:
+            res = []
+            for v in interface_cfg:
+		if type(v) == list:
+		    pass
+		else:
+                    res += v.items()
+                    config = dict(res)
+        else:
+            config = interface_cfg
+        cfg = json.dumps(config)
+        with open('{}/network-cfg.json'.format(cls.onos_config_path), 'w') as f:
+            f.write(cfg)
+        return cord_test_onos_restart()
+
+    @classmethod
+    def host_config_load(cls, host_config = None):
+	for host in host_config:
+	    status, code = OnosCtrl.host_config(host)
+	    if status is False:
+                log.info('JSON request returned status %d' %code)
+                assert_equal(status, True)
+
+    @classmethod
+    def generate_interface_config(cls, hosts = 1):
+        num = 0
+        start_host = ( 192 << 24) | ( 168 << 16)  |  (10 << 8) | 0
+        end_host =   ( 200 << 24 ) | (168 << 16)  |  (10 << 8) | 0
+        ports_dict = { 'ports' : {} }
+        interface_list = []
+        hosts_list = []
+        for n in xrange(start_host, end_host, 256):
+            port_map = ports_dict['ports']
+            port = num + 1 if num < cls.MAX_PORTS - 1 else cls.MAX_PORTS - 1
+            device_port_key = '{0}/{1}'.format(cls.device_id, port)
+            try:
+                interfaces = port_map[device_port_key]['interfaces']
+            except:
+                port_map[device_port_key] = { 'interfaces' : [] }
+                interfaces = port_map[device_port_key]['interfaces']
+            ip = n + 1
+            host_ip = n + 2
+            ips = '%d.%d.%d.%d/24'%( (ip >> 24) & 0xff, ( (ip >> 16) & 0xff ), ( (ip >> 8 ) & 0xff ), ip & 0xff)
+            host = '%d.%d.%d.%d' % ( (host_ip >> 24) & 0xff, ( ( host_ip >> 16) & 0xff ), ( (host_ip >> 8 ) & 0xff ), host_ip & 0xff )
+            mac = RandMAC()._fix()
+            hosts_list.append((host, mac))
+            if num < cls.MAX_PORTS - 1:
+                interface_dict = { 'name' : 'b1-{}'.format(port), 'ips': [ips], 'mac' : mac }
+                interfaces.append(interface_dict)
+                interface_list.append(interface_dict['name'])
+            else:
+                interfaces[0]['ips'].append(ips)
+            num += 1
+            if num == hosts:
+                break
+        cls.hosts_list = hosts_list
+        return (cls.device_dict, ports_dict, hosts_list)
+
+    @classmethod
+    def generate_host_config(cls):
+        num = 0
+        hosts_dict = {}
+        for host, mac in cls.hosts_list:
+            port = num + 1 if num < cls.MAX_PORTS - 1 else cls.MAX_PORTS - 1
+	    hosts_dict[host] = {'mac':mac, 'vlan':'none', 'ipAddresses':[host], 'location':{ 'elementId' : '{}'.format(cls.device_id), 'port': port}}
+            num += 1
+        return hosts_dict.values()
+
+    @classmethod
+    def proxyarp_activate(cls, deactivate = False):
+        app = 'org.onosproject.proxyarp'
+        onos_ctrl = OnosCtrl(app)
+        if deactivate is True:
+            onos_ctrl.deactivate()
+        else:
+            onos_ctrl.activate()
+        time.sleep(3)
+
+    @classmethod
+    def proxyarp_config(cls, hosts = 1):
+        proxyarp_configs = cls.generate_interface_config(hosts = hosts)
+	cls.interface_config_load(interface_cfg = proxyarp_configs)
+	hostcfg = cls.generate_host_config()
+	cls.host_config_load(host_config = hostcfg)
+        return proxyarp_configs
+
+    def proxyarp_arpreply_verify(self, ingress, hostip, hostmac, PositiveTest=True):
+	log.info('verifying arp reply for host ip %s host mac %s on interface %s'%(hostip ,hostmac ,self.port_map[ingress]))
+	self.success = False
+        def recv_task():
+            def recv_cb(pkt):
+                log.info('Arp Reply seen with source Mac is %s' %(pkt[ARP].hwsrc))
+                self.success = True if PositiveTest == True else False
+            sniff(count=1, timeout=2, lfilter = lambda p: ARP in p and p[ARP].op == 2 and p[ARP].hwsrc == hostmac,
+                  prn = recv_cb, iface = self.port_map[ingress])
+        t = threading.Thread(target = recv_task)
+        t.start()
+        pkt = (Ether(dst = 'ff:ff:ff:ff:ff:ff')/ARP(op=1,pdst=hostip))
+        log.info('sending arp request  for dest ip %s on interface %s' %
+                 (hostip, self.port_map[ingress]))
+        sendp( pkt, count = 10, iface = self.port_map[ingress])
+        t.join()
+	if PositiveTest:
+            assert_equal(self.success, True)
+	else:
+	    assert_equal(self.success, False)
+
+    def __proxyarp_hosts_verify(self, hosts = 1,PositiveTest = True):
+        _,_,hosts_config = self.proxyarp_config(hosts = hosts)
+	log.info('\nhosts_config %s and its type %s'%(hosts_config,type(hosts_config)))
+        self.cliEnter()
+        connected_hosts = json.loads(self.cli.hosts(jsonFormat = True))
+        log.info('Discovered hosts: %s' %connected_hosts)
+        #We read from cli if we expect less number of routes to avoid cli timeouts
+        if hosts <= 10000:
+            assert_equal(len(connected_hosts), hosts)
+	ingress = hosts+1
+	for hostip, hostmac in hosts_config:
+	        self.proxyarp_arpreply_verify(ingress,hostip,hostmac,PositiveTest = PositiveTest)
+		time.sleep(1)
+	self.cliExit()
+        return True
+
+    def test_proxyarp_with_1_host(self, hosts=1):
+        res = self.__proxyarp_hosts_verify(hosts = hosts)
+        assert_equal(res, True)
+	#cls.proxyarp_host_unload()
+    def test_proxyarp_with_10_hosts(self, hosts=10):
+        res = self.__proxyarp_hosts_verify(hosts = hosts)
+        assert_equal(res, True)
+    def test_proxyarp_with_50_hosts(self, hosts=50):
+        res = self.__proxyarp_hosts_verify(hosts = hosts)
+        assert_equal(res, True)
+    def test_proxyarp_app_with_disabling_and_re_enabling(self,hosts = 3):
+	ports_map, egress_map,hosts_config = self.proxyarp_config(hosts = hosts)
+	ingress = hosts+1
+	for hostip, hostmac in hosts_config:
+	    self.proxyarp_arpreply_verify(ingress,hostip,hostmac,PositiveTest = True)
+	    time.sleep(1)
+	log.info('Deactivating proxyarp  app and expecting not to get arp reply from ONOS')
+	self.proxyarp_activate(deactivate = True)
+	for hostip, hostmac in hosts_config:
+	    self.proxyarp_arpreply_verify(ingress,hostip,hostmac,PositiveTest = False)
+	    time.sleep(1)
+	log.info('activating proxyarp  app and expecting to get arp reply from ONOS')
+	self.proxyarp_activate(deactivate = False)
+	for hostip, hostmac in hosts_config:
+            self.proxyarp_arpreply_verify(ingress,hostip,hostmac,PositiveTest = True)
+            time.sleep(1)
+
+    def test_proxyarp_nonexisting_host(self,hosts = 1):
+    	_,_,hosts_config = self.proxyarp_config(hosts = hosts)
+	ingress = hosts + 2
+	for host, mac in hosts_config:
+	    self.proxyarp_arpreply_verify(ingress,host,mac,PositiveTest = True)
+	new_host = hosts_config[-1][0].split('.')
+	new_host[2] = str(int(new_host[2])+1)
+	new_host = '.'.join(new_host)
+	new_mac =  RandMAC()._fix()
+	log.info('verifying arp reply for host ip %s on interface %s'%(new_host,self.port_map[ingress]))
+	res=srp1(Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op=1,pdst=new_host),timeout=2,iface=self.port_map[ingress])
+	assert_equal(res, None)
+	log.info('arp reply not seen for host ip %s on interface %s as expected'%(new_host,self.port_map[ingress]))
+	hosts = hosts + 1
+	_,_,hosts_config = self.proxyarp_config(hosts = hosts)
+	for host in hosts_config:
+	    if host[0] == new_host:
+		new_mac = host[1]
+	self.proxyarp_arpreply_verify(ingress,new_host,new_mac,PositiveTest = True)
+
+    def test_proxyarp_removing_host(self,hosts = 3):
+        ports_map, egress_map,hosts_config = self.proxyarp_config(hosts = hosts)
+        ingress = hosts+1
+        for hostip, hostmac in hosts_config:
+            self.proxyarp_arpreply_verify(ingress,hostip,hostmac,PositiveTest = True)
+            time.sleep(1)
+	host_mac = hosts_config[0][1]
+        log.info('removing host entry %s' % host_mac)
+        self.cliEnter()
+        hostentries = json.loads(self.cli.hosts(jsonFormat = True))
+        for host in hostentries:
+	    res = host_mac.upper() in host.values()
+	    if res:
+	 	break
+	assert_equal(res, True)
+        hostid = host_mac+'/'+'None'
+        delete_host  = self.cli.host_remove(hostid)
+        hostentries = json.loads(self.cli.hosts(jsonFormat = True))
+	for host in hostentries:
+            res = host_mac.upper() in host.values()
+            if res:
+                break
+        assert_equal(res, False)
+        self.proxyarp_arpreply_verify(ingress,hosts_config[0][0],host_mac,PositiveTest = False)
+        time.sleep(1)
+        self.cliExit()
+
+    def test_proxyarp_concurrent_requests_with_multiple_host_and_different_interfaces(self,hosts = 10):
+	ports_map, egress_map,hosts_config = self.proxyarp_config(hosts = hosts)
+	self.success = True
+	ingress = hosts+1
+	ports = range(ingress,ingress+10)
+	hostmac = []
+	hostip = []
+	for ip,mac in hosts_config:
+	    hostmac.append(mac)
+	    hostip.append(ip)
+	success_dir = {}
+	def verify_proxyarp(*r):
+            ingress,hostmac,hostip = r[0],r[1],r[2]
+            def mac_recv_task():
+                def recv_cb(pkt):
+		    log.info('Arp Reply seen with source Mac is %s' %(pkt[ARP].hwsrc))
+                    success_dir[current_thread().name] = True
+		sniff(count=1, timeout=5,lfilter = lambda p: ARP in p and p[ARP].op == 2 and p[ARP].hwsrc == hostmac,
+                    prn = recv_cb, iface = self.port_map[ingress])
+	    t = threading.Thread(target = mac_recv_task)
+	    t.start()
+	    pkt = (Ether(dst = 'ff:ff:ff:ff:ff:ff')/ARP(op=1,pdst= hostip))
+            log.info('sending arp request  for dest ip %s on interface %s' %
+                 (hostip,self.port_map[ingress]))
+            sendp(pkt, count = 10,iface = self.port_map[ingress])
+            t.join()
+	t = []
+	for i in range(10):
+	    t.append(threading.Thread(target = verify_proxyarp, args = [ports[i],hostmac[i],hostip[i]]))
+        for i in range(10):
+	    t[i].start()
+	for i in range(10):
+            t[i].join()
+        if len(success_dir) != 10:
+                self.success = False
+        assert_equal(self.success, True)
+
+    def test_proxyarp_disabling_enabling_app_initiating_concurrent_requests(self,hosts = 10):
+	'''Test sending arp requests to multiple host ips at once from different interfaces by disabling and re-enabling proxyarp app'''
+        ports_map, egress_map,hosts_config = self.proxyarp_config(hosts = hosts)
+        self.success = True
+        ingress = hosts+1
+        ports = range(ingress,ingress+10)
+        hostmac = []
+        hostip = []
+        for ip,mac in hosts_config:
+            hostmac.append(mac)
+            hostip.append(ip)
+        success_dir = {}
+        def verify_proxyarp(*r):
+            ingress,hostmac,hostip = r[0],r[1],r[2]
+            def mac_recv_task():
+                def recv_cb(pkt):
+                    log.info('Arp Reply seen with source Mac is %s' %(pkt[ARP].hwsrc))
+                    success_dir[current_thread().name] = True
+                sniff(count=1, timeout=5,lfilter = lambda p: ARP in p and p[ARP].op == 2 and p[ARP].hwsrc == hostmac,
+                    prn = recv_cb, iface = self.port_map[ingress])
+            t = threading.Thread(target = mac_recv_task)
+            t.start()
+            pkt = (Ether(dst = 'ff:ff:ff:ff:ff:ff')/ARP(op=1,pdst= hostip))
+            log.info('sending arp request  for dest ip %s on interface %s' %
+                 (hostip,self.port_map[ingress]))
+            sendp(pkt, count = 10,iface = self.port_map[ingress])
+            t.join()
+        t1 = []
+	#starting multi threading before proxyarp disable
+        for i in range(10):
+            t1.append(threading.Thread(target = verify_proxyarp, args = [ports[i],hostmac[i],hostip[i]]))
+        for i in range(10):
+            t1[i].start()
+        for i in range(10):
+            t1[i].join()
+        if len(success_dir) != 10:
+                self.success = False
+        assert_equal(self.success, True)
+	self.proxyarp_activate(deactivate = True)
+	#starting multi threading after proxyarp disable
+	t2 = []
+	self.success = False
+	for i in range(10):
+            t2.append(threading.Thread(target = verify_proxyarp, args = [ports[i],hostmac[i],hostip[i]]))
+        for i in range(10):
+            t2[i].start()
+        for i in range(10):
+            t2[i].join()
+        if len(success_dir) != 10:
+                self.success = True
+        assert_equal(self.success, False)
+	self.proxyarp_activate(deactivate = False)
+	#starting multi threading after proxyarp re-enable
+	self.success = True
+	t3 = []
+	for i in range(10):
+            t3.append(threading.Thread(target = verify_proxyarp, args = [ports[i],hostmac[i],hostip[i]]))
+        for i in range(10):
+            t3[i].start()
+        for i in range(10):
+            t3[i].join()
+        if len(success_dir) != 20:
+                self.success = False
+	assert_equal(self.success, True)
+
+    def test_proxyarp_with_existing_and_non_existing_hostIPs_initiating_concurrent_requests(self,hosts = 5):
+        ports_map, egress_map,hosts_config = self.proxyarp_config(hosts = hosts)
+        self.success = True
+        ingress = hosts+1
+        ports = range(ingress,ingress+10)
+        hostmac = []
+        hostip = []
+        for ip,mac in hosts_config:
+            hostmac.append(mac)
+            hostip.append(ip)
+	#adding 5 non-existing host IPs to hostip list
+	for i in range(1,6):
+	    ip = hostip[-1].split('.')
+	    ip[3] = str(int(ip[3])+int(i))
+            ip = '.'.join(ip)
+	    hostip.append(ip)
+	    hostmac.append(RandMAC()._fix())
+        success_dir = {}
+	replied_hosts = []
+        def verify_proxyarp(*r):
+            ingress,hostmac,hostip = r[0],r[1],r[2]
+            def mac_recv_task():
+                def recv_cb(pkt):
+                    log.info('Arp Reply seen with source Mac is %s' %(pkt[ARP].hwsrc))
+                    success_dir[current_thread().name] = True
+		    replied_hosts.append(hostip)
+                sniff(count=1, timeout=5,lfilter = lambda p: ARP in p and p[ARP].op == 2 and p[ARP].psrc == hostip,
+                    prn = recv_cb, iface = self.port_map[ingress])
+            t = threading.Thread(target = mac_recv_task)
+            t.start()
+            pkt = (Ether(dst = 'ff:ff:ff:ff:ff:ff')/ARP(op=1,pdst= hostip))
+            log.info('sending arp request  for dest ip %s on interface %s' %
+                 (hostip,self.port_map[ingress]))
+            sendp(pkt, count = 10,iface = self.port_map[ingress])
+            t.join()
+        t = []
+        for i in range(10):
+            t.append(threading.Thread(target = verify_proxyarp, args = [ports[i],hostmac[i],hostip[i]]))
+        for i in range(10):
+            t[i].start()
+        for i in range(10):
+            t[i].join()
+        if len(success_dir) != 5 and len(replied_hosts) != 5:
+                self.success = False
+        assert_equal(self.success, True)
+	for i in range(5):
+	    if hostip[i] not in replied_hosts:
+		self.success = False
+	assert_equal(self.success, True)
diff --git a/src/test/subscriberMultiTable/subscriberMultiTableTest.py b/src/test/subscriberMultiTable/subscriberMultiTableTest.py
index 7cc9eed..a6eec2a 100644
--- a/src/test/subscriberMultiTable/subscriberMultiTableTest.py
+++ b/src/test/subscriberMultiTable/subscriberMultiTableTest.py
@@ -505,7 +505,7 @@
           return self.test_status
 
       def test_subscriber_join_recv(self):
-          """Test subscriber join and receive"""
+          """Test subscriber join and receive for channel surfing"""
           self.num_subscribers = 5
           self.num_channels = 1
           test_status = self.subscriber_join_verify(num_subscribers = self.num_subscribers,
@@ -527,7 +527,7 @@
           assert_equal(test_status, True)
 
       def test_subscriber_join_next(self):
-          """Test subscriber join next for channels"""
+          """Test subscriber join next for channel surfing"""
           self.num_subscribers = 5
           self.num_channels = 10
           test_status = self.subscriber_join_verify(num_subscribers = self.num_subscribers,
diff --git a/src/test/utils/OnosCtrl.py b/src/test/utils/OnosCtrl.py
index 92437cb..e323112 100644
--- a/src/test/utils/OnosCtrl.py
+++ b/src/test/utils/OnosCtrl.py
@@ -24,6 +24,7 @@
     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)
+    host_cfg_url = 'http://%s:8181/onos/v1/hosts/' %(controller)
 
     def __init__(self, app, controller = None):
         self.app = app
@@ -118,3 +119,11 @@
         app_url = '{}/{}'.format(url, app_name)
         resp = requests.delete(app_url, auth = cls.auth)
         return resp.ok, resp.status_code
+
+    @classmethod
+    def host_config(cls, config):
+        if config:
+           json_data = json.dumps(config)
+           resp = requests.post(cls.host_cfg_url, auth = cls.auth, data = json_data)
+           return resp.ok, resp.status_code
+        return False, 400