From 79f2bb3006d725966902a1e513b00b15d43c382b Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Wed, 25 Feb 2026 14:35:04 +0200 Subject: [PATCH 1/2] net, localnet: Adjust tests to IPv6 Localnet tests need adjustments to run on IPv6 single stack clusters: - Add random IPv6 address to vlan and non-vlan interface on tested VM - Run extra iperf3 server to test IPv6 tcp connectivity test_vmi_reports_ip_on_secondary_interface_without_vlan test was also adjusted but quarantined on IPv6 single stack clusters due to a bug in which the requested assigned IP addresses on the tested VM are not visible in VMI status Signed-off-by: Asia Khromov --- tests/network/libs/ip.py | 4 + tests/network/localnet/conftest.py | 256 +++++++++++++----- tests/network/localnet/liblocalnet.py | 31 +-- tests/network/localnet/test_default_bridge.py | 61 +++-- tests/network/localnet/test_jumbo_frames.py | 40 ++- tests/network/localnet/test_ovs_bridge.py | 38 ++- 6 files changed, 303 insertions(+), 127 deletions(-) diff --git a/tests/network/libs/ip.py b/tests/network/libs/ip.py index 34791bf41d..6d104f1e7b 100644 --- a/tests/network/libs/ip.py +++ b/tests/network/libs/ip.py @@ -93,3 +93,7 @@ def filter_link_local_addresses(ip_addresses: list[str]) -> list[ipaddress.IPv4A List of IP address objects with link-local addresses removed. """ return [ip for addr in ip_addresses if not (ip := ipaddress.ip_interface(address=addr).ip).is_link_local] + + +def ip_header_size(ip: ipaddress.IPv4Address | ipaddress.IPv6Address) -> int: + return IPV4_HEADER_SIZE if ip.version == 4 else IPV6_HEADER_SIZE diff --git a/tests/network/localnet/conftest.py b/tests/network/localnet/conftest.py index bb47eee707..5999852be4 100644 --- a/tests/network/localnet/conftest.py +++ b/tests/network/localnet/conftest.py @@ -1,18 +1,18 @@ from collections.abc import Generator +from typing import Final import pytest from kubernetes.dynamic import DynamicClient from ocp_resources.namespace import Namespace import tests.network.libs.nodenetworkconfigurationpolicy as libnncp -from libs.net.traffic_generator import TcpServer, client_server_active_connection -from libs.net.traffic_generator import VMTcpClient as TcpClient +from libs.net.traffic_generator import client_server_active_connection from libs.net.vmspec import lookup_iface_status from libs.vm.spec import Interface, Multus, Network from libs.vm.vm import BaseVirtualMachine from tests.network.libs import cloudinit from tests.network.libs import cluster_user_defined_network as libcudn -from tests.network.libs.ip import IPV4_HEADER_SIZE, TCP_HEADER_SIZE, random_ipv4_address +from tests.network.libs.ip import filter_link_local_addresses, random_ipv4_address, random_ipv6_address from tests.network.localnet.liblocalnet import ( LINK_STATE_DOWN, LOCALNET_BR_EX_INTERFACE, @@ -23,10 +23,9 @@ LOCALNET_OVS_BRIDGE_NETWORK, LOCALNET_TEST_LABEL, create_nncp_localnet_on_secondary_node_nic, - create_traffic_client, - create_traffic_server, localnet_cudn, localnet_vm, + random_ip_addresses, run_vms, ) from utilities.constants import ( @@ -36,6 +35,9 @@ from utilities.virt import migrate_vm_and_verify PRIMARY_INTERFACE_NAME = "eth0" +SECONDARY_INTERFACE_NAME = "eth1" +IPERF_FIRST_SERVER_PORT: Final[int] = 5201 +IPERF_SECOND_SERVER_PORT: Final[int] = 5202 @pytest.fixture(scope="module") @@ -126,23 +128,57 @@ def ipv4_localnet_address_pool() -> Generator[str]: @pytest.fixture(scope="module") -def vm_localnet_1_secondary_ip(ipv4_localnet_address_pool: Generator[str]) -> str: - return next(ipv4_localnet_address_pool) +def ipv6_localnet_address_pool() -> Generator[str]: + return (f"{random_ipv6_address(net_seed=0, host_address=host_value)}/64" for host_value in range(1, 254)) + + +@pytest.fixture(scope="module") +def vm_localnet_1_vlan_iface_addresses( + ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, +) -> list[str]: + return random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + + +@pytest.fixture(scope="module") +def vm_localnet_1_no_vlan_iface_addresses( + ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, +) -> list[str]: + return random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) @pytest.fixture(scope="module") def vm_localnet_1( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], - vm_localnet_1_secondary_ip: str, + ipv6_localnet_address_pool: Generator[str], + vm_localnet_1_vlan_iface_addresses: list[str], + vm_localnet_1_no_vlan_iface_addresses: list[str], cudn_localnet: libcudn.ClusterUserDefinedNetwork, cudn_localnet_no_vlan: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: """ Creates a VM with two interfaces: - - Primary interface (eth0): connected to VLAN-enabled localnet - - Secondary interface (eth1): connected to no-VLAN localnet + - Primary interface (eth0): connected to VLAN-enabled localnet (IPv4/IPv6 based on cluster support) + - Secondary interface (eth1): connected to no-VLAN localnet (IPv4/IPv6 based on cluster support) """ with localnet_vm( namespace=namespace_localnet_1.name, @@ -158,8 +194,12 @@ def vm_localnet_1( ], network_data=cloudinit.NetworkData( ethernets={ - PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)]), - "eth1": cloudinit.EthernetDevice(addresses=[vm_localnet_1_secondary_ip]), + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=vm_localnet_1_vlan_iface_addresses, + ), + SECONDARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=vm_localnet_1_no_vlan_iface_addresses, + ), } ), ) as vm: @@ -170,8 +210,11 @@ def vm_localnet_1( def vm_localnet_2( namespace_localnet_2: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_2.name, @@ -180,7 +223,16 @@ def vm_localnet_2( networks=[Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet.name))], interfaces=[Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={})], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ), + ) + } ), ) as vm: yield vm @@ -195,21 +247,31 @@ def localnet_running_vms( @pytest.fixture() -def localnet_server(localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine]) -> Generator[TcpServer]: - with create_traffic_server(vm=localnet_running_vms[0]) as server: - assert server.is_running() - yield server +def localnet_active_connections( + localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine], +) -> Generator[list[tuple]]: + server_vm, client_vm = localnet_running_vms + iface = lookup_iface_status(vm=server_vm, iface_name=LOCALNET_BR_EX_INTERFACE) + addresses = filter_link_local_addresses(ip_addresses=iface.ipAddresses) - -@pytest.fixture() -def localnet_client(localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine]) -> Generator[TcpClient]: - with create_traffic_client( - server_vm=localnet_running_vms[0], - client_vm=localnet_running_vms[1], + with client_server_active_connection( + client_vm=client_vm, + server_vm=server_vm, spec_logical_network=LOCALNET_BR_EX_INTERFACE, - ) as client: - assert client.is_running() - yield client + port=IPERF_FIRST_SERVER_PORT, + ip_family=addresses[0].version, + ) as (client0, server0): + if len(addresses) == 1: + yield [(addresses[0], client0, server0)] + else: + with client_server_active_connection( + client_vm=client_vm, + server_vm=server_vm, + spec_logical_network=LOCALNET_BR_EX_INTERFACE, + port=IPERF_SECOND_SERVER_PORT, + ip_family=addresses[1].version, + ) as (client1, server1): + yield [(addresses[0], client0, server0), (addresses[1], client1, server1)] @pytest.fixture(scope="module") @@ -233,8 +295,11 @@ def cudn_localnet_ovs_bridge( def vm_ovs_bridge_localnet_link_down( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet_ovs_bridge: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_1.name, @@ -245,7 +310,16 @@ def vm_ovs_bridge_localnet_link_down( ], interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={}, state=LINK_STATE_DOWN)], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + ) + } ), ) as vm: yield vm @@ -255,8 +329,11 @@ def vm_ovs_bridge_localnet_link_down( def vm_ovs_bridge_localnet_1( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet_ovs_bridge: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_1.name, @@ -267,7 +344,16 @@ def vm_ovs_bridge_localnet_1( ], interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + ) + } ), ) as vm: yield vm @@ -277,8 +363,11 @@ def vm_ovs_bridge_localnet_1( def vm_ovs_bridge_localnet_2( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet_ovs_bridge: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_1.name, @@ -289,7 +378,16 @@ def vm_ovs_bridge_localnet_2( ], interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + ) + } ), ) as vm: yield vm @@ -319,35 +417,55 @@ def ovs_bridge_localnet_running_vms( @pytest.fixture() -def localnet_ovs_bridge_server( +def ovs_bridge_localnet_active_connections( ovs_bridge_localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine], -) -> Generator[TcpServer]: - with create_traffic_server(vm=ovs_bridge_localnet_running_vms[0]) as server: - assert server.is_running() - yield server +) -> Generator[list[tuple]]: + server_vm, client_vm = ovs_bridge_localnet_running_vms + iface = lookup_iface_status(vm=server_vm, iface_name=LOCALNET_OVS_BRIDGE_INTERFACE) + addresses = filter_link_local_addresses(ip_addresses=iface.ipAddresses) - -@pytest.fixture() -def localnet_ovs_bridge_client( - ovs_bridge_localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine], -) -> Generator[TcpClient]: - with create_traffic_client( - server_vm=ovs_bridge_localnet_running_vms[0], - client_vm=ovs_bridge_localnet_running_vms[1], + with client_server_active_connection( + client_vm=client_vm, + server_vm=server_vm, spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, - ) as client: - assert client.is_running() - yield client + port=IPERF_FIRST_SERVER_PORT, + ip_family=addresses[0].version, + ) as (client0, server0): + if len(addresses) == 1: + yield [(addresses[0], client0, server0)] + else: + with client_server_active_connection( + client_vm=client_vm, + server_vm=server_vm, + spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, + port=IPERF_SECOND_SERVER_PORT, + ip_family=addresses[1].version, + ) as (client1, server1): + yield [(addresses[0], client0, server0), (addresses[1], client1, server1)] @pytest.fixture() def localnet_vms_have_connectivity(localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualMachine]) -> None: + server_vm, client_vm = localnet_running_vms + iface = lookup_iface_status(vm=server_vm, iface_name=LOCALNET_BR_EX_INTERFACE) + addresses = filter_link_local_addresses(ip_addresses=iface.ipAddresses) with client_server_active_connection( - client_vm=localnet_running_vms[0], - server_vm=localnet_running_vms[1], + client_vm=client_vm, + server_vm=server_vm, spec_logical_network=LOCALNET_BR_EX_INTERFACE, + ip_family=addresses[0].version, ): - pass + if len(addresses) == 1: + pass + else: + with client_server_active_connection( + client_vm=client_vm, + server_vm=server_vm, + spec_logical_network=LOCALNET_BR_EX_INTERFACE, + port=IPERF_SECOND_SERVER_PORT, + ip_family=addresses[1].version, + ): + pass @pytest.fixture() @@ -410,8 +528,11 @@ def cudn_localnet_ovs_bridge_jumbo_frame( def vm1_ovs_bridge_localnet_jumbo_frame( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet_ovs_bridge_jumbo_frame: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_1.name, @@ -424,7 +545,16 @@ def vm1_ovs_bridge_localnet_jumbo_frame( ], interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + ) + } ), ) as vm: yield vm @@ -434,8 +564,11 @@ def vm1_ovs_bridge_localnet_jumbo_frame( def vm2_ovs_bridge_localnet_jumbo_frame( namespace_localnet_1: Namespace, ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], cudn_localnet_ovs_bridge_jumbo_frame: libcudn.ClusterUserDefinedNetwork, unprivileged_client: DynamicClient, + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_1.name, @@ -448,7 +581,16 @@ def vm2_ovs_bridge_localnet_jumbo_frame( ], interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})], network_data=cloudinit.NetworkData( - ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])} + ethernets={ + PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice( + addresses=random_ip_addresses( + ipv4_localnet_address_pool=ipv4_localnet_address_pool, + ipv6_localnet_address_pool=ipv6_localnet_address_pool, + ipv4_supported_cluster=ipv4_supported_cluster, + ipv6_supported_cluster=ipv6_supported_cluster, + ) + ) + } ), ) as vm: yield vm @@ -460,17 +602,3 @@ def ovs_bridge_localnet_running_jumbo_frame_vms( ) -> Generator[tuple[BaseVirtualMachine, BaseVirtualMachine]]: vm1, vm2 = run_vms(vms=(vm1_ovs_bridge_localnet_jumbo_frame, vm2_ovs_bridge_localnet_jumbo_frame)) yield vm1, vm2 - - -@pytest.fixture() -def localnet_ovs_bridge_jumbo_frame_client_and_server_vms( - ovs_bridge_localnet_running_jumbo_frame_vms: tuple[BaseVirtualMachine, BaseVirtualMachine], - cluster_hardware_mtu: int, -) -> Generator[tuple[TcpClient, TcpServer], None, None]: - with client_server_active_connection( - client_vm=ovs_bridge_localnet_running_jumbo_frame_vms[1], - server_vm=ovs_bridge_localnet_running_jumbo_frame_vms[0], - spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, - maximum_segment_size=cluster_hardware_mtu - IPV4_HEADER_SIZE - TCP_HEADER_SIZE, - ) as (client, server): - yield client, server diff --git a/tests/network/localnet/liblocalnet.py b/tests/network/localnet/liblocalnet.py index 2f9ec4f0cc..815bc6c374 100644 --- a/tests/network/localnet/liblocalnet.py +++ b/tests/network/localnet/liblocalnet.py @@ -6,9 +6,6 @@ from kubernetes.client import ApiException from kubernetes.dynamic import DynamicClient -from libs.net.traffic_generator import IPERF_SERVER_PORT, TcpServer -from libs.net.traffic_generator import VMTcpClient as TcpClient -from libs.net.vmspec import lookup_iface_status_ip from libs.vm.affinity import new_pod_anti_affinity from libs.vm.factory import base_vmspec, fedora_vm from libs.vm.spec import CloudInitNoCloud, Devices, Interface, Metadata, Network @@ -32,6 +29,20 @@ LOGGER = logging.getLogger(__name__) +def random_ip_addresses( + ipv4_localnet_address_pool: Generator[str], + ipv6_localnet_address_pool: Generator[str], + ipv4_supported_cluster: bool, + ipv6_supported_cluster: bool, +) -> list[str]: + addresses = [] + if ipv4_supported_cluster: + addresses.append(next(ipv4_localnet_address_pool)) + if ipv6_supported_cluster: + addresses.append(next(ipv6_localnet_address_pool)) + return addresses + + def run_vms(vms: tuple[BaseVirtualMachine, ...]) -> tuple[BaseVirtualMachine, ...]: for vm in vms: try: @@ -46,20 +57,6 @@ def run_vms(vms: tuple[BaseVirtualMachine, ...]) -> tuple[BaseVirtualMachine, .. return vms -def create_traffic_server(vm: BaseVirtualMachine) -> TcpServer: - return TcpServer(vm=vm, port=IPERF_SERVER_PORT) - - -def create_traffic_client( - server_vm: BaseVirtualMachine, client_vm: BaseVirtualMachine, spec_logical_network: str -) -> TcpClient: - return TcpClient( - vm=client_vm, - server_ip=str(lookup_iface_status_ip(vm=server_vm, iface_name=spec_logical_network, ip_family=4)), - server_port=IPERF_SERVER_PORT, - ) - - def localnet_vm( namespace: str, name: str, diff --git a/tests/network/localnet/test_default_bridge.py b/tests/network/localnet/test_default_bridge.py index 40f3a25755..01de2413cd 100644 --- a/tests/network/localnet/test_default_bridge.py +++ b/tests/network/localnet/test_default_bridge.py @@ -1,62 +1,81 @@ from ipaddress import ip_interface import pytest +from libs.net.cluster import is_ipv6_single_stack_cluster from libs.net.traffic_generator import client_server_active_connection, is_tcp_connection -from libs.net.vmspec import lookup_iface_status_ip +from libs.net.vmspec import lookup_iface_status +from tests.network.libs.ip import filter_link_local_addresses from tests.network.localnet.liblocalnet import ( LOCALNET_BR_EX_INTERFACE, LOCALNET_BR_EX_INTERFACE_NO_VLAN, ) +from utilities.constants import QUARANTINED from utilities.virt import migrate_vm_and_verify @pytest.mark.gating -@pytest.mark.ipv4 @pytest.mark.single_nic @pytest.mark.s390x @pytest.mark.usefixtures("nncp_localnet") @pytest.mark.polarion("CNV-11775") -def test_connectivity_over_migration_between_localnet_vms(localnet_server, localnet_client): - migrate_vm_and_verify(vm=localnet_client.vm) - assert is_tcp_connection(server=localnet_server, client=localnet_client) +def test_connectivity_over_migration_between_localnet_vms( + subtests, + localnet_running_vms, + localnet_active_connections, +): + _, client_vm = localnet_running_vms + migrate_vm_and_verify(vm=client_vm) + for dst_ip, client, server in localnet_active_connections: + with subtests.test(msg=f"IPv{dst_ip.version}"): + assert is_tcp_connection(server=server, client=client) -@pytest.mark.ipv4 @pytest.mark.single_nic @pytest.mark.s390x @pytest.mark.usefixtures("nncp_localnet") @pytest.mark.polarion("CNV-11925") -def test_connectivity_post_migration_between_localnet_vms(migrated_localnet_vm, localnet_running_vms): +def test_connectivity_post_migration_between_localnet_vms( + subtests, + migrated_localnet_vm, + localnet_running_vms, +): vms = list(localnet_running_vms) vms.remove(migrated_localnet_vm) (base_localnet_vm,) = vms - with client_server_active_connection( - client_vm=base_localnet_vm, - server_vm=migrated_localnet_vm, - spec_logical_network=LOCALNET_BR_EX_INTERFACE, - port=8888, - ) as (client, server): - assert is_tcp_connection(server=server, client=client) + iface = lookup_iface_status(vm=migrated_localnet_vm, iface_name=LOCALNET_BR_EX_INTERFACE) + for dst_ip in filter_link_local_addresses(ip_addresses=iface.ipAddresses): + with subtests.test(msg=f"IPv{dst_ip.version}"): + with client_server_active_connection( + client_vm=base_localnet_vm, + server_vm=migrated_localnet_vm, + spec_logical_network=LOCALNET_BR_EX_INTERFACE, + port=8888, + ip_family=dst_ip.version, + ) as (client, server): + assert is_tcp_connection(server=server, client=client) -@pytest.mark.ipv4 @pytest.mark.single_nic @pytest.mark.s390x @pytest.mark.usefixtures("nncp_localnet") @pytest.mark.polarion("CNV-12363") def test_vmi_reports_ip_on_secondary_interface_without_vlan( localnet_running_vms, - vm_localnet_1_secondary_ip, + vm_localnet_1_no_vlan_iface_addresses, ): """ Test that vm_localnet_1's secondary interface on a no-VLAN localnet - correctly reports the IP address for that interface. + correctly reports the IP addresses for that interface based on cluster network stack. """ + if is_ipv6_single_stack_cluster(): + pytest.xfail(reason=f"{QUARANTINED}: The requested IP is assigned but not visible in VMI: CNV-80582") vm, _ = localnet_running_vms - vm_ip = lookup_iface_status_ip(vm=vm, iface_name=LOCALNET_BR_EX_INTERFACE_NO_VLAN, ip_family=4) - assert vm_ip == ip_interface(vm_localnet_1_secondary_ip).ip, ( - f"IP address mismatch for interface {LOCALNET_BR_EX_INTERFACE_NO_VLAN} on VM {vm.name}, " - f"expected {ip_interface(vm_localnet_1_secondary_ip).ip}, got {vm_ip}" + + iface_status = lookup_iface_status(vm=vm, iface_name=LOCALNET_BR_EX_INTERFACE_NO_VLAN) + reported_ips = set( + filter_link_local_addresses(ip_addresses=[ip_interface(addr).ip for addr in iface_status.ipAddresses]) ) + expected_ips = {ip_interface(addr).ip for addr in vm_localnet_1_no_vlan_iface_addresses} + assert reported_ips == expected_ips diff --git a/tests/network/localnet/test_jumbo_frames.py b/tests/network/localnet/test_jumbo_frames.py index 02267db579..e400754b0c 100644 --- a/tests/network/localnet/test_jumbo_frames.py +++ b/tests/network/localnet/test_jumbo_frames.py @@ -1,30 +1,48 @@ import pytest -from libs.net.traffic_generator import is_tcp_connection -from libs.net.vmspec import lookup_iface_status_ip -from tests.network.libs.ip import ICMP_HEADER_SIZE, IPV4_HEADER_SIZE +from libs.net.traffic_generator import client_server_active_connection, is_tcp_connection +from libs.net.vmspec import lookup_iface_status +from tests.network.libs.ip import ( + ICMP_HEADER_SIZE, + TCP_HEADER_SIZE, + filter_link_local_addresses, + ip_header_size, +) from tests.network.localnet.liblocalnet import LOCALNET_OVS_BRIDGE_INTERFACE from utilities.virt import vm_console_run_commands pytestmark = [ pytest.mark.special_infra, pytest.mark.jumbo_frame, - pytest.mark.ipv4, ] @pytest.mark.polarion("CNV-12349") @pytest.mark.usefixtures("nncp_localnet_on_secondary_node_nic_with_jumbo_frame") def test_connectivity_ovs_bridge_jumbo_frames_no_fragmentation( + subtests, cluster_hardware_mtu, ovs_bridge_localnet_running_jumbo_frame_vms, - localnet_ovs_bridge_jumbo_frame_client_and_server_vms, ): - ping_payload_size = cluster_hardware_mtu - ICMP_HEADER_SIZE - IPV4_HEADER_SIZE vm1, vm2 = ovs_bridge_localnet_running_jumbo_frame_vms - dst_ip = lookup_iface_status_ip(vm=vm2, iface_name=LOCALNET_OVS_BRIDGE_INTERFACE, ip_family=4) - ping_cmd_jumbo_frame_no_fragmentation = f"ping -q -c 3 {dst_ip} -s {ping_payload_size} -M do" - vm_console_run_commands(vm=vm1, commands=[ping_cmd_jumbo_frame_no_fragmentation]) + iface = lookup_iface_status(vm=vm2, iface_name=LOCALNET_OVS_BRIDGE_INTERFACE, timeout=120) + vm2_addresses = filter_link_local_addresses(ip_addresses=iface.ipAddresses) - client, server = localnet_ovs_bridge_jumbo_frame_client_and_server_vms - assert is_tcp_connection(server=server, client=client) + for dst_ip in vm2_addresses: + ping_payload_size = cluster_hardware_mtu - ip_header_size(ip=dst_ip) - ICMP_HEADER_SIZE + with subtests.test(msg=f"Jumbo frame ping to IPv{dst_ip.version} {dst_ip}"): + vm_console_run_commands( + vm=vm1, + commands=[f"ping{' -6' if dst_ip.version == 6 else ''} -q -c 3 {dst_ip} -s {ping_payload_size} -M do"], + ) + + for dst_ip in vm2_addresses: + with subtests.test(msg=f"TCP iperf3 over IPv{dst_ip.version}"): + with client_server_active_connection( + client_vm=vm2, + server_vm=vm1, + spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, + maximum_segment_size=cluster_hardware_mtu - ip_header_size(ip=dst_ip) - TCP_HEADER_SIZE, + ip_family=dst_ip.version, + ) as (client, server): + assert is_tcp_connection(server=server, client=client) diff --git a/tests/network/localnet/test_ovs_bridge.py b/tests/network/localnet/test_ovs_bridge.py index 7ba7f38403..755fc574af 100644 --- a/tests/network/localnet/test_ovs_bridge.py +++ b/tests/network/localnet/test_ovs_bridge.py @@ -2,6 +2,7 @@ from libs.net.traffic_generator import client_server_active_connection, is_tcp_connection from libs.net.vmspec import lookup_iface_status +from tests.network.libs.ip import filter_link_local_addresses from tests.network.localnet.liblocalnet import ( LINK_STATE_UP, LOCALNET_OVS_BRIDGE_INTERFACE, @@ -9,38 +10,47 @@ from utilities.virt import migrate_vm_and_verify -@pytest.mark.ipv4 @pytest.mark.s390x @pytest.mark.usefixtures("nncp_localnet_on_secondary_node_nic") @pytest.mark.polarion("CNV-11905") def test_connectivity_over_migration_between_ovs_bridge_localnet_vms( - localnet_ovs_bridge_server, localnet_ovs_bridge_client + subtests, + ovs_bridge_localnet_running_vms, + ovs_bridge_localnet_active_connections, ): - migrate_vm_and_verify(vm=localnet_ovs_bridge_client.vm) - assert is_tcp_connection(server=localnet_ovs_bridge_server, client=localnet_ovs_bridge_client) + _, client_vm = ovs_bridge_localnet_running_vms + migrate_vm_and_verify(vm=client_vm) + for dst_ip, client, server in ovs_bridge_localnet_active_connections: + with subtests.test(msg=f"IPv{dst_ip.version}"): + assert is_tcp_connection(server=server, client=client) -@pytest.mark.ipv4 @pytest.mark.usefixtures("nncp_localnet_on_secondary_node_nic") @pytest.mark.polarion("CNV-12006") def test_connectivity_after_interface_state_change_in_ovs_bridge_localnet_vms( + subtests, ovs_bridge_localnet_running_vms_one_with_interface_down, ): (vm1_with_initial_link_down, vm2) = ovs_bridge_localnet_running_vms_one_with_interface_down vm1_with_initial_link_down.set_interface_state(network_name=LOCALNET_OVS_BRIDGE_INTERFACE, state=LINK_STATE_UP) - lookup_iface_status( + iface = lookup_iface_status( vm=vm1_with_initial_link_down, iface_name=LOCALNET_OVS_BRIDGE_INTERFACE, predicate=lambda interface: ( - "guest-agent" in interface["infoSource"] and interface["linkState"] == LINK_STATE_UP + "guest-agent" in interface["infoSource"] + and interface["linkState"] == LINK_STATE_UP + and interface.get("ipAddresses") ), ) - with client_server_active_connection( - client_vm=vm2, - server_vm=vm1_with_initial_link_down, - spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, - port=8888, - ) as (client, server): - assert is_tcp_connection(server=server, client=client) + for dst_ip in filter_link_local_addresses(ip_addresses=iface.ipAddresses): + with subtests.test(msg=f"IPv{dst_ip.version}"): + with client_server_active_connection( + client_vm=vm2, + server_vm=vm1_with_initial_link_down, + spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, + port=8888, + ip_family=dst_ip.version, + ) as (client, server): + assert is_tcp_connection(server=server, client=client) From 90612342e46bc96e00c1b2e2cb973ffa8ec8aeeb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:24:55 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/network/localnet/test_default_bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/network/localnet/test_default_bridge.py b/tests/network/localnet/test_default_bridge.py index 01de2413cd..95784ecf36 100644 --- a/tests/network/localnet/test_default_bridge.py +++ b/tests/network/localnet/test_default_bridge.py @@ -1,8 +1,8 @@ from ipaddress import ip_interface import pytest -from libs.net.cluster import is_ipv6_single_stack_cluster +from libs.net.cluster import is_ipv6_single_stack_cluster from libs.net.traffic_generator import client_server_active_connection, is_tcp_connection from libs.net.vmspec import lookup_iface_status from tests.network.libs.ip import filter_link_local_addresses