123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- /*
- Copyright 2015 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package images
- import (
- "fmt"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "k8s.io/apimachinery/pkg/util/clock"
- "k8s.io/client-go/tools/record"
- statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
- "k8s.io/kubernetes/pkg/kubelet/container"
- containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
- statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing"
- )
- var zero time.Time
- var sandboxImage = "k8s.gcr.io/pause-amd64:latest"
- func newRealImageGCManager(policy ImageGCPolicy) (*realImageGCManager, *containertest.FakeRuntime, *statstest.StatsProvider) {
- fakeRuntime := &containertest.FakeRuntime{}
- mockStatsProvider := new(statstest.StatsProvider)
- return &realImageGCManager{
- runtime: fakeRuntime,
- policy: policy,
- imageRecords: make(map[string]*imageRecord),
- statsProvider: mockStatsProvider,
- recorder: &record.FakeRecorder{},
- sandboxImage: sandboxImage,
- }, fakeRuntime, mockStatsProvider
- }
- // Accessors used for thread-safe testing.
- func (im *realImageGCManager) imageRecordsLen() int {
- im.imageRecordsLock.Lock()
- defer im.imageRecordsLock.Unlock()
- return len(im.imageRecords)
- }
- func (im *realImageGCManager) getImageRecord(name string) (*imageRecord, bool) {
- im.imageRecordsLock.Lock()
- defer im.imageRecordsLock.Unlock()
- v, ok := im.imageRecords[name]
- vCopy := *v
- return &vCopy, ok
- }
- // Returns the id of the image with the given ID.
- func imageID(id int) string {
- return fmt.Sprintf("image-%d", id)
- }
- // Returns the name of the image with the given ID.
- func imageName(id int) string {
- return imageID(id) + "-name"
- }
- // Make an image with the specified ID.
- func makeImage(id int, size int64) container.Image {
- return container.Image{
- ID: imageID(id),
- Size: size,
- }
- }
- // Make a container with the specified ID. It will use the image with the same ID.
- func makeContainer(id int) *container.Container {
- return &container.Container{
- ID: container.ContainerID{Type: "test", ID: fmt.Sprintf("container-%d", id)},
- Image: imageName(id),
- ImageID: imageID(id),
- }
- }
- func TestDetectImagesInitialDetect(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- makeImage(2, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- {
- ID: container.ContainerID{Type: "test", ID: fmt.Sprintf("container-%d", 1)},
- ImageID: imageID(1),
- // The image filed is not set to simulate a no-name image
- },
- {
- ID: container.ContainerID{Type: "test", ID: fmt.Sprintf("container-%d", 2)},
- Image: imageName(2),
- ImageID: imageID(2),
- },
- },
- }},
- }
- startTime := time.Now().Add(-time.Millisecond)
- _, err := manager.detectImages(zero)
- assert := assert.New(t)
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 3)
- noContainer, ok := manager.getImageRecord(imageID(0))
- require.True(t, ok)
- assert.Equal(zero, noContainer.firstDetected)
- assert.Equal(zero, noContainer.lastUsed)
- withContainerUsingNoNameImage, ok := manager.getImageRecord(imageID(1))
- require.True(t, ok)
- assert.Equal(zero, withContainerUsingNoNameImage.firstDetected)
- assert.True(withContainerUsingNoNameImage.lastUsed.After(startTime))
- withContainer, ok := manager.getImageRecord(imageID(2))
- require.True(t, ok)
- assert.Equal(zero, withContainer.firstDetected)
- assert.True(withContainer.lastUsed.After(startTime))
- }
- func TestDetectImagesWithNewImage(t *testing.T) {
- // Just one image initially.
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- _, err := manager.detectImages(zero)
- assert := assert.New(t)
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 2)
- // Add a new image.
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 1024),
- makeImage(2, 1024),
- }
- detectedTime := zero.Add(time.Second)
- startTime := time.Now().Add(-time.Millisecond)
- _, err = manager.detectImages(detectedTime)
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 3)
- noContainer, ok := manager.getImageRecord(imageID(0))
- require.True(t, ok)
- assert.Equal(zero, noContainer.firstDetected)
- assert.Equal(zero, noContainer.lastUsed)
- withContainer, ok := manager.getImageRecord(imageID(1))
- require.True(t, ok)
- assert.Equal(zero, withContainer.firstDetected)
- assert.True(withContainer.lastUsed.After(startTime))
- newContainer, ok := manager.getImageRecord(imageID(2))
- require.True(t, ok)
- assert.Equal(detectedTime, newContainer.firstDetected)
- assert.Equal(zero, noContainer.lastUsed)
- }
- func TestDeleteUnusedImagesExemptSandboxImage(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- {
- ID: sandboxImage,
- Size: 1024,
- },
- }
- err := manager.DeleteUnusedImages()
- assert := assert.New(t)
- assert.Len(fakeRuntime.ImageList, 1)
- require.NoError(t, err)
- }
- func TestDetectImagesContainerStopped(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- _, err := manager.detectImages(zero)
- assert := assert.New(t)
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 2)
- withContainer, ok := manager.getImageRecord(imageID(1))
- require.True(t, ok)
- // Simulate container being stopped.
- fakeRuntime.AllPodList = []*containertest.FakePod{}
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 2)
- container1, ok := manager.getImageRecord(imageID(0))
- require.True(t, ok)
- assert.Equal(zero, container1.firstDetected)
- assert.Equal(zero, container1.lastUsed)
- container2, ok := manager.getImageRecord(imageID(1))
- require.True(t, ok)
- assert.Equal(zero, container2.firstDetected)
- assert.True(container2.lastUsed.Equal(withContainer.lastUsed))
- }
- func TestDetectImagesWithRemovedImages(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- _, err := manager.detectImages(zero)
- assert := assert.New(t)
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 2)
- // Simulate both images being removed.
- fakeRuntime.ImageList = []container.Image{}
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- assert.Equal(manager.imageRecordsLen(), 0)
- }
- func TestFreeSpaceImagesInUseContainersAreIgnored(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- spaceFreed, err := manager.freeSpace(2048, time.Now())
- assert := assert.New(t)
- require.NoError(t, err)
- assert.EqualValues(1024, spaceFreed)
- assert.Len(fakeRuntime.ImageList, 1)
- }
- func TestDeleteUnusedImagesRemoveAllUnusedImages(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- makeImage(2, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(2),
- },
- }},
- }
- err := manager.DeleteUnusedImages()
- assert := assert.New(t)
- require.NoError(t, err)
- assert.Len(fakeRuntime.ImageList, 1)
- }
- func TestFreeSpaceRemoveByLeastRecentlyUsed(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(0),
- makeContainer(1),
- },
- }},
- }
- // Make 1 be more recently used than 0.
- _, err := manager.detectImages(zero)
- require.NoError(t, err)
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{},
- }},
- }
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- require.Equal(t, manager.imageRecordsLen(), 2)
- spaceFreed, err := manager.freeSpace(1024, time.Now())
- assert := assert.New(t)
- require.NoError(t, err)
- assert.EqualValues(1024, spaceFreed)
- assert.Len(fakeRuntime.ImageList, 1)
- }
- func TestFreeSpaceTiesBrokenByDetectedTime(t *testing.T) {
- manager, fakeRuntime, _ := newRealImageGCManager(ImageGCPolicy{})
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- }
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(0),
- },
- }},
- }
- // Make 1 more recently detected but used at the same time as 0.
- _, err := manager.detectImages(zero)
- require.NoError(t, err)
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- fakeRuntime.AllPodList = []*containertest.FakePod{}
- _, err = manager.detectImages(time.Now())
- require.NoError(t, err)
- require.Equal(t, manager.imageRecordsLen(), 2)
- spaceFreed, err := manager.freeSpace(1024, time.Now())
- assert := assert.New(t)
- require.NoError(t, err)
- assert.EqualValues(2048, spaceFreed)
- assert.Len(fakeRuntime.ImageList, 1)
- }
- func TestGarbageCollectBelowLowThreshold(t *testing.T) {
- policy := ImageGCPolicy{
- HighThresholdPercent: 90,
- LowThresholdPercent: 80,
- }
- manager, _, mockStatsProvider := newRealImageGCManager(policy)
- // Expect 40% usage.
- mockStatsProvider.On("ImageFsStats").Return(&statsapi.FsStats{
- AvailableBytes: uint64Ptr(600),
- CapacityBytes: uint64Ptr(1000),
- }, nil)
- assert.NoError(t, manager.GarbageCollect())
- }
- func TestGarbageCollectCadvisorFailure(t *testing.T) {
- policy := ImageGCPolicy{
- HighThresholdPercent: 90,
- LowThresholdPercent: 80,
- }
- manager, _, mockStatsProvider := newRealImageGCManager(policy)
- mockStatsProvider.On("ImageFsStats").Return(&statsapi.FsStats{}, fmt.Errorf("error"))
- assert.NotNil(t, manager.GarbageCollect())
- }
- func TestGarbageCollectBelowSuccess(t *testing.T) {
- policy := ImageGCPolicy{
- HighThresholdPercent: 90,
- LowThresholdPercent: 80,
- }
- manager, fakeRuntime, mockStatsProvider := newRealImageGCManager(policy)
- // Expect 95% usage and most of it gets freed.
- mockStatsProvider.On("ImageFsStats").Return(&statsapi.FsStats{
- AvailableBytes: uint64Ptr(50),
- CapacityBytes: uint64Ptr(1000),
- }, nil)
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 450),
- }
- assert.NoError(t, manager.GarbageCollect())
- }
- func TestGarbageCollectNotEnoughFreed(t *testing.T) {
- policy := ImageGCPolicy{
- HighThresholdPercent: 90,
- LowThresholdPercent: 80,
- }
- manager, fakeRuntime, mockStatsProvider := newRealImageGCManager(policy)
- // Expect 95% usage and little of it gets freed.
- mockStatsProvider.On("ImageFsStats").Return(&statsapi.FsStats{
- AvailableBytes: uint64Ptr(50),
- CapacityBytes: uint64Ptr(1000),
- }, nil)
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 50),
- }
- assert.NotNil(t, manager.GarbageCollect())
- }
- func TestGarbageCollectImageNotOldEnough(t *testing.T) {
- policy := ImageGCPolicy{
- HighThresholdPercent: 90,
- LowThresholdPercent: 80,
- MinAge: time.Minute * 1,
- }
- fakeRuntime := &containertest.FakeRuntime{}
- mockStatsProvider := new(statstest.StatsProvider)
- manager := &realImageGCManager{
- runtime: fakeRuntime,
- policy: policy,
- imageRecords: make(map[string]*imageRecord),
- statsProvider: mockStatsProvider,
- recorder: &record.FakeRecorder{},
- }
- fakeRuntime.ImageList = []container.Image{
- makeImage(0, 1024),
- makeImage(1, 2048),
- }
- // 1 image is in use, and another one is not old enough
- fakeRuntime.AllPodList = []*containertest.FakePod{
- {Pod: &container.Pod{
- Containers: []*container.Container{
- makeContainer(1),
- },
- }},
- }
- fakeClock := clock.NewFakeClock(time.Now())
- t.Log(fakeClock.Now())
- _, err := manager.detectImages(fakeClock.Now())
- require.NoError(t, err)
- require.Equal(t, manager.imageRecordsLen(), 2)
- // no space freed since one image is in used, and another one is not old enough
- spaceFreed, err := manager.freeSpace(1024, fakeClock.Now())
- assert := assert.New(t)
- require.NoError(t, err)
- assert.EqualValues(0, spaceFreed)
- assert.Len(fakeRuntime.ImageList, 2)
- // move clock by minAge duration, then 1 image will be garbage collected
- fakeClock.Step(policy.MinAge)
- spaceFreed, err = manager.freeSpace(1024, fakeClock.Now())
- require.NoError(t, err)
- assert.EqualValues(1024, spaceFreed)
- assert.Len(fakeRuntime.ImageList, 1)
- }
- func TestValidateImageGCPolicy(t *testing.T) {
- testCases := []struct {
- name string
- imageGCPolicy ImageGCPolicy
- expectErr string
- }{
- {
- name: "Test for LowThresholdPercent < HighThresholdPercent",
- imageGCPolicy: ImageGCPolicy{
- HighThresholdPercent: 2,
- LowThresholdPercent: 1,
- },
- },
- {
- name: "Test for HighThresholdPercent < 0,",
- imageGCPolicy: ImageGCPolicy{
- HighThresholdPercent: -1,
- },
- expectErr: "invalid HighThresholdPercent -1, must be in range [0-100]",
- },
- {
- name: "Test for HighThresholdPercent > 100",
- imageGCPolicy: ImageGCPolicy{
- HighThresholdPercent: 101,
- },
- expectErr: "invalid HighThresholdPercent 101, must be in range [0-100]",
- },
- {
- name: "Test for LowThresholdPercent < 0",
- imageGCPolicy: ImageGCPolicy{
- LowThresholdPercent: -1,
- },
- expectErr: "invalid LowThresholdPercent -1, must be in range [0-100]",
- },
- {
- name: "Test for LowThresholdPercent > 100",
- imageGCPolicy: ImageGCPolicy{
- LowThresholdPercent: 101,
- },
- expectErr: "invalid LowThresholdPercent 101, must be in range [0-100]",
- },
- {
- name: "Test for LowThresholdPercent > HighThresholdPercent",
- imageGCPolicy: ImageGCPolicy{
- HighThresholdPercent: 1,
- LowThresholdPercent: 2,
- },
- expectErr: "LowThresholdPercent 2 can not be higher than HighThresholdPercent 1",
- },
- }
- for _, tc := range testCases {
- if _, err := NewImageGCManager(nil, nil, nil, nil, tc.imageGCPolicy, ""); err != nil {
- if err.Error() != tc.expectErr {
- t.Errorf("[%s:]Expected err:%v, but got:%v", tc.name, tc.expectErr, err.Error())
- }
- }
- }
- }
- func TestImageCacheReturnCopiedList(t *testing.T) {
- cache := &imageCache{}
- testList := []container.Image{{ID: "1"}, {ID: "2"}}
- cache.set(testList)
- list := cache.get()
- assert.Len(t, list, 2)
- list[0].ID = "3"
- assert.Equal(t, cache.get(), testList)
- }
- func uint64Ptr(i uint64) *uint64 {
- return &i
- }
|