[SDFAB-493] UP4 data plane failure test

Change-Id: I25a7c728d1a6d068ac5f9bb02319e6c32623a6b0
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params
index e23f4ae..ceb29b6 100644
--- a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params
@@ -17,7 +17,14 @@
 
     <UP4>
         <s1u_address>10.32.11.126</s1u_address>
-        <enb_address>10.32.11.124</enb_address>
+        <enodebs>
+            <enodeb_1>
+                <host>TRexClient</host>
+                <enb_address>10.32.11.124</enb_address>
+                <interface>pairbond</interface> <!-- useless for this test, we use TRex to generate traffic -->
+                <ues>ue1,ue2</ues>
+            </enodeb_1>
+        </enodebs>
         <ues>
             <ue1>
                 <pfcp_session_id>100</pfcp_session_id>
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py
index 37629da..2a851c4 100644
--- a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py
@@ -34,7 +34,7 @@
         up4 = UP4()
         trex = Trex()
         # Get the P4RT client connected to UP4 in the first available ONOS instance
-        up4.setup(main.Cluster.active(0).p4rtUp4)
+        up4.setup(main.Cluster.active(0).p4rtUp4, no_host=True)
         trex.setup(main.TRexClient)
 
         main.step("Program PDRs and FARs via UP4")
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
index 5d1cf1d..7662f82 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
@@ -1,5 +1,5 @@
 <PARAMS>
-    <testcases>1,2,3,4</testcases>
+    <testcases>1,2,3,4,5</testcases>
 
     <GRAPH>
         <nodeCluster>pairedleaves</nodeCluster>
@@ -16,10 +16,21 @@
     </kubernetes>
 
     <UP4>
-        <pdn_host>Compute1</pdn_host>
-        <enodeb_host>Compute3</enodeb_host>
+        <pdn_host>MgmtServer</pdn_host>
+        <enodebs>
+            <enodeb_1>
+                <host>Compute3</host>
+                <enb_address>10.32.11.194</enb_address>
+                <ues>ue1,ue2</ues>
+            </enodeb_1>
+            <enodeb_2>
+                <host>Compute1</host>
+                <interface>eno3</interface>
+                <enb_address>10.32.11.122</enb_address>
+                <ues>ue3</ues>
+            </enodeb_2>
+        </enodebs>
         <s1u_address>10.32.11.126</s1u_address>
-        <enb_address>10.32.11.194</enb_address>
         <router_mac>00:00:0A:4C:1C:46</router_mac>
         <ues>
             <ue1>
@@ -40,9 +51,28 @@
                 <qfi></qfi>
                 <five_g>False</five_g>
             </ue2>
+            <ue3>
+                <pfcp_session_id>100</pfcp_session_id>
+                <ue_address>10.240.0.3</ue_address>
+                <teid>201</teid>
+                <up_id>30</up_id>
+                <down_id>31</down_id>
+                <qfi></qfi>
+                <five_g>False</five_g>
+            </ue3>
         </ues>
+        <switch_to_kill>Leaf2</switch_to_kill> <!-- Component name of the switch to kill in CASE 5 -->
+        <enodebs_fail>enodeb_1</enodebs_fail> <!-- list of eNodeBs that should fail traffic forwarding in CASE 5-->
     </UP4>
 
