...

Source file src/k8s.io/kubernetes/test/e2e/common/node/lifecycle_hook.go

Documentation: k8s.io/kubernetes/test/e2e/common/node

     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.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  			// 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  })
   632  

View as plain text