Skip to content

Commit c97d85b

Browse files
committed
MGMT-20706: fix DPU host mode support for self-hosted OVN templates
- Add conditional logic to self-hosted ovnkube-node.yaml template to exclude OVN database and control plane containers (ovn-controller, ovn-acl-logging, kube-rbac-proxy-ovn-metrics, northd, nbdb, sbdb) when OVN_NODE_MODE=dpu-host - Ensure only essential containers (ovnkube-controller, kube-rbac-proxy-node) run in dpu-host mode for both managed and self-hosted deployments - Add comprehensive unified tests covering both template types across all OVN_NODE_MODE values (full, smart-nic, dpu-host) with container validation, YAML validity checks, and Azure platform compatibility and shared helper functions This enables DPU host nodes to run minimal OVN components while maintaining full functionality for other deployment modes.
1 parent bf88d53 commit c97d85b

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed

bindata/network/ovn-kubernetes/managed/ovnkube-node.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ spec:
185185
- name: ovn-node-metrics-cert
186186
mountPath: /etc/pki/tls/metrics-cert
187187
readOnly: True
188+
{{ if ne .OVN_NODE_MODE "dpu-host" }}
188189
- name: kube-rbac-proxy-ovn-metrics
189190
image: {{.KubeRBACProxyImage}}
190191
command:
@@ -209,6 +210,8 @@ spec:
209210
- name: ovn-node-metrics-cert
210211
mountPath: /etc/pki/tls/metrics-cert
211212
readOnly: True
213+
{{ end }}
214+
{{ if ne .OVN_NODE_MODE "dpu-host" }}
212215
# ovn-northd: convert network objects in nbdb to flows in sbdb
213216
- name: northd
214217
image: "{{.OvnImage}}"
@@ -369,6 +372,7 @@ spec:
369372
cpu: 10m
370373
memory: 300Mi
371374
terminationMessagePolicy: FallbackToLogsOnError
375+
{{ end }}
372376

373377
# ovnkube-controller: does node-level bookkeeping and configuration
374378
- name: ovnkube-controller

bindata/network/ovn-kubernetes/self-hosted/ovnkube-node.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ spec:
220220
- name: ovn-node-metrics-cert
221221
mountPath: /etc/pki/tls/metrics-cert
222222
readOnly: True
223+
{{ if ne .OVN_NODE_MODE "dpu-host" }}
223224
- name: kube-rbac-proxy-ovn-metrics
224225
image: {{.KubeRBACProxyImage}}
225226
command:
@@ -244,6 +245,8 @@ spec:
244245
- name: ovn-node-metrics-cert
245246
mountPath: /etc/pki/tls/metrics-cert
246247
readOnly: True
248+
{{ end }}
249+
{{ if ne .OVN_NODE_MODE "dpu-host" }}
247250
# ovn-northd: convert network objects in nbdb to flows in sbdb
248251
- name: northd
249252
image: "{{.OvnImage}}"
@@ -430,6 +433,7 @@ spec:
430433
cpu: 10m
431434
memory: 300Mi
432435
terminationMessagePolicy: FallbackToLogsOnError
436+
{{ end }}
433437

