Skip to content

Commit 38d0d23

Browse files
committed
Add RouteAdvertisements for no-overlay CUDNs
Add per-network FRRConfiguration selector config so unmanaged no-overlay UDN networks can create RouteAdvertisements before waiting for transport acceptance. Render RouteAdvertisements for primary and secondary CUDNs using the UDN network labels, and clean them up with the other UDN cluster-scoped resources. Signed-off-by: Venkat Kunaparaju <vkunaparaju@nvidia.com>
1 parent d8e6021 commit 38d0d23

5 files changed

Lines changed: 142 additions & 5 deletions

File tree

README.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,11 @@ tft:
8383
mode: "(33)"
8484
topology: "(34)"
8585
transport: "(35)"
86-
kubeconfig: (36)
87-
kubeconfig_infra: (36)
88-
dpu_node_host_label: (37)
86+
frr_configuration_selector: # (36)
87+
"(37)": "(38)"
88+
kubeconfig: (39)
89+
kubeconfig_infra: (39)
90+
dpu_node_host_label: (40)
8991
```
9092

9193
1. "name" - This is the name of the test. Any string value to identify the test.
@@ -206,10 +208,13 @@ dpu_node_host_label: (37)
206208
33. "mode" - (Optional) Field under `udn_primary_network`. Supported values are `udn` and `cudn`.
207209
34. "topology" - (Optional) Field under `udn_primary_network`. Supported values are `layer3` and `layer2`.
208210
35. "transport" - (Optional) Field under `udn_primary_network`. Supported values are `overlay` and `no-overlay`; `no-overlay` requires `mode: cudn` and `topology: layer3`.
209-
36. "kubeconfig", "kubeconfig_infra": if set to non-empty strings, then these are the KUBECONFIG
211+
36. "frr_configuration_selector" - (Optional) Field under `udn_primary_network`. Map of `frrConfigurationSelector.matchLabels` labels used to create RouteAdvertisements for unmanaged no-overlay CUDNs. If omitted or empty, RouteAdvertisements are not created.
212+
37. selector label key - A Kubernetes label key under `frr_configuration_selector`.
213+
38. selector label value - A Kubernetes label value under `frr_configuration_selector`. Empty string values are supported.
214+
39. "kubeconfig", "kubeconfig_infra": if set to non-empty strings, then these are the KUBECONFIG
210215
files. "kubeconfig_infra" must be set for DPU cluster mode. If both are empty, the configs
211216
are detected based on the files we find at /root/kubeconfig.*.
212-
37. "dpu_node_host_label": (Required for DPU mode) The label on DPU nodes that identifies
217+
40. "dpu_node_host_label": (Required for DPU mode) The label on DPU nodes that identifies
213218
which host worker node they belong to. For NVIDIA DPUs, use `provisioning.dpu.nvidia.com/host`.
214219

215220

@@ -235,6 +240,18 @@ The primary CIDR defaults to `15.1.0.0/16`. Secondary CIDRs default to `15.2.0.0
235240

236241
`udn_primary_network` supports `mode` values `udn` and `cudn`, `topology` values `layer3` and `layer2`, and `transport` values `overlay` and `no-overlay`. `no-overlay` requires `mode: cudn` and `topology: layer3`.
237242

243+
For unmanaged no-overlay primary CUDNs, set `frr_configuration_selector` to create a RouteAdvertisements object that advertises the selected CUDN pod network. The selector is a map of `frrConfigurationSelector.matchLabels` labels. When `TFT_UDN_NO_OVERLAY_ROUTING_MANAGED` is true, RouteAdvertisements are not created.
244+
245+
```yaml
246+
udn_primary_network:
247+
mode: cudn
248+
topology: layer3
249+
transport: no-overlay
250+
frr_configuration_selector:
251+
network: blue
252+
ra.k8s.ovn.org/example: ""
253+
```
254+
238255
## DPU Mode
239256
240257
When running with a DPU (Data Processing Unit) cluster, the `validate_offload` plugin needs
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: k8s.ovn.org/v1
2+
kind: RouteAdvertisements
3+
metadata:
4+
name: {{ network_name }}
5+
labels:
6+
tft-tests: "udn"
7+
spec:
8+
advertisements:
9+
- PodNetwork
10+
frrConfigurationSelector:
11+
matchLabels:
12+
{%- for key, value in frr_configuration_selector %}
13+
{{ key }}: {{ value }}
14+
{%- endfor %}
15+
networkSelectors:
16+
- clusterUserDefinedNetworkSelector:
17+
networkSelector:
18+
matchLabels:
19+
tft-tests: "udn"
20+
tft-udn-network: {{ network_label }}
21+
networkSelectionType: ClusterUserDefinedNetworks
22+
nodeSelector: {}

