k8s.io/kubernetes@v1.29.3/pkg/kubelet/kuberuntime/kuberuntime_image_test.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 kuberuntime 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "testing" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/util/sets" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 "k8s.io/kubernetes/pkg/credentialprovider" 32 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 33 ) 34 35 func TestPullImage(t *testing.T) { 36 ctx := context.Background() 37 _, _, fakeManager, err := createTestRuntimeManager() 38 assert.NoError(t, err) 39 40 imageRef, err := fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}, nil, nil) 41 assert.NoError(t, err) 42 assert.Equal(t, "busybox", imageRef) 43 44 images, err := fakeManager.ListImages(ctx) 45 assert.NoError(t, err) 46 assert.Equal(t, 1, len(images)) 47 assert.Equal(t, images[0].RepoTags, []string{"busybox"}) 48 } 49 50 func TestPullImageWithError(t *testing.T) { 51 ctx := context.Background() 52 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 53 assert.NoError(t, err) 54 55 // trying to pull an image with an invalid name should return an error 56 imageRef, err := fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: ":invalid"}, nil, nil) 57 assert.Error(t, err) 58 assert.Equal(t, "", imageRef) 59 60 fakeImageService.InjectError("PullImage", fmt.Errorf("test-error")) 61 imageRef, err = fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}, nil, nil) 62 assert.Error(t, err) 63 assert.Equal(t, "", imageRef) 64 65 images, err := fakeManager.ListImages(ctx) 66 assert.NoError(t, err) 67 assert.Equal(t, 0, len(images)) 68 } 69 70 func TestPullImageWithInvalidImageName(t *testing.T) { 71 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 72 assert.NoError(t, err) 73 74 imageList := []string{"FAIL", "http://fail", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"} 75 fakeImageService.SetFakeImages(imageList) 76 for _, val := range imageList { 77 ctx := context.Background() 78 imageRef, err := fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: val}, nil, nil) 79 assert.Error(t, err) 80 assert.Equal(t, "", imageRef) 81 82 } 83 } 84 85 func TestListImages(t *testing.T) { 86 ctx := context.Background() 87 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 88 assert.NoError(t, err) 89 90 images := []string{"1111", "2222", "3333"} 91 expected := sets.NewString(images...) 92 fakeImageService.SetFakeImages(images) 93 94 actualImages, err := fakeManager.ListImages(ctx) 95 assert.NoError(t, err) 96 actual := sets.NewString() 97 for _, i := range actualImages { 98 actual.Insert(i.ID) 99 } 100 101 assert.Equal(t, expected.List(), actual.List()) 102 } 103 104 func TestListImagesPinnedField(t *testing.T) { 105 ctx := context.Background() 106 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 107 assert.NoError(t, err) 108 109 imagesPinned := map[string]bool{ 110 "1111": false, 111 "2222": true, 112 "3333": false, 113 } 114 imageList := []string{} 115 for image, pinned := range imagesPinned { 116 fakeImageService.SetFakeImagePinned(image, pinned) 117 imageList = append(imageList, image) 118 } 119 fakeImageService.SetFakeImages(imageList) 120 121 actualImages, err := fakeManager.ListImages(ctx) 122 assert.NoError(t, err) 123 for _, image := range actualImages { 124 assert.Equal(t, imagesPinned[image.ID], image.Pinned) 125 } 126 } 127 128 func TestListImagesWithError(t *testing.T) { 129 ctx := context.Background() 130 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 131 assert.NoError(t, err) 132 133 fakeImageService.InjectError("ListImages", fmt.Errorf("test-failure")) 134 135 actualImages, err := fakeManager.ListImages(ctx) 136 assert.Error(t, err) 137 assert.Nil(t, actualImages) 138 } 139 140 func TestGetImageRef(t *testing.T) { 141 ctx := context.Background() 142 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 143 assert.NoError(t, err) 144 145 image := "busybox" 146 fakeImageService.SetFakeImages([]string{image}) 147 imageRef, err := fakeManager.GetImageRef(ctx, kubecontainer.ImageSpec{Image: image}) 148 assert.NoError(t, err) 149 assert.Equal(t, image, imageRef) 150 } 151 152 func TestGetImageRefImageNotAvailableLocally(t *testing.T) { 153 ctx := context.Background() 154 _, _, fakeManager, err := createTestRuntimeManager() 155 assert.NoError(t, err) 156 157 image := "busybox" 158 159 imageRef, err := fakeManager.GetImageRef(ctx, kubecontainer.ImageSpec{Image: image}) 160 assert.NoError(t, err) 161 162 imageNotAvailableLocallyRef := "" 163 assert.Equal(t, imageNotAvailableLocallyRef, imageRef) 164 } 165 166 func TestGetImageRefWithError(t *testing.T) { 167 ctx := context.Background() 168 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 169 assert.NoError(t, err) 170 171 image := "busybox" 172 173 fakeImageService.InjectError("ImageStatus", fmt.Errorf("test-error")) 174 175 imageRef, err := fakeManager.GetImageRef(ctx, kubecontainer.ImageSpec{Image: image}) 176 assert.Error(t, err) 177 assert.Equal(t, "", imageRef) 178 } 179 180 func TestRemoveImage(t *testing.T) { 181 ctx := context.Background() 182 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 183 assert.NoError(t, err) 184 185 _, err = fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}, nil, nil) 186 assert.NoError(t, err) 187 assert.Equal(t, 1, len(fakeImageService.Images)) 188 189 err = fakeManager.RemoveImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}) 190 assert.NoError(t, err) 191 assert.Equal(t, 0, len(fakeImageService.Images)) 192 } 193 194 func TestRemoveImageNoOpIfImageNotLocal(t *testing.T) { 195 ctx := context.Background() 196 _, _, fakeManager, err := createTestRuntimeManager() 197 assert.NoError(t, err) 198 199 err = fakeManager.RemoveImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}) 200 assert.NoError(t, err) 201 } 202 203 func TestRemoveImageWithError(t *testing.T) { 204 ctx := context.Background() 205 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 206 assert.NoError(t, err) 207 208 _, err = fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}, nil, nil) 209 assert.NoError(t, err) 210 assert.Equal(t, 1, len(fakeImageService.Images)) 211 212 fakeImageService.InjectError("RemoveImage", fmt.Errorf("test-failure")) 213 214 err = fakeManager.RemoveImage(ctx, kubecontainer.ImageSpec{Image: "busybox"}) 215 assert.Error(t, err) 216 assert.Equal(t, 1, len(fakeImageService.Images)) 217 } 218 219 func TestImageStats(t *testing.T) { 220 ctx := context.Background() 221 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 222 assert.NoError(t, err) 223 224 const imageSize = 64 225 fakeImageService.SetFakeImageSize(imageSize) 226 images := []string{"1111", "2222", "3333"} 227 fakeImageService.SetFakeImages(images) 228 229 actualStats, err := fakeManager.ImageStats(ctx) 230 assert.NoError(t, err) 231 expectedStats := &kubecontainer.ImageStats{TotalStorageBytes: imageSize * uint64(len(images))} 232 assert.Equal(t, expectedStats, actualStats) 233 } 234 235 func TestImageStatsWithError(t *testing.T) { 236 ctx := context.Background() 237 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 238 assert.NoError(t, err) 239 240 fakeImageService.InjectError("ListImages", fmt.Errorf("test-failure")) 241 242 actualImageStats, err := fakeManager.ImageStats(ctx) 243 assert.Error(t, err) 244 assert.Nil(t, actualImageStats) 245 } 246 247 func TestPullWithSecrets(t *testing.T) { 248 ctx := context.Background() 249 // auth value is equivalent to: "username":"passed-user","password":"passed-password" 250 dockerCfg := map[string]map[string]string{"index.docker.io/v1/": {"email": "passed-email", "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk"}} 251 dockercfgContent, err := json.Marshal(dockerCfg) 252 if err != nil { 253 t.Errorf("unexpected error: %v", err) 254 } 255 256 dockerConfigJSON := map[string]map[string]map[string]string{"auths": dockerCfg} 257 dockerConfigJSONContent, err := json.Marshal(dockerConfigJSON) 258 if err != nil { 259 t.Errorf("unexpected error: %v", err) 260 } 261 262 tests := map[string]struct { 263 imageName string 264 passedSecrets []v1.Secret 265 builtInDockerConfig credentialprovider.DockerConfig 266 expectedAuth *runtimeapi.AuthConfig 267 }{ 268 "no matching secrets": { 269 "ubuntu", 270 []v1.Secret{}, 271 credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{}), 272 nil, 273 }, 274 "default keyring secrets": { 275 "ubuntu", 276 []v1.Secret{}, 277 credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{ 278 "index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil}, 279 }), 280 &runtimeapi.AuthConfig{Username: "built-in", Password: "password"}, 281 }, 282 "default keyring secrets unused": { 283 "ubuntu", 284 []v1.Secret{}, 285 credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{ 286 "extraneous": {Username: "built-in", Password: "password", Provider: nil}, 287 }), 288 nil, 289 }, 290 "builtin keyring secrets, but use passed": { 291 "ubuntu", 292 []v1.Secret{{Type: v1.SecretTypeDockercfg, Data: map[string][]byte{v1.DockerConfigKey: dockercfgContent}}}, 293 credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{ 294 "index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil}, 295 }), 296 &runtimeapi.AuthConfig{Username: "passed-user", Password: "passed-password"}, 297 }, 298 "builtin keyring secrets, but use passed with new docker config": { 299 "ubuntu", 300 []v1.Secret{{Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{v1.DockerConfigJsonKey: dockerConfigJSONContent}}}, 301 credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{ 302 "index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil}, 303 }), 304 &runtimeapi.AuthConfig{Username: "passed-user", Password: "passed-password"}, 305 }, 306 } 307 for description, test := range tests { 308 builtInKeyRing := &credentialprovider.BasicDockerKeyring{} 309 builtInKeyRing.Add(test.builtInDockerConfig) 310 _, fakeImageService, fakeManager, err := customTestRuntimeManager(builtInKeyRing) 311 require.NoError(t, err) 312 313 _, err = fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: test.imageName}, test.passedSecrets, nil) 314 require.NoError(t, err) 315 fakeImageService.AssertImagePulledWithAuth(t, &runtimeapi.ImageSpec{Image: test.imageName, Annotations: make(map[string]string)}, test.expectedAuth, description) 316 } 317 } 318 319 func TestPullWithSecretsWithError(t *testing.T) { 320 ctx := context.Background() 321 322 dockerCfg := map[string]map[string]map[string]string{ 323 "auths": { 324 "index.docker.io/v1/": { 325 "email": "passed-email", 326 "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk", 327 }, 328 }, 329 } 330 331 dockerConfigJSON, err := json.Marshal(dockerCfg) 332 if err != nil { 333 t.Fatal(err) 334 } 335 336 for _, test := range []struct { 337 name string 338 imageName string 339 passedSecrets []v1.Secret 340 shouldInjectError bool 341 }{ 342 { 343 name: "invalid docker secret", 344 imageName: "ubuntu", 345 passedSecrets: []v1.Secret{{Type: v1.SecretTypeDockercfg, Data: map[string][]byte{v1.DockerConfigKey: []byte("invalid")}}}, 346 }, 347 { 348 name: "secret provided, pull failed", 349 imageName: "ubuntu", 350 passedSecrets: []v1.Secret{ 351 {Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{v1.DockerConfigKey: dockerConfigJSON}}, 352 }, 353 shouldInjectError: true, 354 }, 355 } { 356 t.Run(test.name, func(t *testing.T) { 357 _, fakeImageService, fakeManager, err := createTestRuntimeManager() 358 assert.NoError(t, err) 359 360 if test.shouldInjectError { 361 fakeImageService.InjectError("PullImage", fmt.Errorf("test-error")) 362 } 363 364 imageRef, err := fakeManager.PullImage(ctx, kubecontainer.ImageSpec{Image: test.imageName}, test.passedSecrets, nil) 365 assert.Error(t, err) 366 assert.Equal(t, "", imageRef) 367 368 images, err := fakeManager.ListImages(ctx) 369 assert.NoError(t, err) 370 assert.Equal(t, 0, len(images)) 371 }) 372 } 373 } 374 375 func TestPullThenListWithAnnotations(t *testing.T) { 376 ctx := context.Background() 377 _, _, fakeManager, err := createTestRuntimeManager() 378 assert.NoError(t, err) 379 380 imageSpec := kubecontainer.ImageSpec{ 381 Image: "12345", 382 Annotations: []kubecontainer.Annotation{ 383 {Name: "kubernetes.io/runtimehandler", Value: "handler_name"}, 384 }, 385 } 386 387 _, err = fakeManager.PullImage(ctx, imageSpec, nil, nil) 388 assert.NoError(t, err) 389 390 images, err := fakeManager.ListImages(ctx) 391 assert.NoError(t, err) 392 assert.Equal(t, 1, len(images)) 393 assert.Equal(t, images[0].Spec, imageSpec) 394 }