Skip to content

Commit 21a8ef7

Browse files
authored
Merge branch 'main' into kubevirt_vm_labels
2 parents 9747a01 + 3984f5a commit 21a8ef7

6 files changed

Lines changed: 160 additions & 62 deletions

File tree

libs/net/vmspec.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from kubernetes.dynamic.client import ResourceField
55
from timeout_sampler import TimeoutExpiredError, TimeoutSampler, retry
66

7-
from libs.vm.spec import Devices, Interface, Network, SpecDisk, VMISpec, Volume
7+
from libs.vm.spec import Devices, Network, SpecDisk, VMISpec, Volume
88
from libs.vm.vm import BaseVirtualMachine
99

1010
LOOKUP_IFACE_STATUS_TIMEOUT_SEC: Final[int] = 30
@@ -124,15 +124,6 @@ def lookup_primary_network(vm: BaseVirtualMachine) -> Network:
124124
raise VMInterfaceSpecNotFoundError(f"No interface connected to the primary network was found in VM {vm.name}.")
125125

126126

127-
def add_network_interface(vmi_spec: VMISpec, network: Network, interface: Interface) -> VMISpec:
128-
vmi_spec.networks = vmi_spec.networks or []
129-
vmi_spec.networks.append(network)
130-
vmi_spec.domain.devices = vmi_spec.domain.devices or Devices()
131-
vmi_spec.domain.devices.interfaces = vmi_spec.domain.devices.interfaces or []
132-
vmi_spec.domain.devices.interfaces.append(interface)
133-
return vmi_spec
134-
135-
136127
def add_volume_disk(vmi_spec: VMISpec, volume: Volume, disk: SpecDisk) -> VMISpec:
137128
vmi_spec.volumes = vmi_spec.volumes or []
138129
vmi_spec.volumes.append(volume)

tests/network/libs/cluster_user_defined_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ class Role(Enum):
4949

5050
role: str
5151
physicalNetworkName: str # noqa: N815
52-
vlan: Vlan
5352
ipam: Ipam
53+
vlan: Vlan | None = None
5454

5555

5656
@dataclass