+    <!--  Parameters for UP4 CASE2 related to Emulated BESS UPF  -->
+    <BESS_UPF>
+        <enodeb_host>Compute3</enodeb_host>
+        <enb_address>10.32.11.194</enb_address>
+        <ue_address>10.241.0.1</ue_address> <!-- different subnet than the one used for UP4 UEs -->
+        <bess_host>Compute2</bess_host>
+    </BESS_UPF>
+
     <UP4_delete_pod>onos-tost-onos-classic-0</UP4_delete_pod>
 
     <TOPO>
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
index 99fbf4c..ef659fc 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
@@ -82,7 +82,6 @@
         Verify removed PDRs and FARs
         """
         BESS_TEID = 300
-        BESS_UE_ADDR = "10.241.0.1"
         GPDU_PORT = 2152
         UE_PORT = 400
         PDN_PORT = 800
@@ -111,12 +110,14 @@
         up4.setup(main.Cluster.active(0).p4rtUp4)
 
         # Setup the emulated BESS host and required parameters
-        bess_host = main.Compute2  # FIXME: Parametrize?
+        bess_host = getattr(main, main.params["BESS_UPF"]["bess_host"])
         bess_interface = bess_host.interfaces[0]
         bess_s1u_address = bess_interface["ips"][0]
         bess_host.startScapy(ifaceName=bess_interface["name"], enableGtp=True)
-        enodeb_host = up4.enodeb_host
-        enodeb_interface = up4.enodeb_interface
+        bess_ue_address = main.params["BESS_UPF"]["ue_address"]
+        enodeb_host = getattr(main, main.params["BESS_UPF"]["enodeb_host"])
+        enodeb_address = main.params["BESS_UPF"]["enb_address"]
+        enodeb_interface = enodeb_host.interfaces[0]["name"]
         pdn_host = up4.pdn_host
         pdn_interface = up4.pdn_interface
 
@@ -136,7 +137,7 @@
         # Start filter before sending packets, BESS should receive GTP encapped
         # packets
         pkt_filter_upstream = "ip and udp src port %d and udp dst port %d and src host %s and dst host %s" % (
-            GPDU_PORT, GPDU_PORT, up4.enb_address, bess_s1u_address)
+            GPDU_PORT, GPDU_PORT, enodeb_address, bess_s1u_address)
         main.log.info("Start listening on %s intf %s" % (
             bess_host.name, bess_interface["name"]))
         main.log.debug("BPF Filter BESS Upstream: \n %s" % pkt_filter_upstream)
@@ -145,9 +146,9 @@
                               pktFilter=pkt_filter_upstream)
         # Send GTP Packet
         UP4.buildGtpPacket(enodeb_host,
-                           src_ip_outer=up4.enb_address,
+                           src_ip_outer=enodeb_address,
                            dst_ip_outer=bess_s1u_address,
-                           src_ip_inner=BESS_UE_ADDR,
+                           src_ip_inner=bess_ue_address,
                            dst_ip_inner=pdn_interface["ips"][0],
                            src_udp_inner=UE_PORT,
                            dst_udp_inner=PDN_PORT,
@@ -168,7 +169,7 @@
         main.step("Test upstream BESS -> fabric -> PDN")
         # Start filter before sending packets, PDN should receive non-GTP packet
         pkt_filter_upstream = "ip and udp src port %d and udp dst port %d and src host %s and dst host %s" % (
-            UE_PORT, PDN_PORT, BESS_UE_ADDR, pdn_interface["ips"][0])
+            UE_PORT, PDN_PORT, bess_ue_address, pdn_interface["ips"][0])
         main.log.info("Start listening on %s intf %s" % (
             pdn_host.name, pdn_interface["name"]))
         main.log.debug("BPF Filter PDN Upstream: \n %s" % pkt_filter_upstream)
@@ -177,7 +178,7 @@
                              pktFilter=pkt_filter_upstream)
         # Send UDP Packet
         UP4.buildUdpPacket(bess_host,
-                           src_ip=BESS_UE_ADDR,
+                           src_ip=bess_ue_address,
                            dst_ip=pdn_interface["ips"][0],
                            src_udp=UE_PORT,
                            dst_udp=PDN_PORT)
@@ -196,7 +197,7 @@
         # ------- PDN -> fabric -> BESS (not-encapped)
         main.step("Test downstream PDN -> fabric -> BESS")
         pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s and dst host %s" % (
-            PDN_PORT, UE_PORT, pdn_interface["ips"][0], BESS_UE_ADDR)
+            PDN_PORT, UE_PORT, pdn_interface["ips"][0], bess_ue_address)
         main.log.info("Start listening on %s intf %s" % (
             bess_host.name, bess_interface["name"]))
         main.log.debug(
@@ -207,7 +208,7 @@
         UP4.buildUdpPacket(pdn_host,
                            dst_eth=up4.router_mac,
                            src_ip=pdn_interface["ips"][0],
-                           dst_ip=BESS_UE_ADDR,
+                           dst_ip=bess_ue_address,
                            src_udp=PDN_PORT,
                            dst_udp=UE_PORT)
         pdn_host.sendPacket()
@@ -224,20 +225,20 @@
         # ------- BESS -> fabric -> eNB (encapped)
         main.step("Test downstream BESS -> fabric -> eNB")
         pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s and dst host %s" % (
-            GPDU_PORT, GPDU_PORT, bess_s1u_address, up4.enb_address)
+            GPDU_PORT, GPDU_PORT, bess_s1u_address, enodeb_address)
         main.log.info("Start listening on %s intf %s" % (
-            enodeb_host.name, enodeb_interface["name"]))
+            enodeb_host.name, enodeb_interface))
         main.log.debug(
             "BPF Filter BESS Downstream: \n %s" % pkt_filter_downstream)
-        enodeb_host.startFilter(ifaceName=enodeb_interface["name"],
+        enodeb_host.startFilter(ifaceName=enodeb_interface,
                                 sniffCount=1,
                                 pktFilter=pkt_filter_downstream)
         # Build GTP packet from BESS host
         UP4.buildGtpPacket(bess_host,
                            src_ip_outer=bess_s1u_address,
-                           dst_ip_outer=up4.enb_address,
+                           dst_ip_outer=enodeb_address,
                            src_ip_inner=pdn_interface["ips"][0],
-                           dst_ip_inner=BESS_UE_ADDR,
+                           dst_ip_inner=bess_ue_address,
                            src_udp_inner=PDN_PORT,
                            dst_udp_inner=UE_PORT,
                            teid=BESS_TEID)
@@ -605,3 +606,123 @@
 
         run.saveOnosDiagsIfFailure(main)
         run.cleanup(main)
+
+    def CASE5(self, main):
+        main.case("UP4 Data Plane Failure Test")
+        """
+        Program PDRs/FARs
+        Kill one switch
+        Verify that traffic from eNodebs that are connected to that switch fails
+        Verify that traffic from other eNodeBs is being forwarded
+        Wait for the switch to be up again
+        Check flows
+        Remove PDRs/FARs (cleanup)
+        """
+        try:
+            from tests.USECASE.SegmentRouting.dependencies.up4 import UP4, \
+                N_FLOWS_PER_UE
+            from tests.USECASE.SegmentRouting.dependencies.Testcaselib import \
+                Testcaselib as run
+            from tests.USECASE.SegmentRouting.SRStaging.dependencies.SRStagingTest import \
+                SRStagingTest
+            import time
+            import itertools
+        except ImportError as e:
+            main.log.error("Import not found. Exiting the test")
+            main.log.error(e)
+            main.cleanAndExit()
+        n_switches = int(main.params["TOPO"]["switchNum"])
+        switch_to_kill = main.params["UP4"]["switch_to_kill"]
+
+        run.initTest(main)
+        main.log.info(main.Cluster.numCtrls)
+        main.Cluster.setRunningNode(3)
+        run.installOnos(main, skipPackage=True, cliSleep=5)
+
+        onos_cli = main.Cluster.active(0).CLI
+
+        up4 = UP4()
+
+        initial_flow_count = onos_cli.checkFlowCount()
+
+        main.step("Program and Verify PDRs and FARs via UP4")
+        up4.setup(main.Cluster.active(0).p4rtUp4)
+        up4.attachUes()
+        up4.verifyUp4Flow(onos_cli)
+
+        run.checkFlows(
+            main,
+            minFlowCount=initial_flow_count+(len(up4.emulated_ues)*4*n_switches)
+        )
+
+        main.step("Kill switch")
+        switch_component = getattr(main, switch_to_kill)
+        switch_component.handle.sendline("sudo reboot")
+
+        sleepTime = 20
+        main.log.info("Sleeping %s seconds for Fabric to react" % sleepTime)
+        time.sleep(sleepTime)
+
+        available = utilities.retry(SRStagingTest.switchIsConnected,
+                                    True,
+                                    args=[switch_component],
+                                    attempts=300,
+                                    getRetryingTime=True)
+        main.log.info("Switch %s is available in ONOS? %s" % (
+            switch_to_kill, available))
+        utilities.assert_equal(
+            expect=False,
+            actual=available,
+            onpass="Switch was rebooted (ONL reboot) successfully",
+            onfail="Switch was not rebooted (ONL reboot) successfully"
+        )
+
+        enodebs_fail = main.params["UP4"]["enodebs_fail"].split(",")
+        enodebs_no_fail = list(set(up4.enodebs.keys()) - set(enodebs_fail))
+
+        # ------- Test Upstream traffic (enbs->pdn)
+        main.step("Test upstream traffic FAIL")
+        up4.testUpstreamTraffic(enb_names=enodebs_fail, shouldFail=True)
+        main.step("Test upstream traffic NO FAIL")
+        up4.testUpstreamTraffic(enb_names=enodebs_no_fail, shouldFail=False)
+
+        # ------- Test Downstream traffic (pdn->enbs)
+        main.step("Test downstream traffic FAIL")
+        up4.testDownstreamTraffic(enb_names=enodebs_fail, shouldFail=True)
+        main.step("Test downstream traffic NO FAIL")
+        up4.testDownstreamTraffic(enb_names=enodebs_no_fail, shouldFail=False)
+
+        # Reconnect to the switch
+        connect = utilities.retry(switch_component.connect,
+                                  main.FALSE,
+                                  attempts=30,
+                                  getRetryingTime=True)
+        main.log.info("Connected to the switch %s? %s" % (
+            switch_to_kill, connect))
+        # Wait switch to be back in ONOS
+        available = utilities.retry(SRStagingTest.switchIsConnected,
+                                    False,
+                                    args=[switch_component],
+                                    attempts=300,
+                                    getRetryingTime=True)
+        main.log.info("Switch %s is available in ONOS? %s" % (
+            switch_to_kill, available))
+        utilities.assert_equal(
+            expect=True,
+            actual=available and connect == main.TRUE,
+            onpass="Switch is back available in ONOS",
+            onfail="Switch is not available in ONOS, may influence subsequent tests!"
+        )
+
+        main.step("Test upstream traffic AFTER switch reboot")
+        up4.testUpstreamTraffic()
+
+        main.step("Cleanup PDRs and FARs via UP4")
+        up4.detachUes()
+        up4.verifyNoUesFlow(onos_cli)
+        up4.teardown()
+
+        run.checkFlows(main, minFlowCount=initial_flow_count)
+
+        # Teardown
+        run.cleanup(main)
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
index 8605cad..731c259 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
@@ -27,6 +27,35 @@
             </COMPONENTS>
         </ONOScell>
 
+        <MgmtServer>
+            <host>10.76.28.66</host>
+            <user>jenkins</user>
+            <password></password>
+            <type>HostDriver</type>
+            <connect_order>6</connect_order>
+            <jump_host></jump_host>
+            <COMPONENTS>
+                <mac></mac>
+                <inband>false</inband>
+                <dhcp>True</dhcp>
+                <ip>10.32.11.1</ip>
+                <shortName>mgmt</shortName>
+                <port1></port1>
+                <link1></link1>
+                <ifaceName>pairbond</ifaceName>
+                <scapy_path>/usr/bin/scapy</scapy_path>
+                <routes>
+                    <route1>
+                        <network></network>
+                        <netmask></netmask>
+                        <gw></gw>
+                        <interface></interface>
+                    </route1>
+                </routes>
+                <sudo_required>true</sudo_required>
+            </COMPONENTS>
+        </MgmtServer>
+
         <Compute1>
             <host>10.76.28.74</host>
             <user>jenkins</user>
@@ -114,6 +143,21 @@
             </COMPONENTS>
         </Compute3>
 
+        <Leaf2>
+            <host>10.76.28.71</host>
+            <user>root</user>
+            <password>onl</password>
+            <type>StratumOSSwitchDriver</type>
+            <connect_order>10</connect_order>
+            <COMPONENTS>
+                <shortName>leaf2</shortName>
+                <port1>2</port1>
+                <link1>Host2</link1>
+                <onosConfigPath></onosConfigPath>
+                <onosConfigFile></onosConfigFile>
+            </COMPONENTS>
+        </Leaf2>
+
     <!--  This component is not needed, but required to use the Testcaselib  -->
         <NetworkBench>
             <host>10.76.28.66</host>
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
index bc6a49a..0f6c8a8 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
@@ -1,4 +1,5 @@
 from distutils.util import strtobool
+import copy
 
 FALSE = '0'
 TRUE = '1'
@@ -20,16 +21,28 @@
     """
     Utility that manages interaction with UP4 via a P4RuntimeCliDriver available
     in the cluster. Additionally, can verify connectivity by crafting GTP packets
