Skip to content

Commit 7b6aebe

Browse files
committed
net: implement NAD live-update test for localnet
Implement the NAD reference change test for localnet. The reference VM has eth1 on CUDN-VLAN-A and eth2 on CUDN-VLAN-B. CUDN-VLAN-A is reused from the existing cudn_localnet fixture; only CUDN-VLAN-B is created specifically for this test. arp_ignore=1 and arp_announce=2 are set via cloud-init runcmd to prevent ARP cache poisoning when both secondary interfaces share the same subnet. --bind-dev forces iperf3 server responses out the correct interface, eliminating ECMP routing ambiguity. Signed-off-by: Asia Khromov <azhivovk@redhat.com> Assisted-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent d2e31ea commit 7b6aebe

2 files changed

Lines changed: 181 additions & 3 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from collections.abc import Generator
2+
from typing import Final
3+
4+
import pytest
5+
from kubernetes.dynamic import DynamicClient
6+
from ocp_resources.namespace import Namespace
7+
8+
from libs.net.ip import filter_link_local_addresses, random_cidr_addresses_by_family
9+
from libs.net.vmspec import lookup_iface_status, wait_for_ifaces_status
10+
from libs.vm.spec import Interface, Multus, Network
11+
from libs.vm.vm import BaseVirtualMachine
12+
from tests.network.libs import cloudinit
13+
from tests.network.libs import cluster_user_defined_network as libcudn
14+
from tests.network.libs.connectivity import ARP_ISOLATION_SYSCTL_CMD, poll_tcp_connectivity
15+
from tests.network.localnet.liblocalnet import (
16+
CUDN_B_NAME,
17+
GUEST_1ST_IFACE_NAME,
18+
GUEST_2ND_IFACE_NAME,
19+
IFACE_A_NAME,
20+
IFACE_B_NAME,
21+
LOCALNET_BR_EX_NETWORK,
22+
LOCALNET_TEST_LABEL,
23+
localnet_cudn,
24+
localnet_vm,
25+
)
26+
27+
NET_SEED: Final[int] = 0
28+
29+
30+
@pytest.fixture(scope="module")
31+
def cudn_nad_ref_vlan_b(
32+
admin_client: DynamicClient,
33+
cudn_localnet: libcudn.ClusterUserDefinedNetwork,
34+
vlan_index_number: Generator[int],
35+
) -> Generator[libcudn.ClusterUserDefinedNetwork]:
36+
with localnet_cudn(
37+
name=CUDN_B_NAME,
38+
match_labels=LOCALNET_TEST_LABEL,
39+
vlan_id=next(vlan_index_number),
40+
physical_network_name=LOCALNET_BR_EX_NETWORK,
41+
client=admin_client,
42+
) as cudn:
43+
cudn.wait_for_status_success()
44+
yield cudn
45+
46+
47+
@pytest.fixture(scope="module")
48+
def ref_vm_localnet(
49+
namespace_localnet_1: Namespace,
50+
unprivileged_client: DynamicClient,
51+
cudn_localnet: libcudn.ClusterUserDefinedNetwork,
52+
cudn_nad_ref_vlan_b: libcudn.ClusterUserDefinedNetwork,
53+
) -> Generator[BaseVirtualMachine]:
54+
iface_a_ips = random_cidr_addresses_by_family(net_seed=NET_SEED, host_address=1)
55+
iface_b_ips = random_cidr_addresses_by_family(net_seed=NET_SEED, host_address=2)
56+
with localnet_vm(
57+
namespace=namespace_localnet_1.name,
58+
name="ref-vm",
59+
client=unprivileged_client,
60+
networks=[
61+
Network(name=IFACE_A_NAME, multus=Multus(networkName=cudn_localnet.name)),
62+
Network(name=IFACE_B_NAME, multus=Multus(networkName=cudn_nad_ref_vlan_b.name)),
63+
],
64+
interfaces=[
65+
Interface(name=IFACE_A_NAME, bridge={}),
66+
Interface(name=IFACE_B_NAME, bridge={}),
67+
],
68+
network_data=cloudinit.NetworkData(
69+
ethernets={
70+
GUEST_1ST_IFACE_NAME: cloudinit.EthernetDevice(addresses=iface_a_ips),
71+
GUEST_2ND_IFACE_NAME: cloudinit.EthernetDevice(addresses=iface_b_ips),
72+
}
73+
),
74+
runcmd=ARP_ISOLATION_SYSCTL_CMD,
75+
) as vm:
76+
vm.start(wait=True)
77+
vm.wait_for_agent_connected()
78+
wait_for_ifaces_status(
79+
vm=vm,
80+
ip_addresses_by_spec_net_name={
81+
IFACE_A_NAME: [addr.split("/")[0] for addr in iface_a_ips],
82+
IFACE_B_NAME: [addr.split("/")[0] for addr in iface_b_ips],
83+
},
84+
)
85+
yield vm
86+
87+
88+
@pytest.fixture()
89+
def under_test_vm_localnet(
90+
namespace_localnet_1: Namespace,
91+
unprivileged_client: DynamicClient,
92+
cudn_localnet: libcudn.ClusterUserDefinedNetwork,
93+
) -> Generator[BaseVirtualMachine]:
94+
iface_a_ips = random_cidr_addresses_by_family(net_seed=NET_SEED, host_address=3)
95+
with localnet_vm(
96+
namespace=namespace_localnet_1.name,
97+
name="under-test-vm",
98+
client=unprivileged_client,
99+
networks=[Network(name=IFACE_A_NAME, multus=Multus(networkName=cudn_localnet.name))],
100+
interfaces=[Interface(name=IFACE_A_NAME, bridge={})],
101+
network_data=cloudinit.NetworkData(
102+
ethernets={GUEST_1ST_IFACE_NAME: cloudinit.EthernetDevice(addresses=iface_a_ips)},
103+
),
104+
) as vm:
105+
vm.start(wait=True)
106+
vm.wait_for_agent_connected()
107+
wait_for_ifaces_status(
108+
vm=vm,
109+
ip_addresses_by_spec_net_name={
110+
IFACE_A_NAME: [addr.split("/")[0] for addr in iface_a_ips],
111+
},
112+
)
113+
yield vm
114+
115+
116+
@pytest.fixture()
117+
def baseline_connectivity_localnet(
118+
under_test_vm_localnet: BaseVirtualMachine,
119+
ref_vm_localnet: BaseVirtualMachine,
120+
) -> None:
121+
for server_ip in filter_link_local_addresses(
122+
ip_addresses=lookup_iface_status(vm=ref_vm_localnet, iface_name=IFACE_A_NAME).ipAddresses
123+
):
124+
poll_tcp_connectivity(
125+
client_vm=under_test_vm_localnet,
126+
server_vm=ref_vm_localnet,
127+
server_ip=str(server_ip),
128+
server_bind_dev=GUEST_1ST_IFACE_NAME,
129+
)
130+
for server_ip in filter_link_local_addresses(
131+
ip_addresses=lookup_iface_status(vm=ref_vm_localnet, iface_name=IFACE_B_NAME).ipAddresses
132+
):
133+
poll_tcp_connectivity(
134+
client_vm=under_test_vm_localnet,
135+
server_vm=ref_vm_localnet,
136+
server_ip=str(server_ip),
137+
server_bind_dev=GUEST_2ND_IFACE_NAME,
138+
expect_connectivity=False,
139+
)

