1
16
17 package v1beta1_test
18
19 import (
20 "reflect"
21 "testing"
22
23 appsv1beta1 "k8s.io/api/apps/v1beta1"
24
25 v1 "k8s.io/api/core/v1"
26 apiequality "k8s.io/apimachinery/pkg/api/equality"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/util/intstr"
30 utilfeature "k8s.io/apiserver/pkg/util/feature"
31 featuregatetesting "k8s.io/component-base/featuregate/testing"
32 "k8s.io/kubernetes/pkg/api/legacyscheme"
33 _ "k8s.io/kubernetes/pkg/apis/apps/install"
34 . "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
35 _ "k8s.io/kubernetes/pkg/apis/core/install"
36 "k8s.io/kubernetes/pkg/features"
37 "k8s.io/utils/ptr"
38 )
39
40 func TestSetDefaultDeployment(t *testing.T) {
41 defaultIntOrString := intstr.FromString("25%")
42 differentIntOrString := intstr.FromInt32(5)
43 period := int64(v1.DefaultTerminationGracePeriodSeconds)
44 defaultTemplate := v1.PodTemplateSpec{
45 Spec: v1.PodSpec{
46 DNSPolicy: v1.DNSClusterFirst,
47 RestartPolicy: v1.RestartPolicyAlways,
48 SecurityContext: &v1.PodSecurityContext{},
49 TerminationGracePeriodSeconds: &period,
50 SchedulerName: v1.DefaultSchedulerName,
51 },
52 }
53 tests := []struct {
54 original *appsv1beta1.Deployment
55 expected *appsv1beta1.Deployment
56 }{
57 {
58 original: &appsv1beta1.Deployment{},
59 expected: &appsv1beta1.Deployment{
60 Spec: appsv1beta1.DeploymentSpec{
61 Replicas: ptr.To[int32](1),
62 Strategy: appsv1beta1.DeploymentStrategy{
63 Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
64 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{
65 MaxSurge: &defaultIntOrString,
66 MaxUnavailable: &defaultIntOrString,
67 },
68 },
69 RevisionHistoryLimit: ptr.To[int32](2),
70 ProgressDeadlineSeconds: ptr.To[int32](600),
71 Template: defaultTemplate,
72 },
73 },
74 },
75 {
76 original: &appsv1beta1.Deployment{
77 Spec: appsv1beta1.DeploymentSpec{
78 Replicas: ptr.To[int32](5),
79 Strategy: appsv1beta1.DeploymentStrategy{
80 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{
81 MaxSurge: &differentIntOrString,
82 },
83 },
84 },
85 },
86 expected: &appsv1beta1.Deployment{
87 Spec: appsv1beta1.DeploymentSpec{
88 Replicas: ptr.To[int32](5),
89 Strategy: appsv1beta1.DeploymentStrategy{
90 Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
91 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{
92 MaxSurge: &differentIntOrString,
93 MaxUnavailable: &defaultIntOrString,
94 },
95 },
96 RevisionHistoryLimit: ptr.To[int32](2),
97 ProgressDeadlineSeconds: ptr.To[int32](600),
98 Template: defaultTemplate,
99 },
100 },
101 },
102 {
103 original: &appsv1beta1.Deployment{
104 Spec: appsv1beta1.DeploymentSpec{
105 Replicas: ptr.To[int32](3),
106 Strategy: appsv1beta1.DeploymentStrategy{
107 Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
108 RollingUpdate: nil,
109 },
110 },
111 },
112 expected: &appsv1beta1.Deployment{
113 Spec: appsv1beta1.DeploymentSpec{
114 Replicas: ptr.To[int32](3),
115 Strategy: appsv1beta1.DeploymentStrategy{
116 Type: appsv1beta1.RollingUpdateDeploymentStrategyType,
117 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{
118 MaxSurge: &defaultIntOrString,
119 MaxUnavailable: &defaultIntOrString,
120 },
121 },
122 RevisionHistoryLimit: ptr.To[int32](2),
123 ProgressDeadlineSeconds: ptr.To[int32](600),
124 Template: defaultTemplate,
125 },
126 },
127 },
128 {
129 original: &appsv1beta1.Deployment{
130 Spec: appsv1beta1.DeploymentSpec{
131 Replicas: ptr.To[int32](5),
132 Strategy: appsv1beta1.DeploymentStrategy{
133 Type: appsv1beta1.RecreateDeploymentStrategyType,
134 },
135 RevisionHistoryLimit: ptr.To[int32](0),
136 },
137 },
138 expected: &appsv1beta1.Deployment{
139 Spec: appsv1beta1.DeploymentSpec{
140 Replicas: ptr.To[int32](5),
141 Strategy: appsv1beta1.DeploymentStrategy{
142 Type: appsv1beta1.RecreateDeploymentStrategyType,
143 },
144 RevisionHistoryLimit: ptr.To[int32](0),
145 ProgressDeadlineSeconds: ptr.To[int32](600),
146 Template: defaultTemplate,
147 },
148 },
149 },
150 {
151 original: &appsv1beta1.Deployment{
152 Spec: appsv1beta1.DeploymentSpec{
153 Replicas: ptr.To[int32](5),
154 Strategy: appsv1beta1.DeploymentStrategy{
155 Type: appsv1beta1.RecreateDeploymentStrategyType,
156 },
157 ProgressDeadlineSeconds: ptr.To[int32](30),
158 RevisionHistoryLimit: ptr.To[int32](2),
159 },
160 },
161 expected: &appsv1beta1.Deployment{
162 Spec: appsv1beta1.DeploymentSpec{
163 Replicas: ptr.To[int32](5),
164 Strategy: appsv1beta1.DeploymentStrategy{
165 Type: appsv1beta1.RecreateDeploymentStrategyType,
166 },
167 ProgressDeadlineSeconds: ptr.To[int32](30),
168 RevisionHistoryLimit: ptr.To[int32](2),
169 Template: defaultTemplate,
170 },
171 },
172 },
173 }
174
175 for _, test := range tests {
176 original := test.original
177 expected := test.expected
178 obj2 := roundTrip(t, runtime.Object(original))
179 got, ok := obj2.(*appsv1beta1.Deployment)
180 if !ok {
181 t.Errorf("unexpected object: %v", got)
182 t.FailNow()
183 }
184 if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) {
185 t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec)
186 }
187 }
188 }
189
190 func TestDefaultDeploymentAvailability(t *testing.T) {
191 d := roundTrip(t, runtime.Object(&appsv1beta1.Deployment{})).(*appsv1beta1.Deployment)
192
193 maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(d.Spec.Strategy.RollingUpdate.MaxUnavailable, int(*(d.Spec.Replicas)), false)
194 if err != nil {
195 t.Fatalf("unexpected error: %v", err)
196 }
197
198 if *(d.Spec.Replicas)-int32(maxUnavailable) <= 0 {
199 t.Fatalf("the default value of maxUnavailable can lead to no active replicas during rolling update")
200 }
201 }
202
203 func TestSetDefaultStatefulSet(t *testing.T) {
204 defaultLabels := map[string]string{"foo": "bar"}
205 var defaultPartition int32 = 0
206 var notTheDefaultPartition int32 = 42
207 var defaultReplicas int32 = 1
208
209 period := int64(v1.DefaultTerminationGracePeriodSeconds)
210 defaultTemplate := v1.PodTemplateSpec{
211 Spec: v1.PodSpec{
212 DNSPolicy: v1.DNSClusterFirst,
213 RestartPolicy: v1.RestartPolicyAlways,
214 SecurityContext: &v1.PodSecurityContext{},
215 TerminationGracePeriodSeconds: &period,
216 SchedulerName: v1.DefaultSchedulerName,
217 },
218 ObjectMeta: metav1.ObjectMeta{
219 Labels: defaultLabels,
220 },
221 }
222
223 tests := []struct {
224 name string
225 original *appsv1beta1.StatefulSet
226 expected *appsv1beta1.StatefulSet
227 enableMaxUnavailablePolicy bool
228 enableStatefulSetAutoDelete bool
229 }{
230 {
231 name: "labels and default update strategy",
232 original: &appsv1beta1.StatefulSet{
233 Spec: appsv1beta1.StatefulSetSpec{
234 Template: defaultTemplate,
235 },
236 },
237 expected: &appsv1beta1.StatefulSet{
238 Spec: appsv1beta1.StatefulSetSpec{
239 Replicas: &defaultReplicas,
240 MinReadySeconds: int32(0),
241 Template: defaultTemplate,
242 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
243 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
244 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
245 RollingUpdate: nil,
246 },
247 RevisionHistoryLimit: ptr.To[int32](10),
248 Selector: &metav1.LabelSelector{
249 MatchLabels: map[string]string{"foo": "bar"},
250 MatchExpressions: []metav1.LabelSelectorRequirement{},
251 },
252 },
253 },
254 },
255 {
256 name: "Alternate update strategy",
257 original: &appsv1beta1.StatefulSet{
258 Spec: appsv1beta1.StatefulSetSpec{
259 Template: defaultTemplate,
260 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
261 Type: appsv1beta1.RollingUpdateStatefulSetStrategyType,
262 },
263 },
264 },
265 expected: &appsv1beta1.StatefulSet{
266 Spec: appsv1beta1.StatefulSetSpec{
267 Replicas: &defaultReplicas,
268 MinReadySeconds: int32(0),
269 Template: defaultTemplate,
270 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
271 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
272 Type: appsv1beta1.RollingUpdateStatefulSetStrategyType,
273 RollingUpdate: nil,
274 },
275 RevisionHistoryLimit: ptr.To[int32](10),
276 Selector: &metav1.LabelSelector{
277 MatchLabels: map[string]string{"foo": "bar"},
278 MatchExpressions: []metav1.LabelSelectorRequirement{},
279 },
280 },
281 },
282 },
283 {
284 name: "Parallel pod management policy.",
285 original: &appsv1beta1.StatefulSet{
286 Spec: appsv1beta1.StatefulSetSpec{
287 Template: defaultTemplate,
288 PodManagementPolicy: appsv1beta1.ParallelPodManagement,
289 },
290 },
291 expected: &appsv1beta1.StatefulSet{
292 Spec: appsv1beta1.StatefulSetSpec{
293 Replicas: &defaultReplicas,
294 MinReadySeconds: int32(0),
295 Template: defaultTemplate,
296 PodManagementPolicy: appsv1beta1.ParallelPodManagement,
297 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
298 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
299 RollingUpdate: nil,
300 },
301 RevisionHistoryLimit: ptr.To[int32](10),
302 Selector: &metav1.LabelSelector{
303 MatchLabels: map[string]string{"foo": "bar"},
304 MatchExpressions: []metav1.LabelSelectorRequirement{},
305 },
306 },
307 },
308 },
309 {
310 name: "MaxUnavailable disabled, with maxUnavailable not specified",
311 original: &appsv1beta1.StatefulSet{
312 Spec: appsv1beta1.StatefulSetSpec{
313 Template: defaultTemplate,
314 },
315 },
316 expected: &appsv1beta1.StatefulSet{
317 Spec: appsv1beta1.StatefulSetSpec{
318 Replicas: &defaultReplicas,
319 Template: defaultTemplate,
320 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
321 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
322 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
323 RollingUpdate: nil,
324 },
325 RevisionHistoryLimit: ptr.To[int32](10),
326 Selector: &metav1.LabelSelector{
327 MatchLabels: map[string]string{"foo": "bar"},
328 MatchExpressions: []metav1.LabelSelectorRequirement{},
329 },
330 },
331 },
332 enableMaxUnavailablePolicy: false,
333 },
334 {
335 name: "MaxUnavailable disabled, with default maxUnavailable specified",
336 original: &appsv1beta1.StatefulSet{
337 Spec: appsv1beta1.StatefulSetSpec{
338 Template: defaultTemplate,
339 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
340 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
341 Partition: &defaultPartition,
342 MaxUnavailable: ptr.To(intstr.FromInt32(1)),
343 },
344 },
345 },
346 },
347 expected: &appsv1beta1.StatefulSet{
348 Spec: appsv1beta1.StatefulSetSpec{
349 Replicas: &defaultReplicas,
350 Template: defaultTemplate,
351 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
352 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
353 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
354 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
355 Partition: ptr.To[int32](0),
356 MaxUnavailable: ptr.To(intstr.FromInt32(1)),
357 },
358 },
359 RevisionHistoryLimit: ptr.To[int32](10),
360 Selector: &metav1.LabelSelector{
361 MatchLabels: map[string]string{"foo": "bar"},
362 MatchExpressions: []metav1.LabelSelectorRequirement{},
363 },
364 },
365 },
366 enableMaxUnavailablePolicy: false,
367 },
368 {
369 name: "MaxUnavailable disabled, with non default maxUnavailable specified",
370 original: &appsv1beta1.StatefulSet{
371 Spec: appsv1beta1.StatefulSetSpec{
372 Template: defaultTemplate,
373 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
374 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
375 Partition: ¬TheDefaultPartition,
376 MaxUnavailable: ptr.To(intstr.FromInt32(3)),
377 },
378 },
379 },
380 },
381 expected: &appsv1beta1.StatefulSet{
382 Spec: appsv1beta1.StatefulSetSpec{
383 Replicas: &defaultReplicas,
384 Template: defaultTemplate,
385 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
386 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
387 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
388 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
389 Partition: ptr.To[int32](42),
390 MaxUnavailable: ptr.To(intstr.FromInt32(3)),
391 },
392 },
393 RevisionHistoryLimit: ptr.To[int32](10),
394 Selector: &metav1.LabelSelector{
395 MatchLabels: map[string]string{"foo": "bar"},
396 MatchExpressions: []metav1.LabelSelectorRequirement{},
397 },
398 },
399 },
400 enableMaxUnavailablePolicy: false,
401 },
402 {
403 name: "MaxUnavailable enabled, with no maxUnavailable specified",
404 original: &appsv1beta1.StatefulSet{
405 Spec: appsv1beta1.StatefulSetSpec{
406 Template: defaultTemplate,
407 },
408 },
409 expected: &appsv1beta1.StatefulSet{
410 Spec: appsv1beta1.StatefulSetSpec{
411 Replicas: &defaultReplicas,
412 Template: defaultTemplate,
413 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
414 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
415 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
416 RollingUpdate: nil,
417 },
418 RevisionHistoryLimit: ptr.To[int32](10),
419 Selector: &metav1.LabelSelector{
420 MatchLabels: map[string]string{"foo": "bar"},
421 MatchExpressions: []metav1.LabelSelectorRequirement{},
422 },
423 },
424 },
425 enableMaxUnavailablePolicy: true,
426 },
427 {
428 name: "MaxUnavailable enabled, with non default maxUnavailable specified",
429 original: &appsv1beta1.StatefulSet{
430 Spec: appsv1beta1.StatefulSetSpec{
431 Template: defaultTemplate,
432 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
433 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
434 Partition: ¬TheDefaultPartition,
435 MaxUnavailable: ptr.To(intstr.FromInt32(3)),
436 },
437 },
438 },
439 },
440 expected: &appsv1beta1.StatefulSet{
441 Spec: appsv1beta1.StatefulSetSpec{
442 Replicas: &defaultReplicas,
443 Template: defaultTemplate,
444 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
445 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
446 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
447 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{
448 Partition: ptr.To[int32](42),
449 MaxUnavailable: ptr.To(intstr.FromInt32(3)),
450 },
451 },
452 RevisionHistoryLimit: ptr.To[int32](10),
453 Selector: &metav1.LabelSelector{
454 MatchLabels: map[string]string{"foo": "bar"},
455 MatchExpressions: []metav1.LabelSelectorRequirement{},
456 },
457 },
458 },
459 enableMaxUnavailablePolicy: true,
460 },
461 {
462 name: "StatefulSetAutoDeletePVC enabled",
463 original: &appsv1beta1.StatefulSet{
464 Spec: appsv1beta1.StatefulSetSpec{
465 Template: defaultTemplate,
466 },
467 },
468 expected: &appsv1beta1.StatefulSet{
469 Spec: appsv1beta1.StatefulSetSpec{
470 Replicas: &defaultReplicas,
471 Template: defaultTemplate,
472 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement,
473 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{
474 Type: appsv1beta1.OnDeleteStatefulSetStrategyType,
475 RollingUpdate: nil,
476 },
477 RevisionHistoryLimit: ptr.To[int32](10),
478 PersistentVolumeClaimRetentionPolicy: &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{
479 WhenDeleted: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType,
480 WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType,
481 },
482 Selector: &metav1.LabelSelector{
483 MatchLabels: map[string]string{"foo": "bar"},
484 MatchExpressions: []metav1.LabelSelectorRequirement{},
485 },
486 },
487 },
488 enableStatefulSetAutoDelete: true,
489 },
490 }
491
492 for _, test := range tests {
493 test := test
494 t.Run(test.name, func(t *testing.T) {
495 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy)()
496 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, test.enableStatefulSetAutoDelete)()
497 obj2 := roundTrip(t, runtime.Object(test.original))
498 got, ok := obj2.(*appsv1beta1.StatefulSet)
499 if !ok {
500 t.Errorf("unexpected object: %v", got)
501 t.FailNow()
502 }
503 if !apiequality.Semantic.DeepEqual(got.Spec, test.expected.Spec) {
504 t.Errorf("got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", got.Spec, test.expected.Spec)
505 }
506 })
507 }
508 }
509
510 func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
511 data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj)
512 if err != nil {
513 t.Errorf("%v\n %#v", err, obj)
514 return nil
515 }
516 obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data)
517 if err != nil {
518 t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj)
519 return nil
520 }
521 obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object)
522 err = legacyscheme.Scheme.Convert(obj2, obj3, nil)
523 if err != nil {
524 t.Errorf("%v\nSource: %#v", err, obj2)
525 return nil
526 }
527 return obj3
528 }
529
View as plain text