testConfig.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,24 @@ def _construct_capabilities_pod(
9898
return {"add": tuple(arg["add"])}
9999

100100

101+
def _construct_str_mapping(pctx: StructParseParseContext) -> Mapping[str, str]:
102+
arg = common.structparse_check_strdict(pctx.arg, pctx.yamlpath)
103+
result: dict[str, str] = {}
104+
for key, value in arg.items():
105+
if not key:
106+
raise pctx.value_error("label key cannot be empty")
107+
if value is None:
108+
result[key] = ""
109+
elif isinstance(value, str):
110+
result[key] = value
111+
else:
112+
raise pctx.value_error(
113+
f"expects string label values but got {repr(value)}",
114+
key=key,
115+
)
116+
return result
117+
118+
101119
T2 = TypeVar("T2", bound="ConfNodeServer | ConfNodeClient")
102120

103121

@@ -602,6 +620,7 @@ class ConfUdnNetwork(StructParseBase):
602620
mode: UdnNetworkMode
603621
topology: UdnNetworkTopology
604622
transport: Optional[UdnNetworkTransport]
623+
frr_configuration_selector: Mapping[str, str]
605624

606625
def serialize(self) -> dict[str, Any]:
607626
data: dict[str, Any] = {
@@ -610,6 +629,8 @@ def serialize(self) -> dict[str, Any]:
610629
}
611630
if self.transport is not None:
612631
data["transport"] = self.transport.name
632+
if self.frr_configuration_selector:
633+
data["frr_configuration_selector"] = dict(self.frr_configuration_selector)
613634
return data
614635

615636
@staticmethod
@@ -626,6 +647,7 @@ def parse(
626647
mode = UdnNetworkMode.UDN
627648
topology = default_topology
628649
transport: Optional[UdnNetworkTransport] = UdnNetworkTransport.OVERLAY
650+
frr_configuration_selector: Mapping[str, str] = {}
629651
else:
630652
with pctx.with_strdict() as varg:
631653
transport_configured = "transport" in varg.vdict
@@ -644,6 +666,11 @@ def parse(
644666
enum_type=UdnNetworkTransport,
645667
default=None,
646668
)
669+
frr_configuration_selector = common.structparse_pop_obj(
670+
varg.for_key("frr_configuration_selector"),
671+
construct=_construct_str_mapping,
672+
default={},
673+
)
647674
if transport is None and topology != UdnNetworkTopology.LOCALNET:
648675
transport = UdnNetworkTransport.OVERLAY
649676
if topology == UdnNetworkTopology.LOCALNET and transport_configured:
@@ -674,13 +701,19 @@ def parse(
674701
"no-overlay transport is only supported for layer3 primary CUDN",
675702
key="transport",
676703
)
704+
if frr_configuration_selector and transport != UdnNetworkTransport.NO_OVERLAY:
705+
raise pctx.value_error(
706+
"frr_configuration_selector requires transport no-overlay",
707+
key="frr_configuration_selector",
708+
)
677709

678710
return ConfUdnNetwork(
679711
yamlidx=pctx.yamlidx,
680712
yamlpath=pctx.yamlpath,
681713
mode=mode,
682714
topology=topology,
683715
transport=transport,
716+
frr_configuration_selector=frr_configuration_selector,
684717
)
685718

686719

tests/test_testConfig.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ def test_udn_primary_network_defaults_preserve_existing_behavior() -> None:
430430
assert primary.mode == UdnNetworkMode.UDN
431431
assert primary.topology == UdnNetworkTopology.LAYER3
432432
assert primary.transport == UdnNetworkTransport.OVERLAY
433+
assert primary.frr_configuration_selector == {}
433434

434435
_check_testConfig(tc)
435436

@@ -443,6 +444,9 @@ def test_udn_primary_network_config() -> None:
443444
mode: cudn
444445
topology: layer3
445446
transport: no-overlay
447+
frr_configuration_selector:
448+
network: blue
449+
ra.k8s.ovn.org/example: ""
446450
connections:
447451
- {}
448452
""")
@@ -455,6 +459,10 @@ def test_udn_primary_network_config() -> None:
455459
assert primary.mode == UdnNetworkMode.CUDN
456460
assert primary.topology == UdnNetworkTopology.LAYER3
457461
assert primary.transport == UdnNetworkTransport.NO_OVERLAY
462+
assert primary.frr_configuration_selector == {
463+
"network": "blue",
464+
"ra.k8s.ovn.org/example": "",
465+
}
458466

459467
_check_testConfig(tc)
460468

trafficFlowTests.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,45 @@ def _wait_for_cudn_transport_accepted(
8585
f"ClusterUserDefinedNetwork/{cudn_name} TransportAccepted not True, continuing"
8686
)
8787

88+
def _setup_udn_route_advertisements(
89+
self,
90+
cfg_descr: ConfigDescriptor,
91+
*,
92+
network: testConfig.ConfUdnNetwork,
93+
network_name: str,
94+
network_label: str,
95+
) -> None:
96+
if not network.frr_configuration_selector:
97+
logger.info(
98+
"No no-overlay FRRConfiguration selector configured; "
99+
f"not creating RouteAdvertisements for {network_name}"
100+
)
101+
return
102+
103+
client = cfg_descr.tc.client_tenant
104+
in_template = tftbase.get_manifest("udn-route-advertisements.yaml.j2")
105+
out_yaml = tftbase.get_manifest_renderpath(
106+
f"udn-route-advertisements-{network_label}.yaml"
107+
)
108+
_j = json.dumps
109+
kjinja2.render_file(
110+
in_template,
111+
{
112+
"network_name": _j(network_name),
113+
"network_label": _j(network_label),
114+
"frr_configuration_selector": [
115+
(_j(k), _j(v))
116+
for k, v in network.frr_configuration_selector.items()
117+
],
118+
},
119+
out_file=out_yaml,
120+
)
121+
logger.info(
122+
f'Generate {network_label} RouteAdvertisements "{out_yaml}" '
123+
f'(from "{in_template}")'
124+
)
125+
client.oc(f"apply -f {out_yaml}", die_on_error=True)
126+
88127
def _setup_udn_network(
89128
self,
90129
cfg_descr: ConfigDescriptor,
@@ -147,6 +186,13 @@ def _setup_udn_network(
147186
)
148187
logger.info(f'Generate {network_label} "{out_yaml}" (from "{in_template}")')
149188
client.oc(f"apply -f {out_yaml}", die_on_error=True)
189+
if is_no_overlay and not routing_managed:
190+
self._setup_udn_route_advertisements(
191+
cfg_descr,
192+
network=network,
193+
network_name=network_name,
194+
network_label=network_label,
195+
)
150196
if is_no_overlay:
151197
self._wait_for_cudn_transport_accepted(cfg_descr, network_name)
152198

@@ -218,6 +264,7 @@ def _setup_udn(self, cfg_descr: ConfigDescriptor) -> None:
218264
mode=network.mode,
219265
topology=network.topology,
220266
transport=network.transport,
267+
frr_configuration_selector={},
221268
)
222269
self._setup_udn_network(
223270
cfg_descr,
@@ -358,6 +405,11 @@ def _cleanup_stale_udn(self, cfg_descr: ConfigDescriptor) -> None:
358405
namespace=None,
359406
may_fail=True,
360407
)
408+
client.oc(
409+
"delete routeadvertisements -l tft-tests",
410+
namespace=None,
411+
may_fail=True,
412+
)
361413
if client.oc_get(f"namespace/{udn_ns}", may_fail=True) is None:
362414
return
363415
logger.info(
@@ -457,6 +509,11 @@ def _cleanup_udn(self, cfg_descr: ConfigDescriptor) -> None:
457509
namespace=None,
458510
may_fail=True,
459511
)
512+
client.oc(
513+
"delete routeadvertisements -l tft-tests",
514+
namespace=None,
515+
may_fail=True,
516+
)
460517
client.oc(f"delete userdefinednetwork -l tft-tests -n {udn_ns}", may_fail=True)
461518
if self._udn_ns_created:
462519
logger.info(f"Deleting UDN namespace {udn_ns}")

0 commit comments

Comments
 (0)