From 3c2ef607257fa4a29a042f58901932c31d0055bf Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Mon, 1 Jun 2026 13:03:38 +0300 Subject: [PATCH 1/2] net, udn: Extend udn_vm with optional affinity param The new primary UDN tests need to pin VMs to specific RHCOS node types during live migration, requiring explicit node affinity support in udn_vm. Add affinity parameter. template_labels and affinity are independent: labels are always applied when provided; affinity takes precedence over the auto-generated pod anti-affinity from template_labels, but does not suppress the labels. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- tests/network/libs/vm_factory.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/network/libs/vm_factory.py b/tests/network/libs/vm_factory.py index b4e12b23d7..532088e105 100644 --- a/tests/network/libs/vm_factory.py +++ b/tests/network/libs/vm_factory.py @@ -5,6 +5,7 @@ from libs.net.udn import udn_primary_network from libs.vm.affinity import new_pod_anti_affinity from libs.vm.factory import base_vmspec, fedora_vm +from libs.vm.spec import Affinity from libs.vm.vm import BaseVirtualMachine @@ -15,16 +16,34 @@ def udn_vm( binding: str, template_labels: dict | None = None, anti_affinity_namespaces: list[str] | None = None, + affinity: Affinity | None = None, ) -> BaseVirtualMachine: + """Create a Fedora VM connected to a primary UDN using the specified binding. + + Args: + namespace_name: Namespace in which the VM will be created. + name: Name of the VM. + client: Kubernetes dynamic client. + binding: UDN binding plugin name (e.g. UDN_BINDING_DEFAULT_PLUGIN_NAME). + template_labels: Optional labels to add to the VM pod template, also used as anti-affinity key. + anti_affinity_namespaces: Optional namespaces to scope the pod anti-affinity rule. + affinity: Optional explicit affinity rules. When set, takes precedence over auto-generated anti-affinity from template_labels. + + Returns: + Configured BaseVirtualMachine object (not yet started). + """ spec = base_vmspec() iface, network = udn_primary_network(name="udn-primary", binding=binding) spec.template.spec.domain.devices.interfaces = [iface] # type: ignore spec.template.spec.networks = [network] + if affinity is not None: + spec.template.spec.affinity = affinity if template_labels: spec.template.metadata.labels = spec.template.metadata.labels or {} # type: ignore spec.template.metadata.labels.update(template_labels) # type: ignore - # Use the first label key and first value as the anti-affinity label to use: - label, *_ = template_labels.items() - spec.template.spec.affinity = new_pod_anti_affinity(label=label, namespaces=anti_affinity_namespaces) + if affinity is None: + # Use the first label key and first value as the anti-affinity label to use: + label, *_ = template_labels.items() + spec.template.spec.affinity = new_pod_anti_affinity(label=label, namespaces=anti_affinity_namespaces) return fedora_vm(namespace=namespace_name, name=name, client=client, spec=spec) From 03381cbf03376289468d687b5aaba4e9fecbbba3 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Mon, 1 Jun 2026 13:03:44 +0300 Subject: [PATCH 2/2] net: Add primary UDN connectivity tests The l2bridge UDN binding depends on components that may behave differently across RHCOS versions. These tests verify that an active TCP connection over a primary UDN is preserved when the server VM migrates between RHCOS 9 and RHCOS 10 nodes in both directions. Tests cover IPv4 only - IPv6 is not yet officially supported in primary UDN. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- .../rhel9_rhel10_cluster/conftest.py | 82 +++++++++++++++++++ .../rhel9_rhel10_cluster/test_connectivity.py | 33 +++++++- 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 tests/network/user_defined_network/rhel9_rhel10_cluster/conftest.py diff --git a/tests/network/user_defined_network/rhel9_rhel10_cluster/conftest.py b/tests/network/user_defined_network/rhel9_rhel10_cluster/conftest.py new file mode 100644 index 0000000000..3adc1aa70b --- /dev/null +++ b/tests/network/user_defined_network/rhel9_rhel10_cluster/conftest.py @@ -0,0 +1,82 @@ +from collections.abc import Generator + +import pytest +from kubernetes.dynamic import DynamicClient +from ocp_resources.namespace import Namespace +from ocp_resources.user_defined_network import Layer2UserDefinedNetwork + +from libs.net.ip import filter_link_local_addresses +from libs.net.traffic_generator import TcpServer, VMTcpClient, client_server_active_connection +from libs.net.udn import UDN_BINDING_DEFAULT_PLUGIN_NAME +from libs.net.vmspec import lookup_iface_status +from libs.vm.affinity import new_node_affinity +from libs.vm.vm import BaseVirtualMachine +from tests.network.libs.nodes import RHCOS9_WORKER_LABEL +from tests.network.libs.vm_factory import udn_vm + +_UDN_PRIMARY_IFACE_NAME = "udn-primary" + + +@pytest.fixture(scope="class") +def udn_server_vm( + admin_client: DynamicClient, + udn_namespace: Namespace, + namespaced_layer2_user_defined_network: Layer2UserDefinedNetwork, +) -> Generator[BaseVirtualMachine]: + with udn_vm( + namespace_name=udn_namespace.name, + name="server-vm", + client=admin_client, + binding=UDN_BINDING_DEFAULT_PLUGIN_NAME, + affinity=new_node_affinity(key=RHCOS9_WORKER_LABEL, exists=True), + ) as vm: + vm.start(wait=True) + vm.wait_for_agent_connected() + lookup_iface_status( + vm=vm, + iface_name=_UDN_PRIMARY_IFACE_NAME, + predicate=lambda iface: ( + "guest-agent" in iface["infoSource"] + and bool(filter_link_local_addresses(ip_addresses=iface.get("ipAddresses", []))) + ), + ) + yield vm + + +@pytest.fixture(scope="class") +def udn_client_vm( + admin_client: DynamicClient, + udn_namespace: Namespace, + namespaced_layer2_user_defined_network: Layer2UserDefinedNetwork, +) -> Generator[BaseVirtualMachine]: + with udn_vm( + namespace_name=udn_namespace.name, + name="client-vm", + client=admin_client, + binding=UDN_BINDING_DEFAULT_PLUGIN_NAME, + affinity=new_node_affinity(key=RHCOS9_WORKER_LABEL, exists=True), + ) as vm: + vm.start(wait=True) + vm.wait_for_agent_connected() + lookup_iface_status( + vm=vm, + iface_name=_UDN_PRIMARY_IFACE_NAME, + predicate=lambda iface: ( + "guest-agent" in iface["infoSource"] + and bool(filter_link_local_addresses(ip_addresses=iface.get("ipAddresses", []))) + ), + ) + yield vm + + +@pytest.fixture(scope="class") +def udn_active_tcp_connection( + udn_client_vm: BaseVirtualMachine, + udn_server_vm: BaseVirtualMachine, +) -> Generator[tuple[VMTcpClient, TcpServer]]: + with client_server_active_connection( + client_vm=udn_client_vm, + server_vm=udn_server_vm, + spec_logical_network=_UDN_PRIMARY_IFACE_NAME, + ) as connection: + yield connection diff --git a/tests/network/user_defined_network/rhel9_rhel10_cluster/test_connectivity.py b/tests/network/user_defined_network/rhel9_rhel10_cluster/test_connectivity.py index fb75b56a1f..b29f7a3759 100644 --- a/tests/network/user_defined_network/rhel9_rhel10_cluster/test_connectivity.py +++ b/tests/network/user_defined_network/rhel9_rhel10_cluster/test_connectivity.py @@ -10,9 +10,14 @@ import pytest -__test__ = False +from libs.net.traffic_generator import is_tcp_connection +from libs.vm.affinity import new_node_affinity +from tests.network.libs.nodes import RHCOS9_WORKER_LABEL +from utilities.virt import migrate_vm_and_verify +@pytest.mark.mixed_os_nodes +@pytest.mark.ipv4 @pytest.mark.incremental class TestConnectivity: """ @@ -23,7 +28,12 @@ class TestConnectivity: """ @pytest.mark.polarion("CNV-15952") - def test_connectivity_preserved_during_server_migration_to_rhcos10(self): + def test_connectivity_preserved_during_server_migration_to_rhcos10( + self, + admin_client, + udn_server_vm, + udn_active_tcp_connection, + ): """ Test that an active TCP connection over a primary UDN is preserved when the server VM migrates from an RHCOS 9 node to an RHCOS 10 node. @@ -39,9 +49,20 @@ def test_connectivity_preserved_during_server_migration_to_rhcos10(self): Expected: - The active TCP connection from the client VM to the server VM is preserved during the migration """ + udn_server_vm.set_template_affinity(affinity=new_node_affinity(key=RHCOS9_WORKER_LABEL, exists=False)) + migrate_vm_and_verify(vm=udn_server_vm, client=admin_client) + client, server = udn_active_tcp_connection + assert is_tcp_connection(server=server, client=client), ( + f"TCP connection lost after migrating {udn_server_vm.name} to RHCOS 10 node" + ) @pytest.mark.polarion("CNV-15965") - def test_connectivity_preserved_during_server_migration_to_rhcos9(self): + def test_connectivity_preserved_during_server_migration_to_rhcos9( + self, + admin_client, + udn_server_vm, + udn_active_tcp_connection, + ): """ Test that an active TCP connection over a primary UDN is preserved when the server VM migrates from an RHCOS 10 node to an RHCOS 9 node. @@ -57,3 +78,9 @@ def test_connectivity_preserved_during_server_migration_to_rhcos9(self): Expected: - The active TCP connection from the client VM to the server VM is preserved during the migration """ + udn_server_vm.set_template_affinity(affinity=new_node_affinity(key=RHCOS9_WORKER_LABEL, exists=True)) + migrate_vm_and_verify(vm=udn_server_vm, client=admin_client) + client, server = udn_active_tcp_connection + assert is_tcp_connection(server=server, client=client), ( + f"TCP connection lost after migrating {udn_server_vm.name} back to RHCOS 9 node" + )