Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/vm/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class VMISpec:
volumes: list[Volume] | None = None
terminationGracePeriodSeconds: int | None = None # noqa: N815
affinity: Affinity | None = None
nodeSelector: dict[str, str] | None = None # noqa: N815


@dataclass
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ markers =
rwx_default_storage: Tests that require RWX storage
descheduler: Tests that require kube-descheduler on nodes
remote_cluster: Tests that require a remote cluster
mixed_os_nodes: Tests that require a dual-stream cluster with both RHCOS 9 and RHCOS 10 worker nodes

## Required operators
mtv: Tests that require the MTV operator to be installed
Expand Down
15 changes: 15 additions & 0 deletions tests/network/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
from timeout_sampler import TimeoutExpiredError

from libs.net.cluster import ipv4_supported_cluster, ipv6_supported_cluster
from tests.network.libs.nodes import (
RHCOS_9_VERSION_PREFIX,
RHCOS_10_VERSION_PREFIX,
node_by_rhcos_version,
)
from tests.network.utils import get_vlan_index_number
from utilities.constants import (
CLUSTER,
Expand Down Expand Up @@ -321,3 +326,13 @@ def _verify_mtv_installed():
message="Network cluster verification failed",
admin_client=admin_client,
)


@pytest.fixture(scope="module")
def rhcos9_node(workers):
return node_by_rhcos_version(workers=workers, rhcos_version_prefix=RHCOS_9_VERSION_PREFIX)


@pytest.fixture(scope="module")
def rhcos10_node(workers):
return node_by_rhcos_version(workers=workers, rhcos_version_prefix=RHCOS_10_VERSION_PREFIX)
41 changes: 41 additions & 0 deletions tests/network/libs/nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Final

from ocp_resources.node import Node
from ocp_resources.resource import ResourceEditor

from libs.vm.vm import BaseVirtualMachine

HOSTNAME_LABEL: Final[str] = "kubernetes.io/hostname"
RHCOS_9_VERSION_PREFIX: Final[str] = "Red Hat Enterprise Linux CoreOS 9"
RHCOS_10_VERSION_PREFIX: Final[str] = "Red Hat Enterprise Linux CoreOS 10"


def node_by_rhcos_version(workers: list[Node], rhcos_version_prefix: str) -> Node:
"""Return the first worker node whose OS image starts with the given RHCOS version prefix.

Args:
workers: List of worker nodes to search.
rhcos_version_prefix: Expected prefix of the node osImage field (e.g. "Red Hat Enterprise Linux CoreOS 9").

Returns:
The first matching Node.

Raises:
ValueError: If no worker node matches the prefix.
"""
for node in workers:
if node.instance.status.nodeInfo.osImage.startswith(rhcos_version_prefix):
return node
raise ValueError(f"No worker node found with RHCOS version prefix: {rhcos_version_prefix!r}")


def update_vm_node_selector(vm: BaseVirtualMachine, node: Node) -> None:
"""Patch the VM spec to pin it to the given node via nodeSelector.

Args:
vm: VirtualMachine to update.
node: Target worker node.
"""
ResourceEditor(
patches={vm: {"spec": {"template": {"spec": {"nodeSelector": {HOSTNAME_LABEL: node.hostname}}}}}}
).update()
25 changes: 24 additions & 1 deletion tests/network/libs/vm_factory.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""This module provides various virtual machine configurations with a focus on network setups."""

from kubernetes.dynamic import DynamicClient
from ocp_resources.node import Node

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.vm import BaseVirtualMachine
from tests.network.libs.nodes import HOSTNAME_LABEL


def udn_vm(
Expand All @@ -15,12 +17,33 @@ def udn_vm(
binding: str,
template_labels: dict | None = None,
anti_affinity_namespaces: list[str] | None = None,
node: Node | None = None,
) -> BaseVirtualMachine:
"""Create a Fedora VM connected to a primary UDN using the specified binding.

When node is provided the VM is pinned to that node via nodeSelector and no
anti-affinity is applied. When template_labels are provided without a node,
pod anti-affinity is used for scheduling.

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.
node: If provided, pins the VM to this node via nodeSelector (takes precedence over anti-affinity).

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 template_labels:
if node is not None:
spec.template.spec.nodeSelector = {HOSTNAME_LABEL: node.hostname}
elif 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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from collections.abc import Generator

import pytest
from kubernetes.dynamic import DynamicClient
from ocp_resources.namespace import Namespace
from ocp_resources.node import Node
from ocp_resources.user_defined_network import Layer2UserDefinedNetwork

from libs.net.ip import filter_link_local_addresses
from libs.net.traffic_generator import active_tcp_connections
from libs.net.udn import UDN_BINDING_DEFAULT_PLUGIN_NAME
from libs.net.vmspec import lookup_iface_status
from libs.vm.vm import BaseVirtualMachine
from tests.network.libs.vm_factory import udn_vm

_UDN_PRIMARY_IFACE_NAME = "udn-primary"


@pytest.fixture(scope="module")
def udn_server_vm(
admin_client: DynamicClient,
udn_namespace: Namespace,
namespaced_layer2_user_defined_network: Layer2UserDefinedNetwork,
rhcos9_node: Node,
) -> Generator[BaseVirtualMachine]:
with udn_vm(
namespace_name=udn_namespace.name,
name="server-vm",
client=admin_client,
binding=UDN_BINDING_DEFAULT_PLUGIN_NAME,
node=rhcos9_node,
) 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="module")
def udn_client_vm(
admin_client: DynamicClient,
udn_namespace: Namespace,
namespaced_layer2_user_defined_network: Layer2UserDefinedNetwork,
rhcos9_node: Node,
) -> Generator[BaseVirtualMachine]:
with udn_vm(
namespace_name=udn_namespace.name,
name="client-vm",
client=admin_client,
binding=UDN_BINDING_DEFAULT_PLUGIN_NAME,
node=rhcos9_node,
) 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="module")
def udn_active_tcp_connection(
udn_client_vm: BaseVirtualMachine,
udn_server_vm: BaseVirtualMachine,
) -> Generator:
with active_tcp_connections(
client_vm=udn_client_vm,
server_vm=udn_server_vm,
iface_name=_UDN_PRIMARY_IFACE_NAME,
) as connections:
yield connections
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@

import pytest

__test__ = False
from libs.net.traffic_generator import is_tcp_connection
from tests.network.libs.nodes import update_vm_node_selector
from utilities.virt import migrate_vm_and_verify


@pytest.mark.mixed_os_nodes
@pytest.mark.ipv4
# Incremental: the second test depends on the server VM already being on RHCOS 10
# as a side effect of the first test's migration.
@pytest.mark.incremental
class TestConnectivity:
"""
Expand All @@ -23,7 +29,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,
udn_server_vm,
rhcos10_node,
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.
Expand All @@ -39,9 +50,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
"""
update_vm_node_selector(vm=udn_server_vm, node=rhcos10_node)
migrate_vm_and_verify(vm=udn_server_vm)
for client, server in 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,
udn_server_vm,
rhcos9_node,
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.
Expand All @@ -57,3 +79,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
"""
update_vm_node_selector(vm=udn_server_vm, node=rhcos9_node)
migrate_vm_and_verify(vm=udn_server_vm)
for client, server in 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"
)