diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index f95f733fc..dd9337b36 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -32,6 +32,18 @@ jobs: - name: Build the PFCP Agent Docker image run: DOCKER_TARGETS=pfcpiface make docker-build + build-ptf: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./ptf + steps: + - uses: actions/checkout@v4 + + - name: Build PTF image + run: make build + lint: name: lint runs-on: ubuntu-latest diff --git a/README.md b/README.md index 12cd09d47..a3825b06c 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ registry: [upf-epc-bess](https://hub.docker.com/r/omecproject/upf-epc-bess), * GTPu path monitoring * Network Token Functions (_**experimental**_) * Support for DPDK, CNDP, AF_PACKET and AF_XDP modes + - BESS uses DPDK 22.11.4 ### P4-UPF P4-UPF implements a core set of features capable of supporting requirements for diff --git a/ptf/Dockerfile b/ptf/Dockerfile index cde5da211..ef4ed2e17 100644 --- a/ptf/Dockerfile +++ b/ptf/Dockerfile @@ -9,11 +9,9 @@ ARG TREX_VER=2.92-scapy-2.4.5 ARG TREX_EXT_LIBS=/external_libs ARG TREX_LIBS=/trex_python ARG UNITTEST_XML_REPORTING_VER=3.0.4 -ARG PROTOBUF_VER=3.12 -ARG GRPC_VER=1.26 - +ARG PROTOBUF_VER=3.20 # Install dependencies for general PTF test definitions -FROM ubuntu:20.04 as ptf-deps +FROM ubuntu:22.04 as ptf-deps ARG SCAPY_VER ARG PTF_VER @@ -21,22 +19,21 @@ ARG UNITTEST_XML_REPORTING_VER ARG PROTOBUF_VER ARG GRPC_VER -ENV RUNTIME_DEPS \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ python3 \ python3-pip \ python3-setuptools \ - git + git && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* -ENV PIP_DEPS \ +RUN pip3 install --no-cache-dir --root /python_output \ git+https://github.com/p4lang/ptf@$PTF_VER \ protobuf==$PROTOBUF_VER \ - grpcio==$GRPC_VER \ + grpcio \ unittest-xml-reporting==$UNITTEST_XML_REPORTING_VER -RUN apt update && \ - apt install -y $RUNTIME_DEPS -RUN pip3 install --no-cache-dir --root /python_output $PIP_DEPS - # Install TRex traffic gen and library for TRex API FROM alpine:3.19.1 as trex-builder ARG TREX_VER @@ -45,8 +42,8 @@ ARG TREX_LIBS ENV TREX_SCRIPT_DIR=/trex-core-${TREX_VER}/scripts -RUN apk update && apk add --no-cache -U wget -RUN wget https://github.com/stratum/trex-core/archive/v${TREX_VER}.zip && \ +RUN apk update && apk add --no-cache -U wget && \ + wget https://github.com/stratum/trex-core/archive/v${TREX_VER}.zip && \ unzip -qq v${TREX_VER}.zip && \ mkdir -p /output/${TREX_EXT_LIBS} && \ mkdir -p /output/${TREX_LIBS} && \ @@ -55,13 +52,14 @@ RUN wget https://github.com/stratum/trex-core/archive/v${TREX_VER}.zip && \ cp -r ${TREX_SCRIPT_DIR}/automation/trex_control_plane/stf/trex_stf_lib /output/${TREX_LIBS} # Synthesize all dependencies for runtime -FROM ubuntu:20.04 +FROM ubuntu:22.04 ARG TREX_EXT_LIBS ARG TREX_LIBS ARG SCAPY_VER -ENV RUNTIME_DEPS \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ make \ net-tools \ python3 \ @@ -73,12 +71,14 @@ ENV RUNTIME_DEPS \ build-essential \ python3-pip \ wget \ - netbase - -RUN apt update && \ - apt install -y $RUNTIME_DEPS && \ + netbase && \ + apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN pip3 install --no-cache-dir scipy==1.5.4 numpy==1.19.4 matplotlib==3.3.3 pyyaml==5.4.1 +RUN pip3 install --no-cache-dir \ + scipy \ + numpy \ + matplotlib \ + pyyaml ENV TREX_EXT_LIBS=${TREX_EXT_LIBS} ENV PYTHONPATH=${TREX_EXT_LIBS}:${TREX_LIBS} @@ -87,7 +87,9 @@ COPY --from=trex-builder /output / COPY --from=ptf-deps /python_output / # Install custom scapy version from TRex so PTF tests can access certain scapy features -RUN cd ${TREX_EXT_LIBS}/scapy-${SCAPY_VER}/ && python3 setup.py install -RUN ldconfig +# TODO: Move to a newer Scapy version compatible with latest Ubuntu versions +WORKDIR ${TREX_EXT_LIBS}/scapy-${SCAPY_VER} +RUN python3 setup.py install \ + && ldconfig ENTRYPOINT [] diff --git a/ptf/README.md b/ptf/README.md index 5fc73aaa3..9b4715b14 100644 --- a/ptf/README.md +++ b/ptf/README.md @@ -73,46 +73,40 @@ hosting TRex, where results are asserted. ## Steps to run tests The run script assumes that the TRex daemon server and the UPF -instance are already running on their respective machines. It also -assumes that all the following config files are configured correctly to -route traffic to the UPF and vice versa. -* `upf/scripts/docker_setup.sh` script updated with proper values for - `ifaces`, `macaddrs`, `nhmacaddrs` parameters. For reference - please refer `upf/ptf/config/docker_setup.sh` file -* `upf/conf/upf.jsonc` file updated with proper values for - `measure_flow`, `measure_upf` parameters. For reference - please refer `upf/ptf/config/upf.jsonc` file +instance are already running on their respective machines. Please see +[here](../docs/INSTALL.md#configuration-dpdk-mode) for instructions to deploy +the UPF in DPDK mode. Note that the following additional changes are required +in the `conf/upf.jsonc` file: `"measure_flow": true`, N3 interface set to +`"ifname": "access"` and N6 interface set to `"ifname": "core"`. +To install TRex onto your server, please refer to the +[TRex installation guide](https://trex-tgn.cisco.com/trex/doc/trex_manual.html#_download_and_installation) + +### Steps +1. Update the following files accordingly to route traffic to the UPF and vice versa. * `upf/ptf/.env` file updated with `UPF_ADDR` and `TREX_ADDR` parameters * `upf/ptf/config/trex-cfg-for-ptf.yaml` file updated with proper values for `interfaces`, `port_info`, and `platform` parameters -* `upf/ptf/tests/linerate/baseline.py` file updated with proper values for - `TREX_SRC_MAC` and `UPF_DEST_MAC` -* `upf/ptf/tests/linerate/mbr.py` file updated with proper values for - `BESS_CORE_MAC` and `BESS_ACCESS_MAC` -* `upf/ptf/tests/linerate/qos_metrics.py` file updated with proper values for - `UPF_DEST_MAC` +* `upf/ptf/tests/linerate/common.py` file updated with proper MAC address values + for `TREX_SRC_MAC`, `UPF_CORE_MAC`, and `UPF_ACCESS_MAC` -To install TRex onto your server, please refer to the [TRex installation -guide](https://trex-tgn.cisco.com/trex/doc/trex_manual.html#_download_and_installation) - -### Steps -1. Generate BESS Python protobuf files for gRPC library and PTF -Dockerfile image build dependencies: +2. Generate BESS Python protobuf files for gRPC library and PTF Dockerfile image + build dependencies: ```bash make build ``` -2. Run PTF tests using the `run_tests` script: +3. Run PTF tests using the `run_tests` script: ```bash -sudo ./run_tests -t [test-dir] [optional: filename/filename.test_case] +./run_tests -t [test-dir] [optional: filename/filename.test_case] ``` ### Examples To run all test cases in the `unary/` directory: ```bash -sudo ./run_tests -t tests/unary +./run_tests -t tests/unary ``` To run a specific test case: ```bash -sudo ./run_tests -t tests/linerate/ baseline.DownlinkPerformanceBaselineTest -sudo ./run_tests -t tests/linerate/ mbr -sudo ./run_tests -t tests/linerate/ qos_metrics +./run_tests -t tests/linerate/ baseline.DownlinkPerformanceBaselineTest +./run_tests -t tests/linerate/ mbr +./run_tests -t tests/linerate/ qos_metrics ``` +Note: If the above fails, `sudo` may be needed diff --git a/ptf/lib/ptf_runner.py b/ptf/lib/ptf_runner.py index ef67d8640..69198e600 100644 --- a/ptf/lib/ptf_runner.py +++ b/ptf/lib/ptf_runner.py @@ -114,7 +114,7 @@ def set_up_trex_server(trex_daemon_client, trex_address, trex_config): trex_daemon_client.start_stateless(cfg=trex_config_file_on_server) except ConnectionRefusedError: error( - "Unable to connect to server %s.\n" + "Did you start the Trex daemon?", + "Unable to connect to server %s. Did you start the Trex daemon?", trex_address, ) return False diff --git a/ptf/tests/linerate/baseline.py b/ptf/tests/linerate/baseline.py index c4f8e77ea..e2104d0df 100644 --- a/ptf/tests/linerate/baseline.py +++ b/ptf/tests/linerate/baseline.py @@ -12,21 +12,7 @@ from trex_test import TrexTest from trex_utils import * -#Source MAC address for DL traffic -TREX_SRC_MAC = "b4:96:91:b4:4b:09" -#Destination MAC address for DL traffic -UPF_DEST_MAC = "b4:96:91:b2:06:41" - -# Port setup -TREX_SENDER_PORT = 1 -BESS_SENDER_PORT = 1 - -# test specs -DURATION = 10 -RATE = 100_000 # 100 Kpps -UE_COUNT = 10_000 # 10k UEs -PKT_SIZE = 64 - +from common import * class DownlinkPerformanceBaselineTest(TrexTest, GrpcTest): """ @@ -38,13 +24,7 @@ class DownlinkPerformanceBaselineTest(TrexTest, GrpcTest): @autocleanup def runTest(self): n3TEID = 0 - - app_ip = '10.128.4.22' - startIP = IPv4Address("16.0.0.1") - endIP = startIP + UE_COUNT - 1 - - accessIP = IPv4Address("198.19.0.1") - enbIP = IPv4Address("11.1.1.128") # arbitrary ip for nonexistent gnodeB + endIP = UE_IP_START + UE_COUNT - 1 # program UPF for downlink traffic by installing PDRs and FARs print("Installing PDRs and FARs...") @@ -52,7 +32,7 @@ def runTest(self): # install N6 DL PDR to match UE dst IP pdrDown = self.createPDR( srcIface=CORE, - dstIP=int(startIP + i), + dstIP=int(UE_IP_START + i), srcIfaceMask=0xFF, dstIPMask=0xFFFFFFFF, precedence=255, @@ -71,8 +51,8 @@ def runTest(self): applyAction=ACTION_FORWARD, dstIntf=DST_ACCESS, tunnelType=0x1, - tunnelIP4Src=int(accessIP), - tunnelIP4Dst=int(enbIP), # only one eNB to send to downlink + tunnelIP4Src=int(N3_IP), + tunnelIP4Dst=int(GNB_IP), tunnelTEID=0, tunnelPort=GTPU_PORT, ) @@ -97,7 +77,7 @@ def runTest(self): vm = STLVM() vm.var( name="dst", - min_value=str(startIP), + min_value=str(UE_IP_START), max_value=str(endIP), size=4, op="random", @@ -105,8 +85,8 @@ def runTest(self): vm.write(fv_name="dst", pkt_offset="IP.dst") vm.fix_chksum() - eth = Ether(dst=UPF_DEST_MAC, src=TREX_SRC_MAC) - ip = IP(src=app_ip, id=0) + eth = Ether(dst=UPF_CORE_MAC, src=TREX_SRC_MAC) + ip = IP(src=PDN_IP, id=0) udp = UDP(sport=10002, dport=10001, chksum=0) pkt = eth/ip/udp @@ -121,21 +101,21 @@ def runTest(self): # fail due to port DOWN state time.sleep(20) - self.trex_client.add_streams(stream, ports=[BESS_SENDER_PORT]) + self.trex_client.add_streams(stream, ports=[UPF_CORE_PORT]) print("Running traffic...") s_time = time.time() self.trex_client.start( - ports=[BESS_SENDER_PORT], + ports=[UPF_CORE_PORT], mult="1", duration=DURATION, ) - self.trex_client.wait_on_traffic(ports=[TREX_SENDER_PORT]) + self.trex_client.wait_on_traffic(ports=[UPF_CORE_PORT]) print(f"Duration was {time.time() - s_time}") trex_stats = self.trex_client.get_stats() - lat_stats = get_latency_stats(0, trex_stats) - flow_stats = get_flow_stats(0, trex_stats) + lat_stats = get_latency_stats(TREX_RECEIVER_PORT, trex_stats) + flow_stats = get_flow_stats(TREX_RECEIVER_PORT, trex_stats) # Verify test results met baseline performance expectations diff --git a/ptf/tests/linerate/common.py b/ptf/tests/linerate/common.py new file mode 100644 index 000000000..3020f2c52 --- /dev/null +++ b/ptf/tests/linerate/common.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2024 Intel Corporation + +from ipaddress import IPv4Address + +# MAC addresses +TREX_SRC_MAC = "b4:96:91:b4:4b:09" # Source MAC address for DL traffic +UPF_CORE_MAC = "b4:96:91:b2:06:41" # MAC address of N6 for the UPF/BESS +UPF_ACCESS_MAC = "b4:96:91:b2:06:40" # MAC address of N3 for the UPF/BESS + +# Port setup +TREX_SENDER_PORT = 1 +TREX_RECEIVER_PORT = 0 +UPF_CORE_PORT = 1 +UPF_ACCESS_PORT = 0 + +# test specs +DURATION = 10 +RATE = 100_000 # 100 Kpps +UE_COUNT = 10_000 # 10k UEs +PKT_SIZE = 64 +PKT_SIZE_L = 1400 # Packet size for MBR test + +# IP addresses +UE_IP_START = IPv4Address("16.0.0.1") +GNB_IP = IPv4Address("11.1.1.129") +N3_IP = IPv4Address("198.18.0.1") +PDN_IP = IPv4Address("6.6.6.6") # Must be routable by route_control + diff --git a/ptf/tests/linerate/mbr.py b/ptf/tests/linerate/mbr.py index 3f29a724d..0d3d2b1c7 100644 --- a/ptf/tests/linerate/mbr.py +++ b/ptf/tests/linerate/mbr.py @@ -1,8 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2022-present Open Networking Foundation -from ipaddress import IPv4Address - from grpc_test import * from pkt_utils import GTPU_PORT, pkt_add_gtpu from scapy.layers.l2 import Ether @@ -11,27 +9,7 @@ from trex_test import TrexTest from trex_utils import * -# TODO: move to global constant file (or env) -# since it depends on Trex config and server where BESS is running -# Port setup -# MAC address of N6 for the UPF/BESS -BESS_CORE_MAC = "b4:96:91:b2:06:41" -# MAC address of N3 for the UPF/BESS -BESS_ACCESS_MAC = "b4:96:91:b2:06:40" -BESS_CORE_PORT = 1 -BESS_ACCESS_PORT = 0 - -# test specs -DURATION = 3 -PKT_SIZE = 1400 - -# TODO: move to global constant file (or env) -UE_IP = IPv4Address("16.0.0.1") -ENB_IP = IPv4Address("11.1.1.129") -N3_IP = IPv4Address("198.18.0.1") -# Must be routable by route_control -PDN_IP = IPv4Address("6.6.6.6") - +from common import * class AppMbrTest(TrexTest, GrpcTest): """Base class for app MBR testing""" @@ -44,7 +22,7 @@ def run_dl_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: pdrDown = self.createPDR( srcIface=CORE, - dstIP=int(UE_IP), + dstIP=int(UE_IP_START), srcIfaceMask=0xFF, dstIPMask=0xFFFFFFFF, precedence=255, @@ -63,7 +41,7 @@ def run_dl_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: dstIntf=DST_ACCESS, tunnelType=0x1, # Replace with constant tunnelIP4Src=int(N3_IP), - tunnelIP4Dst=int(ENB_IP), + tunnelIP4Dst=int(GNB_IP), tunnelTEID=teid, tunnelPort=GTPU_PORT, ) @@ -80,9 +58,9 @@ def run_dl_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: self.addApplicationQER(qer) pkt = testutils.simple_udp_packet( - pktlen=PKT_SIZE, - eth_dst=BESS_CORE_MAC, - ip_dst=str(UE_IP), + pktlen=PKT_SIZE_L, + eth_dst=UPF_CORE_MAC, + ip_dst=str(UE_IP_START), with_udp_chksum=False, ) app_payload_size = len(pkt[Ether].payload) @@ -97,13 +75,13 @@ def run_dl_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: mode=STLTXCont(bps_L2=stream_bps), flow_stats=STLFlowLatencyStats(pg_id=0), ) - self.trex_client.add_streams(stream, ports=[BESS_CORE_PORT]) + self.trex_client.add_streams(stream, ports=[UPF_CORE_PORT]) start_and_monitor_port_stats( client=self.trex_client, num_samples=num_samples, - tx_port=BESS_CORE_PORT, - rx_port=BESS_ACCESS_PORT, + tx_port=UPF_CORE_PORT, + rx_port=UPF_ACCESS_PORT, min_tx_bps=stream_bps * 0.95, ) @@ -151,16 +129,16 @@ def run_ul_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: self.addApplicationQER(qer) pkt = testutils.simple_udp_packet( - pktlen=PKT_SIZE, - eth_dst=BESS_ACCESS_MAC, - ip_src=str(UE_IP), + pktlen=PKT_SIZE_L, + eth_dst=UPF_ACCESS_MAC, + ip_src=str(UE_IP_START), ip_dst=str(PDN_IP), with_udp_chksum=False, ) app_payload_size = len(pkt[Ether].payload) gtpu_pkt = pkt_add_gtpu( pkt=pkt, - out_ipv4_src=str(ENB_IP), + out_ipv4_src=str(GNB_IP), out_ipv4_dst=str(N3_IP), teid=teid, ) @@ -175,13 +153,13 @@ def run_ul_traffic(self, mbr_bps, stream_bps, num_samples) -> FlowStats: mode=STLTXCont(bps_L2=stream_bps), flow_stats=STLFlowLatencyStats(pg_id=0), ) - self.trex_client.add_streams(stream, ports=[BESS_ACCESS_PORT]) + self.trex_client.add_streams(stream, ports=[UPF_ACCESS_PORT]) start_and_monitor_port_stats( client=self.trex_client, num_samples=num_samples, - tx_port=BESS_ACCESS_PORT, - rx_port=BESS_CORE_PORT, + tx_port=UPF_ACCESS_PORT, + rx_port=UPF_CORE_PORT, min_tx_bps=stream_bps * 0.95, ) diff --git a/ptf/tests/linerate/qos_metrics.py b/ptf/tests/linerate/qos_metrics.py index 84f54e06e..f335e59b7 100644 --- a/ptf/tests/linerate/qos_metrics.py +++ b/ptf/tests/linerate/qos_metrics.py @@ -11,18 +11,7 @@ from trex_stl_lib.api import STLVM, STLPktBuilder, STLStream, STLTXCont from trex_test import TrexTest -#Destination MAC address for DL traffic -UPF_DEST_MAC = "b4:96:91:b2:06:41" - -# Port setup -BESS_SENDER_PORT = 1 -BESS_RECEIVER_PORT = 0 - -# Test specs -DURATION = 10 -RATE = 100_000 # 100 Kpps -UE_COUNT = 10_000 # 10k UEs -PKT_SIZE = 64 +from common import * class PerFlowQosMetricsTest(TrexTest, GrpcTest): @@ -35,14 +24,7 @@ class PerFlowQosMetricsTest(TrexTest, GrpcTest): @autocleanup def runTest(self): n3TEID = 0 - - startIP = IPv4Address("16.0.0.1") - endIP = startIP + UE_COUNT - 1 - - accessIP = IPv4Address("198.19.0.1") - enbIP = IPv4Address( - "11.1.1.129" - ) # arbitrary ip for non-existent eNodeB for gtpu encap + endIP = UE_IP_START + UE_COUNT - 1 # program UPF for downlink traffic by installing PDRs and FARs print("Installing PDRs and FARs...") @@ -50,7 +32,7 @@ def runTest(self): # install N6 DL PDR to match UE dst IP pdrDown = self.createPDR( srcIface=CORE, - dstIP=int(startIP + i), + dstIP=int(UE_IP_START + i), srcIfaceMask=0xFF, dstIPMask=0xFFFFFFFF, precedence=255, @@ -69,8 +51,8 @@ def runTest(self): applyAction=ACTION_FORWARD, dstIntf=DST_ACCESS, tunnelType=0x1, - tunnelIP4Src=int(accessIP), - tunnelIP4Dst=int(enbIP), # only one eNB to send to downlink + tunnelIP4Src=int(N3_IP), + tunnelIP4Dst=int(GNB_IP), tunnelTEID=0, tunnelPort=GTPU_PORT, ) @@ -95,7 +77,7 @@ def runTest(self): vm = STLVM() vm.var( name="dst", - min_value=str(startIP), + min_value=str(UE_IP_START), max_value=str(endIP), size=4, op="random", @@ -105,21 +87,21 @@ def runTest(self): pkt = testutils.simple_udp_packet( pktlen=PKT_SIZE, - eth_dst=UPF_DEST_MAC, + eth_dst=UPF_CORE_MAC, with_udp_chksum=False, ) stream = STLStream( packet=STLPktBuilder(pkt=pkt, vm=vm), mode=STLTXCont(pps=RATE), ) - self.trex_client.add_streams(stream, ports=[BESS_SENDER_PORT]) + self.trex_client.add_streams(stream, ports=[UPF_CORE_PORT]) print("Running traffic...") s_time = time.time() - self.trex_client.start(ports=[BESS_SENDER_PORT], mult="1", duration=DURATION) + self.trex_client.start(ports=[UPF_CORE_PORT], mult="1", duration=DURATION) # FIXME: pull QoS metrics at end instead of while traffic running - time.sleep(DURATION - 5) + time.sleep(DURATION) if self.trex_client.is_traffic_active(): stats = self.getSessionStats(q=[90, 99, 99.9], quiet=True) @@ -127,7 +109,7 @@ def runTest(self): postDlQos = stats["postDlQos"] postUlQos = stats["postUlQos"] - self.trex_client.wait_on_traffic(ports=[BESS_RECEIVER_PORT]) + self.trex_client.wait_on_traffic(ports=[UPF_ACCESS_PORT]) print(f"Duration was {time.time() - s_time}") trex_stats = self.trex_client.get_stats()