Skip to content

Commit 70baaba

Browse files
authored
net, BGP: adjust BGP environment to use localnet NAD (#3132)
The current implementation of BGP testing is based on some knowledge about the infrastructure: it is expected that the cluster has several NICs, we should know the vlan of the main traffic on the cluster, as well as for the static assignment of IP to an external router, we expect a predefined environment variables. This PR gets rid of these dependencies and allows us to run BGP tests without additional knowledge about the cluster. Main changes: 1) the usage of the NNCP and NAD localnet, the env no longer dependent on secondary NICs. 2) dynamic receiving of IP address for an external router from the main cluster network via DHCP. 3) a condition under which the external router and the UDN VM are guaranteed to live on different cluster nodes, which is vital with the current localnet configuration. Along the way, as necessary: 1) adding `dhcp-client` to net-tools infra image 2) libs: add `namespaces` to anti-affinity and related calls 3) removing BGP sanity check as useless with the current implementation 4) de-quarantining BGP tests 5) doc fixes <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Utility container now includes a DHCP client. * **Tests** * Re-enabled BGP connectivity tests and refactored BGP test infra to use explicit external FRR pod state, module-scoped fixtures, and a localnet network attachment. * Removed legacy NIC-selection fixture and node-network-state helper modules. * **Behavior Changes** * Tier2 tagging now considers BGP-marked tests when no other exclusions apply. * VM anti-affinity can target specific namespaces. * **Documentation** * Simplified BGP test README by removing cluster requirements and env setup. * **Chores** * Removed BGP environment-variable verification from network sanity checks. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents bd2f584 + 108d5a8 commit 70baaba

File tree

12 files changed

+89
-167
lines changed

12 files changed

+89
-167
lines changed

conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
"node_remediation",
7979
"swap",
8080
"numa",
81-
"bgp",
8281
"cclm",
8382
"mtv",
8483
]

containers/utility/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ RUN set -eux; \
1111
yum -y update && \
1212
yum install -y \
1313
NetworkManager \
14+
dhcp-client \
1415
iperf3 \
1516
iproute \
1617
nftables \

libs/vm/affinity.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def new_label(key_prefix: str) -> tuple[str, str]:
1515
return f"{key_prefix}-{uuid.uuid4().hex[:8]}", "true"
1616

1717

18-
def new_pod_anti_affinity(label: tuple[str, str]) -> Affinity:
18+
def new_pod_anti_affinity(label: tuple[str, str], namespaces: list[str] | None = None) -> Affinity:
1919
(key, value) = label
2020
return Affinity(
2121
podAntiAffinity=PodAntiAffinity(
@@ -25,6 +25,7 @@ def new_pod_anti_affinity(label: tuple[str, str]) -> Affinity:
2525
matchExpressions=[LabelSelectorRequirement(key=key, values=[value], operator="In")]
2626
),
2727
topologyKey=f"{Resource.ApiGroup.KUBERNETES_IO}/hostname",
28+
namespaces=namespaces,
2829
)
2930
]
3031
)

libs/vm/spec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class PodAffinityTerm:
110110
labelSelector: LabelSelector # noqa: N815
111111
topologyKey: str # noqa: N815
112112
namespaceSelector: dict[str, Any] | None = None # noqa: N815
113+
namespaces: list[str] | None = None
113114

114115

115116
@dataclass

tests/conftest.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,13 +1014,6 @@ def sriov_workers(schedulable_nodes):
10141014
yield [node for node in schedulable_nodes if node.labels.get(sriov_worker_label) == "true"]
10151015

10161016

1017-
@pytest.fixture(scope="session")
1018-
def vlan_base_iface(worker_node1, nodes_available_nics):
1019-
# Select the last NIC from the list as a way to ensure that the selected NIC
1020-
# is not already used (e.g. as a bond's port).
1021-
return nodes_available_nics[worker_node1.name][-1]
1022-
1023-
10241017
@pytest.fixture(scope="session")
10251018
def sriov_ifaces(sriov_nodes_states, workers_utility_pods):
10261019
node = sriov_nodes_states[0]

