1
16
17 package deployment
18
19 import (
20 "context"
21 "testing"
22
23 apps "k8s.io/api/apps/v1"
24 "k8s.io/apimachinery/pkg/util/intstr"
25 "k8s.io/client-go/kubernetes/fake"
26 core "k8s.io/client-go/testing"
27 "k8s.io/client-go/tools/record"
28 "k8s.io/klog/v2/ktesting"
29 )
30
31 func TestDeploymentController_reconcileNewReplicaSet(t *testing.T) {
32 tests := []struct {
33 deploymentReplicas int32
34 maxSurge intstr.IntOrString
35 oldReplicas int32
36 newReplicas int32
37 scaleExpected bool
38 expectedNewReplicas int32
39 }{
40 {
41
42 deploymentReplicas: 10,
43 maxSurge: intstr.FromInt32(0),
44 oldReplicas: 10,
45 newReplicas: 0,
46 scaleExpected: false,
47 },
48 {
49 deploymentReplicas: 10,
50 maxSurge: intstr.FromInt32(2),
51 oldReplicas: 10,
52 newReplicas: 0,
53 scaleExpected: true,
54 expectedNewReplicas: 2,
55 },
56 {
57 deploymentReplicas: 10,
58 maxSurge: intstr.FromInt32(2),
59 oldReplicas: 5,
60 newReplicas: 0,
61 scaleExpected: true,
62 expectedNewReplicas: 7,
63 },
64 {
65 deploymentReplicas: 10,
66 maxSurge: intstr.FromInt32(2),
67 oldReplicas: 10,
68 newReplicas: 2,
69 scaleExpected: false,
70 },
71 {
72
73 deploymentReplicas: 10,
74 maxSurge: intstr.FromInt32(2),
75 oldReplicas: 2,
76 newReplicas: 11,
77 scaleExpected: true,
78 expectedNewReplicas: 10,
79 },
80 }
81
82 for i := range tests {
83 test := tests[i]
84 t.Logf("executing scenario %d", i)
85 newRS := rs("foo-v2", test.newReplicas, nil, noTimestamp)
86 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
87 allRSs := []*apps.ReplicaSet{newRS, oldRS}
88 maxUnavailable := intstr.FromInt32(0)
89 deployment := newDeployment("foo", test.deploymentReplicas, nil, &test.maxSurge, &maxUnavailable, map[string]string{"foo": "bar"})
90 fake := fake.Clientset{}
91 controller := &DeploymentController{
92 client: &fake,
93 eventRecorder: &record.FakeRecorder{},
94 }
95 _, ctx := ktesting.NewTestContext(t)
96 ctx, cancel := context.WithCancel(ctx)
97 defer cancel()
98 scaled, err := controller.reconcileNewReplicaSet(ctx, allRSs, newRS, deployment)
99 if err != nil {
100 t.Errorf("unexpected error: %v", err)
101 continue
102 }
103 if !test.scaleExpected {
104 if scaled || len(fake.Actions()) > 0 {
105 t.Errorf("unexpected scaling: %v", fake.Actions())
106 }
107 continue
108 }
109 if test.scaleExpected && !scaled {
110 t.Errorf("expected scaling to occur")
111 continue
112 }
113 if len(fake.Actions()) != 1 {
114 t.Errorf("expected 1 action during scale, got: %v", fake.Actions())
115 continue
116 }
117 updated := fake.Actions()[0].(core.UpdateAction).GetObject().(*apps.ReplicaSet)
118 if e, a := test.expectedNewReplicas, *(updated.Spec.Replicas); e != a {
119 t.Errorf("expected update to %d replicas, got %d", e, a)
120 }
121 }
122 }
123
124 func TestDeploymentController_reconcileOldReplicaSets(t *testing.T) {
125 tests := []struct {
126 deploymentReplicas int32
127 maxUnavailable intstr.IntOrString
128 oldReplicas int32
129 newReplicas int32
130 readyPodsFromOldRS int
131 readyPodsFromNewRS int
132 scaleExpected bool
133 expectedOldReplicas int32
134 }{
135 {
136 deploymentReplicas: 10,
137 maxUnavailable: intstr.FromInt32(0),
138 oldReplicas: 10,
139 newReplicas: 0,
140 readyPodsFromOldRS: 10,
141 readyPodsFromNewRS: 0,
142 scaleExpected: true,
143 expectedOldReplicas: 9,
144 },
145 {
146 deploymentReplicas: 10,
147 maxUnavailable: intstr.FromInt32(2),
148 oldReplicas: 10,
149 newReplicas: 0,
150 readyPodsFromOldRS: 10,
151 readyPodsFromNewRS: 0,
152 scaleExpected: true,
153 expectedOldReplicas: 8,
154 },
155 {
156 deploymentReplicas: 10,
157 maxUnavailable: intstr.FromInt32(2),
158 oldReplicas: 10,
159 newReplicas: 0,
160 readyPodsFromOldRS: 8,
161 readyPodsFromNewRS: 0,
162 scaleExpected: true,
163 expectedOldReplicas: 8,
164 },
165 {
166 deploymentReplicas: 10,
167 maxUnavailable: intstr.FromInt32(2),
168 oldReplicas: 10,
169 newReplicas: 0,
170 readyPodsFromOldRS: 9,
171 readyPodsFromNewRS: 0,
172 scaleExpected: true,
173 expectedOldReplicas: 8,
174 },
175 {
176 deploymentReplicas: 10,
177 maxUnavailable: intstr.FromInt32(2),
178 oldReplicas: 8,
179 newReplicas: 2,
180 readyPodsFromOldRS: 8,
181 readyPodsFromNewRS: 0,
182 scaleExpected: false,
183 },
184 }
185 for i := range tests {
186 test := tests[i]
187 t.Logf("executing scenario %d", i)
188
189 newSelector := map[string]string{"foo": "new"}
190 oldSelector := map[string]string{"foo": "old"}
191 newRS := rs("foo-new", test.newReplicas, newSelector, noTimestamp)
192 newRS.Status.AvailableReplicas = int32(test.readyPodsFromNewRS)
193 oldRS := rs("foo-old", test.oldReplicas, oldSelector, noTimestamp)
194 oldRS.Status.AvailableReplicas = int32(test.readyPodsFromOldRS)
195 oldRSs := []*apps.ReplicaSet{oldRS}
196 allRSs := []*apps.ReplicaSet{oldRS, newRS}
197 maxSurge := intstr.FromInt32(0)
198 deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, newSelector)
199 fakeClientset := fake.Clientset{}
200 controller := &DeploymentController{
201 client: &fakeClientset,
202 eventRecorder: &record.FakeRecorder{},
203 }
204 _, ctx := ktesting.NewTestContext(t)
205 scaled, err := controller.reconcileOldReplicaSets(ctx, allRSs, oldRSs, newRS, deployment)
206 if err != nil {
207 t.Errorf("unexpected error: %v", err)
208 continue
209 }
210 if !test.scaleExpected && scaled {
211 t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
212 }
213 if test.scaleExpected && !scaled {
214 t.Errorf("expected scaling to occur")
215 continue
216 }
217 continue
218 }
219 }
220
221 func TestDeploymentController_cleanupUnhealthyReplicas(t *testing.T) {
222 tests := []struct {
223 oldReplicas int32
224 readyPods int
225 unHealthyPods int
226 maxCleanupCount int
227 cleanupCountExpected int
228 }{
229 {
230 oldReplicas: 10,
231 readyPods: 8,
232 unHealthyPods: 2,
233 maxCleanupCount: 1,
234 cleanupCountExpected: 1,
235 },
236 {
237 oldReplicas: 10,
238 readyPods: 8,
239 unHealthyPods: 2,
240 maxCleanupCount: 3,
241 cleanupCountExpected: 2,
242 },
243 {
244 oldReplicas: 10,
245 readyPods: 8,
246 unHealthyPods: 2,
247 maxCleanupCount: 0,
248 cleanupCountExpected: 0,
249 },
250 {
251 oldReplicas: 10,
252 readyPods: 10,
253 unHealthyPods: 0,
254 maxCleanupCount: 3,
255 cleanupCountExpected: 0,
256 },
257 }
258
259 for i, test := range tests {
260 t.Logf("executing scenario %d", i)
261 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
262 oldRS.Status.AvailableReplicas = int32(test.readyPods)
263 oldRSs := []*apps.ReplicaSet{oldRS}
264 maxSurge := intstr.FromInt32(2)
265 maxUnavailable := intstr.FromInt32(2)
266 deployment := newDeployment("foo", 10, nil, &maxSurge, &maxUnavailable, nil)
267 fakeClientset := fake.Clientset{}
268
269 controller := &DeploymentController{
270 client: &fakeClientset,
271 eventRecorder: &record.FakeRecorder{},
272 }
273 _, ctx := ktesting.NewTestContext(t)
274 _, cleanupCount, err := controller.cleanupUnhealthyReplicas(ctx, oldRSs, deployment, int32(test.maxCleanupCount))
275 if err != nil {
276 t.Errorf("unexpected error: %v", err)
277 continue
278 }
279 if int(cleanupCount) != test.cleanupCountExpected {
280 t.Errorf("expected %v unhealthy replicas been cleaned up, got %v", test.cleanupCountExpected, cleanupCount)
281 continue
282 }
283 }
284 }
285
286 func TestDeploymentController_scaleDownOldReplicaSetsForRollingUpdate(t *testing.T) {
287 tests := []struct {
288 deploymentReplicas int32
289 maxUnavailable intstr.IntOrString
290 readyPods int
291 oldReplicas int32
292 scaleExpected bool
293 expectedOldReplicas int32
294 }{
295 {
296 deploymentReplicas: 10,
297 maxUnavailable: intstr.FromInt32(0),
298 readyPods: 10,
299 oldReplicas: 10,
300 scaleExpected: true,
301 expectedOldReplicas: 9,
302 },
303 {
304 deploymentReplicas: 10,
305 maxUnavailable: intstr.FromInt32(2),
306 readyPods: 10,
307 oldReplicas: 10,
308 scaleExpected: true,
309 expectedOldReplicas: 8,
310 },
311 {
312 deploymentReplicas: 10,
313 maxUnavailable: intstr.FromInt32(2),
314 readyPods: 8,
315 oldReplicas: 10,
316 scaleExpected: false,
317 },
318 {
319 deploymentReplicas: 10,
320 maxUnavailable: intstr.FromInt32(2),
321 readyPods: 10,
322 oldReplicas: 0,
323 scaleExpected: false,
324 },
325 {
326 deploymentReplicas: 10,
327 maxUnavailable: intstr.FromInt32(2),
328 readyPods: 1,
329 oldReplicas: 10,
330 scaleExpected: false,
331 },
332 }
333
334 for i := range tests {
335 test := tests[i]
336 t.Logf("executing scenario %d", i)
337 oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
338 oldRS.Status.AvailableReplicas = int32(test.readyPods)
339 allRSs := []*apps.ReplicaSet{oldRS}
340 oldRSs := []*apps.ReplicaSet{oldRS}
341 maxSurge := intstr.FromInt32(0)
342 deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, map[string]string{"foo": "bar"})
343 fakeClientset := fake.Clientset{}
344 controller := &DeploymentController{
345 client: &fakeClientset,
346 eventRecorder: &record.FakeRecorder{},
347 }
348 _, ctx := ktesting.NewTestContext(t)
349 scaled, err := controller.scaleDownOldReplicaSetsForRollingUpdate(ctx, allRSs, oldRSs, deployment)
350 if err != nil {
351 t.Errorf("unexpected error: %v", err)
352 continue
353 }
354 if !test.scaleExpected {
355 if scaled != 0 {
356 t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
357 }
358 continue
359 }
360 if test.scaleExpected && scaled == 0 {
361 t.Errorf("expected scaling to occur; actions: %v", fakeClientset.Actions())
362 continue
363 }
364
365
366 var updateAction core.UpdateAction
367 for _, action := range fakeClientset.Actions() {
368 switch a := action.(type) {
369 case core.UpdateAction:
370 if updateAction != nil {
371 t.Errorf("expected only 1 update action; had %v and found %v", updateAction, a)
372 } else {
373 updateAction = a
374 }
375 }
376 }
377 if updateAction == nil {
378 t.Errorf("expected an update action")
379 continue
380 }
381 updated := updateAction.GetObject().(*apps.ReplicaSet)
382 if e, a := test.expectedOldReplicas, *(updated.Spec.Replicas); e != a {
383 t.Errorf("expected update to %d replicas, got %d", e, a)
384 }
385 }
386 }
387
View as plain text