tests/network/localnet/nad_ref_change/test_nad_ref_change.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,26 @@
1212

1313
import pytest
1414

15+
from libs.net.ip import filter_link_local_addresses
16+
from libs.net.vmspec import lookup_iface_status
17+
from tests.network.libs.connectivity import poll_tcp_connectivity
18+
from tests.network.libs.nad_ref import update_nad_references
19+
from tests.network.localnet.liblocalnet import (
20+
GUEST_1ST_IFACE_NAME,
21+
GUEST_2ND_IFACE_NAME,
22+
IFACE_A_NAME,
23+
IFACE_B_NAME,
24+
)
1525

26+
27+
@pytest.mark.usefixtures("nncp_localnet", "baseline_connectivity_localnet")
1628
@pytest.mark.polarion("CNV-15948")
17-
def test_running_vm_vlan_change():
29+
def test_running_vm_vlan_change(
30+
subtests,
31+
under_test_vm_localnet,
32+
ref_vm_localnet,
33+
cudn_nad_ref_vlan_b,
34+
):
1835
"""
1936
Test that a running VM can change the VLAN of its secondary localnet network, without rebooting.
2037
The VM should establish TCP connectivity on the new VLAN.
@@ -33,6 +50,28 @@ def test_running_vm_vlan_change():
3350
- Under-test VM eventually has TCP connectivity to the reference VM on NAD-VLAN-B
3451
- Under-test VM has no TCP connectivity to the reference VM on NAD-VLAN-A
3552
"""
53+
update_nad_references(vm=under_test_vm_localnet, nad_name_by_net={IFACE_A_NAME: cudn_nad_ref_vlan_b.name})
3654

37-
38-
test_running_vm_vlan_change.__test__ = False
55+
for server_ip in filter_link_local_addresses(
56+
ip_addresses=lookup_iface_status(vm=ref_vm_localnet, iface_name=IFACE_B_NAME).ipAddresses
57+
):
58+
with subtests.test(msg=f"IPv{server_ip.version} connectivity on {IFACE_B_NAME}"):
59+
poll_tcp_connectivity(
60+
client_vm=under_test_vm_localnet,
61+
server_vm=ref_vm_localnet,
62+
server_ip=str(server_ip),
63+
server_bind_dev=GUEST_2ND_IFACE_NAME,
64+
client_bind_dev=GUEST_1ST_IFACE_NAME,
65+
)
66+
for server_ip in filter_link_local_addresses(
67+
ip_addresses=lookup_iface_status(vm=ref_vm_localnet, iface_name=IFACE_A_NAME).ipAddresses
68+
):
69+
with subtests.test(msg=f"IPv{server_ip.version} no connectivity on {IFACE_A_NAME}"):
70+
poll_tcp_connectivity(
71+
client_vm=under_test_vm_localnet,
72+
server_vm=ref_vm_localnet,
73+
server_ip=str(server_ip),
74+
server_bind_dev=GUEST_1ST_IFACE_NAME,
75+
client_bind_dev=GUEST_1ST_IFACE_NAME,
76+
expect_connectivity=False,
77+
)

0 commit comments

Comments
 (0)