-    via Scapy with an HostDriver component, specified via <enodeb_host>, <pdn_host>,
+    via Scapy with an HostDriver component, specified via <enodebs>, <pdn_host>,
     and <router_mac> parameters.
 
     Example params file:
     <UP4>
         <pdn_host>Compute1</pdn_host> # Needed to verify connectivity with scapy
-        <enodeb_host>Compute3</enodeb_host> # Needed to verify connectivity with scapy
+         <enodebs> # List of emulated eNodeBs
+            <enode_1>
+                <host>Compute1</host>  # Host that emulates this eNodeB
+                <interface>eno3</interface> # Name of the linux interface to use on the host, if not specified take the default
+                <enb_address>10.32.11.122</enb_address> # IP address of the eNodeB
+                <ues>ue3</ues> # Emulated ues connected to this eNB
+            </enode_1>
+            <enodeb_2>
+                <host>Compute3</host>
+                <enb_address>10.32.11.194</enb_address>
+                <ues>ue1,ue2</ues>
+            </enodeb_2>
+        </enodebs>
+        <enodeb_host>Compute3</enodeb_host>
         <router_mac>00:00:0A:4C:1C:46</router_mac> # Needed to verify connectivity with scapy
         <s1u_address>10.32.11.126</s1u_address>
-        <enb_address>10.32.11.100</enb_address>
         <ues>
             <ue2>
                 <pfcp_session_id>100</pfcp_session_id>