tests/network/bgp/README.md

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,3 @@ An external BGP router is required for real-world BGP connectivity, as customers
88
cluster. For testing, the router is implemented as a pod running FRR within the cluster, serving the same role as an
99
external router. The external BGP router (a pod in this case) must be on the same network as the cluster nodes
1010
to enable direct BGP sessions.
11-
12-
### Cluster Requirements
13-
14-
The current implementation requires:
15-
- Each node must have at least two NICs.
16-
- All secondary NICs must be connected to the same VLAN as the main IP of the `br-ex` interface.
17-
18-
### Environment Variables
19-
20-
Set these before running tests:
21-
22-
- `PRIMARY_NODE_NETWORK_VLAN_TAG`: the VLAN number of the `br-ex` main IP. Setting this variable implies that a cluster
23-
reflects the requirements mentioned above.
24-
- `EXTERNAL_FRR_STATIC_IPV4`: reserved IPv4 in CIDR format for the external FRR pod (e.g., 192.0.2.10/24)
25-
within the PRIMARY_NODE_NETWORK_VLAN_TAG network.
26-
27-
```bash
28-
export PRIMARY_NODE_NETWORK_VLAN_TAG=<vlan_id>
29-
export EXTERNAL_FRR_STATIC_IPV4=<static_ip_cidr>
30-
```

tests/network/bgp/conftest.py

Lines changed: 48 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import shlex
32
from collections.abc import Generator
43
from pathlib import Path
@@ -9,8 +8,8 @@
98
from kubernetes.dynamic import DynamicClient
109
from ocp_resources.config_map import ConfigMap
1110
from ocp_resources.namespace import Namespace
11+
from ocp_resources.network_attachment_definition import OVNOverlayNetworkAttachmentDefinition
1212
from ocp_resources.node import Node
13-
from ocp_resources.pod import Pod
1413

1514
from libs.net import netattachdef as libnad
1615
from libs.net.traffic_generator import PodTcpClient as TcpClient
@@ -21,7 +20,9 @@
2120
from tests.network.libs import cluster_user_defined_network as libcudn
2221
from tests.network.libs import nodenetworkconfigurationpolicy as libnncp
2322
from tests.network.libs.bgp import (
23+
EXTERNAL_FRR_POD_LABEL,
2424
POD_SECONDARY_IFACE_NAME,
25+
ExternalFrrPodInfo,
2526
create_cudn_route_advertisements,
2627
create_frr_configuration,
2728
deploy_external_frr_pod,
@@ -31,7 +32,6 @@
3132
)
3233
from tests.network.libs.ip import random_ipv4_address
3334
from tests.network.libs.label_selector import LabelSelector
34-
from tests.network.libs.nodenetworkstate import DEFAULT_ROUTE_V4, lookup_br_ex_gateway_v4
3535
from tests.network.libs.vm_factory import udn_vm
3636
from utilities.infra import get_node_selector_dict
3737

@@ -42,60 +42,44 @@
4242
EXTERNAL_PROVIDER_SUBNET_IPV4: Final[str] = f"{random_ipv4_address(net_seed=1, host_address=0)}/24"
4343
EXTERNAL_PROVIDER_IP_V4: Final[str] = f"{random_ipv4_address(net_seed=1, host_address=150)}/24"
4444
IPERF3_SERVER_PORT: Final[int] = 2354
45+
LOCALNET_NETWORK_NAME: Final[str] = "localnet-network-bgp"
4546

4647

