k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/node/pre_stop.go (about) 1 /* 2 Copyright 2014 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 "encoding/json" 22 "fmt" 23 "net" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/util/uuid" 29 "k8s.io/apimachinery/pkg/util/wait" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/kubernetes/pkg/cluster/ports" 32 "k8s.io/kubernetes/test/e2e/framework" 33 e2ekubelet "k8s.io/kubernetes/test/e2e/framework/kubelet" 34 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 35 imageutils "k8s.io/kubernetes/test/utils/image" 36 admissionapi "k8s.io/pod-security-admission/api" 37 38 "github.com/onsi/ginkgo/v2" 39 ) 40 41 // State partially cloned from webserver.go 42 type State struct { 43 Received map[string]int 44 } 45 46 func testPreStop(ctx context.Context, c clientset.Interface, ns string) { 47 // This is the server that will receive the preStop notification 48 podDescr := e2epod.NewAgnhostPod(ns, "server", nil, nil, []v1.ContainerPort{{ContainerPort: 8080}}, "nettest") 49 ginkgo.By(fmt.Sprintf("Creating server pod %s in namespace %s", podDescr.Name, ns)) 50 podDescr, err := c.CoreV1().Pods(ns).Create(ctx, podDescr, metav1.CreateOptions{}) 51 framework.ExpectNoError(err, fmt.Sprintf("creating pod %s", podDescr.Name)) 52 53 // At the end of the test, clean up by removing the pod. 54 ginkgo.DeferCleanup(func(ctx context.Context) error { 55 ginkgo.By("Deleting the server pod") 56 return c.CoreV1().Pods(ns).Delete(ctx, podDescr.Name, metav1.DeleteOptions{}) 57 }) 58 59 ginkgo.By("Waiting for pods to come up.") 60 err = e2epod.WaitForPodRunningInNamespace(ctx, c, podDescr) 61 framework.ExpectNoError(err, "waiting for server pod to start") 62 63 val := "{\"Source\": \"prestop\"}" 64 65 podOut, err := c.CoreV1().Pods(ns).Get(ctx, podDescr.Name, metav1.GetOptions{}) 66 framework.ExpectNoError(err, "getting pod info") 67 68 podURL := net.JoinHostPort(podOut.Status.PodIP, "8080") 69 70 preStopDescr := &v1.Pod{ 71 ObjectMeta: metav1.ObjectMeta{ 72 Name: "tester", 73 }, 74 Spec: v1.PodSpec{ 75 Containers: []v1.Container{ 76 { 77 Name: "tester", 78 Image: imageutils.GetE2EImage(imageutils.BusyBox), 79 Command: []string{"sleep", "600"}, 80 Lifecycle: &v1.Lifecycle{ 81 PreStop: &v1.LifecycleHandler{ 82 Exec: &v1.ExecAction{ 83 Command: []string{ 84 "wget", "-O-", "--post-data=" + val, fmt.Sprintf("http://%s/write", podURL), 85 }, 86 }, 87 }, 88 }, 89 }, 90 }, 91 }, 92 } 93 94 ginkgo.By(fmt.Sprintf("Creating tester pod %s in namespace %s", preStopDescr.Name, ns)) 95 preStopDescr, err = c.CoreV1().Pods(ns).Create(ctx, preStopDescr, metav1.CreateOptions{}) 96 framework.ExpectNoError(err, fmt.Sprintf("creating pod %s", preStopDescr.Name)) 97 deletePreStop := true 98 99 // At the end of the test, clean up by removing the pod. 100 ginkgo.DeferCleanup(func(ctx context.Context) error { 101 if deletePreStop { 102 ginkgo.By("Deleting the tester pod") 103 return c.CoreV1().Pods(ns).Delete(ctx, preStopDescr.Name, metav1.DeleteOptions{}) 104 } 105 return nil 106 }) 107 108 err = e2epod.WaitForPodRunningInNamespace(ctx, c, preStopDescr) 109 framework.ExpectNoError(err, "waiting for tester pod to start") 110 111 // Delete the pod with the preStop handler. 112 ginkgo.By("Deleting pre-stop pod") 113 if err := c.CoreV1().Pods(ns).Delete(ctx, preStopDescr.Name, metav1.DeleteOptions{}); err == nil { 114 deletePreStop = false 115 } 116 framework.ExpectNoError(err, fmt.Sprintf("deleting pod: %s", preStopDescr.Name)) 117 118 // Validate that the server received the web poke. 119 err = wait.Poll(time.Second*5, time.Second*60, func() (bool, error) { 120 121 ctx, cancel := context.WithTimeout(ctx, framework.SingleCallTimeout) 122 defer cancel() 123 124 var body []byte 125 body, err = c.CoreV1().RESTClient().Get(). 126 Namespace(ns). 127 Resource("pods"). 128 SubResource("proxy"). 129 Name(podDescr.Name). 130 Suffix("read"). 131 DoRaw(ctx) 132 133 if err != nil { 134 if ctx.Err() != nil { 135 framework.Failf("Error validating prestop: %v", err) 136 return true, err 137 } 138 ginkgo.By(fmt.Sprintf("Error validating prestop: %v", err)) 139 } else { 140 framework.Logf("Saw: %s", string(body)) 141 state := State{} 142 err := json.Unmarshal(body, &state) 143 if err != nil { 144 framework.Logf("Error parsing: %v", err) 145 return false, nil 146 } 147 if state.Received["prestop"] != 0 { 148 return true, nil 149 } 150 } 151 return false, nil 152 }) 153 framework.ExpectNoError(err, "validating pre-stop.") 154 } 155 156 var _ = SIGDescribe("PreStop", func() { 157 f := framework.NewDefaultFramework("prestop") 158 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 159 var podClient *e2epod.PodClient 160 ginkgo.BeforeEach(func() { 161 podClient = e2epod.NewPodClient(f) 162 }) 163 164 /* 165 Release: v1.9 166 Testname: Pods, prestop hook 167 Description: Create a server pod with a rest endpoint '/write' that changes state.Received field. Create a Pod with a pre-stop handle that posts to the /write endpoint on the server Pod. Verify that the Pod with pre-stop hook is running. Delete the Pod with the pre-stop hook. Before the Pod is deleted, pre-stop handler MUST be called when configured. Verify that the Pod is deleted and a call to prestop hook is verified by checking the status received on the server Pod. 168 */ 169 framework.ConformanceIt("should call prestop when killing a pod", func(ctx context.Context) { 170 testPreStop(ctx, f.ClientSet, f.Namespace.Name) 171 }) 172 173 ginkgo.It("graceful pod terminated should wait until preStop hook completes the process", func(ctx context.Context) { 174 gracefulTerminationPeriodSeconds := int64(30) 175 ginkgo.By("creating the pod") 176 name := "pod-prestop-hook-" + string(uuid.NewUUID()) 177 pod := getPodWithpreStopLifeCycle(name) 178 179 ginkgo.By("submitting the pod to kubernetes") 180 podClient.Create(ctx, pod) 181 182 ginkgo.By("waiting for pod running") 183 framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)) 184 185 var err error 186 pod, err = podClient.Get(ctx, pod.Name, metav1.GetOptions{}) 187 framework.ExpectNoError(err, "failed to GET scheduled pod") 188 189 ginkgo.By("deleting the pod gracefully") 190 err = podClient.Delete(ctx, pod.Name, *metav1.NewDeleteOptions(gracefulTerminationPeriodSeconds)) 191 framework.ExpectNoError(err, "failed to delete pod") 192 193 // wait for less than the gracePeriod termination ensuring the 194 // preStop hook is still executing. 195 time.Sleep(15 * time.Second) 196 197 ginkgo.By("verifying the pod is running while in the graceful period termination") 198 result := &v1.PodList{} 199 err = wait.Poll(time.Second*5, time.Second*60, func() (bool, error) { 200 client, err := e2ekubelet.ProxyRequest(ctx, f.ClientSet, pod.Spec.NodeName, "pods", ports.KubeletPort) 201 framework.ExpectNoError(err, "failed to get the pods of the node") 202 err = client.Into(result) 203 framework.ExpectNoError(err, "failed to parse the pods of the node") 204 205 for _, kubeletPod := range result.Items { 206 if pod.Name != kubeletPod.Name { 207 continue 208 } else if kubeletPod.Status.Phase == v1.PodRunning { 209 framework.Logf("pod is running") 210 return true, err 211 } 212 } 213 return false, err 214 }) 215 216 framework.ExpectNoError(err, "validate-pod-is-running") 217 }) 218 }) 219 220 func getPodWithpreStopLifeCycle(name string) *v1.Pod { 221 return &v1.Pod{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: name, 224 }, 225 Spec: v1.PodSpec{ 226 Containers: []v1.Container{ 227 { 228 Name: "nginx", 229 Image: imageutils.GetE2EImage(imageutils.Nginx), 230 Lifecycle: &v1.Lifecycle{ 231 PreStop: &v1.LifecycleHandler{ 232 Exec: &v1.ExecAction{ 233 Command: []string{"sh", "-c", "while true; do echo preStop; sleep 1; done"}, 234 }, 235 }, 236 }, 237 }, 238 }, 239 }, 240 } 241 }