1
16
17 package node
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23 "time"
24
25 v1 "k8s.io/api/core/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/util/intstr"
28 "k8s.io/kubernetes/test/e2e/feature"
29 "k8s.io/kubernetes/test/e2e/framework"
30 e2enode "k8s.io/kubernetes/test/e2e/framework/node"
31 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
32 "k8s.io/kubernetes/test/e2e/nodefeature"
33 imageutils "k8s.io/kubernetes/test/utils/image"
34 admissionapi "k8s.io/pod-security-admission/api"
35
36 "github.com/onsi/ginkgo/v2"
37 "github.com/onsi/gomega"
38 )
39
40 var _ = SIGDescribe("Container Lifecycle Hook", func() {
41 f := framework.NewDefaultFramework("container-lifecycle-hook")
42 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
43 var podClient *e2epod.PodClient
44 const (
45 podCheckInterval = 1 * time.Second
46 postStartWaitTimeout = 2 * time.Minute
47 preStopWaitTimeout = 30 * time.Second
48 )
49 ginkgo.Context("when create a pod with lifecycle hook", func() {
50 var (
51 targetIP, targetURL, targetNode string
52
53 httpPorts = []v1.ContainerPort{
54 {
55 ContainerPort: 8080,
56 Protocol: v1.ProtocolTCP,
57 },
58 }
59 httpsPorts = []v1.ContainerPort{
60 {
61 ContainerPort: 9090,
62 Protocol: v1.ProtocolTCP,
63 },
64 }
65 httpsArgs = []string{
66 "netexec",
67 "--http-port", "9090",
68 "--udp-port", "9091",
69 "--tls-cert-file", "/localhost.crt",
70 "--tls-private-key-file", "/localhost.key",
71 }
72 )
73
74 podHandleHookRequest := e2epod.NewAgnhostPodFromContainers(
75 "", "pod-handle-http-request", nil,
76 e2epod.NewAgnhostContainer("container-handle-http-request", nil, httpPorts, "netexec"),
77 e2epod.NewAgnhostContainer("container-handle-https-request", nil, httpsPorts, httpsArgs...),
78 )
79
80 ginkgo.BeforeEach(func(ctx context.Context) {
81 node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
82 framework.ExpectNoError(err)
83 targetNode = node.Name
84 nodeSelection := e2epod.NodeSelection{}
85 e2epod.SetAffinity(&nodeSelection, targetNode)
86 e2epod.SetNodeSelection(&podHandleHookRequest.Spec, nodeSelection)
87
88 podClient = e2epod.NewPodClient(f)
89 ginkgo.By("create the container to handle the HTTPGet hook request.")
90 newPod := podClient.CreateSync(ctx, podHandleHookRequest)
91 targetIP = newPod.Status.PodIP
92 targetURL = targetIP
93 if strings.Contains(targetIP, ":") {
94 targetURL = fmt.Sprintf("[%s]", targetIP)
95 }
96 })
97 testPodWithHook := func(ctx context.Context, podWithHook *v1.Pod) {
98 ginkgo.By("create the pod with lifecycle hook")
99 podClient.CreateSync(ctx, podWithHook)
100 const (
101 defaultHandler = iota
102 httpsHandler
103 )
104 handlerContainer := defaultHandler
105 if podWithHook.Spec.Containers[0].Lifecycle.PostStart != nil {
106 ginkgo.By("check poststart hook")
107 if podWithHook.Spec.Containers[0].Lifecycle.PostStart.HTTPGet != nil {
108 if v1.URISchemeHTTPS == podWithHook.Spec.Containers[0].Lifecycle.PostStart.HTTPGet.Scheme {
109 handlerContainer = httpsHandler
110 }
111 }
112 gomega.Eventually(ctx, func(ctx context.Context) error {
113 return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
114 `GET /echo\?msg=poststart`)
115 }, postStartWaitTimeout, podCheckInterval).Should(gomega.BeNil())
116 }
117 ginkgo.By("delete the pod with lifecycle hook")
118 podClient.DeleteSync(ctx, podWithHook.Name, *metav1.NewDeleteOptions(15), e2epod.DefaultPodDeletionTimeout)
119 if podWithHook.Spec.Containers[0].Lifecycle.PreStop != nil {
120 ginkgo.By("check prestop hook")
121 if podWithHook.Spec.Containers[0].Lifecycle.PreStop.HTTPGet != nil {
122 if v1.URISchemeHTTPS == podWithHook.Spec.Containers[0].Lifecycle.PreStop.HTTPGet.Scheme {
123 handlerContainer = httpsHandler
124 }
125 }
126 gomega.Eventually(ctx, func(ctx context.Context) error {
127 return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
128 `GET /echo\?msg=prestop`)
129 }, preStopWaitTimeout, podCheckInterval).Should(gomega.BeNil())
130 }
131 }
132
137 framework.ConformanceIt("should execute poststart exec hook properly", f.WithNodeConformance(), func(ctx context.Context) {
138 lifecycle := &v1.Lifecycle{
139 PostStart: &v1.LifecycleHandler{
140 Exec: &v1.ExecAction{
141 Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=poststart"},
142 },
143 },
144 }
145 podWithHook := getPodWithHook("pod-with-poststart-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
146
147 testPodWithHook(ctx, podWithHook)
148 })
149
154 framework.ConformanceIt("should execute prestop exec hook properly", f.WithNodeConformance(), func(ctx context.Context) {
155 lifecycle := &v1.Lifecycle{
156 PreStop: &v1.LifecycleHandler{
157 Exec: &v1.ExecAction{
158 Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=prestop"},
159 },
160 },
161 }
162 podWithHook := getPodWithHook("pod-with-prestop-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
163 testPodWithHook(ctx, podWithHook)
164 })
165
170 framework.ConformanceIt("should execute poststart http hook properly", f.WithNodeConformance(), func(ctx context.Context) {
171 lifecycle := &v1.Lifecycle{
172 PostStart: &v1.LifecycleHandler{
173 HTTPGet: &v1.HTTPGetAction{
174 Path: "/echo?msg=poststart",
175 Host: targetIP,
176 Port: intstr.FromInt32(8080),
177 },
178 },
179 }
180 podWithHook := getPodWithHook("pod-with-poststart-http-hook", imageutils.GetPauseImageName(), lifecycle)
181
182 nodeSelection := e2epod.NodeSelection{}
183 e2epod.SetAffinity(&nodeSelection, targetNode)
184 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
185 testPodWithHook(ctx, podWithHook)
186 })
187
192 f.It("should execute poststart https hook properly [MinimumKubeletVersion:1.23]", f.WithNodeConformance(), func(ctx context.Context) {
193 lifecycle := &v1.Lifecycle{
194 PostStart: &v1.LifecycleHandler{
195 HTTPGet: &v1.HTTPGetAction{
196 Scheme: v1.URISchemeHTTPS,
197 Path: "/echo?msg=poststart",
198 Host: targetIP,
199 Port: intstr.FromInt32(9090),
200 },
201 },
202 }
203 podWithHook := getPodWithHook("pod-with-poststart-https-hook", imageutils.GetPauseImageName(), lifecycle)
204
205 nodeSelection := e2epod.NodeSelection{}
206 e2epod.SetAffinity(&nodeSelection, targetNode)
207 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
208 testPodWithHook(ctx, podWithHook)
209 })
210
215 framework.ConformanceIt("should execute prestop http hook properly", f.WithNodeConformance(), func(ctx context.Context) {
216 lifecycle := &v1.Lifecycle{
217 PreStop: &v1.LifecycleHandler{
218 HTTPGet: &v1.HTTPGetAction{
219 Path: "/echo?msg=prestop",
220 Host: targetIP,
221 Port: intstr.FromInt32(8080),
222 },
223 },
224 }
225 podWithHook := getPodWithHook("pod-with-prestop-http-hook", imageutils.GetPauseImageName(), lifecycle)
226
227 nodeSelection := e2epod.NodeSelection{}
228 e2epod.SetAffinity(&nodeSelection, targetNode)
229 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
230 testPodWithHook(ctx, podWithHook)
231 })
232
237 f.It("should execute prestop https hook properly [MinimumKubeletVersion:1.23]", f.WithNodeConformance(), func(ctx context.Context) {
238 lifecycle := &v1.Lifecycle{
239 PreStop: &v1.LifecycleHandler{
240 HTTPGet: &v1.HTTPGetAction{
241 Scheme: v1.URISchemeHTTPS,
242 Path: "/echo?msg=prestop",
243 Host: targetIP,
244 Port: intstr.FromInt32(9090),
245 },
246 },
247 }
248 podWithHook := getPodWithHook("pod-with-prestop-https-hook", imageutils.GetPauseImageName(), lifecycle)
249
250 nodeSelection := e2epod.NodeSelection{}
251 e2epod.SetAffinity(&nodeSelection, targetNode)
252 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
253 testPodWithHook(ctx, podWithHook)
254 })
255 })
256 })
257
258 var _ = SIGDescribe(nodefeature.SidecarContainers, feature.SidecarContainers, "Restartable Init Container Lifecycle Hook", func() {
259 f := framework.NewDefaultFramework("restartable-init-container-lifecycle-hook")
260 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
261 var podClient *e2epod.PodClient
262 const (
263 podCheckInterval = 1 * time.Second
264 postStartWaitTimeout = 2 * time.Minute
265 preStopWaitTimeout = 30 * time.Second
266 )
267 ginkgo.Context("when create a pod with lifecycle hook", func() {
268 var (
269 targetIP, targetURL, targetNode string
270
271 httpPorts = []v1.ContainerPort{
272 {
273 ContainerPort: 8080,
274 Protocol: v1.ProtocolTCP,
275 },
276 }
277 httpsPorts = []v1.ContainerPort{
278 {
279 ContainerPort: 9090,
280 Protocol: v1.ProtocolTCP,
281 },
282 }
283 httpsArgs = []string{
284 "netexec",
285 "--http-port", "9090",
286 "--udp-port", "9091",
287 "--tls-cert-file", "/localhost.crt",
288 "--tls-private-key-file", "/localhost.key",
289 }
290 )
291
292 podHandleHookRequest := e2epod.NewAgnhostPodFromContainers(
293 "", "pod-handle-http-request", nil,
294 e2epod.NewAgnhostContainer("container-handle-http-request", nil, httpPorts, "netexec"),
295 e2epod.NewAgnhostContainer("container-handle-https-request", nil, httpsPorts, httpsArgs...),
296 )
297
298 ginkgo.BeforeEach(func(ctx context.Context) {
299 node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
300 framework.ExpectNoError(err)
301 targetNode = node.Name
302 nodeSelection := e2epod.NodeSelection{}
303 e2epod.SetAffinity(&nodeSelection, targetNode)
304 e2epod.SetNodeSelection(&podHandleHookRequest.Spec, nodeSelection)
305
306 podClient = e2epod.NewPodClient(f)
307 ginkgo.By("create the container to handle the HTTPGet hook request.")
308 newPod := podClient.CreateSync(ctx, podHandleHookRequest)
309 targetIP = newPod.Status.PodIP
310 targetURL = targetIP
311 if strings.Contains(targetIP, ":") {
312 targetURL = fmt.Sprintf("[%s]", targetIP)
313 }
314 })
315 testPodWithHook := func(ctx context.Context, podWithHook *v1.Pod) {
316 ginkgo.By("create the pod with lifecycle hook")
317 podClient.CreateSync(ctx, podWithHook)
318 const (
319 defaultHandler = iota
320 httpsHandler
321 )
322 handlerContainer := defaultHandler
323 if podWithHook.Spec.InitContainers[0].Lifecycle.PostStart != nil {
324 ginkgo.By("check poststart hook")
325 if podWithHook.Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet != nil {
326 if v1.URISchemeHTTPS == podWithHook.Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme {
327 handlerContainer = httpsHandler
328 }
329 }
330 gomega.Eventually(ctx, func(ctx context.Context) error {
331 return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
332 `GET /echo\?msg=poststart`)
333 }, postStartWaitTimeout, podCheckInterval).Should(gomega.BeNil())
334 }
335 ginkgo.By("delete the pod with lifecycle hook")
336 podClient.DeleteSync(ctx, podWithHook.Name, *metav1.NewDeleteOptions(15), e2epod.DefaultPodDeletionTimeout)
337 if podWithHook.Spec.InitContainers[0].Lifecycle.PreStop != nil {
338 ginkgo.By("check prestop hook")
339 if podWithHook.Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet != nil {
340 if v1.URISchemeHTTPS == podWithHook.Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme {
341 handlerContainer = httpsHandler
342 }
343 }
344 gomega.Eventually(ctx, func(ctx context.Context) error {
345 return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
346 `GET /echo\?msg=prestop`)
347 }, preStopWaitTimeout, podCheckInterval).Should(gomega.BeNil())
348 }
349 }
350
360 ginkgo.It("should execute poststart exec hook properly", func(ctx context.Context) {
361 lifecycle := &v1.Lifecycle{
362 PostStart: &v1.LifecycleHandler{
363 Exec: &v1.ExecAction{
364 Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=poststart"},
365 },
366 },
367 }
368 podWithHook := getSidecarPodWithHook("pod-with-poststart-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
369
370 testPodWithHook(ctx, podWithHook)
371 })
372
382 ginkgo.It("should execute prestop exec hook properly", func(ctx context.Context) {
383 lifecycle := &v1.Lifecycle{
384 PreStop: &v1.LifecycleHandler{
385 Exec: &v1.ExecAction{
386 Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=prestop"},
387 },
388 },
389 }
390 podWithHook := getSidecarPodWithHook("pod-with-prestop-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
391 testPodWithHook(ctx, podWithHook)
392 })
393
403 ginkgo.It("should execute poststart http hook properly", func(ctx context.Context) {
404 lifecycle := &v1.Lifecycle{
405 PostStart: &v1.LifecycleHandler{
406 HTTPGet: &v1.HTTPGetAction{
407 Path: "/echo?msg=poststart",
408 Host: targetIP,
409 Port: intstr.FromInt32(8080),
410 },
411 },
412 }
413 podWithHook := getSidecarPodWithHook("pod-with-poststart-http-hook", imageutils.GetPauseImageName(), lifecycle)
414
415 nodeSelection := e2epod.NodeSelection{}
416 e2epod.SetAffinity(&nodeSelection, targetNode)
417 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
418 testPodWithHook(ctx, podWithHook)
419 })
420
430 ginkgo.It("should execute poststart https hook properly [MinimumKubeletVersion:1.23]", func(ctx context.Context) {
431 lifecycle := &v1.Lifecycle{
432 PostStart: &v1.LifecycleHandler{
433 HTTPGet: &v1.HTTPGetAction{
434 Scheme: v1.URISchemeHTTPS,
435 Path: "/echo?msg=poststart",
436 Host: targetIP,
437 Port: intstr.FromInt32(9090),
438 },
439 },
440 }
441 podWithHook := getSidecarPodWithHook("pod-with-poststart-https-hook", imageutils.GetPauseImageName(), lifecycle)
442
443 nodeSelection := e2epod.NodeSelection{}
444 e2epod.SetAffinity(&nodeSelection, targetNode)
445 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
446 testPodWithHook(ctx, podWithHook)
447 })
448
458 ginkgo.It("should execute prestop http hook properly", func(ctx context.Context) {
459 lifecycle := &v1.Lifecycle{
460 PreStop: &v1.LifecycleHandler{
461 HTTPGet: &v1.HTTPGetAction{
462 Path: "/echo?msg=prestop",
463 Host: targetIP,
464 Port: intstr.FromInt32(8080),
465 },
466 },
467 }
468 podWithHook := getSidecarPodWithHook("pod-with-prestop-http-hook", imageutils.GetPauseImageName(), lifecycle)
469
470 nodeSelection := e2epod.NodeSelection{}
471 e2epod.SetAffinity(&nodeSelection, targetNode)
472 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
473 testPodWithHook(ctx, podWithHook)
474 })
475
485 ginkgo.It("should execute prestop https hook properly [MinimumKubeletVersion:1.23]", func(ctx context.Context) {
486 lifecycle := &v1.Lifecycle{
487 PreStop: &v1.LifecycleHandler{
488 HTTPGet: &v1.HTTPGetAction{
489 Scheme: v1.URISchemeHTTPS,
490 Path: "/echo?msg=prestop",
491 Host: targetIP,
492 Port: intstr.FromInt32(9090),
493 },
494 },
495 }
496 podWithHook := getSidecarPodWithHook("pod-with-prestop-https-hook", imageutils.GetPauseImageName(), lifecycle)
497
498 nodeSelection := e2epod.NodeSelection{}
499 e2epod.SetAffinity(&nodeSelection, targetNode)
500 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
501 testPodWithHook(ctx, podWithHook)
502 })
503 })
504 })
505
506 func getPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *v1.Pod {
507 return &v1.Pod{
508 ObjectMeta: metav1.ObjectMeta{
509 Name: name,
510 },
511 Spec: v1.PodSpec{
512 Containers: []v1.Container{
513 {
514 Name: name,
515 Image: image,
516 Lifecycle: lifecycle,
517 },
518 },
519 },
520 }
521 }
522
523 func getSidecarPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *v1.Pod {
524 return &v1.Pod{
525 ObjectMeta: metav1.ObjectMeta{
526 Name: name,
527 },
528 Spec: v1.PodSpec{
529 InitContainers: []v1.Container{
530 {
531 Name: name,
532 Image: image,
533 Lifecycle: lifecycle,
534 RestartPolicy: func() *v1.ContainerRestartPolicy {
535 restartPolicy := v1.ContainerRestartPolicyAlways
536 return &restartPolicy
537 }(),
538 },
539 },
540 Containers: []v1.Container{
541 {
542 Name: "main",
543 Image: imageutils.GetPauseImageName(),
544 },
545 },
546 },
547 }
548 }
549
550 var _ = SIGDescribe(feature.PodLifecycleSleepAction, func() {
551 f := framework.NewDefaultFramework("pod-lifecycle-sleep-action")
552 f.NamespacePodSecurityEnforceLevel = admissionapi.LevelBaseline
553 var podClient *e2epod.PodClient
554
555 validDuration := func(duration time.Duration, low, high int64) bool {
556 return duration >= time.Second*time.Duration(low) && duration <= time.Second*time.Duration(high)
557 }
558
559 ginkgo.Context("when create a pod with lifecycle hook using sleep action", func() {
560 ginkgo.BeforeEach(func(ctx context.Context) {
561 podClient = e2epod.NewPodClient(f)
562 })
563 ginkgo.It("valid prestop hook using sleep action", func(ctx context.Context) {
564 lifecycle := &v1.Lifecycle{
565 PreStop: &v1.LifecycleHandler{
566 Sleep: &v1.SleepAction{Seconds: 5},
567 },
568 }
569 podWithHook := getPodWithHook("pod-with-prestop-sleep-hook", imageutils.GetPauseImageName(), lifecycle)
570 ginkgo.By("create the pod with lifecycle hook using sleep action")
571 podClient.CreateSync(ctx, podWithHook)
572 ginkgo.By("delete the pod with lifecycle hook using sleep action")
573 start := time.Now()
574 podClient.DeleteSync(ctx, podWithHook.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
575 cost := time.Since(start)
576
577
578
579 if !validDuration(cost, 5, 30) {
580 framework.Failf("unexpected delay duration before killing the pod, cost = %v", cost)
581 }
582 })
583
584 ginkgo.It("reduce GracePeriodSeconds during runtime", func(ctx context.Context) {
585 lifecycle := &v1.Lifecycle{
586 PreStop: &v1.LifecycleHandler{
587 Sleep: &v1.SleepAction{Seconds: 15},
588 },
589 }
590 podWithHook := getPodWithHook("pod-with-prestop-sleep-hook", imageutils.GetPauseImageName(), lifecycle)
591 ginkgo.By("create the pod with lifecycle hook using sleep action")
592 podClient.CreateSync(ctx, podWithHook)
593 ginkgo.By("delete the pod with lifecycle hook using sleep action")
594 start := time.Now()
595 podClient.DeleteSync(ctx, podWithHook.Name, *metav1.NewDeleteOptions(2), e2epod.DefaultPodDeletionTimeout)
596 cost := time.Since(start)
597
598
599
600 if !validDuration(cost, 2, 15) {
601 framework.Failf("unexpected delay duration before killing the pod, cost = %v", cost)
602 }
603 })
604
605 ginkgo.It("ignore terminated container", func(ctx context.Context) {
606 lifecycle := &v1.Lifecycle{
607 PreStop: &v1.LifecycleHandler{
608 Sleep: &v1.SleepAction{Seconds: 20},
609 },
610 }
611 name := "pod-with-prestop-sleep-hook"
612 podWithHook := getPodWithHook(name, imageutils.GetE2EImage(imageutils.BusyBox), lifecycle)
613 podWithHook.Spec.Containers[0].Command = []string{"/bin/sh"}
614 podWithHook.Spec.Containers[0].Args = []string{"-c", "exit 0"}
615 podWithHook.Spec.RestartPolicy = v1.RestartPolicyNever
616 ginkgo.By("create the pod with lifecycle hook using sleep action")
617 p := podClient.Create(ctx, podWithHook)
618 framework.ExpectNoError(e2epod.WaitForContainerTerminated(ctx, f.ClientSet, f.Namespace.Name, p.Name, name, 3*time.Minute))
619 ginkgo.By("delete the pod with lifecycle hook using sleep action")
620 start := time.Now()
621 podClient.DeleteSync(ctx, podWithHook.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
622 cost := time.Since(start)
623
624
625 if !validDuration(cost, 0, 15) {
626 framework.Failf("unexpected delay duration before killing the pod, cost = %v", cost)
627 }
628 })
629
630 })
631 })
632
View as plain text