Skip to content

Commit 3437949

Browse files
Merge pull request #30973 from BhargaviGudi/node-swap
OCPBUGS-81638: Manual cherry-pick node swap feature testcases to 4.21
2 parents e6cd3ea + bd9c3fe commit 3437949

12 files changed

Lines changed: 2708 additions & 1 deletion

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ require (
116116
k8s.io/kube-aggregator v0.34.1
117117
k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3
118118
k8s.io/kubectl v0.34.1
119+
k8s.io/kubelet v0.31.1
119120
k8s.io/kubernetes v1.34.1
120121
k8s.io/pod-security-admission v0.34.1
121122
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
@@ -399,7 +400,6 @@ require (
399400
k8s.io/externaljwt v0.0.0 // indirect
400401
k8s.io/kms v0.34.1 // indirect
401402
k8s.io/kube-scheduler v0.0.0 // indirect
402-
k8s.io/kubelet v0.31.1 // indirect
403403
k8s.io/mount-utils v0.0.0 // indirect
404404
k8s.io/sample-apiserver v0.0.0 // indirect
405405
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect

test/extended/node/node_swap.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
g "github.com/onsi/ginkgo/v2"
10+
o "github.com/onsi/gomega"
11+
ote "github.com/openshift-eng/openshift-tests-extension/pkg/ginkgo"
12+
13+
corev1 "k8s.io/api/core/v1"
14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"k8s.io/apimachinery/pkg/util/wait"
18+
"k8s.io/kubernetes/test/e2e/framework"
19+
20+
configv1 "github.com/openshift/api/config/v1"
21+
machineconfigv1 "github.com/openshift/api/machineconfiguration/v1"
22+
mcclient "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
23+
exutil "github.com/openshift/origin/test/extended/util"
24+
)
25+
26+
var _ = g.Describe("[Jira:Node][sig-node] Node non-cnv swap configuration", func() {
27+
defer g.GinkgoRecover()
28+
29+
var oc = exutil.NewCLI("node-swap")
30+
31+
g.BeforeEach(func(ctx context.Context) {
32+
// Skip all tests on MicroShift clusters
33+
isMicroShift, err := exutil.IsMicroShiftCluster(oc.AdminKubeClient())
34+
o.Expect(err).NotTo(o.HaveOccurred())
35+
if isMicroShift {
36+
g.Skip("Skipping test on MicroShift cluster")
37+
}
38+
})
39+
40+
// This test validates that:
41+
// - Worker nodes have failSwapOn=false to allow kubelet to start even if swap is present at OS level
42+
// - Control plane nodes have failSwapOn=true to prevent kubelet from starting if swap is enabled
43+
// - All nodes have swapBehavior=NoSwap to ensure kubelet does not utilize swap even if available at OS level
44+
// The swapBehavior=NoSwap configuration ensures that even if swap is manually enabled on a worker node,
45+
// the kubelet will not use it for memory management, maintaining consistent behavior across the cluster.
46+
g.It("should have correct default kubelet swap settings with worker nodes failSwapOn=false, control plane nodes failSwapOn=true, and both swapBehavior=NoSwap [OCP-86394]", ote.Informing(), func(ctx context.Context) {
47+
g.By("Getting worker nodes")
48+
allWorkerNodes, err := getNodesByLabel(ctx, oc, "node-role.kubernetes.io/worker")
49+
o.Expect(err).NotTo(o.HaveOccurred())
50+
o.Expect(len(allWorkerNodes)).Should(o.BeNumerically(">", 0), "Expected at least one worker node")
51+
52+
// Filter out nodes that are also control plane (e.g., SNO)
53+
workerNodes := getPureWorkerNodes(allWorkerNodes)
54+
55+
g.By("Validating kubelet configuration on each worker node")
56+
for _, node := range workerNodes {
57+
config, err := getKubeletConfigFromNode(ctx, oc, node.Name)
58+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get kubelet config for worker node %s", node.Name)
59+
60+
g.By(fmt.Sprintf("Checking failSwapOn=false on worker node %s", node.Name))
61+
o.Expect(config.FailSwapOn).NotTo(o.BeNil(), "failSwapOn should be set on worker node %s", node.Name)
62+
o.Expect(*config.FailSwapOn).To(o.BeFalse(), "failSwapOn should be false on worker node %s", node.Name)
63+
framework.Logf("Worker node %s: failSwapOn=%v ✓", node.Name, *config.FailSwapOn)
64+
65+
g.By(fmt.Sprintf("Checking swapBehavior=NoSwap on worker node %s", node.Name))
66+
o.Expect(config.MemorySwap).NotTo(o.BeNil(), "memorySwap should be set on worker node %s", node.Name)
67+
o.Expect(config.MemorySwap.SwapBehavior).To(o.Equal("NoSwap"), "swapBehavior should be NoSwap on worker node %s", node.Name)
68+
framework.Logf("Worker node %s: swapBehavior=%s ✓", node.Name, config.MemorySwap.SwapBehavior)
69+
}
70+
71+
// Skip control plane validation on Hypershift (control plane is external)
72+
controlPlaneTopology, err := exutil.GetControlPlaneTopology(oc)
73+
o.Expect(err).NotTo(o.HaveOccurred())
74+
75+
if *controlPlaneTopology != configv1.ExternalTopologyMode {
76+
g.By("Getting control plane nodes")
77+
controlPlaneNodes, err := getControlPlaneNodes(ctx, oc)
78+
o.Expect(err).NotTo(o.HaveOccurred())
79+
o.Expect(len(controlPlaneNodes)).Should(o.BeNumerically(">", 0), "Expected at least one control plane node")
80+
81+
g.By("Validating kubelet configuration on each control plane node")
82+
for _, node := range controlPlaneNodes {
83+
config, err := getKubeletConfigFromNode(ctx, oc, node.Name)
84+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get kubelet config for control plane node %s", node.Name)
85+
86+
g.By(fmt.Sprintf("Checking failSwapOn=true on control plane node %s", node.Name))
87+
o.Expect(config.FailSwapOn).NotTo(o.BeNil(), "failSwapOn should be set on control plane node %s", node.Name)
88+
o.Expect(*config.FailSwapOn).To(o.BeTrue(), "failSwapOn should be true on control plane node %s", node.Name)
89+
framework.Logf("Control plane node %s: failSwapOn=%v ✓", node.Name, *config.FailSwapOn)
90+
91+
g.By(fmt.Sprintf("Checking swapBehavior=NoSwap on control plane node %s", node.Name))
92+
o.Expect(config.MemorySwap).NotTo(o.BeNil(), "memorySwap should be set on control plane node %s", node.Name)
93+
o.Expect(config.MemorySwap.SwapBehavior).To(o.Equal("NoSwap"), "swapBehavior should be NoSwap on control plane node %s", node.Name)
94+
framework.Logf("Control plane node %s: swapBehavior=%s ✓", node.Name, config.MemorySwap.SwapBehavior)
95+
}
96+
} else {
97+
framework.Logf("Skipping control plane validation on Hypershift (external control plane)")
98+
}
99+
framework.Logf("Test PASSED: All nodes have correct default swap settings")
100+
})
101+
102+
g.It("should reject user override of swap settings via KubeletConfig API [OCP-86395]", ote.Informing(), func(ctx context.Context) {
103+
// Skip on Hypershift - MachineConfig API is not available
104+
controlPlaneTopology, err := exutil.GetControlPlaneTopology(oc)
105+
o.Expect(err).NotTo(o.HaveOccurred())
106+
if *controlPlaneTopology == configv1.ExternalTopologyMode {
107+
g.Skip("Skipping test on Hypershift - MachineConfig API not available")
108+
}
109+
110+
g.By("Creating machine config client")
111+
mcClient, err := mcclient.NewForConfig(oc.KubeFramework().ClientConfig())
112+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to create machine config client")
113+
114+
g.By("Getting initial machine config resourceVersion")
115+
// Get the initial resourceVersion of the worker machine config before creating KubeletConfig
116+
workerGeneratedKubeletMC, err := getWorkerGeneratedKubeletMC(ctx, mcClient)
117+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to find worker-generated-kubelet MachineConfig")
118+
initialResourceVersion := workerGeneratedKubeletMC.ResourceVersion
119+
framework.Logf("Initial %s resourceVersion: %s", workerGeneratedKubeletMC.Name, initialResourceVersion)
120+
121+
g.By("Creating a KubeletConfig with swap settings")
122+
kcName := fmt.Sprintf("test-swap-override-%d", time.Now().UnixNano())
123+
kubeletConfig := &machineconfigv1.KubeletConfig{
124+
ObjectMeta: metav1.ObjectMeta{
125+
Name: kcName,
126+
},
127+
Spec: machineconfigv1.KubeletConfigSpec{
128+
KubeletConfig: &runtime.RawExtension{
129+
Raw: []byte(`{
130+
"failSwapOn": true,
131+
"memorySwap": {
132+
"swapBehavior": "LimitedSwap"
133+
}
134+
}`),
135+
},
136+
},
137+
}
138+
139+
g.By("Attempting to apply the KubeletConfig")
140+
defer func() {
141+
if err := mcClient.MachineconfigurationV1().KubeletConfigs().Delete(ctx, kcName, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
142+
framework.Logf("cleanup failed for KubeletConfig %s: %v", kcName, err)
143+
}
144+
}()
145+
framework.Logf("Creating KubeletConfig with failSwapOn=true and swapBehavior=LimitedSwap")
146+
_, err = mcClient.MachineconfigurationV1().KubeletConfigs().Create(ctx, kubeletConfig, metav1.CreateOptions{})
147+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to create KubeletConfig")
148+
149+
g.By("Checking KubeletConfig status for expected error message")
150+
err = wait.Poll(2*time.Second, 30*time.Second, func() (bool, error) {
151+
kc, err := mcClient.MachineconfigurationV1().KubeletConfigs().Get(ctx, kcName, metav1.GetOptions{})
152+
if err != nil {
153+
return false, err
154+
}
155+
156+
if kc.Status.ObservedGeneration != kc.Generation {
157+
framework.Logf("Waiting for controller to process generation %d (current: %d)", kc.Generation, kc.Status.ObservedGeneration)
158+
return false, nil
159+
}
160+
161+
// Fail fast if KubeletConfig was unexpectedly accepted
162+
for _, condition := range kc.Status.Conditions {
163+
if condition.Type == machineconfigv1.KubeletConfigSuccess && condition.Status == corev1.ConditionFalse {
164+
return false, fmt.Errorf("KubeletConfig was unexpectedly accepted")
165+
}
166+
}
167+
168+
// Check for Failure condition with the expected error message
169+
for _, condition := range kc.Status.Conditions {
170+
if condition.Type == machineconfigv1.KubeletConfigFailure && condition.Status == corev1.ConditionFalse {
171+
framework.Logf("Found Failure condition: %s", condition.Message)
172+
if strings.Contains(condition.Message, "failSwapOn is not allowed to be set") {
173+
return true, nil
174+
}
175+
}
176+
}
177+
return false, nil
178+
})
179+
o.Expect(err).NotTo(o.HaveOccurred(), "Expected to find error message about failSwapOn not being allowed in KubeletConfig status")
180+
181+
g.By("Verifying machine config was not created or updated")
182+
// Wait a bit to ensure no update happens
183+
time.Sleep(5 * time.Second)
184+
185+
// Check if the machine config was created or updated (compare to initial resourceVersion captured earlier)
186+
workerMCAfter, err := getWorkerGeneratedKubeletMC(ctx, mcClient)
187+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to find worker-generated-kubelet MachineConfig for verification")
188+
o.Expect(workerMCAfter.ResourceVersion).To(o.Equal(initialResourceVersion), "Machine config %s should not be updated when failSwapOn is rejected", workerMCAfter.Name)
189+
framework.Logf("Verified: %s was not updated (resourceVersion: %s)", workerMCAfter.Name, workerMCAfter.ResourceVersion)
190+
191+
g.By("Verifying worker nodes still have correct swap settings")
192+
allWorkerNodes, err := getNodesByLabel(ctx, oc, "node-role.kubernetes.io/worker")
193+
o.Expect(err).NotTo(o.HaveOccurred())
194+
o.Expect(len(allWorkerNodes)).Should(o.BeNumerically(">", 0), "Expected at least one worker node")
195+
196+
// Filter out nodes that are also control plane (e.g., SNO)
197+
workerNodes := getPureWorkerNodes(allWorkerNodes)
198+
199+
for _, node := range workerNodes {
200+
config, err := getKubeletConfigFromNode(ctx, oc, node.Name)
201+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get kubelet config for worker node %s", node.Name)
202+
203+
g.By(fmt.Sprintf("Verifying failSwapOn=false remains unchanged on worker node %s", node.Name))
204+
o.Expect(config.FailSwapOn).NotTo(o.BeNil(), "failSwapOn should be set on worker node %s", node.Name)
205+
o.Expect(*config.FailSwapOn).To(o.BeFalse(), "failSwapOn should still be false on worker node %s after rejection", node.Name)
206+
framework.Logf("Worker node %s: failSwapOn=%v (unchanged) ✓", node.Name, *config.FailSwapOn)
207+
208+
g.By(fmt.Sprintf("Verifying swapBehavior=NoSwap remains unchanged on worker node %s", node.Name))
209+
o.Expect(config.MemorySwap).NotTo(o.BeNil(), "memorySwap should be set on worker node %s", node.Name)
210+
o.Expect(config.MemorySwap.SwapBehavior).To(o.Equal("NoSwap"), "swapBehavior should still be NoSwap on worker node %s after rejection", node.Name)
211+
framework.Logf("Worker node %s: swapBehavior=%s (unchanged) ✓", node.Name, config.MemorySwap.SwapBehavior)
212+
}
213+
214+
framework.Logf("Test PASSED: KubeletConfig with failSwapOn was properly rejected")
215+
})
216+
})

0 commit comments

Comments
 (0)