k8s.io/kubernetes@v1.29.3/test/e2e/common/node/runtime.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 "os" 23 "path" 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/kubernetes/pkg/kubelet/images" 30 "k8s.io/kubernetes/test/e2e/framework" 31 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 32 imageutils "k8s.io/kubernetes/test/utils/image" 33 admissionapi "k8s.io/pod-security-admission/api" 34 35 "github.com/onsi/ginkgo/v2" 36 "github.com/onsi/gomega" 37 gomegatypes "github.com/onsi/gomega/types" 38 ) 39 40 var _ = SIGDescribe("Container Runtime", func() { 41 f := framework.NewDefaultFramework("container-runtime") 42 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline 43 44 ginkgo.Describe("blackbox test", func() { 45 ginkgo.Context("when starting a container that exits", func() { 46 47 /* 48 Release: v1.13 49 Testname: Container Runtime, Restart Policy, Pod Phases 50 Description: If the restart policy is set to 'Always', Pod MUST be restarted when terminated, If restart policy is 'OnFailure', Pod MUST be started only if it is terminated with non-zero exit code. If the restart policy is 'Never', Pod MUST never be restarted. All these three test cases MUST verify the restart counts accordingly. 51 */ 52 framework.ConformanceIt("should run with the expected status", f.WithNodeConformance(), func(ctx context.Context) { 53 restartCountVolumeName := "restart-count" 54 restartCountVolumePath := "/restart-count" 55 testContainer := v1.Container{ 56 Image: framework.BusyBoxImage, 57 VolumeMounts: []v1.VolumeMount{ 58 { 59 MountPath: restartCountVolumePath, 60 Name: restartCountVolumeName, 61 }, 62 }, 63 } 64 testVolumes := []v1.Volume{ 65 { 66 Name: restartCountVolumeName, 67 VolumeSource: v1.VolumeSource{ 68 EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory}, 69 }, 70 }, 71 } 72 testCases := []struct { 73 Name string 74 RestartPolicy v1.RestartPolicy 75 Phase v1.PodPhase 76 State ContainerState 77 RestartCount int32 78 Ready bool 79 }{ 80 {"terminate-cmd-rpa", v1.RestartPolicyAlways, v1.PodRunning, ContainerStateRunning, 2, true}, 81 {"terminate-cmd-rpof", v1.RestartPolicyOnFailure, v1.PodSucceeded, ContainerStateTerminated, 1, false}, 82 {"terminate-cmd-rpn", v1.RestartPolicyNever, v1.PodFailed, ContainerStateTerminated, 0, false}, 83 } 84 for _, testCase := range testCases { 85 86 // It failed at the 1st run, then succeeded at 2nd run, then run forever 87 cmdScripts := ` 88 f=%s 89 count=$(echo 'hello' >> $f ; wc -l $f | awk {'print $1'}) 90 if [ $count -eq 1 ]; then 91 exit 1 92 fi 93 if [ $count -eq 2 ]; then 94 exit 0 95 fi 96 while true; do sleep 1; done 97 ` 98 tmpCmd := fmt.Sprintf(cmdScripts, path.Join(restartCountVolumePath, "restartCount")) 99 testContainer.Name = testCase.Name 100 testContainer.Command = []string{"sh", "-c", tmpCmd} 101 terminateContainer := ConformanceContainer{ 102 PodClient: e2epod.NewPodClient(f), 103 Container: testContainer, 104 RestartPolicy: testCase.RestartPolicy, 105 Volumes: testVolumes, 106 } 107 terminateContainer.Create(ctx) 108 ginkgo.DeferCleanup(framework.IgnoreNotFound(terminateContainer.Delete)) 109 110 ginkgo.By(fmt.Sprintf("Container '%s': should get the expected 'RestartCount'", testContainer.Name)) 111 gomega.Eventually(ctx, func() (int32, error) { 112 status, err := terminateContainer.GetStatus(ctx) 113 return status.RestartCount, err 114 }, ContainerStatusRetryTimeout, ContainerStatusPollInterval).Should(gomega.Equal(testCase.RestartCount)) 115 116 ginkgo.By(fmt.Sprintf("Container '%s': should get the expected 'Phase'", testContainer.Name)) 117 gomega.Eventually(ctx, terminateContainer.GetPhase, ContainerStatusRetryTimeout, ContainerStatusPollInterval).Should(gomega.Equal(testCase.Phase)) 118 119 ginkgo.By(fmt.Sprintf("Container '%s': should get the expected 'Ready' condition", testContainer.Name)) 120 isReady, err := terminateContainer.IsReady(ctx) 121 gomega.Expect(isReady).To(gomega.Equal(testCase.Ready)) 122 framework.ExpectNoError(err) 123 124 status, err := terminateContainer.GetStatus(ctx) 125 framework.ExpectNoError(err) 126 127 ginkgo.By(fmt.Sprintf("Container '%s': should get the expected 'State'", testContainer.Name)) 128 gomega.Expect(GetContainerState(status.State)).To(gomega.Equal(testCase.State)) 129 130 ginkgo.By(fmt.Sprintf("Container '%s': should be possible to delete", testContainer.Name)) 131 gomega.Expect(terminateContainer.Delete(ctx)).To(gomega.Succeed()) 132 gomega.Eventually(ctx, terminateContainer.Present, ContainerStatusRetryTimeout, ContainerStatusPollInterval).Should(gomega.BeFalse()) 133 } 134 }) 135 }) 136 137 ginkgo.Context("on terminated container", func() { 138 rootUser := int64(0) 139 nonRootUser := int64(10000) 140 adminUserName := "ContainerAdministrator" 141 nonAdminUserName := "ContainerUser" 142 143 // Create and then terminate the container under defined PodPhase to verify if termination message matches the expected output. Lastly delete the created container. 144 matchTerminationMessage := func(ctx context.Context, container v1.Container, expectedPhase v1.PodPhase, expectedMsg gomegatypes.GomegaMatcher) { 145 container.Name = "termination-message-container" 146 c := ConformanceContainer{ 147 PodClient: e2epod.NewPodClient(f), 148 Container: container, 149 RestartPolicy: v1.RestartPolicyNever, 150 } 151 152 ginkgo.By("create the container") 153 c.Create(ctx) 154 ginkgo.DeferCleanup(framework.IgnoreNotFound(c.Delete)) 155 156 ginkgo.By(fmt.Sprintf("wait for the container to reach %s", expectedPhase)) 157 gomega.Eventually(ctx, c.GetPhase, ContainerStatusRetryTimeout, ContainerStatusPollInterval).Should(gomega.Equal(expectedPhase)) 158 159 ginkgo.By("get the container status") 160 status, err := c.GetStatus(ctx) 161 framework.ExpectNoError(err) 162 163 ginkgo.By("the container should be terminated") 164 gomega.Expect(GetContainerState(status.State)).To(gomega.Equal(ContainerStateTerminated)) 165 166 ginkgo.By("the termination message should be set") 167 framework.Logf("Expected: %v to match Container's Termination Message: %v --", expectedMsg, status.State.Terminated.Message) 168 gomega.Expect(status.State.Terminated.Message).Should(expectedMsg) 169 170 ginkgo.By("delete the container") 171 gomega.Expect(c.Delete(ctx)).To(gomega.Succeed()) 172 } 173 174 f.It("should report termination message if TerminationMessagePath is set", f.WithNodeConformance(), func(ctx context.Context) { 175 container := v1.Container{ 176 Image: framework.BusyBoxImage, 177 Command: []string{"/bin/sh", "-c"}, 178 Args: []string{"/bin/echo -n DONE > /dev/termination-log"}, 179 TerminationMessagePath: "/dev/termination-log", 180 SecurityContext: &v1.SecurityContext{}, 181 } 182 if framework.NodeOSDistroIs("windows") { 183 container.SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{RunAsUserName: &adminUserName} 184 } else { 185 container.SecurityContext.RunAsUser = &rootUser 186 } 187 matchTerminationMessage(ctx, container, v1.PodSucceeded, gomega.Equal("DONE")) 188 }) 189 190 /* 191 Release: v1.15 192 Testname: Container Runtime, TerminationMessagePath, non-root user and non-default path 193 Description: Create a pod with a container to run it as a non-root user with a custom TerminationMessagePath set. Pod redirects the output to the provided path successfully. When the container is terminated, the termination message MUST match the expected output logged in the provided custom path. 194 */ 195 framework.ConformanceIt("should report termination message if TerminationMessagePath is set as non-root user and at a non-default path", f.WithNodeConformance(), func(ctx context.Context) { 196 container := v1.Container{ 197 Image: framework.BusyBoxImage, 198 Command: []string{"/bin/sh", "-c"}, 199 Args: []string{"/bin/echo -n DONE > /dev/termination-custom-log"}, 200 TerminationMessagePath: "/dev/termination-custom-log", 201 SecurityContext: &v1.SecurityContext{}, 202 } 203 if framework.NodeOSDistroIs("windows") { 204 container.SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{RunAsUserName: &nonAdminUserName} 205 } else { 206 container.SecurityContext.RunAsUser = &nonRootUser 207 } 208 matchTerminationMessage(ctx, container, v1.PodSucceeded, gomega.Equal("DONE")) 209 }) 210 211 /* 212 Release: v1.15 213 Testname: Container Runtime, TerminationMessage, from container's log output of failing container 214 Description: Create a pod with an container. Container's output is recorded in log and container exits with an error. When container is terminated, termination message MUST match the expected output recorded from container's log. 215 */ 216 framework.ConformanceIt("should report termination message from log output if TerminationMessagePolicy FallbackToLogsOnError is set", f.WithNodeConformance(), func(ctx context.Context) { 217 container := v1.Container{ 218 Image: framework.BusyBoxImage, 219 Command: []string{"/bin/sh", "-c"}, 220 Args: []string{"/bin/echo -n DONE; /bin/false"}, 221 TerminationMessagePath: "/dev/termination-log", 222 TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, 223 } 224 matchTerminationMessage(ctx, container, v1.PodFailed, gomega.Equal("DONE")) 225 }) 226 227 /* 228 Release: v1.15 229 Testname: Container Runtime, TerminationMessage, from log output of succeeding container 230 Description: Create a pod with an container. Container's output is recorded in log and container exits successfully without an error. When container is terminated, terminationMessage MUST have no content as container succeed. 231 */ 232 framework.ConformanceIt("should report termination message as empty when pod succeeds and TerminationMessagePolicy FallbackToLogsOnError is set", f.WithNodeConformance(), func(ctx context.Context) { 233 container := v1.Container{ 234 Image: framework.BusyBoxImage, 235 Command: []string{"/bin/sh", "-c"}, 236 Args: []string{"/bin/echo -n DONE; /bin/true"}, 237 TerminationMessagePath: "/dev/termination-log", 238 TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, 239 } 240 matchTerminationMessage(ctx, container, v1.PodSucceeded, gomega.Equal("")) 241 }) 242 243 /* 244 Release: v1.15 245 Testname: Container Runtime, TerminationMessage, from file of succeeding container 246 Description: Create a pod with an container. Container's output is recorded in a file and the container exits successfully without an error. When container is terminated, terminationMessage MUST match with the content from file. 247 */ 248 framework.ConformanceIt("should report termination message from file when pod succeeds and TerminationMessagePolicy FallbackToLogsOnError is set", f.WithNodeConformance(), func(ctx context.Context) { 249 container := v1.Container{ 250 Image: framework.BusyBoxImage, 251 Command: []string{"/bin/sh", "-c"}, 252 Args: []string{"/bin/echo -n OK > /dev/termination-log; /bin/echo DONE; /bin/true"}, 253 TerminationMessagePath: "/dev/termination-log", 254 TerminationMessagePolicy: v1.TerminationMessageFallbackToLogsOnError, 255 } 256 matchTerminationMessage(ctx, container, v1.PodSucceeded, gomega.Equal("OK")) 257 }) 258 }) 259 260 ginkgo.Context("when running a container with a new image", func() { 261 262 // Images used for ConformanceContainer are not added into NodePrePullImageList, because this test is 263 // testing image pulling, these images don't need to be prepulled. The ImagePullPolicy 264 // is v1.PullAlways, so it won't be blocked by framework image pre-pull list check. 265 imagePullTest := func(ctx context.Context, image string, hasSecret bool, expectedPhase v1.PodPhase, expectedPullStatus bool, windowsImage bool) { 266 command := []string{"/bin/sh", "-c", "while true; do sleep 1; done"} 267 if windowsImage { 268 // -t: Ping the specified host until stopped. 269 command = []string{"ping", "-t", "localhost"} 270 } 271 container := ConformanceContainer{ 272 PodClient: e2epod.NewPodClient(f), 273 Container: v1.Container{ 274 Name: "image-pull-test", 275 Image: image, 276 Command: command, 277 ImagePullPolicy: v1.PullAlways, 278 }, 279 RestartPolicy: v1.RestartPolicyNever, 280 } 281 if hasSecret { 282 // The service account only has pull permission 283 auth := ` 284 { 285 "auths": { 286 "https://gcr.io": { 287 "auth": "X2pzb25fa2V5OnsKICAidHlwZSI6ICJzZXJ2aWNlX2FjY291bnQiLAogICJwcm9qZWN0X2lkIjogImF1dGhlbnRpY2F0ZWQtaW1hZ2UtcHVsbGluZyIsCiAgInByaXZhdGVfa2V5X2lkIjogImI5ZjJhNjY0YWE5YjIwNDg0Y2MxNTg2MDYzZmVmZGExOTIyNGFjM2IiLAogICJwcml2YXRlX2tleSI6ICItLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzdTSG5LVEVFaVlMamZcbkpmQVBHbUozd3JCY2VJNTBKS0xxS21GWE5RL3REWGJRK2g5YVl4aldJTDhEeDBKZTc0bVovS01uV2dYRjVLWlNcbm9BNktuSU85Yi9SY1NlV2VpSXRSekkzL1lYVitPNkNjcmpKSXl4anFWam5mVzJpM3NhMzd0OUE5VEZkbGZycm5cbjR6UkpiOWl4eU1YNGJMdHFGR3ZCMDNOSWl0QTNzVlo1ODhrb1FBZmgzSmhhQmVnTWorWjRSYko0aGVpQlFUMDNcbnZVbzViRWFQZVQ5RE16bHdzZWFQV2dydDZOME9VRGNBRTl4bGNJek11MjUzUG4vSzgySFpydEx4akd2UkhNVXhcbng0ZjhwSnhmQ3h4QlN3Z1NORit3OWpkbXR2b0wwRmE3ZGducFJlODZWRDY2ejNZenJqNHlLRXRqc2hLZHl5VWRcbkl5cVhoN1JSQWdNQkFBRUNnZ0VBT3pzZHdaeENVVlFUeEFka2wvSTVTRFVidi9NazRwaWZxYjJEa2FnbmhFcG9cbjFJajJsNGlWMTByOS9uenJnY2p5VlBBd3pZWk1JeDFBZVF0RDdoUzRHWmFweXZKWUc3NkZpWFpQUm9DVlB6b3VcbmZyOGRDaWFwbDV0enJDOWx2QXNHd29DTTdJWVRjZmNWdDdjRTEyRDNRS3NGNlo3QjJ6ZmdLS251WVBmK0NFNlRcbmNNMHkwaCtYRS9kMERvSERoVy96YU1yWEhqOFRvd2V1eXRrYmJzNGYvOUZqOVBuU2dET1lQd2xhbFZUcitGUWFcbkpSd1ZqVmxYcEZBUW14M0Jyd25rWnQzQ2lXV2lGM2QrSGk5RXRVYnRWclcxYjZnK1JRT0licWFtcis4YlJuZFhcbjZWZ3FCQWtKWjhSVnlkeFVQMGQxMUdqdU9QRHhCbkhCbmM0UW9rSXJFUUtCZ1FEMUNlaWN1ZGhXdGc0K2dTeGJcbnplanh0VjFONDFtZHVjQnpvMmp5b1dHbzNQVDh3ckJPL3lRRTM0cU9WSi9pZCs4SThoWjRvSWh1K0pBMDBzNmdcblRuSXErdi9kL1RFalk4MW5rWmlDa21SUFdiWHhhWXR4UjIxS1BYckxOTlFKS2ttOHRkeVh5UHFsOE1veUdmQ1dcbjJ2aVBKS05iNkhabnY5Q3lqZEo5ZzJMRG5RS0JnUUREcVN2eURtaGViOTIzSW96NGxlZ01SK205Z2xYVWdTS2dcbkVzZlllbVJmbU5XQitDN3ZhSXlVUm1ZNU55TXhmQlZXc3dXRldLYXhjK0krYnFzZmx6elZZdFpwMThNR2pzTURcbmZlZWZBWDZCWk1zVXQ3Qmw3WjlWSjg1bnRFZHFBQ0xwWitaLzN0SVJWdWdDV1pRMWhrbmxHa0dUMDI0SkVFKytcbk55SDFnM2QzUlFLQmdRQ1J2MXdKWkkwbVBsRklva0tGTkh1YTBUcDNLb1JTU1hzTURTVk9NK2xIckcxWHJtRjZcbkMwNGNTKzQ0N0dMUkxHOFVUaEpKbTRxckh0Ti9aK2dZOTYvMm1xYjRIakpORDM3TVhKQnZFYTN5ZUxTOHEvK1JcbjJGOU1LamRRaU5LWnhQcG84VzhOSlREWTVOa1BaZGh4a2pzSHdVNGRTNjZwMVRESUU0MGd0TFpaRFFLQmdGaldcbktyblFpTnEzOS9iNm5QOFJNVGJDUUFKbmR3anhTUU5kQTVmcW1rQTlhRk9HbCtqamsxQ1BWa0tNSWxLSmdEYkpcbk9heDl2OUc2Ui9NSTFIR1hmV3QxWU56VnRocjRIdHNyQTB0U3BsbWhwZ05XRTZWejZuQURqdGZQSnMyZUdqdlhcbmpQUnArdjhjY21MK3dTZzhQTGprM3ZsN2VlNXJsWWxNQndNdUdjUHhBb0dBZWRueGJXMVJMbVZubEFpSEx1L0xcbmxtZkF3RFdtRWlJMFVnK1BMbm9Pdk81dFE1ZDRXMS94RU44bFA0cWtzcGtmZk1Rbk5oNFNZR0VlQlQzMlpxQ1RcbkpSZ2YwWGpveXZ2dXA5eFhqTWtYcnBZL3ljMXpmcVRaQzBNTzkvMVVjMWJSR2RaMmR5M2xSNU5XYXA3T1h5Zk9cblBQcE5Gb1BUWGd2M3FDcW5sTEhyR3pNPVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwKICAiY2xpZW50X2VtYWlsIjogImltYWdlLXB1bGxpbmdAYXV0aGVudGljYXRlZC1pbWFnZS1wdWxsaW5nLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwKICAiY2xpZW50X2lkIjogIjExMzc5NzkxNDUzMDA3MzI3ODcxMiIsCiAgImF1dGhfdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi9hdXRoIiwKICAidG9rZW5fdXJpIjogImh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsCiAgImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9vYXV0aDIvdjEvY2VydHMiLAogICJjbGllbnRfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9yb2JvdC92MS9tZXRhZGF0YS94NTA5L2ltYWdlLXB1bGxpbmclNDBhdXRoZW50aWNhdGVkLWltYWdlLXB1bGxpbmcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iCn0=", 288 "email": "image-pulling@authenticated-image-pulling.iam.gserviceaccount.com" 289 } 290 } 291 }` 292 // we might be told to use a different docker config JSON. 293 if framework.TestContext.DockerConfigFile != "" { 294 contents, err := os.ReadFile(framework.TestContext.DockerConfigFile) 295 framework.ExpectNoError(err) 296 auth = string(contents) 297 } 298 secret := &v1.Secret{ 299 Data: map[string][]byte{v1.DockerConfigJsonKey: []byte(auth)}, 300 Type: v1.SecretTypeDockerConfigJson, 301 } 302 secret.Name = "image-pull-secret-" + string(uuid.NewUUID()) 303 ginkgo.By("create image pull secret") 304 _, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, secret, metav1.CreateOptions{}) 305 framework.ExpectNoError(err) 306 ginkgo.DeferCleanup(f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Delete, secret.Name, metav1.DeleteOptions{}) 307 container.ImagePullSecrets = []string{secret.Name} 308 } 309 // checkContainerStatus checks whether the container status matches expectation. 310 checkContainerStatus := func(ctx context.Context) error { 311 status, err := container.GetStatus(ctx) 312 if err != nil { 313 return fmt.Errorf("failed to get container status: %w", err) 314 } 315 // We need to check container state first. The default pod status is pending, If we check pod phase first, 316 // and the expected pod phase is Pending, the container status may not even show up when we check it. 317 // Check container state 318 if !expectedPullStatus { 319 if status.State.Running == nil { 320 return fmt.Errorf("expected container state: Running, got: %q", 321 GetContainerState(status.State)) 322 } 323 } 324 if expectedPullStatus { 325 if status.State.Waiting == nil { 326 return fmt.Errorf("expected container state: Waiting, got: %q", 327 GetContainerState(status.State)) 328 } 329 reason := status.State.Waiting.Reason 330 if reason != images.ErrImagePull.Error() && 331 reason != images.ErrImagePullBackOff.Error() { 332 return fmt.Errorf("unexpected waiting reason: %q", reason) 333 } 334 } 335 // Check pod phase 336 phase, err := container.GetPhase(ctx) 337 if err != nil { 338 return fmt.Errorf("failed to get pod phase: %w", err) 339 } 340 if phase != expectedPhase { 341 return fmt.Errorf("expected pod phase: %q, got: %q", expectedPhase, phase) 342 } 343 return nil 344 } 345 346 // The image registry is not stable, which sometimes causes the test to fail. Add retry mechanism to make this less flaky. 347 const flakeRetry = 3 348 for i := 1; i <= flakeRetry; i++ { 349 var err error 350 ginkgo.By("create the container") 351 container.Create(ctx) 352 ginkgo.By("check the container status") 353 for start := time.Now(); time.Since(start) < ContainerStatusRetryTimeout; time.Sleep(ContainerStatusPollInterval) { 354 if err = checkContainerStatus(ctx); err == nil { 355 break 356 } 357 } 358 ginkgo.By("delete the container") 359 _ = container.Delete(ctx) 360 if err == nil { 361 break 362 } 363 if i < flakeRetry { 364 framework.Logf("No.%d attempt failed: %v, retrying...", i, err) 365 } else { 366 framework.Failf("All %d attempts failed: %v", flakeRetry, err) 367 } 368 } 369 } 370 371 f.It("should not be able to pull image from invalid registry", f.WithNodeConformance(), func(ctx context.Context) { 372 image := imageutils.GetE2EImage(imageutils.InvalidRegistryImage) 373 imagePullTest(ctx, image, false, v1.PodPending, true, false) 374 }) 375 376 f.It("should be able to pull image", f.WithNodeConformance(), func(ctx context.Context) { 377 // NOTE(claudiub): The agnhost image is supposed to work on both Linux and Windows. 378 image := imageutils.GetE2EImage(imageutils.Agnhost) 379 imagePullTest(ctx, image, false, v1.PodRunning, false, false) 380 }) 381 382 f.It("should not be able to pull from private registry without secret", f.WithNodeConformance(), func(ctx context.Context) { 383 image := imageutils.GetE2EImage(imageutils.AuthenticatedAlpine) 384 imagePullTest(ctx, image, false, v1.PodPending, true, false) 385 }) 386 387 f.It("should be able to pull from private registry with secret", f.WithNodeConformance(), func(ctx context.Context) { 388 image := imageutils.GetE2EImage(imageutils.AuthenticatedAlpine) 389 isWindows := false 390 if framework.NodeOSDistroIs("windows") { 391 image = imageutils.GetE2EImage(imageutils.AuthenticatedWindowsNanoServer) 392 isWindows = true 393 } 394 imagePullTest(ctx, image, true, v1.PodRunning, false, isWindows) 395 }) 396 }) 397 }) 398 })