Skip to content

Commit fe1f3a0

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 9fbde7e commit fe1f3a0

File tree

2 files changed

+299
-238
lines changed

2 files changed

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

0 commit comments

Comments
 (0)