1
16
17 package e2enode
18
19 import (
20 "context"
21 "fmt"
22 "os"
23 "path/filepath"
24 "time"
25
26 v1 "k8s.io/api/core/v1"
27 apiequality "k8s.io/apimachinery/pkg/api/equality"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/types"
31 "k8s.io/apimachinery/pkg/util/uuid"
32 "k8s.io/apimachinery/pkg/util/wait"
33 clientset "k8s.io/client-go/kubernetes"
34 kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
35 "k8s.io/kubernetes/test/e2e/framework"
36 imageutils "k8s.io/kubernetes/test/utils/image"
37 admissionapi "k8s.io/pod-security-admission/api"
38
39 "github.com/google/go-cmp/cmp"
40 "github.com/onsi/ginkgo/v2"
41 "github.com/onsi/gomega"
42 "k8s.io/cli-runtime/pkg/printers"
43 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
44 )
45
46 var _ = SIGDescribe("MirrorPod", func() {
47 f := framework.NewDefaultFramework("mirror-pod")
48 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
49 ginkgo.Context("when create a mirror pod ", func() {
50 var ns, podPath, staticPodName, mirrorPodName string
51 ginkgo.BeforeEach(func(ctx context.Context) {
52 ns = f.Namespace.Name
53 staticPodName = "static-pod-" + string(uuid.NewUUID())
54 mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
55
56 podPath = kubeletCfg.StaticPodPath
57
58 ginkgo.By("create the static pod")
59 err := createStaticPod(podPath, staticPodName, ns,
60 imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
61 framework.ExpectNoError(err)
62
63 ginkgo.By("wait for the mirror pod to be running")
64 gomega.Eventually(ctx, func(ctx context.Context) error {
65 return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
66 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
67 })
68
73 f.It("should be updated when static pod updated", f.WithNodeConformance(), func(ctx context.Context) {
74 ginkgo.By("get mirror pod uid")
75 pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
76 framework.ExpectNoError(err)
77 uid := pod.UID
78
79 ginkgo.By("update the static pod container image")
80 image := imageutils.GetPauseImageName()
81 err = createStaticPod(podPath, staticPodName, ns, image, v1.RestartPolicyAlways)
82 framework.ExpectNoError(err)
83
84 ginkgo.By("wait for the mirror pod to be updated")
85 gomega.Eventually(ctx, func(ctx context.Context) error {
86 return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
87 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
88
89 ginkgo.By("check the mirror pod container image is updated")
90 pod, err = f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
91 framework.ExpectNoError(err)
92 gomega.Expect(pod.Spec.Containers).To(gomega.HaveLen(1))
93 gomega.Expect(pod.Spec.Containers[0].Image).To(gomega.Equal(image))
94 })
95
100 f.It("should be recreated when mirror pod gracefully deleted", f.WithNodeConformance(), func(ctx context.Context) {
101 ginkgo.By("get mirror pod uid")
102 pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
103 framework.ExpectNoError(err)
104 uid := pod.UID
105
106 ginkgo.By("delete the mirror pod with grace period 30s")
107 err = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, mirrorPodName, *metav1.NewDeleteOptions(30))
108 framework.ExpectNoError(err)
109
110 ginkgo.By("wait for the mirror pod to be recreated")
111 gomega.Eventually(ctx, func(ctx context.Context) error {
112 return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
113 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
114 })
115
120 f.It("should be recreated when mirror pod forcibly deleted", f.WithNodeConformance(), func(ctx context.Context) {
121 ginkgo.By("get mirror pod uid")
122 pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
123 framework.ExpectNoError(err)
124 uid := pod.UID
125
126 ginkgo.By("delete the mirror pod with grace period 0s")
127 err = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, mirrorPodName, *metav1.NewDeleteOptions(0))
128 framework.ExpectNoError(err)
129
130 ginkgo.By("wait for the mirror pod to be recreated")
131 gomega.Eventually(ctx, func(ctx context.Context) error {
132 return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
133 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
134 })
135 ginkgo.AfterEach(func(ctx context.Context) {
136 ginkgo.By("delete the static pod")
137 err := deleteStaticPod(podPath, staticPodName, ns)
138 framework.ExpectNoError(err)
139
140 ginkgo.By("wait for the mirror pod to disappear")
141 gomega.Eventually(ctx, func(ctx context.Context) error {
142 return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
143 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
144 })
145 })
146 ginkgo.Context("when create a mirror pod without changes ", func() {
147 var ns, podPath, staticPodName, mirrorPodName string
148 ginkgo.BeforeEach(func() {
149 })
150
155 f.It("should successfully recreate when file is removed and recreated", f.WithNodeConformance(), func(ctx context.Context) {
156 ns = f.Namespace.Name
157 staticPodName = "static-pod-" + string(uuid.NewUUID())
158 mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
159
160 podPath = kubeletCfg.StaticPodPath
161 ginkgo.By("create the static pod")
162 err := createStaticPod(podPath, staticPodName, ns,
163 imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
164 framework.ExpectNoError(err)
165
166 ginkgo.By("wait for the mirror pod to be running")
167 gomega.Eventually(ctx, func(ctx context.Context) error {
168 return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
169 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
170
171 ginkgo.By("delete the pod manifest from disk")
172 err = deleteStaticPod(podPath, staticPodName, ns)
173 framework.ExpectNoError(err)
174
175 ginkgo.By("recreate the file")
176 err = createStaticPod(podPath, staticPodName, ns,
177 imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
178 framework.ExpectNoError(err)
179
180 ginkgo.By("mirror pod should restart with count 1")
181 gomega.Eventually(ctx, func(ctx context.Context) error {
182 return checkMirrorPodRunningWithRestartCount(ctx, 2*time.Second, 2*time.Minute, f.ClientSet, mirrorPodName, ns, 1)
183 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
184
185 ginkgo.By("mirror pod should stay running")
186 gomega.Consistently(ctx, func(ctx context.Context) error {
187 return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
188 }, time.Second*30, time.Second*4).Should(gomega.BeNil())
189
190 ginkgo.By("delete the static pod")
191 err = deleteStaticPod(podPath, staticPodName, ns)
192 framework.ExpectNoError(err)
193
194 ginkgo.By("wait for the mirror pod to disappear")
195 gomega.Eventually(ctx, func(ctx context.Context) error {
196 return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
197 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
198 })
199 })
200 ginkgo.Context("when recreating a static pod", func() {
201 var ns, podPath, staticPodName, mirrorPodName string
202 f.It("it should launch successfully even if it temporarily failed termination due to volume failing to unmount", f.WithNodeConformance(), f.WithSerial(), func(ctx context.Context) {
203 node := getNodeName(ctx, f)
204 ns = f.Namespace.Name
205 c := f.ClientSet
206 nfsTestConfig, nfsServerPod, nfsServerHost := e2evolume.NewNFSServerWithNodeName(ctx, c, ns, []string{"-G", "777", "/exports"}, node)
207 ginkgo.DeferCleanup(func(ctx context.Context) {
208 framework.Logf("Cleaning up NFS server pod")
209 e2evolume.TestServerCleanup(ctx, f, nfsTestConfig)
210 })
211
212 podPath = kubeletCfg.StaticPodPath
213 staticPodName = "static-pod-nfs-test-pod" + string(uuid.NewUUID())
214 mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
215
216 ginkgo.By(fmt.Sprintf("Creating nfs test pod: %s", staticPodName))
217
218 err := createStaticPodUsingNfs(nfsServerHost, node, "sleep 999999", podPath, staticPodName, ns)
219 framework.ExpectNoError(err)
220 ginkgo.By(fmt.Sprintf("Wating for nfs test pod: %s to start running...", staticPodName))
221 gomega.Eventually(func() error {
222 return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
223 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
224
225 mirrorPod, err := c.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
226 framework.ExpectNoError(err)
227
228 hash, ok := mirrorPod.Annotations[kubetypes.ConfigHashAnnotationKey]
229 if !ok || hash == "" {
230 framework.Failf("Failed to get hash for mirrorPod")
231 }
232
233 ginkgo.By("Stopping the NFS server")
234 stopNfsServer(f, nfsServerPod)
235
236 ginkgo.By("Waiting for NFS server to stop...")
237 time.Sleep(30 * time.Second)
238
239 ginkgo.By(fmt.Sprintf("Deleting the static nfs test pod: %s", staticPodName))
240 err = deleteStaticPod(podPath, staticPodName, ns)
241 framework.ExpectNoError(err)
242
243
244 gomega.Consistently(func() bool {
245 return podVolumeDirectoryExists(types.UID(hash))
246 }, 5*time.Minute, 10*time.Second).Should(gomega.BeTrue(), "pod volume should exist while nfs server is stopped")
247
248 ginkgo.By("Start the NFS server")
249 restartNfsServer(f, nfsServerPod)
250
251 ginkgo.By("Waiting for the pod volume to deleted after the NFS server is started")
252 gomega.Eventually(func() bool {
253 return podVolumeDirectoryExists(types.UID(hash))
254 }, 5*time.Minute, 10*time.Second).Should(gomega.BeFalse(), "pod volume should be deleted after nfs server is started")
255
256
257 err = createStaticPodUsingNfs(nfsServerHost, node, "sleep 999999", podPath, staticPodName, ns)
258 framework.ExpectNoError(err)
259 ginkgo.By(fmt.Sprintf("Wating for nfs test pod: %s to start running (after being recreated)", staticPodName))
260 gomega.Eventually(func() error {
261 return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
262 }, 5*time.Minute, 5*time.Second).Should(gomega.BeNil())
263 })
264
265 ginkgo.AfterEach(func(ctx context.Context) {
266 ginkgo.By("delete the static pod")
267 err := deleteStaticPod(podPath, staticPodName, ns)
268 framework.ExpectNoError(err)
269
270 ginkgo.By("wait for the mirror pod to disappear")
271 gomega.Eventually(ctx, func(ctx context.Context) error {
272 return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
273 }, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
274
275 })
276
277 })
278
279 })
280
281 func podVolumeDirectoryExists(uid types.UID) bool {
282 podVolumePath := fmt.Sprintf("/var/lib/kubelet/pods/%s/volumes/", uid)
283 var podVolumeDirectoryExists bool
284
285 if _, err := os.Stat(podVolumePath); !os.IsNotExist(err) {
286 podVolumeDirectoryExists = true
287 }
288
289 return podVolumeDirectoryExists
290 }
291
292
293
294
295 func restartNfsServer(f *framework.Framework, serverPod *v1.Pod) {
296 const startcmd = "/usr/sbin/rpc.nfsd 1"
297 _, _, err := e2evolume.PodExec(f, serverPod, startcmd)
298 framework.ExpectNoError(err)
299
300 }
301
302
303
304
305 func stopNfsServer(f *framework.Framework, serverPod *v1.Pod) {
306 const stopcmd = "/usr/sbin/rpc.nfsd 0"
307 _, _, err := e2evolume.PodExec(f, serverPod, stopcmd)
308 framework.ExpectNoError(err)
309 }
310
311 func createStaticPodUsingNfs(nfsIP string, nodeName string, cmd string, dir string, name string, ns string) error {
312 ginkgo.By("create pod using nfs volume")
313
314 isPrivileged := true
315 cmdLine := []string{"-c", cmd}
316 pod := &v1.Pod{
317 TypeMeta: metav1.TypeMeta{
318 Kind: "Pod",
319 APIVersion: "v1",
320 },
321 ObjectMeta: metav1.ObjectMeta{
322 Name: name,
323 Namespace: ns,
324 },
325 Spec: v1.PodSpec{
326 NodeName: nodeName,
327 Containers: []v1.Container{
328 {
329 Name: "pod-nfs-vol",
330 Image: imageutils.GetE2EImage(imageutils.BusyBox),
331 Command: []string{"/bin/sh"},
332 Args: cmdLine,
333 VolumeMounts: []v1.VolumeMount{
334 {
335 Name: "nfs-vol",
336 MountPath: "/mnt",
337 },
338 },
339 SecurityContext: &v1.SecurityContext{
340 Privileged: &isPrivileged,
341 },
342 },
343 },
344 RestartPolicy: v1.RestartPolicyNever,
345 Volumes: []v1.Volume{
346 {
347 Name: "nfs-vol",
348 VolumeSource: v1.VolumeSource{
349 NFS: &v1.NFSVolumeSource{
350 Server: nfsIP,
351 Path: "/",
352 ReadOnly: false,
353 },
354 },
355 },
356 },
357 },
358 }
359
360 file := staticPodPath(dir, name, ns)
361 f, err := os.OpenFile(file, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
362 if err != nil {
363 return err
364 }
365 defer f.Close()
366
367 y := printers.YAMLPrinter{}
368 y.PrintObj(pod, f)
369
370 return nil
371 }
372
373 func staticPodPath(dir, name, namespace string) string {
374 return filepath.Join(dir, namespace+"-"+name+".yaml")
375 }
376
377 func createStaticPod(dir, name, namespace, image string, restart v1.RestartPolicy) error {
378 template := `
379 apiVersion: v1
380 kind: Pod
381 metadata:
382 name: %s
383 namespace: %s
384 spec:
385 containers:
386 - name: test
387 image: %s
388 restartPolicy: %s
389 `
390 file := staticPodPath(dir, name, namespace)
391 podYaml := fmt.Sprintf(template, name, namespace, image, string(restart))
392
393 f, err := os.OpenFile(file, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
394 if err != nil {
395 return err
396 }
397 defer f.Close()
398
399 _, err = f.WriteString(podYaml)
400 return err
401 }
402
403 func deleteStaticPod(dir, name, namespace string) error {
404 file := staticPodPath(dir, name, namespace)
405 return os.Remove(file)
406 }
407
408 func checkMirrorPodDisappear(ctx context.Context, cl clientset.Interface, name, namespace string) error {
409 _, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
410 if apierrors.IsNotFound(err) {
411 return nil
412 }
413 if err == nil {
414 return fmt.Errorf("mirror pod %v/%v still exists", namespace, name)
415 }
416 return fmt.Errorf("expect mirror pod %v/%v to not exist but got error: %w", namespace, name, err)
417 }
418
419 func checkMirrorPodRunning(ctx context.Context, cl clientset.Interface, name, namespace string) error {
420 pod, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
421 if err != nil {
422 return fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
423 }
424 if pod.Status.Phase != v1.PodRunning {
425 return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
426 }
427 for i := range pod.Status.ContainerStatuses {
428 if pod.Status.ContainerStatuses[i].State.Running == nil {
429 return fmt.Errorf("expected the mirror pod %q with container %q to be running (got containers=%v)", name, pod.Status.ContainerStatuses[i].Name, pod.Status.ContainerStatuses[i].State)
430 }
431 }
432 return validateMirrorPod(ctx, cl, pod)
433 }
434
435 func checkMirrorPodRunningWithRestartCount(ctx context.Context, interval time.Duration, timeout time.Duration, cl clientset.Interface, name, namespace string, count int32) error {
436 var pod *v1.Pod
437 var err error
438 err = wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (bool, error) {
439 pod, err = cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
440 if err != nil {
441 return false, fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
442 }
443 if pod.Status.Phase != v1.PodRunning {
444 return false, fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
445 }
446 for i := range pod.Status.ContainerStatuses {
447 if pod.Status.ContainerStatuses[i].State.Waiting != nil {
448
449 return false, nil
450 }
451 if pod.Status.ContainerStatuses[i].State.Running == nil {
452 return false, fmt.Errorf("expected the mirror pod %q with container %q to be running (got containers=%v)", name, pod.Status.ContainerStatuses[i].Name, pod.Status.ContainerStatuses[i].State)
453 }
454 if pod.Status.ContainerStatuses[i].RestartCount == count {
455
456 return true, nil
457 }
458 }
459 return false, nil
460 })
461 if err != nil {
462 return err
463 }
464 return validateMirrorPod(ctx, cl, pod)
465 }
466
467 func checkMirrorPodRecreatedAndRunning(ctx context.Context, cl clientset.Interface, name, namespace string, oUID types.UID) error {
468 pod, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
469 if err != nil {
470 return fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
471 }
472 if pod.UID == oUID {
473 return fmt.Errorf("expected the uid of mirror pod %q to be changed, got %q", name, pod.UID)
474 }
475 if pod.Status.Phase != v1.PodRunning {
476 return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
477 }
478 return validateMirrorPod(ctx, cl, pod)
479 }
480
481 func validateMirrorPod(ctx context.Context, cl clientset.Interface, mirrorPod *v1.Pod) error {
482 hash, ok := mirrorPod.Annotations[kubetypes.ConfigHashAnnotationKey]
483 if !ok || hash == "" {
484 return fmt.Errorf("expected mirror pod %q to have a hash annotation", mirrorPod.Name)
485 }
486 mirrorHash, ok := mirrorPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]
487 if !ok || mirrorHash == "" {
488 return fmt.Errorf("expected mirror pod %q to have a mirror pod annotation", mirrorPod.Name)
489 }
490 if hash != mirrorHash {
491 return fmt.Errorf("expected mirror pod %q to have a matching mirror pod hash: got %q; expected %q", mirrorPod.Name, mirrorHash, hash)
492 }
493 source, ok := mirrorPod.Annotations[kubetypes.ConfigSourceAnnotationKey]
494 if !ok {
495 return fmt.Errorf("expected mirror pod %q to have a source annotation", mirrorPod.Name)
496 }
497 if source == kubetypes.ApiserverSource {
498 return fmt.Errorf("expected mirror pod %q source to not be 'api'; got: %q", mirrorPod.Name, source)
499 }
500
501 if len(mirrorPod.OwnerReferences) != 1 {
502 return fmt.Errorf("expected mirror pod %q to have a single owner reference: got %d", mirrorPod.Name, len(mirrorPod.OwnerReferences))
503 }
504 node, err := cl.CoreV1().Nodes().Get(ctx, framework.TestContext.NodeName, metav1.GetOptions{})
505 if err != nil {
506 return fmt.Errorf("failed to fetch test node: %w", err)
507 }
508
509 controller := true
510 expectedOwnerRef := metav1.OwnerReference{
511 APIVersion: "v1",
512 Kind: "Node",
513 Name: framework.TestContext.NodeName,
514 UID: node.UID,
515 Controller: &controller,
516 }
517 ref := mirrorPod.OwnerReferences[0]
518 if !apiequality.Semantic.DeepEqual(ref, expectedOwnerRef) {
519 return fmt.Errorf("unexpected mirror pod %q owner ref: %v", mirrorPod.Name, cmp.Diff(expectedOwnerRef, ref))
520 }
521
522 return nil
523 }
524
View as plain text