-
Notifications
You must be signed in to change notification settings - Fork 68
net: Introduce dual-stream linux bridge tests #4772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a26c2d3
700f0ba
3088816
972e888
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commit message: Can you please reference some kind of proof from openshift? It's far from intuitive and I need to believe this otherwise I can't trust that you're migrating to/from the right nodes. In addition: "Co-Authored-By" -> "Assited by"
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shows each worker node alongside its OS image and whether it has the worker-rhcos9 label:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it in VMI spec and not in VM spec?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
|
|
||
| @dataclass | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import ipaddress | ||
| from collections.abc import Generator | ||
| from typing import Final | ||
|
|
||
| import pytest | ||
| from kubernetes.dynamic import DynamicClient | ||
| from ocp_resources.namespace import Namespace | ||
|
|
||
| import tests.network.libs.nodenetworkconfigurationpolicy as libnncp | ||
| from libs.net.ip import random_cidr_addresses_by_family | ||
| from libs.net.netattachdef import CNIPluginBridgeConfig, NetConfig, NetworkAttachmentDefinition | ||
| from libs.net.traffic_generator import TcpServer, VMTcpClient, active_tcp_connections | ||
| from libs.net.vmspec import wait_for_ifaces_status | ||
| from libs.vm.vm import BaseVirtualMachine | ||
| from tests.network.l2_bridge.rhel9_rhel10_cluster.lib_helpers import LINUX_BRIDGE_IFACE_NAME, bridge_vm | ||
|
|
||
| _SERVER_HOST_ADDRESS: Final[int] = 1 | ||
| _CLIENT_HOST_ADDRESS: Final[int] = 2 | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def dual_stream_bridge_nad( | ||
| admin_client: DynamicClient, | ||
| namespace: Namespace, | ||
| bridge_nncp: libnncp.NodeNetworkConfigurationPolicy, | ||
| ) -> Generator[NetworkAttachmentDefinition]: | ||
|
azhivovk marked this conversation as resolved.
|
||
| config = NetConfig( | ||
| name="rhel9-rhel10-bridge-nad", | ||
| plugins=[CNIPluginBridgeConfig(bridge=bridge_nncp.desired_state_spec.interfaces[0].name)], # type: ignore | ||
| ) | ||
| with NetworkAttachmentDefinition( | ||
| name="rhel9-rhel10-bridge-nad", | ||
| namespace=namespace.name, | ||
| config=config, | ||
| client=admin_client, | ||
| ) as nad: | ||
| yield nad | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def bridge_server_vm( | ||
| unprivileged_client: DynamicClient, | ||
| namespace: Namespace, | ||
| dual_stream_bridge_nad: NetworkAttachmentDefinition, | ||
| ) -> Generator[BaseVirtualMachine]: | ||
| addresses = random_cidr_addresses_by_family(net_seed=0, host_address=_SERVER_HOST_ADDRESS) | ||
| with bridge_vm( | ||
| namespace=namespace.name, | ||
| name="server-vm", | ||
| client=unprivileged_client, | ||
| bridge_network_name=dual_stream_bridge_nad.name, | ||
| addresses=addresses, | ||
| ) 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: [str(ipaddress.ip_interface(addr).ip) for addr in addresses] | ||
| }, | ||
| ) | ||
| yield vm | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def bridge_client_vm( | ||
| unprivileged_client: DynamicClient, | ||
| namespace: Namespace, | ||
| dual_stream_bridge_nad: NetworkAttachmentDefinition, | ||
| ) -> Generator[BaseVirtualMachine]: | ||
| addresses = random_cidr_addresses_by_family(net_seed=0, host_address=_CLIENT_HOST_ADDRESS) | ||
| with bridge_vm( | ||
| namespace=namespace.name, | ||
| name="client-vm", | ||
| client=unprivileged_client, | ||
| bridge_network_name=dual_stream_bridge_nad.name, | ||
| addresses=addresses, | ||
| ) 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: [str(ipaddress.ip_interface(addr).ip) for addr in addresses] | ||
| }, | ||
| ) | ||
| yield vm | ||
|
azhivovk marked this conversation as resolved.
|
||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def bridge_active_tcp_connection( | ||
| bridge_client_vm: BaseVirtualMachine, | ||
| bridge_server_vm: BaseVirtualMachine, | ||
| ) -> Generator[list[tuple[VMTcpClient, TcpServer]]]: | ||
| with active_tcp_connections( | ||
| client_vm=bridge_client_vm, | ||
| server_vm=bridge_server_vm, | ||
| iface_name=LINUX_BRIDGE_IFACE_NAME, | ||
| ) as connections: | ||
| yield connections | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| from typing import Final | ||
|
|
||
| from kubernetes.dynamic import DynamicClient | ||
|
|
||
| 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 EthernetDevice | ||
| from tests.network.libs.vm_scheduling import RHCOS9_NODE_SELECTOR | ||
|
|
||
| LINUX_BRIDGE_IFACE_NAME: Final[str] = "linux-bridge-1" | ||
|
|
||
|
|
||
| def bridge_vm( | ||
| namespace: str, | ||
| name: str, | ||
| client: DynamicClient, | ||
| bridge_network_name: str, | ||
| addresses: list[str], | ||
| ) -> BaseVirtualMachine: | ||
| """Create a Fedora VM with a primary masquerade and a secondary Linux bridge interface. | ||
|
|
||
| The VM is scheduled on RHCOS 9 worker nodes via the worker-rhcos9 role label. | ||
| Pass the same addresses list to wait_for_ifaces_status so that expected IPs | ||
| are derived from the same configuration used to create the VM. | ||
|
|
||
| Args: | ||
| namespace: Namespace in which the VM will be created. | ||
| name: Name of the VM. | ||
| client: Kubernetes dynamic client. | ||
| bridge_network_name: Name of the NetworkAttachmentDefinition for the bridge. | ||
| addresses: CIDR addresses for the secondary interface (e.g. ["192.168.1.1/24"]). | ||
|
|
||
| Returns: | ||
| Configured BaseVirtualMachine object (not yet started). | ||
| """ | ||
| spec = base_vmspec() | ||
| spec.template.spec.domain.devices = Devices( | ||
| interfaces=[ | ||
| Interface(name="default", masquerade={}), | ||
| Interface(name=LINUX_BRIDGE_IFACE_NAME, bridge={}), | ||
| ] | ||
| ) | ||
| spec.template.spec.networks = [ | ||
| Network(name="default", pod={}), | ||
| Network(name=LINUX_BRIDGE_IFACE_NAME, multus=Multus(networkName=bridge_network_name)), | ||
| ] | ||
| spec.template.spec.nodeSelector = RHCOS9_NODE_SELECTOR | ||
|
|
||
| userdata = cloudinit.UserData(users=[]) | ||
| disk, volume = cloudinitdisk_storage( | ||
| data=CloudInitNoCloud( | ||
| networkData=cloudinit.asyaml( | ||
| no_cloud=cloudinit.NetworkData(ethernets={"eth1": EthernetDevice(addresses=addresses)}) | ||
| ), | ||
| 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) | ||
|
azhivovk marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| from typing import Final | ||
|
|
||
| from ocp_resources.resource import ResourceEditor | ||
|
|
||
| from libs.vm.vm import BaseVirtualMachine | ||
|
|
||
| RHCOS9_WORKER_LABEL: Final[str] = "node-role.kubernetes.io/worker-rhcos9" | ||
| WORKER_LABEL: Final[str] = "node-role.kubernetes.io/worker" | ||
|
|
||
| RHCOS9_NODE_SELECTOR: Final[dict[str, str]] = {RHCOS9_WORKER_LABEL: ""} | ||
|
|
||
|
|
||
| def set_vm_node_selector(vm: BaseVirtualMachine, label: str) -> None: | ||
| """Set a nodeSelector on the VM to schedule it on nodes carrying the given label. | ||
|
|
||
| Clears any existing nodeAffinity so only the nodeSelector is active. | ||
|
|
||
| Args: | ||
| vm: VirtualMachine to update. | ||
| label: Node role label key (e.g. "node-role.kubernetes.io/worker-rhcos9"). | ||
| """ | ||
| ResourceEditor( | ||
| patches={vm: {"spec": {"template": {"spec": {"nodeSelector": {label: ""}, "affinity": None}}}}} | ||
| ).update() | ||
|
|
||
|
|
||
| def set_vm_node_affinity(vm: BaseVirtualMachine, excluded_label: str) -> None: | ||
| """Set a nodeAffinity on the VM to schedule it on worker nodes NOT carrying the given label. | ||
|
|
||
| Clears any existing nodeSelector so only the nodeAffinity is active. | ||
|
|
||
| Args: | ||
| vm: VirtualMachine to update. | ||
| excluded_label: Node role label key that target nodes must NOT have. | ||
| """ | ||
| ResourceEditor( | ||
| patches={ | ||
| vm: { | ||
| "spec": { | ||
| "template": { | ||
| "spec": { | ||
| "nodeSelector": None, | ||
| "affinity": { | ||
| "nodeAffinity": { | ||
| "requiredDuringSchedulingIgnoredDuringExecution": { | ||
| "nodeSelectorTerms": [ | ||
| { | ||
| "matchExpressions": [ | ||
| {"key": excluded_label, "operator": "DoesNotExist"}, | ||
| {"key": WORKER_LABEL, "operator": "Exists"}, | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| }, | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ).update() |
Uh oh!
There was an error while loading. Please reload this page.