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  }