1
16
17 package e2enode
18
19 import (
20 "context"
21 "time"
22
23 v1 "k8s.io/api/core/v1"
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 internalapi "k8s.io/cri-api/pkg/apis"
26 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
27 kubefeatures "k8s.io/kubernetes/pkg/features"
28 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
29 "k8s.io/kubernetes/test/e2e/framework"
30 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
31 admissionapi "k8s.io/pod-security-admission/api"
32
33 "github.com/onsi/ginkgo/v2"
34 "github.com/onsi/gomega"
35 )
36
37 const (
38
39
40 checkGCUntil time.Duration = 6 * time.Minute
41 checkGCFreq time.Duration = 30 * time.Second
42 )
43
44 var _ = SIGDescribe("ImageGarbageCollect", framework.WithSerial(), framework.WithNodeFeature("GarbageCollect"), func() {
45 f := framework.NewDefaultFramework("image-garbage-collect-test")
46 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
47 var is internalapi.ImageManagerService
48 ginkgo.BeforeEach(func() {
49 var err error
50 _, is, err = getCRIClient()
51 framework.ExpectNoError(err)
52 })
53 ginkgo.AfterEach(func() {
54 framework.ExpectNoError(PrePullAllImages())
55 })
56 ginkgo.Context("when ImageMaximumGCAge is set", func() {
57 tempSetCurrentKubeletConfig(f, func(ctx context.Context, initialConfig *kubeletconfig.KubeletConfiguration) {
58 initialConfig.ImageMaximumGCAge = metav1.Duration{Duration: time.Duration(time.Minute * 1)}
59 initialConfig.ImageMinimumGCAge = metav1.Duration{Duration: time.Duration(time.Second * 1)}
60 if initialConfig.FeatureGates == nil {
61 initialConfig.FeatureGates = make(map[string]bool)
62 }
63 initialConfig.FeatureGates[string(kubefeatures.ImageMaximumGCAge)] = true
64 })
65 ginkgo.It("should GC unused images", func(ctx context.Context) {
66 pod := innocentPod()
67 e2epod.NewPodClient(f).CreateBatch(ctx, []*v1.Pod{pod})
68
69 _, err := is.PullImage(context.Background(), &runtimeapi.ImageSpec{Image: agnhostImage}, nil, nil)
70 framework.ExpectNoError(err)
71
72 allImages, err := is.ListImages(context.Background(), &runtimeapi.ImageFilter{})
73 framework.ExpectNoError(err)
74
75 e2epod.NewPodClient(f).DeleteSync(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
76
77
78
79 gomega.Eventually(ctx, func() int {
80 gcdImageList, err := is.ListImages(context.Background(), &runtimeapi.ImageFilter{})
81 framework.ExpectNoError(err)
82 return len(gcdImageList)
83 }, checkGCUntil, checkGCFreq).Should(gomega.BeNumerically("<", len(allImages)))
84 })
85 ginkgo.It("should not GC unused images prematurely", func(ctx context.Context) {
86 pod := innocentPod()
87 e2epod.NewPodClient(f).CreateBatch(ctx, []*v1.Pod{pod})
88
89 _, err := is.PullImage(context.Background(), &runtimeapi.ImageSpec{Image: agnhostImage}, nil, nil)
90 framework.ExpectNoError(err)
91
92 allImages, err := is.ListImages(context.Background(), &runtimeapi.ImageFilter{})
93 framework.ExpectNoError(err)
94
95 e2epod.NewPodClient(f).DeleteSync(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{}, e2epod.DefaultPodDeletionTimeout)
96
97 restartKubelet(true)
98 waitForKubeletToStart(ctx, f)
99
100
101
102 gomega.Consistently(ctx, func() int {
103 gcdImageList, err := is.ListImages(context.Background(), &runtimeapi.ImageFilter{})
104 framework.ExpectNoError(err)
105 return len(gcdImageList)
106 }, 50*time.Second, 10*time.Second).Should(gomega.Equal(len(allImages)))
107
108
109
110 gomega.Eventually(ctx, func() int {
111 gcdImageList, err := is.ListImages(context.Background(), &runtimeapi.ImageFilter{})
112 framework.ExpectNoError(err)
113 return len(gcdImageList)
114 }, checkGCUntil, checkGCFreq).Should(gomega.BeNumerically("<", len(allImages)))
115 })
116 })
117 })
118
View as plain text