Skip to content

Commit 53198d5

Browse files
committed
Namespaced network object end2end tests
Move `[sriov] operator No SriovNetworkNodePolicy ...` test cases to its own test file. Implement test case to verify controller and webhook logic Signed-off-by: Andrea Panattoni <[email protected]>
1 parent 1c97f9a commit 53198d5

File tree

2 files changed

+297
-238
lines changed

2 files changed

+297
-238
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
package tests
2+
3+
import (
4+
"context"
5+
"strings"
6+
"time"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
11+
sriovv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
12+
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
13+
"github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/cluster"
14+
"github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/discovery"
15+
"github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/namespaces"
16+
"github.com/k8snetworkplumbingwg/sriov-network-operator/test/util/nodes"
17+
. "github.com/onsi/ginkgo/v2"
18+
. "github.com/onsi/gomega"
19+
corev1 "k8s.io/api/core/v1"
20+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
21+
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
22+
)
23+
24+
var _ = Describe("[sriov] operator", Ordered, ContinueOnFailure, func() {
25+
Describe("No SriovNetworkNodePolicy", func() {
26+
Context("SR-IOV network config daemon can be set by nodeselector", func() {
27+
// 26186
28+
It("Should schedule the config daemon on selected nodes", func() {
29+
if discovery.Enabled() {
30+
Skip("Test unsuitable to be run in discovery mode")
31+
}
32+
33+
By("Checking that a daemon is scheduled on each worker node")
34+
Eventually(func() bool {
35+
return daemonsScheduledOnNodes("node-role.kubernetes.io/worker=")
36+
}, 3*time.Minute, 1*time.Second).Should(Equal(true))
37+
38+
By("Labeling one worker node with the label needed for the daemon")
39+
allNodes, err := clients.CoreV1Interface.Nodes().List(context.Background(), metav1.ListOptions{
40+
LabelSelector: "node-role.kubernetes.io/worker",
41+
})
42+
Expect(err).ToNot(HaveOccurred())
43+
44+
selectedNodes, err := nodes.MatchingOptionalSelector(clients, allNodes.Items)
45+
Expect(err).ToNot(HaveOccurred())
46+
47+
Expect(len(selectedNodes)).To(BeNumerically(">", 0), "There must be at least one worker")
48+
candidate := selectedNodes[0]
49+
candidate.Labels["sriovenabled"] = "true"
50+
_, err = clients.CoreV1Interface.Nodes().Update(context.Background(), &candidate, metav1.UpdateOptions{})
51+
Expect(err).ToNot(HaveOccurred())
52+
53+
By("Setting the node selector for each daemon")
54+
cfg := sriovv1.SriovOperatorConfig{}
55+
err = clients.Get(context.TODO(), runtimeclient.ObjectKey{
56+
Name: "default",
57+
Namespace: operatorNamespace,
58+
}, &cfg)
59+
Expect(err).ToNot(HaveOccurred())
60+
cfg.Spec.ConfigDaemonNodeSelector = map[string]string{
61+
"sriovenabled": "true",
62+
}
63+
Eventually(func() error {
64+
return clients.Update(context.TODO(), &cfg)
65+
}, 1*time.Minute, 5*time.Second).ShouldNot(HaveOccurred())
66+
67+
By("Checking that a daemon is scheduled only on selected node")
68+
Eventually(func() bool {
69+
return !daemonsScheduledOnNodes("sriovenabled!=true") &&
70+
daemonsScheduledOnNodes("sriovenabled=true")
71+
}, 1*time.Minute, 1*time.Second).Should(Equal(true))
72+
73+
By("Restoring the node selector for daemons")
74+
err = clients.Get(context.TODO(), runtimeclient.ObjectKey{
75+
Name: "default",
76+
Namespace: operatorNamespace,
77+
}, &cfg)
78+
Expect(err).ToNot(HaveOccurred())
79+
cfg.Spec.ConfigDaemonNodeSelector = map[string]string{}
80+
Eventually(func() error {
81+
return clients.Update(context.TODO(), &cfg)
82+
}, 1*time.Minute, 5*time.Second).ShouldNot(HaveOccurred())
83+
84+
By("Checking that a daemon is scheduled on each worker node")
85+
Eventually(func() bool {
86+
return daemonsScheduledOnNodes("node-role.kubernetes.io/worker")
87+
}, 1*time.Minute, 1*time.Second).Should(Equal(true))
88+
})
89+
})
90+
91+
Context("LogLevel affects operator's logs", func() {
92+
It("when set to 0 no lifecycle logs are present", func() {
93+
if discovery.Enabled() {
94+
Skip("Test unsuitable to be run in discovery mode")
95+
}
96+
97+
initialLogLevelValue := getOperatorConfigLogLevel()
98+
DeferCleanup(func() {
99+
By("Restore LogLevel to its initial value")
100+
setOperatorConfigLogLevel(initialLogLevelValue)
101+
})
102+
103+
initialDisableDrain, err := cluster.GetNodeDrainState(clients, operatorNamespace)
104+
Expect(err).ToNot(HaveOccurred())
105+
106+
DeferCleanup(func() {
107+
By("Restore DisableDrain to its initial value")
108+
Eventually(func() error {
109+
return cluster.SetDisableNodeDrainState(clients, operatorNamespace, initialDisableDrain)
110+
}, 1*time.Minute, 5*time.Second).ShouldNot(HaveOccurred())
111+
})
112+
113+
By("Set operator LogLevel to 2")
114+
setOperatorConfigLogLevel(2)
115+
116+
By("Flip DisableDrain to trigger operator activity")
117+
since := time.Now().Add(-10 * time.Second)
118+
Eventually(func() error {
119+
return cluster.SetDisableNodeDrainState(clients, operatorNamespace, !initialDisableDrain)
120+
}, 1*time.Minute, 5*time.Second).ShouldNot(HaveOccurred())
121+
122+
By("Assert logs contains verbose output")
123+
Eventually(func(g Gomega) {
124+
logs := getOperatorLogs(since)
125+
g.Expect(logs).To(
126+
ContainElement(And(
127+
ContainSubstring("Reconciling SriovOperatorConfig"),
128+
)),
129+
)
130+
131+
// Should contain verbose logging
132+
g.Expect(logs).To(
133+
ContainElement(
134+
ContainSubstring("Start to sync webhook objects"),
135+
),
136+
)
137+
}, 1*time.Minute, 5*time.Second).Should(Succeed())
138+
139+
By("Reduce operator LogLevel to 0")
140+
setOperatorConfigLogLevel(0)
141+
142+
By("Flip DisableDrain again to trigger operator activity")
143+
since = time.Now().Add(-10 * time.Second)
144+
Eventually(func() error {
145+
return cluster.SetDisableNodeDrainState(clients, operatorNamespace, initialDisableDrain)
146+
}, 1*time.Minute, 5*time.Second).ShouldNot(HaveOccurred())
147+
148+
By("Assert logs contains less operator activity")
149+
Eventually(func(g Gomega) {
150+
logs := getOperatorLogs(since)
151+
152+
// time only contains sec, but we can have race here that in the same sec there was a sync
153+
afterLogs := []string{}
154+
found := false
155+
for _, log := range logs {
156+
if found {
157+
afterLogs = append(afterLogs, log)
158+
}
159+
if strings.Contains(log, "{\"new-level\": 0, \"current-level\": 2}") {
160+
found = true
161+
}
162+
}
163+
g.Expect(found).To(BeTrue())
164+
g.Expect(afterLogs).To(
165+
ContainElement(And(
166+
ContainSubstring("Reconciling SriovOperatorConfig"),
167+
)),
168+
)
169+
170+
// Should not contain verbose logging
171+
g.Expect(afterLogs).ToNot(
172+
ContainElement(
173+
ContainSubstring("Start to sync webhook objects"),
174+
),
175+
)
176+
}, 3*time.Minute, 5*time.Second).Should(Succeed())
177+
})
178+
})
179+
180+
DescribeTable("should gracefully restart quickly", func(webookEnabled bool) {
181+
DeferCleanup(setSriovOperatorSpecFlag(operatorNetworkInjectorFlag, webookEnabled))
182+
DeferCleanup(setSriovOperatorSpecFlag(operatorWebhookFlag, webookEnabled))
183+
184+
// This test case ensure leader election process runs smoothly when the operator's pod is restarted.
185+
oldLease, err := clients.CoordinationV1Interface.Leases(operatorNamespace).Get(context.Background(), consts.LeaderElectionID, metav1.GetOptions{})
186+
if k8serrors.IsNotFound(err) {
187+
Skip("Leader Election is not enabled on the cluster. Skipping")
188+
}
189+
Expect(err).ToNot(HaveOccurred())
190+
191+
oldOperatorPod := getOperatorPod()
192+
193+
By("Delete the operator's pod")
194+
deleteOperatorPod()
195+
196+
By("Wait the new operator's pod to start")
197+
Eventually(func(g Gomega) {
198+
newOperatorPod := getOperatorPod()
199+
Expect(newOperatorPod.Name).ToNot(Equal(oldOperatorPod.Name))
200+
Expect(newOperatorPod.Status.Phase).To(Equal(corev1.PodRunning))
201+
}, 45*time.Second, 5*time.Second)
202+
203+
By("Assert the new operator's pod acquire the lease before 30 seconds")
204+
Eventually(func(g Gomega) {
205+
newLease, err := clients.CoordinationV1Interface.Leases(operatorNamespace).Get(context.Background(), consts.LeaderElectionID, metav1.GetOptions{})
206+
g.Expect(err).ToNot(HaveOccurred())
207+
208+
g.Expect(newLease.Spec.HolderIdentity).ToNot(Equal(oldLease.Spec.HolderIdentity))
209+
}, 30*time.Second, 5*time.Second).Should(Succeed())
210+
},
211+
Entry("webhooks enabled", true),
212+
Entry("webhooks disabled", true),
213+
)
214+
215+
Context("SriovNetworkMetricsExporter", func() {
216+
BeforeEach(func() {
217+
if discovery.Enabled() {
218+
Skip("Test unsuitable to be run in discovery mode")
219+
}
220+
221+
initialValue := isFeatureFlagEnabled("metricsExporter")
222+
DeferCleanup(func() {
223+
By("Restoring initial feature flag value")
224+
setFeatureFlag("metricsExporter", initialValue)
225+
})
226+
227+
By("Enabling `metricsExporter` feature flag")
228+
setFeatureFlag("metricsExporter", true)
229+
})
230+
231+
It("should be deployed if the feature gate is enabled", func() {
232+
By("Checking that a daemon is scheduled on selected node")
233+
Eventually(func() bool {
234+
return isDaemonsetScheduledOnNodes("node-role.kubernetes.io/worker", "app=sriov-network-metrics-exporter")
235+
}).WithTimeout(time.Minute).WithPolling(time.Second).Should(Equal(true))
236+
})
237+
238+
It("should deploy ServiceMonitor if the Prometheus operator is installed", func() {
239+
_, err := clients.ServiceMonitors(operatorNamespace).List(context.Background(), metav1.ListOptions{})
240+
if k8serrors.IsNotFound(err) {
241+
Skip("Prometheus operator not available in the cluster")
242+
}
243+
244+
By("Checking ServiceMonitor is deployed if needed")
245+
Eventually(func(g Gomega) {
246+
_, err := clients.ServiceMonitors(operatorNamespace).Get(context.Background(), "sriov-network-metrics-exporter", metav1.GetOptions{})
247+
g.Expect(err).ToNot(HaveOccurred())
248+
}).WithTimeout(time.Minute).WithPolling(time.Second).Should(Succeed())
249+
})
250+
251+
It("should remove ServiceMonitor when the feature is turned off", func() {
252+
setFeatureFlag("metricsExporter", false)
253+
Eventually(func(g Gomega) {
254+
_, err := clients.ServiceMonitors(operatorNamespace).Get(context.Background(), "sriov-network-metrics-exporter", metav1.GetOptions{})
255+
g.Expect(k8serrors.IsNotFound(err)).To(BeTrue())
256+
}).WithTimeout(time.Minute).WithPolling(time.Second).Should(Succeed())
257+
})
258+
})
259+
260+
Context("Namespaced network objects", func() {
261+
BeforeAll(func() {
262+
263+
})
264+
265+
DescribeTable("can be create in every namespaces", func(object runtimeclient.Object) {
266+
err := clients.Create(context.Background(), object)
267+
Expect(err).ToNot(HaveOccurred())
268+
269+
waitForNetAttachDef(object.GetName(), object.GetNamespace())
270+
271+
err = clients.Delete(context.Background(), object)
272+
Expect(err).ToNot(HaveOccurred())
273+
274+
Eventually(func() bool {
275+
netAttDef := &netattdefv1.NetworkAttachmentDefinition{}
276+
err := clients.Get(context.Background(), runtimeclient.ObjectKey{Name: object.GetName(), Namespace: object.GetNamespace()}, netAttDef)
277+
return err != nil && k8serrors.IsNotFound(err)
278+
}, 2*time.Minute, 10*time.Second).Should(BeTrue())
279+
},
280+
Entry("SriovNetwork", &sriovv1.SriovNetwork{ObjectMeta: metav1.ObjectMeta{Name: "sriovnet1", Namespace: namespaces.Test}}),
281+
Entry("SriovIBNetwork", &sriovv1.SriovIBNetwork{ObjectMeta: metav1.ObjectMeta{Name: "sriovibnet1", Namespace: namespaces.Test}}),
282+
Entry("OVSNetwork", &sriovv1.OVSNetwork{ObjectMeta: metav1.ObjectMeta{Name: "ovsnet1", Namespace: namespaces.Test}}),
283+
)
284+
285+
DescribeTable("can NOT be in application namespace and have .Spec.NetworkNamespace != ''", func(object runtimeclient.Object) {
286+
err := clients.Create(context.Background(), object)
287+
Expect(err).To(HaveOccurred())
288+
Expect(k8serrors.ReasonForError(err)).
289+
To(Equal(metav1.StatusReason(".Spec.NetworkNamespace field can't be specified if the resource is not in the openshift-sriov-network-operator namespace")))
290+
},
291+
Entry("SriovNetwork", &sriovv1.SriovNetwork{ObjectMeta: metav1.ObjectMeta{Name: "sriovnet1", Namespace: namespaces.Test}, Spec: sriovv1.SriovNetworkSpec{NetworkNamespace: "default"}}),
292+
Entry("SriovIBNetwork", &sriovv1.SriovIBNetwork{ObjectMeta: metav1.ObjectMeta{Name: "sriovibnet1", Namespace: namespaces.Test}, Spec: sriovv1.SriovIBNetworkSpec{NetworkNamespace: "default"}}),
293+
Entry("OVSNetwork", &sriovv1.OVSNetwork{ObjectMeta: metav1.ObjectMeta{Name: "ovsnet1", Namespace: namespaces.Test}, Spec: sriovv1.OVSNetworkSpec{NetworkNamespace: "default"}}),
294+
)
295+
})
296+
})
297+
})

0 commit comments

Comments
 (0)