434438
# ovnkube-controller: does node-level bookkeeping and configuration
435439
- name: ovnkube-controller
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package network
2+
3+
import (
4+
"testing"
5+
6+
"github.com/ghodss/yaml"
7+
. "github.com/onsi/gomega"
8+
appsv1 "k8s.io/api/apps/v1"
9+
uns "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
10+
11+
"github.com/openshift/cluster-network-operator/pkg/render"
12+
)
13+
14+
// TestOVNKubernetesNodeModeTemplates tests that both managed and self-hosted templates
15+
// correctly handle different OVN_NODE_MODE values for container inclusion/exclusion and YAML validity
16+
func TestOVNKubernetesNodeModeTemplates(t *testing.T) {
17+
18+
templates := []struct {
19+
name string
20+
templatePath string
21+
}{
22+
{
23+
name: "managed",
24+
templatePath: "../../bindata/network/ovn-kubernetes/managed/ovnkube-node.yaml",
25+
},
26+
{
27+
name: "self-hosted",
28+
templatePath: "../../bindata/network/ovn-kubernetes/self-hosted/ovnkube-node.yaml",
29+
},
30+
}
31+
32+
modes := []struct {
33+
name string
34+
ovnNodeMode string
35+
expectedContainers []string
36+
expectedDaemonSet string
37+
}{
38+
{
39+
name: "full mode",
40+
ovnNodeMode: "full",
41+
expectedContainers: []string{
42+
"ovn-controller",
43+
"ovn-acl-logging",
44+
"kube-rbac-proxy-node",
45+
"kube-rbac-proxy-ovn-metrics",
46+
"northd",
47+
"nbdb",
48+
"sbdb",
49+
"ovnkube-controller",
50+
},
51+
expectedDaemonSet: "ovnkube-node",
52+
},
53+
{
54+
name: "smart-nic mode",
55+
ovnNodeMode: "smart-nic",
56+
expectedContainers: []string{
57+
"ovn-controller",
58+
"ovn-acl-logging",
59+
"kube-rbac-proxy-node",
60+
"kube-rbac-proxy-ovn-metrics",
61+
"northd",
62+
"nbdb",
63+
"sbdb",
64+
"ovnkube-controller",
65+
},
66+
expectedDaemonSet: "ovnkube-node-smart-nic",
67+
},
68+
{
69+
name: "dpu-host mode",
70+
ovnNodeMode: "dpu-host",
71+
expectedContainers: []string{
72+
"kube-rbac-proxy-node",
73+
"ovnkube-controller",
74+
},
75+
expectedDaemonSet: "ovnkube-node-dpu-host",
76+
},
77+
}
78+
79+
for _, template := range templates {
80+
for _, mode := range modes {
81+
testName := template.name + "_" + mode.name
82+
t.Run(testName, func(t *testing.T) {
83+
g := NewGomegaWithT(t)
84+
85+
// Create render data
86+
data := createTestRenderData(mode.ovnNodeMode)
87+
88+
// Render the template
89+
objs, err := render.RenderTemplate(template.templatePath, &data)
90+
g.Expect(err).NotTo(HaveOccurred(), "Template rendering should succeed for %s %s", template.name, mode.name)
91+
g.Expect(objs).To(HaveLen(1), "Should render exactly one object")
92+
93+
// Verify it's a DaemonSet with correct name
94+
obj := objs[0]
95+
g.Expect(obj.GetKind()).To(Equal("DaemonSet"))
96+
g.Expect(obj.GetName()).To(Equal(mode.expectedDaemonSet))
97+
g.Expect(obj.GetNamespace()).To(Equal("openshift-ovn-kubernetes"))
98+
99+
// Extract container names
100+
containers, found, err := uns.NestedSlice(obj.Object, "spec", "template", "spec", "containers")
101+
g.Expect(err).NotTo(HaveOccurred())
102+
g.Expect(found).To(BeTrue())
103+
104+
var containerNames []string
105+
for _, container := range containers {
106+
cmap := container.(map[string]interface{})
107+
name, found, err := uns.NestedString(cmap, "name")
108+
g.Expect(err).NotTo(HaveOccurred())
109+
g.Expect(found).To(BeTrue())
110+
containerNames = append(containerNames, name)
111+
}
112+
113+
// Verify container list exactly matches expected containers
114+
expectedContainersInterface := make([]interface{}, len(mode.expectedContainers))
115+
for i, container := range mode.expectedContainers {
116+
expectedContainersInterface[i] = container
117+
}
118+
g.Expect(containerNames).To(ConsistOf(expectedContainersInterface...),
119+
"Container list for %s %s should exactly match expected containers", template.name, mode.name)
120+
121+
// Verify YAML validity - the object should be valid YAML
122+
yamlBytes, err := yaml.Marshal(obj)
123+
g.Expect(err).NotTo(HaveOccurred(), "Object should be valid YAML for %s %s", template.name, mode.name)
124+
g.Expect(yamlBytes).NotTo(BeEmpty(), "YAML should not be empty for %s %s", template.name, mode.name)
125+
126+
// Verify it can be unmarshaled back to a DaemonSet
127+
ds := &appsv1.DaemonSet{}
128+
err = yaml.Unmarshal(yamlBytes, ds)
129+
g.Expect(err).NotTo(HaveOccurred(), "Should be able to unmarshal to DaemonSet for %s %s", template.name, mode.name)
130+
g.Expect(ds.Kind).To(Equal("DaemonSet"))
131+
})
132+
}
133+
}
134+
}
135+
136+
// createTestRenderData creates a standard render data structure with all required template variables
137+
func createTestRenderData(ovnNodeMode string) render.RenderData {
138+
data := render.MakeRenderData()
139+
data.Data["OVN_NODE_MODE"] = ovnNodeMode
140+
141+
// Set required template variables to avoid rendering errors
142+
data.Data["OvnImage"] = "registry.redhat.io/openshift4/ose-ovn-kubernetes:latest"
143+
data.Data["KubeRBACProxyImage"] = "registry.redhat.io/openshift4/ose-kube-rbac-proxy:latest"
144+
data.Data["ReleaseVersion"] = "4.14.0"
145+
data.Data["KUBERNETES_SERVICE_PORT"] = "443"
146+
data.Data["KUBERNETES_SERVICE_HOST"] = "kubernetes.default.svc"
147+
data.Data["OVN_CONTROLLER_INACTIVITY_PROBE"] = "30000"
148+
data.Data["OVN_NORTHD_PROBE_INTERVAL"] = "30000"
149+
data.Data["CNIBinDir"] = "/var/lib/cni/bin"
150+
data.Data["CNIConfDir"] = "/etc/cni/net.d"
151+
data.Data["IsSNO"] = false
152+
data.Data["OVNPlatformAzure"] = false
153+
data.Data["NETWORK_NODE_IDENTITY_ENABLE"] = false
154+
data.Data["OVN_NETWORK_SEGMENTATION_ENABLE"] = false
155+
data.Data["DefaultMasqueradeNetworkCIDRs"] = ""
156+
data.Data["OVNIPsecEnable"] = false
157+
data.Data["DpuHostModeLabel"] = ""
158+
data.Data["SmartNicModeLabel"] = ""
159+
data.Data["DpuModeLabel"] = ""
160+
data.Data["MgmtPortResourceName"] = ""
161+
data.Data["HTTP_PROXY"] = ""
162+
data.Data["HTTPS_PROXY"] = ""
163+
data.Data["NO_PROXY"] = ""
164+
data.Data["NetFlowCollectors"] = ""
165+
data.Data["SFlowCollectors"] = ""
166+
data.Data["IPFIXCollectors"] = ""
167+
data.Data["IPFIXCacheMaxFlows"] = ""
168+
data.Data["IPFIXCacheActiveTimeout"] = ""
169+
data.Data["IPFIXSampling"] = ""
170+
data.Data["K8S_APISERVER"] = "https://test:8443"
171+
data.Data["OVNKubeConfigHash"] = "test-hash"
172+
173+
// Additional variables for self-hosted template
174+
data.Data["IsNetworkTypeLiveMigration"] = false
175+
data.Data["V4MasqueradeSubnet"] = ""
176+
data.Data["V6MasqueradeSubnet"] = ""
177+
data.Data["V4JoinSubnet"] = ""
178+
data.Data["V6JoinSubnet"] = ""
179+
data.Data["V4TransitSwitchSubnet"] = ""
180+
data.Data["V6TransitSwitchSubnet"] = ""
181+
data.Data["NodeIdentityCertDuration"] = "24h"
182+
183+
return data
184+
}

0 commit comments

Comments
 (0)