k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/common/node/lifecycle_hook.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 /* 133 Release: v1.9 134 Testname: Pod Lifecycle, post start exec hook 135 Description: When a post start handler is specified in the container lifecycle using a 'Exec' action, then the handler MUST be invoked after the start of the container. A server pod is created that will serve http requests, create a second pod with a container lifecycle specifying a post start that invokes the server pod using ExecAction to validate that the post start is executed. 136 */ 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 /* 150 Release: v1.9 151 Testname: Pod Lifecycle, prestop exec hook 152 Description: When a pre-stop handler is specified in the container lifecycle using a 'Exec' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve http requests, create a second pod with a container lifecycle specifying a pre-stop that invokes the server pod using ExecAction to validate that the pre-stop is executed. 153 */ 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 /* 166 Release: v1.9 167 Testname: Pod Lifecycle, post start http hook 168 Description: When a post start handler is specified in the container lifecycle using a HttpGet action, then the handler MUST be invoked after the start of the container. A server pod is created that will serve http requests, create a second pod on the same node with a container lifecycle specifying a post start that invokes the server pod to validate that the post start is executed. 169 */ 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 // make sure we spawn the test pod on the same node as the webserver. 182 nodeSelection := e2epod.NodeSelection{} 183 e2epod.SetAffinity(&nodeSelection, targetNode) 184 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 185 testPodWithHook(ctx, podWithHook) 186 }) 187 /* 188 Release : v1.23 189 Testname: Pod Lifecycle, poststart https hook 190 Description: When a post-start handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod on the same node with a container lifecycle specifying a post-start that invokes the server pod to validate that the post-start is executed. 191 */ 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 // make sure we spawn the test pod on the same node as the webserver. 205 nodeSelection := e2epod.NodeSelection{} 206 e2epod.SetAffinity(&nodeSelection, targetNode) 207 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 208 testPodWithHook(ctx, podWithHook) 209 }) 210 /* 211 Release : v1.9 212 Testname: Pod Lifecycle, prestop http hook 213 Description: When a pre-stop handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve http requests, create a second pod on the same node with a container lifecycle specifying a pre-stop that invokes the server pod to validate that the pre-stop is executed. 214 */ 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 // make sure we spawn the test pod on the same node as the webserver. 227 nodeSelection := e2epod.NodeSelection{} 228 e2epod.SetAffinity(&nodeSelection, targetNode) 229 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 230 testPodWithHook(ctx, podWithHook) 231 }) 232 /* 233 Release : v1.23 234 Testname: Pod Lifecycle, prestop https hook 235 Description: When a pre-stop handler is specified in the container lifecycle using a 'HttpGet' action, then the handler MUST be invoked before the container is terminated. A server pod is created that will serve https requests, create a second pod on the same node with a container lifecycle specifying a pre-stop that invokes the server pod to validate that the pre-stop is executed. 236 */ 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 // make sure we spawn the test pod on the same node as the webserver. 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 /* 351 Release: v1.28 352 Testname: Pod Lifecycle with restartable init container, post start exec hook 353 Description: When a post start handler is specified in the container 354 lifecycle using a 'Exec' action, then the handler MUST be invoked after 355 the start of the container. A server pod is created that will serve http 356 requests, create a second pod with a container lifecycle specifying a 357 post start that invokes the server pod using ExecAction to validate that 358 the post start is executed. 359 */ 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 /* 373 Release: v1.28 374 Testname: Pod Lifecycle with restartable init container, prestop exec hook 375 Description: When a pre-stop handler is specified in the container 376 lifecycle using a 'Exec' action, then the handler MUST be invoked before 377 the container is terminated. A server pod is created that will serve http 378 requests, create a second pod with a container lifecycle specifying a 379 pre-stop that invokes the server pod using ExecAction to validate that 380 the pre-stop is executed. 381 */ 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 /* 394 Release: v1.28 395 Testname: Pod Lifecycle with restartable init container, post start http hook 396 Description: When a post start handler is specified in the container 397 lifecycle using a HttpGet action, then the handler MUST be invoked after 398 the start of the container. A server pod is created that will serve http 399 requests, create a second pod on the same node with a container lifecycle 400 specifying a post start that invokes the server pod to validate that the 401 post start is executed. 402 */ 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 // make sure we spawn the test pod on the same node as the webserver. 415 nodeSelection := e2epod.NodeSelection{} 416 e2epod.SetAffinity(&nodeSelection, targetNode) 417 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 418 testPodWithHook(ctx, podWithHook) 419 }) 420 /* 421 Release : v1.28 422 Testname: Pod Lifecycle with restartable init container, poststart https hook 423 Description: When a post-start handler is specified in the container 424 lifecycle using a 'HttpGet' action, then the handler MUST be invoked 425 before the container is terminated. A server pod is created that will 426 serve https requests, create a second pod on the same node with a 427 container lifecycle specifying a post-start that invokes the server pod 428 to validate that the post-start is executed. 429 */ 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 // make sure we spawn the test pod on the same node as the webserver. 443 nodeSelection := e2epod.NodeSelection{} 444 e2epod.SetAffinity(&nodeSelection, targetNode) 445 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 446 testPodWithHook(ctx, podWithHook) 447 }) 448 /* 449 Release : v1.28 450 Testname: Pod Lifecycle with restartable init container, prestop http hook 451 Description: When a pre-stop handler is specified in the container 452 lifecycle using a 'HttpGet' action, then the handler MUST be invoked 453 before the container is terminated. A server pod is created that will 454 serve http requests, create a second pod on the same node with a 455 container lifecycle specifying a pre-stop that invokes the server pod to 456 validate that the pre-stop is executed. 457 */ 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 // make sure we spawn the test pod on the same node as the webserver. 470 nodeSelection := e2epod.NodeSelection{} 471 e2epod.SetAffinity(&nodeSelection, targetNode) 472 e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection) 473 testPodWithHook(ctx, podWithHook) 474 }) 475 /* 476 Release : v1.28 477 Testname: Pod Lifecycle with restartable init container, prestop https hook 478 Description: When a pre-stop handler is specified in the container 479 lifecycle using a 'HttpGet' action, then the handler MUST be invoked 480 before the container is terminated. A server pod is created that will 481 serve https requests, create a second pod on the same node with a 482 container lifecycle specifying a pre-stop that invokes the server pod to 483 validate that the pre-stop is executed. 484 */ 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 // make sure we spawn the test pod on the same node as the webserver. 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.NamespacePodSecurityLevel = 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 // cost should be 577 // longer than 5 seconds (pod should sleep for 5 seconds) 578 // shorter than gracePeriodSeconds (default 30 seconds here) 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 // cost should be 598 // longer than 2 seconds (we change gracePeriodSeconds to 2 seconds here, and it's less than sleep action) 599 // shorter than sleep action (to make sure it doesn't take effect) 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 // cost should be 624 // shorter than sleep action (container is terminated and sleep action should be ignored) 625 if !validDuration(cost, 0, 15) { 626 framework.Failf("unexpected delay duration before killing the pod, cost = %v", cost) 627 } 628 }) 629 630 }) 631 })