@@ -41,19 +54,20 @@
                 <five_g>False</five_g>
             </ue2>
         </ues>
+        <switch_to_kill>Leaf2</switch_to_kill> # Component name of the switch to kill in CASE 5
+        <enodebs_fail>enodeb_1</enodebs_fail> # List of eNodeBs that should fail traffic forwarding in CASE 5
     </UP4>
     """
 
     def __init__(self):
         self.s1u_address = None
-        self.enb_address = None
-        self.enodeb_host = None
-        self.enodeb_interface = None
+        self.enodebs = None
         self.pdn_host = None
         self.pdn_interface = None
         self.router_mac = None
-        self.emulated_ues = []
+        self.emulated_ues = {}
         self.up4_client = None
+        self.no_host = False
 
     def setup(self, p4rt_client, no_host=False):
         """
@@ -63,58 +77,73 @@
         :return:
         """
         self.s1u_address = main.params["UP4"]["s1u_address"]
-        self.enb_address = main.params["UP4"]["enb_address"]
         self.emulated_ues = main.params["UP4"]['ues']
         self.up4_client = p4rt_client
+        self.no_host = no_host
 
         # Optional Parameters
-        if not no_host:
-            if "enodeb_host" in main.params["UP4"]:
-                self.enodeb_host = getattr(main,
-                                           main.params["UP4"]["enodeb_host"])
-                self.enodeb_interface = self.enodeb_host.interfaces[0]
-            if "pdn_host" in main.params["UP4"]:
-                self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
-                self.pdn_interface = self.pdn_host.interfaces[0]
-            self.router_mac = main.params["UP4"].get("router_mac", None)
+
+        self.enodebs = copy.deepcopy((main.params["UP4"]["enodebs"]))
+        for enb in self.enodebs.values():
+            enb["ues"] = enb["ues"].split(",")
+            enb["host"] = getattr(main, enb["host"])
+            # If interface not provided by the params, use the default in the host
+            if "interface" not in enb.keys():
+                enb["interface"] = enb["host"].interfaces[0]["name"]
+        if "pdn_host" in main.params["UP4"]:
+            self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
+            self.pdn_interface = self.pdn_host.interfaces[0]
+        self.router_mac = main.params["UP4"].get("router_mac", None)
 
         # Start components
         self.up4_client.startP4RtClient()
-        if self.enodeb_host is not None:
-            self.enodeb_host.startScapy(ifaceName=self.enodeb_interface["name"],
-                                        enableGtp=True)
-        if self.pdn_host is not None:
-            self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
+        if not self.no_host:
+            if self.enodebs is not None:
+                for enb in self.enodebs.values():
+                    enb["host"].startScapy(ifaceName=enb["interface"],
+                                            enableGtp=True)
+            if self.pdn_host is not None:
+                self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
 
     def teardown(self):
         self.up4_client.stopP4RtClient()
-        if self.enodeb_host is not None:
-            self.enodeb_host.stopScapy()
-        if self.pdn_host is not None:
-            self.pdn_host.stopScapy()
+        if not self.no_host:
+            if self.enodebs is not None:
+                for enb in self.enodebs.values():
+                    enb["host"].stopScapy()
+            if self.pdn_host is not None:
+                self.pdn_host.stopScapy()
 
     def attachUes(self):
-        for ue in self.emulated_ues.values():
+        for (name, ue) in self.emulated_ues.items():
             ue = UP4.__sanitizeUeData(ue)
-            self.attachUe(**ue)
+            self.attachUe(name, **ue)
 
     def detachUes(self):
-        for ue in self.emulated_ues.values():
+        for (name, ue) in self.emulated_ues.items():
             ue = UP4.__sanitizeUeData(ue)
-            self.detachUe(**ue)
+            self.detachUe(name, **ue)
 
-    def testUpstreamTraffic(self):
-        if self.enodeb_host is None or self.pdn_host is None:
+    def testUpstreamTraffic(self, enb_names=None, shouldFail=False):
+        if self.enodebs is None or self.pdn_host is None:
             main.log.error(
                 "Need eNodeB and PDN host params to generate scapy traffic")
             return
         # Scapy filter needs to start before sending traffic
+        if enb_names is None or enb_names == []:
+            enodebs = self.enodebs.values()
+        else:
+            enodebs = [self.enodebs[enb] for enb in enb_names]
         pkt_filter_upstream = ""
-        for ue in self.emulated_ues.values():
-            if "ue_address" in ue:
-                if len(pkt_filter_upstream) != 0:
-                    pkt_filter_upstream += " or "
-                pkt_filter_upstream += "src host " + ue["ue_address"]
+        ues = []
+        for enb in enodebs:
+            for ue_name in enb["ues"]:
+                ue = self.emulated_ues[ue_name]
+                if "ue_address" in ue:
+                    ues.append(ue)
+                    if len(pkt_filter_upstream) != 0:
+                        pkt_filter_upstream += " or "
+                    pkt_filter_upstream += "src host " + ue["ue_address"]
         pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
                               (PDN_PORT, pkt_filter_upstream,
                                self.pdn_interface["ips"][0])
@@ -122,33 +151,35 @@
                       (self.pdn_host.name, self.pdn_interface["name"]))
         main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
         self.pdn_host.startFilter(ifaceName=self.pdn_interface["name"],
-                                  sniffCount=len(self.emulated_ues),
+                                  sniffCount=len(ues),
                                   pktFilter=pkt_filter_upstream)
 
         main.log.info(
-            "Sending %d packets from eNodeB host" % len(self.emulated_ues))
-        for ue in self.emulated_ues.values():
-            UP4.buildGtpPacket(self.enodeb_host,
-                               src_ip_outer=self.enb_address,
-                               dst_ip_outer=self.s1u_address,
-                               src_ip_inner=ue["ue_address"],
-                               dst_ip_inner=self.pdn_interface["ips"][0],
-                               src_udp_inner=UE_PORT,
-                               dst_udp_inner=PDN_PORT,
-                               teid=int(ue["teid"]))
-
-            self.enodeb_host.sendPacket(iface=self.enodeb_interface["name"])
+            "Sending %d packets from eNodeB host" % len(ues))
+        for enb in enodebs:
+            for ue_name in enb["ues"]:
+                main.log.info(ue_name)
+                ue = self.emulated_ues[ue_name]
+                main.log.info(str(ue))
+                UP4.buildGtpPacket(enb["host"],
+                                   src_ip_outer=enb["enb_address"],
+                                   dst_ip_outer=self.s1u_address,
+                                   src_ip_inner=ue["ue_address"],
+                                   dst_ip_inner=self.pdn_interface["ips"][0],
+                                   src_udp_inner=UE_PORT,
+                                   dst_udp_inner=PDN_PORT,
+                                   teid=int(ue["teid"]))
+                enb["host"].sendPacket(iface=enb["interface"])
 
         packets = UP4.checkFilterAndGetPackets(self.pdn_host)
         fail = False
-        if len(self.emulated_ues) != packets.count('Ether'):
+        if len(ues) != packets.count('Ether'):
             fail = True
             msg = "Failed to capture packets in PDN.\n" + str(packets)
         else:
             msg = "Correctly captured packet in PDN. "
         # We expect exactly 1 packet per UE
-        pktsFiltered = [packets.count("src=" + ue["ue_address"])
-                        for ue in self.emulated_ues.values()]
+        pktsFiltered = [packets.count("src=" + ue["ue_address"]) for ue in ues]
         if pktsFiltered.count(1) != len(pktsFiltered):
             fail = True
             msg += "\nError on the number of packets per UE in downstream.\n" + str(packets)
@@ -156,25 +187,33 @@
             msg += "\nOne packet per UE in upstream. "
 
         utilities.assert_equal(
-            expect=False, actual=fail, onpass=msg, onfail=msg)
+            expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
 
-    def testDownstreamTraffic(self):
-        if self.enodeb_host is None or self.pdn_host is None:
+    def testDownstreamTraffic(self, enb_names=None, shouldFail=False):
+        if self.enodebs is None or self.pdn_host is None:
             main.log.error(
                 "Need eNodeB and PDN host params to generate scapy traffic")
             return
-        pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and dst host %s and src host %s" % (
-            GPDU_PORT, GPDU_PORT, self.enb_address, self.s1u_address)
-        main.log.info("Start listening on %s intf %s" % (
-            self.enodeb_host.name, self.enodeb_interface["name"]))
-        main.log.debug("BPF Filter Downstream: \n %s" % pkt_filter_downstream)
-        self.enodeb_host.startFilter(ifaceName=self.enodeb_interface["name"],
-                                     sniffCount=len(self.emulated_ues),
-                                     pktFilter=pkt_filter_downstream)
+        if enb_names is None or enb_names == []:
+            enodebs = self.enodebs.values()
+        else:
+            enodebs = [self.enodebs[enb] for enb in enb_names]
+        pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
+            GPDU_PORT, GPDU_PORT, self.s1u_address)
+        ues = []
+        for enb in enodebs:
+            filter_down = pkt_filter_downstream + " and dst host %s" % enb["enb_address"]
+            main.log.info("Start listening on %s intf %s" % (
+                enb["host"], enb["interface"]))
+            main.log.debug("BPF Filter Downstream: \n %s" % filter_down)
+            enb["host"].startFilter(ifaceName=enb["interface"],
+                                    sniffCount=len(enb["ues"]),
+                                    pktFilter=filter_down)
+            ues.extend([self.emulated_ues[ue_name] for ue_name in enb["ues"]])
 
         main.log.info(
-            "Sending %d packets from PDN host" % len(self.emulated_ues))
-        for ue in self.emulated_ues.values():
+            "Sending %d packets from PDN host" % len(ues))
+        for ue in ues:
             # From PDN we have to set dest MAC, otherwise scapy will do ARP
             # request for the UE IP address.
             UP4.buildUdpPacket(self.pdn_host,
@@ -184,19 +223,21 @@
                                src_udp=PDN_PORT,
                                dst_udp=UE_PORT)
             self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
-
-        packets = UP4.checkFilterAndGetPackets(self.enodeb_host)
-
+        packets = ""
+        for enb in enodebs:
+            pkt = UP4.checkFilterAndGetPackets(enb["host"])
+            packets += pkt
         # The BPF filter might capture non-GTP packets because we can't filter
         # GTP header in BPF. For this reason, check that the captured packets
         # are from the expected tunnels.
         # TODO: check inner UDP and IP fields as well
         # FIXME: with newer scapy TEID becomes teid (required for Scapy 2.4.5)
-        pktsFiltered = [packets.count("TEID=" + hex(int(ue["teid"])) + "L ")
-                        for ue in self.emulated_ues.values()]
-
+        pktsFiltered= [packets.count("TEID=" + hex(int(ue["teid"])) + "L ")
+             for ue in ues]
+        main.log.info("PACKETS: " + str(packets))
+        main.log.info("PKTs Filtered: " + str(pktsFiltered))
         fail = False
-        if len(self.emulated_ues) != sum(pktsFiltered):
+        if len(ues) != sum(pktsFiltered):
             fail = True
             msg = "Failed to capture packets in eNodeB.\n" + str(packets)
         else:
@@ -209,7 +250,7 @@
             msg += "\nOne packet per GTP TEID in downstream. "
 
         utilities.assert_equal(
-            expect=False, actual=fail, onpass=msg, onfail=msg)
+            expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
 
     def readPdrsNumber(self):
         """
