Add image garbage collection policy to imageManager.

This commit is contained in:
Victor Marmol 2015-03-15 20:37:19 -07:00
parent 303a1f7ea1
commit 6bcbf12a3d
7 changed files with 304 additions and 177 deletions

View File

@ -18,7 +18,7 @@ package cadvisor
import ( import (
cadvisorApi "github.com/google/cadvisor/info/v1" cadvisorApi "github.com/google/cadvisor/info/v1"
cadvisorApi2 "github.com/google/cadvisor/info/v2" cadvisorApiV2 "github.com/google/cadvisor/info/v2"
) )
// Fake cAdvisor implementation. // Fake cAdvisor implementation.
@ -39,6 +39,6 @@ func (c *Fake) MachineInfo() (*cadvisorApi.MachineInfo, error) {
return new(cadvisorApi.MachineInfo), nil return new(cadvisorApi.MachineInfo), nil
} }
func (c *Fake) DockerImagesFsInfo() (cadvisorApi2.FsInfo, error) { func (c *Fake) DockerImagesFsInfo() (cadvisorApiV2.FsInfo, error) {
return cadvisorApi2.FsInfo{}, nil return cadvisorApiV2.FsInfo{}, nil
} }

View File

@ -28,7 +28,7 @@ import (
cadvisorFs "github.com/google/cadvisor/fs" cadvisorFs "github.com/google/cadvisor/fs"
cadvisorHttp "github.com/google/cadvisor/http" cadvisorHttp "github.com/google/cadvisor/http"
cadvisorApi "github.com/google/cadvisor/info/v1" cadvisorApi "github.com/google/cadvisor/info/v1"
cadvisorApi2 "github.com/google/cadvisor/info/v2" cadvisorApiV2 "github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/manager" "github.com/google/cadvisor/manager"
"github.com/google/cadvisor/storage/memory" "github.com/google/cadvisor/storage/memory"
"github.com/google/cadvisor/utils/sysfs" "github.com/google/cadvisor/utils/sysfs"
@ -113,13 +113,13 @@ func (self *cadvisorClient) MachineInfo() (*cadvisorApi.MachineInfo, error) {
return self.GetMachineInfo() return self.GetMachineInfo()
} }
func (self *cadvisorClient) DockerImagesFsInfo() (cadvisorApi2.FsInfo, error) { func (self *cadvisorClient) DockerImagesFsInfo() (cadvisorApiV2.FsInfo, error) {
res, err := self.GetFsInfo(cadvisorFs.LabelDockerImages) res, err := self.GetFsInfo(cadvisorFs.LabelDockerImages)
if err != nil { if err != nil {
return cadvisorApi2.FsInfo{}, err return cadvisorApiV2.FsInfo{}, err
} }
if len(res) == 0 { if len(res) == 0 {
return cadvisorApi2.FsInfo{}, fmt.Errorf("failed to find information for the filesystem containing Docker images") return cadvisorApiV2.FsInfo{}, fmt.Errorf("failed to find information for the filesystem containing Docker images")
} }
// TODO(vmarmol): Handle this better when Docker has more than one image filesystem. // TODO(vmarmol): Handle this better when Docker has more than one image filesystem.
if len(res) > 1 { if len(res) > 1 {

View File

@ -18,7 +18,7 @@ package cadvisor
import ( import (
cadvisorApi "github.com/google/cadvisor/info/v1" cadvisorApi "github.com/google/cadvisor/info/v1"
cadvisorApi2 "github.com/google/cadvisor/info/v2" cadvisorApiV2 "github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@ -46,7 +46,7 @@ func (c *Mock) MachineInfo() (*cadvisorApi.MachineInfo, error) {
return args.Get(0).(*cadvisorApi.MachineInfo), args.Error(1) return args.Get(0).(*cadvisorApi.MachineInfo), args.Error(1)
} }
func (c *Mock) DockerImagesFsInfo() (cadvisorApi2.FsInfo, error) { func (c *Mock) DockerImagesFsInfo() (cadvisorApiV2.FsInfo, error) {
args := c.Called() args := c.Called()
return args.Get(0).(cadvisorApi2.FsInfo), args.Error(1) return args.Get(0).(cadvisorApiV2.FsInfo), args.Error(1)
} }

View File

@ -22,7 +22,7 @@ import (
"errors" "errors"
cadvisorApi "github.com/google/cadvisor/info/v1" cadvisorApi "github.com/google/cadvisor/info/v1"
cadvisorApi2 "github.com/google/cadvisor/info/v2" cadvisorApiV2 "github.com/google/cadvisor/info/v2"
) )
type cadvisorUnsupported struct { type cadvisorUnsupported struct {
@ -48,6 +48,6 @@ func (self *cadvisorUnsupported) MachineInfo() (*cadvisorApi.MachineInfo, error)
return nil, unsupportedErr return nil, unsupportedErr
} }
func (self *cadvisorUnsupported) DockerImagesFsInfo() (cadvisorApi2.FsInfo, error) { func (self *cadvisorUnsupported) DockerImagesFsInfo() (cadvisorApiV2.FsInfo, error) {
return cadvisorApi2.FsInfo{}, unsupportedErr return cadvisorApiV2.FsInfo{}, unsupportedErr
} }

View File

@ -18,7 +18,7 @@ package cadvisor
import ( import (
cadvisorApi "github.com/google/cadvisor/info/v1" cadvisorApi "github.com/google/cadvisor/info/v1"
cadvisorApi2 "github.com/google/cadvisor/info/v2" cadvisorApiV2 "github.com/google/cadvisor/info/v2"
) )
// Interface is an abstract interface for testability. It abstracts the interface to cAdvisor. // Interface is an abstract interface for testability. It abstracts the interface to cAdvisor.
@ -28,5 +28,5 @@ type Interface interface {
MachineInfo() (*cadvisorApi.MachineInfo, error) MachineInfo() (*cadvisorApi.MachineInfo, error)
// Returns usage information about the filesystem holding Docker images. // Returns usage information about the filesystem holding Docker images.
DockerImagesFsInfo() (cadvisorApi2.FsInfo, error) DockerImagesFsInfo() (cadvisorApiV2.FsInfo, error)
} }

View File

@ -17,10 +17,12 @@ limitations under the License.
package kubelet package kubelet
import ( import (
"fmt"
"sort" "sort"
"sync" "sync"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
docker "github.com/fsouza/go-dockerclient" docker "github.com/fsouza/go-dockerclient"
@ -29,31 +31,40 @@ import (
// Manages lifecycle of all images. // Manages lifecycle of all images.
// //
// Class is thread-safe. // Implementation is thread-safe.
type imageManager interface { type imageManager interface {
// Starts the image manager. // Applies the garbage collection policy. Errors include being unable to free
Start() error // enough space as per the garbage collection policy.
GarbageCollect() error
// Tries to free bytesToFree worth of images on the disk.
//
// Returns the number of bytes free and an error if any occured. The number of
// bytes freed is always returned.
// Note that error may be nil and the number of bytes free may be less
// than bytesToFree.
FreeSpace(bytesToFree int64) (int64, error)
// TODO(vmarmol): Have this subsume pulls as well. // TODO(vmarmol): Have this subsume pulls as well.
} }
// A policy for garbage collecting images. Policy defines an allowed band in
// which garbage collection will be run.
type ImageGCPolicy struct {
// Any usage above this threshold will always trigger garbage collection.
// This is the highest usage we will allow.
HighThresholdPercent int
// Any usage below this threshold will never trigger garbage collection.
// This is the lowest threshold we will try to garbage collect to.
LowThresholdPercent int
}
type realImageManager struct { type realImageManager struct {
// Connection to the Docker daemon. // Connection to the Docker daemon.
dockerClient dockertools.DockerInterface dockerClient dockertools.DockerInterface
// Records of images and their use. // Records of images and their use.
imageRecords map[string]*imageRecord imageRecords map[string]*imageRecord
// Lock for imageRecords.
imageRecordsLock sync.Mutex imageRecordsLock sync.Mutex
// The image garbage collection policy in use.
policy ImageGCPolicy
// cAdvisor instance.
cadvisor cadvisor.Interface
} }
// Information about the images we track. // Information about the images we track.
@ -68,14 +79,30 @@ type imageRecord struct {
size int64 size int64
} }
func newImageManager(dockerClient dockertools.DockerInterface) imageManager { func newImageManager(dockerClient dockertools.DockerInterface, cadvisorInterface cadvisor.Interface, policy ImageGCPolicy) (imageManager, error) {
return &realImageManager{ // Validate policy.
dockerClient: dockerClient, if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 {
imageRecords: make(map[string]*imageRecord), return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent)
} }
if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 {
return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent)
}
im := &realImageManager{
dockerClient: dockerClient,
policy: policy,
imageRecords: make(map[string]*imageRecord),
cadvisor: cadvisorInterface,
}
err := im.start()
if err != nil {
return nil, fmt.Errorf("failed to start image manager: %v", err)
}
return im, nil
} }
func (self *realImageManager) Start() error { func (self *realImageManager) start() error {
// Initial detection make detected time "unknown" in the past. // Initial detection make detected time "unknown" in the past.
var zero time.Time var zero time.Time
err := self.detectImages(zero) err := self.detectImages(zero)
@ -83,7 +110,7 @@ func (self *realImageManager) Start() error {
return err return err
} }
util.Forever(func() { go util.Forever(func() {
err := self.detectImages(time.Now()) err := self.detectImages(time.Now())
if err != nil { if err != nil {
glog.Warningf("[ImageManager] Failed to monitor images: %v", err) glog.Warningf("[ImageManager] Failed to monitor images: %v", err)
@ -144,7 +171,40 @@ func (self *realImageManager) detectImages(detected time.Time) error {
return nil return nil
} }
func (self *realImageManager) FreeSpace(bytesToFree int64) (int64, error) { func (self *realImageManager) GarbageCollect() error {
// Get disk usage on disk holding images.
fsInfo, err := self.cadvisor.DockerImagesFsInfo()
if err != nil {
return err
}
usage := int64(fsInfo.Usage)
capacity := int64(fsInfo.Capacity)
usagePercent := int(usage * 100 / capacity)
// If over the max threshold, free enough to place us at the lower threshold.
if usagePercent >= self.policy.HighThresholdPercent {
amountToFree := usage - (int64(self.policy.LowThresholdPercent) * capacity / 100)
freed, err := self.freeSpace(amountToFree)
if err != nil {
return err
}
if freed < amountToFree {
// TODO(vmarmol): Surface event.
return fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d, but freed %d", amountToFree, freed)
}
}
return nil
}
// Tries to free bytesToFree worth of images on the disk.
//
// Returns the number of bytes free and an error if any occured. The number of
// bytes freed is always returned.
// Note that error may be nil and the number of bytes free may be less
// than bytesToFree.
func (self *realImageManager) freeSpace(bytesToFree int64) (int64, error) {
startTime := time.Now() startTime := time.Now()
err := self.detectImages(startTime) err := self.detectImages(startTime)
if err != nil { if err != nil {

View File

@ -21,17 +21,39 @@ import (
"testing" "testing"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/dockertools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
docker "github.com/fsouza/go-dockerclient" docker "github.com/fsouza/go-dockerclient"
cadvisorApiV2 "github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var zero time.Time var zero time.Time
func newRealImageManager(dockerClient dockertools.DockerInterface) *realImageManager { func newRealImageManager(t *testing.T, policy ImageGCPolicy) (*realImageManager, *dockertools.FakeDockerClient, *cadvisor.Mock) {
return newImageManager(dockerClient).(*realImageManager) fakeDocker := &dockertools.FakeDockerClient{
RemovedImages: util.NewStringSet(),
}
mockCadvisor := new(cadvisor.Mock)
im, err := newImageManager(fakeDocker, mockCadvisor, policy)
require.NoError(t, err)
return im.(*realImageManager), fakeDocker, mockCadvisor
}
// Accessors used for thread-safe testing.
func (self *realImageManager) imageRecordsLen() int {
self.imageRecordsLock.Lock()
defer self.imageRecordsLock.Unlock()
return len(self.imageRecords)
}
func (self *realImageManager) getImageRecord(name string) (*imageRecord, bool) {
self.imageRecordsLock.Lock()
defer self.imageRecordsLock.Unlock()
v, ok := self.imageRecords[name]
vCopy := *v
return &vCopy, ok
} }
// Returns the name of the image with the given ID. // Returns the name of the image with the given ID.
@ -56,27 +78,25 @@ func makeContainer(id int) docker.APIContainers {
} }
func TestDetectImagesInitialDetect(t *testing.T) { func TestDetectImagesInitialDetect(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
}, }
ContainerList: []docker.APIContainers{ fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1), makeContainer(1),
},
} }
manager := newRealImageManager(fakeDocker)
startTime := time.Now().Add(-time.Millisecond) startTime := time.Now().Add(-time.Millisecond)
err := manager.detectImages(zero) err := manager.detectImages(zero)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 2) assert.Equal(manager.imageRecordsLen(), 2)
noContainer, ok := manager.imageRecords[imageName(0)] noContainer, ok := manager.getImageRecord(imageName(0))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, noContainer.detected) assert.Equal(zero, noContainer.detected)
assert.Equal(zero, noContainer.lastUsed) assert.Equal(zero, noContainer.lastUsed)
withContainer, ok := manager.imageRecords[imageName(1)] withContainer, ok := manager.getImageRecord(imageName(1))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, withContainer.detected) assert.Equal(zero, withContainer.detected)
assert.True(withContainer.lastUsed.After(startTime)) assert.True(withContainer.lastUsed.After(startTime))
@ -84,21 +104,19 @@ func TestDetectImagesInitialDetect(t *testing.T) {
func TestDetectImagesWithNewImage(t *testing.T) { func TestDetectImagesWithNewImage(t *testing.T) {
// Just one image initially. // Just one image initially.
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
}, }
ContainerList: []docker.APIContainers{ fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1), makeContainer(1),
},
} }
manager := newRealImageManager(fakeDocker)
err := manager.detectImages(zero) err := manager.detectImages(zero)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 2) assert.Equal(manager.imageRecordsLen(), 2)
// Add a new image. // Add a new image.
fakeDocker.Images = []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
@ -110,147 +128,96 @@ func TestDetectImagesWithNewImage(t *testing.T) {
detectedTime := zero.Add(time.Second) detectedTime := zero.Add(time.Second)
startTime := time.Now().Add(-time.Millisecond) startTime := time.Now().Add(-time.Millisecond)
err = manager.detectImages(detectedTime) err = manager.detectImages(detectedTime)
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 3) assert.Equal(manager.imageRecordsLen(), 3)
noContainer, ok := manager.imageRecords[imageName(0)] noContainer, ok := manager.getImageRecord(imageName(0))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, noContainer.detected) assert.Equal(zero, noContainer.detected)
assert.Equal(zero, noContainer.lastUsed) assert.Equal(zero, noContainer.lastUsed)
withContainer, ok := manager.imageRecords[imageName(1)] withContainer, ok := manager.getImageRecord(imageName(1))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, withContainer.detected) assert.Equal(zero, withContainer.detected)
assert.True(withContainer.lastUsed.After(startTime)) assert.True(withContainer.lastUsed.After(startTime))
newContainer, ok := manager.imageRecords[imageName(2)] newContainer, ok := manager.getImageRecord(imageName(2))
require.True(t, ok) require.True(t, ok)
assert.Equal(detectedTime, newContainer.detected) assert.Equal(detectedTime, newContainer.detected)
assert.Equal(zero, noContainer.lastUsed) assert.Equal(zero, noContainer.lastUsed)
} }
func TestDetectImagesContainerStopped(t *testing.T) { func TestDetectImagesContainerStopped(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
}, }
ContainerList: []docker.APIContainers{ fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1), makeContainer(1),
},
} }
manager := newRealImageManager(fakeDocker)
err := manager.detectImages(zero) err := manager.detectImages(zero)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 2) assert.Equal(manager.imageRecordsLen(), 2)
withContainer, ok := manager.imageRecords[imageName(1)] withContainer, ok := manager.getImageRecord(imageName(1))
require.True(t, ok) require.True(t, ok)
// Simulate container being stopped. // Simulate container being stopped.
fakeDocker.ContainerList = []docker.APIContainers{} fakeDocker.ContainerList = []docker.APIContainers{}
err = manager.detectImages(time.Now()) err = manager.detectImages(time.Now())
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 2) assert.Equal(manager.imageRecordsLen(), 2)
container1, ok := manager.imageRecords[imageName(0)] container1, ok := manager.getImageRecord(imageName(0))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, container1.detected) assert.Equal(zero, container1.detected)
assert.Equal(zero, container1.lastUsed) assert.Equal(zero, container1.lastUsed)
container2, ok := manager.imageRecords[imageName(1)] container2, ok := manager.getImageRecord(imageName(1))
require.True(t, ok) require.True(t, ok)
assert.Equal(zero, container2.detected) assert.Equal(zero, container2.detected)
assert.True(container2.lastUsed.Equal(withContainer.lastUsed)) assert.True(container2.lastUsed.Equal(withContainer.lastUsed))
} }
func TestDetectImagesWithRemovedImages(t *testing.T) { func TestDetectImagesWithRemovedImages(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
}, }
ContainerList: []docker.APIContainers{ fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1), makeContainer(1),
},
} }
manager := newRealImageManager(fakeDocker)
err := manager.detectImages(zero) err := manager.detectImages(zero)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 2) assert.Equal(manager.imageRecordsLen(), 2)
// Simulate both images being removed. // Simulate both images being removed.
fakeDocker.Images = []docker.APIImages{} fakeDocker.Images = []docker.APIImages{}
err = manager.detectImages(time.Now()) err = manager.detectImages(time.Now())
require.Nil(t, err) require.NoError(t, err)
assert.Len(manager.imageRecords, 0) assert.Equal(manager.imageRecordsLen(), 0)
} }
func TestFreeSpaceImagesInUseContainersAreIgnored(t *testing.T) { func TestFreeSpaceImagesInUseContainersAreIgnored(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
}, }
ContainerList: []docker.APIContainers{ fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1), makeContainer(1),
},
RemovedImages: util.NewStringSet(),
} }
manager := newRealImageManager(fakeDocker)
spaceFreed, err := manager.FreeSpace(2048) spaceFreed, err := manager.freeSpace(2048)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Equal(1024, spaceFreed) assert.Equal(1024, spaceFreed)
assert.Len(fakeDocker.RemovedImages, 1) assert.Len(fakeDocker.RemovedImages, 1)
assert.True(fakeDocker.RemovedImages.Has(imageName(0))) assert.True(fakeDocker.RemovedImages.Has(imageName(0)))
} }
func TestFreeSpaceRemoveByLeastRecentlyUsed(t *testing.T) { func TestFreeSpaceRemoveByLeastRecentlyUsed(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{
makeImage(0, 1024),
makeImage(1, 2048),
},
ContainerList: []docker.APIContainers{
makeContainer(0),
makeContainer(1),
},
RemovedImages: util.NewStringSet(),
}
manager := newRealImageManager(fakeDocker)
// Make 1 be more recently used than 0.
require.Nil(t, manager.detectImages(zero))
fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1),
}
require.Nil(t, manager.detectImages(time.Now()))
fakeDocker.ContainerList = []docker.APIContainers{}
require.Nil(t, manager.detectImages(time.Now()))
require.Len(t, manager.imageRecords, 2)
spaceFreed, err := manager.FreeSpace(1024)
assert := assert.New(t)
require.Nil(t, err)
assert.Equal(1024, spaceFreed)
assert.Len(fakeDocker.RemovedImages, 1)
assert.True(fakeDocker.RemovedImages.Has(imageName(0)))
}
func TestFreeSpaceTiesBrokenByDetectedTime(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{
Images: []docker.APIImages{
makeImage(0, 1024),
},
ContainerList: []docker.APIContainers{
makeContainer(0),
},
RemovedImages: util.NewStringSet(),
}
manager := newRealImageManager(fakeDocker)
// Make 1 more recently detected but used at the same time as 0.
require.Nil(t, manager.detectImages(zero))
fakeDocker.Images = []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
makeImage(1, 2048), makeImage(1, 2048),
@ -259,43 +226,143 @@ func TestFreeSpaceTiesBrokenByDetectedTime(t *testing.T) {
makeContainer(0), makeContainer(0),
makeContainer(1), makeContainer(1),
} }
require.Nil(t, manager.detectImages(time.Now()))
fakeDocker.ContainerList = []docker.APIContainers{}
require.Nil(t, manager.detectImages(time.Now()))
require.Len(t, manager.imageRecords, 2)
spaceFreed, err := manager.FreeSpace(1024) // Make 1 be more recently used than 0.
require.NoError(t, manager.detectImages(zero))
fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(1),
}
require.NoError(t, manager.detectImages(time.Now()))
fakeDocker.ContainerList = []docker.APIContainers{}
require.NoError(t, manager.detectImages(time.Now()))
require.Equal(t, manager.imageRecordsLen(), 2)
spaceFreed, err := manager.freeSpace(1024)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Equal(1024, spaceFreed)
assert.Len(fakeDocker.RemovedImages, 1)
assert.True(fakeDocker.RemovedImages.Has(imageName(0)))
}
func TestFreeSpaceTiesBrokenByDetectedTime(t *testing.T) {
manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024),
}
fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(0),
}
// Make 1 more recently detected but used at the same time as 0.
require.NoError(t, manager.detectImages(zero))
fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024),
makeImage(1, 2048),
}
fakeDocker.ContainerList = []docker.APIContainers{
makeContainer(0),
makeContainer(1),
}
require.NoError(t, manager.detectImages(time.Now()))
fakeDocker.ContainerList = []docker.APIContainers{}
require.NoError(t, manager.detectImages(time.Now()))
require.Equal(t, manager.imageRecordsLen(), 2)
spaceFreed, err := manager.freeSpace(1024)
assert := assert.New(t)
require.NoError(t, err)
assert.Equal(1024, spaceFreed) assert.Equal(1024, spaceFreed)
assert.Len(fakeDocker.RemovedImages, 1) assert.Len(fakeDocker.RemovedImages, 1)
assert.True(fakeDocker.RemovedImages.Has(imageName(0))) assert.True(fakeDocker.RemovedImages.Has(imageName(0)))
} }
func TestFreeSpaceImagesAlsoDoesLookupByRepoTags(t *testing.T) { func TestFreeSpaceImagesAlsoDoesLookupByRepoTags(t *testing.T) {
fakeDocker := &dockertools.FakeDockerClient{ manager, fakeDocker, _ := newRealImageManager(t, ImageGCPolicy{})
Images: []docker.APIImages{ fakeDocker.Images = []docker.APIImages{
makeImage(0, 1024), makeImage(0, 1024),
{ {
ID: "5678", ID: "5678",
RepoTags: []string{"potato", "salad"}, RepoTags: []string{"potato", "salad"},
VirtualSize: 2048, VirtualSize: 2048,
}, },
}
fakeDocker.ContainerList = []docker.APIContainers{
{
ID: "c5678",
Image: "salad",
}, },
ContainerList: []docker.APIContainers{
{
ID: "c5678",
Image: "salad",
},
},
RemovedImages: util.NewStringSet(),
} }
manager := newRealImageManager(fakeDocker)
spaceFreed, err := manager.FreeSpace(1024) spaceFreed, err := manager.freeSpace(1024)
assert := assert.New(t) assert := assert.New(t)
require.Nil(t, err) require.NoError(t, err)
assert.Equal(1024, spaceFreed) assert.Equal(1024, spaceFreed)
assert.Len(fakeDocker.RemovedImages, 1) assert.Len(fakeDocker.RemovedImages, 1)
assert.True(fakeDocker.RemovedImages.Has(imageName(0))) assert.True(fakeDocker.RemovedImages.Has(imageName(0)))
} }
func TestGarbageCollectBelowLowThreshold(t *testing.T) {
policy := ImageGCPolicy{
HighThresholdPercent: 90,
LowThresholdPercent: 80,
}
manager, _, mockCadvisor := newRealImageManager(t, policy)
// Expect 40% usage.
mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorApiV2.FsInfo{
Usage: 400,
Capacity: 1000,
}, nil)
assert.NoError(t, manager.GarbageCollect())
}
func TestGarbageCollectCadvisorFailure(t *testing.T) {
policy := ImageGCPolicy{
HighThresholdPercent: 90,
LowThresholdPercent: 80,
}
manager, _, mockCadvisor := newRealImageManager(t, policy)
mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorApiV2.FsInfo{}, fmt.Errorf("error"))
assert.NotNil(t, manager.GarbageCollect())
}
func TestGarbageCollectBelowSuccess(t *testing.T) {
policy := ImageGCPolicy{
HighThresholdPercent: 90,
LowThresholdPercent: 80,
}
manager, fakeDocker, mockCadvisor := newRealImageManager(t, policy)
// Expect 95% usage and most of it gets freed.
mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorApiV2.FsInfo{
Usage: 950,
Capacity: 1000,
}, nil)
fakeDocker.Images = []docker.APIImages{
makeImage(0, 450),
}
assert.NoError(t, manager.GarbageCollect())
}
func TestGarbageCollectNotEnoughFreed(t *testing.T) {
policy := ImageGCPolicy{
HighThresholdPercent: 90,
LowThresholdPercent: 80,
}
manager, fakeDocker, mockCadvisor := newRealImageManager(t, policy)
// Expect 95% usage and little of it gets freed.
mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorApiV2.FsInfo{
Usage: 950,
Capacity: 1000,
}, nil)
fakeDocker.Images = []docker.APIImages{
makeImage(0, 50),
}
assert.NotNil(t, manager.GarbageCollect())
}