1
16
17 package ttl
18
19 import (
20 "context"
21 "testing"
22
23 "k8s.io/api/core/v1"
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 "k8s.io/client-go/kubernetes/fake"
26 listers "k8s.io/client-go/listers/core/v1"
27 core "k8s.io/client-go/testing"
28 "k8s.io/client-go/tools/cache"
29 "k8s.io/client-go/util/workqueue"
30 "k8s.io/klog/v2/ktesting"
31
32 "github.com/stretchr/testify/assert"
33 )
34
35 func TestPatchNode(t *testing.T) {
36 testCases := []struct {
37 node *v1.Node
38 ttlSeconds int
39 patch string
40 }{
41 {
42 node: &v1.Node{},
43 ttlSeconds: 0,
44 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"0\"}}}",
45 },
46 {
47 node: &v1.Node{},
48 ttlSeconds: 10,
49 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"10\"}}}",
50 },
51 {
52 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name"}},
53 ttlSeconds: 10,
54 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"10\"}}}",
55 },
56 {
57 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}},
58 ttlSeconds: 10,
59 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"10\"}}}",
60 },
61 {
62 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "0"}}},
63 ttlSeconds: 10,
64 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"10\"}}}",
65 },
66 {
67 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "0", "a": "b"}}},
68 ttlSeconds: 10,
69 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"10\"}}}",
70 },
71 {
72 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "10", "a": "b"}}},
73 ttlSeconds: 10,
74 patch: "{}",
75 },
76 }
77
78 for i, testCase := range testCases {
79 fakeClient := &fake.Clientset{}
80 ttlController := &Controller{
81 kubeClient: fakeClient,
82 }
83 err := ttlController.patchNodeWithAnnotation(context.TODO(), testCase.node, v1.ObjectTTLAnnotationKey, testCase.ttlSeconds)
84 if err != nil {
85 t.Errorf("%d: unexpected error: %v", i, err)
86 continue
87 }
88 actions := fakeClient.Actions()
89 assert.Equal(t, 1, len(actions), "unexpected actions: %#v", actions)
90 patchAction := actions[0].(core.PatchActionImpl)
91 assert.Equal(t, testCase.patch, string(patchAction.Patch), "%d: unexpected patch: %s", i, string(patchAction.Patch))
92 }
93 }
94
95 func TestUpdateNodeIfNeeded(t *testing.T) {
96 testCases := []struct {
97 node *v1.Node
98 desiredTTL int
99 patch string
100 }{
101 {
102 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name"}},
103 desiredTTL: 0,
104 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"0\"}}}",
105 },
106 {
107 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name"}},
108 desiredTTL: 15,
109 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"15\"}}}",
110 },
111 {
112 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name"}},
113 desiredTTL: 30,
114 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"30\"}}}",
115 },
116 {
117 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name", Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "0"}}},
118 desiredTTL: 60,
119 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"60\"}}}",
120 },
121 {
122 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name", Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "60"}}},
123 desiredTTL: 60,
124 patch: "",
125 },
126 {
127 node: &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "name", Annotations: map[string]string{"node.alpha.kubernetes.io/ttl": "60"}}},
128 desiredTTL: 30,
129 patch: "{\"metadata\":{\"annotations\":{\"node.alpha.kubernetes.io/ttl\":\"30\"}}}",
130 },
131 }
132
133 for i, testCase := range testCases {
134 fakeClient := &fake.Clientset{}
135 nodeStore := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
136 nodeStore.Add(testCase.node)
137 ttlController := &Controller{
138 kubeClient: fakeClient,
139 nodeStore: listers.NewNodeLister(nodeStore),
140 desiredTTLSeconds: testCase.desiredTTL,
141 }
142 if err := ttlController.updateNodeIfNeeded(context.TODO(), testCase.node.Name); err != nil {
143 t.Errorf("%d: unexpected error: %v", i, err)
144 continue
145 }
146 actions := fakeClient.Actions()
147 if testCase.patch == "" {
148 assert.Equal(t, 0, len(actions), "unexpected actions: %#v", actions)
149 } else {
150 assert.Equal(t, 1, len(actions), "unexpected actions: %#v", actions)
151 patchAction := actions[0].(core.PatchActionImpl)
152 assert.Equal(t, testCase.patch, string(patchAction.Patch), "%d: unexpected patch: %s", i, string(patchAction.Patch))
153 }
154 }
155 }
156
157 func TestDesiredTTL(t *testing.T) {
158 testCases := []struct {
159 addNode bool
160 deleteNode bool
161 nodeCount int
162 desiredTTL int
163 boundaryStep int
164 expectedTTL int
165 }{
166 {
167 addNode: true,
168 nodeCount: 0,
169 desiredTTL: 0,
170 boundaryStep: 0,
171 expectedTTL: 0,
172 },
173 {
174 addNode: true,
175 nodeCount: 99,
176 desiredTTL: 0,
177 boundaryStep: 0,
178 expectedTTL: 0,
179 },
180 {
181 addNode: true,
182 nodeCount: 100,
183 desiredTTL: 0,
184 boundaryStep: 0,
185 expectedTTL: 15,
186 },
187 {
188 deleteNode: true,
189 nodeCount: 101,
190 desiredTTL: 15,
191 boundaryStep: 1,
192 expectedTTL: 15,
193 },
194 {
195 deleteNode: true,
196 nodeCount: 91,
197 desiredTTL: 15,
198 boundaryStep: 1,
199 expectedTTL: 15,
200 },
201 {
202 addNode: true,
203 nodeCount: 91,
204 desiredTTL: 15,
205 boundaryStep: 1,
206 expectedTTL: 15,
207 },
208 {
209 deleteNode: true,
210 nodeCount: 90,
211 desiredTTL: 15,
212 boundaryStep: 1,
213 expectedTTL: 0,
214 },
215 {
216 deleteNode: true,
217 nodeCount: 1800,
218 desiredTTL: 300,
219 boundaryStep: 4,
220 expectedTTL: 60,
221 },
222 {
223 deleteNode: true,
224 nodeCount: 10000,
225 desiredTTL: 300,
226 boundaryStep: 4,
227 expectedTTL: 300,
228 },
229 }
230
231 for i, testCase := range testCases {
232 ttlController := &Controller{
233 queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
234 nodeCount: testCase.nodeCount,
235 desiredTTLSeconds: testCase.desiredTTL,
236 boundaryStep: testCase.boundaryStep,
237 }
238 if testCase.addNode {
239 logger, _ := ktesting.NewTestContext(t)
240 ttlController.addNode(logger, &v1.Node{})
241 }
242 if testCase.deleteNode {
243 ttlController.deleteNode(&v1.Node{})
244 }
245 assert.Equal(t, testCase.expectedTTL, ttlController.getDesiredTTLSeconds(),
246 "%d: unexpected ttl: %d", i, ttlController.getDesiredTTLSeconds())
247 }
248 }
249
View as plain text