1
16
17 package validation
18
19 import (
20 "fmt"
21 "strings"
22 "testing"
23
24 "github.com/google/go-cmp/cmp"
25 "github.com/google/go-cmp/cmp/cmpopts"
26 "k8s.io/apimachinery/pkg/api/resource"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/util/dump"
30 "k8s.io/apimachinery/pkg/util/intstr"
31 "k8s.io/apimachinery/pkg/util/validation/field"
32 utilfeature "k8s.io/apiserver/pkg/util/feature"
33 featuregatetesting "k8s.io/component-base/featuregate/testing"
34 "k8s.io/kubernetes/pkg/api/pod"
35 "k8s.io/kubernetes/pkg/apis/apps"
36 api "k8s.io/kubernetes/pkg/apis/core"
37 corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
38 "k8s.io/kubernetes/pkg/features"
39 "k8s.io/utils/ptr"
40 )
41
42 type statefulSetTweak func(ss *apps.StatefulSet)
43
44 func mkStatefulSet(template *api.PodTemplate, tweaks ...statefulSetTweak) apps.StatefulSet {
45 ss := apps.StatefulSet{
46 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
47 Spec: apps.StatefulSetSpec{
48 PodManagementPolicy: apps.OrderedReadyPodManagement,
49 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
50 Template: template.Template,
51 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
52 },
53 }
54
55 for _, tw := range tweaks {
56 tw(&ss)
57 }
58
59 return ss
60 }
61
62 func tweakName(name string) statefulSetTweak {
63 return func(ss *apps.StatefulSet) {
64 ss.ObjectMeta.Name = name
65 }
66 }
67
68 func tweakNamespace(ns string) statefulSetTweak {
69 return func(ss *apps.StatefulSet) {
70 ss.ObjectMeta.Namespace = ns
71 }
72 }
73
74 func tweakLabels(key string, value string) statefulSetTweak {
75 return func(ss *apps.StatefulSet) {
76 if ss.ObjectMeta.Labels == nil {
77 ss.ObjectMeta.Labels = map[string]string{}
78 }
79 ss.ObjectMeta.Labels[key] = value
80 }
81 }
82
83 func tweakAnnotations(key string, value string) statefulSetTweak {
84 return func(ss *apps.StatefulSet) {
85 if ss.ObjectMeta.Annotations == nil {
86 ss.ObjectMeta.Annotations = map[string]string{}
87 }
88 ss.ObjectMeta.Annotations[key] = value
89 }
90 }
91
92 func tweakFinalizers(finalizers ...string) statefulSetTweak {
93 return func(ss *apps.StatefulSet) {
94 ss.ObjectMeta.Finalizers = finalizers
95 }
96 }
97
98 func tweakPodManagementPolicy(policy apps.PodManagementPolicyType) statefulSetTweak {
99 return func(ss *apps.StatefulSet) {
100 ss.Spec.PodManagementPolicy = policy
101 }
102 }
103
104 func tweakReplicas(replicas int32) statefulSetTweak {
105 return func(ss *apps.StatefulSet) {
106 ss.Spec.Replicas = replicas
107 }
108 }
109
110 func tweakSelectorLabels(labels map[string]string) statefulSetTweak {
111 return func(ss *apps.StatefulSet) {
112 if labels == nil {
113 ss.Spec.Selector = nil
114 } else {
115 ss.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels}
116 }
117 }
118 }
119
120 func tweakTemplateRestartPolicy(rp api.RestartPolicy) statefulSetTweak {
121 return func(ss *apps.StatefulSet) {
122 ss.Spec.Template.Spec.RestartPolicy = rp
123 }
124 }
125
126 func tweakMinReadySeconds(t int32) statefulSetTweak {
127 return func(ss *apps.StatefulSet) {
128 ss.Spec.MinReadySeconds = t
129 }
130 }
131
132 func tweakOrdinalsStart(s int32) statefulSetTweak {
133 return func(ss *apps.StatefulSet) {
134 ss.Spec.Ordinals = &apps.StatefulSetOrdinals{Start: s}
135 }
136 }
137
138 func tweakPVCTemplate(pvc ...api.PersistentVolumeClaim) statefulSetTweak {
139 return func(ss *apps.StatefulSet) {
140 ss.Spec.VolumeClaimTemplates = pvc
141 }
142 }
143
144 func tweakUpdateStrategyType(t apps.StatefulSetUpdateStrategyType) statefulSetTweak {
145 return func(ss *apps.StatefulSet) {
146 ss.Spec.UpdateStrategy.Type = t
147 }
148 }
149
150 func tweakRollingUpdatePartition(partition int32) statefulSetTweak {
151 return func(ss *apps.StatefulSet) {
152 if ss.Spec.UpdateStrategy.RollingUpdate == nil {
153 ss.Spec.UpdateStrategy.RollingUpdate = &apps.RollingUpdateStatefulSetStrategy{}
154 }
155 ss.Spec.UpdateStrategy.RollingUpdate.Partition = partition
156 }
157 }
158
159 func tweakMaxUnavailable(mu intstr.IntOrString) statefulSetTweak {
160 return func(ss *apps.StatefulSet) {
161 if ss.Spec.UpdateStrategy.RollingUpdate == nil {
162 ss.Spec.UpdateStrategy.RollingUpdate = &apps.RollingUpdateStatefulSetStrategy{}
163 }
164 ss.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = ptr.To(mu)
165 }
166 }
167
168 func tweakPVCPolicy(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) statefulSetTweak {
169 return func(ss *apps.StatefulSet) {
170 ss.Spec.PersistentVolumeClaimRetentionPolicy = policy
171 }
172 }
173
174 type pvcPolicyTweak func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy)
175
176 func mkPVCPolicy(tweaks ...pvcPolicyTweak) *apps.StatefulSetPersistentVolumeClaimRetentionPolicy {
177 policy := &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{}
178
179 for _, tw := range tweaks {
180 tw(policy)
181 }
182
183 return policy
184 }
185
186 func tweakPVCDeletedPolicy(t apps.PersistentVolumeClaimRetentionPolicyType) pvcPolicyTweak {
187 return func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) {
188 policy.WhenDeleted = t
189 }
190 }
191
192 func tweakPVCScalePolicy(t apps.PersistentVolumeClaimRetentionPolicyType) pvcPolicyTweak {
193 return func(policy *apps.StatefulSetPersistentVolumeClaimRetentionPolicy) {
194 policy.WhenScaled = t
195 }
196 }
197
198 func TestValidateStatefulSet(t *testing.T) {
199 validLabels := map[string]string{"a": "b"}
200 validPodTemplate := api.PodTemplate{
201 Template: api.PodTemplateSpec{
202 ObjectMeta: metav1.ObjectMeta{
203 Labels: validLabels,
204 },
205 Spec: api.PodSpec{
206 RestartPolicy: api.RestartPolicyAlways,
207 DNSPolicy: api.DNSClusterFirst,
208 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
209 },
210 },
211 }
212
213 validHostNetPodTemplate := api.PodTemplate{
214 Template: api.PodTemplateSpec{
215 ObjectMeta: metav1.ObjectMeta{
216 Labels: validLabels,
217 },
218 Spec: api.PodSpec{
219 SecurityContext: &api.PodSecurityContext{
220 HostNetwork: true,
221 },
222 RestartPolicy: api.RestartPolicyAlways,
223 DNSPolicy: api.DNSClusterFirst,
224 Containers: []api.Container{{
225 Name: "abc",
226 Image: "image",
227 ImagePullPolicy: "IfNotPresent",
228 Ports: []api.ContainerPort{{
229 ContainerPort: 12345,
230 Protocol: api.ProtocolTCP,
231 }},
232 }},
233 },
234 },
235 }
236
237 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
238 invalidPodTemplate := api.PodTemplate{
239 Template: api.PodTemplateSpec{
240 Spec: api.PodSpec{
241 RestartPolicy: api.RestartPolicyAlways,
242 DNSPolicy: api.DNSClusterFirst,
243 },
244 ObjectMeta: metav1.ObjectMeta{
245 Labels: invalidLabels,
246 },
247 },
248 }
249
250 invalidTime := int64(60)
251 invalidPodTemplate2 := api.PodTemplate{
252 Template: api.PodTemplateSpec{
253 ObjectMeta: metav1.ObjectMeta{
254 Labels: validLabels,
255 },
256 Spec: api.PodSpec{
257 RestartPolicy: api.RestartPolicyAlways,
258 DNSPolicy: api.DNSClusterFirst,
259 ActiveDeadlineSeconds: &invalidTime,
260 },
261 },
262 }
263
264 const enableStatefulSetAutoDeletePVC = "[enable StatefulSetAutoDeletePVC]"
265
266 type testCase struct {
267 name string
268 set apps.StatefulSet
269 errs field.ErrorList
270 }
271
272 successCases := []testCase{{
273 name: "alpha name",
274 set: mkStatefulSet(&validPodTemplate, tweakName("abc")),
275 }, {
276 name: "alphanumeric name",
277 set: mkStatefulSet(&validPodTemplate, tweakName("abc-123")),
278 }, {
279 name: "hostNetwork true",
280 set: mkStatefulSet(&validHostNetPodTemplate),
281 }, {
282 name: "parallel pod management",
283 set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)),
284 }, {
285 name: "ordered ready pod management",
286 set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
287 }, {
288 name: "update strategy",
289 set: mkStatefulSet(&validPodTemplate,
290 tweakReplicas(3),
291 tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
292 tweakRollingUpdatePartition(2),
293 ),
294 }, {
295 name: "PVC policy " + enableStatefulSetAutoDeletePVC,
296 set: mkStatefulSet(&validPodTemplate,
297 tweakPVCPolicy(mkPVCPolicy(
298 tweakPVCDeletedPolicy(apps.DeletePersistentVolumeClaimRetentionPolicyType),
299 tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
300 )),
301 ),
302 }, {
303 name: "maxUnavailable with parallel pod management",
304 set: mkStatefulSet(&validPodTemplate,
305 tweakReplicas(3),
306 tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
307 tweakRollingUpdatePartition(2),
308 tweakMaxUnavailable(intstr.FromInt32(2)),
309 ),
310 }, {
311 name: "ordinals.start positive value",
312 set: mkStatefulSet(&validPodTemplate,
313 tweakReplicas(3),
314 tweakOrdinalsStart(2),
315 ),
316 },
317 }
318
319 errorCases := []testCase{{
320 name: "zero-length name",
321 set: mkStatefulSet(&validPodTemplate, tweakName("")),
322 errs: field.ErrorList{
323 field.Required(field.NewPath("metadata", "name"), ""),
324 },
325 }, {
326 name: "name-with-dots",
327 set: mkStatefulSet(&validPodTemplate, tweakName("abc.123")),
328 errs: field.ErrorList{
329 field.Invalid(field.NewPath("metadata", "name"), "abc.123", ""),
330 },
331 }, {
332 name: "long name",
333 set: mkStatefulSet(&validPodTemplate, tweakName(strings.Repeat("a", 64))),
334 errs: field.ErrorList{
335 field.Invalid(field.NewPath("metadata", "name"), strings.Repeat("a", 64), ""),
336 },
337 }, {
338 name: "missing-namespace",
339 set: mkStatefulSet(&validPodTemplate, tweakNamespace("")),
340 errs: field.ErrorList{
341 field.Required(field.NewPath("metadata", "namespace"), ""),
342 },
343 }, {
344 name: "empty selector",
345 set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(nil)),
346 errs: field.ErrorList{
347 field.Required(field.NewPath("spec", "selector"), ""),
348 field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""),
349 },
350 }, {
351 name: "selector_doesnt_match",
352 set: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(map[string]string{"foo": "bar"})),
353 errs: field.ErrorList{
354 field.Invalid(field.NewPath("spec", "template", "metadata", "labels"), nil, ""),
355 },
356 }, {
357 name: "negative_replicas",
358 set: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)),
359 errs: field.ErrorList{
360 field.Invalid(field.NewPath("spec", "replicas"), nil, ""),
361 },
362 }, {
363 name: "invalid_label",
364 set: mkStatefulSet(&validPodTemplate,
365 tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
366 ),
367 errs: field.ErrorList{
368 field.Invalid(field.NewPath("metadata", "labels"), nil, ""),
369 },
370 }, {
371 name: "invalid_label 2",
372 set: mkStatefulSet(&invalidPodTemplate,
373 tweakLabels("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
374 tweakSelectorLabels(invalidLabels),
375 ),
376 errs: field.ErrorList{
377 field.Invalid(field.NewPath("metadata", "labels"), nil, ""),
378 field.Invalid(field.NewPath("spec", "selector"), nil, ""),
379 field.Invalid(field.NewPath("spec", "selector", "matchLabels"), nil, ""),
380 },
381 }, {
382 name: "invalid_annotation",
383 set: mkStatefulSet(&validPodTemplate,
384 tweakAnnotations("NoUppercaseOrSpecialCharsLike=Equals", "bar"),
385 ),
386 errs: field.ErrorList{
387 field.Invalid(field.NewPath("metadata", "annotations"), nil, ""),
388 },
389 }, {
390 name: "invalid restart policy 1",
391 set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyOnFailure)),
392 errs: field.ErrorList{
393 field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
394 },
395 }, {
396 name: "invalid restart policy 2",
397 set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy(api.RestartPolicyNever)),
398 errs: field.ErrorList{
399 field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
400 },
401 }, {
402 name: "empty restart policy",
403 set: mkStatefulSet(&validPodTemplate, tweakTemplateRestartPolicy("")),
404 errs: field.ErrorList{
405 field.NotSupported[string](field.NewPath("spec", "template", "spec", "restartPolicy"), nil, nil),
406 },
407 }, {
408 name: "invalid update strategy",
409 set: mkStatefulSet(&validPodTemplate,
410 tweakReplicas(3),
411 tweakUpdateStrategyType("foo"),
412 ),
413 errs: field.ErrorList{
414 field.Invalid(field.NewPath("spec", "updateStrategy"), nil, ""),
415 },
416 }, {
417 name: "empty update strategy",
418 set: mkStatefulSet(&validPodTemplate,
419 tweakReplicas(3),
420 tweakUpdateStrategyType(""),
421 ),
422 errs: field.ErrorList{
423 field.Required(field.NewPath("spec", "updateStrategy"), ""),
424 },
425 }, {
426 name: "invalid rolling update",
427 set: mkStatefulSet(&validPodTemplate,
428 tweakReplicas(3),
429 tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType),
430 tweakRollingUpdatePartition(1),
431 ),
432 errs: field.ErrorList{
433 field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate"), nil, ""),
434 },
435 }, {
436 name: "negative parition",
437 set: mkStatefulSet(&validPodTemplate,
438 tweakReplicas(3),
439 tweakRollingUpdatePartition(-1),
440 ),
441 errs: field.ErrorList{
442 field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "partition"), nil, ""),
443 },
444 }, {
445 name: "empty pod management policy",
446 set: mkStatefulSet(&validPodTemplate,
447 tweakPodManagementPolicy(""),
448 tweakReplicas(3),
449 ),
450 errs: field.ErrorList{
451 field.Required(field.NewPath("spec", "podManagementPolicy"), ""),
452 },
453 }, {
454 name: "invalid pod management policy",
455 set: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("foo")),
456 errs: field.ErrorList{
457 field.Invalid(field.NewPath("spec", "podManagementPolicy"), nil, ""),
458 },
459 }, {
460 name: "set active deadline seconds",
461 set: mkStatefulSet(&invalidPodTemplate2, tweakReplicas(3)),
462 errs: field.ErrorList{
463 field.Forbidden(field.NewPath("spec", "template", "spec", "activeDeadlineSeconds"), ""),
464 },
465 }, {
466 name: "empty PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC,
467 set: mkStatefulSet(&validPodTemplate,
468 tweakPVCPolicy(mkPVCPolicy()),
469 ),
470 errs: field.ErrorList{
471 field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil),
472 field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil),
473 },
474 }, {
475 name: "invalid PersistentVolumeClaimRetentionPolicy " + enableStatefulSetAutoDeletePVC,
476 set: mkStatefulSet(&validPodTemplate,
477 tweakPVCPolicy(mkPVCPolicy(
478 tweakPVCDeletedPolicy("invalid-retention-policy"),
479 tweakPVCScalePolicy("invalid-retention-policy"),
480 )),
481 ),
482 errs: field.ErrorList{
483 field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenDeleted"), nil, nil),
484 field.NotSupported[string](field.NewPath("spec", "persistentVolumeClaimRetentionPolicy", "whenScaled"), nil, nil),
485 },
486 }, {
487 name: "zero maxUnavailable",
488 set: mkStatefulSet(&validPodTemplate,
489 tweakReplicas(3),
490 tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
491 tweakMaxUnavailable(intstr.FromInt32(0)),
492 ),
493 errs: field.ErrorList{
494 field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
495 },
496 }, {
497 name: "zero percent maxUnavailable",
498 set: mkStatefulSet(&validPodTemplate,
499 tweakReplicas(3),
500 tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
501 tweakMaxUnavailable(intstr.FromString("0%")),
502 ),
503 errs: field.ErrorList{
504 field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
505 },
506 }, {
507 name: "greater than 100 percent maxUnavailable",
508 set: mkStatefulSet(&validPodTemplate,
509 tweakReplicas(3),
510 tweakUpdateStrategyType(apps.RollingUpdateStatefulSetStrategyType),
511 tweakMaxUnavailable(intstr.FromString("101%")),
512 ),
513 errs: field.ErrorList{
514 field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
515 },
516 }, {
517 name: "invalid ordinals.start",
518 set: mkStatefulSet(&validPodTemplate,
519 tweakReplicas(3),
520 tweakOrdinalsStart(-2),
521 ),
522 errs: field.ErrorList{
523 field.Invalid(field.NewPath("spec", "ordinals.start"), nil, ""),
524 },
525 },
526 }
527
528 cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })}
529 for _, testCase := range append(successCases, errorCases...) {
530 name := testCase.name
531 var testTitle string
532 if len(testCase.errs) == 0 {
533 testTitle = fmt.Sprintf("success case %s", name)
534 } else {
535 testTitle = fmt.Sprintf("error case %s", name)
536 }
537
538 t.Run(testTitle, func(t *testing.T) {
539 if strings.Contains(name, enableStatefulSetAutoDeletePVC) {
540 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)()
541 }
542
543 errs := ValidateStatefulSet(&testCase.set, pod.GetValidationOptionsFromPodTemplate(&testCase.set.Spec.Template, nil))
544 wantErrs := testCase.errs
545 if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" {
546 t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
547 }
548 })
549 }
550 }
551
552
553 func generateStatefulSetSpec(minSeconds int32) *apps.StatefulSetSpec {
554 labels := map[string]string{"a": "b"}
555 podTemplate := api.PodTemplate{
556 Template: api.PodTemplateSpec{
557 ObjectMeta: metav1.ObjectMeta{
558 Labels: labels,
559 },
560 Spec: api.PodSpec{
561 RestartPolicy: api.RestartPolicyAlways,
562 DNSPolicy: api.DNSClusterFirst,
563 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
564 },
565 },
566 }
567 ss := &apps.StatefulSetSpec{
568 PodManagementPolicy: "OrderedReady",
569 Selector: &metav1.LabelSelector{MatchLabels: labels},
570 Template: podTemplate.Template,
571 Replicas: 3,
572 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
573 MinReadySeconds: minSeconds,
574 }
575 return ss
576 }
577
578
579 func TestValidateStatefulSetMinReadySeconds(t *testing.T) {
580 testCases := map[string]struct {
581 ss *apps.StatefulSetSpec
582 expectErr bool
583 }{
584 "valid : minReadySeconds enabled, zero": {
585 ss: generateStatefulSetSpec(0),
586 expectErr: false,
587 },
588 "invalid : minReadySeconds enabled, negative": {
589 ss: generateStatefulSetSpec(-1),
590 expectErr: true,
591 },
592 "valid : minReadySeconds enabled, very large value": {
593 ss: generateStatefulSetSpec(2147483647),
594 expectErr: false,
595 },
596 "invalid : minReadySeconds enabled, large negative": {
597 ss: generateStatefulSetSpec(-2147483648),
598 expectErr: true,
599 },
600 }
601 for tcName, tc := range testCases {
602 t.Run(tcName, func(t *testing.T) {
603 errs := ValidateStatefulSetSpec(tc.ss, field.NewPath("spec", "minReadySeconds"),
604 corevalidation.PodValidationOptions{})
605 if tc.expectErr && len(errs) == 0 {
606 t.Errorf("Unexpected success")
607 }
608 if !tc.expectErr && len(errs) != 0 {
609 t.Errorf("Unexpected error(s): %v", errs)
610 }
611 })
612 }
613 }
614
615 func TestValidateStatefulSetStatus(t *testing.T) {
616 observedGenerationMinusOne := int64(-1)
617 collisionCountMinusOne := int32(-1)
618 tests := []struct {
619 name string
620 replicas int32
621 readyReplicas int32
622 currentReplicas int32
623 updatedReplicas int32
624 availableReplicas int32
625 observedGeneration *int64
626 collisionCount *int32
627 expectedErr bool
628 }{{
629 name: "valid status",
630 replicas: 3,
631 readyReplicas: 3,
632 currentReplicas: 2,
633 updatedReplicas: 1,
634 expectedErr: false,
635 }, {
636 name: "invalid replicas",
637 replicas: -1,
638 readyReplicas: 3,
639 currentReplicas: 2,
640 updatedReplicas: 1,
641 expectedErr: true,
642 }, {
643 name: "invalid readyReplicas",
644 replicas: 3,
645 readyReplicas: -1,
646 currentReplicas: 2,
647 updatedReplicas: 1,
648 expectedErr: true,
649 }, {
650 name: "invalid currentReplicas",
651 replicas: 3,
652 readyReplicas: 3,
653 currentReplicas: -1,
654 updatedReplicas: 1,
655 expectedErr: true,
656 }, {
657 name: "invalid updatedReplicas",
658 replicas: 3,
659 readyReplicas: 3,
660 currentReplicas: 2,
661 updatedReplicas: -1,
662 expectedErr: true,
663 }, {
664 name: "invalid observedGeneration",
665 replicas: 3,
666 readyReplicas: 3,
667 currentReplicas: 2,
668 updatedReplicas: 1,
669 observedGeneration: &observedGenerationMinusOne,
670 expectedErr: true,
671 }, {
672 name: "invalid collisionCount",
673 replicas: 3,
674 readyReplicas: 3,
675 currentReplicas: 2,
676 updatedReplicas: 1,
677 collisionCount: &collisionCountMinusOne,
678 expectedErr: true,
679 }, {
680 name: "readyReplicas greater than replicas",
681 replicas: 3,
682 readyReplicas: 4,
683 currentReplicas: 2,
684 updatedReplicas: 1,
685 expectedErr: true,
686 }, {
687 name: "currentReplicas greater than replicas",
688 replicas: 3,
689 readyReplicas: 3,
690 currentReplicas: 4,
691 updatedReplicas: 1,
692 expectedErr: true,
693 }, {
694 name: "updatedReplicas greater than replicas",
695 replicas: 3,
696 readyReplicas: 3,
697 currentReplicas: 2,
698 updatedReplicas: 4,
699 expectedErr: true,
700 }, {
701 name: "invalid: number of available replicas",
702 replicas: 3,
703 readyReplicas: 3,
704 currentReplicas: 2,
705 availableReplicas: int32(-1),
706 expectedErr: true,
707 }, {
708 name: "invalid: available replicas greater than replicas",
709 replicas: 3,
710 readyReplicas: 3,
711 currentReplicas: 2,
712 availableReplicas: int32(4),
713 expectedErr: true,
714 }, {
715 name: "invalid: available replicas greater than ready replicas",
716 replicas: 3,
717 readyReplicas: 2,
718 currentReplicas: 2,
719 availableReplicas: int32(3),
720 expectedErr: true,
721 },
722 }
723
724 for _, test := range tests {
725 t.Run(test.name, func(t *testing.T) {
726 status := apps.StatefulSetStatus{
727 Replicas: test.replicas,
728 ReadyReplicas: test.readyReplicas,
729 CurrentReplicas: test.currentReplicas,
730 UpdatedReplicas: test.updatedReplicas,
731 ObservedGeneration: test.observedGeneration,
732 CollisionCount: test.collisionCount,
733 AvailableReplicas: test.availableReplicas,
734 }
735
736 errs := ValidateStatefulSetStatus(&status, field.NewPath("status"))
737 if hasErr := len(errs) > 0; hasErr != test.expectedErr {
738 t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errs.ToAggregate().Error())
739 }
740 })
741 }
742 }
743
744 func TestValidateStatefulSetUpdate(t *testing.T) {
745 validLabels := map[string]string{"a": "b"}
746 validLabels2 := map[string]string{"c": "d"}
747 validPodTemplate := api.PodTemplate{
748 Template: api.PodTemplateSpec{
749 ObjectMeta: metav1.ObjectMeta{
750 Labels: validLabels,
751 },
752 Spec: api.PodSpec{
753 RestartPolicy: api.RestartPolicyAlways,
754 DNSPolicy: api.DNSClusterFirst,
755 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
756 },
757 },
758 }
759 validPodTemplate2 := api.PodTemplate{
760 Template: api.PodTemplateSpec{
761 ObjectMeta: metav1.ObjectMeta{
762 Labels: validLabels2,
763 },
764 Spec: api.PodSpec{
765 RestartPolicy: api.RestartPolicyAlways,
766 DNSPolicy: api.DNSClusterFirst,
767 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
768 },
769 },
770 }
771
772 storageClass := "storage-class1"
773 storageClass2 := "storage-class2"
774
775 validPVCTemplate := api.PersistentVolumeClaim{
776 ObjectMeta: metav1.ObjectMeta{
777 Name: "pvc-abc",
778 },
779 Spec: api.PersistentVolumeClaimSpec{
780 StorageClassName: &storageClass,
781 Resources: api.VolumeResourceRequirements{
782 Requests: api.ResourceList{
783 api.ResourceStorage: resource.MustParse("1Gi"),
784 },
785 },
786 },
787 }
788 validPVCTemplateChangedSize := *validPVCTemplate.DeepCopy()
789 validPVCTemplateChangedSize.Spec.Resources.Requests[api.ResourceStorage] = resource.MustParse("2Gi")
790
791 validPVCTemplateChangedClass := *validPVCTemplate.DeepCopy()
792 validPVCTemplateChangedClass.Spec.StorageClassName = &storageClass2
793
794 validPVCTemplate2 := api.PersistentVolumeClaim{
795 ObjectMeta: metav1.ObjectMeta{
796 Name: "pvc-abc2",
797 },
798 Spec: api.PersistentVolumeClaimSpec{
799 StorageClassName: &storageClass2,
800 Resources: api.VolumeResourceRequirements{
801 Requests: api.ResourceList{
802 api.ResourceStorage: resource.MustParse("2Gi"),
803 },
804 },
805 },
806 }
807
808 addContainersValidTemplate := validPodTemplate.DeepCopy()
809 addContainersValidTemplate.Template.Spec.Containers = append(addContainersValidTemplate.Template.Spec.Containers,
810 api.Container{Name: "def", Image: "image2", ImagePullPolicy: "IfNotPresent"})
811 if len(addContainersValidTemplate.Template.Spec.Containers) != len(validPodTemplate.Template.Spec.Containers)+1 {
812 t.Errorf("failure during test setup: template %v should have more containers than template %v", addContainersValidTemplate, validPodTemplate)
813 }
814
815 type testCase struct {
816 name string
817 old apps.StatefulSet
818 update apps.StatefulSet
819 errs field.ErrorList
820 }
821
822 successCases := []testCase{{
823 name: "update replica count",
824 old: mkStatefulSet(&validPodTemplate),
825 update: mkStatefulSet(&validPodTemplate, tweakReplicas(3)),
826 }, {
827 name: "update containers 1",
828 old: mkStatefulSet(&validPodTemplate),
829 update: mkStatefulSet(addContainersValidTemplate),
830 }, {
831 name: "update containers 2",
832 old: mkStatefulSet(addContainersValidTemplate),
833 update: mkStatefulSet(&validPodTemplate),
834 }, {
835 name: "update containers and pvc retention policy 1",
836 old: mkStatefulSet(addContainersValidTemplate),
837 update: mkStatefulSet(&validPodTemplate,
838 tweakPVCPolicy(mkPVCPolicy(
839 tweakPVCDeletedPolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
840 tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
841 )),
842 ),
843 }, {
844 name: "update containers and pvc retention policy 2",
845 old: mkStatefulSet(&validPodTemplate,
846 tweakPVCPolicy(mkPVCPolicy(
847 tweakPVCScalePolicy(apps.RetainPersistentVolumeClaimRetentionPolicyType),
848 )),
849 ),
850 update: mkStatefulSet(&validPodTemplate),
851 }, {
852 name: "update update strategy",
853 old: mkStatefulSet(&validPodTemplate),
854 update: mkStatefulSet(&validPodTemplate,
855 tweakUpdateStrategyType(apps.OnDeleteStatefulSetStrategyType),
856 ),
857 }, {
858 name: "update min ready seconds 1",
859 old: mkStatefulSet(&validPodTemplate),
860 update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)),
861 }, {
862 name: "update min ready seconds 2",
863 old: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(5)),
864 update: mkStatefulSet(&validPodTemplate, tweakMinReadySeconds(10)),
865 }, {
866 name: "update existing instance with now-invalid name",
867 old: mkStatefulSet(&validPodTemplate, tweakFinalizers("final")),
868 update: mkStatefulSet(&validPodTemplate, tweakFinalizers()),
869 }, {
870 name: "update existing instance with .spec.ordinals.start",
871 old: mkStatefulSet(&validPodTemplate),
872 update: mkStatefulSet(&validPodTemplate, tweakOrdinalsStart(3)),
873 },
874 }
875
876 errorCases := []testCase{{
877 name: "update name",
878 old: mkStatefulSet(&validPodTemplate, tweakName("abc")),
879 update: mkStatefulSet(&validPodTemplate, tweakName("abc2")),
880 errs: field.ErrorList{
881 field.Invalid(field.NewPath("metadata", "name"), nil, ""),
882 },
883 }, {
884 name: "update namespace",
885 old: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault)),
886 update: mkStatefulSet(&validPodTemplate, tweakNamespace(metav1.NamespaceDefault+"1")),
887 errs: field.ErrorList{
888 field.Invalid(field.NewPath("metadata", "namespace"), nil, ""),
889 },
890 }, {
891 name: "update selector",
892 old: mkStatefulSet(&validPodTemplate, tweakSelectorLabels(validLabels)),
893 update: mkStatefulSet(&validPodTemplate2,
894 tweakSelectorLabels(validLabels2),
895 ),
896 errs: field.ErrorList{
897 field.Forbidden(field.NewPath("spec"), ""),
898 },
899 }, {
900 name: "update pod management policy 1",
901 old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy("")),
902 update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
903 errs: field.ErrorList{
904 field.Forbidden(field.NewPath("spec"), ""),
905 },
906 }, {
907 name: "update pod management policy 2",
908 old: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.ParallelPodManagement)),
909 update: mkStatefulSet(&validPodTemplate, tweakPodManagementPolicy(apps.OrderedReadyPodManagement)),
910 errs: field.ErrorList{
911 field.Forbidden(field.NewPath("spec"), ""),
912 },
913 }, {
914 name: "update to negative replicas",
915 old: mkStatefulSet(&validPodTemplate),
916 update: mkStatefulSet(&validPodTemplate, tweakReplicas(-1)),
917 errs: field.ErrorList{
918 field.Invalid(field.NewPath("spec", "replicas"), nil, ""),
919 },
920 }, {
921 name: "update pvc template size",
922 old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
923 update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedSize)),
924 errs: field.ErrorList{
925 field.Forbidden(field.NewPath("spec"), ""),
926 },
927 }, {
928 name: "update pvc template storage class",
929 old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
930 update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplateChangedClass)),
931 errs: field.ErrorList{
932 field.Forbidden(field.NewPath("spec"), ""),
933 },
934 }, {
935 name: "add new pvc template",
936 old: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate)),
937 update: mkStatefulSet(&validPodTemplate, tweakPVCTemplate(validPVCTemplate, validPVCTemplate2)),
938 errs: field.ErrorList{
939 field.Forbidden(field.NewPath("spec"), ""),
940 },
941 },
942 }
943
944 cmpOpts := []cmp.Option{
945 cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"),
946 cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() }),
947 cmpopts.EquateEmpty(),
948 }
949 for _, testCase := range append(successCases, errorCases...) {
950 name := testCase.name
951 var testTitle string
952 if len(testCase.errs) == 0 {
953 testTitle = fmt.Sprintf("success case %s", name)
954 } else {
955 testTitle = fmt.Sprintf("error case %s", name)
956 }
957
958 t.Run(testTitle, func(t *testing.T) {
959 testCase.old.ObjectMeta.ResourceVersion = "1"
960 testCase.update.ObjectMeta.ResourceVersion = "1"
961
962 errs := ValidateStatefulSetUpdate(&testCase.update, &testCase.old, pod.GetValidationOptionsFromPodTemplate(&testCase.update.Spec.Template, &testCase.old.Spec.Template))
963 wantErrs := testCase.errs
964 if diff := cmp.Diff(wantErrs, errs, cmpOpts...); diff != "" {
965 t.Errorf("Unexpected validation errors (-want,+got):\n%s", diff)
966 }
967 })
968 }
969 }
970
971 func TestValidateControllerRevision(t *testing.T) {
972 newControllerRevision := func(name, namespace string, data runtime.Object, revision int64) apps.ControllerRevision {
973 return apps.ControllerRevision{
974 ObjectMeta: metav1.ObjectMeta{
975 Name: name,
976 Namespace: namespace,
977 },
978 Data: data,
979 Revision: revision,
980 }
981 }
982
983 podTemplate := api.PodTemplate{
984 Template: api.PodTemplateSpec{
985 Spec: api.PodSpec{
986 RestartPolicy: api.RestartPolicyAlways,
987 DNSPolicy: api.DNSClusterFirst,
988 },
989 ObjectMeta: metav1.ObjectMeta{
990 Labels: map[string]string{"a": "b"},
991 },
992 },
993 }
994
995 ss := mkStatefulSet(&podTemplate)
996
997 var (
998 valid = newControllerRevision("validname", "validns", &ss, 0)
999 badRevision = newControllerRevision("validname", "validns", &ss, -1)
1000 emptyName = newControllerRevision("", "validns", &ss, 0)
1001 invalidName = newControllerRevision("NoUppercaseOrSpecialCharsLike=Equals", "validns", &ss, 0)
1002 emptyNs = newControllerRevision("validname", "", &ss, 100)
1003 invalidNs = newControllerRevision("validname", "NoUppercaseOrSpecialCharsLike=Equals", &ss, 100)
1004 nilData = newControllerRevision("validname", "NoUppercaseOrSpecialCharsLike=Equals", nil, 100)
1005 )
1006
1007 tests := map[string]struct {
1008 history apps.ControllerRevision
1009 isValid bool
1010 }{
1011 "valid": {valid, true},
1012 "negative revision": {badRevision, false},
1013 "empty name": {emptyName, false},
1014 "invalid name": {invalidName, false},
1015 "empty namespace": {emptyNs, false},
1016 "invalid namespace": {invalidNs, false},
1017 "nil data": {nilData, false},
1018 }
1019
1020 for name, tc := range tests {
1021 t.Run(name, func(t *testing.T) {
1022 errs := ValidateControllerRevision(&tc.history)
1023 if tc.isValid && len(errs) > 0 {
1024 t.Errorf("%v: unexpected error: %v", name, errs)
1025 }
1026 if !tc.isValid && len(errs) == 0 {
1027 t.Errorf("%v: unexpected non-error", name)
1028 }
1029 })
1030 }
1031 }
1032
1033 func TestValidateControllerRevisionUpdate(t *testing.T) {
1034 newControllerRevision := func(version, name, namespace string, data runtime.Object, revision int64) apps.ControllerRevision {
1035 return apps.ControllerRevision{
1036 ObjectMeta: metav1.ObjectMeta{
1037 Name: name,
1038 Namespace: namespace,
1039 ResourceVersion: version,
1040 },
1041 Data: data,
1042 Revision: revision,
1043 }
1044 }
1045
1046 podTemplate := api.PodTemplate{
1047 Template: api.PodTemplateSpec{
1048 Spec: api.PodSpec{
1049 RestartPolicy: api.RestartPolicyAlways,
1050 DNSPolicy: api.DNSClusterFirst,
1051 },
1052 ObjectMeta: metav1.ObjectMeta{
1053 Labels: map[string]string{"a": "b"},
1054 },
1055 },
1056 }
1057
1058 ss := mkStatefulSet(&podTemplate, tweakName("abc"))
1059 modifiedss := mkStatefulSet(&podTemplate, tweakName("cdf"))
1060
1061 var (
1062 valid = newControllerRevision("1", "validname", "validns", &ss, 0)
1063 noVersion = newControllerRevision("", "validname", "validns", &ss, 0)
1064 changedData = newControllerRevision("1", "validname", "validns", &modifiedss, 0)
1065 changedRevision = newControllerRevision("1", "validname", "validns", &ss, 1)
1066 )
1067
1068 cases := []struct {
1069 name string
1070 newHistory apps.ControllerRevision
1071 oldHistory apps.ControllerRevision
1072 isValid bool
1073 }{{
1074 name: "valid",
1075 newHistory: valid,
1076 oldHistory: valid,
1077 isValid: true,
1078 }, {
1079 name: "invalid",
1080 newHistory: noVersion,
1081 oldHistory: valid,
1082 isValid: false,
1083 }, {
1084 name: "changed data",
1085 newHistory: changedData,
1086 oldHistory: valid,
1087 isValid: false,
1088 }, {
1089 name: "changed revision",
1090 newHistory: changedRevision,
1091 oldHistory: valid,
1092 isValid: true,
1093 },
1094 }
1095
1096 for _, tc := range cases {
1097 t.Run(tc.name, func(t *testing.T) {
1098 errs := ValidateControllerRevisionUpdate(&tc.newHistory, &tc.oldHistory)
1099 if tc.isValid && len(errs) > 0 {
1100 t.Errorf("%v: unexpected error: %v", tc.name, errs)
1101 }
1102 if !tc.isValid && len(errs) == 0 {
1103 t.Errorf("%v: unexpected non-error", tc.name)
1104 }
1105 })
1106 }
1107 }
1108
1109 func TestValidateDaemonSetStatusUpdate(t *testing.T) {
1110 type dsUpdateTest struct {
1111 old apps.DaemonSet
1112 update apps.DaemonSet
1113 }
1114
1115 successCases := []dsUpdateTest{{
1116 old: apps.DaemonSet{
1117 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1118 Status: apps.DaemonSetStatus{
1119 CurrentNumberScheduled: 1,
1120 NumberMisscheduled: 2,
1121 DesiredNumberScheduled: 3,
1122 NumberReady: 1,
1123 UpdatedNumberScheduled: 1,
1124 NumberAvailable: 1,
1125 NumberUnavailable: 2,
1126 },
1127 },
1128 update: apps.DaemonSet{
1129 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1130 Status: apps.DaemonSetStatus{
1131 CurrentNumberScheduled: 1,
1132 NumberMisscheduled: 1,
1133 DesiredNumberScheduled: 3,
1134 NumberReady: 1,
1135 UpdatedNumberScheduled: 1,
1136 NumberAvailable: 1,
1137 NumberUnavailable: 2,
1138 },
1139 },
1140 },
1141 }
1142
1143 for _, successCase := range successCases {
1144 successCase.old.ObjectMeta.ResourceVersion = "1"
1145 successCase.update.ObjectMeta.ResourceVersion = "1"
1146 if errs := ValidateDaemonSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
1147 t.Errorf("expected success: %v", errs)
1148 }
1149 }
1150 errorCases := map[string]dsUpdateTest{
1151 "negative values": {
1152 old: apps.DaemonSet{
1153 ObjectMeta: metav1.ObjectMeta{
1154 Name: "abc",
1155 Namespace: metav1.NamespaceDefault,
1156 ResourceVersion: "10",
1157 },
1158 Status: apps.DaemonSetStatus{
1159 CurrentNumberScheduled: 1,
1160 NumberMisscheduled: 2,
1161 DesiredNumberScheduled: 3,
1162 NumberReady: 1,
1163 ObservedGeneration: 3,
1164 UpdatedNumberScheduled: 1,
1165 NumberAvailable: 1,
1166 NumberUnavailable: 2,
1167 },
1168 },
1169 update: apps.DaemonSet{
1170 ObjectMeta: metav1.ObjectMeta{
1171 Name: "abc",
1172 Namespace: metav1.NamespaceDefault,
1173 ResourceVersion: "10",
1174 },
1175 Status: apps.DaemonSetStatus{
1176 CurrentNumberScheduled: -1,
1177 NumberMisscheduled: -1,
1178 DesiredNumberScheduled: -3,
1179 NumberReady: -1,
1180 ObservedGeneration: -3,
1181 UpdatedNumberScheduled: -1,
1182 NumberAvailable: -1,
1183 NumberUnavailable: -2,
1184 },
1185 },
1186 },
1187 "negative CurrentNumberScheduled": {
1188 old: apps.DaemonSet{
1189 ObjectMeta: metav1.ObjectMeta{
1190 Name: "abc",
1191 Namespace: metav1.NamespaceDefault,
1192 ResourceVersion: "10",
1193 },
1194 Status: apps.DaemonSetStatus{
1195 CurrentNumberScheduled: 1,
1196 NumberMisscheduled: 2,
1197 DesiredNumberScheduled: 3,
1198 NumberReady: 1,
1199 ObservedGeneration: 3,
1200 UpdatedNumberScheduled: 1,
1201 NumberAvailable: 1,
1202 NumberUnavailable: 2,
1203 },
1204 },
1205 update: apps.DaemonSet{
1206 ObjectMeta: metav1.ObjectMeta{
1207 Name: "abc",
1208 Namespace: metav1.NamespaceDefault,
1209 ResourceVersion: "10",
1210 },
1211 Status: apps.DaemonSetStatus{
1212 CurrentNumberScheduled: -1,
1213 NumberMisscheduled: 1,
1214 DesiredNumberScheduled: 3,
1215 NumberReady: 1,
1216 ObservedGeneration: 3,
1217 UpdatedNumberScheduled: 1,
1218 NumberAvailable: 1,
1219 NumberUnavailable: 2,
1220 },
1221 },
1222 },
1223 "negative NumberMisscheduled": {
1224 old: apps.DaemonSet{
1225 ObjectMeta: metav1.ObjectMeta{
1226 Name: "abc",
1227 Namespace: metav1.NamespaceDefault,
1228 ResourceVersion: "10",
1229 },
1230 Status: apps.DaemonSetStatus{
1231 CurrentNumberScheduled: 1,
1232 NumberMisscheduled: 2,
1233 DesiredNumberScheduled: 3,
1234 NumberReady: 1,
1235 ObservedGeneration: 3,
1236 UpdatedNumberScheduled: 1,
1237 NumberAvailable: 1,
1238 NumberUnavailable: 2,
1239 },
1240 },
1241 update: apps.DaemonSet{
1242 ObjectMeta: metav1.ObjectMeta{
1243 Name: "abc",
1244 Namespace: metav1.NamespaceDefault,
1245 ResourceVersion: "10",
1246 },
1247 Status: apps.DaemonSetStatus{
1248 CurrentNumberScheduled: 1,
1249 NumberMisscheduled: -1,
1250 DesiredNumberScheduled: 3,
1251 NumberReady: 1,
1252 ObservedGeneration: 3,
1253 UpdatedNumberScheduled: 1,
1254 NumberAvailable: 1,
1255 NumberUnavailable: 2,
1256 },
1257 },
1258 },
1259 "negative DesiredNumberScheduled": {
1260 old: apps.DaemonSet{
1261 ObjectMeta: metav1.ObjectMeta{
1262 Name: "abc",
1263 Namespace: metav1.NamespaceDefault,
1264 ResourceVersion: "10",
1265 },
1266 Status: apps.DaemonSetStatus{
1267 CurrentNumberScheduled: 1,
1268 NumberMisscheduled: 2,
1269 DesiredNumberScheduled: 3,
1270 NumberReady: 1,
1271 ObservedGeneration: 3,
1272 UpdatedNumberScheduled: 1,
1273 NumberAvailable: 1,
1274 NumberUnavailable: 2,
1275 },
1276 },
1277 update: apps.DaemonSet{
1278 ObjectMeta: metav1.ObjectMeta{
1279 Name: "abc",
1280 Namespace: metav1.NamespaceDefault,
1281 ResourceVersion: "10",
1282 },
1283 Status: apps.DaemonSetStatus{
1284 CurrentNumberScheduled: 1,
1285 NumberMisscheduled: 1,
1286 DesiredNumberScheduled: -3,
1287 NumberReady: 1,
1288 ObservedGeneration: 3,
1289 UpdatedNumberScheduled: 1,
1290 NumberAvailable: 1,
1291 NumberUnavailable: 2,
1292 },
1293 },
1294 },
1295 "negative NumberReady": {
1296 old: apps.DaemonSet{
1297 ObjectMeta: metav1.ObjectMeta{
1298 Name: "abc",
1299 Namespace: metav1.NamespaceDefault,
1300 ResourceVersion: "10",
1301 },
1302 Status: apps.DaemonSetStatus{
1303 CurrentNumberScheduled: 1,
1304 NumberMisscheduled: 2,
1305 DesiredNumberScheduled: 3,
1306 NumberReady: 1,
1307 ObservedGeneration: 3,
1308 UpdatedNumberScheduled: 1,
1309 NumberAvailable: 1,
1310 NumberUnavailable: 2,
1311 },
1312 },
1313 update: apps.DaemonSet{
1314 ObjectMeta: metav1.ObjectMeta{
1315 Name: "abc",
1316 Namespace: metav1.NamespaceDefault,
1317 ResourceVersion: "10",
1318 },
1319 Status: apps.DaemonSetStatus{
1320 CurrentNumberScheduled: 1,
1321 NumberMisscheduled: 1,
1322 DesiredNumberScheduled: 3,
1323 NumberReady: -1,
1324 ObservedGeneration: 3,
1325 UpdatedNumberScheduled: 1,
1326 NumberAvailable: 1,
1327 NumberUnavailable: 2,
1328 },
1329 },
1330 },
1331 "negative ObservedGeneration": {
1332 old: apps.DaemonSet{
1333 ObjectMeta: metav1.ObjectMeta{
1334 Name: "abc",
1335 Namespace: metav1.NamespaceDefault,
1336 ResourceVersion: "10",
1337 },
1338 Status: apps.DaemonSetStatus{
1339 CurrentNumberScheduled: 1,
1340 NumberMisscheduled: 2,
1341 DesiredNumberScheduled: 3,
1342 NumberReady: 1,
1343 ObservedGeneration: 3,
1344 UpdatedNumberScheduled: 1,
1345 NumberAvailable: 1,
1346 NumberUnavailable: 2,
1347 },
1348 },
1349 update: apps.DaemonSet{
1350 ObjectMeta: metav1.ObjectMeta{
1351 Name: "abc",
1352 Namespace: metav1.NamespaceDefault,
1353 ResourceVersion: "10",
1354 },
1355 Status: apps.DaemonSetStatus{
1356 CurrentNumberScheduled: 1,
1357 NumberMisscheduled: 1,
1358 DesiredNumberScheduled: 3,
1359 NumberReady: 1,
1360 ObservedGeneration: -3,
1361 UpdatedNumberScheduled: 1,
1362 NumberAvailable: 1,
1363 NumberUnavailable: 2,
1364 },
1365 },
1366 },
1367 "negative UpdatedNumberScheduled": {
1368 old: apps.DaemonSet{
1369 ObjectMeta: metav1.ObjectMeta{
1370 Name: "abc",
1371 Namespace: metav1.NamespaceDefault,
1372 ResourceVersion: "10",
1373 },
1374 Status: apps.DaemonSetStatus{
1375 CurrentNumberScheduled: 1,
1376 NumberMisscheduled: 2,
1377 DesiredNumberScheduled: 3,
1378 NumberReady: 1,
1379 ObservedGeneration: 3,
1380 UpdatedNumberScheduled: 1,
1381 NumberAvailable: 1,
1382 NumberUnavailable: 2,
1383 },
1384 },
1385 update: apps.DaemonSet{
1386 ObjectMeta: metav1.ObjectMeta{
1387 Name: "abc",
1388 Namespace: metav1.NamespaceDefault,
1389 ResourceVersion: "10",
1390 },
1391 Status: apps.DaemonSetStatus{
1392 CurrentNumberScheduled: 1,
1393 NumberMisscheduled: 1,
1394 DesiredNumberScheduled: 3,
1395 NumberReady: 1,
1396 ObservedGeneration: 3,
1397 UpdatedNumberScheduled: -1,
1398 NumberAvailable: 1,
1399 NumberUnavailable: 2,
1400 },
1401 },
1402 },
1403 "negative NumberAvailable": {
1404 old: apps.DaemonSet{
1405 ObjectMeta: metav1.ObjectMeta{
1406 Name: "abc",
1407 Namespace: metav1.NamespaceDefault,
1408 ResourceVersion: "10",
1409 },
1410 Status: apps.DaemonSetStatus{
1411 CurrentNumberScheduled: 1,
1412 NumberMisscheduled: 2,
1413 DesiredNumberScheduled: 3,
1414 NumberReady: 1,
1415 ObservedGeneration: 3,
1416 UpdatedNumberScheduled: 1,
1417 NumberAvailable: 1,
1418 NumberUnavailable: 2,
1419 },
1420 },
1421 update: apps.DaemonSet{
1422 ObjectMeta: metav1.ObjectMeta{
1423 Name: "abc",
1424 Namespace: metav1.NamespaceDefault,
1425 ResourceVersion: "10",
1426 },
1427 Status: apps.DaemonSetStatus{
1428 CurrentNumberScheduled: 1,
1429 NumberMisscheduled: 1,
1430 DesiredNumberScheduled: 3,
1431 NumberReady: 1,
1432 ObservedGeneration: 3,
1433 UpdatedNumberScheduled: 1,
1434 NumberAvailable: -1,
1435 NumberUnavailable: 2,
1436 },
1437 },
1438 },
1439 "negative NumberUnavailable": {
1440 old: apps.DaemonSet{
1441 ObjectMeta: metav1.ObjectMeta{
1442 Name: "abc",
1443 Namespace: metav1.NamespaceDefault,
1444 ResourceVersion: "10",
1445 },
1446 Status: apps.DaemonSetStatus{
1447 CurrentNumberScheduled: 1,
1448 NumberMisscheduled: 2,
1449 DesiredNumberScheduled: 3,
1450 NumberReady: 1,
1451 ObservedGeneration: 3,
1452 UpdatedNumberScheduled: 1,
1453 NumberAvailable: 1,
1454 NumberUnavailable: 2,
1455 },
1456 },
1457 update: apps.DaemonSet{
1458 ObjectMeta: metav1.ObjectMeta{
1459 Name: "abc",
1460 Namespace: metav1.NamespaceDefault,
1461 ResourceVersion: "10",
1462 },
1463 Status: apps.DaemonSetStatus{
1464 CurrentNumberScheduled: 1,
1465 NumberMisscheduled: 1,
1466 DesiredNumberScheduled: 3,
1467 NumberReady: 1,
1468 ObservedGeneration: 3,
1469 UpdatedNumberScheduled: 1,
1470 NumberAvailable: 1,
1471 NumberUnavailable: -2,
1472 },
1473 },
1474 },
1475 }
1476
1477 for testName, errorCase := range errorCases {
1478 if errs := ValidateDaemonSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
1479 t.Errorf("expected failure: %s", testName)
1480 }
1481 }
1482 }
1483
1484 func TestValidateDaemonSetUpdate(t *testing.T) {
1485 validSelector := map[string]string{"a": "b"}
1486 validSelector2 := map[string]string{"c": "d"}
1487 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
1488
1489 validPodSpecAbc := api.PodSpec{
1490 RestartPolicy: api.RestartPolicyAlways,
1491 DNSPolicy: api.DNSClusterFirst,
1492 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1493 }
1494 validPodSpecDef := api.PodSpec{
1495 RestartPolicy: api.RestartPolicyAlways,
1496 DNSPolicy: api.DNSClusterFirst,
1497 Containers: []api.Container{{Name: "def", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1498 }
1499 validPodSpecNodeSelector := api.PodSpec{
1500 NodeSelector: validSelector,
1501 NodeName: "xyz",
1502 RestartPolicy: api.RestartPolicyAlways,
1503 DNSPolicy: api.DNSClusterFirst,
1504 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1505 }
1506 validPodSpecVolume := api.PodSpec{
1507 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
1508 RestartPolicy: api.RestartPolicyAlways,
1509 DNSPolicy: api.DNSClusterFirst,
1510 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
1511 }
1512
1513 validPodTemplateAbc := api.PodTemplate{
1514 Template: api.PodTemplateSpec{
1515 ObjectMeta: metav1.ObjectMeta{
1516 Labels: validSelector,
1517 },
1518 Spec: validPodSpecAbc,
1519 },
1520 }
1521 validPodTemplateAbcSemanticallyEqual := api.PodTemplate{
1522 Template: api.PodTemplateSpec{
1523 ObjectMeta: metav1.ObjectMeta{
1524 Labels: validSelector,
1525 },
1526 Spec: validPodSpecAbc,
1527 },
1528 }
1529 validPodTemplateAbcSemanticallyEqual.Template.Spec.ImagePullSecrets = []api.LocalObjectReference{}
1530 validPodTemplateNodeSelector := api.PodTemplate{
1531 Template: api.PodTemplateSpec{
1532 ObjectMeta: metav1.ObjectMeta{
1533 Labels: validSelector,
1534 },
1535 Spec: validPodSpecNodeSelector,
1536 },
1537 }
1538 validPodTemplateAbc2 := api.PodTemplate{
1539 Template: api.PodTemplateSpec{
1540 ObjectMeta: metav1.ObjectMeta{
1541 Labels: validSelector2,
1542 },
1543 Spec: validPodSpecAbc,
1544 },
1545 }
1546 validPodTemplateDef := api.PodTemplate{
1547 Template: api.PodTemplateSpec{
1548 ObjectMeta: metav1.ObjectMeta{
1549 Labels: validSelector2,
1550 },
1551 Spec: validPodSpecDef,
1552 },
1553 }
1554 invalidPodTemplate := api.PodTemplate{
1555 Template: api.PodTemplateSpec{
1556 Spec: api.PodSpec{
1557
1558 RestartPolicy: api.RestartPolicyAlways,
1559 DNSPolicy: api.DNSClusterFirst,
1560 },
1561 ObjectMeta: metav1.ObjectMeta{
1562 Labels: validSelector,
1563 },
1564 },
1565 }
1566 readWriteVolumePodTemplate := api.PodTemplate{
1567 Template: api.PodTemplateSpec{
1568 ObjectMeta: metav1.ObjectMeta{
1569 Labels: validSelector,
1570 },
1571 Spec: validPodSpecVolume,
1572 },
1573 }
1574 type dsUpdateTest struct {
1575 old apps.DaemonSet
1576 update apps.DaemonSet
1577 expectedErrNum int
1578 enableSkipReadOnlyValidationGCE bool
1579 }
1580 successCases := map[string]dsUpdateTest{
1581 "no change": {
1582 old: apps.DaemonSet{
1583 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1584 Spec: apps.DaemonSetSpec{
1585 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1586 TemplateGeneration: 1,
1587 Template: validPodTemplateAbc.Template,
1588 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1589 Type: apps.OnDeleteDaemonSetStrategyType,
1590 },
1591 },
1592 },
1593 update: apps.DaemonSet{
1594 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1595 Spec: apps.DaemonSetSpec{
1596 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1597 TemplateGeneration: 1,
1598 Template: validPodTemplateAbc.Template,
1599 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1600 Type: apps.OnDeleteDaemonSetStrategyType,
1601 },
1602 },
1603 },
1604 },
1605 "change template and selector": {
1606 old: apps.DaemonSet{
1607 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1608 Spec: apps.DaemonSetSpec{
1609 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1610 TemplateGeneration: 2,
1611 Template: validPodTemplateAbc.Template,
1612 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1613 Type: apps.OnDeleteDaemonSetStrategyType,
1614 },
1615 },
1616 },
1617 update: apps.DaemonSet{
1618 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1619 Spec: apps.DaemonSetSpec{
1620 Selector: &metav1.LabelSelector{MatchLabels: validSelector2},
1621 TemplateGeneration: 3,
1622 Template: validPodTemplateAbc2.Template,
1623 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1624 Type: apps.OnDeleteDaemonSetStrategyType,
1625 },
1626 },
1627 },
1628 },
1629 "change template": {
1630 old: apps.DaemonSet{
1631 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1632 Spec: apps.DaemonSetSpec{
1633 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1634 TemplateGeneration: 3,
1635 Template: validPodTemplateAbc.Template,
1636 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1637 Type: apps.OnDeleteDaemonSetStrategyType,
1638 },
1639 },
1640 },
1641 update: apps.DaemonSet{
1642 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1643 Spec: apps.DaemonSetSpec{
1644 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1645 TemplateGeneration: 4,
1646 Template: validPodTemplateNodeSelector.Template,
1647 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1648 Type: apps.OnDeleteDaemonSetStrategyType,
1649 },
1650 },
1651 },
1652 },
1653 "change container image name": {
1654 old: apps.DaemonSet{
1655 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1656 Spec: apps.DaemonSetSpec{
1657 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1658 TemplateGeneration: 1,
1659 Template: validPodTemplateAbc.Template,
1660 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1661 Type: apps.OnDeleteDaemonSetStrategyType,
1662 },
1663 },
1664 },
1665 update: apps.DaemonSet{
1666 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1667 Spec: apps.DaemonSetSpec{
1668 Selector: &metav1.LabelSelector{MatchLabels: validSelector2},
1669 TemplateGeneration: 2,
1670 Template: validPodTemplateDef.Template,
1671 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1672 Type: apps.OnDeleteDaemonSetStrategyType,
1673 },
1674 },
1675 },
1676 },
1677 "change update strategy": {
1678 old: apps.DaemonSet{
1679 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1680 Spec: apps.DaemonSetSpec{
1681 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1682 TemplateGeneration: 4,
1683 Template: validPodTemplateAbc.Template,
1684 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1685 Type: apps.OnDeleteDaemonSetStrategyType,
1686 },
1687 },
1688 },
1689 update: apps.DaemonSet{
1690 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1691 Spec: apps.DaemonSetSpec{
1692 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1693 TemplateGeneration: 4,
1694 Template: validPodTemplateAbc.Template,
1695 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1696 Type: apps.RollingUpdateDaemonSetStrategyType,
1697 RollingUpdate: &apps.RollingUpdateDaemonSet{
1698 MaxUnavailable: intstr.FromInt32(1),
1699 },
1700 },
1701 },
1702 },
1703 },
1704 "unchanged templateGeneration upon semantically equal template update": {
1705 old: apps.DaemonSet{
1706 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1707 Spec: apps.DaemonSetSpec{
1708 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1709 TemplateGeneration: 4,
1710 Template: validPodTemplateAbc.Template,
1711 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1712 Type: apps.OnDeleteDaemonSetStrategyType,
1713 },
1714 },
1715 },
1716 update: apps.DaemonSet{
1717 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1718 Spec: apps.DaemonSetSpec{
1719 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1720 TemplateGeneration: 4,
1721 Template: validPodTemplateAbcSemanticallyEqual.Template,
1722 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1723 Type: apps.RollingUpdateDaemonSetStrategyType,
1724 RollingUpdate: &apps.RollingUpdateDaemonSet{
1725 MaxUnavailable: intstr.FromInt32(1),
1726 },
1727 },
1728 },
1729 },
1730 },
1731 "Read-write volume verification": {
1732 enableSkipReadOnlyValidationGCE: true,
1733 old: apps.DaemonSet{
1734 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1735 Spec: apps.DaemonSetSpec{
1736 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1737 TemplateGeneration: 1,
1738 Template: validPodTemplateAbc.Template,
1739 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1740 Type: apps.OnDeleteDaemonSetStrategyType,
1741 },
1742 },
1743 },
1744 update: apps.DaemonSet{
1745 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1746 Spec: apps.DaemonSetSpec{
1747 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1748 TemplateGeneration: 2,
1749 Template: readWriteVolumePodTemplate.Template,
1750 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1751 Type: apps.OnDeleteDaemonSetStrategyType,
1752 },
1753 },
1754 },
1755 },
1756 }
1757 for testName, successCase := range successCases {
1758 t.Run(testName, func(t *testing.T) {
1759 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
1760
1761 successCase.old.ObjectMeta.ResourceVersion = "1"
1762 successCase.update.ObjectMeta.ResourceVersion = "2"
1763
1764 if successCase.expectedErrNum > 0 {
1765 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
1766 }
1767 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
1768 t.Errorf("%q has incorrect test setup with no resource version set", testName)
1769 }
1770 if errs := ValidateDaemonSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
1771 t.Errorf("%q expected no error, but got: %v", testName, errs)
1772 }
1773 })
1774 }
1775 errorCases := map[string]dsUpdateTest{
1776 "change daemon name": {
1777 old: apps.DaemonSet{
1778 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
1779 Spec: apps.DaemonSetSpec{
1780 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1781 TemplateGeneration: 1,
1782 Template: validPodTemplateAbc.Template,
1783 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1784 Type: apps.OnDeleteDaemonSetStrategyType,
1785 },
1786 },
1787 },
1788 update: apps.DaemonSet{
1789 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1790 Spec: apps.DaemonSetSpec{
1791 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1792 TemplateGeneration: 1,
1793 Template: validPodTemplateAbc.Template,
1794 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1795 Type: apps.OnDeleteDaemonSetStrategyType,
1796 },
1797 },
1798 },
1799 expectedErrNum: 1,
1800 },
1801 "invalid selector": {
1802 old: apps.DaemonSet{
1803 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1804 Spec: apps.DaemonSetSpec{
1805 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1806 TemplateGeneration: 1,
1807 Template: validPodTemplateAbc.Template,
1808 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1809 Type: apps.OnDeleteDaemonSetStrategyType,
1810 },
1811 },
1812 },
1813 update: apps.DaemonSet{
1814 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1815 Spec: apps.DaemonSetSpec{
1816 Selector: &metav1.LabelSelector{MatchLabels: invalidSelector},
1817 TemplateGeneration: 1,
1818 Template: validPodTemplateAbc.Template,
1819 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1820 Type: apps.OnDeleteDaemonSetStrategyType,
1821 },
1822 },
1823 },
1824 expectedErrNum: 1,
1825 },
1826 "invalid pod": {
1827 old: apps.DaemonSet{
1828 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1829 Spec: apps.DaemonSetSpec{
1830 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1831 TemplateGeneration: 1,
1832 Template: validPodTemplateAbc.Template,
1833 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1834 Type: apps.OnDeleteDaemonSetStrategyType,
1835 },
1836 },
1837 },
1838 update: apps.DaemonSet{
1839 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1840 Spec: apps.DaemonSetSpec{
1841 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1842 TemplateGeneration: 2,
1843 Template: invalidPodTemplate.Template,
1844 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1845 Type: apps.OnDeleteDaemonSetStrategyType,
1846 },
1847 },
1848 },
1849 expectedErrNum: 1,
1850 },
1851 "invalid read-write volume": {
1852 enableSkipReadOnlyValidationGCE: false,
1853 old: apps.DaemonSet{
1854 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1855 Spec: apps.DaemonSetSpec{
1856 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1857 TemplateGeneration: 1,
1858 Template: validPodTemplateAbc.Template,
1859 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1860 Type: apps.OnDeleteDaemonSetStrategyType,
1861 },
1862 },
1863 },
1864 update: apps.DaemonSet{
1865 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1866 Spec: apps.DaemonSetSpec{
1867 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1868 TemplateGeneration: 2,
1869 Template: readWriteVolumePodTemplate.Template,
1870 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1871 Type: apps.OnDeleteDaemonSetStrategyType,
1872 },
1873 },
1874 },
1875 expectedErrNum: 1,
1876 },
1877 "invalid update strategy": {
1878 old: apps.DaemonSet{
1879 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1880 Spec: apps.DaemonSetSpec{
1881 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1882 TemplateGeneration: 1,
1883 Template: validPodTemplateAbc.Template,
1884 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1885 Type: apps.OnDeleteDaemonSetStrategyType,
1886 },
1887 },
1888 },
1889 update: apps.DaemonSet{
1890 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1891 Spec: apps.DaemonSetSpec{
1892 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1893 TemplateGeneration: 1,
1894 Template: validPodTemplateAbc.Template,
1895 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1896 Type: "Random",
1897 },
1898 },
1899 },
1900 expectedErrNum: 1,
1901 },
1902 "negative templateGeneration": {
1903 old: apps.DaemonSet{
1904 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1905 Spec: apps.DaemonSetSpec{
1906 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1907 TemplateGeneration: -1,
1908 Template: validPodTemplateAbc.Template,
1909 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1910 Type: apps.OnDeleteDaemonSetStrategyType,
1911 },
1912 },
1913 },
1914 update: apps.DaemonSet{
1915 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1916 Spec: apps.DaemonSetSpec{
1917 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1918 TemplateGeneration: -1,
1919 Template: validPodTemplateAbc.Template,
1920 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1921 Type: apps.OnDeleteDaemonSetStrategyType,
1922 },
1923 },
1924 },
1925 expectedErrNum: 1,
1926 },
1927 "decreased templateGeneration": {
1928 old: apps.DaemonSet{
1929 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1930 Spec: apps.DaemonSetSpec{
1931 TemplateGeneration: 2,
1932 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1933 Template: validPodTemplateAbc.Template,
1934 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1935 Type: apps.OnDeleteDaemonSetStrategyType,
1936 },
1937 },
1938 },
1939 update: apps.DaemonSet{
1940 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1941 Spec: apps.DaemonSetSpec{
1942 TemplateGeneration: 1,
1943 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1944 Template: validPodTemplateAbc.Template,
1945 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1946 Type: apps.OnDeleteDaemonSetStrategyType,
1947 },
1948 },
1949 },
1950 expectedErrNum: 1,
1951 },
1952 "unchanged templateGeneration upon template update": {
1953 old: apps.DaemonSet{
1954 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1955 Spec: apps.DaemonSetSpec{
1956 TemplateGeneration: 2,
1957 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
1958 Template: validPodTemplateAbc.Template,
1959 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1960 Type: apps.OnDeleteDaemonSetStrategyType,
1961 },
1962 },
1963 },
1964 update: apps.DaemonSet{
1965 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
1966 Spec: apps.DaemonSetSpec{
1967 TemplateGeneration: 2,
1968 Selector: &metav1.LabelSelector{MatchLabels: validSelector2},
1969 Template: validPodTemplateAbc2.Template,
1970 UpdateStrategy: apps.DaemonSetUpdateStrategy{
1971 Type: apps.OnDeleteDaemonSetStrategyType,
1972 },
1973 },
1974 },
1975 expectedErrNum: 1,
1976 },
1977 }
1978 for testName, errorCase := range errorCases {
1979 t.Run(testName, func(t *testing.T) {
1980 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
1981
1982 errorCase.old.ObjectMeta.ResourceVersion = "1"
1983 errorCase.update.ObjectMeta.ResourceVersion = "2"
1984
1985 if errorCase.expectedErrNum <= 0 {
1986 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
1987 }
1988 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
1989 t.Errorf("%q has incorrect test setup with no resource version set", testName)
1990 }
1991
1992 if errs := ValidateDaemonSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
1993 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
1994 } else {
1995 t.Logf("(PASS) %q got errors %v", testName, errs)
1996 }
1997 })
1998 }
1999 }
2000
2001 func TestValidateDaemonSet(t *testing.T) {
2002 validSelector := map[string]string{"a": "b"}
2003 validPodTemplate := api.PodTemplate{
2004 Template: api.PodTemplateSpec{
2005 ObjectMeta: metav1.ObjectMeta{
2006 Labels: validSelector,
2007 },
2008 Spec: api.PodSpec{
2009 RestartPolicy: api.RestartPolicyAlways,
2010 DNSPolicy: api.DNSClusterFirst,
2011 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2012 },
2013 },
2014 }
2015 validHostNetPodTemplate := api.PodTemplate{
2016 Template: api.PodTemplateSpec{
2017 ObjectMeta: metav1.ObjectMeta{
2018 Labels: validSelector,
2019 },
2020 Spec: api.PodSpec{
2021 SecurityContext: &api.PodSecurityContext{
2022 HostNetwork: true,
2023 },
2024 RestartPolicy: api.RestartPolicyAlways,
2025 DNSPolicy: api.DNSClusterFirst,
2026 Containers: []api.Container{{
2027 Name: "abc",
2028 Image: "image",
2029 ImagePullPolicy: "IfNotPresent",
2030 TerminationMessagePolicy: api.TerminationMessageReadFile,
2031 Ports: []api.ContainerPort{{
2032 ContainerPort: 12345,
2033 Protocol: api.ProtocolTCP,
2034 }},
2035 }},
2036 },
2037 },
2038 }
2039 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
2040 invalidPodTemplate := api.PodTemplate{
2041 Template: api.PodTemplateSpec{
2042 Spec: api.PodSpec{
2043 RestartPolicy: api.RestartPolicyAlways,
2044 DNSPolicy: api.DNSClusterFirst,
2045 },
2046 ObjectMeta: metav1.ObjectMeta{
2047 Labels: invalidSelector,
2048 },
2049 },
2050 }
2051 successCases := []apps.DaemonSet{{
2052 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2053 Spec: apps.DaemonSetSpec{
2054 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2055 Template: validPodTemplate.Template,
2056 UpdateStrategy: apps.DaemonSetUpdateStrategy{
2057 Type: apps.OnDeleteDaemonSetStrategyType,
2058 },
2059 },
2060 }, {
2061 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
2062 Spec: apps.DaemonSetSpec{
2063 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2064 Template: validPodTemplate.Template,
2065 UpdateStrategy: apps.DaemonSetUpdateStrategy{
2066 Type: apps.OnDeleteDaemonSetStrategyType,
2067 },
2068 },
2069 }, {
2070 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
2071 Spec: apps.DaemonSetSpec{
2072 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2073 Template: validHostNetPodTemplate.Template,
2074 UpdateStrategy: apps.DaemonSetUpdateStrategy{
2075 Type: apps.OnDeleteDaemonSetStrategyType,
2076 },
2077 },
2078 },
2079 }
2080 for _, successCase := range successCases {
2081 if errs := ValidateDaemonSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
2082 t.Errorf("expected success: %v", errs)
2083 }
2084 }
2085
2086 errorCases := map[string]apps.DaemonSet{
2087 "zero-length ID": {
2088 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
2089 Spec: apps.DaemonSetSpec{
2090 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2091 Template: validPodTemplate.Template,
2092 },
2093 },
2094 "missing-namespace": {
2095 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
2096 Spec: apps.DaemonSetSpec{
2097 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2098 Template: validPodTemplate.Template,
2099 },
2100 },
2101 "nil selector": {
2102 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2103 Spec: apps.DaemonSetSpec{
2104 Template: validPodTemplate.Template,
2105 },
2106 },
2107 "empty selector": {
2108 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2109 Spec: apps.DaemonSetSpec{
2110 Selector: &metav1.LabelSelector{},
2111 Template: validPodTemplate.Template,
2112 },
2113 },
2114 "selector_doesnt_match": {
2115 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2116 Spec: apps.DaemonSetSpec{
2117 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
2118 Template: validPodTemplate.Template,
2119 },
2120 },
2121 "invalid template": {
2122 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2123 Spec: apps.DaemonSetSpec{
2124 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2125 },
2126 },
2127 "invalid_label": {
2128 ObjectMeta: metav1.ObjectMeta{
2129 Name: "abc-123",
2130 Namespace: metav1.NamespaceDefault,
2131 Labels: map[string]string{
2132 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
2133 },
2134 },
2135 Spec: apps.DaemonSetSpec{
2136 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2137 Template: validPodTemplate.Template,
2138 },
2139 },
2140 "invalid_label 2": {
2141 ObjectMeta: metav1.ObjectMeta{
2142 Name: "abc-123",
2143 Namespace: metav1.NamespaceDefault,
2144 Labels: map[string]string{
2145 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
2146 },
2147 },
2148 Spec: apps.DaemonSetSpec{
2149 Template: invalidPodTemplate.Template,
2150 },
2151 },
2152 "invalid_annotation": {
2153 ObjectMeta: metav1.ObjectMeta{
2154 Name: "abc-123",
2155 Namespace: metav1.NamespaceDefault,
2156 Annotations: map[string]string{
2157 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
2158 },
2159 },
2160 Spec: apps.DaemonSetSpec{
2161 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2162 Template: validPodTemplate.Template,
2163 },
2164 },
2165 "invalid restart policy 1": {
2166 ObjectMeta: metav1.ObjectMeta{
2167 Name: "abc-123",
2168 Namespace: metav1.NamespaceDefault,
2169 },
2170 Spec: apps.DaemonSetSpec{
2171 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2172 Template: api.PodTemplateSpec{
2173 Spec: api.PodSpec{
2174 RestartPolicy: api.RestartPolicyOnFailure,
2175 DNSPolicy: api.DNSClusterFirst,
2176 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2177 },
2178 ObjectMeta: metav1.ObjectMeta{
2179 Labels: validSelector,
2180 },
2181 },
2182 },
2183 },
2184 "invalid restart policy 2": {
2185 ObjectMeta: metav1.ObjectMeta{
2186 Name: "abc-123",
2187 Namespace: metav1.NamespaceDefault,
2188 },
2189 Spec: apps.DaemonSetSpec{
2190 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2191 Template: api.PodTemplateSpec{
2192 Spec: api.PodSpec{
2193 RestartPolicy: api.RestartPolicyNever,
2194 DNSPolicy: api.DNSClusterFirst,
2195 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2196 },
2197 ObjectMeta: metav1.ObjectMeta{
2198 Labels: validSelector,
2199 },
2200 },
2201 },
2202 },
2203 "template may not contain ephemeral containers": {
2204 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2205 Spec: apps.DaemonSetSpec{
2206 Selector: &metav1.LabelSelector{MatchLabels: validSelector},
2207 Template: api.PodTemplateSpec{
2208 ObjectMeta: metav1.ObjectMeta{
2209 Labels: validSelector,
2210 },
2211 Spec: api.PodSpec{
2212 RestartPolicy: api.RestartPolicyAlways,
2213 DNSPolicy: api.DNSClusterFirst,
2214 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2215 EphemeralContainers: []api.EphemeralContainer{{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}},
2216 },
2217 },
2218 UpdateStrategy: apps.DaemonSetUpdateStrategy{
2219 Type: apps.OnDeleteDaemonSetStrategyType,
2220 },
2221 },
2222 },
2223 }
2224 for k, v := range errorCases {
2225 errs := ValidateDaemonSet(&v, corevalidation.PodValidationOptions{})
2226 if len(errs) == 0 {
2227 t.Errorf("expected failure for %s", k)
2228 }
2229 for i := range errs {
2230 field := errs[i].Field
2231 if !strings.HasPrefix(field, "spec.template.") &&
2232 !strings.HasPrefix(field, "spec.updateStrategy") &&
2233 field != "metadata.name" &&
2234 field != "metadata.namespace" &&
2235 field != "spec.selector" &&
2236 field != "spec.template" &&
2237 field != "GCEPersistentDisk.ReadOnly" &&
2238 field != "spec.template.labels" &&
2239 field != "metadata.annotations" &&
2240 field != "metadata.labels" {
2241 t.Errorf("%s: missing prefix for: %v", k, errs[i])
2242 }
2243 }
2244 }
2245 }
2246
2247 func validDeployment(tweaks ...func(d *apps.Deployment)) *apps.Deployment {
2248 d := &apps.Deployment{
2249 ObjectMeta: metav1.ObjectMeta{
2250 Name: "abc",
2251 Namespace: metav1.NamespaceDefault,
2252 },
2253 Spec: apps.DeploymentSpec{
2254 Selector: &metav1.LabelSelector{
2255 MatchLabels: map[string]string{
2256 "name": "abc",
2257 },
2258 },
2259 Strategy: apps.DeploymentStrategy{
2260 Type: apps.RollingUpdateDeploymentStrategyType,
2261 RollingUpdate: &apps.RollingUpdateDeployment{
2262 MaxSurge: intstr.FromInt32(1),
2263 MaxUnavailable: intstr.FromInt32(1),
2264 },
2265 },
2266 Template: api.PodTemplateSpec{
2267 ObjectMeta: metav1.ObjectMeta{
2268 Name: "abc",
2269 Namespace: metav1.NamespaceDefault,
2270 Labels: map[string]string{
2271 "name": "abc",
2272 },
2273 },
2274 Spec: api.PodSpec{
2275 RestartPolicy: api.RestartPolicyAlways,
2276 DNSPolicy: api.DNSDefault,
2277 Containers: []api.Container{{
2278 Name: "nginx",
2279 Image: "image",
2280 ImagePullPolicy: api.PullNever,
2281 TerminationMessagePolicy: api.TerminationMessageReadFile,
2282 }},
2283 },
2284 },
2285 RollbackTo: &apps.RollbackConfig{
2286 Revision: 1,
2287 },
2288 },
2289 }
2290
2291 for _, tweak := range tweaks {
2292 tweak(d)
2293 }
2294
2295 return d
2296 }
2297
2298 func TestValidateDeployment(t *testing.T) {
2299 successCases := []*apps.Deployment{
2300 validDeployment(),
2301 validDeployment(func(d *apps.Deployment) {
2302 d.Spec.Template.Spec.SecurityContext = &api.PodSecurityContext{
2303 HostNetwork: true,
2304 }
2305 d.Spec.Template.Spec.Containers[0].Ports = []api.ContainerPort{{
2306 ContainerPort: 12345,
2307 Protocol: api.ProtocolTCP,
2308 }}
2309 }),
2310 }
2311 for _, successCase := range successCases {
2312 if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
2313 t.Errorf("expected success: %v", errs)
2314 }
2315 }
2316
2317 errorCases := map[string]*apps.Deployment{}
2318 errorCases["metadata.name: Required value"] = &apps.Deployment{
2319 ObjectMeta: metav1.ObjectMeta{
2320 Namespace: metav1.NamespaceDefault,
2321 },
2322 }
2323
2324 invalidSelectorDeployment := validDeployment()
2325 invalidSelectorDeployment.Spec.Selector = &metav1.LabelSelector{
2326 MatchLabels: map[string]string{
2327 "name": "def",
2328 },
2329 }
2330 errorCases["`selector` does not match template `labels`"] = invalidSelectorDeployment
2331
2332
2333 invalidRestartPolicyDeployment := validDeployment()
2334 invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever
2335 errorCases["Unsupported value: \"Never\""] = invalidRestartPolicyDeployment
2336
2337
2338 invalidStrategyDeployment := validDeployment()
2339 invalidStrategyDeployment.Spec.Strategy.Type = apps.DeploymentStrategyType("randomType")
2340 errorCases[`supported values: "Recreate", "RollingUpdate"`] = invalidStrategyDeployment
2341
2342
2343 invalidRecreateDeployment := validDeployment()
2344 invalidRecreateDeployment.Spec.Strategy = apps.DeploymentStrategy{
2345 Type: apps.RecreateDeploymentStrategyType,
2346 RollingUpdate: &apps.RollingUpdateDeployment{},
2347 }
2348 errorCases["may not be specified when strategy `type` is 'Recreate'"] = invalidRecreateDeployment
2349
2350
2351 invalidMaxSurgeDeployment := validDeployment()
2352 invalidMaxSurgeDeployment.Spec.Strategy = apps.DeploymentStrategy{
2353 Type: apps.RollingUpdateDeploymentStrategyType,
2354 RollingUpdate: &apps.RollingUpdateDeployment{
2355 MaxSurge: intstr.FromString("20Percent"),
2356 },
2357 }
2358 errorCases["a valid percent string must be"] = invalidMaxSurgeDeployment
2359
2360
2361 invalidRollingUpdateDeployment := validDeployment()
2362 invalidRollingUpdateDeployment.Spec.Strategy = apps.DeploymentStrategy{
2363 Type: apps.RollingUpdateDeploymentStrategyType,
2364 RollingUpdate: &apps.RollingUpdateDeployment{
2365 MaxSurge: intstr.FromString("0%"),
2366 MaxUnavailable: intstr.FromInt32(0),
2367 },
2368 }
2369 errorCases["may not be 0 when `maxSurge` is 0"] = invalidRollingUpdateDeployment
2370
2371
2372 invalidMaxUnavailableDeployment := validDeployment()
2373 invalidMaxUnavailableDeployment.Spec.Strategy = apps.DeploymentStrategy{
2374 Type: apps.RollingUpdateDeploymentStrategyType,
2375 RollingUpdate: &apps.RollingUpdateDeployment{
2376 MaxUnavailable: intstr.FromString("110%"),
2377 },
2378 }
2379 errorCases["must not be greater than 100%"] = invalidMaxUnavailableDeployment
2380
2381
2382 invalidRollbackRevisionDeployment := validDeployment()
2383 invalidRollbackRevisionDeployment.Spec.RollbackTo.Revision = -3
2384 errorCases["must be greater than or equal to 0"] = invalidRollbackRevisionDeployment
2385
2386
2387 invalidProgressDeadlineDeployment := validDeployment()
2388 seconds := int32(600)
2389 invalidProgressDeadlineDeployment.Spec.ProgressDeadlineSeconds = &seconds
2390 invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds
2391 errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment
2392
2393
2394 invalidEphemeralContainersDeployment := validDeployment()
2395 invalidEphemeralContainersDeployment.Spec.Template.Spec.EphemeralContainers = []api.EphemeralContainer{{
2396 EphemeralContainerCommon: api.EphemeralContainerCommon{
2397 Name: "ec",
2398 Image: "image",
2399 ImagePullPolicy: "IfNotPresent",
2400 TerminationMessagePolicy: "File"},
2401 }}
2402 errorCases["ephemeral containers not allowed"] = invalidEphemeralContainersDeployment
2403
2404 for k, v := range errorCases {
2405 errs := ValidateDeployment(v, corevalidation.PodValidationOptions{})
2406 if len(errs) == 0 {
2407 t.Errorf("[%s] expected failure", k)
2408 } else if !strings.Contains(errs[0].Error(), k) {
2409 t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
2410 }
2411 }
2412 }
2413
2414 func TestValidateDeploymentStatus(t *testing.T) {
2415 collisionCount := int32(-3)
2416 tests := []struct {
2417 name string
2418
2419 replicas int32
2420 updatedReplicas int32
2421 readyReplicas int32
2422 availableReplicas int32
2423 observedGeneration int64
2424 collisionCount *int32
2425
2426 expectedErr bool
2427 }{{
2428 name: "valid status",
2429 replicas: 3,
2430 updatedReplicas: 3,
2431 readyReplicas: 2,
2432 availableReplicas: 1,
2433 observedGeneration: 2,
2434 expectedErr: false,
2435 }, {
2436 name: "invalid replicas",
2437 replicas: -1,
2438 updatedReplicas: 2,
2439 readyReplicas: 2,
2440 availableReplicas: 1,
2441 observedGeneration: 2,
2442 expectedErr: true,
2443 }, {
2444 name: "invalid updatedReplicas",
2445 replicas: 2,
2446 updatedReplicas: -1,
2447 readyReplicas: 2,
2448 availableReplicas: 1,
2449 observedGeneration: 2,
2450 expectedErr: true,
2451 }, {
2452 name: "invalid readyReplicas",
2453 replicas: 3,
2454 readyReplicas: -1,
2455 availableReplicas: 1,
2456 observedGeneration: 2,
2457 expectedErr: true,
2458 }, {
2459 name: "invalid availableReplicas",
2460 replicas: 3,
2461 readyReplicas: 3,
2462 availableReplicas: -1,
2463 observedGeneration: 2,
2464 expectedErr: true,
2465 }, {
2466 name: "invalid observedGeneration",
2467 replicas: 3,
2468 readyReplicas: 3,
2469 availableReplicas: 3,
2470 observedGeneration: -1,
2471 expectedErr: true,
2472 }, {
2473 name: "updatedReplicas greater than replicas",
2474 replicas: 3,
2475 updatedReplicas: 4,
2476 readyReplicas: 3,
2477 availableReplicas: 3,
2478 observedGeneration: 1,
2479 expectedErr: true,
2480 }, {
2481 name: "readyReplicas greater than replicas",
2482 replicas: 3,
2483 readyReplicas: 4,
2484 availableReplicas: 3,
2485 observedGeneration: 1,
2486 expectedErr: true,
2487 }, {
2488 name: "availableReplicas greater than replicas",
2489 replicas: 3,
2490 readyReplicas: 3,
2491 availableReplicas: 4,
2492 observedGeneration: 1,
2493 expectedErr: true,
2494 }, {
2495 name: "availableReplicas greater than readyReplicas",
2496 replicas: 3,
2497 readyReplicas: 2,
2498 availableReplicas: 3,
2499 observedGeneration: 1,
2500 expectedErr: true,
2501 }, {
2502 name: "invalid collisionCount",
2503 replicas: 3,
2504 observedGeneration: 1,
2505 collisionCount: &collisionCount,
2506 expectedErr: true,
2507 },
2508 }
2509
2510 for _, test := range tests {
2511 status := apps.DeploymentStatus{
2512 Replicas: test.replicas,
2513 UpdatedReplicas: test.updatedReplicas,
2514 ReadyReplicas: test.readyReplicas,
2515 AvailableReplicas: test.availableReplicas,
2516 ObservedGeneration: test.observedGeneration,
2517 CollisionCount: test.collisionCount,
2518 }
2519
2520 errs := ValidateDeploymentStatus(&status, field.NewPath("status"))
2521 if hasErr := len(errs) > 0; hasErr != test.expectedErr {
2522 errString := dump.Pretty(errs)
2523 t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
2524 }
2525 }
2526 }
2527
2528 func TestValidateDeploymentStatusUpdate(t *testing.T) {
2529 collisionCount := int32(1)
2530 otherCollisionCount := int32(2)
2531 tests := []struct {
2532 name string
2533
2534 from, to apps.DeploymentStatus
2535
2536 expectedErr bool
2537 }{{
2538 name: "increase: valid update",
2539 from: apps.DeploymentStatus{
2540 CollisionCount: nil,
2541 },
2542 to: apps.DeploymentStatus{
2543 CollisionCount: &collisionCount,
2544 },
2545 expectedErr: false,
2546 }, {
2547 name: "stable: valid update",
2548 from: apps.DeploymentStatus{
2549 CollisionCount: &collisionCount,
2550 },
2551 to: apps.DeploymentStatus{
2552 CollisionCount: &collisionCount,
2553 },
2554 expectedErr: false,
2555 }, {
2556 name: "unset: invalid update",
2557 from: apps.DeploymentStatus{
2558 CollisionCount: &collisionCount,
2559 },
2560 to: apps.DeploymentStatus{
2561 CollisionCount: nil,
2562 },
2563 expectedErr: true,
2564 }, {
2565 name: "decrease: invalid update",
2566 from: apps.DeploymentStatus{
2567 CollisionCount: &otherCollisionCount,
2568 },
2569 to: apps.DeploymentStatus{
2570 CollisionCount: &collisionCount,
2571 },
2572 expectedErr: true,
2573 },
2574 }
2575
2576 for _, test := range tests {
2577 meta := metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault, ResourceVersion: "1"}
2578 from := &apps.Deployment{
2579 ObjectMeta: meta,
2580 Status: test.from,
2581 }
2582 to := &apps.Deployment{
2583 ObjectMeta: meta,
2584 Status: test.to,
2585 }
2586
2587 errs := ValidateDeploymentStatusUpdate(to, from)
2588 if hasErr := len(errs) > 0; hasErr != test.expectedErr {
2589 errString := dump.Pretty(errs)
2590 t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString)
2591 }
2592 }
2593 }
2594
2595 func validDeploymentRollback() *apps.DeploymentRollback {
2596 return &apps.DeploymentRollback{
2597 Name: "abc",
2598 UpdatedAnnotations: map[string]string{
2599 "created-by": "abc",
2600 },
2601 RollbackTo: apps.RollbackConfig{
2602 Revision: 1,
2603 },
2604 }
2605 }
2606
2607 func TestValidateDeploymentUpdate(t *testing.T) {
2608 validLabels := map[string]string{"a": "b"}
2609 validPodTemplate := api.PodTemplate{
2610 Template: api.PodTemplateSpec{
2611 ObjectMeta: metav1.ObjectMeta{
2612 Labels: validLabels,
2613 },
2614 Spec: api.PodSpec{
2615 RestartPolicy: api.RestartPolicyAlways,
2616 DNSPolicy: api.DNSClusterFirst,
2617 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2618 },
2619 },
2620 }
2621 readWriteVolumePodTemplate := api.PodTemplate{
2622 Template: api.PodTemplateSpec{
2623 ObjectMeta: metav1.ObjectMeta{
2624 Labels: validLabels,
2625 },
2626 Spec: api.PodSpec{
2627 RestartPolicy: api.RestartPolicyAlways,
2628 DNSPolicy: api.DNSClusterFirst,
2629 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2630 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
2631 },
2632 },
2633 }
2634 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
2635 invalidPodTemplate := api.PodTemplate{
2636 Template: api.PodTemplateSpec{
2637 Spec: api.PodSpec{
2638
2639 RestartPolicy: api.RestartPolicyAlways,
2640 DNSPolicy: api.DNSClusterFirst,
2641 },
2642 ObjectMeta: metav1.ObjectMeta{
2643 Labels: invalidLabels,
2644 },
2645 },
2646 }
2647 type depUpdateTest struct {
2648 old apps.Deployment
2649 update apps.Deployment
2650 expectedErrNum int
2651 enableSkipReadOnlyValidationGCE bool
2652 }
2653 successCases := map[string]depUpdateTest{
2654 "positive replicas": {
2655 old: apps.Deployment{
2656 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2657 Spec: apps.DeploymentSpec{
2658 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2659 Template: validPodTemplate.Template,
2660 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2661 },
2662 },
2663 update: apps.Deployment{
2664 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2665 Spec: apps.DeploymentSpec{
2666 Replicas: 1,
2667 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2668 Template: readWriteVolumePodTemplate.Template,
2669 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2670 },
2671 },
2672 },
2673 "Read-write volume verification": {
2674 enableSkipReadOnlyValidationGCE: true,
2675 old: apps.Deployment{
2676 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2677 Spec: apps.DeploymentSpec{
2678 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2679 Template: validPodTemplate.Template,
2680 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2681 },
2682 },
2683 update: apps.Deployment{
2684 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2685 Spec: apps.DeploymentSpec{
2686 Replicas: 2,
2687 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2688 Template: readWriteVolumePodTemplate.Template,
2689 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2690 },
2691 },
2692 },
2693 }
2694 for testName, successCase := range successCases {
2695 t.Run(testName, func(t *testing.T) {
2696 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
2697
2698 successCase.old.ObjectMeta.ResourceVersion = "1"
2699 successCase.update.ObjectMeta.ResourceVersion = "2"
2700
2701 if successCase.expectedErrNum > 0 {
2702 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
2703 }
2704 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
2705 t.Errorf("%q has incorrect test setup with no resource version set", testName)
2706 }
2707
2708 if errs := ValidateDeploymentUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
2709 t.Errorf("%q expected no error, but got: %v", testName, errs)
2710 }
2711 })
2712 errorCases := map[string]depUpdateTest{
2713 "more than one read/write": {
2714 old: apps.Deployment{
2715 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
2716 Spec: apps.DeploymentSpec{
2717 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2718 Template: validPodTemplate.Template,
2719 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2720 },
2721 },
2722 update: apps.Deployment{
2723 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2724 Spec: apps.DeploymentSpec{
2725 Replicas: 2,
2726 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2727 Template: readWriteVolumePodTemplate.Template,
2728 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2729 },
2730 },
2731 expectedErrNum: 2,
2732 },
2733 "invalid selector": {
2734 old: apps.Deployment{
2735 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
2736 Spec: apps.DeploymentSpec{
2737 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2738 Template: validPodTemplate.Template,
2739 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2740 },
2741 },
2742 update: apps.Deployment{
2743 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2744 Spec: apps.DeploymentSpec{
2745 Replicas: 2,
2746 Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
2747 Template: validPodTemplate.Template,
2748 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2749 },
2750 },
2751 expectedErrNum: 3,
2752 },
2753 "invalid pod": {
2754 old: apps.Deployment{
2755 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
2756 Spec: apps.DeploymentSpec{
2757 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2758 Template: validPodTemplate.Template,
2759 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2760 },
2761 },
2762 update: apps.Deployment{
2763 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2764 Spec: apps.DeploymentSpec{
2765 Replicas: 2,
2766 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2767 Template: invalidPodTemplate.Template,
2768 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2769 },
2770 },
2771 expectedErrNum: 4,
2772 },
2773 "negative replicas": {
2774 old: apps.Deployment{
2775 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2776 Spec: apps.DeploymentSpec{
2777 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2778 Template: validPodTemplate.Template,
2779 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2780 },
2781 },
2782 update: apps.Deployment{
2783 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2784 Spec: apps.DeploymentSpec{
2785 Replicas: -1,
2786 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2787 Template: readWriteVolumePodTemplate.Template,
2788 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType},
2789 },
2790 },
2791 expectedErrNum: 1,
2792 },
2793 }
2794 for testName, errorCase := range errorCases {
2795 t.Run(testName, func(t *testing.T) {
2796 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
2797
2798 errorCase.old.ObjectMeta.ResourceVersion = "1"
2799 errorCase.update.ObjectMeta.ResourceVersion = "2"
2800
2801 if errorCase.expectedErrNum <= 0 {
2802 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
2803 }
2804 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
2805 t.Errorf("%q has incorrect test setup with no resource version set", testName)
2806 }
2807
2808 if errs := ValidateDeploymentUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
2809 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
2810 } else {
2811 t.Logf("(PASS) %q got errors %v", testName, errs)
2812 }
2813 })
2814 }
2815 }
2816 }
2817
2818 func TestValidateDeploymentRollback(t *testing.T) {
2819 noAnnotation := validDeploymentRollback()
2820 noAnnotation.UpdatedAnnotations = nil
2821 successCases := []*apps.DeploymentRollback{
2822 validDeploymentRollback(),
2823 noAnnotation,
2824 }
2825 for _, successCase := range successCases {
2826 if errs := ValidateDeploymentRollback(successCase); len(errs) != 0 {
2827 t.Errorf("expected success: %v", errs)
2828 }
2829 }
2830
2831 errorCases := map[string]*apps.DeploymentRollback{}
2832 invalidNoName := validDeploymentRollback()
2833 invalidNoName.Name = ""
2834 errorCases["name: Required value"] = invalidNoName
2835
2836 for k, v := range errorCases {
2837 errs := ValidateDeploymentRollback(v)
2838 if len(errs) == 0 {
2839 t.Errorf("[%s] expected failure", k)
2840 } else if !strings.Contains(errs[0].Error(), k) {
2841 t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
2842 }
2843 }
2844 }
2845
2846 func TestValidateReplicaSetStatus(t *testing.T) {
2847 tests := []struct {
2848 name string
2849
2850 replicas int32
2851 fullyLabeledReplicas int32
2852 readyReplicas int32
2853 availableReplicas int32
2854 observedGeneration int64
2855
2856 expectedErr bool
2857 }{{
2858 name: "valid status",
2859 replicas: 3,
2860 fullyLabeledReplicas: 3,
2861 readyReplicas: 2,
2862 availableReplicas: 1,
2863 observedGeneration: 2,
2864 expectedErr: false,
2865 }, {
2866 name: "invalid replicas",
2867 replicas: -1,
2868 fullyLabeledReplicas: 3,
2869 readyReplicas: 2,
2870 availableReplicas: 1,
2871 observedGeneration: 2,
2872 expectedErr: true,
2873 }, {
2874 name: "invalid fullyLabeledReplicas",
2875 replicas: 3,
2876 fullyLabeledReplicas: -1,
2877 readyReplicas: 2,
2878 availableReplicas: 1,
2879 observedGeneration: 2,
2880 expectedErr: true,
2881 }, {
2882 name: "invalid readyReplicas",
2883 replicas: 3,
2884 fullyLabeledReplicas: 3,
2885 readyReplicas: -1,
2886 availableReplicas: 1,
2887 observedGeneration: 2,
2888 expectedErr: true,
2889 }, {
2890 name: "invalid availableReplicas",
2891 replicas: 3,
2892 fullyLabeledReplicas: 3,
2893 readyReplicas: 3,
2894 availableReplicas: -1,
2895 observedGeneration: 2,
2896 expectedErr: true,
2897 }, {
2898 name: "invalid observedGeneration",
2899 replicas: 3,
2900 fullyLabeledReplicas: 3,
2901 readyReplicas: 3,
2902 availableReplicas: 3,
2903 observedGeneration: -1,
2904 expectedErr: true,
2905 }, {
2906 name: "fullyLabeledReplicas greater than replicas",
2907 replicas: 3,
2908 fullyLabeledReplicas: 4,
2909 readyReplicas: 3,
2910 availableReplicas: 3,
2911 observedGeneration: 1,
2912 expectedErr: true,
2913 }, {
2914 name: "readyReplicas greater than replicas",
2915 replicas: 3,
2916 fullyLabeledReplicas: 3,
2917 readyReplicas: 4,
2918 availableReplicas: 3,
2919 observedGeneration: 1,
2920 expectedErr: true,
2921 }, {
2922 name: "availableReplicas greater than replicas",
2923 replicas: 3,
2924 fullyLabeledReplicas: 3,
2925 readyReplicas: 3,
2926 availableReplicas: 4,
2927 observedGeneration: 1,
2928 expectedErr: true,
2929 }, {
2930 name: "availableReplicas greater than readyReplicas",
2931 replicas: 3,
2932 fullyLabeledReplicas: 3,
2933 readyReplicas: 2,
2934 availableReplicas: 3,
2935 observedGeneration: 1,
2936 expectedErr: true,
2937 },
2938 }
2939
2940 for _, test := range tests {
2941 status := apps.ReplicaSetStatus{
2942 Replicas: test.replicas,
2943 FullyLabeledReplicas: test.fullyLabeledReplicas,
2944 ReadyReplicas: test.readyReplicas,
2945 AvailableReplicas: test.availableReplicas,
2946 ObservedGeneration: test.observedGeneration,
2947 }
2948
2949 if hasErr := len(ValidateReplicaSetStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr {
2950 t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr)
2951 }
2952 }
2953 }
2954
2955 func TestValidateReplicaSetStatusUpdate(t *testing.T) {
2956 validLabels := map[string]string{"a": "b"}
2957 validPodTemplate := api.PodTemplate{
2958 Template: api.PodTemplateSpec{
2959 ObjectMeta: metav1.ObjectMeta{
2960 Labels: validLabels,
2961 },
2962 Spec: api.PodSpec{
2963 RestartPolicy: api.RestartPolicyAlways,
2964 DNSPolicy: api.DNSClusterFirst,
2965 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
2966 },
2967 },
2968 }
2969 type rcUpdateTest struct {
2970 old apps.ReplicaSet
2971 update apps.ReplicaSet
2972 }
2973 successCases := []rcUpdateTest{{
2974 old: apps.ReplicaSet{
2975 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2976 Spec: apps.ReplicaSetSpec{
2977 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2978 Template: validPodTemplate.Template,
2979 },
2980 Status: apps.ReplicaSetStatus{
2981 Replicas: 2,
2982 },
2983 },
2984 update: apps.ReplicaSet{
2985 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
2986 Spec: apps.ReplicaSetSpec{
2987 Replicas: 3,
2988 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
2989 Template: validPodTemplate.Template,
2990 },
2991 Status: apps.ReplicaSetStatus{
2992 Replicas: 4,
2993 },
2994 },
2995 },
2996 }
2997 for _, successCase := range successCases {
2998 successCase.old.ObjectMeta.ResourceVersion = "1"
2999 successCase.update.ObjectMeta.ResourceVersion = "1"
3000 if errs := ValidateReplicaSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
3001 t.Errorf("expected success: %v", errs)
3002 }
3003 }
3004 errorCases := map[string]rcUpdateTest{
3005 "negative replicas": {
3006 old: apps.ReplicaSet{
3007 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
3008 Spec: apps.ReplicaSetSpec{
3009 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3010 Template: validPodTemplate.Template,
3011 },
3012 Status: apps.ReplicaSetStatus{
3013 Replicas: 3,
3014 },
3015 },
3016 update: apps.ReplicaSet{
3017 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3018 Spec: apps.ReplicaSetSpec{
3019 Replicas: 2,
3020 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3021 Template: validPodTemplate.Template,
3022 },
3023 Status: apps.ReplicaSetStatus{
3024 Replicas: -3,
3025 },
3026 },
3027 },
3028 }
3029 for testName, errorCase := range errorCases {
3030 if errs := ValidateReplicaSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
3031 t.Errorf("expected failure: %s", testName)
3032 }
3033 }
3034
3035 }
3036
3037 func TestValidateReplicaSetUpdate(t *testing.T) {
3038 validLabels := map[string]string{"a": "b"}
3039 validPodTemplate := api.PodTemplate{
3040 Template: api.PodTemplateSpec{
3041 ObjectMeta: metav1.ObjectMeta{
3042 Labels: validLabels,
3043 },
3044 Spec: api.PodSpec{
3045 RestartPolicy: api.RestartPolicyAlways,
3046 DNSPolicy: api.DNSClusterFirst,
3047 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3048 },
3049 },
3050 }
3051 readWriteVolumePodTemplate := api.PodTemplate{
3052 Template: api.PodTemplateSpec{
3053 ObjectMeta: metav1.ObjectMeta{
3054 Labels: validLabels,
3055 },
3056 Spec: api.PodSpec{
3057 RestartPolicy: api.RestartPolicyAlways,
3058 DNSPolicy: api.DNSClusterFirst,
3059 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3060 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
3061 },
3062 },
3063 }
3064 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
3065 invalidPodTemplate := api.PodTemplate{
3066 Template: api.PodTemplateSpec{
3067 Spec: api.PodSpec{
3068 RestartPolicy: api.RestartPolicyAlways,
3069 DNSPolicy: api.DNSClusterFirst,
3070 },
3071 ObjectMeta: metav1.ObjectMeta{
3072 Labels: invalidLabels,
3073 },
3074 },
3075 }
3076 type rcUpdateTest struct {
3077 old apps.ReplicaSet
3078 update apps.ReplicaSet
3079 expectedErrNum int
3080 enableSkipReadOnlyValidationGCE bool
3081 }
3082 successCases := map[string]rcUpdateTest{
3083 "positive replicas": {
3084 old: apps.ReplicaSet{
3085 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3086 Spec: apps.ReplicaSetSpec{
3087 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3088 Template: validPodTemplate.Template,
3089 },
3090 },
3091 update: apps.ReplicaSet{
3092 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3093 Spec: apps.ReplicaSetSpec{
3094 Replicas: 3,
3095 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3096 Template: validPodTemplate.Template,
3097 },
3098 },
3099 },
3100 "Read-write volume verification": {
3101 enableSkipReadOnlyValidationGCE: true,
3102 old: apps.ReplicaSet{
3103 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3104 Spec: apps.ReplicaSetSpec{
3105 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3106 Template: validPodTemplate.Template,
3107 },
3108 },
3109 update: apps.ReplicaSet{
3110 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3111 Spec: apps.ReplicaSetSpec{
3112 Replicas: 3,
3113 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3114 Template: readWriteVolumePodTemplate.Template,
3115 },
3116 },
3117 },
3118 }
3119 for testName, successCase := range successCases {
3120 t.Run(testName, func(t *testing.T) {
3121 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, successCase.enableSkipReadOnlyValidationGCE)()
3122
3123 successCase.old.ObjectMeta.ResourceVersion = "1"
3124 successCase.update.ObjectMeta.ResourceVersion = "2"
3125
3126 if successCase.expectedErrNum > 0 {
3127 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum)
3128 }
3129 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 {
3130 t.Errorf("%q has incorrect test setup with no resource version set", testName)
3131 }
3132
3133 if errs := ValidateReplicaSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 {
3134 t.Errorf("%q expected no error, but got: %v", testName, errs)
3135 }
3136 })
3137 }
3138 errorCases := map[string]rcUpdateTest{
3139 "more than one read/write": {
3140 old: apps.ReplicaSet{
3141 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
3142 Spec: apps.ReplicaSetSpec{
3143 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3144 Template: validPodTemplate.Template,
3145 },
3146 },
3147 update: apps.ReplicaSet{
3148 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3149 Spec: apps.ReplicaSetSpec{
3150 Replicas: 2,
3151 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3152 Template: readWriteVolumePodTemplate.Template,
3153 },
3154 },
3155 expectedErrNum: 2,
3156 },
3157 "invalid selector": {
3158 old: apps.ReplicaSet{
3159 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
3160 Spec: apps.ReplicaSetSpec{
3161 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3162 Template: validPodTemplate.Template,
3163 },
3164 },
3165 update: apps.ReplicaSet{
3166 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3167 Spec: apps.ReplicaSetSpec{
3168 Replicas: 2,
3169 Selector: &metav1.LabelSelector{MatchLabels: invalidLabels},
3170 Template: validPodTemplate.Template,
3171 },
3172 },
3173 expectedErrNum: 3,
3174 },
3175 "invalid pod": {
3176 old: apps.ReplicaSet{
3177 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
3178 Spec: apps.ReplicaSetSpec{
3179 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3180 Template: validPodTemplate.Template,
3181 },
3182 },
3183 update: apps.ReplicaSet{
3184 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3185 Spec: apps.ReplicaSetSpec{
3186 Replicas: 2,
3187 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3188 Template: invalidPodTemplate.Template,
3189 },
3190 },
3191 expectedErrNum: 4,
3192 },
3193 "negative replicas": {
3194 old: apps.ReplicaSet{
3195 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3196 Spec: apps.ReplicaSetSpec{
3197 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3198 Template: validPodTemplate.Template,
3199 },
3200 },
3201 update: apps.ReplicaSet{
3202 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3203 Spec: apps.ReplicaSetSpec{
3204 Replicas: -1,
3205 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3206 Template: validPodTemplate.Template,
3207 },
3208 },
3209 expectedErrNum: 1,
3210 },
3211 }
3212 for testName, errorCase := range errorCases {
3213 t.Run(testName, func(t *testing.T) {
3214 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SkipReadOnlyValidationGCE, errorCase.enableSkipReadOnlyValidationGCE)()
3215
3216 errorCase.old.ObjectMeta.ResourceVersion = "1"
3217 errorCase.update.ObjectMeta.ResourceVersion = "2"
3218
3219 if errorCase.expectedErrNum <= 0 {
3220 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum)
3221 }
3222 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 {
3223 t.Errorf("%q has incorrect test setup with no resource version set", testName)
3224 }
3225
3226 if errs := ValidateReplicaSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum {
3227 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs)
3228 } else {
3229 t.Logf("(PASS) %q got errors %v", testName, errs)
3230 }
3231 })
3232 }
3233 }
3234
3235 func TestValidateReplicaSet(t *testing.T) {
3236 validLabels := map[string]string{"a": "b"}
3237 validPodTemplate := api.PodTemplate{
3238 Template: api.PodTemplateSpec{
3239 ObjectMeta: metav1.ObjectMeta{
3240 Labels: validLabels,
3241 },
3242 Spec: api.PodSpec{
3243 RestartPolicy: api.RestartPolicyAlways,
3244 DNSPolicy: api.DNSClusterFirst,
3245 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3246 },
3247 },
3248 }
3249 validHostNetPodTemplate := api.PodTemplate{
3250 Template: api.PodTemplateSpec{
3251 ObjectMeta: metav1.ObjectMeta{
3252 Labels: validLabels,
3253 },
3254 Spec: api.PodSpec{
3255 SecurityContext: &api.PodSecurityContext{
3256 HostNetwork: true,
3257 },
3258 RestartPolicy: api.RestartPolicyAlways,
3259 DNSPolicy: api.DNSClusterFirst,
3260 Containers: []api.Container{{
3261 Name: "abc",
3262 Image: "image",
3263 ImagePullPolicy: "IfNotPresent",
3264 TerminationMessagePolicy: api.TerminationMessageReadFile,
3265 Ports: []api.ContainerPort{{
3266 ContainerPort: 12345,
3267 Protocol: api.ProtocolTCP,
3268 }},
3269 }},
3270 },
3271 },
3272 }
3273 readWriteVolumePodTemplate := api.PodTemplate{
3274 Template: api.PodTemplateSpec{
3275 ObjectMeta: metav1.ObjectMeta{
3276 Labels: validLabels,
3277 },
3278 Spec: api.PodSpec{
3279 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
3280 RestartPolicy: api.RestartPolicyAlways,
3281 DNSPolicy: api.DNSClusterFirst,
3282 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3283 },
3284 },
3285 }
3286 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
3287 invalidPodTemplate := api.PodTemplate{
3288 Template: api.PodTemplateSpec{
3289 Spec: api.PodSpec{
3290 RestartPolicy: api.RestartPolicyAlways,
3291 DNSPolicy: api.DNSClusterFirst,
3292 },
3293 ObjectMeta: metav1.ObjectMeta{
3294 Labels: invalidLabels,
3295 },
3296 },
3297 }
3298 successCases := []apps.ReplicaSet{{
3299 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3300 Spec: apps.ReplicaSetSpec{
3301 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3302 Template: validPodTemplate.Template,
3303 },
3304 }, {
3305 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
3306 Spec: apps.ReplicaSetSpec{
3307 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3308 Template: validPodTemplate.Template,
3309 },
3310 }, {
3311 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault},
3312 Spec: apps.ReplicaSetSpec{
3313 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3314 Template: validHostNetPodTemplate.Template,
3315 },
3316 }, {
3317 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
3318 Spec: apps.ReplicaSetSpec{
3319 Replicas: 1,
3320 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3321 Template: readWriteVolumePodTemplate.Template,
3322 },
3323 },
3324 }
3325 for _, successCase := range successCases {
3326 if errs := ValidateReplicaSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 {
3327 t.Errorf("expected success: %v", errs)
3328 }
3329 }
3330
3331 errorCases := map[string]apps.ReplicaSet{
3332 "zero-length ID": {
3333 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault},
3334 Spec: apps.ReplicaSetSpec{
3335 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3336 Template: validPodTemplate.Template,
3337 },
3338 },
3339 "missing-namespace": {
3340 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"},
3341 Spec: apps.ReplicaSetSpec{
3342 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3343 Template: validPodTemplate.Template,
3344 },
3345 },
3346 "empty selector": {
3347 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3348 Spec: apps.ReplicaSetSpec{
3349 Template: validPodTemplate.Template,
3350 },
3351 },
3352 "selector_doesnt_match": {
3353 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3354 Spec: apps.ReplicaSetSpec{
3355 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
3356 Template: validPodTemplate.Template,
3357 },
3358 },
3359 "invalid manifest": {
3360 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3361 Spec: apps.ReplicaSetSpec{
3362 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3363 },
3364 },
3365 "read-write persistent disk with > 1 pod": {
3366 ObjectMeta: metav1.ObjectMeta{Name: "abc"},
3367 Spec: apps.ReplicaSetSpec{
3368 Replicas: 2,
3369 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3370 Template: readWriteVolumePodTemplate.Template,
3371 },
3372 },
3373 "negative_replicas": {
3374 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
3375 Spec: apps.ReplicaSetSpec{
3376 Replicas: -1,
3377 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3378 },
3379 },
3380 "invalid_label": {
3381 ObjectMeta: metav1.ObjectMeta{
3382 Name: "abc-123",
3383 Namespace: metav1.NamespaceDefault,
3384 Labels: map[string]string{
3385 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
3386 },
3387 },
3388 Spec: apps.ReplicaSetSpec{
3389 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3390 Template: validPodTemplate.Template,
3391 },
3392 },
3393 "invalid_label 2": {
3394 ObjectMeta: metav1.ObjectMeta{
3395 Name: "abc-123",
3396 Namespace: metav1.NamespaceDefault,
3397 Labels: map[string]string{
3398 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
3399 },
3400 },
3401 Spec: apps.ReplicaSetSpec{
3402 Template: invalidPodTemplate.Template,
3403 },
3404 },
3405 "invalid_annotation": {
3406 ObjectMeta: metav1.ObjectMeta{
3407 Name: "abc-123",
3408 Namespace: metav1.NamespaceDefault,
3409 Annotations: map[string]string{
3410 "NoUppercaseOrSpecialCharsLike=Equals": "bar",
3411 },
3412 },
3413 Spec: apps.ReplicaSetSpec{
3414 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3415 Template: validPodTemplate.Template,
3416 },
3417 },
3418 "invalid restart policy 1": {
3419 ObjectMeta: metav1.ObjectMeta{
3420 Name: "abc-123",
3421 Namespace: metav1.NamespaceDefault,
3422 },
3423 Spec: apps.ReplicaSetSpec{
3424 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3425 Template: api.PodTemplateSpec{
3426 Spec: api.PodSpec{
3427 RestartPolicy: api.RestartPolicyOnFailure,
3428 DNSPolicy: api.DNSClusterFirst,
3429 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3430 },
3431 ObjectMeta: metav1.ObjectMeta{
3432 Labels: validLabels,
3433 },
3434 },
3435 },
3436 },
3437 "invalid restart policy 2": {
3438 ObjectMeta: metav1.ObjectMeta{
3439 Name: "abc-123",
3440 Namespace: metav1.NamespaceDefault,
3441 },
3442 Spec: apps.ReplicaSetSpec{
3443 Selector: &metav1.LabelSelector{MatchLabels: validLabels},
3444 Template: api.PodTemplateSpec{
3445 Spec: api.PodSpec{
3446 RestartPolicy: api.RestartPolicyNever,
3447 DNSPolicy: api.DNSClusterFirst,
3448 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}},
3449 },
3450 ObjectMeta: metav1.ObjectMeta{
3451 Labels: validLabels,
3452 },
3453 },
3454 },
3455 },
3456 }
3457 for k, v := range errorCases {
3458 errs := ValidateReplicaSet(&v, corevalidation.PodValidationOptions{})
3459 if len(errs) == 0 {
3460 t.Errorf("expected failure for %s", k)
3461 }
3462 for i := range errs {
3463 field := errs[i].Field
3464 if !strings.HasPrefix(field, "spec.template.") &&
3465 field != "metadata.name" &&
3466 field != "metadata.namespace" &&
3467 field != "spec.selector" &&
3468 field != "spec.template" &&
3469 field != "GCEPersistentDisk.ReadOnly" &&
3470 field != "spec.replicas" &&
3471 field != "spec.template.labels" &&
3472 field != "metadata.annotations" &&
3473 field != "metadata.labels" &&
3474 field != "status.replicas" {
3475 t.Errorf("%s: missing prefix for: %v", k, errs[i])
3476 }
3477 }
3478 }
3479 }
3480
3481 func TestDaemonSetUpdateMaxSurge(t *testing.T) {
3482 testCases := map[string]struct {
3483 ds *apps.RollingUpdateDaemonSet
3484 expectError bool
3485 }{
3486 "invalid: unset": {
3487 ds: &apps.RollingUpdateDaemonSet{},
3488 expectError: true,
3489 },
3490 "invalid: zero percent": {
3491 ds: &apps.RollingUpdateDaemonSet{
3492 MaxUnavailable: intstr.FromString("0%"),
3493 },
3494 expectError: true,
3495 },
3496 "invalid: zero": {
3497 ds: &apps.RollingUpdateDaemonSet{
3498 MaxUnavailable: intstr.FromInt32(0),
3499 },
3500 expectError: true,
3501 },
3502 "valid: one": {
3503 ds: &apps.RollingUpdateDaemonSet{
3504 MaxUnavailable: intstr.FromInt32(1),
3505 },
3506 },
3507 "valid: one percent": {
3508 ds: &apps.RollingUpdateDaemonSet{
3509 MaxUnavailable: intstr.FromString("1%"),
3510 },
3511 },
3512 "valid: 100%": {
3513 ds: &apps.RollingUpdateDaemonSet{
3514 MaxUnavailable: intstr.FromString("100%"),
3515 },
3516 },
3517 "invalid: greater than 100%": {
3518 ds: &apps.RollingUpdateDaemonSet{
3519 MaxUnavailable: intstr.FromString("101%"),
3520 },
3521 expectError: true,
3522 },
3523
3524 "valid: surge and unavailable set": {
3525 ds: &apps.RollingUpdateDaemonSet{
3526 MaxUnavailable: intstr.FromString("1%"),
3527 MaxSurge: intstr.FromString("1%"),
3528 },
3529 expectError: true,
3530 },
3531
3532 "invalid: surge enabled, unavailable zero percent": {
3533 ds: &apps.RollingUpdateDaemonSet{
3534 MaxUnavailable: intstr.FromString("0%"),
3535 },
3536 expectError: true,
3537 },
3538 "invalid: surge enabled, unavailable zero": {
3539 ds: &apps.RollingUpdateDaemonSet{
3540 MaxUnavailable: intstr.FromInt32(0),
3541 },
3542 expectError: true,
3543 },
3544 "valid: surge enabled, unavailable one": {
3545 ds: &apps.RollingUpdateDaemonSet{
3546 MaxUnavailable: intstr.FromInt32(1),
3547 },
3548 },
3549 "valid: surge enabled, unavailable one percent": {
3550 ds: &apps.RollingUpdateDaemonSet{
3551 MaxUnavailable: intstr.FromString("1%"),
3552 },
3553 },
3554 "valid: surge enabled, unavailable 100%": {
3555 ds: &apps.RollingUpdateDaemonSet{
3556 MaxUnavailable: intstr.FromString("100%"),
3557 },
3558 },
3559 "invalid: surge enabled, unavailable greater than 100%": {
3560 ds: &apps.RollingUpdateDaemonSet{
3561 MaxUnavailable: intstr.FromString("101%"),
3562 },
3563 expectError: true,
3564 },
3565
3566 "invalid: surge enabled, surge zero percent": {
3567 ds: &apps.RollingUpdateDaemonSet{
3568 MaxSurge: intstr.FromString("0%"),
3569 },
3570 expectError: true,
3571 },
3572 "invalid: surge enabled, surge zero": {
3573 ds: &apps.RollingUpdateDaemonSet{
3574 MaxSurge: intstr.FromInt32(0),
3575 },
3576 expectError: true,
3577 },
3578 "valid: surge enabled, surge one": {
3579 ds: &apps.RollingUpdateDaemonSet{
3580 MaxSurge: intstr.FromInt32(1),
3581 },
3582 },
3583 "valid: surge enabled, surge one percent": {
3584 ds: &apps.RollingUpdateDaemonSet{
3585 MaxSurge: intstr.FromString("1%"),
3586 },
3587 },
3588 "valid: surge enabled, surge 100%": {
3589 ds: &apps.RollingUpdateDaemonSet{
3590 MaxSurge: intstr.FromString("100%"),
3591 },
3592 },
3593 "invalid: surge enabled, surge greater than 100%": {
3594 ds: &apps.RollingUpdateDaemonSet{
3595 MaxSurge: intstr.FromString("101%"),
3596 },
3597 expectError: true,
3598 },
3599
3600 "invalid: surge enabled, surge and unavailable set": {
3601 ds: &apps.RollingUpdateDaemonSet{
3602 MaxUnavailable: intstr.FromString("1%"),
3603 MaxSurge: intstr.FromString("1%"),
3604 },
3605 expectError: true,
3606 },
3607
3608 "invalid: surge enabled, surge and unavailable zero percent": {
3609 ds: &apps.RollingUpdateDaemonSet{
3610 MaxUnavailable: intstr.FromString("0%"),
3611 MaxSurge: intstr.FromString("0%"),
3612 },
3613 expectError: true,
3614 },
3615 "invalid: surge enabled, surge and unavailable zero": {
3616 ds: &apps.RollingUpdateDaemonSet{
3617 MaxUnavailable: intstr.FromInt32(0),
3618 MaxSurge: intstr.FromInt32(0),
3619 },
3620 expectError: true,
3621 },
3622 "invalid: surge enabled, surge and unavailable mixed zero": {
3623 ds: &apps.RollingUpdateDaemonSet{
3624 MaxUnavailable: intstr.FromInt32(0),
3625 MaxSurge: intstr.FromString("0%"),
3626 },
3627 expectError: true,
3628 },
3629 }
3630 for tcName, tc := range testCases {
3631 t.Run(tcName, func(t *testing.T) {
3632 errs := ValidateRollingUpdateDaemonSet(tc.ds, field.NewPath("spec", "updateStrategy", "rollingUpdate"))
3633 if tc.expectError && len(errs) == 0 {
3634 t.Errorf("Unexpected success")
3635 }
3636 if !tc.expectError && len(errs) != 0 {
3637 t.Errorf("Unexpected error(s): %v", errs)
3638 }
3639 })
3640 }
3641 }
3642
View as plain text