tests/network/localnet/conftest.py

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
from libs.net.traffic_generator import TcpServer
1010
from libs.net.traffic_generator import VMTcpClient as TcpClient
1111
from libs.net.vmspec import lookup_iface_status
12+
from libs.vm.spec import Interface, Multus, Network
1213
from libs.vm.vm import BaseVirtualMachine
14+
from tests.network.libs import cloudinit
1315
from tests.network.libs import cluster_user_defined_network as libcudn
1416
from tests.network.libs.ip import random_ipv4_address
1517
from tests.network.localnet.liblocalnet import (
1618
LINK_STATE_DOWN,
19+
LOCALNET_BR_EX_INTERFACE,
20+
LOCALNET_BR_EX_INTERFACE_NO_VLAN,
1721
LOCALNET_BR_EX_NETWORK,
22+
LOCALNET_BR_EX_NETWORK_NO_VLAN,
23+
LOCALNET_OVS_BRIDGE_INTERFACE,
1824
LOCALNET_OVS_BRIDGE_NETWORK,
1925
LOCALNET_TEST_LABEL,
2026
client_server_active_connection,
@@ -31,6 +37,7 @@
3137
from utilities.virt import migrate_vm_and_verify
3238

3339
NNCP_INTERFACE_TYPE_OVS_BRIDGE = "ovs-bridge"
40+
PRIMARY_INTERFACE_NAME = "eth0"
3441

3542

3643
@pytest.fixture(scope="module")
@@ -95,25 +102,61 @@ def cudn_localnet(
95102
yield cudn
96103

97104

105+
@pytest.fixture(scope="module")
106+
def cudn_localnet_no_vlan(
107+
namespace_localnet_1: Namespace,
108+
) -> Generator[libcudn.ClusterUserDefinedNetwork]:
109+
with localnet_cudn(
110+
name=LOCALNET_BR_EX_NETWORK_NO_VLAN,
111+
match_labels=LOCALNET_TEST_LABEL,
112+
physical_network_name=LOCALNET_BR_EX_NETWORK,
113+
) as cudn:
114+
cudn.wait_for_status_success()
115+
yield cudn
116+
117+
98118
@pytest.fixture(scope="module")
99119
def ipv4_localnet_address_pool() -> Generator[str]:
100120
return (f"{random_ipv4_address(net_seed=0, host_address=host_value)}/24" for host_value in range(1, 254))
101121

102122

123+
@pytest.fixture(scope="module")
124+
def vm_localnet_1_secondary_ip(ipv4_localnet_address_pool: Generator[str]) -> str:
125+
return next(ipv4_localnet_address_pool)
126+
127+
103128
@pytest.fixture(scope="module")
104129
def vm_localnet_1(
105130
namespace_localnet_1: Namespace,
106131
ipv4_localnet_address_pool: Generator[str],
132+
vm_localnet_1_secondary_ip: str,
107133
cudn_localnet: libcudn.ClusterUserDefinedNetwork,
134+
cudn_localnet_no_vlan: libcudn.ClusterUserDefinedNetwork,
108135
unprivileged_client: DynamicClient,
109136
) -> Generator[BaseVirtualMachine]:
137+
"""
138+
Creates a VM with two interfaces:
139+
- Primary interface (eth0): connected to VLAN-enabled localnet
140+
- Secondary interface (eth1): connected to no-VLAN localnet
141+
"""
110142
with localnet_vm(
111143
namespace=namespace_localnet_1.name,
112144
name="test-vm1",
113-
physical_network_name=cudn_localnet.name,
114-
spec_logical_network=LOCALNET_BR_EX_NETWORK,
115-
cidr=next(ipv4_localnet_address_pool),
116145
client=unprivileged_client,
146+
networks=[
147+
Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet.name)),
148+
Network(name=LOCALNET_BR_EX_INTERFACE_NO_VLAN, multus=Multus(networkName=cudn_localnet_no_vlan.name)),
149+
],
150+
interfaces=[
151+
Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={}),
152+
Interface(name=LOCALNET_BR_EX_INTERFACE_NO_VLAN, bridge={}),
153+
],
154+
network_data=cloudinit.NetworkData(
155+
ethernets={
156+
PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)]),
157+
"eth1": cloudinit.EthernetDevice(addresses=[vm_localnet_1_secondary_ip]),
158+
}
159+
),
117160
) as vm:
118161
yield vm
119162

@@ -128,10 +171,12 @@ def vm_localnet_2(
128171
with localnet_vm(
129172
namespace=namespace_localnet_2.name,
130173
name="test-vm2",
131-
physical_network_name=cudn_localnet.name,
132-
spec_logical_network=LOCALNET_BR_EX_NETWORK,
133-
cidr=next(ipv4_localnet_address_pool),
134174
client=unprivileged_client,
175+
networks=[Network(name=LOCALNET_BR_EX_INTERFACE, multus=Multus(networkName=cudn_localnet.name))],
176+
interfaces=[Interface(name=LOCALNET_BR_EX_INTERFACE, bridge={})],
177+
network_data=cloudinit.NetworkData(
178+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
179+
),
135180
) as vm:
136181
yield vm
137182

@@ -156,7 +201,7 @@ def localnet_client(localnet_running_vms: tuple[BaseVirtualMachine, BaseVirtualM
156201
with create_traffic_client(
157202
server_vm=localnet_running_vms[0],
158203
client_vm=localnet_running_vms[1],
159-
spec_logical_network=LOCALNET_BR_EX_NETWORK,
204+
spec_logical_network=LOCALNET_BR_EX_INTERFACE,
160205
) as client:
161206
assert client.is_running()
162207
yield client
@@ -227,11 +272,14 @@ def vm_ovs_bridge_localnet_link_down(
227272
with localnet_vm(
228273
namespace=namespace_localnet_1.name,
229274
name="localnet-ovs-link-down-vm",
230-
physical_network_name=cudn_localnet_ovs_bridge.name,
231-
spec_logical_network=LOCALNET_OVS_BRIDGE_NETWORK,
232-
cidr=next(ipv4_localnet_address_pool),
233275
client=unprivileged_client,
234-
interface_state=LINK_STATE_DOWN,
276+
networks=[
277+
Network(name=LOCALNET_OVS_BRIDGE_INTERFACE, multus=Multus(networkName=cudn_localnet_ovs_bridge.name))
278+
],
279+
interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={}, state=LINK_STATE_DOWN)],
280+
network_data=cloudinit.NetworkData(
281+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
282+
),
235283
) as vm:
236284
yield vm
237285

@@ -246,10 +294,14 @@ def vm_ovs_bridge_localnet_1(
246294
with localnet_vm(
247295
namespace=namespace_localnet_1.name,
248296
name="localnet-ovs-vm1",
249-
physical_network_name=cudn_localnet_ovs_bridge.name,
250-
spec_logical_network=LOCALNET_OVS_BRIDGE_NETWORK,
251-
cidr=next(ipv4_localnet_address_pool),
252297
client=unprivileged_client,
298+
networks=[
299+
Network(name=LOCALNET_OVS_BRIDGE_INTERFACE, multus=Multus(networkName=cudn_localnet_ovs_bridge.name))
300+
],
301+
interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})],
302+
network_data=cloudinit.NetworkData(
303+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
304+
),
253305
) as vm:
254306
yield vm
255307

