Skip to content

Commit 1487da7

Browse files
CherryPicked: [cnv-4.22] net, evpn: add source provider migration test (#5206)
Cherry-pick from `main` branch, original PR: #4817, PR owner: servolkov Signed-off-by: Sergei Volkov <sevolkov@redhat.com> Co-authored-by: Sergei Volkov <sevolkov@redhat.com>
1 parent 9c0190f commit 1487da7

4 files changed

Lines changed: 108 additions & 15 deletions

File tree

libs/vm/spec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class Interface:
7777
passtBinding: dict[Any, Any] | None = None # noqa: N815
7878
binding: NetBinding | None = None
7979
state: str | None = None
80+
macAddress: str | None = None # noqa: N815
8081

8182

8283
@dataclass

tests/network/bgp/evpn/conftest.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111
from ocp_resources.resource import ResourceEditor
1212
from ocp_resources.vtep import VTEP
1313

14-
from libs.net.ip import random_ipv4_address, random_ipv6_address
14+
from libs.net.ip import random_ipv4_address
1515
from libs.net.traffic_generator import TcpServer
16-
from libs.net.udn import UDN_BINDING_DEFAULT_PLUGIN_NAME, create_udn_namespace
16+
from libs.net.udn import UDN_BINDING_DEFAULT_PLUGIN_NAME, create_udn_namespace, udn_primary_network
17+
from libs.vm.affinity import new_pod_anti_affinity
18+
from libs.vm.factory import base_vmspec, fedora_vm
19+
from libs.vm.spec import Devices, Metadata
1720
from libs.vm.vm import BaseVirtualMachine
1821
from tests.network.bgp.evpn.libevpn import (
22+
CUDN_EVPN_SUBNET_IPV6,
23+
EVPN_CUDN_NET_SEED,
1924
EndpointTcpClient,
2025
EvpnEndpoint,
2126
cudn_evpn_subnets,
@@ -41,8 +46,9 @@
4146
EVPN_ADVERTISE_LABEL: Final[dict] = {"advertise": "evpn"}
4247
APP_EVPN_CUDN_LABEL: Final[dict] = {**EVPN_ADVERTISE_LABEL, "app": "cudn-evpn"}
4348
CUDN_EVPN_BGP_LABEL: Final[dict] = {"cudn-bgp": "evpn"}
44-
EXTERNAL_L2_ENDPOINT_IPV4: Final[str] = f"{random_ipv4_address(net_seed=5, host_address=250)}/24"
45-
EXTERNAL_L2_ENDPOINT_IPV6: Final[str] = f"{random_ipv6_address(net_seed=5, host_address=250)}/64"
49+
EXTERNAL_L2_ENDPOINT_IPV4: Final[str] = f"{random_ipv4_address(net_seed=EVPN_CUDN_NET_SEED, host_address=250)}/24"
50+
EXTERNAL_L2_ENDPOINT_IPV6: Final[str] = f"{ipaddress.ip_network(CUDN_EVPN_SUBNET_IPV6, strict=False)[250]}/64"
51+
EXTERNAL_L2_ENDPOINT_MAC: Final[str] = "02:00:05:00:fa:00"
4652
EXTERNAL_L3_ENDPOINT_IPV4: Final[str] = "192.168.100.100/24"
4753
EXTERNAL_L3_ENDPOINT_IPV6: Final[str] = "fd01:1234:5678::64/64"
4854
EXTERNAL_L3_GATEWAY_IPV4: Final[str] = "192.168.100.1/24"
@@ -233,9 +239,10 @@ def external_l2_endpoint(
233239
pod=frr_external_pod.pod,
234240
vni=EVPN_MAC_VRF_VNI,
235241
endpoint_ips=[EXTERNAL_L2_ENDPOINT_IPV4, EXTERNAL_L2_ENDPOINT_IPV6],
242+
mac_address=EXTERNAL_L2_ENDPOINT_MAC,
236243
)
237244
yield endpoint
238-
teardown_evpn_l2_endpoint(pod=frr_external_pod.pod)
245+
teardown_evpn_l2_endpoint(pod=frr_external_pod.pod, vni=EVPN_MAC_VRF_VNI)
239246

240247

241248
@pytest.fixture(scope="module")
@@ -269,3 +276,30 @@ def evpn_routed_l3_active_connections(
269276
) -> Generator[list[tuple[EndpointTcpClient, TcpServer]]]:
270277
with evpn_workloads_active_connections(endpoint=external_l3_endpoint, vm=vm_evpn_target) as connections:
271278
yield connections
279+
280+
281+
@pytest.fixture()
282+
def vm_source_provider(
283+
external_l2_endpoint: EvpnEndpoint,
284+
namespace_evpn: Namespace,
285+
cudn_evpn_layer2: libcudn.ClusterUserDefinedNetwork,
286+
admin_client: DynamicClient,
287+
frr_external_pod: ExternalFrrPodInfo,
288+
) -> Generator[BaseVirtualMachine]:
289+
spec = base_vmspec()
290+
network_name = "udn-network"
291+
iface, network = udn_primary_network(name=network_name, binding=UDN_BINDING_DEFAULT_PLUGIN_NAME)
292+
iface.macAddress = external_l2_endpoint.mac_address
293+
spec.template.spec.domain.devices = Devices(interfaces=[iface])
294+
spec.template.spec.networks = [network]
295+
spec.template.metadata = Metadata(
296+
labels=EXTERNAL_FRR_POD_LABEL,
297+
annotations={"network.kubevirt.io/addresses": json.dumps({network_name: external_l2_endpoint.ip_addresses})},
298+
)
299+
label, *_ = EXTERNAL_FRR_POD_LABEL.items()
300+
spec.template.spec.affinity = new_pod_anti_affinity(
301+
label=label, namespaces=[frr_external_pod.pod.namespace, namespace_evpn.name]
302+
)
303+
304+
with fedora_vm(namespace=namespace_evpn.name, name="vm-source-provider", client=admin_client, spec=spec) as vm:
305+
yield vm

tests/network/bgp/evpn/libevpn.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424

2525
LOGGER = logging.getLogger(__name__)
2626

27-
CUDN_EVPN_SUBNET_IPV4: str = f"{random_ipv4_address(net_seed=5, host_address=0)}/24"
28-
CUDN_EVPN_SUBNET_IPV6: str = f"{random_ipv6_address(net_seed=5, host_address=0)}/64"
27+
EVPN_CUDN_NET_SEED: int = 5
28+
CUDN_EVPN_SUBNET_IPV4: str = f"{random_ipv4_address(net_seed=EVPN_CUDN_NET_SEED, host_address=0)}/24"
29+
CUDN_EVPN_SUBNET_IPV6: str = f"{random_ipv6_address(net_seed=EVPN_CUDN_NET_SEED, host_address=0)}/64"
2930

3031
_BRIDGE_NAME: str = "br0"
3132
_VXLAN_NAME: str = "vxlan0"
@@ -51,6 +52,7 @@ class EvpnEndpoint:
5152
pod: Pod
5253
ip_addresses: list[str]
5354
netns_name: str
55+
mac_address: str | None = None
5456

5557

5658
class EndpointTcpClient(PodTcpClient):
@@ -143,6 +145,7 @@ def deploy_evpn_l2_endpoint(
143145
pod: Pod,
144146
vni: int,
145147
endpoint_ips: list[str],
148+
mac_address: str | None = None,
146149
) -> EvpnEndpoint:
147150
"""Creates a stretched L2 endpoint on the shared SVD bridge.
148151
@@ -155,32 +158,46 @@ def deploy_evpn_l2_endpoint(
155158
pod: The FRR pod hosting the endpoint.
156159
vni: MAC-VRF VNI (must match CUDN's macVRF VNI).
157160
endpoint_ips: IPs with prefix length (e.g. ["10.0.5.250/24", "fd00::fa/64"]).
161+
mac_address: Explicit MAC for the endpoint interface (locally-administered).
158162
159163
Returns:
160164
EvpnEndpoint.
161165
"""
162-
commands = _build_l2_endpoint_commands(vni=vni, endpoint_ips=endpoint_ips)
166+
commands = _build_l2_endpoint_commands(vni=vni, endpoint_ips=endpoint_ips, mac_address=mac_address)
163167
for command in commands:
164168
pod.execute(command=shlex.split(command), container=NET_TOOLS_CONTAINER_NAME)
165169

166170
bare_ips = [ip.split("/")[0] for ip in endpoint_ips]
167171
LOGGER.info(f"EVPN L2 endpoint deployed: {bare_ips} in namespace {_L2_ENDPOINT_NETNS}")
168172

169-
return EvpnEndpoint(pod=pod, ip_addresses=bare_ips, netns_name=_L2_ENDPOINT_NETNS)
173+
return EvpnEndpoint(pod=pod, ip_addresses=bare_ips, netns_name=_L2_ENDPOINT_NETNS, mac_address=mac_address)
170174

171175

172-
def teardown_evpn_l2_endpoint(pod: Pod) -> None:
173-
"""Removes the EVPN L2 endpoint (netns, veth) from the FRR pod."""
176+
def teardown_evpn_l2_endpoint(pod: Pod, vni: int) -> None:
177+
"""Removes the EVPN L2 endpoint (netns, veth, VLAN/VNI mappings) from the FRR pod.
178+
179+
Args:
180+
pod: The FRR pod hosting the endpoint.
181+
vni: MAC-VRF VNI used during deployment.
182+
"""
174183
for cmd in [
175184
f"ip netns delete {_L2_ENDPOINT_NETNS}",
176185
f"ip link delete {_L2_VETH_POD_SIDE}",
186+
f"bridge vlan del dev {_VXLAN_NAME} vid {_L2_VID} tunnel_info id {vni}",
187+
f"bridge vni del dev {_VXLAN_NAME} vni {vni}",
188+
f"bridge vlan del dev {_VXLAN_NAME} vid {_L2_VID}",
189+
f"bridge vlan del dev {_BRIDGE_NAME} vid {_L2_VID} self",
177190
]:
178191
pod.execute(command=shlex.split(cmd), container=NET_TOOLS_CONTAINER_NAME, ignore_rc=True)
179192

180193
LOGGER.info(f"EVPN L2 endpoint removed: namespace={_L2_ENDPOINT_NETNS}")
181194

182195

183-
def _build_l2_endpoint_commands(vni: int, endpoint_ips: list[str]) -> list[str]:
196+
def _build_l2_endpoint_commands(
197+
vni: int,
198+
endpoint_ips: list[str],
199+
mac_address: str | None = None,
200+
) -> list[str]:
184201
return [
185202
f"bridge vlan add dev {_BRIDGE_NAME} vid {_L2_VID} self",
186203
f"bridge vlan add dev {_VXLAN_NAME} vid {_L2_VID}",
@@ -193,6 +210,11 @@ def _build_l2_endpoint_commands(vni: int, endpoint_ips: list[str]) -> list[str]:
193210
f"ip netns add {_L2_ENDPOINT_NETNS}",
194211
f"ip link set {_L2_VETH_EP_SIDE} netns {_L2_ENDPOINT_NETNS}",
195212
*(f"ip netns exec {_L2_ENDPOINT_NETNS} ip addr add {ip} dev {_L2_VETH_EP_SIDE}" for ip in endpoint_ips),
213+
*(
214+
[f"ip netns exec {_L2_ENDPOINT_NETNS} ip link set dev {_L2_VETH_EP_SIDE} address {mac_address}"]
215+
if mac_address
216+
else []
217+
),
196218
f"ip netns exec {_L2_ENDPOINT_NETNS} ip link set {_L2_VETH_EP_SIDE} up",
197219
f"ip netns exec {_L2_ENDPOINT_NETNS} ip link set lo up",
198220
]

tests/network/bgp/evpn/test_evpn_connectivity.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,21 @@
2020

2121
import pytest
2222

23+
from libs.net.ip import random_ipv4_address, random_ipv6_address
2324
from libs.net.traffic_generator import active_tcp_connections, is_tcp_connection
2425
from libs.net.vmspec import lookup_primary_network
25-
from tests.network.bgp.evpn.libevpn import assert_evpn_workloads_connectivity, evpn_workloads_active_connections
26+
from tests.network.bgp.evpn.libevpn import (
27+
EVPN_CUDN_NET_SEED,
28+
assert_evpn_workloads_connectivity,
29+
deploy_evpn_l2_endpoint,
30+
evpn_workloads_active_connections,
31+
teardown_evpn_l2_endpoint,
32+
)
2633
from utilities.virt import migrate_vm_and_verify
2734

35+
_L2_ENDPOINT_IPV4: str = f"{random_ipv4_address(net_seed=EVPN_CUDN_NET_SEED, host_address=249)}/24"
36+
_L2_ENDPOINT_IPV6: str = f"{random_ipv6_address(net_seed=EVPN_CUDN_NET_SEED, host_address=249)}/64"
37+
2838
pytestmark = [
2939
pytest.mark.bgp,
3040
pytest.mark.ipv4,
@@ -175,7 +185,15 @@ def test_connectivity_after_udn_vm_cold_reboot(
175185

176186

177187
@pytest.mark.polarion("CNV-15233")
178-
def test_source_provider_migration():
188+
@pytest.mark.order("last")
189+
def test_source_provider_migration(
190+
external_l3_endpoint,
191+
cudn_evpn_layer2,
192+
vm_source_provider,
193+
vm_evpn_target,
194+
frr_external_pod,
195+
subtests,
196+
):
179197
"""
180198
Scenario emulates a migration of an external workload (Source Provider) into the OCP cluster as a CUDN VM,
181199
while preserving its IP and MAC addresses, and maintaining connectivity.
@@ -184,6 +202,7 @@ def test_source_provider_migration():
184202
- External Source Provider L2 and L3 endpoints.
185203
- Running connectivity reference VM with a primary EVPN-enabled CUDN.
186204
- TCP connectivity exists between the connectivity reference VM and the external L2 and L3 endpoints.
205+
Precondition is verified in preceding tests.
187206
188207
Steps:
189208
1. Shut down/remove the external L2 endpoint.
@@ -193,6 +212,23 @@ def test_source_provider_migration():
193212
Expected:
194213
- New connections are established after new UDN VM deployment.
195214
"""
215+
mac_vrf_vni = cudn_evpn_layer2.instance.spec.network.evpn.macVRF.vni
216+
217+
teardown_evpn_l2_endpoint(pod=frr_external_pod.pod, vni=mac_vrf_vni)
196218

219+
vm_source_provider.start(wait=True)
220+
vm_source_provider.wait_for_agent_connected()
197221

198-
test_source_provider_migration.__test__ = False
222+
new_l2_endpoint = deploy_evpn_l2_endpoint(
223+
pod=frr_external_pod.pod,
224+
vni=mac_vrf_vni,
225+
endpoint_ips=[_L2_ENDPOINT_IPV4, _L2_ENDPOINT_IPV6],
226+
)
227+
228+
assert_evpn_workloads_connectivity(
229+
target_vm=vm_evpn_target,
230+
ref_vm=vm_source_provider,
231+
l2_endpoint=new_l2_endpoint,
232+
l3_endpoint=external_l3_endpoint,
233+
subtests=subtests,
234+
)

0 commit comments

Comments
 (0)