[PATCH v7] dts: refactor flow suite with generator pattern
Patrick Robb
patrickrobb1997 at gmail.com
Tue Oct 6 21:55:44 CEST 2026
I will test this tonight.
What do we have right now in terms of DTS dev systems and NICs for non-iol
people like me. Is it still:
iol-dts-tester
And
iol-dts-dut
With mlnx cx5 and intel xl710?
Just checking in.
On Tue, Aug 11, 2026 at 1:04 PM Andrew Bailey <abailey at iol.unh.edu> wrote:
> Refactor current flow test suite to utilize a generator
> method, which dynamically creates flow rules from a
> dictionary of pattern and action fields during runtime.
> This allows for much more extensive testing of the flow
> API without exponential code growth.
>
> Signed-off-by: Andrew Bailey <abailey at iol.unh.edu>
> Signed-off-by: Dean Marx <dmarx at iol.unh.edu>
> Reviewed-by: Patrick Robb <probb at iol.unh.edu>
> ---
>
> v7:
> * Resolve merge conflicts.
> dts/tests/TestSuite_flow_offload.py | 206 ++++++++++++++++++++++++----
> 1 file changed, 182 insertions(+), 24 deletions(-)
>
> diff --git a/dts/tests/TestSuite_flow_offload.py
> b/dts/tests/TestSuite_flow_offload.py
> index be11d09875..d1a6c2fa74 100644
> --- a/dts/tests/TestSuite_flow_offload.py
> +++ b/dts/tests/TestSuite_flow_offload.py
> @@ -1,7 +1,7 @@
> # SPDX-License-Identifier: BSD-3-Clause
> # Copyright(c) 2025 University of New Hampshire
>
> -"""RTE Flow testing suite.
> +"""Flow offload testing suite.
>
> This suite verifies a range of flow rules built using patterns
> and actions from the RTE Flow API. It would be impossible to cover
> @@ -10,7 +10,7 @@
>
> """
>
> -from dataclasses import dataclass
> +from dataclasses import dataclass, field
> from itertools import product
> from typing import Any, Callable, Optional
>
> @@ -18,6 +18,7 @@
> from scapy.layers.inet6 import IPv6
> from scapy.layers.l2 import ARP, Dot1Q, Ether
> from scapy.layers.sctp import SCTP
> +from scapy.layers.vxlan import VXLAN
> from scapy.packet import Packet, Raw
>
> from api.capabilities import NicCapability, requires_nic_capability
> @@ -50,10 +51,16 @@ class Protocol:
> scapy_class: type[Packet]
> pattern_name: str
> fields: list[PatternField]
> + default_values: dict[str, Any] = field(default_factory=dict)
>
> def build_scapy_layer(self, field_values: dict[str, Any]) -> Packet:
> - """Construct a Scapy layer with the given field values."""
> - return self.scapy_class(**field_values)
> + """Construct a Scapy layer with the given field values.
> +
> + Default values are applied first, then overridden by any
> + explicit field values so test parameters always win.
> + """
> + merged = {**self.default_values, **field_values}
> + return self.scapy_class(**merged)
>
>
> @dataclass
> @@ -125,6 +132,7 @@ class JumpTest:
>
>
> PROTOCOLS: dict[str, Protocol] = {
> + # -------------------- Base Protocols --------------------
> "eth": Protocol(
> name="eth",
> scapy_class=Ether,
> @@ -215,6 +223,89 @@ class JumpTest:
> PatternField("op", "opcode", [1, 2]),
> ],
> ),
> + # -------------------- VXLAN Outer Protocols --------------------
> + "eth_outer": Protocol(
> + name="eth_outer",
> + scapy_class=Ether,
> + pattern_name="eth",
> + fields=[
> + PatternField("src", "src", ["02:00:00:00:00:00"]),
> + PatternField("dst", "dst", ["02:00:00:00:00:02"]),
> + ],
> + ),
> + "ipv4_outer": Protocol(
> + name="ipv4_outer",
> + scapy_class=IP,
> + pattern_name="ipv4",
> + fields=[
> + PatternField("src", "src", ["10.0.0.1"]),
> + PatternField("dst", "dst", ["10.0.0.2"]),
> + PatternField("ttl", "ttl", [64, 128]),
> + PatternField("tos", "tos", [0, 4]),
> + ],
> + ),
> + "udp_outer": Protocol(
> + name="udp_outer",
> + scapy_class=UDP,
> + pattern_name="udp",
> + fields=[],
> + default_values={"dport": 4789},
> + ),
> + # -------------------- VXLAN Tunnel Header --------------------
> + "vxlan": Protocol(
> + name="vxlan",
> + scapy_class=VXLAN,
> + pattern_name="vxlan",
> + fields=[
> + PatternField("vni", "vni", [42, 100]),
> + ],
> + ),
> + # -------------------- VXLAN Inner Protocols --------------------
> + "eth_inner": Protocol(
> + name="eth_inner",
> + scapy_class=Ether,
> + pattern_name="eth",
> + fields=[
> + PatternField("src", "src", ["02:00:00:00:00:00"]),
> + PatternField("dst", "dst", ["02:00:00:00:00:02"]),
> + ],
> + ),
> + "ipv4_inner": Protocol(
> + name="ipv4_inner",
> + scapy_class=IP,
> + pattern_name="ipv4",
> + fields=[
> + PatternField("src", "src", ["192.168.1.1"]),
> + PatternField("dst", "dst", ["192.168.1.2"]),
> + ],
> + ),
> + "ipv6_inner": Protocol(
> + name="ipv6_inner",
> + scapy_class=IPv6,
> + pattern_name="ipv6",
> + fields=[
> + PatternField("src", "src", ["2001:db8::1"]),
> + PatternField("dst", "dst", ["2001:db8::2"]),
> + ],
> + ),
> + "tcp_inner": Protocol(
> + name="tcp_inner",
> + scapy_class=TCP,
> + pattern_name="tcp",
> + fields=[
> + PatternField("sport", "src", [1234]),
> + PatternField("dport", "dst", [80]),
> + ],
> + ),
> + "udp_inner": Protocol(
> + name="udp_inner",
> + scapy_class=UDP,
> + pattern_name="udp",
> + fields=[
> + PatternField("sport", "src", [5000]),
> + PatternField("dport", "dst", [53]),
> + ],
> + ),
> }
>
>
> @@ -234,6 +325,7 @@ class JumpTest:
> }
>
> PROTOCOL_STACKS = [
> + # -------------------- Non-tunnel stacks --------------------
> [("eth", True)],
> [("eth", False), ("ipv4", True)],
> [("eth", False), ("ipv4", True), ("tcp", True)],
> @@ -253,6 +345,66 @@ class JumpTest:
> [("eth", False), ("vlan", False), ("ipv6", True), ("tcp", True)],
> [("eth", False), ("vlan", False), ("ipv6", True), ("udp", True)],
> [("eth", False), ("arp", True)],
> + # -------------------- VXLAN tunnel stacks --------------------
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv4_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv4_inner", False),
> + ("tcp_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv4_inner", False),
> + ("udp_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv6_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv6_inner", False),
> + ("tcp_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ("ipv6_inner", False),
> + ("udp_inner", False),
> + ],
> + [
> + ("eth_outer", False),
> + ("ipv4_outer", True),
> + ("udp_outer", False),
> + ("vxlan", True),
> + ("eth_inner", False),
> + ],
> ]
>
>
> @@ -304,6 +456,10 @@ def generate(
> ) -> list[FlowTest]:
> """Generate test cases for patterns matching fields across
> multiple protocols.
>
> + Pattern parts are assembled in stack order to preserve positional
> + correctness, which is required for tunnel encapsulations where
> + inner and outer layers share the same pattern_name.
> +
> Args:
> protocol_stack: List of (protocol_name, test_fields) tuples.
> If test_fields is True, iterate through field
> combinations.
> @@ -316,15 +472,12 @@ def generate(
> List of FlowTest objects ready for execution.
> """
> action_spec = self.actions[action_name]
> -
> - wildcard_protocols = [name for name, test_fields in
> protocol_stack if not test_fields]
> - field_test_protocols = [name for name, test_fields in
> protocol_stack if test_fields]
> all_protocol_names = [name for name, _ in protocol_stack]
> -
> - wildcard_pattern_parts = [self.protocols[name].pattern_name for
> name in wildcard_protocols]
> + field_test_protocols = [name for name, test_fields in
> protocol_stack if test_fields]
>
> if not field_test_protocols:
> - pattern = " / ".join(wildcard_pattern_parts)
> + pattern_parts = [self.protocols[name].pattern_name for name,
> _ in protocol_stack]
> + pattern = " / ".join(pattern_parts)
> flow_rule = FlowRule(
> direction="ingress",
> pattern=[pattern],
> @@ -339,7 +492,7 @@ def generate(
> packet=packet,
> verification_type=action_spec.verification_type,
>
> verification_params=action_spec.build_verification_params(action_value),
> - description=" / ".join(wildcard_pattern_parts) + f"
> -> {action_spec.name}",
> + description=pattern + f" -> {action_spec.name}",
> )
> ]
>
> @@ -354,24 +507,32 @@ def generate(
> max_vals = max(len(f_spec.test_parameters) for _, f_spec in
> field_combo)
>
> for i in range(max_vals):
> - field_pattern_parts = []
> + field_value_map: dict[str, tuple[Protocol, PatternField,
> Any]] = {}
> all_field_values: dict[str, dict[str, Any]] = {}
> - desc_parts = []
>
> for protocol_spec, field_spec in field_combo:
> val = field_spec.test_parameters[i %
> len(field_spec.test_parameters)]
> -
> - field_pattern_parts.append(
> - f"{protocol_spec.pattern_name}
> {field_spec.pattern_field} is {val}"
> - )
> + field_value_map[protocol_spec.name] =
> (protocol_spec, field_spec, val)
>
> if protocol_spec.name not in all_field_values:
> all_field_values[protocol_spec.name] = {}
> all_field_values[protocol_spec.name][field_spec.scapy_field]
> = val
>
> - desc_parts.append(f"{protocol_spec.name
> }[{field_spec.scapy_field}={val}]")
> + pattern_parts = []
> + desc_parts = []
> +
> + for proto_name, test_fields in protocol_stack:
> + proto_spec = self.protocols[proto_name]
> + if test_fields and proto_name in field_value_map:
> + _, f_spec, val = field_value_map[proto_name]
> + pattern_parts.append(
> + f"{proto_spec.pattern_name}
> {f_spec.pattern_field} is {val}"
> + )
> + desc_parts.append(f"{proto_spec.name
> }[{f_spec.scapy_field}={val}]")
> + else:
> + pattern_parts.append(proto_spec.pattern_name)
>
> - full_pattern = " / ".join(wildcard_pattern_parts +
> field_pattern_parts)
> + full_pattern = " / ".join(pattern_parts)
>
> flow_rule = FlowRule(
> direction="ingress",
> @@ -384,10 +545,7 @@ def generate(
> all_protocol_names, all_field_values, add_payload=True
> )
>
> - wildcard_desc = " / ".join(wildcard_pattern_parts)
> - field_desc = " / ".join(desc_parts)
> - full_desc = f"{wildcard_desc} / {field_desc}" if
> wildcard_desc else field_desc
> -
> + full_desc = " / ".join(desc_parts)
> test_cases.append(
> FlowTest(
> flow_rule=flow_rule,
> @@ -403,7 +561,7 @@ def generate(
>
> @requires_nic_capability(NicCapability.FLOW_CTRL)
> class TestFlowOffload(TestSuite):
> - """Flow offload test suite.
> + """Flow Offload test suite.
>
> This suite consists of 4 test cases:
> 1. Queue Action: Verifies queue actions with multi-protocol patterns
> --
> 2.55.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mails.dpdk.org/archives/dev/attachments/20261006/540f6fd3/attachment-0001.htm>
More information about the dev
mailing list