From 05e54ae653b08424c279d369c16a8c63bcc38436 Mon Sep 17 00:00:00 2001 From: Sergei Volkov Date: Wed, 12 Aug 2026 01:46:11 +0200 Subject: [PATCH] net, upgrade: add dedicated NIC bridge localnet connectivity upgrade test Extend localnet upgrade tests to cover connectivity over a dedicated NIC bridge. VMs now carry two localnet interfaces (br-ex + dedicated NIC bridge). Tests verify TCP connectivity over both bridges before and after upgrade. Signed-off-by: Sergei Volkov Assisted-by: Claude Opus 4.6 (1M context) --- tests/network/upgrade/conftest.py | 106 ++++++++++++++++-- .../upgrade/test_localnet_connectivity.py | 90 +++++++++++++-- 2 files changed, 174 insertions(+), 22 deletions(-) diff --git a/tests/network/upgrade/conftest.py b/tests/network/upgrade/conftest.py index 39a7e6037a..bdd993811b 100644 --- a/tests/network/upgrade/conftest.py +++ b/tests/network/upgrade/conftest.py @@ -1,10 +1,11 @@ -from collections.abc import Generator from typing import TYPE_CHECKING import pytest from ocp_resources.virtual_machine import VirtualMachine if TYPE_CHECKING: + from collections.abc import Generator + from kubernetes.dynamic import DynamicClient from ocp_resources.namespace import Namespace @@ -20,10 +21,14 @@ from tests.network.libs import cloudinit from tests.network.libs.localnet import ( GUEST_1ST_IFACE_NAME, + GUEST_2ND_IFACE_NAME, LOCALNET_BR_EX_INTERFACE, LOCALNET_BR_EX_NETWORK, + LOCALNET_OVS_BRIDGE_INTERFACE, + LOCALNET_OVS_BRIDGE_NETWORK, LOCALNET_TEST_LABEL, LOCALNET_VM_ANTI_AFFINITY, + create_nncp_localnet_on_secondary_node_nic, ip_addresses_from_pool, localnet_cloudinit, localnet_cudn, @@ -211,15 +216,27 @@ def vm_localnet_upgrade_a( unprivileged_client: DynamicClient, namespace_localnet_upgrade: Namespace, cudn_localnet_upgrade: ClusterUserDefinedNetwork, + cudn_dedicated_nic_bridge_localnet_upgrade: ClusterUserDefinedNetwork, ipv4_localnet_address_pool_upgrade: Generator[str], ipv6_localnet_address_pool_upgrade: Generator[str], + ipv4_dedicated_nic_bridge_localnet_address_pool_upgrade: Generator[str], + ipv6_dedicated_nic_bridge_localnet_address_pool_upgrade: Generator[str], ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_upgrade.name, name="upgrade-localnet-vm-a", client=unprivileged_client, - networks=[Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet_upgrade.name))], - interfaces=[Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={})], + networks=[ + Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet_upgrade.name)), + Network( + name=LOCALNET_OVS_BRIDGE_INTERFACE, + multus=Multus(networkName=cudn_dedicated_nic_bridge_localnet_upgrade.name), + ), + ], + interfaces=[ + Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={}), + Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={}), + ], cloud_init=localnet_cloudinit( network_data=cloudinit.NetworkData( ethernets={ @@ -229,6 +246,12 @@ def vm_localnet_upgrade_a( ipv6_pool=ipv6_localnet_address_pool_upgrade, ), ), + GUEST_2ND_IFACE_NAME: cloudinit.EthernetDevice( + addresses=ip_addresses_from_pool( + ipv4_pool=ipv4_dedicated_nic_bridge_localnet_address_pool_upgrade, + ipv6_pool=ipv6_dedicated_nic_bridge_localnet_address_pool_upgrade, + ), + ), } ) ), @@ -242,15 +265,27 @@ def vm_localnet_upgrade_b( unprivileged_client: DynamicClient, namespace_localnet_upgrade: Namespace, cudn_localnet_upgrade: ClusterUserDefinedNetwork, + cudn_dedicated_nic_bridge_localnet_upgrade: ClusterUserDefinedNetwork, ipv4_localnet_address_pool_upgrade: Generator[str], ipv6_localnet_address_pool_upgrade: Generator[str], + ipv4_dedicated_nic_bridge_localnet_address_pool_upgrade: Generator[str], + ipv6_dedicated_nic_bridge_localnet_address_pool_upgrade: Generator[str], ) -> Generator[BaseVirtualMachine]: with localnet_vm( namespace=namespace_localnet_upgrade.name, name="upgrade-localnet-vm-b", client=unprivileged_client, - networks=[Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet_upgrade.name))], - interfaces=[Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={})], + networks=[ + Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet_upgrade.name)), + Network( + name=LOCALNET_OVS_BRIDGE_INTERFACE, + multus=Multus(networkName=cudn_dedicated_nic_bridge_localnet_upgrade.name), + ), + ], + interfaces=[ + Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={}), + Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={}), + ], cloud_init=localnet_cloudinit( network_data=cloudinit.NetworkData( ethernets={ @@ -260,6 +295,12 @@ def vm_localnet_upgrade_b( ipv6_pool=ipv6_localnet_address_pool_upgrade, ), ), + GUEST_2ND_IFACE_NAME: cloudinit.EthernetDevice( + addresses=ip_addresses_from_pool( + ipv4_pool=ipv4_dedicated_nic_bridge_localnet_address_pool_upgrade, + ipv6_pool=ipv6_dedicated_nic_bridge_localnet_address_pool_upgrade, + ), + ), } ) ), @@ -278,11 +319,52 @@ def localnet_running_vms_upgrade( ip_family for ip_family, enabled in ((4, ipv4_supported_cluster()), (6, ipv6_supported_cluster())) if enabled ] for vm in (vm_a, vm_b): - lookup_iface_status( - vm=vm, - iface_name=LOCALNET_BR_EX_INTERFACE, - predicate=lambda interface: ( - len(filter_link_local_addresses(ip_addresses=interface.get("ipAddresses", []))) == len(ip_families) - ), - ) + for iface_name in (LOCALNET_BR_EX_INTERFACE, LOCALNET_OVS_BRIDGE_INTERFACE): + lookup_iface_status( + vm=vm, + iface_name=iface_name, + predicate=lambda interface: ( + len(filter_link_local_addresses(ip_addresses=interface.get("ipAddresses", []))) == len(ip_families) + ), + ) return vm_a, vm_b + + +@pytest.fixture(scope="session") +def nncp_dedicated_nic_bridge_localnet_upgrade( + nmstate_dependent_placeholder: None, + admin_client: DynamicClient, + hosts_common_available_ports: list[str], +) -> Generator[libnncp.NodeNetworkConfigurationPolicy]: + with create_nncp_localnet_on_secondary_node_nic( + node_nic_name=hosts_common_available_ports[-1], + client=admin_client, + ) as nncp: + yield nncp + + +@pytest.fixture(scope="session") +def cudn_dedicated_nic_bridge_localnet_upgrade( + admin_client: DynamicClient, + nncp_dedicated_nic_bridge_localnet_upgrade: libnncp.NodeNetworkConfigurationPolicy, + namespace_localnet_upgrade: Namespace, +) -> Generator[ClusterUserDefinedNetwork]: + with localnet_cudn( + name=LOCALNET_OVS_BRIDGE_NETWORK, + match_labels=LOCALNET_TEST_LABEL, + vlan_id=cluster_vlans()[0], + physical_network_name=LOCALNET_OVS_BRIDGE_NETWORK, + client=admin_client, + ) as cudn: + cudn.wait_for_status_success() + yield cudn + + +@pytest.fixture(scope="session") +def ipv4_dedicated_nic_bridge_localnet_address_pool_upgrade() -> Generator[str]: + return (f"{random_ipv4_address(net_seed=1, host_address=host)}/24" for host in range(1, 254)) + + +@pytest.fixture(scope="session") +def ipv6_dedicated_nic_bridge_localnet_address_pool_upgrade() -> Generator[str]: + return (f"{random_ipv6_address(net_seed=1, host_address=host)}/64" for host in range(1, 254)) diff --git a/tests/network/upgrade/test_localnet_connectivity.py b/tests/network/upgrade/test_localnet_connectivity.py index 7ae428ffaa..901a178fd0 100644 --- a/tests/network/upgrade/test_localnet_connectivity.py +++ b/tests/network/upgrade/test_localnet_connectivity.py @@ -1,13 +1,15 @@ """ Localnet connectivity upgrade tests. -Verifies IPAM-less localnet VM connectivity is preserved across cluster upgrades. +Verifies IPAM-less localnet VM connectivity is preserved across cluster upgrades, +for both default bridge (br-ex) and dedicated NIC bridge. https://redhat.atlassian.net/browse/CNV-85783 Preconditions: - - OVN bridge mapping configured via NNCP - - IPAM-less localnet CUDN - - Two running VMs on different nodes with static IPs on localnet + - OVN bridge mapping configured via NNCP for the default bridge (br-ex) + - OVS bridge mapping configured via NNCP on a secondary NIC available on all worker nodes + - IPAM-less localnet CUDNs configured for both default bridge and dedicated NIC bridge + - Two running VMs on different nodes with static IPs on both localnet interfaces """ import os @@ -17,14 +19,19 @@ from libs.net.ip import filter_link_local_addresses 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.localnet import LOCALNET_BR_EX_INTERFACE +from tests.network.libs.localnet import LOCALNET_BR_EX_INTERFACE, LOCALNET_OVS_BRIDGE_INTERFACE from tests.upgrade_params import ( IUO_UPGRADE_TEST_DEPENDENCY_NODE_ID, IUO_UPGRADE_TEST_ORDERING_NODE_ID, ) from utilities.constants.pytest import DEPENDENCY_SCOPE_SESSION -BEFORE_UPGRADE_TEST_ID = f"{os.path.abspath(__file__)}::test_default_bridge_localnet_connectivity_before_upgrade" +BEFORE_UPGRADE_DEFAULT_BRIDGE_TEST_ID = ( + f"{os.path.abspath(__file__)}::test_default_bridge_localnet_connectivity_before_upgrade" +) +BEFORE_UPGRADE_DEDICATED_NIC_BRIDGE_TEST_ID = ( + f"{os.path.abspath(__file__)}::test_dedicated_nic_bridge_localnet_connectivity_before_upgrade" +) pytestmark = [ pytest.mark.upgrade, @@ -34,10 +41,10 @@ ] -@pytest.mark.single_nic @pytest.mark.polarion("CNV-16258") @pytest.mark.order(before=IUO_UPGRADE_TEST_ORDERING_NODE_ID) -@pytest.mark.dependency(name=BEFORE_UPGRADE_TEST_ID, scope=DEPENDENCY_SCOPE_SESSION) +# Post-upgrade test depends on this to skip if pre-upgrade connectivity already fails. +@pytest.mark.dependency(name=BEFORE_UPGRADE_DEFAULT_BRIDGE_TEST_ID, scope=DEPENDENCY_SCOPE_SESSION) def test_default_bridge_localnet_connectivity_before_upgrade(subtests, localnet_running_vms_upgrade): """ Preconditions: @@ -62,13 +69,41 @@ def test_default_bridge_localnet_connectivity_before_upgrade(subtests, localnet_ assert is_tcp_connection(server=server, client=client) -@pytest.mark.single_nic +@pytest.mark.polarion("CNV-16332") +@pytest.mark.order(before=IUO_UPGRADE_TEST_ORDERING_NODE_ID) +# Post-upgrade test depends on this to skip if pre-upgrade connectivity already fails. +@pytest.mark.dependency(name=BEFORE_UPGRADE_DEDICATED_NIC_BRIDGE_TEST_ID, scope=DEPENDENCY_SCOPE_SESSION) +def test_dedicated_nic_bridge_localnet_connectivity_before_upgrade(subtests, localnet_running_vms_upgrade): + """ + Preconditions: + - Two running VMs on different nodes with static IPs on localnet over dedicated NIC bridge + + Steps: + 1. Establish TCP connection between the VMs over localnet. + + Expected: + - TCP connection succeeds. + """ + vm_a, vm_b = localnet_running_vms_upgrade + iface = lookup_iface_status(vm=vm_b, iface_name=LOCALNET_OVS_BRIDGE_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=vm_a, + server_vm=vm_b, + spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, + ip_family=dst_ip.version, + ) as (client, server): + assert is_tcp_connection(server=server, client=client) + + @pytest.mark.polarion("CNV-16259") @pytest.mark.order(after=IUO_UPGRADE_TEST_ORDERING_NODE_ID) +# Requires upgrade completion and pre-upgrade baseline connectivity. @pytest.mark.dependency( depends=[ IUO_UPGRADE_TEST_DEPENDENCY_NODE_ID, - BEFORE_UPGRADE_TEST_ID, + BEFORE_UPGRADE_DEFAULT_BRIDGE_TEST_ID, ], scope=DEPENDENCY_SCOPE_SESSION, ) @@ -95,3 +130,38 @@ def test_default_bridge_localnet_connectivity_after_upgrade(subtests, localnet_r ip_family=dst_ip.version, ) as (client, server): assert is_tcp_connection(server=server, client=client) + + +@pytest.mark.polarion("CNV-16333") +@pytest.mark.order(after=IUO_UPGRADE_TEST_ORDERING_NODE_ID) +# Requires upgrade completion and pre-upgrade baseline connectivity. +@pytest.mark.dependency( + depends=[ + IUO_UPGRADE_TEST_DEPENDENCY_NODE_ID, + BEFORE_UPGRADE_DEDICATED_NIC_BRIDGE_TEST_ID, + ], + scope=DEPENDENCY_SCOPE_SESSION, +) +def test_dedicated_nic_bridge_localnet_connectivity_after_upgrade(subtests, localnet_running_vms_upgrade): + """ + Preconditions: + - Cluster upgraded successfully + - Two running VMs on different nodes with static IPs on localnet over dedicated NIC bridge + + Steps: + 1. Establish TCP connection between the VMs over localnet. + + Expected: + - TCP connection succeeds, connectivity preserved after upgrade. + """ + vm_a, vm_b = localnet_running_vms_upgrade + iface = lookup_iface_status(vm=vm_b, iface_name=LOCALNET_OVS_BRIDGE_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=vm_a, + server_vm=vm_b, + spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE, + ip_family=dst_ip.version, + ) as (client, server): + assert is_tcp_connection(server=server, client=client)