@@ -305,7 +346,7 @@
         fars = onosCli.sendline(cmdStr="up4:read-fars", showResponse=True,
                                 noExit=True, expectJson=False)
         fail = False
-        for ue in self.emulated_ues.values():
+        for (ue_name, ue) in self.emulated_ues.items():
             if pdrs.count(self.upPdrOnosString(**ue)) != 1:
                 failMsg += self.upPdrOnosString(**ue) + "\n"
                 fail = True
@@ -315,8 +356,8 @@
             if fars.count(self.upFarOnosString(**ue)) != 1:
                 failMsg += self.upFarOnosString(**ue) + "\n"
                 fail = True
-            if fars.count(self.downFarOnosString(**ue)) != 1:
-                failMsg += self.downFarOnosString(**ue) + "\n"
+            if fars.count(self.downFarOnosString(ue_name, **ue)) != 1:
+                failMsg += self.downFarOnosString(ue_name, **ue) + "\n"
                 fail = True
         return not fail
 
@@ -351,16 +392,16 @@
         return "PDR{{Match(Dst={}, !GTP) -> LoadParams(SEID={}, FAR={}, CtrIdx={})}}".format(
             ue_address, hex(int(pfcp_session_id)), far_id_down, ctr_id_down)
 
-    def downFarOnosString(self, pfcp_session_id, teid=None, down_id=None,
+    def downFarOnosString(self, ue_name, pfcp_session_id, teid=None, down_id=None,
                           teid_down=None, far_id_down=None, **kwargs):
         if down_id is not None:
             far_id_down = down_id
         if teid is not None:
             teid_down = teid
+        enb_address = self.__getEnbAddress(ue_name)
         return "FAR{{Match(ID={}, SEID={}) -> Encap(Src={}, SPort={}, TEID={}, Dst={})}}".format(
             far_id_down, hex(int(pfcp_session_id)), self.s1u_address, GPDU_PORT,
-            hex(int(teid_down)),
-            self.enb_address)
+            hex(int(teid_down)), enb_address)
 
     def upFarOnosString(self, pfcp_session_id, up_id=None, far_id_up=None,
                         **kwargs):
