[SDFAB-506] Add QoS test for on edge ports with GTP encapped traffic

Also, create library to attach, detach UEs via UP4 and to generate Traffic via TRex.

Change-Id: I9cd7bedbc0f799813dd033220fcdffb5baf20eda
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params
new file mode 100644
index 0000000..056b731
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.params
@@ -0,0 +1,120 @@
+<PARAMS>
+    <testcases>1</testcases>
+
+    <GRAPH>
+        <nodeCluster>pairedleaves</nodeCluster>
+        <builds>20</builds>
+        <jobName>QOS</jobName>
+        <branch>master</branch>
+    </GRAPH>
+
+    <persistent_setup>True</persistent_setup>
+
+    <kubernetes>
+        <appName>onos-classic</appName>
+        <namespace>tost</namespace>
+    </kubernetes>
+
+    <UP4>
+        <s1u_address>10.32.11.126</s1u_address>
+        <enb_address>10.32.11.124</enb_address>
+        <ues>
+            <ue1>
+                <pfcp_session_id>100</pfcp_session_id>
+                <ue_address>10.240.0.1</ue_address>
+                <teid>100</teid>
+                <up_id>10</up_id>
+                <down_id>11</down_id>
+                <!-- <qfi>0</qfi>Best Effort -->
+                <five_g>False</five_g>
+            </ue1>
+            <ue2>
+                <pfcp_session_id>100</pfcp_session_id>
+                <ue_address>10.240.0.2</ue_address>
+                <teid>200</teid>
+                <up_id>20</up_id>
+                <down_id>21</down_id>
+                <qfi>2</qfi> <!-- Real Time -->
+                <five_g>False</five_g>
+            </ue2>
+        </ues>
+    </UP4>
+
+    <TREX>
+        <port_stats>0,2</port_stats> <!-- TRex port 0 = PDN, TRex port 2 = eNodeB -->
+        <flows>
+            <BE_FROM_PDN>
+                <name>Best Effort</name>
+                <l1_bps>1500000000</l1_bps>
+                <trex_port>0</trex_port>
+                <packet>
+                    <pktlen>1400</pktlen>
+                    <ip_src>10.32.11.125</ip_src>
+                    <ip_dst>10.240.0.1</ip_dst>
+                    <eth_src>3C:EC:EF:3E:0B:A1</eth_src>
+                    <eth_dst>00:00:0A:4C:1C:46</eth_dst>
+                </packet>
+            </BE_FROM_PDN>
+            <RT_FROM_PDN>
+                <name>Real Time</name>
+                <l1_bps>40000000</l1_bps>
+                <trex_port>0</trex_port>
+                <packet>
+                    <pktlen>1400</pktlen>
+                    <ip_src>10.32.11.125</ip_src>
+                    <ip_dst>10.240.0.2</ip_dst>
+                    <eth_src>3C:EC:EF:3E:0B:A1</eth_src>
+                    <eth_dst>00:00:0A:4C:1C:46</eth_dst>
+                </packet>
+                <latency_stats>true</latency_stats>
+                <flow_id>10</flow_id> <!-- Mandatory when latency_stats=true -->
+                <delay>50000</delay> <!-- wait 50 ms till start to let queues fill up -->
+                <expected_min_received>1</expected_min_received>
+                <expected_max_dropped>0</expected_max_dropped>
+                <expected_99_9_percentile_latency>100</expected_99_9_percentile_latency>
+            </RT_FROM_PDN>
+        </flows>
+    </TREX>
+
+    <TOPO>
+        <switchNum>2</switchNum>
+        <linkNum>2</linkNum>
+    </TOPO>
+
+    <ONOS_Logging>
+        <org.onosproject.p4runtime.ctl.client>DEBUG</org.onosproject.p4runtime.ctl.client>
+        <org.onosproject.p4runtime.ctl.client.writerequestimpl>TRACE</org.onosproject.p4runtime.ctl.client.writerequestimpl>
+        <org.onosproject.segmentrouting>DEBUG</org.onosproject.segmentrouting>
+        <org.onosproject.gnmi.ctl>TRACE</org.onosproject.gnmi.ctl>
+        <org.omecproject.up4>TRACE</org.omecproject.up4>
+    </ONOS_Logging>
+    <ONOS_Logging_Reset>
+        <org.onosproject.p4runtime.ctl.client>INFO</org.onosproject.p4runtime.ctl.client>
+        <org.onosproject.p4runtime.ctl.client.writerequestimpl>INFO</org.onosproject.p4runtime.ctl.client.writerequestimpl>
+        <org.onosproject.segmentrouting>DEBUG</org.onosproject.segmentrouting>
+        <org.onosproject.gnmi.ctl>INFO</org.onosproject.gnmi.ctl>
+        <org.omecproject.up4>INFO</org.omecproject.up4>
+    </ONOS_Logging_Reset>
+
+    <ENV>
+        <cellName>productionCell</cellName>
+        <cellApps>drivers,fpm,lldpprovider,hostprovider,netcfghostprovider,drivers.bmv2,org.opencord.fabric-tofino,pipelines.fabric,org.stratumproject.fabric-tna,drivers.barefoot,segmentrouting,t3,up4</cellApps>
+    </ENV>
+
+    <DEPENDENCY>
+        <useCommonConf>False</useCommonConf>
+        <useCommonTopo>True</useCommonTopo>
+        <useBmv2>True</useBmv2>
+        <bmv2SwitchType>stratum</bmv2SwitchType>
+        <switchPrefix></switchPrefix>
+        <stratumRoot>~/stratum</stratumRoot>
+        <topology>trellis_fabric.py</topology>
+        <lib></lib>
+    </DEPENDENCY>
+
+    <SCALE>
+        <size>3</size>
+        <max>3</max>
+    </SCALE>
+
+</PARAMS>
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py
new file mode 100644
index 0000000..6aed867
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.py
@@ -0,0 +1,69 @@
+class QOS:
+
+    def __init__(self):
+        self.default = ''
+
+    def CASE1(self, main):
+        # Leaf-Edge-Mobile
+        # Attach 2 UEs with different QFI
+        # Generate traffic with Trex for the two UEs
+        # --> no packet drop on RT flow, reasonable latency on RT flow
+        try:
+            from tests.USECASE.SegmentRouting.dependencies.up4 import UP4
+            from tests.USECASE.SegmentRouting.dependencies.trex import Trex
+            from tests.USECASE.SegmentRouting.dependencies.Testcaselib import \
+                Testcaselib as run
+            import json
+        except ImportError as e:
+            main.log.error("Import not found. Exiting the test")
+            main.log.error(e)
+            main.cleanAndExit()
+
+        run.initTest(main)
+        main.log.info(main.Cluster.numCtrls)
+        main.Cluster.setRunningNode(3)
+        run.installOnos(main, skipPackage=True, cliSleep=5)
+
+        main.step("Start P4rt client and setup TRex")
+        up4 = UP4()
+        trex = Trex()
+        # Get the P4RT client connected to UP4 in the first available ONOS instance
+        up4.setup(main.Cluster.active(0).p4rtUp4)
+        trex.setup(main.TRexClient)
+
+        main.step("Attach UEs")
+        up4.attachUes()
+
+        # Load traffic config for the current test case
+        main.step("Load test JSON config")
+        cfgFile = main.configPath + "/tests/" + "leaf_edge_mobile.json"
+        with open(cfgFile) as cfg:
+            testCfg = json.load(cfg)
+
+        main.step("Send traffic with TRex")
+        for flow in testCfg["flows"]:
+            trex.createFlow(flow)
+        trex.sendAndReceiveTraffic(testCfg["duration"])
+
+        main.step("Log port and flow stats")
+        trex.logPortStats()
+        for flow in testCfg["flows"]:
+            trex.logFlowStats(flow)
+
+        # Assert Flow Stats
+        for flow in testCfg["flows"]:
+            if trex.isFlowStats(flow):
+                main.step("{}: Assert RX Packets".format(flow))
+                trex.assertRxPackets(flow)
+                main.step("{}: Assert Dropped Packets".format(flow))
+                trex.assertDroppedPacket(flow)
+                main.step("{}: Assert 99.9 Percentile Latency".format(flow))
+                trex.assert99_9PercentileLatency(flow)
+
+        main.step("Detach UEs")
+        up4.detachUes()
+
+        main.step("Teardown")
+        trex.teardown()
+        up4.teardown()
+        run.cleanup(main)
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/QOS.topo b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.topo
new file mode 100644
index 0000000..631d4e8
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/QOS.topo
@@ -0,0 +1,55 @@
+<TOPOLOGY>
+    <COMPONENT>
+        <ONOScell>
+            <host>localhost</host>  # ONOS "bench" machine
+            <user>jenkins</user>
+            <password></password>
+            <type>OnosClusterDriver</type>
+            <connect_order>50</connect_order>
+            <jump_host></jump_host>
+            <home>~/onos</home>   # defines where onos home is on the build machine. Defaults to "~/onos/" if empty.
+            <COMPONENTS>
+                <kubeConfig>~/.kube/dev-pairedleaves-tucson</kubeConfig>  # If set, will attempt to use this file for setting up port-forwarding
+                <useDocker>True</useDocker>  # Whether to use docker for ONOS nodes
+                <docker_prompt>\$</docker_prompt>
+                <cluster_name></cluster_name>  # Used as a prefix for cluster components. Defaults to 'ONOS'
+                <diff_clihost>True</diff_clihost> # if it has different host other than localhost for CLI. True or empty. OC# will be used if True.
+                <karaf_username>karaf</karaf_username>
+                <karaf_password>karaf</karaf_password>
+                <web_user>karaf</web_user>
+                <web_pass>karaf</web_pass>
+                <karafPrompt_username>karaf</karafPrompt_username>
+                <rest_port></rest_port>
+                <prompt></prompt>  # TODO: we technically need a few of these, one per component
+                <onos_home>~/onos/</onos_home>  # defines where onos home is on the target cell machine. Defaults to entry in "home" if empty.
+                <nodes> 3 </nodes>  # number of nodes in the cluster
+                <up4_port>51001</up4_port> # Port where the UP4 P4Runtime server is listening
+            </COMPONENTS>
+        </ONOScell>
+
+        <!-- No need for any HostDriver components, traffic is being generated by TRex-->
+        <TRexClient>
+            <host>localhost</host>
+            <type>TrexClientDriver</type>
+            <connect_order>5</connect_order>
+            <COMPONENTS>
+                <trex_address>10.76.28.72</trex_address> <!-- Compute2 -->
+                <trex_config>trex_config.yaml</trex_config> <!-- relative path starting from ./dependencies-->
+                <force_restart>True</force_restart>
+                <software_mode>True</software_mode>
+            </COMPONENTS>
+        </TRexClient>
+
+    <!--  This component is not needed, but required to use the Testcaselib  -->
+        <NetworkBench>
+            <host>10.76.28.66</host>
+            <user>jenkins</user>
+            <password></password>
+            <type>NetworkDriver</type>
+            <connect_order>1</connect_order>
+            <COMPONENTS>
+            </COMPONENTS>
+        </NetworkBench>
+
+    </COMPONENT>
+</TOPOLOGY>
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/README.md b/TestON/tests/USECASE/SegmentRouting/QOS/README.md
new file mode 100644
index 0000000..fdf9a1b
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/README.md
@@ -0,0 +1,4 @@
+# Requirements (TODO)
+
+- ptf
+- trex
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/__init__.py b/TestON/tests/USECASE/SegmentRouting/QOS/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/__init__.py
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/__init__.py b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/__init__.py
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/tests/leaf_edge_mobile.json b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/tests/leaf_edge_mobile.json
new file mode 100644
index 0000000..f32fa79
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/tests/leaf_edge_mobile.json
@@ -0,0 +1,5 @@
+{
+  "flows": ["BE_FROM_PDN", "RT_FROM_PDN"],
+  "duration": 10
+}
+
diff --git a/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/trex_config.yaml b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/trex_config.yaml
new file mode 100644
index 0000000..00a1f85
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/QOS/dependencies/trex_config.yaml
@@ -0,0 +1,19 @@
+# TRex Port ID=0 --> PCI BUS: d8:00.0, Linux Intf: enp216s0f0 connected to leaf1/0 (PDN)
+# TRex Port ID=1 --> PCI BUS: d8:00.1, Linux Intf: enp216s0f1 not connected, but required by TRex to have an even number of interfaces
+# TRex Port ID=2 --> PCI BUS: 5e:00.0, Linux Intf: enp94s0f0 connected to leaf2/0 (eNodeB)
+# TRex Port ID=3 --> PCI BUS: 5e:00.1, Linux Intf: enp94s0f1 connected to leaf2/4
+
+- version: 2
+  port_limit: 4
+  interfaces: [ 'd8:00.0', 'd8:00.1', '5e:00.0', '5e:00.1']
+  port_bandwidth_gb: 40
+  c: 16
+  port_info:
+    - src_mac: 40:A6:B7:22:AB:40
+      dest_mac: 00:00:0A:4C:1C:46
+    - src_mac: 40:A6:B7:22:AB:41
+      dest_mac: 00:00:0A:4C:1C:46
+    - src_mac: 40:A6:B7:22:AB:20
+      dest_mac: 00:00:0A:4C:1C:46
+    - src_mac: 40:A6:B7:22:AB:21
+      dest_mac: 00:00:0A:4C:1C:46
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
index a7f9804..0dcbbd8 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
@@ -13,191 +13,39 @@
         Verify traffic received from UE
         Detach UE
         """
-        UE_PORT = 400
-        PDN_PORT = 800
-        GPDU_PORT = 2152
         try:
-            from tests.USECASE.SegmentRouting.dependencies.up4libcli import \
-                Up4LibCli
+            from tests.USECASE.SegmentRouting.dependencies.up4 import UP4
             from tests.USECASE.SegmentRouting.dependencies.Testcaselib import \
                 Testcaselib as run
-            from distutils.util import strtobool
         except ImportError as e:
             main.log.error("Import not found. Exiting the test")
             main.log.error(e)
             main.cleanAndExit()
 
-        # TODO: Move to a setup script
         run.initTest(main)
         main.log.info(main.Cluster.numCtrls)
         main.Cluster.setRunningNode(3)
         run.installOnos(main, skipPackage=True, cliSleep=5)
 
-        # Get the P4RT client connected to UP4 in the first available ONOS instance
-        up4Client = main.Cluster.active(0).p4rtUp4
-
-        s1u_address = main.params["UP4"]["s1u_address"]
-        enb_address = main.params["UP4"]["enb_address"]
-        router_mac = main.params["UP4"]["router_mac"]
-
-        pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
-        pdn_interface = pdn_host.interfaces[0]
-
-        enodeb_host = getattr(main, main.params["UP4"]["enodeb_host"])
-        enodeb_interface = enodeb_host.interfaces[0]
-
-        emulated_ues = main.params["UP4"]['ues']
-        n_ues = len(emulated_ues)
-
         main.step("Start scapy and p4rt client")
-        pdn_host.startScapy(ifaceName=pdn_interface["name"])
-        enodeb_host.startScapy(ifaceName=enodeb_interface["name"],
-                               enableGtp=True)
-        up4Client.startP4RtClient()
+        up4 = UP4()
+        # Get the P4RT client connected to UP4 in the first available ONOS instance
+        up4.setup(main.Cluster.active(0).p4rtUp4)
 
-        # TODO: move to library in dependencies
         main.step("Attach UEs")
-        for ue in emulated_ues.values():
-            # Sanitize values coming from the params file
-            if "five_g" in ue:
-                ue["five_g"] = bool(strtobool(ue["five_g"]))
-            if "qfi" in ue and ue["qfi"] == "":
-                ue["qfi"] = None
-            Up4LibCli.attachUe(up4Client, s1u_address=s1u_address,
-                               enb_address=enb_address,
-                               **ue)
+        up4.attachUes()
 
-        # ----------------- Test Upstream traffic (enb->pdn)
+        # ------- Test Upstream traffic (enb->pdn)
         main.step("Test upstream traffic")
-        # Scapy filter needs to start before sending traffic
-        pkt_filter_upstream = ""
-        for ue in 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"]
-        pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
-                              (PDN_PORT, pkt_filter_upstream,
-                               pdn_interface["ips"][0])
-        main.log.info("Start listening on %s intf %s" %
-                      (main.params["UP4"]["pdn_host"], pdn_interface["name"]))
-        main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
-        pdn_host.startFilter(ifaceName=pdn_interface["name"],
-                             sniffCount=n_ues,
-                             pktFilter=pkt_filter_upstream)
+        up4.testUpstreamTraffic()
 
-        main.log.info("Sending %d packets from eNodeB host" % len(emulated_ues))
-        for ue in emulated_ues.values():
-            enodeb_host.buildEther()
-            enodeb_host.buildIP(src=enb_address, dst=s1u_address)
-            enodeb_host.buildUDP(ipVersion=4, dport=GPDU_PORT)
-            # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
-            enodeb_host.buildGTP(gtp_type=0xFF, TEID=int(ue["teid"]))
-            enodeb_host.buildIP(overGtp=True, src=ue["ue_address"],
-                                dst=pdn_interface["ips"][0])
-            enodeb_host.buildUDP(ipVersion=4, overGtp=True, sport=UE_PORT,
-                                 dport=PDN_PORT)
-
-            enodeb_host.sendPacket(iface=enodeb_interface["name"])
-
-        finished = pdn_host.checkFilter()
-        packets = ""
-        if finished:
-            packets = pdn_host.readPackets(detailed=True)
-            for p in packets.splitlines():
-                main.log.debug(p)
-            # We care only of the last line from readPackets
-            packets = packets.splitlines()[-1]
-        else:
-            kill = pdn_host.killFilter()
-            main.log.debug(kill)
-
-        fail = False
-        if len(emulated_ues) != packets.count('Ether'):
-            fail = True
-            msg = "Failed to capture packets in PDN. "
-        else:
-            msg = "Correctly captured packet in PDN. "
-        # We expect exactly 1 packet per UE
-        pktsFiltered = [packets.count("src=" + ue["ue_address"])
-                        for ue in emulated_ues.values()]
-        if pktsFiltered.count(1) != len(pktsFiltered):
-            fail = True
-            msg += "More than one packet per UE in downstream. "
-        else:
-            msg += "One packet per UE in upstream. "
-
-        utilities.assert_equal(
-            expect=False, actual=fail, onpass=msg, onfail=msg)
-
-        # --------------- Test Downstream traffic (pdn->enb)
+        # ------- Test Downstream traffic (pdn->enb)
         main.step("Test downstream traffic")
-        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, enb_address, s1u_address)
-        main.log.info("Start listening on %s intf %s" % (
-            main.params["UP4"]["enodeb_host"], enodeb_interface["name"]))
-        main.log.debug("BPF Filter Downstream: \n %s" % pkt_filter_downstream)
-        enodeb_host.startFilter(ifaceName=enodeb_interface["name"],
-                                sniffCount=len(emulated_ues),
-                                pktFilter=pkt_filter_downstream)
+        up4.testDownstreamTraffic()
 
-        main.log.info("Sending %d packets from PDN host" % len(emulated_ues))
-        for ue in emulated_ues.values():
-            # From PDN we have to set dest MAC, otherwise scapy will do ARP
-            # request for the UE IP address.
-            pdn_host.buildEther(dst=router_mac)
-            pdn_host.buildIP(src=pdn_interface["ips"][0],
-                             dst=ue["ue_address"])
-            pdn_host.buildUDP(ipVersion=4, sport=PDN_PORT, dport=UE_PORT)
-            pdn_host.sendPacket(iface=pdn_interface["name"])
-
-        finished = enodeb_host.checkFilter()
-        packets = ""
-        if finished:
-            packets = enodeb_host.readPackets(detailed=True)
-            for p in packets.splitlines():
-                main.log.debug(p)
-            # We care only of the last line from readPackets
-            packets = packets.splitlines()[-1]
-        else:
-            kill = enodeb_host.killFilter()
-            main.log.debug(kill)
-
-        # 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 emulated_ues.values()]
-
-        fail = False
-        if len(emulated_ues) != sum(pktsFiltered):
-            fail = True
-            msg = "Failed to capture packets in eNodeB. "
-        else:
-            msg = "Correctly captured packets in eNodeB. "
-        # We expect exactly 1 packet per UE
-        if pktsFiltered.count(1) != len(pktsFiltered):
-            fail = True
-            msg += "More than one packet per GTP TEID in downstream. "
-        else:
-            msg += "One packet per GTP TEID in downstream. "
-
-        utilities.assert_equal(
-            expect=False, actual=fail, onpass=msg, onfail=msg)
-
-        # Detach UEs
         main.step("Detach UEs")
-        for ue in emulated_ues.values():
-            # No need to sanitize values, already sanitized during attachment
-            Up4LibCli.detachUe(up4Client, s1u_address=s1u_address,
-                               enb_address=enb_address,
-                               **ue)
+        up4.detachUes()
 
-        # Teardown
         main.step("Stop scapy and p4rt client")
-        enodeb_host.stopScapy()
-        pdn_host.stopScapy()
-        up4Client.stopP4RtClient()
+        up4.teardown()
         run.cleanup(main)
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/scapy_helper.py b/TestON/tests/USECASE/SegmentRouting/dependencies/scapy_helper.py
new file mode 100644
index 0000000..80546f4
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/scapy_helper.py
@@ -0,0 +1,88 @@
+from scapy.contrib.gtp import GTP_U_Header, GTPPDUSessionContainer
+from scapy.layers.inet import IP, UDP
+from scapy.layers.l2 import Ether
+import codecs
+
+UDP_GTP_PORT = 2152
+DEFAULT_GTP_TUNNEL_SPORT = 1234  # arbitrary, but different from 2152
+
+IP_HDR_BYTES = 20
+UDP_HDR_BYTES = 8
+GTPU_HDR_BYTES = 8
+GTPU_OPTIONS_HDR_BYTES = 4
+GTPU_EXT_PSC_BYTES = 4
+
+
+def simple_gtp_udp_packet(
+        eth_dst=None,
+        eth_src=None,
+        ip_src="192.168.0.1",
+        ip_dst="192.168.0.2",
+        s1u_addr="100.0.0.1",
+        enb_addr="192.168.101.1",
+        ip_ttl=64,
+        gtp_teid=0xFF,  # dummy teid
+        pktlen=136,
+        ext_psc_type=None,
+        ext_psc_qfi=0,
+):
+    pktlen = pktlen - IP_HDR_BYTES - UDP_HDR_BYTES - GTPU_HDR_BYTES
+    if ext_psc_type is not None:
+        pktlen = pktlen - GTPU_OPTIONS_HDR_BYTES - GTPU_EXT_PSC_BYTES
+    pkt = simple_udp_packet(eth_src=eth_src, eth_dst=eth_dst, ip_src=ip_src,
+                            ip_dst=ip_dst, pktlen=pktlen)
+    gtp_pkt = pkt_add_gtp(
+        pkt,
+        out_ipv4_src=enb_addr,
+        out_ipv4_dst=s1u_addr,
+        teid=gtp_teid,
+        ext_psc_type=ext_psc_type,
+        ext_psc_qfi=ext_psc_qfi,
+    )
+    gtp_pkt[Ether].src = eth_src
+    gtp_pkt[Ether].dst = eth_dst
+    gtp_pkt[IP].ttl = ip_ttl
+    return gtp_pkt
+
+
+def pkt_add_gtp(
+        pkt,
+        out_ipv4_src,
+        out_ipv4_dst,
+        teid,
+        sport=DEFAULT_GTP_TUNNEL_SPORT,
+        dport=UDP_GTP_PORT,
+        ext_psc_type=None,
+        ext_psc_qfi=None,
+):
+    gtp_pkt = (
+            Ether(src=pkt[Ether].src, dst=pkt[Ether].dst)
+            / IP(src=out_ipv4_src, dst=out_ipv4_dst, tos=0, id=0x1513, flags=0,
+                 frag=0, )
+            / UDP(sport=sport, dport=dport, chksum=0)
+            / GTP_U_Header(gtp_type=255, teid=teid)
+    )
+    if ext_psc_type is not None:
+        # Add QoS Flow Identifier (QFI) as an extension header (required for 5G RAN)
+        gtp_pkt = gtp_pkt / GTPPDUSessionContainer(type=ext_psc_type,
+                                                   QFI=ext_psc_qfi)
+    return gtp_pkt / pkt[Ether].payload
+
+
+# Simplified version of simple_udp_packet from https://github.com/p4lang/ptf/blob/master/src/ptf/testutils.py
+def simple_udp_packet(
+        pktlen=100,
+        eth_dst="00:01:02:03:04:05",
+        eth_src="00:06:07:08:09:0a",
+        ip_src="192.168.0.1",
+        ip_dst="192.168.0.2",
+        udp_sport=1234,
+        udp_dport=80,
+        udp_payload=None,
+):
+    pkt = Ether(src=eth_src, dst=eth_dst) / IP(src=ip_src, dst=ip_dst) / UDP(
+        sport=udp_sport, dport=udp_dport)
+    if udp_payload:
+        pkt = pkt / udp_payload
+    return pkt / codecs.decode(
+        "".join(["%02x" % (x % 256) for x in range(pktlen - len(pkt))]), "hex")
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/trex.py b/TestON/tests/USECASE/SegmentRouting/dependencies/trex.py
new file mode 100644
index 0000000..9245f16
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/trex.py
@@ -0,0 +1,186 @@
+from distutils.util import strtobool
+from tests.USECASE.SegmentRouting.dependencies import scapy_helper
+
+
+class Trex:
+    """
+    Utility that manages interaction with TRex server via TRexDriver component
+    Example params:
+    <TREX>
+        <port_stats>0,1</port_stats>
+        <flows>
+            <RT_FROM_UE>
+                <name>Real Time</name>
+                <l1_bps>40000000</l1_bps>
+                <trex_port>0</trex_port>
+                <packet>
+                    <pktlen>1400</pktlen>
+                    <ip_src>10.240.0.2</ip_src>
+                    <ip_dst>10.32.11.101</ip_dst>
+                    <eth_src>3C:EC:EF:3E:0B:A0</eth_src>
+                    <eth_dst>00:00:0A:4C:1C:46</eth_dst>
+                    <gtp_teid>200</gtp_teid>
+                    <s1u_addr>10.32.11.126</s1u_addr>
+                    <enb_addr>10.32.11.100</enb_addr>
+                </packet>
+                <latency_stats>true</latency_stats>
+                <flow_id>10</flow_id> <!-- Mandatory when latency_stats=true -->
+                <delay>50000</delay> <!-- wait 50 ms till start to let queues fill up -->
+                <expected_min_received>1</expected_min_received>
+                <expected_max_dropped>0</expected_max_dropped>
+                <expected_max_latency>1500</expected_max_latency>
+                <expected_99_9_percentile_latency>100</expected_99_9_percentile_latency>
+            </RT_FROM_UE>
+        </flows>
+    <TREX>
+    """
+
+    def __init__(self):
+        self.trex_client = None
+        self.traffic_flows = {}
+        self.port_stats = []
+        self.packets = {}  # Per-flow dictionary of packets
+
+    def setup(self, trex_client):
+        self.trex_client = trex_client
+        self.traffic_flows = main.params["TREX"]["flows"]
+        if "port_stats" in main.params["TREX"] and \
+                main.params["TREX"].get("port_stats") is not '':
+            self.port_stats = [int(p) for p in
+                               main.params["TREX"].get("port_stats").split(",")]
+        self.trex_client.setupTrex(main.configPath)
+
+    def teardown(self):
+        self.trex_client.stopTrexServer()
+
+    def createFlow(self, flow_name):
+        if flow_name not in self.traffic_flows:
+            main.log.error("CFG flow not present in params")
+            return False
+        self.traffic_flows[flow_name]["packet"] = Trex.__sanitizePacketConfig(
+            self.traffic_flows[flow_name]["packet"])
+        if "gtp_teid" in self.traffic_flows[flow_name]["packet"]:
+            # packets must be GTP encapped
+            self.packets[flow_name] = scapy_helper.simple_gtp_udp_packet(
+                **self.traffic_flows[flow_name]["packet"])
+        else:
+            self.packets[flow_name] = scapy_helper.simple_udp_packet(
+                **self.traffic_flows[flow_name]["packet"])
+
+    def sendAndReceiveTraffic(self, duration):
+        """
+        Connect the client, create the flows in trex (with packets created with
+        createFlow, send and receive the traffic, and disconnect the client.
+        :param duration: traffic duration
+        :return:
+        """
+        self.trex_client.connectTrexClient()
+        for flow_name, packet in self.packets.items():
+            flow_config = self.traffic_flows[flow_name]
+            Trex.__sanitizeFlowConfig(flow_config)
+            self.trex_client.addStream(pkt=packet,
+                                       trex_port=flow_config["trex_port"],
+                                       l1_bps=flow_config["l1_bps"],
+                                       percentage=flow_config["percentage"],
+                                       delay=flow_config["delay"],
+                                       flow_id=flow_config["flow_id"],
+                                       flow_stats=flow_config["latency_stats"])
+        self.trex_client.startAndWaitTraffic(duration=duration)
+        self.trex_client.disconnectTrexClient()
+
+    def assertRxPackets(self, flow_name):
+        if not self.isFlowStats(flow_name):
+            main.log.info("No flow stats for flow {}".format(flow_name))
+        expected_min_received = int(
+            self.traffic_flows[flow_name].get("expected_min_received", "1"))
+        flow_id = self.traffic_flows[flow_name]["flow_id"]
+        flow_stats = self.trex_client.getFlowStats(flow_id)
+        utilities.assert_equals(
+            expect=True,
+            actual=flow_stats.rx_packets >= expected_min_received,
+            onpass="Traffic Flow {}: Received traffic".format(flow_name),
+            onfail="Traffic Flow {}: No traffic received".format(flow_name))
+
+    def assertDroppedPacket(self, flow_name):
+        if not self.isFlowStats(flow_name):
+            main.log.info("No flow stats for flow {}".format(flow_name))
+        expected_max_dropped = int(
+            self.traffic_flows[flow_name].get("expected_max_dropped", "0"))
+        latency_stats = self.__getLatencyStats(flow_name)
+        utilities.assert_equals(
+            expect=True,
+            actual=latency_stats.dropped <= expected_max_dropped,
+            onpass="Traffic Flow {}: {} packets dropped, below threshold ({})".format(
+                flow_name, latency_stats.dropped,
+                expected_max_dropped),
+            onfail="Traffic Flow {}: {} packets dropped, above threshold ({})".format(
+                flow_name, latency_stats.dropped,
+                expected_max_dropped))
+
+    def assertMaxLatency(self, flow_name):
+        if not self.isFlowStats(flow_name):
+            main.log.info("No flow stats for flow {}".format(flow_name))
+        expected_max_latency = int(
+            self.traffic_flows[flow_name].get("expected_max_latency", "0"))
+        latency_stats = self.__getLatencyStats(flow_name)
+        utilities.assert_equals(
+            expect=True,
+            actual=latency_stats.total_max <= expected_max_latency,
+            onpass="Traffic Flow {}: Maximum latency below threshold".format(
+                flow_name),
+            onfail="Traffic Flow {}: Maximum latency is too high {}".format(
+                flow_name, latency_stats.total_max))
+
+    def assert99_9PercentileLatency(self, flow_name):
+        if not self.isFlowStats(flow_name):
+            main.log.info("No flow stats for flow {}".format(flow_name))
+        expected_99_9_percentile_latency = int(
+            self.traffic_flows[flow_name].get(
+                "expected_99_9_percentile_latency", "0"))
+        latency_stats = self.__getLatencyStats(flow_name)
+        utilities.assert_equals(
+            expect=True,
+            actual=latency_stats.percentile_99_9 <= expected_99_9_percentile_latency,
+            onpass="Traffic Flow {}: 99.9th percentile latency below threshold".format(
+                flow_name),
+            onfail="Traffic Flow {}: 99.9th percentile latency is too high {}".format(
+                flow_name, latency_stats.percentile_99_9))
+
+    def logPortStats(self):
+        main.log.debug(self.port_stats)
+        for port in self.port_stats:
+            self.trex_client.logPortStats(port)
+
+    def logFlowStats(self, flow_name):
+        if self.isFlowStats(flow_name):
+            flow_id = self.traffic_flows[flow_name]["flow_id"]
+            self.trex_client.logFlowStats(flow_id)
+            self.trex_client.logLatencyStats(flow_id)
+
+    def isFlowStats(self, flow_name):
+        return self.traffic_flows[flow_name]["latency_stats"]
+
+    def __getLatencyStats(self, flow_name):
+        flow_id = self.traffic_flows[flow_name]["flow_id"]
+        return self.trex_client.getLatencyStats(flow_id)
+
+    @staticmethod
+    def __sanitizePacketConfig(packet):
+        if "gtp_teid" in packet.keys():
+            packet["gtp_teid"] = int(packet["gtp_teid"])
+        if "pktlen" in packet.keys():
+            packet["pktlen"] = int(packet["pktlen"])
+        return packet
+
+    @staticmethod
+    def __sanitizeFlowConfig(flow_config):
+        flow_config["trex_port"] = int(flow_config["trex_port"])
+        flow_config["percentage"] = float(
+            flow_config["percentage"]) if "percentage" in flow_config else None
+        flow_config["l1_bps"] = float(
+            flow_config["l1_bps"]) if "l1_bps" in flow_config else None
+        flow_config["delay"] = int(flow_config.get("delay", 0))
+        flow_config["flow_id"] = int(
+            flow_config["flow_id"]) if "flow_id" in flow_config else None
+        flow_config["latency_stats"] = bool(
+            strtobool(flow_config.get("latency_stats", "False")))
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
new file mode 100644
index 0000000..dd1c72a
--- /dev/null
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
@@ -0,0 +1,408 @@
+from distutils.util import strtobool
+
+FALSE = '0'
+TRUE = '1'
+DIR_UPLINK = '1'
+DIR_DOWNLINK = '2'
+IFACE_ACCESS = '1'
+IFACE_CORE = '2'
+TUNNEL_SPORT = '2152'
+TUNNEL_TYPE_GPDU = '3'
+
+UE_PORT = 400
+PDN_PORT = 800
+GPDU_PORT = 2152
+
+
+class UP4:
+    """
+    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>,
+    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
+        <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>
+                <ue_address>10.240.0.2</ue_address>
+                <teid>200</teid>
+                <up_id>20</up_id>
+                <down_id>21</down_id>
+                <qfi>2</qfi>
+                <five_g>False</five_g>
+            </ue2>
+        </ues>
+    </UP4>
+    """
+
+    def __init__(self):
+        self.s1u_address = None
+        self.enb_address = None
+        self.enodeb_host = None
+        self.enodeb_interface = None
+        self.pdn_host = None
+        self.pdn_interface = None
+        self.router_mac = None
+        self.emulated_ues = []
+        self.up4_client = None
+
+    def setup(self, p4rt_client):
+        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
+
+        # Optional Parameters
+        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)
+
+        # 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"])
+
+    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()
+
+    def attachUes(self):
+        for ue in self.emulated_ues.values():
+            # Sanitize values coming from the params file
+            ue = UP4.__sanitizeUeData(ue)
+            self.attachUe(**ue)
+
+    def detachUes(self):
+        for ue in self.emulated_ues.values():
+            # No need to sanitize, has already been done in attach
+            self.detachUe(**ue)
+
+    def testUpstreamTraffic(self):
+        if self.enodeb_host 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
+        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"]
+        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])
+        main.log.info("Start listening on %s intf %s" %
+                      (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),
+                                  pktFilter=pkt_filter_upstream)
+
+        main.log.info(
+            "Sending %d packets from eNodeB host" % len(self.emulated_ues))
+        for ue in self.emulated_ues.values():
+            self.enodeb_host.buildEther()
+            self.enodeb_host.buildIP(src=self.enb_address, dst=self.s1u_address)
+            self.enodeb_host.buildUDP(ipVersion=4, dport=GPDU_PORT)
+            # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
+            self.enodeb_host.buildGTP(gtp_type=0xFF, TEID=int(ue["teid"]))
+            self.enodeb_host.buildIP(overGtp=True, src=ue["ue_address"],
+                                     dst=self.pdn_interface["ips"][0])
+            self.enodeb_host.buildUDP(ipVersion=4, overGtp=True, sport=UE_PORT,
+                                      dport=PDN_PORT)
+
+            self.enodeb_host.sendPacket(iface=self.enodeb_interface["name"])
+
+        finished = self.pdn_host.checkFilter()
+        packets = ""
+        if finished:
+            packets = self.pdn_host.readPackets(detailed=True)
+            for p in packets.splitlines():
+                main.log.debug(p)
+            # We care only of the last line from readPackets
+            packets = packets.splitlines()[-1]
+        else:
+            kill = self.pdn_host.killFilter()
+            main.log.debug(kill)
+        fail = False
+        if len(self.emulated_ues) != packets.count('Ether'):
+            fail = True
+            msg = "Failed to capture packets in PDN. "
+        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()]
+        if pktsFiltered.count(1) != len(pktsFiltered):
+            fail = True
+            msg += "More than one packet per UE in downstream. "
+        else:
+            msg += "One packet per UE in upstream. "
+
+        utilities.assert_equal(
+            expect=False, actual=fail, onpass=msg, onfail=msg)
+
+    def testDownstreamTraffic(self):
+        if self.enodeb_host 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)
+
+        main.log.info(
+            "Sending %d packets from PDN host" % len(self.emulated_ues))
+        for ue in self.emulated_ues.values():
+            # From PDN we have to set dest MAC, otherwise scapy will do ARP
+            # request for the UE IP address.
+            self.pdn_host.buildEther(dst=self.router_mac)
+            self.pdn_host.buildIP(src=self.pdn_interface["ips"][0],
+                                  dst=ue["ue_address"])
+            self.pdn_host.buildUDP(ipVersion=4, sport=PDN_PORT, dport=UE_PORT)
+            self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
+
+        finished = self.enodeb_host.checkFilter()
+        packets = ""
+        if finished:
+            packets = self.enodeb_host.readPackets(detailed=True)
+            for p in packets.splitlines():
+                main.log.debug(p)
+            # We care only of the last line from readPackets
+            packets = packets.splitlines()[-1]
+        else:
+            kill = self.enodeb_host.killFilter()
+            main.log.debug(kill)
+
+        # 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()]
+
+        fail = False
+        if len(self.emulated_ues) != sum(pktsFiltered):
+            fail = True
+            msg = "Failed to capture packets in eNodeB. "
+        else:
+            msg = "Correctly captured packets in eNodeB. "
+        # We expect exactly 1 packet per UE
+        if pktsFiltered.count(1) != len(pktsFiltered):
+            fail = True
+            msg += "More than one packet per GTP TEID in downstream. "
+        else:
+            msg += "One packet per GTP TEID in downstream. "
+
+        utilities.assert_equal(
+            expect=False, actual=fail, onpass=msg, onfail=msg)
+
+    @staticmethod
+    def __sanitizeUeData(ue):
+        if "five_g" in ue:
+            ue["five_g"] = bool(strtobool(ue["five_g"]))
+        if "qfi" in ue and ue["qfi"] == "":
+            ue["qfi"] = None
+        return ue
+
+    def attachUe(self, 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,
+                               ue_address,
+                               teid, up_id, down_id,
+                               teid_up, teid_down,
+                               pdr_id_up, far_id_up, ctr_id_up,
+                               pdr_id_down, far_id_down, ctr_id_down,
+                               qfi, five_g, action="program")
+
+    def detachUe(self, 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,
+                               ue_address,
+                               teid, up_id, down_id,
+                               teid_up, teid_down,
+                               pdr_id_up, far_id_up, ctr_id_up,
+                               pdr_id_down, far_id_down, ctr_id_down,
+                               qfi, five_g, action="clear")
+
+    def __programUp4Rules(self, 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, action="program"):
+        if up_id is not None:
+            pdr_id_up = up_id
+            far_id_up = up_id
+            ctr_id_up = up_id
+        if down_id is not None:
+            pdr_id_down = down_id
+            far_id_down = down_id
+            ctr_id_down = down_id
+        if teid is not None:
+            teid_up = teid
+            teid_down = teid
+
+        entries = []
+
+        # ========================#
+        # PDR Entries
+        # ========================#
+
+        # Uplink
+        tableName = 'PreQosPipe.pdrs'
+        actionName = ''
+        matchFields = {}
+        actionParams = {}
+        if qfi is None:
+            actionName = 'PreQosPipe.set_pdr_attributes'
+        else:
+            actionName = 'PreQosPipe.set_pdr_attributes_qos'
+            if five_g:
+                # TODO: currently QFI_MATCH is unsupported in TNA
+                matchFields['has_qfi'] = TRUE
+                matchFields["qfi"] = str(qfi)
+            actionParams['needs_qfi_push'] = FALSE
+            actionParams['qfi'] = str(qfi)
+        # Match fields
+        matchFields['src_iface'] = IFACE_ACCESS
+        matchFields['ue_addr'] = str(ue_address)
+        matchFields['teid'] = str(teid_up)
+        matchFields['tunnel_ipv4_dst'] = str(self.s1u_address)
+        # Action params
+        actionParams['id'] = str(pdr_id_up)
+        actionParams['fseid'] = str(pfcp_session_id)
+        actionParams['ctr_id'] = str(ctr_id_up)
+        actionParams['far_id'] = str(far_id_up)
+        actionParams['needs_gtpu_decap'] = TRUE
+        if not self.__add_entry(tableName, actionName, matchFields,
+                                actionParams, entries, action):
+            return False
+
+        # Downlink
+        tableName = 'PreQosPipe.pdrs'
+        matchFields = {}
+        actionParams = {}
+        if qfi is None:
+            actionName = 'PreQosPipe.set_pdr_attributes'
+        else:
+            actionName = 'PreQosPipe.set_pdr_attributes_qos'
+            # TODO: currently QFI_PUSH is unsupported in TNA
+            actionParams['needs_qfi_push'] = TRUE if five_g else FALSE
+            actionParams['qfi'] = str(qfi)
+        # Match fields
+        matchFields['src_iface'] = IFACE_CORE
+        matchFields['ue_addr'] = str(ue_address)
+        # Action params
+        actionParams['id'] = str(pdr_id_down)
+        actionParams['fseid'] = str(pfcp_session_id)
+        actionParams['ctr_id'] = str(ctr_id_down)
+        actionParams['far_id'] = str(far_id_down)
+        actionParams['needs_gtpu_decap'] = FALSE
+        if not self.__add_entry(tableName, actionName, matchFields,
+                                actionParams, entries, action):
+            return False
+
+        # ========================#
+        # FAR Entries
+        # ========================#
+
+        # Uplink
+        tableName = 'PreQosPipe.load_far_attributes'
+        actionName = 'PreQosPipe.load_normal_far_attributes'
+        matchFields = {}
+        actionParams = {}
+
+        # Match fields
+        matchFields['far_id'] = str(far_id_up)
+        matchFields['session_id'] = str(pfcp_session_id)
+        # Action params
+        actionParams['needs_dropping'] = FALSE
+        actionParams['notify_cp'] = FALSE
+        if not self.__add_entry(tableName, actionName, matchFields,
+                                actionParams, entries, action):
+            return False
+
+        # Downlink
+        tableName = 'PreQosPipe.load_far_attributes'
+        actionName = 'PreQosPipe.load_tunnel_far_attributes'
+        matchFields = {}
+        actionParams = {}
+
+        # Match fields
+        matchFields['far_id'] = str(far_id_down)
+        matchFields['session_id'] = str(pfcp_session_id)
+        # Action params
+        actionParams['needs_dropping'] = FALSE
+        actionParams['notify_cp'] = FALSE
+        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['teid'] = str(teid_down)
+        actionParams['sport'] = TUNNEL_SPORT
+        if not self.__add_entry(tableName, actionName, matchFields,
+                                actionParams, entries, action):
+            return False
+        if action == "program":
+            main.log.info("All entries added successfully.")
+        elif action == "clear":
+            self.__clear_entries(entries)
+
+    def __add_entry(self, tableName, actionName, matchFields, actionParams,
+                    entries, action):
+        if action == "program":
+            self.up4_client.buildP4RtTableEntry(
+                tableName=tableName, actionName=actionName,
+                actionParams=actionParams, matchFields=matchFields)
+            if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
+                main.log.info("*** Entry added.")
+            else:
+                main.log.error("Error during table insertion")
+                self.__clear_entries(entries)
+                return False
+        entries.append({"tableName": tableName, "actionName": actionName,
+                        "matchFields": matchFields,
+                        "actionParams": actionParams})
+        return True
+
+    def __clear_entries(self, entries):
+        for i, entry in enumerate(entries):
+            self.up4_client.buildP4RtTableEntry(**entry)
+            if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
+                main.log.info(
+                    "*** Entry %d of %d deleted." % (i + 1, len(entries)))
+            else:
+                main.log.error("Error during table delete")
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/up4libcli.py b/TestON/tests/USECASE/SegmentRouting/dependencies/up4libcli.py
deleted file mode 100644
index 73dd39b..0000000
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/up4libcli.py
+++ /dev/null
@@ -1,213 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Copyright 2021 Open Networking Foundation (ONF)
-
-Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
-the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
-or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
-
-"""
-
-FALSE = '0'
-TRUE = '1'
-DIR_UPLINK = '1'
-DIR_DOWNLINK = '2'
-IFACE_ACCESS = '1'
-IFACE_CORE = '2'
-TUNNEL_SPORT = '2152'
-TUNNEL_TYPE_GPDU = '3'
-
-
-class Up4LibCli():
-    """
-    Helper library to attach and detach UEs via UP4 P4Runtime APIs.
-    """
-
-    @staticmethod
-    def attachUe(p4rtCli, s1u_address, enb_address, 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):
-        Up4LibCli.__programUp4Rules(p4rtCli, s1u_address, enb_address,
-                                 pfcp_session_id,
-                                 ue_address,
-                                 teid, up_id, down_id,
-                                 teid_up, teid_down,
-                                 pdr_id_up, far_id_up, ctr_id_up,
-                                 pdr_id_down, far_id_down, ctr_id_down,
-                                 qfi, five_g, action="program")
-
-    @staticmethod
-    def detachUe(p4rtCli, s1u_address, enb_address, 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):
-        Up4LibCli.__programUp4Rules(p4rtCli, s1u_address, enb_address,
-                                 pfcp_session_id,
-                                 ue_address,
-                                 teid, up_id, down_id,
-                                 teid_up, teid_down,
-                                 pdr_id_up, far_id_up, ctr_id_up,
-                                 pdr_id_down, far_id_down, ctr_id_down,
-                                 qfi, five_g, action="clear")
-
-    @staticmethod
-    def __programUp4Rules(p4rtCli, s1u_address, enb_address, 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, action="program"):
-        if up_id is not None:
-            pdr_id_up = up_id
-            far_id_up = up_id
-            ctr_id_up = up_id
-        if down_id is not None:
-            pdr_id_down = down_id
-            far_id_down = down_id
-            ctr_id_down = down_id
-        if teid is not None:
-            teid_up = teid
-            teid_down = teid
-
-        entries = []
-
-        # ========================#
-        # PDR Entries
-        # ========================#
-
-        # Uplink
-        tableName = 'PreQosPipe.pdrs'
-        actionName = ''
-        matchFields = {}
-        actionParams = {}
-        if qfi is None:
-            actionName = 'PreQosPipe.set_pdr_attributes'
-        else:
-            actionName = 'PreQosPipe.set_pdr_attributes_qos'
-            if five_g:
-                # TODO: currently QFI_MATCH is unsupported in TNA
-                matchFields['has_qfi'] = TRUE
-                matchFields["qfi"] = str(qfi)
-            actionParams['needs_qfi_push'] = FALSE
-            actionParams['qfi'] = str(qfi)
-        # Match fields
-        matchFields['src_iface'] = IFACE_ACCESS
-        matchFields['ue_addr'] = str(ue_address)
-        matchFields['teid'] = str(teid_up)
-        matchFields['tunnel_ipv4_dst'] = str(s1u_address)
-        # Action params
-        actionParams['id'] = str(pdr_id_up)
-        actionParams['fseid'] = str(pfcp_session_id)
-        actionParams['ctr_id'] = str(ctr_id_up)
-        actionParams['far_id'] = str(far_id_up)
-        actionParams['needs_gtpu_decap'] = TRUE
-        if not Up4LibCli.__add_entry(p4rtCli, tableName, actionName, matchFields,
-                                  actionParams, entries, action):
-            return False
-
-        # Downlink
-        tableName = 'PreQosPipe.pdrs'
-        actionName = ''
-        matchFields = {}
-        actionParams = {}
-        if qfi is None:
-            actionName = 'PreQosPipe.set_pdr_attributes'
-        else:
-            actionName = 'PreQosPipe.set_pdr_attributes_qos'
-            # TODO: currently QFI_PUSH is unsupported in TNA
-            actionParams['needs_qfi_push'] = TRUE if five_g else FALSE
-            actionParams['qfi'] = str(qfi)
-        # Match fields
-        matchFields['src_iface'] = IFACE_CORE
-        matchFields['ue_addr'] = str(ue_address)
-        # Action params
-        actionParams['id'] = str(pdr_id_down)
-        actionParams['fseid'] = str(pfcp_session_id)
-        actionParams['ctr_id'] = str(ctr_id_down)
-        actionParams['far_id'] = str(far_id_down)
-        actionParams['needs_gtpu_decap'] = FALSE
-        if not Up4LibCli.__add_entry(p4rtCli, tableName, actionName, matchFields,
-                                  actionParams, entries, action):
-            return False
-
-        # ========================#
-        # FAR Entries
-        # ========================#
-
-        # Uplink
-        tableName = 'PreQosPipe.load_far_attributes'
-        actionName = 'PreQosPipe.load_normal_far_attributes'
-        matchFields = {}
-        actionParams = {}
-
-        # Match fields
-        matchFields['far_id'] = str(far_id_up)
-        matchFields['session_id'] = str(pfcp_session_id)
-        # Action params
-        actionParams['needs_dropping'] = FALSE
-        actionParams['notify_cp'] = FALSE
-        if not Up4LibCli.__add_entry(p4rtCli, tableName, actionName, matchFields,
-                                  actionParams, entries, action):
-            return False
-
-        # Downlink
-        tableName = 'PreQosPipe.load_far_attributes'
-        actionName = 'PreQosPipe.load_tunnel_far_attributes'
-        matchFields = {}
-        actionParams = {}
-
-        # Match fields
-        matchFields['far_id'] = str(far_id_down)
-        matchFields['session_id'] = str(pfcp_session_id)
-        # Action params
-        actionParams['needs_dropping'] = FALSE
-        actionParams['notify_cp'] = FALSE
-        actionParams['needs_buffering'] = FALSE
-        actionParams['tunnel_type'] = TUNNEL_TYPE_GPDU
-        actionParams['src_addr'] = str(s1u_address)
-        actionParams['dst_addr'] = str(enb_address)
-        actionParams['teid'] = str(teid_down)
-        actionParams['sport'] = TUNNEL_SPORT
-        if not Up4LibCli.__add_entry(p4rtCli, tableName, actionName, matchFields,
-                                  actionParams, entries, action):
-            return False
-
-        if action == "program":
-            main.log.info("All entries added successfully.")
-        elif action == "clear":
-            Up4LibCli.__clear_entries(p4rtCli, entries)
-
-    @staticmethod
-    def __add_entry(p4rtCli, tableName, actionName, matchFields, actionParams,
-                    entries, action):
-        if action == "program":
-            p4rtCli.buildP4RtTableEntry(tableName=tableName,
-                                        actionName=actionName,
-                                        actionParams=actionParams,
-                                        matchFields=matchFields)
-            if p4rtCli.pushTableEntry(debug=True) == main.TRUE:
-                main.log.info("*** Entry added.")
-            else:
-                main.log.error("Error during table insertion")
-                Up4LibCli.__clear_entries(p4rtCli, entries)
-                return False
-        entries.append({"tableName": tableName, "actionName": actionName,
-                        "matchFields": matchFields,
-                        "actionParams": actionParams})
-        return True
-
-    @staticmethod
-    def __clear_entries(p4rtCli, entries):
-        for i, entry in enumerate(entries):
-            p4rtCli.buildP4RtTableEntry(**entry)
-            if p4rtCli.deleteTableEntry(debug=True) == main.TRUE:
-                main.log.info("*** Entry %d of %d deleted." % (i + 1, len(entries)))
-            else:
-                main.log.error("Error during table delete")