From 252125a0263345d204942cceb8b4c12425659d51 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Thu, 21 May 2026 16:39:52 +0300 Subject: [PATCH 1/5] net, libs: extend cloudinit UserData with runcmd Add runcmd field to UserData so VM fixtures can inject first-boot commands via cloud-init instead of via post-boot console calls. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- tests/network/libs/cloudinit.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/network/libs/cloudinit.py b/tests/network/libs/cloudinit.py index b7dfcef889..af4d418d9c 100644 --- a/tests/network/libs/cloudinit.py +++ b/tests/network/libs/cloudinit.py @@ -60,11 +60,16 @@ class NetworkData: class UserData: """Represents user configuration for cloud-init.""" - users: list[Any] """ Part of cloud-init's 'users and groups' module: https://cloudinit.readthedocs.io/en/latest/reference/modules.html#users-and-groups """ + users: list[Any] + """ + Commands to run on first boot: + https://cloudinit.readthedocs.io/en/latest/reference/modules.html#runcmd + """ + runcmd: list[str] | None = None def todict(no_cloud: NetworkData | UserData) -> dict[str, Any]: From 7224f074821a134d2b1e168969f77a199493a2e0 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Tue, 26 May 2026 15:19:43 +0300 Subject: [PATCH 2/5] net, libs: Add set_networks to BaseVirtualMachine Add set_networks() to BaseVirtualMachine for atomic VM network spec patches. Updates self._spec before calling ResourceEditor so the in-memory object stays consistent with the cluster without a re-fetch. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- libs/vm/vm.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/vm/vm.py b/libs/vm/vm.py index 82f86726b2..18756f7f2c 100644 --- a/libs/vm/vm.py +++ b/libs/vm/vm.py @@ -18,6 +18,7 @@ Devices, Disk, Metadata, + Network, SpecDisk, VMISpec, VMSpec, @@ -115,6 +116,19 @@ def update_template_annotations(self, template_annotations: dict[str, str]) -> N } ResourceEditor(patches=patches).update() + def set_networks(self, networks: list[Network]) -> None: + """Replace all secondary networks in the VM spec with a single atomic patch. + + Updates the in-memory spec first so the object stays consistent with the cluster + without requiring a re-fetch after the patch. + + Args: + networks: Full list of Network entries to apply (including the pod network). + """ + self._spec.template.spec.networks = networks + serialized = [asdict(obj=net, dict_factory=self._filter_out_none_values) for net in networks] + ResourceEditor(patches={self: {"spec": {"template": {"spec": {"networks": serialized}}}}}).update() + def set_template_affinity(self, affinity: Affinity | None) -> None: """Replace the VM template affinity. From abb0db230de412390df17bc064c5c1d455f5d6b2 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Tue, 26 May 2026 15:19:52 +0300 Subject: [PATCH 3/5] net, libs: Add wait for condition helpers Add Watch-API-based helpers for waiting on VMI status conditions, following the same resource-version-capture pattern as lookup_iface_status, to avoid missing transitions between the triggering action and the watch. Two separate helpers make the intent explicit: use wait_for_vmi_condition when a condition must be present with a given status, and wait_for_no_vmi_condition when it must have been cleared. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- libs/net/vmspec.py | 105 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/libs/net/vmspec.py b/libs/net/vmspec.py index 8ea78c40c3..adff3acf3d 100644 --- a/libs/net/vmspec.py +++ b/libs/net/vmspec.py @@ -3,6 +3,7 @@ from typing import Any, Final from kubernetes.dynamic.client import ResourceField +from ocp_resources.utils.resource_constants import ResourceConstants from ocp_resources.virtual_machine import VirtualMachine from timeout_sampler import retry @@ -31,6 +32,14 @@ class IpNotFound(Exception): pass +class VMIConditionNotReachedError(Exception): + pass + + +class VMIConditionStillPresentError(Exception): + pass + + def _default_interface_predicate(interface: ResourceField) -> bool: return "guest-agent" in interface["infoSource"] and interface[IP_ADDRESS] @@ -194,3 +203,99 @@ def wait_for_ifaces_status( ) ), ) + + +def wait_for_vmi_condition_status( + vm: BaseVirtualMachine, + condition: str, + status: str = ResourceConstants.Condition.Status.TRUE, + timeout: int = 300, + resource_version: str | None = None, +) -> None: + """Wait for a VMI status condition using the Kubernetes Watch API. + + Args: + vm: The virtual machine to watch. + condition: Condition type to wait for (e.g. "MigrationRequired"). + status: Expected condition status ("True" or "False"). Defaults to "True". + timeout: Maximum seconds to wait. + resource_version: VMI resource version captured before the triggering action. + Ensures no condition transitions are missed between the action and this call. + + Raises: + VMIConditionNotReachedError: If the condition is not reached within timeout. + """ + vmi = vm.vmi + vmi_instance = vmi.instance + existing_conditions = vmi_instance.status.conditions + if existing_conditions and _vmi_condition_set( + existing_conditions=existing_conditions, required_condition=condition, status=status + ): + return + + watch_from_resource_version = resource_version or vmi_instance.metadata.resourceVersion + for event in vmi.watcher(timeout=timeout, resource_version=watch_from_resource_version): + if event["type"] != "MODIFIED": + continue + existing_conditions = event["object"].status.conditions + if existing_conditions and _vmi_condition_set( + existing_conditions=existing_conditions, required_condition=condition, status=status + ): + return + + raise VMIConditionNotReachedError( + f"VMI {vm.name}: condition {condition}={status} was not reached within {timeout}s." + ) + + +def wait_for_no_vmi_condition( + vm: BaseVirtualMachine, + condition: str, + timeout: int = 300, + resource_version: str | None = None, +) -> None: + """Wait until a VMI status condition is absent or False. + + Succeeds when the condition is either removed from the array or set to False. + + Args: + vm: The virtual machine to watch. + condition: Condition type to wait on (e.g. "MigrationRequired"). + timeout: Maximum seconds to wait. + resource_version: VMI resource version captured before the triggering action. + Ensures no condition transitions are missed between the action and this call. + + Raises: + VMIConditionStillPresentError: If the condition is still True or present after timeout. + """ + vmi = vm.vmi + vmi_instance = vmi.instance + existing_conditions = vmi_instance.status.conditions + if existing_conditions and _vmi_condition_not_set( + existing_conditions=existing_conditions, required_condition=condition + ): + return + + watch_from_resource_version = resource_version or vmi_instance.metadata.resourceVersion + for event in vmi.watcher(timeout=timeout, resource_version=watch_from_resource_version): + if event["type"] != "MODIFIED": + continue + existing_conditions = event["object"].status.conditions + if existing_conditions and _vmi_condition_not_set( + existing_conditions=existing_conditions, required_condition=condition + ): + return + + raise VMIConditionStillPresentError(f"VMI {vm.name}: condition {condition} is still present after {timeout}s.") + + +def _vmi_condition_set(existing_conditions: list[ResourceField], required_condition: str, status: str) -> bool: + return any(cond.type == required_condition and cond.status == status for cond in existing_conditions) + + +def _vmi_condition_not_set(existing_conditions: list[ResourceField], required_condition: str) -> bool: + return all( + cond.status == ResourceConstants.Condition.Status.FALSE + for cond in existing_conditions + if cond.type == required_condition + ) From 735f22eaf94c9a77e53174b92f3cfd764a8ad110 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Tue, 26 May 2026 16:29:14 +0300 Subject: [PATCH 4/5] net, l2_bridge: Move LINUX_BRIDGE_IFACE_NAME constants to libl2bridge Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- tests/network/l2_bridge/libl2bridge.py | 3 +++ .../l2_bridge/vmi_interfaces_stability/lib_helpers.py | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/network/l2_bridge/libl2bridge.py b/tests/network/l2_bridge/libl2bridge.py index 458c7ceef9..b46875d601 100644 --- a/tests/network/l2_bridge/libl2bridge.py +++ b/tests/network/l2_bridge/libl2bridge.py @@ -43,6 +43,9 @@ RHCOS9_WORKER_LABEL: Final[str] = f"{NODE_ROLE_KUBERNETES_IO}/worker-rhcos9" +LINUX_BRIDGE_IFACE_NAME_1: Final[str] = "linux-bridge-1" +LINUX_BRIDGE_IFACE_NAME_2: Final[str] = "linux-bridge-2" + NETWORK_MANAGER_UNMANAGE_RUNCMD = [ 'sudo echo -e "[main]\nno-auto-default=*\nignore-carrier=*" > /etc/NetworkManager/conf.d/no-nm-ownership.conf', diff --git a/tests/network/l2_bridge/vmi_interfaces_stability/lib_helpers.py b/tests/network/l2_bridge/vmi_interfaces_stability/lib_helpers.py index c4038a6bff..488f81b3d0 100644 --- a/tests/network/l2_bridge/vmi_interfaces_stability/lib_helpers.py +++ b/tests/network/l2_bridge/vmi_interfaces_stability/lib_helpers.py @@ -1,7 +1,6 @@ import ipaddress import logging from collections.abc import Iterator -from typing import Final from kubernetes.dynamic import DynamicClient from kubernetes.dynamic.resource import ResourceField @@ -13,15 +12,13 @@ from libs.vm.factory import base_vmspec, fedora_vm from libs.vm.spec import CloudInitNoCloud, Interface, Multus, Network from libs.vm.vm import BaseVirtualMachine, add_volume_disk, cloudinitdisk_storage +from tests.network.l2_bridge.libl2bridge import LINUX_BRIDGE_IFACE_NAME_1, LINUX_BRIDGE_IFACE_NAME_2 from tests.network.libs import cloudinit from tests.network.libs.cloudinit import primary_iface_cloud_init from tests.network.localnet.liblocalnet import GUEST_1ST_IFACE_NAME, GUEST_3RD_IFACE_NAME LOGGER = logging.getLogger(__name__) -LINUX_BRIDGE_IFACE_NAME_1: Final[str] = "linux-bridge-1" -LINUX_BRIDGE_IFACE_NAME_2: Final[str] = "linux-bridge-2" - def secondary_network_vm( namespace: str, From 2d48c5d0e94b01ff4a92b87459aac8fe786648a6 Mon Sep 17 00:00:00 2001 From: Asia Khromov Date: Tue, 26 May 2026 16:31:07 +0300 Subject: [PATCH 5/5] net: NAD live-update metadata preservation test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test_vm_state_iface_info_preserved: verifies that updating the NAD reference on a running VM's secondary Linux bridge network does not alter the guest interface MAC address, name, or IP addresses. Introduces two_secondary_bridge_vm() — a shared VM factory for Linux bridge tests with multiple secondary interfaces — and the supporting fixtures. Update nad helper is saving the resource version of VMI, then updating NAD and then ensures the changes are applied by watching the conditions - MigrationRequired=True and then the condition disappears. Currently, this is the way of knowing the NAD live update was successfully applied. Signed-off-by: Asia Khromov Assisted-by: Claude Sonnet 4.6 (1M context) --- .../l2_bridge/nad_ref_change/conftest.py | 84 ++++++++++++++++ .../l2_bridge/nad_ref_change/lib_helpers.py | 99 +++++++++++++++++++ .../nad_ref_change/test_nad_ref_change.py | 48 ++++++--- 3 files changed, 218 insertions(+), 13 deletions(-) create mode 100644 tests/network/l2_bridge/nad_ref_change/conftest.py create mode 100644 tests/network/l2_bridge/nad_ref_change/lib_helpers.py diff --git a/tests/network/l2_bridge/nad_ref_change/conftest.py b/tests/network/l2_bridge/nad_ref_change/conftest.py new file mode 100644 index 0000000000..613544004a --- /dev/null +++ b/tests/network/l2_bridge/nad_ref_change/conftest.py @@ -0,0 +1,84 @@ +from collections.abc import Generator + +import pytest +from kubernetes.dynamic import DynamicClient +from ocp_resources.namespace import Namespace + +from libs.net.ip import random_cidr_addresses_by_family +from libs.net.netattachdef import CNIPluginBridgeConfig, NetConfig, NetworkAttachmentDefinition +from libs.net.vmspec import wait_for_ifaces_status +from libs.vm.vm import BaseVirtualMachine +from tests.network.l2_bridge.libl2bridge import LINUX_BRIDGE_IFACE_NAME_1, LINUX_BRIDGE_IFACE_NAME_2 +from tests.network.l2_bridge.nad_ref_change.lib_helpers import ( + NET_SEED, + two_secondary_bridge_vm, +) +from tests.network.libs import nodenetworkconfigurationpolicy as libnncp +from tests.network.libs.connectivity import ARP_ISOLATION_SYSCTL_CMD + + +@pytest.fixture(scope="module") +def bridge_nad_a( + admin_client: DynamicClient, + namespace: Namespace, + bridge_nncp: libnncp.NodeNetworkConfigurationPolicy, + vlan_index_number: Generator[int], +) -> Generator[NetworkAttachmentDefinition]: + bridge = bridge_nncp.desired_state_spec.interfaces[0].name # type: ignore + with NetworkAttachmentDefinition( + name="nad-vlan-a", + namespace=namespace.name, + config=NetConfig( + name="nad-vlan-a", plugins=[CNIPluginBridgeConfig(bridge=bridge, vlan=next(vlan_index_number))] + ), + client=admin_client, + ) as nad: + yield nad + + +@pytest.fixture(scope="module") +def bridge_nad_b( + admin_client: DynamicClient, + namespace: Namespace, + bridge_nncp: libnncp.NodeNetworkConfigurationPolicy, + vlan_index_number: Generator[int], +) -> Generator[NetworkAttachmentDefinition]: + bridge = bridge_nncp.desired_state_spec.interfaces[0].name # type: ignore[union-attr, index] + with NetworkAttachmentDefinition( + name="nad-vlan-b", + namespace=namespace.name, + config=NetConfig( + name="nad-vlan-b", plugins=[CNIPluginBridgeConfig(bridge=bridge, vlan=next(vlan_index_number))] + ), + client=admin_client, + ) as nad: + yield nad + + +@pytest.fixture(scope="class") +def under_test_vm_two_ifaces( + namespace: Namespace, + unprivileged_client: DynamicClient, + bridge_nad_a: NetworkAttachmentDefinition, +) -> Generator[BaseVirtualMachine]: + iface_a_ips = random_cidr_addresses_by_family(net_seed=NET_SEED, host_address=3) + iface_b_ips = random_cidr_addresses_by_family(net_seed=NET_SEED, host_address=4) + with two_secondary_bridge_vm( + namespace=namespace.name, + name="under-test-vm-two-ifaces", + client=unprivileged_client, + nad_names=[bridge_nad_a.name, bridge_nad_a.name], + ip_addresses=[iface_a_ips, iface_b_ips], + iface_names=[LINUX_BRIDGE_IFACE_NAME_1, LINUX_BRIDGE_IFACE_NAME_2], + runcmd=ARP_ISOLATION_SYSCTL_CMD, + ) as vm: + vm.start(wait=True) + vm.wait_for_agent_connected() + wait_for_ifaces_status( + vm=vm, + ip_addresses_by_spec_net_name={ + LINUX_BRIDGE_IFACE_NAME_1: [addr.split("/")[0] for addr in iface_a_ips], + LINUX_BRIDGE_IFACE_NAME_2: [addr.split("/")[0] for addr in iface_b_ips], + }, + ) + yield vm diff --git a/tests/network/l2_bridge/nad_ref_change/lib_helpers.py b/tests/network/l2_bridge/nad_ref_change/lib_helpers.py new file mode 100644 index 0000000000..f6b49e69d3 --- /dev/null +++ b/tests/network/l2_bridge/nad_ref_change/lib_helpers.py @@ -0,0 +1,99 @@ +from copy import deepcopy +from typing import Final + +from kubernetes.dynamic import DynamicClient + +from libs.net.vmspec import wait_for_no_vmi_condition, wait_for_vmi_condition_status +from libs.vm.factory import base_vmspec, fedora_vm +from libs.vm.spec import ( + CloudInitNoCloud, + Devices, + Interface, + Multus, + Network, +) +from libs.vm.vm import BaseVirtualMachine, add_volume_disk, cloudinitdisk_storage +from tests.network.libs import cloudinit +from tests.network.libs.cloudinit import primary_iface_cloud_init + +NET_SEED: Final[int] = 0 + + +GUEST_IFACE_1: Final[str] = "eth1" +GUEST_IFACE_2: Final[str] = "eth2" + + +def update_nad_references(vm: BaseVirtualMachine, nad_name_by_net: dict[str, str]) -> None: + """Update secondary network NAD references and wait for the change to be fully applied. + + Patches the VM spec atomically, then waits for the MigrationRequired condition to + appear (change detected) and disappear (migration completed). + + Args: + vm: The virtual machine to update. + nad_name_by_net: Mapping of interface name to new NAD name. + """ + resource_version = vm.vmi.instance.metadata.resourceVersion + networks = deepcopy(vm.template_spec.networks) or [] + for network in networks: + if network.name in nad_name_by_net and network.multus: + network.multus.networkName = nad_name_by_net[network.name] + vm.set_networks(networks=networks) + wait_for_vmi_condition_status(vm=vm, condition="MigrationRequired", resource_version=resource_version) + wait_for_no_vmi_condition(vm=vm, condition="MigrationRequired") + + +def two_secondary_bridge_vm( + namespace: str, + name: str, + client: DynamicClient, + nad_names: list[str], + ip_addresses: list[list[str]], + iface_names: list[str], + runcmd: list[str] | None = None, +) -> BaseVirtualMachine: + """Create a Fedora VM with a masquerade primary interface and bridge-bound secondary interfaces. + + Interface layout in guest OS: + eth0 = masquerade (pod network, primary — handles default route and IPv6) + eth1 = first secondary bridge interface + eth2 = second secondary bridge interface (if present) + + Args: + namespace: Namespace to deploy the VM in. + name: VM name. + client: Kubernetes dynamic client. + nad_names: NAD names (multus networkName) for the secondary interfaces, in spec order. + ip_addresses: Per-interface CIDR address lists, aligned with nad_names. + Each inner list contains one address per supported IP family. + iface_names: Logical interface names for the VM spec, aligned with nad_names. + runcmd: Commands to run on first boot via cloud-init runcmd. None means no extra commands. + """ + spec = base_vmspec() + spec.template.spec.domain.devices = Devices( + interfaces=[ + Interface(name="default", masquerade={}), + *[Interface(name=iface_name, bridge={}) for iface_name in iface_names], + ] + ) + spec.template.spec.networks = [ + Network(name="default", pod={}), + *[ + Network(name=iface_name, multus=Multus(networkName=nad_name)) + for iface_name, nad_name in zip(iface_names, nad_names) + ], + ] + ethernets = {} + if primary := primary_iface_cloud_init(): + ethernets["eth0"] = primary + for i, addresses in enumerate(ip_addresses): + ethernets[f"eth{i + 1}"] = cloudinit.EthernetDevice(addresses=addresses) + userdata = cloudinit.UserData(users=[], runcmd=runcmd) + disk, volume = cloudinitdisk_storage( + data=CloudInitNoCloud( + networkData=cloudinit.asyaml(no_cloud=cloudinit.NetworkData(ethernets=ethernets)) if ethernets else "", + userData=cloudinit.format_cloud_config(userdata=userdata), + ) + ) + spec.template.spec = add_volume_disk(vmi_spec=spec.template.spec, volume=volume, disk=disk) + return fedora_vm(namespace=namespace, name=name, client=client, spec=spec) diff --git a/tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py b/tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py index 787f3d62d4..7f42341bb2 100644 --- a/tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py +++ b/tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py @@ -13,6 +13,12 @@ import pytest +from libs.net.vmspec import lookup_iface_status +from tests.network.l2_bridge.libl2bridge import LINUX_BRIDGE_IFACE_NAME_1 +from tests.network.l2_bridge.nad_ref_change.lib_helpers import ( + update_nad_references, +) + @pytest.mark.incremental class TestRunningVMLinuxBridgeVlanChange: @@ -25,23 +31,23 @@ class TestRunningVMLinuxBridgeVlanChange: Initial (both ifaces on VLAN-A): Under-test VM Reference VM +-----------+ +-----------+ - | iface-1 |---VLAN-A -->| iface-A | - | iface-2 |---VLAN-A -->| iface-A | - +-----------+ | iface-B | - +-----------+ + | iface-1 |\\ | | + | | >--VLAN-A --| iface-1 | + | iface-2 |/ | iface-2 | + +-----------+ +-----------+ After first test (iface-1: VLAN-A -> VLAN-B): Under-test VM Reference VM +-----------+ +-----------+ - | iface-1 |---VLAN-B -->| iface-B | - | iface-2 |---VLAN-A -->| iface-A | + | iface-1 |---VLAN-B -->| iface-2 | + | iface-2 |---VLAN-A -->| iface-1 | +-----------+ +-----------+ After third test (iface-1: VLAN-B -> VLAN-A, iface-2: VLAN-A -> VLAN-B): Under-test VM Reference VM +-----------+ +-----------+ - | iface-1 |---VLAN-A -->| iface-A | - | iface-2 |---VLAN-B -->| iface-B | + | iface-1 |---VLAN-A -->| iface-1 | + | iface-2 |---VLAN-B -->| iface-2 | +-----------+ +-----------+ Preconditions: @@ -52,10 +58,12 @@ class TestRunningVMLinuxBridgeVlanChange: - No TCP connectivity between the under-test VM and the reference VM on NAD-VLAN-B """ - __test__ = False - @pytest.mark.polarion("CNV-15945") - def test_vm_state_iface_info_preserved(self): + def test_vm_state_iface_info_preserved( + self, + under_test_vm_two_ifaces, + bridge_nad_b, + ): """ Test that the under-test VM remains running and its secondary network metadata is unchanged after the NAD reference change. @@ -75,6 +83,16 @@ def test_vm_state_iface_info_preserved(self): - Guest first secondary interface MAC address, name, and IP addresses are the same before and after the NAD reference change """ + iface_before = lookup_iface_status(vm=under_test_vm_two_ifaces, iface_name=LINUX_BRIDGE_IFACE_NAME_1) + + update_nad_references( + vm=under_test_vm_two_ifaces, nad_name_by_net={LINUX_BRIDGE_IFACE_NAME_1: bridge_nad_b.name} + ) + + iface_after = lookup_iface_status(vm=under_test_vm_two_ifaces, iface_name=LINUX_BRIDGE_IFACE_NAME_1) + assert iface_after == iface_before, ( + f"Interface info changed after NAD reference update: before={iface_before}, after={iface_after}" + ) @pytest.mark.polarion("CNV-15972") def test_connectivity(self): @@ -87,14 +105,16 @@ def test_connectivity(self): - Running reference VM with secondary Linux bridge networks connected to NAD-VLAN-A and NAD-VLAN-B Steps: - 1. Poll TCP connection from the under-test VM to the reference VM on NAD-VLAN-A - 2. Poll TCP connection from the under-test VM to the reference VM on NAD-VLAN-B + 1. Poll TCP connection from the under-test VM to the reference VM on NAD-VLAN-B + 2. Poll TCP connection from the under-test VM to the reference VM on NAD-VLAN-A Expected: - Under-test VM eventually has TCP connectivity to the reference VM on NAD-VLAN-B - Under-test VM has no TCP connectivity to the reference VM on NAD-VLAN-A """ + test_connectivity.__test__ = False + @pytest.mark.polarion("CNV-15946") def test_two_networks(self): """ @@ -121,6 +141,8 @@ def test_two_networks(self): - Under-test VM second secondary network eventually has TCP connectivity to the reference VM on NAD-VLAN-B """ + test_two_networks.__test__ = False + @pytest.mark.polarion("CNV-15947") def test_non_migratable_vm_nad_change_not_applied():