@@ -377,13 +418,14 @@
             ue["qfi"] = None
         return ue
 
-    def attachUe(self, pfcp_session_id, ue_address,
+    def attachUe(self, ue_name, pfcp_session_id, ue_address,
                  teid=None, up_id=None, down_id=None,
                  teid_up=None, teid_down=None,
                  pdr_id_up=None, far_id_up=None, ctr_id_up=None,
                  pdr_id_down=None, far_id_down=None, ctr_id_down=None,
                  qfi=None, five_g=False):
-        self.__programUp4Rules(pfcp_session_id,
+        self.__programUp4Rules(ue_name,
+                               pfcp_session_id,
                                ue_address,
                                teid, up_id, down_id,
                                teid_up, teid_down,
@@ -391,13 +433,14 @@
                                pdr_id_down, far_id_down, ctr_id_down,
                                qfi, five_g, action="program")
 
-    def detachUe(self, pfcp_session_id, ue_address,
+    def detachUe(self, ue_name, pfcp_session_id, ue_address,
                  teid=None, up_id=None, down_id=None,
                  teid_up=None, teid_down=None,
                  pdr_id_up=None, far_id_up=None, ctr_id_up=None,
                  pdr_id_down=None, far_id_down=None, ctr_id_down=None,
                  qfi=None, five_g=False):
-        self.__programUp4Rules(pfcp_session_id,
+        self.__programUp4Rules(ue_name,
+                               pfcp_session_id,
                                ue_address,
                                teid, up_id, down_id,
                                teid_up, teid_down,
@@ -405,7 +448,7 @@
                                pdr_id_down, far_id_down, ctr_id_down,
                                qfi, five_g, action="clear")
 
-    def __programUp4Rules(self, pfcp_session_id, ue_address,
+    def __programUp4Rules(self, ue_name, pfcp_session_id, ue_address,
                           teid=None, up_id=None, down_id=None,
                           teid_up=None, teid_down=None,
                           pdr_id_up=None, far_id_up=None, ctr_id_up=None,
@@ -425,6 +468,9 @@
 
         entries = []
 
+        # Retrieve eNobeB address from eNodeB list
+        enb_address = self.__getEnbAddress(ue_name)
+
         # ========================#
         # PDR Entries
         # ========================#
@@ -518,7 +564,7 @@
         actionParams['needs_buffering'] = FALSE
         actionParams['tunnel_type'] = TUNNEL_TYPE_GPDU
         actionParams['src_addr'] = str(self.s1u_address)
-        actionParams['dst_addr'] = str(self.enb_address)
+        actionParams['dst_addr'] = str(enb_address)
         actionParams['teid'] = str(teid_down)
         actionParams['sport'] = TUNNEL_SPORT
         if not self.__add_entry(tableName, actionName, matchFields,
@@ -555,6 +601,13 @@
             else:
                 main.log.error("Error during table delete")
 
+    def __getEnbAddress(self, ue_name):
+        for enb in self.enodebs.values():
+            if ue_name in enb["ues"]:
+                return enb["enb_address"]
+        main.log.error("Missing eNodeB address!")
+        return ""
+
     @staticmethod
     def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
                        dst_ip_inner, src_udp_inner, dst_udp_inner, teid):