k8s.io/kubernetes@v1.29.3/test/e2e_node/pod_conditions_test.go (about) 1 /* 2 Copyright 2020 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 e2enode 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "strings" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 admissionapi "k8s.io/pod-security-admission/api" 29 30 "k8s.io/apimachinery/pkg/fields" 31 "k8s.io/apimachinery/pkg/util/uuid" 32 "k8s.io/kubernetes/pkg/kubelet/events" 33 "k8s.io/kubernetes/test/e2e/feature" 34 "k8s.io/kubernetes/test/e2e/framework" 35 e2eevents "k8s.io/kubernetes/test/e2e/framework/events" 36 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 37 testutils "k8s.io/kubernetes/test/utils" 38 imageutils "k8s.io/kubernetes/test/utils/image" 39 40 "k8s.io/kubernetes/pkg/features" 41 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config" 42 43 "github.com/onsi/ginkgo/v2" 44 "github.com/onsi/gomega" 45 ) 46 47 var _ = SIGDescribe("Pod conditions managed by Kubelet", func() { 48 f := framework.NewDefaultFramework("pod-conditions") 49 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 50 51 f.Context("including PodReadyToStartContainers condition", f.WithSerial(), feature.PodReadyToStartContainersCondition, func() { 52 tempSetCurrentKubeletConfig(f, func(ctx context.Context, initialConfig *kubeletconfig.KubeletConfiguration) { 53 initialConfig.FeatureGates = map[string]bool{ 54 string(features.PodReadyToStartContainersCondition): true, 55 } 56 }) 57 ginkgo.It("a pod without init containers should report all conditions set in expected order after the pod is up", runPodReadyConditionsTest(f, false, true)) 58 ginkgo.It("a pod with init containers should report all conditions set in expected order after the pod is up", runPodReadyConditionsTest(f, true, true)) 59 ginkgo.It("a pod failing to mount volumes and without init containers should report scheduled and initialized conditions set", runPodFailingConditionsTest(f, false, true)) 60 ginkgo.It("a pod failing to mount volumes and with init containers should report just the scheduled condition set", runPodFailingConditionsTest(f, true, true)) 61 cleanupPods(f) 62 }) 63 64 ginkgo.Context("without PodReadyToStartContainersCondition condition", func() { 65 ginkgo.It("a pod without init containers should report all conditions set in expected order after the pod is up", runPodReadyConditionsTest(f, false, false)) 66 ginkgo.It("a pod with init containers should report all conditions set in expected order after the pod is up", runPodReadyConditionsTest(f, true, false)) 67 ginkgo.It("a pod failing to mount volumes and without init containers should report scheduled and initialized conditions set", runPodFailingConditionsTest(f, false, false)) 68 ginkgo.It("a pod failing to mount volumes and with init containers should report just the scheduled condition set", runPodFailingConditionsTest(f, true, false)) 69 cleanupPods(f) 70 }) 71 }) 72 73 func runPodFailingConditionsTest(f *framework.Framework, hasInitContainers, checkPodReadyToStart bool) func(ctx context.Context) { 74 return func(ctx context.Context) { 75 ginkgo.By("creating a pod whose sandbox creation is blocked due to a missing volume") 76 77 p := webserverPodSpec("pod-"+string(uuid.NewUUID()), "web1", "init1", hasInitContainers) 78 p.Spec.Volumes = []v1.Volume{ 79 { 80 Name: "cm", 81 VolumeSource: v1.VolumeSource{ 82 ConfigMap: &v1.ConfigMapVolumeSource{ 83 LocalObjectReference: v1.LocalObjectReference{Name: "does-not-exist"}, 84 }, 85 }, 86 }, 87 } 88 p.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{ 89 { 90 Name: "cm", 91 MountPath: "/config", 92 }, 93 } 94 95 p = e2epod.NewPodClient(f).Create(ctx, p) 96 97 ginkgo.By("waiting until kubelet has started trying to set up the pod and started to fail") 98 99 eventSelector := fields.Set{ 100 "involvedObject.kind": "Pod", 101 "involvedObject.name": p.Name, 102 "involvedObject.namespace": f.Namespace.Name, 103 "reason": events.FailedMountVolume, 104 }.AsSelector().String() 105 framework.ExpectNoError(e2eevents.WaitTimeoutForEvent(ctx, f.ClientSet, f.Namespace.Name, eventSelector, "MountVolume.SetUp failed for volume", framework.PodEventTimeout)) 106 107 p, err := e2epod.NewPodClient(f).Get(ctx, p.Name, metav1.GetOptions{}) 108 framework.ExpectNoError(err) 109 110 ginkgo.By("checking pod condition for a pod whose sandbox creation is blocked") 111 112 scheduledTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodScheduled, true) 113 framework.ExpectNoError(err) 114 115 // Verify PodReadyToStartContainers is not set (since sandboxcreation is blocked) 116 if checkPodReadyToStart { 117 _, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodReadyToStartContainers, false) 118 framework.ExpectNoError(err) 119 } 120 121 if hasInitContainers { 122 // Verify PodInitialized is not set if init containers are present (since sandboxcreation is blocked) 123 _, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodInitialized, false) 124 framework.ExpectNoError(err) 125 } else { 126 // Verify PodInitialized is set if init containers are not present (since without init containers, it gets set very early) 127 initializedTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodInitialized, true) 128 framework.ExpectNoError(err) 129 gomega.Expect(initializedTime.Before(scheduledTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("pod without init containers is initialized at: %v which is before pod scheduled at: %v", initializedTime, scheduledTime)) 130 } 131 132 // Verify ContainersReady is not set (since sandboxcreation is blocked) 133 _, err = getTransitionTimeForPodConditionWithStatus(p, v1.ContainersReady, false) 134 framework.ExpectNoError(err) 135 // Verify PodReady is not set (since sandboxcreation is blocked) 136 _, err = getTransitionTimeForPodConditionWithStatus(p, v1.PodReady, false) 137 framework.ExpectNoError(err) 138 } 139 } 140 141 func runPodReadyConditionsTest(f *framework.Framework, hasInitContainers, checkPodReadyToStart bool) func(ctx context.Context) { 142 return func(ctx context.Context) { 143 ginkgo.By("creating a pod that successfully comes up in a ready/running state") 144 145 p := e2epod.NewPodClient(f).Create(ctx, webserverPodSpec("pod-"+string(uuid.NewUUID()), "web1", "init1", hasInitContainers)) 146 framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, f.ClientSet, p.Name, f.Namespace.Name, framework.PodStartTimeout)) 147 148 p, err := e2epod.NewPodClient(f).Get(ctx, p.Name, metav1.GetOptions{}) 149 framework.ExpectNoError(err) 150 isReady, err := testutils.PodRunningReady(p) 151 framework.ExpectNoError(err) 152 if !isReady { 153 framework.Failf("pod %q should be ready", p.Name) 154 } 155 156 ginkgo.By("checking order of pod condition transitions for a pod with no container/sandbox restarts") 157 158 scheduledTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodScheduled, true) 159 framework.ExpectNoError(err) 160 initializedTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodInitialized, true) 161 framework.ExpectNoError(err) 162 163 condBeforeContainersReadyTransitionTime := initializedTime 164 errSubstrIfContainersReadyTooEarly := "is initialized" 165 if checkPodReadyToStart { 166 readyToStartContainersTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodReadyToStartContainers, true) 167 framework.ExpectNoError(err) 168 169 if hasInitContainers { 170 // With init containers, verify the sequence of conditions is: Scheduled => PodReadyToStartContainers => Initialized 171 gomega.Expect(readyToStartContainersTime.Before(scheduledTime)).ToNot(gomega.BeTrue(), fmt.Sprintf("pod with init containers is initialized at: %v which is before pod has ready to start at: %v", initializedTime, readyToStartContainersTime)) 172 gomega.Expect(initializedTime.Before(readyToStartContainersTime)).ToNot(gomega.BeTrue(), fmt.Sprintf("pod with init containers is initialized at: %v which is before pod has ready to start at: %v", initializedTime, readyToStartContainersTime)) 173 } else { 174 // Without init containers, verify the sequence of conditions is: Scheduled => Initialized => PodReadyToStartContainers 175 condBeforeContainersReadyTransitionTime = readyToStartContainersTime 176 errSubstrIfContainersReadyTooEarly = "ready to start" 177 gomega.Expect(initializedTime.Before(scheduledTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("pod without init containers initialized at: %v which is before pod scheduled at: %v", initializedTime, scheduledTime)) 178 gomega.Expect(readyToStartContainersTime.Before(initializedTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("pod without init containers has ready to start at: %v which is before pod is initialized at: %v", readyToStartContainersTime, initializedTime)) 179 } 180 } else { 181 // In the absence of PodHasReadyToStartContainers feature disabled, verify the sequence is: Scheduled => Initialized 182 gomega.Expect(initializedTime.Before(scheduledTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("pod initialized at: %v which is before pod scheduled at: %v", initializedTime, scheduledTime)) 183 } 184 // Verify the next condition to get set is ContainersReady 185 containersReadyTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.ContainersReady, true) 186 framework.ExpectNoError(err) 187 gomega.Expect(containersReadyTime.Before(condBeforeContainersReadyTransitionTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("containers ready at: %v which is before pod %s: %v", containersReadyTime, errSubstrIfContainersReadyTooEarly, initializedTime)) 188 189 // Verify ContainersReady => PodReady 190 podReadyTime, err := getTransitionTimeForPodConditionWithStatus(p, v1.PodReady, true) 191 framework.ExpectNoError(err) 192 gomega.Expect(podReadyTime.Before(containersReadyTime)).NotTo(gomega.BeTrue(), fmt.Sprintf("pod ready at: %v which is before pod containers ready at: %v", podReadyTime, containersReadyTime)) 193 } 194 } 195 196 func getTransitionTimeForPodConditionWithStatus(pod *v1.Pod, condType v1.PodConditionType, expectedStatus bool) (time.Time, error) { 197 for _, cond := range pod.Status.Conditions { 198 if cond.Type == condType { 199 if strings.EqualFold(string(cond.Status), strconv.FormatBool(expectedStatus)) { 200 return cond.LastTransitionTime.Time, nil 201 } 202 return time.Time{}, fmt.Errorf("condition: %s found for pod but status: %s did not match expected status: %s", condType, cond.Status, strconv.FormatBool(expectedStatus)) 203 } 204 } 205 return time.Time{}, fmt.Errorf("condition: %s not found for pod", condType) 206 } 207 208 func webserverPodSpec(podName, containerName, initContainerName string, addInitContainer bool) *v1.Pod { 209 p := &v1.Pod{ 210 ObjectMeta: metav1.ObjectMeta{ 211 Name: podName, 212 }, 213 Spec: v1.PodSpec{ 214 Containers: []v1.Container{ 215 { 216 Name: containerName, 217 Image: imageutils.GetE2EImage(imageutils.Agnhost), 218 Args: []string{"test-webserver"}, 219 }, 220 }, 221 }, 222 } 223 if addInitContainer { 224 p.Spec.InitContainers = []v1.Container{ 225 { 226 Name: initContainerName, 227 Image: imageutils.GetE2EImage(imageutils.BusyBox), 228 Command: []string{"sh", "-c", "sleep 5s"}, 229 }, 230 } 231 } 232 return p 233 }