@@ -264,10 +316,14 @@ def vm_ovs_bridge_localnet_2(
264316
with localnet_vm(
265317
namespace=namespace_localnet_1.name,
266318
name="localnet-ovs-vm2",
267-
physical_network_name=cudn_localnet_ovs_bridge.name,
268-
spec_logical_network=LOCALNET_OVS_BRIDGE_NETWORK,
269-
cidr=next(ipv4_localnet_address_pool),
270319
client=unprivileged_client,
320+
networks=[
321+
Network(name=LOCALNET_OVS_BRIDGE_INTERFACE, multus=Multus(networkName=cudn_localnet_ovs_bridge.name))
322+
],
323+
interfaces=[Interface(name=LOCALNET_OVS_BRIDGE_INTERFACE, bridge={})],
324+
network_data=cloudinit.NetworkData(
325+
ethernets={PRIMARY_INTERFACE_NAME: cloudinit.EthernetDevice(addresses=[next(ipv4_localnet_address_pool)])}
326+
),
271327
) as vm:
272328
yield vm
273329

@@ -279,7 +335,7 @@ def ovs_bridge_localnet_running_vms_one_with_interface_down(
279335
vm1, vm2 = run_vms(vms=(vm_ovs_bridge_localnet_link_down, vm_ovs_bridge_localnet_1))
280336
lookup_iface_status(
281337
vm=vm_ovs_bridge_localnet_link_down,
282-
iface_name=LOCALNET_OVS_BRIDGE_NETWORK,
338+
iface_name=LOCALNET_OVS_BRIDGE_INTERFACE,
283339
predicate=lambda interface: "guest-agent" in interface["infoSource"]
284340
and interface["linkState"] == LINK_STATE_DOWN,
285341
)
@@ -310,7 +366,7 @@ def localnet_ovs_bridge_client(
310366
with create_traffic_client(
311367
server_vm=ovs_bridge_localnet_running_vms[0],
312368
client_vm=ovs_bridge_localnet_running_vms[1],
313-
spec_logical_network=LOCALNET_OVS_BRIDGE_NETWORK,
369+
spec_logical_network=LOCALNET_OVS_BRIDGE_INTERFACE,
314370
) as client:
315371
assert client.is_running()
316372
yield client
@@ -321,7 +377,7 @@ def localnet_vms_have_connectivity(localnet_running_vms: tuple[BaseVirtualMachin
321377
with client_server_active_connection(
322378
client_vm=localnet_running_vms[0],
323379
server_vm=localnet_running_vms[1],
324-
spec_logical_network=LOCALNET_BR_EX_NETWORK,
380+
spec_logical_network=LOCALNET_BR_EX_INTERFACE,
325381
):
326382
pass
327383

tests/network/localnet/liblocalnet.py

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,21 @@
77

88
from libs.net.traffic_generator import TcpServer
99
from libs.net.traffic_generator import VMTcpClient as TcpClient
10-
from libs.net.vmspec import IP_ADDRESS, add_network_interface, add_volume_disk, lookup_iface_status
10+
from libs.net.vmspec import IP_ADDRESS, add_volume_disk, lookup_iface_status
1111
from libs.vm.affinity import new_pod_anti_affinity
1212
from libs.vm.factory import base_vmspec, fedora_vm
13-
from libs.vm.spec import CloudInitNoCloud, Interface, Metadata, Multus, Network
13+
from libs.vm.spec import CloudInitNoCloud, Devices, Interface, Metadata, Network
1414
from libs.vm.vm import BaseVirtualMachine, cloudinitdisk_storage
1515
from tests.network.libs import cloudinit
1616
from tests.network.libs import cluster_user_defined_network as libcudn
1717
from tests.network.libs.label_selector import LabelSelector
1818

1919
LOCALNET_BR_EX_NETWORK = "localnet-br-ex-network"
20+
LOCALNET_BR_EX_NETWORK_NO_VLAN = "localnet-br-ex-network-no-vlan"
2021
LOCALNET_OVS_BRIDGE_NETWORK = "localnet-ovs-network"
22+
LOCALNET_BR_EX_INTERFACE = "localnet-iface-vlan"
23+
LOCALNET_BR_EX_INTERFACE_NO_VLAN = "localnet-iface-no-vlan"
24+
LOCALNET_OVS_BRIDGE_INTERFACE = "localnet-iface-ovs-bridge"
2125
LOCALNET_TEST_LABEL = {"test": "localnet"}
2226
LINK_STATE_UP = "up"
2327
LINK_STATE_DOWN = "down"
@@ -56,52 +60,64 @@ def create_traffic_client(
5660
def localnet_vm(
5761
namespace: str,
5862
name: str,
59-
physical_network_name: str,
60-
spec_logical_network: str,
61-
cidr: str,
6263
client: DynamicClient,
63-
interface_state: str | None = None,
64+
networks: list[Network],
65+
interfaces: list[Interface],
66+
network_data: cloudinit.NetworkData,
6467
) -> BaseVirtualMachine:
6568
"""
66-
Create a Fedora-based Virtual Machine connected to a given localnet network with a static IP configuration.
69+
Create a Fedora-based Virtual Machine connected to localnet network(s).
6770
6871
The VM will:
69-
- Attach to a Multus network using a bridge interface.
7072
- Apply a specific label for anti-affinity scheduling.
71-
- Use cloud-init to configure a static IP address.
7273
- Based on a standard Fedora VM template.
7374
7475
Args:
7576
namespace (str): The namespace where the VM should be created.
7677
name (str): The name of the VM.
77-
physical_network_name (str): The name of the Multus network to attach.
78-
cidr (str): The CIDR address to assign to the VM's interface.
7978
client (DynamicClient): The Kubernetes dynamic client for resource creation.
80-
spec_logical_network (str): The name of the localnet network to attach.
81-
interface_state (str): The state of the interface (optional).
82-
Possible values are "up" or "down". When not specified, it behaves as "up".
79+
networks (list[Network]): List of Network objects defining the networks to attach.
80+
Each Network should have a name and configuration.
81+
interfaces (list[Interface]): List of Interface objects defining the interface configurations.
82+
Each Interface should have a name matching a Network, and additional configuration and state.
83+
network_data (cloudinit.NetworkData): Cloud-init NetworkData object containing the network
84+
configuration for the VM interfaces.
8385
8486
Returns:
8587
BaseVirtualMachine: The configured VM object ready for creation.
88+
89+
Example:
90+
>>> networks = [
91+
... Network(name="net1", multus=Multus(networkName="physical-net1")),
92+
... Network(name="net2", multus=Multus(networkName="physical-net2")),
93+
... ]
94+
>>> interfaces = [
95+
... Interface(name="net1", bridge={}, state="up"),
96+
... Interface(name="net2", bridge={}, state="up"),
97+
... ]
98+
>>> network_data = cloudinit.NetworkData(ethernets={
99+
... "eth0": cloudinit.EthernetDevice(addresses=["172.16.1.1/24"]),
100+
... "eth1": cloudinit.EthernetDevice(addresses=["172.16.2.1/24"]),
101+
... })
102+
>>> vm = localnet_vm(namespace="test-localnet", name="vm1", client=client,
103+
... networks=networks, interfaces=interfaces, network_data=network_data)
86104
"""
87105
spec = base_vmspec()
88106
spec.template.metadata = spec.template.metadata or Metadata()
89107
spec.template.metadata.labels = spec.template.metadata.labels or {}
90108
spec.template.metadata.labels.update(LOCALNET_TEST_LABEL)
91-
vmi_spec = spec.template.spec
92109

93-
vmi_spec = add_network_interface(
94-
vmi_spec=vmi_spec,
95-
network=Network(name=spec_logical_network, multus=Multus(networkName=physical_network_name)),
96-
interface=Interface(name=spec_logical_network, bridge={}, state=interface_state),
97-
)
110+
vmi_spec = spec.template.spec
111+
vmi_spec.networks = networks
112+
vmi_spec.domain.devices = vmi_spec.domain.devices or Devices()
113+
vmi_spec.domain.devices.interfaces = interfaces
98114

99-
netdata = cloudinit.NetworkData(ethernets={"eth0": cloudinit.EthernetDevice(addresses=[cidr])})
100115
# Prevents cloud-init from overriding the default OS user credentials
101116
userdata = cloudinit.UserData(users=[])
102117
disk, volume = cloudinitdisk_storage(
103118
data=CloudInitNoCloud(
104-
networkData=cloudinit.asyaml(no_cloud=netdata), userData=cloudinit.format_cloud_config(userdata=userdata)
119+
networkData=cloudinit.asyaml(no_cloud=network_data),
120+
userData=cloudinit.format_cloud_config(userdata=userdata),
105121
)
106122
)
107123
vmi_spec = add_volume_disk(vmi_spec=vmi_spec, volume=volume, disk=disk)
@@ -113,7 +129,10 @@ def localnet_vm(
113129

114130

115131
def localnet_cudn(
116-
name: str, match_labels: dict[str, str], vlan_id: int, physical_network_name: str
132+
name: str,
133+
match_labels: dict[str, str],
134+
physical_network_name: str,
135+
vlan_id: int | None = None,
117136
) -> libcudn.ClusterUserDefinedNetwork:
118137
"""
119138
Create a ClusterUserDefinedNetwork resource configured for localnet with the specified VLAN ID.
@@ -127,14 +146,18 @@ def localnet_cudn(
127146
Args:
128147
name (str): The name of the CUDN resource.
129148
match_labels (dict[str, str]): Labels for namespace selection.
130-
vlan_id (int): The VLAN ID to configure for the network.
131149
physical_network_name (str): The name of the physical network to associate with the localnet configuration.
150+
vlan_id (int|None): The VLAN ID to configure for the network. If None, no VLAN is configured.
132151
133152
Returns:
134153
ClusterUserDefinedNetwork: The configured CUDN object ready for creation.
135154
"""
136155
ipam = libcudn.Ipam(mode=libcudn.Ipam.Mode.DISABLED.value)
137-
vlan = libcudn.Vlan(mode=libcudn.Vlan.Mode.ACCESS.value, access=libcudn.Access(id=vlan_id))
156+
vlan = (
157+
libcudn.Vlan(mode=libcudn.Vlan.Mode.ACCESS.value, access=libcudn.Access(id=vlan_id))
158+
if vlan_id is not None
159+
else None
160+
)
138161
localnet = libcudn.Localnet(
139162
role=libcudn.Localnet.Role.SECONDARY.value, physicalNetworkName=physical_network_name, vlan=vlan, ipam=ipam
140163
)

0 commit comments

Comments
 (0)