47-
@pytest.fixture(scope="session")
48-
def vlan_nncp(
49-
admin_client: DynamicClient,
50-
vlan_base_iface: str,
51-
worker_node1: Node,
48+
@pytest.fixture(scope="module")
49+
def nncp_localnet_node1(
50+
admin_client: DynamicClient, worker_node1: Node
5251
) -> Generator[libnncp.NodeNetworkConfigurationPolicy]:
52+
desired_state = libnncp.DesiredState(
53+
ovn=libnncp.OVN([
54+
libnncp.BridgeMappings(
55+
localnet=LOCALNET_NETWORK_NAME,
56+
bridge=libnncp.DEFAULT_OVN_EXTERNAL_BRIDGE,
57+
state=libnncp.BridgeMappings.State.PRESENT.value,
58+
)
59+
])
60+
)
5361
with libnncp.NodeNetworkConfigurationPolicy(
5462
client=admin_client,
55-
name="test-vlan-nncp",
56-
desired_state=libnncp.DesiredState(
57-
interfaces=[
58-
libnncp.Interface(
59-
name=f"{vlan_base_iface}.{os.environ['PRIMARY_NODE_NETWORK_VLAN_TAG']}",
60-
state=libnncp.NodeNetworkConfigurationPolicy.Interface.State.UP,
61-
type="vlan",
62-
vlan=libnncp.Vlan(id=int(os.environ["PRIMARY_NODE_NETWORK_VLAN_TAG"]), base_iface=vlan_base_iface),
63-
)
64-
]
65-
),
63+
name="localnet-nncp-bgp",
64+
desired_state=desired_state,
6665
node_selector=get_node_selector_dict(node_selector=worker_node1.hostname),
6766
) as nncp:
6867
nncp.wait_for_status_success()
6968
yield nncp
7069

7170

72-
@pytest.fixture(scope="session")
73-
def br_ex_gateway_v4(worker_node1: Node, admin_client: DynamicClient) -> str:
74-
return lookup_br_ex_gateway_v4(node_name=worker_node1.name, client=admin_client)
75-
76-
77-
@pytest.fixture(scope="session")
78-
def macvlan_nad(
79-
vlan_nncp: libnncp.NodeNetworkConfigurationPolicy,
80-
cnv_tests_utilities_namespace: Namespace,
81-
br_ex_gateway_v4: str,
71+
@pytest.fixture(scope="module")
72+
def nad_localnet(
8273
admin_client: DynamicClient,
83-
) -> Generator[libnad.NetworkAttachmentDefinition]:
84-
macvlan_config = libnad.CNIPluginMacvlanConfig(
85-
master=vlan_nncp.instance.spec.desiredState.interfaces[0].name,
86-
ipam=libnad.IpamStatic(
87-
addresses=[
88-
libnad.IpamStatic.Address(address=os.environ["EXTERNAL_FRR_STATIC_IPV4"], gateway=br_ex_gateway_v4)
89-
],
90-
routes=[libnad.IpamRoute(dst=DEFAULT_ROUTE_V4.dst, gw=br_ex_gateway_v4)],
91-
),
92-
)
93-
94-
with libnad.NetworkAttachmentDefinition(
95-
name="macvlan-nad-bgp",
96-
namespace=cnv_tests_utilities_namespace.name,
97-
config=libnad.NetConfig(name="macvlan-nad-bgp", plugins=[macvlan_config]),
74+
nncp_localnet_node1: libnncp.NodeNetworkConfigurationPolicy,
75+
cnv_tests_utilities_namespace: Namespace,
76+
):
77+
with OVNOverlayNetworkAttachmentDefinition(
9878
client=admin_client,
79+
name="localnet-nad-bgp",
80+
namespace=cnv_tests_utilities_namespace.name,
81+
topology="localnet",
82+
network_name=LOCALNET_NETWORK_NAME,
9983
) as nad:
10084
yield nad
10185

@@ -151,8 +135,8 @@ def cudn_layer2(
151135
subnets=[CUDN_SUBNET_IPV4],
152136
),
153137
),
154-
label=APP_CUDN_LABEL,
155138
client=admin_client,
139+
label=APP_CUDN_LABEL,
156140
) as cudn:
157141
cudn.wait_for_status_success()
158142
yield cudn
@@ -171,10 +155,10 @@ def cudn_route_advertisements(
171155

172156

173157
@pytest.fixture(scope="module")
174-
def frr_configuration_created(admin_client: DynamicClient) -> Generator[None]:
158+
def frr_configuration_created(admin_client: DynamicClient, frr_external_pod: ExternalFrrPodInfo) -> Generator[None]:
175159
with create_frr_configuration(
176160
name="frr-configuration-bgp",
177-
frr_pod_ipv4=os.environ["EXTERNAL_FRR_STATIC_IPV4"].split("/")[0],
161+
frr_pod_ipv4=frr_external_pod.ipv4,
178162
external_subnet_ipv4=EXTERNAL_PROVIDER_SUBNET_IPV4,
179163
client=admin_client,
180164
):
@@ -183,32 +167,30 @@ def frr_configuration_created(admin_client: DynamicClient) -> Generator[None]:
183167

184168
@pytest.fixture(scope="module")
185169
def frr_external_pod(
186-
macvlan_nad: libnad.NetworkAttachmentDefinition,
170+
nad_localnet: libnad.NetworkAttachmentDefinition,
187171
worker_node1: Node,
188172
frr_configmap: ConfigMap,
189173
cnv_tests_utilities_namespace: Namespace,
190-
br_ex_gateway_v4: str,
191174
admin_client: DynamicClient,
192-
) -> Generator[Pod]:
175+
) -> Generator[ExternalFrrPodInfo]:
193176
with deploy_external_frr_pod(
194177
namespace_name=cnv_tests_utilities_namespace.name,
195178
node_name=worker_node1.name,
196-
nad_name=macvlan_nad.name,
179+
nad_name=nad_localnet.name,
197180
frr_configmap_name=frr_configmap.name,
198-
default_route=br_ex_gateway_v4,
199181
client=admin_client,
200-
) as pod:
182+
) as pod_info:
201183
# Assign a secondary IP on the secondary interface to emulate the external provider subnet
202-
pod.execute(
184+
pod_info.pod.execute(
203185
command=shlex.split(f"ip addr add {EXTERNAL_PROVIDER_IP_V4} dev {POD_SECONDARY_IFACE_NAME}"),
204186
container="frr",
205187
)
206-
yield pod
188+
yield pod_info
207189

208190

209191
@pytest.fixture(scope="module")
210192
def bgp_setup_ready(
211-
frr_external_pod: Pod,
193+
frr_external_pod: ExternalFrrPodInfo,
212194
cudn_route_advertisements: None,
213195
frr_configuration_created: None,
214196
workers: list[Node],
@@ -222,8 +204,15 @@ def vm_cudn(
222204
namespace_cudn: Namespace,
223205
cudn_layer2: libcudn.ClusterUserDefinedNetwork,
224206
admin_client: DynamicClient,
207+
frr_external_pod: ExternalFrrPodInfo,
225208
) -> Generator[BaseVirtualMachine]:
226-
with udn_vm(namespace_name=namespace_cudn.name, name="vm-cudn-bgp", client=admin_client) as vm:
209+
with udn_vm(
210+
namespace_name=namespace_cudn.name,
211+
name="vm-cudn-bgp",
212+
client=admin_client,
213+
template_labels=EXTERNAL_FRR_POD_LABEL,
214+
anti_affinity_namespaces=[frr_external_pod.pod.namespace],
215+
) as vm:
227216
vm.start(wait=True)
228217
vm.wait_for_agent_connected()
229218
yield vm
@@ -239,10 +228,10 @@ def tcp_server_cudn_vm(vm_cudn: BaseVirtualMachine) -> Generator[TcpServer]:
239228

240229
@pytest.fixture(scope="module")
241230
def tcp_client_external_network(
242-
frr_external_pod: Pod, vm_cudn: BaseVirtualMachine, tcp_server_cudn_vm: TcpServer
231+
frr_external_pod: ExternalFrrPodInfo, vm_cudn: BaseVirtualMachine, tcp_server_cudn_vm: TcpServer
243232
) -> Generator[TcpClient]:
244233
with TcpClient(
245-
pod=frr_external_pod,
234+
pod=frr_external_pod.pod,
246235
server_ip=lookup_iface_status(vm=vm_cudn, iface_name=lookup_primary_network(vm=vm_cudn).name)[IP_ADDRESS],
247236
server_port=IPERF3_SERVER_PORT,
248237
bind_interface=EXTERNAL_PROVIDER_IP_V4.split("/")[0],

tests/network/bgp/test_bgp_connectivity.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
import pytest
22

33
from libs.net.traffic_generator import is_tcp_connection
4-
from utilities.constants import QUARANTINED
54
from utilities.virt import migrate_vm_and_verify
65

7-
pytestmark = [pytest.mark.bgp, pytest.mark.usefixtures("bgp_setup_ready")]
6+
pytestmark = [pytest.mark.bgp, pytest.mark.ipv4, pytest.mark.usefixtures("bgp_setup_ready")]
87

98

109
@pytest.mark.polarion("CNV-12276")
11-
@pytest.mark.xfail(
12-
reason=f"{QUARANTINED}: BGP test suite infra dependencies are not met, tracked in CNV-69734",
13-
run=False,
14-
)
1510
def test_connectivity_cudn_vm_and_external_network(tcp_server_cudn_vm, tcp_client_external_network):
1611
assert is_tcp_connection(server=tcp_server_cudn_vm, client=tcp_client_external_network)
1712

1813

1914
@pytest.mark.polarion("CNV-12281")
20-
@pytest.mark.xfail(
21-
reason=f"{QUARANTINED}: BGP test suite infra dependencies are not met, tracked in CNV-69734",
22-
run=False,
23-
)
2415
def test_connectivity_is_preserved_during_cudn_vm_migration(
2516
tcp_server_cudn_vm,
2617
tcp_client_external_network,

tests/network/conftest.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"""
66

77
import logging
8-
import os
98

109
import pytest
1110
from kubernetes.dynamic import DynamicClient
@@ -340,26 +339,6 @@ def _verify_ip_family(family, is_supported_in_cluster):
340339
else:
341340
LOGGER.info(f"Validated network lane is running against an {family} supported cluster")
342341

343-
def _verify_bgp_env_vars():
344-
"""Verify if the cluster supports running BGP tests.
345-
346-
Requires the following environment variables to be set:
347-
PRIMARY_NODE_NETWORK_VLAN_TAG: expected VLAN number on the node br-ex interface.
348-
EXTERNAL_FRR_STATIC_IPV4: reserved IP in CIDR format for the external FRR pod inside
349-
PRIMARY_NODE_NETWORK_VLAN_TAG network.
350-
"""
351-
if any(test.get_closest_marker("bgp") and not test.get_closest_marker("xfail") for test in collected_tests):
352-
LOGGER.info("Verifying if the cluster supports running BGP tests...")
353-
required_env_vars = [
354-
"PRIMARY_NODE_NETWORK_VLAN_TAG",
355-
"EXTERNAL_FRR_STATIC_IPV4",
356-
]
357-
missing_env_vars = [var for var in required_env_vars if not os.getenv(var)]
358-
359-
if missing_env_vars:
360-
failure_msgs.append(f"BGP tests require the following environment variables: {missing_env_vars}")
361-
return
362-
363342
def _verify_nmstate_running_pods(_admin_client, namespace):
364343
# TODO: Only test if nmstate is required by the test(s)
365344
if not namespace:
@@ -395,7 +374,6 @@ def _verify_mtv_installed():
395374
_verify_sriov()
396375
_verify_ip_family(family="ipv4", is_supported_in_cluster=ipv4_supported_cluster)
397376
_verify_ip_family(family="ipv6", is_supported_in_cluster=ipv6_supported_cluster)
398-
_verify_bgp_env_vars()
399377
_verify_nmstate_running_pods(_admin_client=admin_client, namespace=nmstate_namespace)
400378
_verify_mtv_installed()
401379

0 commit comments

Comments
 (0)