Skip to content
Closed
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from ocp_resources.resource import Resource, ResourceEditor
from timeout_sampler import retry

from tests.network.libs.apimachinery import dict_normalization_for_dataclass
from libs.net.apimachinery import dict_normalization_for_dataclass

WAIT_FOR_STATUS_TIMEOUT_SEC = 120
WAIT_FOR_STATUS_INTERVAL_SEC = 5
Expand Down
37 changes: 35 additions & 2 deletions libs/net/vmspec.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ipaddress
from collections.abc import Callable
from copy import deepcopy
from typing import Any, Final

from kubernetes.dynamic.client import ResourceField
Expand Down Expand Up @@ -206,7 +207,7 @@ def wait_for_ifaces_status(


def wait_for_vmi_condition_status(
vm: BaseVirtualMachine,
vm: VirtualMachine,
condition: str,
status: str = ResourceConstants.Condition.Status.TRUE,
timeout: int = 300,
Expand Down Expand Up @@ -249,7 +250,7 @@ def wait_for_vmi_condition_status(


def wait_for_no_vmi_condition(
vm: BaseVirtualMachine,
vm: VirtualMachine,
condition: str,
timeout: int = 300,
resource_version: str | None = None,
Expand Down Expand Up @@ -299,3 +300,35 @@ def _vmi_condition_not_set(existing_conditions: list[ResourceField], required_co
for cond in existing_conditions
if cond.type == required_condition
)


def update_nad_references(vm: BaseVirtualMachine, nad_name_by_net: dict[str, str]) -> None:
"""Update secondary 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.
"""
if not nad_name_by_net:
raise ValueError(f"NAD update mapping is empty for VM {vm.name}")
resource_version = vm.vmi.instance.metadata.resourceVersion
networks = vm.template_spec.networks
if not networks:
raise ValueError(f"VM {vm.name} has no template networks to update")
networks = deepcopy(networks)
updated_names = set()
for network in networks:
if network.name in nad_name_by_net:
if not network.multus:
raise ValueError(f"Network {network.name!r} on VM {vm.name} is not a Multus network")
network.multus.networkName = nad_name_by_net[network.name]
updated_names.add(network.name)
missing = set(nad_name_by_net) - updated_names
if missing:
raise ValueError(f"NAD update requested for unknown networks {sorted(missing)} on VM {vm.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")
87 changes: 86 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import tempfile
from bisect import bisect_left
from collections import defaultdict
from collections.abc import Generator
from datetime import UTC, datetime
from signal import SIGINT, SIGTERM, getsignal, signal

Expand All @@ -23,6 +24,7 @@
import requests
import yaml
from bs4 import BeautifulSoup
from kubernetes.dynamic import DynamicClient
from kubernetes.dynamic.exceptions import ResourceNotFoundError
from ocp_resources.application_aware_resource_quota import ApplicationAwareResourceQuota
from ocp_resources.catalog_source import CatalogSource
Expand Down Expand Up @@ -69,10 +71,12 @@
from timeout_sampler import TimeoutSampler

import utilities.hco
from libs.net import nodenetworkconfigurationpolicy as libnncp
from libs.net.cluster import ipv4_supported_cluster, ipv6_supported_cluster
from libs.net.ip import filter_link_local_addresses, random_ipv4_address, random_ipv6_address
from libs.net.netattachdef import CNIPluginBridgeConfig, NetConfig, NetworkAttachmentDefinition
from libs.net.vmspec import lookup_iface_status
from tests.utils import download_and_extract_tar
from tests.utils import download_and_extract_tar, get_vlan_index_number
from utilities.artifactory import get_artifactory_header, get_http_image_url, get_test_artifact_server_url
from utilities.bitwarden import get_cnv_tests_secret_by_name
from utilities.cluster import cache_admin_client, get_oc_whoami_username
Expand Down Expand Up @@ -2739,3 +2743,84 @@ def hugepages_gib_values(workers):
for worker in workers
if (value := worker.instance.status.allocatable.get(NODE_HUGE_PAGES_1GI_KEY))
]


@pytest.fixture(scope="package")
def bridge_nncp(
nmstate_dependent_placeholder: None,
admin_client: DynamicClient,
hosts_common_available_ports: list[str],
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
if not hosts_common_available_ports:
raise ValueError("No common worker NICs available for bridge_nncp fixture")
with libnncp.NodeNetworkConfigurationPolicy(
client=admin_client,
name="l2-bridge-test-nncp",
desired_state=libnncp.DesiredState(
interfaces=[
libnncp.Interface(
name="br1-test",
type=LINUX_BRIDGE,
state=libnncp.Resource.Interface.State.UP,
bridge=libnncp.Bridge(
port=[libnncp.Port(name=hosts_common_available_ports[-1])],
options=libnncp.BridgeOptions(libnncp.STP(enabled=False)),
),
)
]
),
node_selector={WORKER_NODE_LABEL_KEY: ""},
) as nncp_br:
nncp_br.wait_for_status_success()
yield nncp_br


@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 vlan_index_number(vlans_list):
return get_vlan_index_number(vlans_list=vlans_list)


@pytest.fixture(scope="session")
def vlans_list():
vlans = py_config["vlans"]
if not isinstance(vlans, list):
vlans = vlans.split(",")
return [int(_id) for _id in vlans]


@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
2 changes: 1 addition & 1 deletion tests/network/bgp/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
from ocp_resources.node import Node

from libs.net import netattachdef as libnad
from libs.net import nodenetworkconfigurationpolicy as libnncp
from libs.net.ip import random_ipv4_address
from libs.net.traffic_generator import PodTcpClient as TcpClient
from libs.net.traffic_generator import TcpServer
from libs.net.udn import UDN_BINDING_DEFAULT_PLUGIN_NAME, create_udn_namespace
from libs.net.vmspec import lookup_iface_status_ip, lookup_primary_network
from libs.vm.vm import BaseVirtualMachine
from tests.network.libs import cluster_user_defined_network as libcudn
from tests.network.libs import nodenetworkconfigurationpolicy as libnncp
from tests.network.libs.bgp import (
EXTERNAL_FRR_POD_LABEL,
NET_TOOLS_CONTAINER_NAME,
Expand Down
15 changes: 0 additions & 15 deletions tests/network/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@
from ocp_resources.network_config_openshift_io import Network
from ocp_resources.performance_profile import PerformanceProfile
from ocp_resources.pod import Pod
from pytest_testconfig import config as py_config
from timeout_sampler import TimeoutExpiredError

from libs.net.cluster import ipv4_supported_cluster, ipv6_supported_cluster
from tests.network.utils import get_vlan_index_number
from utilities.constants import (
CLUSTER,
CLUSTER_NETWORK_ADDONS_OPERATOR,
Expand Down Expand Up @@ -95,19 +93,6 @@ def sriov_workers_node2(sriov_workers):
return sriov_workers[1]


@pytest.fixture(scope="session")
def vlans_list():
vlans = py_config["vlans"]
if not isinstance(vlans, list):
vlans = vlans.split(",")
return [int(_id) for _id in vlans]


@pytest.fixture(scope="module")
def vlan_index_number(vlans_list):
return get_vlan_index_number(vlans_list=vlans_list)


@pytest.fixture(scope="session")
def cluster_network_mtu(admin_client):
network_resource = Network(name=CLUSTER, client=admin_client)
Expand Down
2 changes: 1 addition & 1 deletion tests/network/l2_bridge/bandwidth/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from kubernetes.dynamic import DynamicClient
from ocp_resources.namespace import Namespace

import tests.network.libs.nodenetworkconfigurationpolicy as libnncp
from libs.net import nodenetworkconfigurationpolicy as libnncp
from libs.net.ip import random_ip_addresses_by_family
from libs.net.netattachdef import (
CNIPluginBandwidthConfig,
Expand Down
31 changes: 1 addition & 30 deletions tests/network/l2_bridge/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from kubernetes.dynamic import DynamicClient
from pyhelper_utils.shell import run_ssh_commands

import tests.network.libs.nodenetworkconfigurationpolicy as libnncp
from libs.net import nodenetworkconfigurationpolicy as libnncp
from libs.net.ip import random_ipv4_address
from libs.net.netattachdef import CNIPluginBridgeConfig, NetConfig, NetworkAttachmentDefinition
from tests.network.l2_bridge.libl2bridge import DHCP_INTERFACE_NAME, bridge_attached_vm
Expand All @@ -18,7 +18,6 @@
UNIQUE_CLIENT_ID,
verify_dhcpd_activated,
)
from utilities.constants import LINUX_BRIDGE, WORKER_NODE_LABEL_KEY
from utilities.data_utils import name_prefix
from utilities.infra import get_node_selector_dict
from utilities.network import (
Expand Down Expand Up @@ -288,34 +287,6 @@ def started_vmb_dhcp_client(l2_bridge_running_vm_b, eth3_nmcli_connection_uuid):
)


@pytest.fixture(scope="package")
def bridge_nncp(
nmstate_dependent_placeholder: None,
admin_client: DynamicClient,
hosts_common_available_ports: list[str],
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
with libnncp.NodeNetworkConfigurationPolicy(
client=admin_client,
name="l2-bridge-test-nncp",
desired_state=libnncp.DesiredState(
interfaces=[
libnncp.Interface(
name="br1-test",
type=LINUX_BRIDGE,
state=libnncp.Resource.Interface.State.UP,
bridge=libnncp.Bridge(
port=[libnncp.Port(name=hosts_common_available_ports[-1])],
options=libnncp.BridgeOptions(libnncp.STP(enabled=False)),
),
)
]
),
node_selector={WORKER_NODE_LABEL_KEY: ""},
) as nncp_br:
nncp_br.wait_for_status_success()
yield nncp_br


@pytest.fixture(scope="class")
def bridge_nad(
admin_client: DynamicClient,
Expand Down
2 changes: 1 addition & 1 deletion tests/network/l2_bridge/migration_stuntime/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from kubernetes.dynamic import DynamicClient
from ocp_resources.namespace import Namespace

import tests.network.libs.nodenetworkconfigurationpolicy as libnncp
from libs.net import netattachdef as libnad
from libs.net import nodenetworkconfigurationpolicy as libnncp
from libs.net.ip import random_ipv4_address, random_ipv6_address
from libs.net.vmspec import lookup_iface_status_ip
from libs.vm.affinity import new_pod_affinity
Expand Down
41 changes: 1 addition & 40 deletions tests/network/l2_bridge/nad_ref_change/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ocp_resources.namespace import Namespace

from libs.net.ip import filter_link_local_addresses, random_cidr_addresses_by_family
from libs.net.netattachdef import CNIPluginBridgeConfig, NetConfig, NetworkAttachmentDefinition
from libs.net.netattachdef import NetworkAttachmentDefinition
from libs.net.vmspec import lookup_iface_status, 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
Expand All @@ -15,48 +15,9 @@
NET_SEED,
two_secondary_bridge_vm,
)
from tests.network.libs import nodenetworkconfigurationpolicy as libnncp
from tests.network.libs.connectivity import ARP_ISOLATION_SYSCTL_CMD, poll_tcp_connectivity


@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="module")
def ref_vm(
namespace: Namespace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
import pytest

from libs.net.ip import filter_link_local_addresses
from libs.net.vmspec import lookup_iface_status
from libs.net.vmspec import lookup_iface_status, update_nad_references
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 (
GUEST_IFACE_1,
GUEST_IFACE_2,
assert_connectivity,
assert_no_connectivity,
)
from tests.network.libs.nad_ref import update_nad_references


@pytest.mark.usefixtures("baseline_connectivity")
Expand Down
2 changes: 1 addition & 1 deletion tests/network/libs/cloudinit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import yaml

from libs.net.apimachinery import dict_normalization_for_dataclass
from libs.net.cluster import ipv4_supported_cluster, ipv6_supported_cluster
from tests.network.libs.apimachinery import dict_normalization_for_dataclass

NETWORK_DATA: Final[str] = "networkData"

Expand Down
2 changes: 1 addition & 1 deletion tests/network/libs/cluster_user_defined_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from kubernetes.dynamic import DynamicClient
from ocp_resources.cluster_user_defined_network import ClusterUserDefinedNetwork as Cudn

from tests.network.libs.apimachinery import dict_normalization_for_dataclass
from libs.net.apimachinery import dict_normalization_for_dataclass
from tests.network.libs.label_selector import LabelSelector


Expand Down
Loading