mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-02 08:17:26 +00:00
Merge pull request #2022 from brendandburns/gc
Add container garbage collection.
This commit is contained in:
commit
f6db096741
@ -67,6 +67,8 @@ var (
|
|||||||
registryBurst = flag.Int("registry_burst", 10, "Maximum size of a bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry_qps. Only used if --registry_qps > 0")
|
registryBurst = flag.Int("registry_burst", 10, "Maximum size of a bursty pulls, temporarily allows pulls to burst to this number, while still not exceeding registry_qps. Only used if --registry_qps > 0")
|
||||||
runonce = flag.Bool("runonce", false, "If true, exit after spawning pods from local manifests or remote urls. Exclusive with --etcd_servers and --enable-server")
|
runonce = flag.Bool("runonce", false, "If true, exit after spawning pods from local manifests or remote urls. Exclusive with --etcd_servers and --enable-server")
|
||||||
enableDebuggingHandlers = flag.Bool("enable_debugging_handlers", true, "Enables server endpoints for log collection and local running of containers and commands")
|
enableDebuggingHandlers = flag.Bool("enable_debugging_handlers", true, "Enables server endpoints for log collection and local running of containers and commands")
|
||||||
|
minimumGCAge = flag.Duration("minimum_container_ttl_duration", 0, "Minimum age for a finished container before it is garbage collected. Examples: '300ms', '10s' or '2h45m'")
|
||||||
|
maxContainerCount = flag.Int("maximum_dead_containers_per_container", 5, "Maximum number of old instances of a container to retain per container. Each container takes up some disk space. Default: 5.")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -183,7 +185,17 @@ func main() {
|
|||||||
*networkContainerImage,
|
*networkContainerImage,
|
||||||
*syncFrequency,
|
*syncFrequency,
|
||||||
float32(*registryPullQPS),
|
float32(*registryPullQPS),
|
||||||
*registryBurst)
|
*registryBurst,
|
||||||
|
*minimumGCAge,
|
||||||
|
*maxContainerCount)
|
||||||
|
go func() {
|
||||||
|
util.Forever(func() {
|
||||||
|
err := k.GarbageCollectContainers()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Garbage collect failed: %v", err)
|
||||||
|
}
|
||||||
|
}, time.Minute*1)
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer util.HandleCrash()
|
defer util.HandleCrash()
|
||||||
|
@ -372,8 +372,10 @@ type ContainerState struct {
|
|||||||
type ContainerStatus struct {
|
type ContainerStatus struct {
|
||||||
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
||||||
// defined for container?
|
// defined for container?
|
||||||
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
// Note that this is calculated from dead containers. But those containers are subject to
|
||||||
|
// garbage collection. This value will get capped at 5 by GC.
|
||||||
|
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
||||||
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
||||||
// not just PodInfo. Now we need this to remove docker.Container from API
|
// not just PodInfo. Now we need this to remove docker.Container from API
|
||||||
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
||||||
|
@ -345,8 +345,10 @@ type ContainerState struct {
|
|||||||
type ContainerStatus struct {
|
type ContainerStatus struct {
|
||||||
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
||||||
// defined for container?
|
// defined for container?
|
||||||
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
// Note that this is calculated from dead containers. But those containers are subject to
|
||||||
|
// garbage collection. This value will get capped at 5 by GC.
|
||||||
|
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
||||||
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
||||||
// not just PodInfo. Now we need this to remove docker.Container from API
|
// not just PodInfo. Now we need this to remove docker.Container from API
|
||||||
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
||||||
|
@ -310,8 +310,10 @@ type ContainerState struct {
|
|||||||
type ContainerStatus struct {
|
type ContainerStatus struct {
|
||||||
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
||||||
// defined for container?
|
// defined for container?
|
||||||
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
// Note that this is calculated from dead containers. But those containers are subject to
|
||||||
|
// garbage collection. This value will get capped at 5 by GC.
|
||||||
|
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
||||||
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
// TODO(dchen1107): Deprecated this soon once we pull entire PodStatus from node,
|
||||||
// not just PodInfo. Now we need this to remove docker.Container from API
|
// not just PodInfo. Now we need this to remove docker.Container from API
|
||||||
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
PodIP string `json:"podIP,omitempty" yaml:"podIP,omitempty"`
|
||||||
|
@ -409,8 +409,10 @@ type ContainerState struct {
|
|||||||
type ContainerStatus struct {
|
type ContainerStatus struct {
|
||||||
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
// TODO(dchen1107): Should we rename PodStatus to a more generic name or have a separate states
|
||||||
// defined for container?
|
// defined for container?
|
||||||
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
State ContainerState `json:"state,omitempty" yaml:"state,omitempty"`
|
||||||
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
// Note that this is calculated from dead containers. But those containers are subject to
|
||||||
|
// garbage collection. This value will get capped at 5 by GC.
|
||||||
|
RestartCount int `json:"restartCount" yaml:"restartCount"`
|
||||||
// TODO(dchen1107): Introduce our own NetworkSettings struct here?
|
// TODO(dchen1107): Introduce our own NetworkSettings struct here?
|
||||||
// TODO(dchen1107): Which image the container is running with?
|
// TODO(dchen1107): Which image the container is running with?
|
||||||
// TODO(dchen1107): Once we have done with integration with cadvisor, resource
|
// TODO(dchen1107): Once we have done with integration with cadvisor, resource
|
||||||
|
@ -50,6 +50,7 @@ type DockerInterface interface {
|
|||||||
CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
|
CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
|
||||||
StartContainer(id string, hostConfig *docker.HostConfig) error
|
StartContainer(id string, hostConfig *docker.HostConfig) error
|
||||||
StopContainer(id string, timeout uint) error
|
StopContainer(id string, timeout uint) error
|
||||||
|
RemoveContainer(opts docker.RemoveContainerOptions) error
|
||||||
InspectImage(image string) (*docker.Image, error)
|
InspectImage(image string) (*docker.Image, error)
|
||||||
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
|
||||||
Logs(opts docker.LogsOptions) error
|
Logs(opts docker.LogsOptions) error
|
||||||
|
@ -29,12 +29,14 @@ type FakeDockerClient struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
ContainerList []docker.APIContainers
|
ContainerList []docker.APIContainers
|
||||||
Container *docker.Container
|
Container *docker.Container
|
||||||
|
ContainerMap map[string]*docker.Container
|
||||||
Image *docker.Image
|
Image *docker.Image
|
||||||
Err error
|
Err error
|
||||||
called []string
|
called []string
|
||||||
Stopped []string
|
Stopped []string
|
||||||
pulled []string
|
pulled []string
|
||||||
Created []string
|
Created []string
|
||||||
|
Removed []string
|
||||||
VersionInfo docker.Env
|
VersionInfo docker.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +72,11 @@ func (f *FakeDockerClient) InspectContainer(id string) (*docker.Container, error
|
|||||||
f.Lock()
|
f.Lock()
|
||||||
defer f.Unlock()
|
defer f.Unlock()
|
||||||
f.called = append(f.called, "inspect_container")
|
f.called = append(f.called, "inspect_container")
|
||||||
|
if f.ContainerMap != nil {
|
||||||
|
if container, ok := f.ContainerMap[id]; ok {
|
||||||
|
return container, f.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
return f.Container, f.Err
|
return f.Container, f.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +129,14 @@ func (f *FakeDockerClient) StopContainer(id string, timeout uint) error {
|
|||||||
return f.Err
|
return f.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error {
|
||||||
|
f.Lock()
|
||||||
|
defer f.Unlock()
|
||||||
|
f.called = append(f.called, "remove")
|
||||||
|
f.Removed = append(f.Removed, opts.ID)
|
||||||
|
return f.Err
|
||||||
|
}
|
||||||
|
|
||||||
// Logs is a test-spy implementation of DockerInterface.Logs.
|
// Logs is a test-spy implementation of DockerInterface.Logs.
|
||||||
// It adds an entry "logs" to the internal method call record.
|
// It adds an entry "logs" to the internal method call record.
|
||||||
func (f *FakeDockerClient) Logs(opts docker.LogsOptions) error {
|
func (f *FakeDockerClient) Logs(opts docker.LogsOptions) error {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -61,7 +62,9 @@ func NewMainKubelet(
|
|||||||
ni string,
|
ni string,
|
||||||
ri time.Duration,
|
ri time.Duration,
|
||||||
pullQPS float32,
|
pullQPS float32,
|
||||||
pullBurst int) *Kubelet {
|
pullBurst int,
|
||||||
|
minimumGCAge time.Duration,
|
||||||
|
maxContainerCount int) *Kubelet {
|
||||||
return &Kubelet{
|
return &Kubelet{
|
||||||
hostname: hn,
|
hostname: hn,
|
||||||
dockerClient: dc,
|
dockerClient: dc,
|
||||||
@ -74,6 +77,8 @@ func NewMainKubelet(
|
|||||||
httpClient: &http.Client{},
|
httpClient: &http.Client{},
|
||||||
pullQPS: pullQPS,
|
pullQPS: pullQPS,
|
||||||
pullBurst: pullBurst,
|
pullBurst: pullBurst,
|
||||||
|
minimumGCAge: minimumGCAge,
|
||||||
|
maxContainerCount: maxContainerCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +130,68 @@ type Kubelet struct {
|
|||||||
// Optional, no statistics will be available if omitted
|
// Optional, no statistics will be available if omitted
|
||||||
cadvisorClient cadvisorInterface
|
cadvisorClient cadvisorInterface
|
||||||
cadvisorLock sync.RWMutex
|
cadvisorLock sync.RWMutex
|
||||||
|
|
||||||
|
// Optional, minimum age required for garbage collection. If zero, no limit.
|
||||||
|
minimumGCAge time.Duration
|
||||||
|
maxContainerCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ByCreated []*docker.Container
|
||||||
|
|
||||||
|
func (a ByCreated) Len() int { return len(a) }
|
||||||
|
func (a ByCreated) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByCreated) Less(i, j int) bool { return a[i].Created.After(a[j].Created) }
|
||||||
|
|
||||||
|
// TODO: these removals are racy, we should make dockerclient threadsafe across List/Inspect transactions.
|
||||||
|
func (kl *Kubelet) purgeOldest(ids []string) error {
|
||||||
|
dockerData := []*docker.Container{}
|
||||||
|
for _, id := range ids {
|
||||||
|
data, err := kl.dockerClient.InspectContainer(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !data.State.Running && (kl.minimumGCAge == 0 || time.Now().Sub(data.State.FinishedAt) > kl.minimumGCAge) {
|
||||||
|
dockerData = append(dockerData, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(ByCreated(dockerData))
|
||||||
|
if len(dockerData) <= kl.maxContainerCount {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dockerData = dockerData[kl.maxContainerCount:]
|
||||||
|
for _, data := range dockerData {
|
||||||
|
if err := kl.dockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: data.ID}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Also enforce a maximum total number of containers.
|
||||||
|
func (kl *Kubelet) GarbageCollectContainers() error {
|
||||||
|
if kl.maxContainerCount == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
containers, err := dockertools.GetKubeletDockerContainers(kl.dockerClient, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uuidToIDMap := map[string][]string{}
|
||||||
|
for _, container := range containers {
|
||||||
|
_, uuid, name, _ := dockertools.ParseDockerName(container.ID)
|
||||||
|
uuidName := uuid + "." + name
|
||||||
|
uuidToIDMap[uuidName] = append(uuidToIDMap[uuidName], container.ID)
|
||||||
|
}
|
||||||
|
for _, list := range uuidToIDMap {
|
||||||
|
if len(list) <= kl.maxContainerCount {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := kl.purgeOldest(list); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCadvisorClient sets the cadvisor client in a thread-safe way.
|
// SetCadvisorClient sets the cadvisor client in a thread-safe way.
|
||||||
|
@ -1193,3 +1193,297 @@ func TestSyncPodEventHandlerFails(t *testing.T) {
|
|||||||
t.Errorf("Wrong containers were stopped: %v", fakeDocker.Stopped)
|
t.Errorf("Wrong containers were stopped: %v", fakeDocker.Stopped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKubeletGarbageCollection(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
containers []docker.APIContainers
|
||||||
|
containerDetails map[string]*docker.Container
|
||||||
|
expectedRemoved []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
containers: []docker.APIContainers{
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "1876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "2876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "3876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "4876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "5876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "6876",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
containerDetails: map[string]*docker.Container{
|
||||||
|
"1876": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "1876",
|
||||||
|
Created: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRemoved: []string{"1876"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
containers: []docker.APIContainers{
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "1876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "2876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "3876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "4876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "5876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "6876",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "7876",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
containerDetails: map[string]*docker.Container{
|
||||||
|
"1876": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: true,
|
||||||
|
},
|
||||||
|
ID: "1876",
|
||||||
|
Created: time.Now(),
|
||||||
|
},
|
||||||
|
"2876": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "2876",
|
||||||
|
Created: time.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRemoved: []string{"2876"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
containers: []docker.APIContainers{
|
||||||
|
{
|
||||||
|
// network container
|
||||||
|
Names: []string{"/k8s_net_foo.new.test_.deadbeef"},
|
||||||
|
ID: "1876",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
kubelet, _, fakeDocker := newTestKubelet(t)
|
||||||
|
kubelet.maxContainerCount = 5
|
||||||
|
fakeDocker.ContainerList = test.containers
|
||||||
|
fakeDocker.ContainerMap = test.containerDetails
|
||||||
|
fakeDocker.Container = &docker.Container{ID: "error", Created: time.Now()}
|
||||||
|
err := kubelet.GarbageCollectContainers()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(fakeDocker.Removed, test.expectedRemoved) {
|
||||||
|
t.Errorf("expected: %v, got: %v", test.expectedRemoved, fakeDocker.Removed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPurgeOldest(t *testing.T) {
|
||||||
|
created := time.Now()
|
||||||
|
tests := []struct {
|
||||||
|
ids []string
|
||||||
|
containerDetails map[string]*docker.Container
|
||||||
|
expectedRemoved []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ids: []string{"1", "2", "3", "4", "5"},
|
||||||
|
containerDetails: map[string]*docker.Container{
|
||||||
|
"1": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: true,
|
||||||
|
},
|
||||||
|
ID: "1",
|
||||||
|
Created: created,
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "2",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "3",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "4",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "5",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ids: []string{"1", "2", "3", "4", "5", "6"},
|
||||||
|
containerDetails: map[string]*docker.Container{
|
||||||
|
"1": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "1",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "2",
|
||||||
|
Created: created.Add(time.Millisecond),
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "3",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "4",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "5",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "6",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRemoved: []string{"2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ids: []string{"1", "2", "3", "4", "5", "6", "7"},
|
||||||
|
containerDetails: map[string]*docker.Container{
|
||||||
|
"1": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "1",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "2",
|
||||||
|
Created: created.Add(time.Millisecond),
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "3",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "4",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "5",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "6",
|
||||||
|
Created: created.Add(time.Microsecond),
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
State: docker.State{
|
||||||
|
Running: false,
|
||||||
|
},
|
||||||
|
ID: "7",
|
||||||
|
Created: created.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedRemoved: []string{"2", "6"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
kubelet, _, fakeDocker := newTestKubelet(t)
|
||||||
|
kubelet.maxContainerCount = 5
|
||||||
|
fakeDocker.ContainerMap = test.containerDetails
|
||||||
|
kubelet.purgeOldest(test.ids)
|
||||||
|
if !reflect.DeepEqual(fakeDocker.Removed, test.expectedRemoved) {
|
||||||
|
t.Errorf("expected: %v, got: %v", test.expectedRemoved, fakeDocker.Removed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user