Skip to content

Commit d687efb

Browse files
authored
net, localnet, jumbo frame: localnet jumbo no fragmentaion test (#2119)
##### Short description: Add new test: Verify tcp connectivity using localnet and jumbo frames over ovs bridge, without fragmentaion This test combines the existing localnet and jumbo frame tests The test includes 2 steps: 1) Verify jumbo frames are not fragmented with ping 2) Verify connectivity using iperf3 client-server with jumbo frame More details in spike: https://issues.redhat.com/browse/CNV-69347 ##### What this PR does / why we need it: This test combines the existing localnet and jumbo frame tests to provide better test coverage. - Added option to configure mtu in NNCP and CUDN - Refactoring of NNCP fixture and added helper function for more generic code - Added iperf3 client-server fixtures for sending jumbo frames from client with a parameter: `--set-mss <maximum_segment_size>` which is calculated by: MSS = MTU - headers size (ip,tcp) ##### jira-ticket: https://issues.redhat.com/browse/CNV-69286 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Optional TCP Maximum Segment Size (MSS) support for traffic clients (default disabled) * Dynamic MTU support and NNCP-driven OVS bridge workflows for local networks, plus helper to configure physical NIC MTU * **Tests** * New jumbo-frame connectivity test ensuring no-fragmentation TCP * Expanded fixtures and test wiring to provision jumbo-frame VMs and client/server scenarios * **Chores** * Added IP/TCP/ICMP header size constants * Increased test wait timeout for improved reliability <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents d143b09 + 2ee0a09 commit d687efb

File tree

7 files changed

+245
-45
lines changed

7 files changed

+245
-45
lines changed

libs/net/traffic_generator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,20 @@ class VMTcpClient(BaseTcpClient):
8787
vm (BaseVirtualMachine): The virtual machine where the client runs.
8888
server_ip (str): The destination IP address of the server the client connects to.
8989
server_port (int): The port on which the server listens for connections.
90+
maximum_segment_size (int): Define explicitly the TCP payload size (in bytes).
91+
Default value is 0 (do not change mss).
9092
"""
9193

9294
def __init__(
9395
self,
9496
vm: BaseVirtualMachine,
9597
server_ip: str,
9698
server_port: int,
99+
maximum_segment_size: int = 0,
97100
):
98101
super().__init__(server_ip=server_ip, server_port=server_port)
99102
self._vm = vm
103+
self._cmd += f" --set-mss {maximum_segment_size}" if maximum_segment_size else ""
100104

101105
def __enter__(self) -> "VMTcpClient":
102106
self._vm.console(

tests/network/libs/cluster_user_defined_network.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class Role(Enum):
5151
physicalNetworkName: str # noqa: N815
5252
ipam: Ipam
5353
vlan: Vlan | None = None
54+
mtu: int | None = None
5455

5556

5657
@dataclass

tests/network/libs/ip.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
_MAX_NUM_OF_RANDOM_OCTETS_PER_SESSION: Final[int] = 16
66
_IPV4_ADDRESS_SUBNET_PREFIX_VMI: Final[str] = "172.16"
7+
TCP_HEADER_SIZE: Final[int] = 20
8+
IPV4_HEADER_SIZE: Final[int] = 20
9+
ICMPV4_HEADER_SIZE: Final[int] = 8
710

811

912
def random_ipv4_address(net_seed: int, host_address: int) -> str:

tests/network/libs/nodenetworkconfigurationpolicy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from tests.network.libs.apimachinery import dict_normalization_for_dataclass
1515

16-
WAIT_FOR_STATUS_TIMEOUT_SEC = 90
16+
WAIT_FOR_STATUS_TIMEOUT_SEC = 120
1717
WAIT_FOR_STATUS_INTERVAL_SEC = 5
1818
DEFAULT_OVN_EXTERNAL_BRIDGE = "br-ex" # Default name for OVN-Kubernetes external bridge
1919

@@ -68,6 +68,7 @@ class Interface:
6868
name: str
6969
type: str
7070
state: str
71+
mtu: int | None = None
7172
ipv4: IPv4 | None = None
7273
ipv6: IPv6 | None = None
7374
bridge: Bridge | None = None

tests/network/localnet/conftest.py

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import pytest
44
from kubernetes.dynamic import DynamicClient
55
from ocp_resources.namespace import Namespace
6-
from ocp_resources.node import Node
76

87
import tests.network.libs.nodenetworkconfigurationpolicy as libnncp
98
from libs.net.traffic_generator import TcpServer
@@ -13,8 +12,9 @@
1312
from libs.vm.vm import BaseVirtualMachine
1413
from tests.network.libs import cloudinit
1514
from tests.network.libs import cluster_user_defined_network as libcudn
16-
from tests.network.libs.ip import random_ipv4_address
15+
from tests.network.libs.ip import IPV4_HEADER_SIZE, TCP_HEADER_SIZE, random_ipv4_address
1716
from tests.network.localnet.liblocalnet import (
17+
_IPERF_SERVER_PORT,
1818
LINK_STATE_DOWN,
1919
LOCALNET_BR_EX_INTERFACE,
2020
LOCALNET_BR_EX_INTERFACE_NO_VLAN,
@@ -24,6 +24,7 @@
2424
LOCALNET_OVS_BRIDGE_NETWORK,
2525
LOCALNET_TEST_LABEL,
2626
client_server_active_connection,
27+
create_nncp_localnet_on_secondary_node_nic,
2728
create_traffic_client,
2829
create_traffic_server,
2930
localnet_cudn,
@@ -36,7 +37,6 @@
3637
from utilities.infra import create_ns
3738
from utilities.virt import migrate_vm_and_verify
3839

39-
NNCP_INTERFACE_TYPE_OVS_BRIDGE = "ovs-bridge"
4040
PRIMARY_INTERFACE_NAME = "eth0"
4141

4242

@@ -207,46 +207,6 @@ def localnet_client(localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualM
207207
yield client
208208

209209

210-
@pytest.fixture(scope="module")
211-
def nncp_localnet_on_secondary_node_nic(
212-
worker_node1: Node, nodes_available_nics: dict[str, list[str]]
213-
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
214-
bridge_name = "localnet-ovs-br"
215-
desired_state = libnncp.DesiredState(
216-
interfaces=[
217-
libnncp.Interface(
218-
name=bridge_name,
219-
type=NNCP_INTERFACE_TYPE_OVS_BRIDGE,
220-
ipv4=libnncp.IPv4(enabled=False),
221-
ipv6=libnncp.IPv6(enabled=False),
222-
state=libnncp.Resource.Interface.State.UP,
223-
bridge=libnncp.Bridge(
224-
options=libnncp.BridgeOptions(libnncp.STP(enabled=False)),
225-
port=[
226-
libnncp.Port(
227-
name=nodes_available_nics[worker_node1.name][-1],
228-
)
229-
],
230-
),
231-
)
232-
],
233-
ovn=libnncp.OVN([
234-
libnncp.BridgeMappings(
235-
localnet=LOCALNET_OVS_BRIDGE_NETWORK,
236-
bridge=bridge_name,
237-
state=libnncp.BridgeMappings.State.PRESENT.value,
238-
)
239-
]),
240-
)
241-
with libnncp.NodeNetworkConfigurationPolicy(
242-
name=bridge_name,
243-
desired_state=desired_state,
244-
node_selector={WORKER_NODE_LABEL_KEY: ""},
245-
) as nncp:
246-
nncp.wait_for_status_success()
247-
yield nncp
248-
249-
250210
@pytest.fixture(scope="module")
251211
def cudn_localnet_ovs_bridge(
252212
vlan_id: int,
@@ -389,3 +349,109 @@ def migrated_localnet_vm(
389349
vm, _ = localnet_running_vms
390350
migrate_vm_and_verify(vm=vm)
391351
return vm
352+
353+
354+
@pytest.fixture(scope="module")
355+
def nncp_localnet_on_secondary_node_nic(
356+
hosts_common_available_ports: list[str],
357+
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
358+
with create_nncp_localnet_on_secondary_node_nic(node_nic_name=(hosts_common_available_ports[-1])) as nncp:
359+
yield nncp
360+
361+
362+
@pytest.fixture(scope="module")
363+
def nncp_localnet_on_secondary_node_nic_with_jumbo_frame(
364+
hosts_common_available_ports: list[str], cluster_hardware_mtu: int
365+
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
366+
with create_nncp_localnet_on_secondary_node_nic(
367+
node_nic_name=(hosts_common_available_ports[-1]), mtu=cluster_hardware_mtu
368+
) as nncp:
369+
yield nncp
370+
371+
372+
@pytest.fixture(scope="module")
373+
def cudn_localnet_ovs_bridge_jumbo_frame(
374+
vlan_id: int,
375+
cluster_hardware_mtu: int,
376+
namespace_localnet_1: Namespace,
377+
) -> Generator[libcudn.ClusterUserDefinedNetwork]:
378+
with localnet_cudn(
379+
name=LOCALNET_OVS_BRIDGE_NETWORK,
380+
match_labels=LOCALNET_TEST_LABEL,
381+
vlan_id=vlan_id,
382+
physical_network_name=LOCALNET_OVS_BRIDGE_NETWORK,
383+
mtu=cluster_hardware_mtu,
384+
) as cudn:
385+
cudn.wait_for_status_success()
386+
yield cudn
387+
388+
389+
@pytest.fixture(scope="module")
390+
def vm1_ovs_bridge_localnet_jumbo_frame(
391+
namespace_localnet_1: Namespace,
392+
ipv4_localnet_address_pool: Generator[str],
393+
cudn_localnet_ovs_bridge_jumbo_frame: libcudn.ClusterUserDefinedNetwork,
394+
unprivileged_client: DynamicClient,
395+
) -> Generator[BaseVirtualMachine]:
396+
with localnet_vm(
397+
namespace=namespace_localnet_1.name,
398+
name="localnet-ovs-vm1-jumbo",
399+
client=unprivileged_client,
400+
networks=[
401+
Network(
402+
name=LOCALNET_OVS_BRIDGE_INTERFACE, multus=Multus(networkName=cudn_localnet_ovs_bridge_jumbo_frame.name)
403+
)
404+
],
405+
interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})],
406+
network_data=cloudinit.NetworkData(
407+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
408+
),
409+
) as vm:
410+
yield vm
411+
412+
413+
@pytest.fixture(scope="module")
414+
def vm2_ovs_bridge_localnet_jumbo_frame(
415+
namespace_localnet_1: Namespace,
416+
ipv4_localnet_address_pool: Generator[str],
417+
cudn_localnet_ovs_bridge_jumbo_frame: libcudn.ClusterUserDefinedNetwork,
418+
unprivileged_client: DynamicClient,
419+
) -> Generator[BaseVirtualMachine]:
420+
with localnet_vm(
421+
namespace=namespace_localnet_1.name,
422+
name="localnet-ovs-vm2-jumbo",
423+
client=unprivileged_client,
424+
networks=[
425+
Network(
426+
name=LOCALNET_OVS_BRIDGE_INTERFACE, multus=Multus(networkName=cudn_localnet_ovs_bridge_jumbo_frame.name)
427+
)
428+
],
429+
interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})],
430+
network_data=cloudinit.NetworkData(
431+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
432+
),
433+
) as vm:
434+
yield vm
435+
436+
437+
@pytest.fixture(scope="module")
438+
def ovs_bridge_localnet_running_jumbo_frame_vms(
439+
vm1_ovs_bridge_localnet_jumbo_frame: BaseVirtualMachine, vm2_ovs_bridge_localnet_jumbo_frame: BaseVirtualMachine
440+
) -> Generator[tuple[BaseVirtualMachine, BaseVirtualMachine]]:
441+
vm1, vm2 = run_vms(vms=(vm1_ovs_bridge_localnet_jumbo_frame, vm2_ovs_bridge_localnet_jumbo_frame))
442+
yield vm1, vm2
443+
444+
445+
@pytest.fixture()
446+
def localnet_ovs_bridge_jumbo_frame_client_and_server_vms(
447+
ovs_bridge_localnet_running_jumbo_frame_vms: tuple[BaseVirtualMachine, BaseVirtualMachine],
448+
cluster_hardware_mtu: int,
449+
) -> Generator[tuple[TcpClient, TcpServer], None, None]:
450+
with client_server_active_connection(
451+
client_vm=ovs_bridge_localnet_running_jumbo_frame_vms[1],
452+
server_vm=ovs_bridge_localnet_running_jumbo_frame_vms[0],
453+
spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE,
454+
port=_IPERF_SERVER_PORT,
455+
maximum_segment_size=cluster_hardware_mtu - IPV4_HEADER_SIZE - TCP_HEADER_SIZE,
456+
) as (client, server):
457+
yield client, server

0 commit comments

Comments
 (0)