k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/apps/validation/validation_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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, ""), // selector is empty, labels are not, so select doesn't match labels 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 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 // generateStatefulSetSpec generates a valid StatefulSet spec 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 // TestValidateStatefulSetMinReadySeconds tests the StatefulSet Spec's minReadySeconds field 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 // no containers specified 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 } 1579 successCases := map[string]dsUpdateTest{ 1580 "no change": { 1581 old: apps.DaemonSet{ 1582 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1583 Spec: apps.DaemonSetSpec{ 1584 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1585 TemplateGeneration: 1, 1586 Template: validPodTemplateAbc.Template, 1587 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1588 Type: apps.OnDeleteDaemonSetStrategyType, 1589 }, 1590 }, 1591 }, 1592 update: apps.DaemonSet{ 1593 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1594 Spec: apps.DaemonSetSpec{ 1595 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1596 TemplateGeneration: 1, 1597 Template: validPodTemplateAbc.Template, 1598 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1599 Type: apps.OnDeleteDaemonSetStrategyType, 1600 }, 1601 }, 1602 }, 1603 }, 1604 "change template and selector": { 1605 old: apps.DaemonSet{ 1606 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1607 Spec: apps.DaemonSetSpec{ 1608 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1609 TemplateGeneration: 2, 1610 Template: validPodTemplateAbc.Template, 1611 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1612 Type: apps.OnDeleteDaemonSetStrategyType, 1613 }, 1614 }, 1615 }, 1616 update: apps.DaemonSet{ 1617 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1618 Spec: apps.DaemonSetSpec{ 1619 Selector: &metav1.LabelSelector{MatchLabels: validSelector2}, 1620 TemplateGeneration: 3, 1621 Template: validPodTemplateAbc2.Template, 1622 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1623 Type: apps.OnDeleteDaemonSetStrategyType, 1624 }, 1625 }, 1626 }, 1627 }, 1628 "change template": { 1629 old: apps.DaemonSet{ 1630 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1631 Spec: apps.DaemonSetSpec{ 1632 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1633 TemplateGeneration: 3, 1634 Template: validPodTemplateAbc.Template, 1635 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1636 Type: apps.OnDeleteDaemonSetStrategyType, 1637 }, 1638 }, 1639 }, 1640 update: apps.DaemonSet{ 1641 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1642 Spec: apps.DaemonSetSpec{ 1643 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1644 TemplateGeneration: 4, 1645 Template: validPodTemplateNodeSelector.Template, 1646 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1647 Type: apps.OnDeleteDaemonSetStrategyType, 1648 }, 1649 }, 1650 }, 1651 }, 1652 "change container image name": { 1653 old: apps.DaemonSet{ 1654 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1655 Spec: apps.DaemonSetSpec{ 1656 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1657 TemplateGeneration: 1, 1658 Template: validPodTemplateAbc.Template, 1659 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1660 Type: apps.OnDeleteDaemonSetStrategyType, 1661 }, 1662 }, 1663 }, 1664 update: apps.DaemonSet{ 1665 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1666 Spec: apps.DaemonSetSpec{ 1667 Selector: &metav1.LabelSelector{MatchLabels: validSelector2}, 1668 TemplateGeneration: 2, 1669 Template: validPodTemplateDef.Template, 1670 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1671 Type: apps.OnDeleteDaemonSetStrategyType, 1672 }, 1673 }, 1674 }, 1675 }, 1676 "change update strategy": { 1677 old: apps.DaemonSet{ 1678 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1679 Spec: apps.DaemonSetSpec{ 1680 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1681 TemplateGeneration: 4, 1682 Template: validPodTemplateAbc.Template, 1683 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1684 Type: apps.OnDeleteDaemonSetStrategyType, 1685 }, 1686 }, 1687 }, 1688 update: apps.DaemonSet{ 1689 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1690 Spec: apps.DaemonSetSpec{ 1691 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1692 TemplateGeneration: 4, 1693 Template: validPodTemplateAbc.Template, 1694 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1695 Type: apps.RollingUpdateDaemonSetStrategyType, 1696 RollingUpdate: &apps.RollingUpdateDaemonSet{ 1697 MaxUnavailable: intstr.FromInt32(1), 1698 }, 1699 }, 1700 }, 1701 }, 1702 }, 1703 "unchanged templateGeneration upon semantically equal template update": { 1704 old: apps.DaemonSet{ 1705 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1706 Spec: apps.DaemonSetSpec{ 1707 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1708 TemplateGeneration: 4, 1709 Template: validPodTemplateAbc.Template, 1710 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1711 Type: apps.OnDeleteDaemonSetStrategyType, 1712 }, 1713 }, 1714 }, 1715 update: apps.DaemonSet{ 1716 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1717 Spec: apps.DaemonSetSpec{ 1718 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1719 TemplateGeneration: 4, 1720 Template: validPodTemplateAbcSemanticallyEqual.Template, 1721 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1722 Type: apps.RollingUpdateDaemonSetStrategyType, 1723 RollingUpdate: &apps.RollingUpdateDaemonSet{ 1724 MaxUnavailable: intstr.FromInt32(1), 1725 }, 1726 }, 1727 }, 1728 }, 1729 }, 1730 "Read-write volume verification": { 1731 old: apps.DaemonSet{ 1732 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1733 Spec: apps.DaemonSetSpec{ 1734 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1735 TemplateGeneration: 1, 1736 Template: validPodTemplateAbc.Template, 1737 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1738 Type: apps.OnDeleteDaemonSetStrategyType, 1739 }, 1740 }, 1741 }, 1742 update: apps.DaemonSet{ 1743 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1744 Spec: apps.DaemonSetSpec{ 1745 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1746 TemplateGeneration: 2, 1747 Template: readWriteVolumePodTemplate.Template, 1748 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1749 Type: apps.OnDeleteDaemonSetStrategyType, 1750 }, 1751 }, 1752 }, 1753 }, 1754 } 1755 for testName, successCase := range successCases { 1756 t.Run(testName, func(t *testing.T) { 1757 // ResourceVersion is required for updates. 1758 successCase.old.ObjectMeta.ResourceVersion = "1" 1759 successCase.update.ObjectMeta.ResourceVersion = "2" 1760 // Check test setup 1761 if successCase.expectedErrNum > 0 { 1762 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum) 1763 } 1764 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 { 1765 t.Errorf("%q has incorrect test setup with no resource version set", testName) 1766 } 1767 if errs := ValidateDaemonSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 { 1768 t.Errorf("%q expected no error, but got: %v", testName, errs) 1769 } 1770 }) 1771 } 1772 errorCases := map[string]dsUpdateTest{ 1773 "change daemon name": { 1774 old: apps.DaemonSet{ 1775 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 1776 Spec: apps.DaemonSetSpec{ 1777 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1778 TemplateGeneration: 1, 1779 Template: validPodTemplateAbc.Template, 1780 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1781 Type: apps.OnDeleteDaemonSetStrategyType, 1782 }, 1783 }, 1784 }, 1785 update: apps.DaemonSet{ 1786 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1787 Spec: apps.DaemonSetSpec{ 1788 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1789 TemplateGeneration: 1, 1790 Template: validPodTemplateAbc.Template, 1791 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1792 Type: apps.OnDeleteDaemonSetStrategyType, 1793 }, 1794 }, 1795 }, 1796 expectedErrNum: 1, 1797 }, 1798 "invalid selector": { 1799 old: apps.DaemonSet{ 1800 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1801 Spec: apps.DaemonSetSpec{ 1802 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1803 TemplateGeneration: 1, 1804 Template: validPodTemplateAbc.Template, 1805 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1806 Type: apps.OnDeleteDaemonSetStrategyType, 1807 }, 1808 }, 1809 }, 1810 update: apps.DaemonSet{ 1811 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1812 Spec: apps.DaemonSetSpec{ 1813 Selector: &metav1.LabelSelector{MatchLabels: invalidSelector}, 1814 TemplateGeneration: 1, 1815 Template: validPodTemplateAbc.Template, 1816 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1817 Type: apps.OnDeleteDaemonSetStrategyType, 1818 }, 1819 }, 1820 }, 1821 expectedErrNum: 1, 1822 }, 1823 "invalid pod": { 1824 old: apps.DaemonSet{ 1825 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1826 Spec: apps.DaemonSetSpec{ 1827 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1828 TemplateGeneration: 1, 1829 Template: validPodTemplateAbc.Template, 1830 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1831 Type: apps.OnDeleteDaemonSetStrategyType, 1832 }, 1833 }, 1834 }, 1835 update: apps.DaemonSet{ 1836 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1837 Spec: apps.DaemonSetSpec{ 1838 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1839 TemplateGeneration: 2, 1840 Template: invalidPodTemplate.Template, 1841 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1842 Type: apps.OnDeleteDaemonSetStrategyType, 1843 }, 1844 }, 1845 }, 1846 expectedErrNum: 1, 1847 }, 1848 "invalid update strategy": { 1849 old: apps.DaemonSet{ 1850 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1851 Spec: apps.DaemonSetSpec{ 1852 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1853 TemplateGeneration: 1, 1854 Template: validPodTemplateAbc.Template, 1855 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1856 Type: apps.OnDeleteDaemonSetStrategyType, 1857 }, 1858 }, 1859 }, 1860 update: apps.DaemonSet{ 1861 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1862 Spec: apps.DaemonSetSpec{ 1863 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1864 TemplateGeneration: 1, 1865 Template: validPodTemplateAbc.Template, 1866 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1867 Type: "Random", 1868 }, 1869 }, 1870 }, 1871 expectedErrNum: 1, 1872 }, 1873 "negative templateGeneration": { 1874 old: apps.DaemonSet{ 1875 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1876 Spec: apps.DaemonSetSpec{ 1877 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1878 TemplateGeneration: -1, 1879 Template: validPodTemplateAbc.Template, 1880 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1881 Type: apps.OnDeleteDaemonSetStrategyType, 1882 }, 1883 }, 1884 }, 1885 update: apps.DaemonSet{ 1886 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1887 Spec: apps.DaemonSetSpec{ 1888 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1889 TemplateGeneration: -1, 1890 Template: validPodTemplateAbc.Template, 1891 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1892 Type: apps.OnDeleteDaemonSetStrategyType, 1893 }, 1894 }, 1895 }, 1896 expectedErrNum: 1, 1897 }, 1898 "decreased templateGeneration": { 1899 old: apps.DaemonSet{ 1900 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1901 Spec: apps.DaemonSetSpec{ 1902 TemplateGeneration: 2, 1903 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1904 Template: validPodTemplateAbc.Template, 1905 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1906 Type: apps.OnDeleteDaemonSetStrategyType, 1907 }, 1908 }, 1909 }, 1910 update: apps.DaemonSet{ 1911 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1912 Spec: apps.DaemonSetSpec{ 1913 TemplateGeneration: 1, 1914 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1915 Template: validPodTemplateAbc.Template, 1916 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1917 Type: apps.OnDeleteDaemonSetStrategyType, 1918 }, 1919 }, 1920 }, 1921 expectedErrNum: 1, 1922 }, 1923 "unchanged templateGeneration upon template update": { 1924 old: apps.DaemonSet{ 1925 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1926 Spec: apps.DaemonSetSpec{ 1927 TemplateGeneration: 2, 1928 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 1929 Template: validPodTemplateAbc.Template, 1930 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1931 Type: apps.OnDeleteDaemonSetStrategyType, 1932 }, 1933 }, 1934 }, 1935 update: apps.DaemonSet{ 1936 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 1937 Spec: apps.DaemonSetSpec{ 1938 TemplateGeneration: 2, 1939 Selector: &metav1.LabelSelector{MatchLabels: validSelector2}, 1940 Template: validPodTemplateAbc2.Template, 1941 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 1942 Type: apps.OnDeleteDaemonSetStrategyType, 1943 }, 1944 }, 1945 }, 1946 expectedErrNum: 1, 1947 }, 1948 } 1949 for testName, errorCase := range errorCases { 1950 t.Run(testName, func(t *testing.T) { 1951 // ResourceVersion is required for updates. 1952 errorCase.old.ObjectMeta.ResourceVersion = "1" 1953 errorCase.update.ObjectMeta.ResourceVersion = "2" 1954 // Check test setup 1955 if errorCase.expectedErrNum <= 0 { 1956 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum) 1957 } 1958 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 { 1959 t.Errorf("%q has incorrect test setup with no resource version set", testName) 1960 } 1961 // Run the tests 1962 if errs := ValidateDaemonSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum { 1963 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs) 1964 } else { 1965 t.Logf("(PASS) %q got errors %v", testName, errs) 1966 } 1967 }) 1968 } 1969 } 1970 1971 func TestValidateDaemonSet(t *testing.T) { 1972 validSelector := map[string]string{"a": "b"} 1973 validPodTemplate := api.PodTemplate{ 1974 Template: api.PodTemplateSpec{ 1975 ObjectMeta: metav1.ObjectMeta{ 1976 Labels: validSelector, 1977 }, 1978 Spec: api.PodSpec{ 1979 RestartPolicy: api.RestartPolicyAlways, 1980 DNSPolicy: api.DNSClusterFirst, 1981 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 1982 }, 1983 }, 1984 } 1985 validHostNetPodTemplate := api.PodTemplate{ 1986 Template: api.PodTemplateSpec{ 1987 ObjectMeta: metav1.ObjectMeta{ 1988 Labels: validSelector, 1989 }, 1990 Spec: api.PodSpec{ 1991 SecurityContext: &api.PodSecurityContext{ 1992 HostNetwork: true, 1993 }, 1994 RestartPolicy: api.RestartPolicyAlways, 1995 DNSPolicy: api.DNSClusterFirst, 1996 Containers: []api.Container{{ 1997 Name: "abc", 1998 Image: "image", 1999 ImagePullPolicy: "IfNotPresent", 2000 TerminationMessagePolicy: api.TerminationMessageReadFile, 2001 Ports: []api.ContainerPort{{ 2002 ContainerPort: 12345, 2003 Protocol: api.ProtocolTCP, 2004 }}, 2005 }}, 2006 }, 2007 }, 2008 } 2009 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 2010 invalidPodTemplate := api.PodTemplate{ 2011 Template: api.PodTemplateSpec{ 2012 Spec: api.PodSpec{ 2013 RestartPolicy: api.RestartPolicyAlways, 2014 DNSPolicy: api.DNSClusterFirst, 2015 }, 2016 ObjectMeta: metav1.ObjectMeta{ 2017 Labels: invalidSelector, 2018 }, 2019 }, 2020 } 2021 successCases := []apps.DaemonSet{{ 2022 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2023 Spec: apps.DaemonSetSpec{ 2024 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2025 Template: validPodTemplate.Template, 2026 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 2027 Type: apps.OnDeleteDaemonSetStrategyType, 2028 }, 2029 }, 2030 }, { 2031 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 2032 Spec: apps.DaemonSetSpec{ 2033 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2034 Template: validPodTemplate.Template, 2035 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 2036 Type: apps.OnDeleteDaemonSetStrategyType, 2037 }, 2038 }, 2039 }, { 2040 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault}, 2041 Spec: apps.DaemonSetSpec{ 2042 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2043 Template: validHostNetPodTemplate.Template, 2044 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 2045 Type: apps.OnDeleteDaemonSetStrategyType, 2046 }, 2047 }, 2048 }, 2049 } 2050 for _, successCase := range successCases { 2051 if errs := ValidateDaemonSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { 2052 t.Errorf("expected success: %v", errs) 2053 } 2054 } 2055 2056 errorCases := map[string]apps.DaemonSet{ 2057 "zero-length ID": { 2058 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 2059 Spec: apps.DaemonSetSpec{ 2060 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2061 Template: validPodTemplate.Template, 2062 }, 2063 }, 2064 "missing-namespace": { 2065 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, 2066 Spec: apps.DaemonSetSpec{ 2067 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2068 Template: validPodTemplate.Template, 2069 }, 2070 }, 2071 "nil selector": { 2072 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2073 Spec: apps.DaemonSetSpec{ 2074 Template: validPodTemplate.Template, 2075 }, 2076 }, 2077 "empty selector": { 2078 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2079 Spec: apps.DaemonSetSpec{ 2080 Selector: &metav1.LabelSelector{}, 2081 Template: validPodTemplate.Template, 2082 }, 2083 }, 2084 "selector_doesnt_match": { 2085 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2086 Spec: apps.DaemonSetSpec{ 2087 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 2088 Template: validPodTemplate.Template, 2089 }, 2090 }, 2091 "invalid template": { 2092 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2093 Spec: apps.DaemonSetSpec{ 2094 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2095 }, 2096 }, 2097 "invalid_label": { 2098 ObjectMeta: metav1.ObjectMeta{ 2099 Name: "abc-123", 2100 Namespace: metav1.NamespaceDefault, 2101 Labels: map[string]string{ 2102 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 2103 }, 2104 }, 2105 Spec: apps.DaemonSetSpec{ 2106 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2107 Template: validPodTemplate.Template, 2108 }, 2109 }, 2110 "invalid_label 2": { 2111 ObjectMeta: metav1.ObjectMeta{ 2112 Name: "abc-123", 2113 Namespace: metav1.NamespaceDefault, 2114 Labels: map[string]string{ 2115 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 2116 }, 2117 }, 2118 Spec: apps.DaemonSetSpec{ 2119 Template: invalidPodTemplate.Template, 2120 }, 2121 }, 2122 "invalid_annotation": { 2123 ObjectMeta: metav1.ObjectMeta{ 2124 Name: "abc-123", 2125 Namespace: metav1.NamespaceDefault, 2126 Annotations: map[string]string{ 2127 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 2128 }, 2129 }, 2130 Spec: apps.DaemonSetSpec{ 2131 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2132 Template: validPodTemplate.Template, 2133 }, 2134 }, 2135 "invalid restart policy 1": { 2136 ObjectMeta: metav1.ObjectMeta{ 2137 Name: "abc-123", 2138 Namespace: metav1.NamespaceDefault, 2139 }, 2140 Spec: apps.DaemonSetSpec{ 2141 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2142 Template: api.PodTemplateSpec{ 2143 Spec: api.PodSpec{ 2144 RestartPolicy: api.RestartPolicyOnFailure, 2145 DNSPolicy: api.DNSClusterFirst, 2146 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2147 }, 2148 ObjectMeta: metav1.ObjectMeta{ 2149 Labels: validSelector, 2150 }, 2151 }, 2152 }, 2153 }, 2154 "invalid restart policy 2": { 2155 ObjectMeta: metav1.ObjectMeta{ 2156 Name: "abc-123", 2157 Namespace: metav1.NamespaceDefault, 2158 }, 2159 Spec: apps.DaemonSetSpec{ 2160 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2161 Template: api.PodTemplateSpec{ 2162 Spec: api.PodSpec{ 2163 RestartPolicy: api.RestartPolicyNever, 2164 DNSPolicy: api.DNSClusterFirst, 2165 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2166 }, 2167 ObjectMeta: metav1.ObjectMeta{ 2168 Labels: validSelector, 2169 }, 2170 }, 2171 }, 2172 }, 2173 "template may not contain ephemeral containers": { 2174 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2175 Spec: apps.DaemonSetSpec{ 2176 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 2177 Template: api.PodTemplateSpec{ 2178 ObjectMeta: metav1.ObjectMeta{ 2179 Labels: validSelector, 2180 }, 2181 Spec: api.PodSpec{ 2182 RestartPolicy: api.RestartPolicyAlways, 2183 DNSPolicy: api.DNSClusterFirst, 2184 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2185 EphemeralContainers: []api.EphemeralContainer{{EphemeralContainerCommon: api.EphemeralContainerCommon{Name: "debug", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}}, 2186 }, 2187 }, 2188 UpdateStrategy: apps.DaemonSetUpdateStrategy{ 2189 Type: apps.OnDeleteDaemonSetStrategyType, 2190 }, 2191 }, 2192 }, 2193 } 2194 for k, v := range errorCases { 2195 errs := ValidateDaemonSet(&v, corevalidation.PodValidationOptions{}) 2196 if len(errs) == 0 { 2197 t.Errorf("expected failure for %s", k) 2198 } 2199 for i := range errs { 2200 field := errs[i].Field 2201 if !strings.HasPrefix(field, "spec.template.") && 2202 !strings.HasPrefix(field, "spec.updateStrategy") && 2203 field != "metadata.name" && 2204 field != "metadata.namespace" && 2205 field != "spec.selector" && 2206 field != "spec.template" && 2207 field != "GCEPersistentDisk.ReadOnly" && 2208 field != "spec.template.labels" && 2209 field != "metadata.annotations" && 2210 field != "metadata.labels" { 2211 t.Errorf("%s: missing prefix for: %v", k, errs[i]) 2212 } 2213 } 2214 } 2215 } 2216 2217 func validDeployment(tweaks ...func(d *apps.Deployment)) *apps.Deployment { 2218 d := &apps.Deployment{ 2219 ObjectMeta: metav1.ObjectMeta{ 2220 Name: "abc", 2221 Namespace: metav1.NamespaceDefault, 2222 }, 2223 Spec: apps.DeploymentSpec{ 2224 Selector: &metav1.LabelSelector{ 2225 MatchLabels: map[string]string{ 2226 "name": "abc", 2227 }, 2228 }, 2229 Strategy: apps.DeploymentStrategy{ 2230 Type: apps.RollingUpdateDeploymentStrategyType, 2231 RollingUpdate: &apps.RollingUpdateDeployment{ 2232 MaxSurge: intstr.FromInt32(1), 2233 MaxUnavailable: intstr.FromInt32(1), 2234 }, 2235 }, 2236 Template: api.PodTemplateSpec{ 2237 ObjectMeta: metav1.ObjectMeta{ 2238 Name: "abc", 2239 Namespace: metav1.NamespaceDefault, 2240 Labels: map[string]string{ 2241 "name": "abc", 2242 }, 2243 }, 2244 Spec: api.PodSpec{ 2245 RestartPolicy: api.RestartPolicyAlways, 2246 DNSPolicy: api.DNSDefault, 2247 Containers: []api.Container{{ 2248 Name: "nginx", 2249 Image: "image", 2250 ImagePullPolicy: api.PullNever, 2251 TerminationMessagePolicy: api.TerminationMessageReadFile, 2252 }}, 2253 }, 2254 }, 2255 RollbackTo: &apps.RollbackConfig{ 2256 Revision: 1, 2257 }, 2258 }, 2259 } 2260 2261 for _, tweak := range tweaks { 2262 tweak(d) 2263 } 2264 2265 return d 2266 } 2267 2268 func TestValidateDeployment(t *testing.T) { 2269 successCases := []*apps.Deployment{ 2270 validDeployment(), 2271 validDeployment(func(d *apps.Deployment) { 2272 d.Spec.Template.Spec.SecurityContext = &api.PodSecurityContext{ 2273 HostNetwork: true, 2274 } 2275 d.Spec.Template.Spec.Containers[0].Ports = []api.ContainerPort{{ 2276 ContainerPort: 12345, 2277 Protocol: api.ProtocolTCP, 2278 }} 2279 }), 2280 } 2281 for _, successCase := range successCases { 2282 if errs := ValidateDeployment(successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { 2283 t.Errorf("expected success: %v", errs) 2284 } 2285 } 2286 2287 errorCases := map[string]*apps.Deployment{} 2288 errorCases["metadata.name: Required value"] = &apps.Deployment{ 2289 ObjectMeta: metav1.ObjectMeta{ 2290 Namespace: metav1.NamespaceDefault, 2291 }, 2292 } 2293 // selector should match the labels in pod template. 2294 invalidSelectorDeployment := validDeployment() 2295 invalidSelectorDeployment.Spec.Selector = &metav1.LabelSelector{ 2296 MatchLabels: map[string]string{ 2297 "name": "def", 2298 }, 2299 } 2300 errorCases["`selector` does not match template `labels`"] = invalidSelectorDeployment 2301 2302 // RestartPolicy should be always. 2303 invalidRestartPolicyDeployment := validDeployment() 2304 invalidRestartPolicyDeployment.Spec.Template.Spec.RestartPolicy = api.RestartPolicyNever 2305 errorCases["Unsupported value: \"Never\""] = invalidRestartPolicyDeployment 2306 2307 // must have valid strategy type 2308 invalidStrategyDeployment := validDeployment() 2309 invalidStrategyDeployment.Spec.Strategy.Type = apps.DeploymentStrategyType("randomType") 2310 errorCases[`supported values: "Recreate", "RollingUpdate"`] = invalidStrategyDeployment 2311 2312 // rollingUpdate should be nil for recreate. 2313 invalidRecreateDeployment := validDeployment() 2314 invalidRecreateDeployment.Spec.Strategy = apps.DeploymentStrategy{ 2315 Type: apps.RecreateDeploymentStrategyType, 2316 RollingUpdate: &apps.RollingUpdateDeployment{}, 2317 } 2318 errorCases["may not be specified when strategy `type` is 'Recreate'"] = invalidRecreateDeployment 2319 2320 // MaxSurge should be in the form of 20%. 2321 invalidMaxSurgeDeployment := validDeployment() 2322 invalidMaxSurgeDeployment.Spec.Strategy = apps.DeploymentStrategy{ 2323 Type: apps.RollingUpdateDeploymentStrategyType, 2324 RollingUpdate: &apps.RollingUpdateDeployment{ 2325 MaxSurge: intstr.FromString("20Percent"), 2326 }, 2327 } 2328 errorCases["a valid percent string must be"] = invalidMaxSurgeDeployment 2329 2330 // MaxSurge and MaxUnavailable cannot both be zero. 2331 invalidRollingUpdateDeployment := validDeployment() 2332 invalidRollingUpdateDeployment.Spec.Strategy = apps.DeploymentStrategy{ 2333 Type: apps.RollingUpdateDeploymentStrategyType, 2334 RollingUpdate: &apps.RollingUpdateDeployment{ 2335 MaxSurge: intstr.FromString("0%"), 2336 MaxUnavailable: intstr.FromInt32(0), 2337 }, 2338 } 2339 errorCases["may not be 0 when `maxSurge` is 0"] = invalidRollingUpdateDeployment 2340 2341 // MaxUnavailable should not be more than 100%. 2342 invalidMaxUnavailableDeployment := validDeployment() 2343 invalidMaxUnavailableDeployment.Spec.Strategy = apps.DeploymentStrategy{ 2344 Type: apps.RollingUpdateDeploymentStrategyType, 2345 RollingUpdate: &apps.RollingUpdateDeployment{ 2346 MaxUnavailable: intstr.FromString("110%"), 2347 }, 2348 } 2349 errorCases["must not be greater than 100%"] = invalidMaxUnavailableDeployment 2350 2351 // Rollback.Revision must be non-negative 2352 invalidRollbackRevisionDeployment := validDeployment() 2353 invalidRollbackRevisionDeployment.Spec.RollbackTo.Revision = -3 2354 errorCases["must be greater than or equal to 0"] = invalidRollbackRevisionDeployment 2355 2356 // ProgressDeadlineSeconds should be greater than MinReadySeconds 2357 invalidProgressDeadlineDeployment := validDeployment() 2358 seconds := int32(600) 2359 invalidProgressDeadlineDeployment.Spec.ProgressDeadlineSeconds = &seconds 2360 invalidProgressDeadlineDeployment.Spec.MinReadySeconds = seconds 2361 errorCases["must be greater than minReadySeconds"] = invalidProgressDeadlineDeployment 2362 2363 // Must not have ephemeral containers 2364 invalidEphemeralContainersDeployment := validDeployment() 2365 invalidEphemeralContainersDeployment.Spec.Template.Spec.EphemeralContainers = []api.EphemeralContainer{{ 2366 EphemeralContainerCommon: api.EphemeralContainerCommon{ 2367 Name: "ec", 2368 Image: "image", 2369 ImagePullPolicy: "IfNotPresent", 2370 TerminationMessagePolicy: "File"}, 2371 }} 2372 errorCases["ephemeral containers not allowed"] = invalidEphemeralContainersDeployment 2373 2374 for k, v := range errorCases { 2375 errs := ValidateDeployment(v, corevalidation.PodValidationOptions{}) 2376 if len(errs) == 0 { 2377 t.Errorf("[%s] expected failure", k) 2378 } else if !strings.Contains(errs[0].Error(), k) { 2379 t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k) 2380 } 2381 } 2382 } 2383 2384 func TestValidateDeploymentStatus(t *testing.T) { 2385 collisionCount := int32(-3) 2386 tests := []struct { 2387 name string 2388 2389 replicas int32 2390 updatedReplicas int32 2391 readyReplicas int32 2392 availableReplicas int32 2393 observedGeneration int64 2394 collisionCount *int32 2395 2396 expectedErr bool 2397 }{{ 2398 name: "valid status", 2399 replicas: 3, 2400 updatedReplicas: 3, 2401 readyReplicas: 2, 2402 availableReplicas: 1, 2403 observedGeneration: 2, 2404 expectedErr: false, 2405 }, { 2406 name: "invalid replicas", 2407 replicas: -1, 2408 updatedReplicas: 2, 2409 readyReplicas: 2, 2410 availableReplicas: 1, 2411 observedGeneration: 2, 2412 expectedErr: true, 2413 }, { 2414 name: "invalid updatedReplicas", 2415 replicas: 2, 2416 updatedReplicas: -1, 2417 readyReplicas: 2, 2418 availableReplicas: 1, 2419 observedGeneration: 2, 2420 expectedErr: true, 2421 }, { 2422 name: "invalid readyReplicas", 2423 replicas: 3, 2424 readyReplicas: -1, 2425 availableReplicas: 1, 2426 observedGeneration: 2, 2427 expectedErr: true, 2428 }, { 2429 name: "invalid availableReplicas", 2430 replicas: 3, 2431 readyReplicas: 3, 2432 availableReplicas: -1, 2433 observedGeneration: 2, 2434 expectedErr: true, 2435 }, { 2436 name: "invalid observedGeneration", 2437 replicas: 3, 2438 readyReplicas: 3, 2439 availableReplicas: 3, 2440 observedGeneration: -1, 2441 expectedErr: true, 2442 }, { 2443 name: "updatedReplicas greater than replicas", 2444 replicas: 3, 2445 updatedReplicas: 4, 2446 readyReplicas: 3, 2447 availableReplicas: 3, 2448 observedGeneration: 1, 2449 expectedErr: true, 2450 }, { 2451 name: "readyReplicas greater than replicas", 2452 replicas: 3, 2453 readyReplicas: 4, 2454 availableReplicas: 3, 2455 observedGeneration: 1, 2456 expectedErr: true, 2457 }, { 2458 name: "availableReplicas greater than replicas", 2459 replicas: 3, 2460 readyReplicas: 3, 2461 availableReplicas: 4, 2462 observedGeneration: 1, 2463 expectedErr: true, 2464 }, { 2465 name: "availableReplicas greater than readyReplicas", 2466 replicas: 3, 2467 readyReplicas: 2, 2468 availableReplicas: 3, 2469 observedGeneration: 1, 2470 expectedErr: true, 2471 }, { 2472 name: "invalid collisionCount", 2473 replicas: 3, 2474 observedGeneration: 1, 2475 collisionCount: &collisionCount, 2476 expectedErr: true, 2477 }, 2478 } 2479 2480 for _, test := range tests { 2481 status := apps.DeploymentStatus{ 2482 Replicas: test.replicas, 2483 UpdatedReplicas: test.updatedReplicas, 2484 ReadyReplicas: test.readyReplicas, 2485 AvailableReplicas: test.availableReplicas, 2486 ObservedGeneration: test.observedGeneration, 2487 CollisionCount: test.collisionCount, 2488 } 2489 2490 errs := ValidateDeploymentStatus(&status, field.NewPath("status")) 2491 if hasErr := len(errs) > 0; hasErr != test.expectedErr { 2492 errString := dump.Pretty(errs) 2493 t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString) 2494 } 2495 } 2496 } 2497 2498 func TestValidateDeploymentStatusUpdate(t *testing.T) { 2499 collisionCount := int32(1) 2500 otherCollisionCount := int32(2) 2501 tests := []struct { 2502 name string 2503 2504 from, to apps.DeploymentStatus 2505 2506 expectedErr bool 2507 }{{ 2508 name: "increase: valid update", 2509 from: apps.DeploymentStatus{ 2510 CollisionCount: nil, 2511 }, 2512 to: apps.DeploymentStatus{ 2513 CollisionCount: &collisionCount, 2514 }, 2515 expectedErr: false, 2516 }, { 2517 name: "stable: valid update", 2518 from: apps.DeploymentStatus{ 2519 CollisionCount: &collisionCount, 2520 }, 2521 to: apps.DeploymentStatus{ 2522 CollisionCount: &collisionCount, 2523 }, 2524 expectedErr: false, 2525 }, { 2526 name: "unset: invalid update", 2527 from: apps.DeploymentStatus{ 2528 CollisionCount: &collisionCount, 2529 }, 2530 to: apps.DeploymentStatus{ 2531 CollisionCount: nil, 2532 }, 2533 expectedErr: true, 2534 }, { 2535 name: "decrease: invalid update", 2536 from: apps.DeploymentStatus{ 2537 CollisionCount: &otherCollisionCount, 2538 }, 2539 to: apps.DeploymentStatus{ 2540 CollisionCount: &collisionCount, 2541 }, 2542 expectedErr: true, 2543 }, 2544 } 2545 2546 for _, test := range tests { 2547 meta := metav1.ObjectMeta{Name: "foo", Namespace: metav1.NamespaceDefault, ResourceVersion: "1"} 2548 from := &apps.Deployment{ 2549 ObjectMeta: meta, 2550 Status: test.from, 2551 } 2552 to := &apps.Deployment{ 2553 ObjectMeta: meta, 2554 Status: test.to, 2555 } 2556 2557 errs := ValidateDeploymentStatusUpdate(to, from) 2558 if hasErr := len(errs) > 0; hasErr != test.expectedErr { 2559 errString := dump.Pretty(errs) 2560 t.Errorf("%s: expected error: %t, got error: %t\nerrors: %s", test.name, test.expectedErr, hasErr, errString) 2561 } 2562 } 2563 } 2564 2565 func validDeploymentRollback() *apps.DeploymentRollback { 2566 return &apps.DeploymentRollback{ 2567 Name: "abc", 2568 UpdatedAnnotations: map[string]string{ 2569 "created-by": "abc", 2570 }, 2571 RollbackTo: apps.RollbackConfig{ 2572 Revision: 1, 2573 }, 2574 } 2575 } 2576 2577 func TestValidateDeploymentUpdate(t *testing.T) { 2578 validLabels := map[string]string{"a": "b"} 2579 validPodTemplate := api.PodTemplate{ 2580 Template: api.PodTemplateSpec{ 2581 ObjectMeta: metav1.ObjectMeta{ 2582 Labels: validLabels, 2583 }, 2584 Spec: api.PodSpec{ 2585 RestartPolicy: api.RestartPolicyAlways, 2586 DNSPolicy: api.DNSClusterFirst, 2587 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2588 }, 2589 }, 2590 } 2591 readWriteVolumePodTemplate := api.PodTemplate{ 2592 Template: api.PodTemplateSpec{ 2593 ObjectMeta: metav1.ObjectMeta{ 2594 Labels: validLabels, 2595 }, 2596 Spec: api.PodSpec{ 2597 RestartPolicy: api.RestartPolicyAlways, 2598 DNSPolicy: api.DNSClusterFirst, 2599 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2600 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, 2601 }, 2602 }, 2603 } 2604 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 2605 invalidPodTemplate := api.PodTemplate{ 2606 Template: api.PodTemplateSpec{ 2607 Spec: api.PodSpec{ 2608 // no containers specified 2609 RestartPolicy: api.RestartPolicyAlways, 2610 DNSPolicy: api.DNSClusterFirst, 2611 }, 2612 ObjectMeta: metav1.ObjectMeta{ 2613 Labels: invalidLabels, 2614 }, 2615 }, 2616 } 2617 type depUpdateTest struct { 2618 old apps.Deployment 2619 update apps.Deployment 2620 expectedErrNum int 2621 } 2622 successCases := map[string]depUpdateTest{ 2623 "positive replicas": { 2624 old: apps.Deployment{ 2625 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2626 Spec: apps.DeploymentSpec{ 2627 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2628 Template: validPodTemplate.Template, 2629 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2630 }, 2631 }, 2632 update: apps.Deployment{ 2633 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2634 Spec: apps.DeploymentSpec{ 2635 Replicas: 1, 2636 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2637 Template: readWriteVolumePodTemplate.Template, 2638 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2639 }, 2640 }, 2641 }, 2642 "Read-write volume verification": { 2643 old: apps.Deployment{ 2644 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2645 Spec: apps.DeploymentSpec{ 2646 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2647 Template: validPodTemplate.Template, 2648 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2649 }, 2650 }, 2651 update: apps.Deployment{ 2652 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2653 Spec: apps.DeploymentSpec{ 2654 Replicas: 2, 2655 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2656 Template: readWriteVolumePodTemplate.Template, 2657 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2658 }, 2659 }, 2660 }, 2661 } 2662 for testName, successCase := range successCases { 2663 t.Run(testName, func(t *testing.T) { 2664 // ResourceVersion is required for updates. 2665 successCase.old.ObjectMeta.ResourceVersion = "1" 2666 successCase.update.ObjectMeta.ResourceVersion = "2" 2667 // Check test setup 2668 if successCase.expectedErrNum > 0 { 2669 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum) 2670 } 2671 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 { 2672 t.Errorf("%q has incorrect test setup with no resource version set", testName) 2673 } 2674 // Run the tests 2675 if errs := ValidateDeploymentUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 { 2676 t.Errorf("%q expected no error, but got: %v", testName, errs) 2677 } 2678 }) 2679 errorCases := map[string]depUpdateTest{ 2680 "invalid selector": { 2681 old: apps.Deployment{ 2682 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 2683 Spec: apps.DeploymentSpec{ 2684 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2685 Template: validPodTemplate.Template, 2686 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2687 }, 2688 }, 2689 update: apps.Deployment{ 2690 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2691 Spec: apps.DeploymentSpec{ 2692 Replicas: 2, 2693 Selector: &metav1.LabelSelector{MatchLabels: invalidLabels}, 2694 Template: validPodTemplate.Template, 2695 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2696 }, 2697 }, 2698 expectedErrNum: 3, 2699 }, 2700 "invalid pod": { 2701 old: apps.Deployment{ 2702 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 2703 Spec: apps.DeploymentSpec{ 2704 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2705 Template: validPodTemplate.Template, 2706 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2707 }, 2708 }, 2709 update: apps.Deployment{ 2710 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2711 Spec: apps.DeploymentSpec{ 2712 Replicas: 2, 2713 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2714 Template: invalidPodTemplate.Template, 2715 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2716 }, 2717 }, 2718 expectedErrNum: 4, 2719 }, 2720 "negative replicas": { 2721 old: apps.Deployment{ 2722 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2723 Spec: apps.DeploymentSpec{ 2724 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2725 Template: validPodTemplate.Template, 2726 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2727 }, 2728 }, 2729 update: apps.Deployment{ 2730 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2731 Spec: apps.DeploymentSpec{ 2732 Replicas: -1, 2733 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2734 Template: readWriteVolumePodTemplate.Template, 2735 Strategy: apps.DeploymentStrategy{Type: apps.RecreateDeploymentStrategyType}, 2736 }, 2737 }, 2738 expectedErrNum: 1, 2739 }, 2740 } 2741 for testName, errorCase := range errorCases { 2742 t.Run(testName, func(t *testing.T) { 2743 // ResourceVersion is required for updates. 2744 errorCase.old.ObjectMeta.ResourceVersion = "1" 2745 errorCase.update.ObjectMeta.ResourceVersion = "2" 2746 // Check test setup 2747 if errorCase.expectedErrNum <= 0 { 2748 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum) 2749 } 2750 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 { 2751 t.Errorf("%q has incorrect test setup with no resource version set", testName) 2752 } 2753 // Run the tests 2754 if errs := ValidateDeploymentUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum { 2755 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs) 2756 } else { 2757 t.Logf("(PASS) %q got errors %v", testName, errs) 2758 } 2759 }) 2760 } 2761 } 2762 } 2763 2764 func TestValidateDeploymentRollback(t *testing.T) { 2765 noAnnotation := validDeploymentRollback() 2766 noAnnotation.UpdatedAnnotations = nil 2767 successCases := []*apps.DeploymentRollback{ 2768 validDeploymentRollback(), 2769 noAnnotation, 2770 } 2771 for _, successCase := range successCases { 2772 if errs := ValidateDeploymentRollback(successCase); len(errs) != 0 { 2773 t.Errorf("expected success: %v", errs) 2774 } 2775 } 2776 2777 errorCases := map[string]*apps.DeploymentRollback{} 2778 invalidNoName := validDeploymentRollback() 2779 invalidNoName.Name = "" 2780 errorCases["name: Required value"] = invalidNoName 2781 2782 for k, v := range errorCases { 2783 errs := ValidateDeploymentRollback(v) 2784 if len(errs) == 0 { 2785 t.Errorf("[%s] expected failure", k) 2786 } else if !strings.Contains(errs[0].Error(), k) { 2787 t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k) 2788 } 2789 } 2790 } 2791 2792 func TestValidateReplicaSetStatus(t *testing.T) { 2793 tests := []struct { 2794 name string 2795 2796 replicas int32 2797 fullyLabeledReplicas int32 2798 readyReplicas int32 2799 availableReplicas int32 2800 observedGeneration int64 2801 2802 expectedErr bool 2803 }{{ 2804 name: "valid status", 2805 replicas: 3, 2806 fullyLabeledReplicas: 3, 2807 readyReplicas: 2, 2808 availableReplicas: 1, 2809 observedGeneration: 2, 2810 expectedErr: false, 2811 }, { 2812 name: "invalid replicas", 2813 replicas: -1, 2814 fullyLabeledReplicas: 3, 2815 readyReplicas: 2, 2816 availableReplicas: 1, 2817 observedGeneration: 2, 2818 expectedErr: true, 2819 }, { 2820 name: "invalid fullyLabeledReplicas", 2821 replicas: 3, 2822 fullyLabeledReplicas: -1, 2823 readyReplicas: 2, 2824 availableReplicas: 1, 2825 observedGeneration: 2, 2826 expectedErr: true, 2827 }, { 2828 name: "invalid readyReplicas", 2829 replicas: 3, 2830 fullyLabeledReplicas: 3, 2831 readyReplicas: -1, 2832 availableReplicas: 1, 2833 observedGeneration: 2, 2834 expectedErr: true, 2835 }, { 2836 name: "invalid availableReplicas", 2837 replicas: 3, 2838 fullyLabeledReplicas: 3, 2839 readyReplicas: 3, 2840 availableReplicas: -1, 2841 observedGeneration: 2, 2842 expectedErr: true, 2843 }, { 2844 name: "invalid observedGeneration", 2845 replicas: 3, 2846 fullyLabeledReplicas: 3, 2847 readyReplicas: 3, 2848 availableReplicas: 3, 2849 observedGeneration: -1, 2850 expectedErr: true, 2851 }, { 2852 name: "fullyLabeledReplicas greater than replicas", 2853 replicas: 3, 2854 fullyLabeledReplicas: 4, 2855 readyReplicas: 3, 2856 availableReplicas: 3, 2857 observedGeneration: 1, 2858 expectedErr: true, 2859 }, { 2860 name: "readyReplicas greater than replicas", 2861 replicas: 3, 2862 fullyLabeledReplicas: 3, 2863 readyReplicas: 4, 2864 availableReplicas: 3, 2865 observedGeneration: 1, 2866 expectedErr: true, 2867 }, { 2868 name: "availableReplicas greater than replicas", 2869 replicas: 3, 2870 fullyLabeledReplicas: 3, 2871 readyReplicas: 3, 2872 availableReplicas: 4, 2873 observedGeneration: 1, 2874 expectedErr: true, 2875 }, { 2876 name: "availableReplicas greater than readyReplicas", 2877 replicas: 3, 2878 fullyLabeledReplicas: 3, 2879 readyReplicas: 2, 2880 availableReplicas: 3, 2881 observedGeneration: 1, 2882 expectedErr: true, 2883 }, 2884 } 2885 2886 for _, test := range tests { 2887 status := apps.ReplicaSetStatus{ 2888 Replicas: test.replicas, 2889 FullyLabeledReplicas: test.fullyLabeledReplicas, 2890 ReadyReplicas: test.readyReplicas, 2891 AvailableReplicas: test.availableReplicas, 2892 ObservedGeneration: test.observedGeneration, 2893 } 2894 2895 if hasErr := len(ValidateReplicaSetStatus(status, field.NewPath("status"))) > 0; hasErr != test.expectedErr { 2896 t.Errorf("%s: expected error: %t, got error: %t", test.name, test.expectedErr, hasErr) 2897 } 2898 } 2899 } 2900 2901 func TestValidateReplicaSetStatusUpdate(t *testing.T) { 2902 validLabels := map[string]string{"a": "b"} 2903 validPodTemplate := api.PodTemplate{ 2904 Template: api.PodTemplateSpec{ 2905 ObjectMeta: metav1.ObjectMeta{ 2906 Labels: validLabels, 2907 }, 2908 Spec: api.PodSpec{ 2909 RestartPolicy: api.RestartPolicyAlways, 2910 DNSPolicy: api.DNSClusterFirst, 2911 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2912 }, 2913 }, 2914 } 2915 type rcUpdateTest struct { 2916 old apps.ReplicaSet 2917 update apps.ReplicaSet 2918 } 2919 successCases := []rcUpdateTest{{ 2920 old: apps.ReplicaSet{ 2921 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2922 Spec: apps.ReplicaSetSpec{ 2923 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2924 Template: validPodTemplate.Template, 2925 }, 2926 Status: apps.ReplicaSetStatus{ 2927 Replicas: 2, 2928 }, 2929 }, 2930 update: apps.ReplicaSet{ 2931 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2932 Spec: apps.ReplicaSetSpec{ 2933 Replicas: 3, 2934 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2935 Template: validPodTemplate.Template, 2936 }, 2937 Status: apps.ReplicaSetStatus{ 2938 Replicas: 4, 2939 }, 2940 }, 2941 }, 2942 } 2943 for _, successCase := range successCases { 2944 successCase.old.ObjectMeta.ResourceVersion = "1" 2945 successCase.update.ObjectMeta.ResourceVersion = "1" 2946 if errs := ValidateReplicaSetStatusUpdate(&successCase.update, &successCase.old); len(errs) != 0 { 2947 t.Errorf("expected success: %v", errs) 2948 } 2949 } 2950 errorCases := map[string]rcUpdateTest{ 2951 "negative replicas": { 2952 old: apps.ReplicaSet{ 2953 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 2954 Spec: apps.ReplicaSetSpec{ 2955 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2956 Template: validPodTemplate.Template, 2957 }, 2958 Status: apps.ReplicaSetStatus{ 2959 Replicas: 3, 2960 }, 2961 }, 2962 update: apps.ReplicaSet{ 2963 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 2964 Spec: apps.ReplicaSetSpec{ 2965 Replicas: 2, 2966 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 2967 Template: validPodTemplate.Template, 2968 }, 2969 Status: apps.ReplicaSetStatus{ 2970 Replicas: -3, 2971 }, 2972 }, 2973 }, 2974 } 2975 for testName, errorCase := range errorCases { 2976 if errs := ValidateReplicaSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { 2977 t.Errorf("expected failure: %s", testName) 2978 } 2979 } 2980 2981 } 2982 2983 func TestValidateReplicaSetUpdate(t *testing.T) { 2984 validLabels := map[string]string{"a": "b"} 2985 validPodTemplate := api.PodTemplate{ 2986 Template: api.PodTemplateSpec{ 2987 ObjectMeta: metav1.ObjectMeta{ 2988 Labels: validLabels, 2989 }, 2990 Spec: api.PodSpec{ 2991 RestartPolicy: api.RestartPolicyAlways, 2992 DNSPolicy: api.DNSClusterFirst, 2993 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 2994 }, 2995 }, 2996 } 2997 readWriteVolumePodTemplate := api.PodTemplate{ 2998 Template: api.PodTemplateSpec{ 2999 ObjectMeta: metav1.ObjectMeta{ 3000 Labels: validLabels, 3001 }, 3002 Spec: api.PodSpec{ 3003 RestartPolicy: api.RestartPolicyAlways, 3004 DNSPolicy: api.DNSClusterFirst, 3005 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 3006 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, 3007 }, 3008 }, 3009 } 3010 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 3011 invalidPodTemplate := api.PodTemplate{ 3012 Template: api.PodTemplateSpec{ 3013 Spec: api.PodSpec{ 3014 RestartPolicy: api.RestartPolicyAlways, 3015 DNSPolicy: api.DNSClusterFirst, 3016 }, 3017 ObjectMeta: metav1.ObjectMeta{ 3018 Labels: invalidLabels, 3019 }, 3020 }, 3021 } 3022 type rcUpdateTest struct { 3023 old apps.ReplicaSet 3024 update apps.ReplicaSet 3025 expectedErrNum int 3026 } 3027 successCases := map[string]rcUpdateTest{ 3028 "positive replicas": { 3029 old: apps.ReplicaSet{ 3030 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3031 Spec: apps.ReplicaSetSpec{ 3032 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3033 Template: validPodTemplate.Template, 3034 }, 3035 }, 3036 update: apps.ReplicaSet{ 3037 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3038 Spec: apps.ReplicaSetSpec{ 3039 Replicas: 3, 3040 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3041 Template: validPodTemplate.Template, 3042 }, 3043 }, 3044 }, 3045 "Read-write volume verification": { 3046 old: apps.ReplicaSet{ 3047 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3048 Spec: apps.ReplicaSetSpec{ 3049 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3050 Template: validPodTemplate.Template, 3051 }, 3052 }, 3053 update: apps.ReplicaSet{ 3054 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3055 Spec: apps.ReplicaSetSpec{ 3056 Replicas: 3, 3057 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3058 Template: readWriteVolumePodTemplate.Template, 3059 }, 3060 }, 3061 }, 3062 } 3063 for testName, successCase := range successCases { 3064 t.Run(testName, func(t *testing.T) { 3065 // ResourceVersion is required for updates. 3066 successCase.old.ObjectMeta.ResourceVersion = "1" 3067 successCase.update.ObjectMeta.ResourceVersion = "2" 3068 // Check test setup 3069 if successCase.expectedErrNum > 0 { 3070 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected no error", testName, successCase.expectedErrNum) 3071 } 3072 if len(successCase.old.ObjectMeta.ResourceVersion) == 0 || len(successCase.update.ObjectMeta.ResourceVersion) == 0 { 3073 t.Errorf("%q has incorrect test setup with no resource version set", testName) 3074 } 3075 // Run the tests 3076 if errs := ValidateReplicaSetUpdate(&successCase.update, &successCase.old, corevalidation.PodValidationOptions{}); len(errs) != 0 { 3077 t.Errorf("%q expected no error, but got: %v", testName, errs) 3078 } 3079 }) 3080 } 3081 errorCases := map[string]rcUpdateTest{ 3082 "invalid selector": { 3083 old: apps.ReplicaSet{ 3084 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 3085 Spec: apps.ReplicaSetSpec{ 3086 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3087 Template: validPodTemplate.Template, 3088 }, 3089 }, 3090 update: apps.ReplicaSet{ 3091 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3092 Spec: apps.ReplicaSetSpec{ 3093 Replicas: 2, 3094 Selector: &metav1.LabelSelector{MatchLabels: invalidLabels}, 3095 Template: validPodTemplate.Template, 3096 }, 3097 }, 3098 expectedErrNum: 3, 3099 }, 3100 "invalid pod": { 3101 old: apps.ReplicaSet{ 3102 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 3103 Spec: apps.ReplicaSetSpec{ 3104 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3105 Template: validPodTemplate.Template, 3106 }, 3107 }, 3108 update: apps.ReplicaSet{ 3109 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3110 Spec: apps.ReplicaSetSpec{ 3111 Replicas: 2, 3112 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3113 Template: invalidPodTemplate.Template, 3114 }, 3115 }, 3116 expectedErrNum: 4, 3117 }, 3118 "negative replicas": { 3119 old: apps.ReplicaSet{ 3120 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3121 Spec: apps.ReplicaSetSpec{ 3122 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3123 Template: validPodTemplate.Template, 3124 }, 3125 }, 3126 update: apps.ReplicaSet{ 3127 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3128 Spec: apps.ReplicaSetSpec{ 3129 Replicas: -1, 3130 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3131 Template: validPodTemplate.Template, 3132 }, 3133 }, 3134 expectedErrNum: 1, 3135 }, 3136 } 3137 for testName, errorCase := range errorCases { 3138 t.Run(testName, func(t *testing.T) { 3139 // ResourceVersion is required for updates. 3140 errorCase.old.ObjectMeta.ResourceVersion = "1" 3141 errorCase.update.ObjectMeta.ResourceVersion = "2" 3142 // Check test setup 3143 if errorCase.expectedErrNum <= 0 { 3144 t.Errorf("%q has incorrect test setup with expectedErrNum %d, expected at least one error", testName, errorCase.expectedErrNum) 3145 } 3146 if len(errorCase.old.ObjectMeta.ResourceVersion) == 0 || len(errorCase.update.ObjectMeta.ResourceVersion) == 0 { 3147 t.Errorf("%q has incorrect test setup with no resource version set", testName) 3148 } 3149 // Run the tests 3150 if errs := ValidateReplicaSetUpdate(&errorCase.update, &errorCase.old, corevalidation.PodValidationOptions{}); len(errs) != errorCase.expectedErrNum { 3151 t.Errorf("%q expected %d errors, but got %d error: %v", testName, errorCase.expectedErrNum, len(errs), errs) 3152 } else { 3153 t.Logf("(PASS) %q got errors %v", testName, errs) 3154 } 3155 }) 3156 } 3157 } 3158 3159 func TestValidateReplicaSet(t *testing.T) { 3160 validLabels := map[string]string{"a": "b"} 3161 validPodTemplate := api.PodTemplate{ 3162 Template: api.PodTemplateSpec{ 3163 ObjectMeta: metav1.ObjectMeta{ 3164 Labels: validLabels, 3165 }, 3166 Spec: api.PodSpec{ 3167 RestartPolicy: api.RestartPolicyAlways, 3168 DNSPolicy: api.DNSClusterFirst, 3169 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 3170 }, 3171 }, 3172 } 3173 validHostNetPodTemplate := api.PodTemplate{ 3174 Template: api.PodTemplateSpec{ 3175 ObjectMeta: metav1.ObjectMeta{ 3176 Labels: validLabels, 3177 }, 3178 Spec: api.PodSpec{ 3179 SecurityContext: &api.PodSecurityContext{ 3180 HostNetwork: true, 3181 }, 3182 RestartPolicy: api.RestartPolicyAlways, 3183 DNSPolicy: api.DNSClusterFirst, 3184 Containers: []api.Container{{ 3185 Name: "abc", 3186 Image: "image", 3187 ImagePullPolicy: "IfNotPresent", 3188 TerminationMessagePolicy: api.TerminationMessageReadFile, 3189 Ports: []api.ContainerPort{{ 3190 ContainerPort: 12345, 3191 Protocol: api.ProtocolTCP, 3192 }}, 3193 }}, 3194 }, 3195 }, 3196 } 3197 readWriteVolumePodTemplate := api.PodTemplate{ 3198 Template: api.PodTemplateSpec{ 3199 ObjectMeta: metav1.ObjectMeta{ 3200 Labels: validLabels, 3201 }, 3202 Spec: api.PodSpec{ 3203 Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}}, 3204 RestartPolicy: api.RestartPolicyAlways, 3205 DNSPolicy: api.DNSClusterFirst, 3206 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 3207 }, 3208 }, 3209 } 3210 invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} 3211 invalidPodTemplate := api.PodTemplate{ 3212 Template: api.PodTemplateSpec{ 3213 Spec: api.PodSpec{ 3214 RestartPolicy: api.RestartPolicyAlways, 3215 DNSPolicy: api.DNSClusterFirst, 3216 }, 3217 ObjectMeta: metav1.ObjectMeta{ 3218 Labels: invalidLabels, 3219 }, 3220 }, 3221 } 3222 successCases := []apps.ReplicaSet{{ 3223 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3224 Spec: apps.ReplicaSetSpec{ 3225 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3226 Template: validPodTemplate.Template, 3227 }, 3228 }, { 3229 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 3230 Spec: apps.ReplicaSetSpec{ 3231 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3232 Template: validPodTemplate.Template, 3233 }, 3234 }, { 3235 ObjectMeta: metav1.ObjectMeta{Name: "hostnet", Namespace: metav1.NamespaceDefault}, 3236 Spec: apps.ReplicaSetSpec{ 3237 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3238 Template: validHostNetPodTemplate.Template, 3239 }, 3240 }, { 3241 ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault}, 3242 Spec: apps.ReplicaSetSpec{ 3243 Replicas: 1, 3244 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3245 Template: readWriteVolumePodTemplate.Template, 3246 }, 3247 }, 3248 } 3249 for _, successCase := range successCases { 3250 if errs := ValidateReplicaSet(&successCase, corevalidation.PodValidationOptions{}); len(errs) != 0 { 3251 t.Errorf("expected success: %v", errs) 3252 } 3253 } 3254 3255 errorCases := map[string]apps.ReplicaSet{ 3256 "zero-length ID": { 3257 ObjectMeta: metav1.ObjectMeta{Name: "", Namespace: metav1.NamespaceDefault}, 3258 Spec: apps.ReplicaSetSpec{ 3259 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3260 Template: validPodTemplate.Template, 3261 }, 3262 }, 3263 "missing-namespace": { 3264 ObjectMeta: metav1.ObjectMeta{Name: "abc-123"}, 3265 Spec: apps.ReplicaSetSpec{ 3266 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3267 Template: validPodTemplate.Template, 3268 }, 3269 }, 3270 "empty selector": { 3271 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3272 Spec: apps.ReplicaSetSpec{ 3273 Template: validPodTemplate.Template, 3274 }, 3275 }, 3276 "selector_doesnt_match": { 3277 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3278 Spec: apps.ReplicaSetSpec{ 3279 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 3280 Template: validPodTemplate.Template, 3281 }, 3282 }, 3283 "invalid manifest": { 3284 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3285 Spec: apps.ReplicaSetSpec{ 3286 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3287 }, 3288 }, 3289 "read-write persistent disk with > 1 pod": { 3290 ObjectMeta: metav1.ObjectMeta{Name: "abc"}, 3291 Spec: apps.ReplicaSetSpec{ 3292 Replicas: 2, 3293 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3294 Template: readWriteVolumePodTemplate.Template, 3295 }, 3296 }, 3297 "negative_replicas": { 3298 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 3299 Spec: apps.ReplicaSetSpec{ 3300 Replicas: -1, 3301 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3302 }, 3303 }, 3304 "invalid_label": { 3305 ObjectMeta: metav1.ObjectMeta{ 3306 Name: "abc-123", 3307 Namespace: metav1.NamespaceDefault, 3308 Labels: map[string]string{ 3309 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 3310 }, 3311 }, 3312 Spec: apps.ReplicaSetSpec{ 3313 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3314 Template: validPodTemplate.Template, 3315 }, 3316 }, 3317 "invalid_label 2": { 3318 ObjectMeta: metav1.ObjectMeta{ 3319 Name: "abc-123", 3320 Namespace: metav1.NamespaceDefault, 3321 Labels: map[string]string{ 3322 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 3323 }, 3324 }, 3325 Spec: apps.ReplicaSetSpec{ 3326 Template: invalidPodTemplate.Template, 3327 }, 3328 }, 3329 "invalid_annotation": { 3330 ObjectMeta: metav1.ObjectMeta{ 3331 Name: "abc-123", 3332 Namespace: metav1.NamespaceDefault, 3333 Annotations: map[string]string{ 3334 "NoUppercaseOrSpecialCharsLike=Equals": "bar", 3335 }, 3336 }, 3337 Spec: apps.ReplicaSetSpec{ 3338 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3339 Template: validPodTemplate.Template, 3340 }, 3341 }, 3342 "invalid restart policy 1": { 3343 ObjectMeta: metav1.ObjectMeta{ 3344 Name: "abc-123", 3345 Namespace: metav1.NamespaceDefault, 3346 }, 3347 Spec: apps.ReplicaSetSpec{ 3348 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3349 Template: api.PodTemplateSpec{ 3350 Spec: api.PodSpec{ 3351 RestartPolicy: api.RestartPolicyOnFailure, 3352 DNSPolicy: api.DNSClusterFirst, 3353 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 3354 }, 3355 ObjectMeta: metav1.ObjectMeta{ 3356 Labels: validLabels, 3357 }, 3358 }, 3359 }, 3360 }, 3361 "invalid restart policy 2": { 3362 ObjectMeta: metav1.ObjectMeta{ 3363 Name: "abc-123", 3364 Namespace: metav1.NamespaceDefault, 3365 }, 3366 Spec: apps.ReplicaSetSpec{ 3367 Selector: &metav1.LabelSelector{MatchLabels: validLabels}, 3368 Template: api.PodTemplateSpec{ 3369 Spec: api.PodSpec{ 3370 RestartPolicy: api.RestartPolicyNever, 3371 DNSPolicy: api.DNSClusterFirst, 3372 Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: api.TerminationMessageReadFile}}, 3373 }, 3374 ObjectMeta: metav1.ObjectMeta{ 3375 Labels: validLabels, 3376 }, 3377 }, 3378 }, 3379 }, 3380 } 3381 for k, v := range errorCases { 3382 errs := ValidateReplicaSet(&v, corevalidation.PodValidationOptions{}) 3383 if len(errs) == 0 { 3384 t.Errorf("expected failure for %s", k) 3385 } 3386 for i := range errs { 3387 field := errs[i].Field 3388 if !strings.HasPrefix(field, "spec.template.") && 3389 field != "metadata.name" && 3390 field != "metadata.namespace" && 3391 field != "spec.selector" && 3392 field != "spec.template" && 3393 field != "GCEPersistentDisk.ReadOnly" && 3394 field != "spec.replicas" && 3395 field != "spec.template.labels" && 3396 field != "metadata.annotations" && 3397 field != "metadata.labels" && 3398 field != "status.replicas" { 3399 t.Errorf("%s: missing prefix for: %v", k, errs[i]) 3400 } 3401 } 3402 } 3403 } 3404 3405 func TestDaemonSetUpdateMaxSurge(t *testing.T) { 3406 testCases := map[string]struct { 3407 ds *apps.RollingUpdateDaemonSet 3408 expectError bool 3409 }{ 3410 "invalid: unset": { 3411 ds: &apps.RollingUpdateDaemonSet{}, 3412 expectError: true, 3413 }, 3414 "invalid: zero percent": { 3415 ds: &apps.RollingUpdateDaemonSet{ 3416 MaxUnavailable: intstr.FromString("0%"), 3417 }, 3418 expectError: true, 3419 }, 3420 "invalid: zero": { 3421 ds: &apps.RollingUpdateDaemonSet{ 3422 MaxUnavailable: intstr.FromInt32(0), 3423 }, 3424 expectError: true, 3425 }, 3426 "valid: one": { 3427 ds: &apps.RollingUpdateDaemonSet{ 3428 MaxUnavailable: intstr.FromInt32(1), 3429 }, 3430 }, 3431 "valid: one percent": { 3432 ds: &apps.RollingUpdateDaemonSet{ 3433 MaxUnavailable: intstr.FromString("1%"), 3434 }, 3435 }, 3436 "valid: 100%": { 3437 ds: &apps.RollingUpdateDaemonSet{ 3438 MaxUnavailable: intstr.FromString("100%"), 3439 }, 3440 }, 3441 "invalid: greater than 100%": { 3442 ds: &apps.RollingUpdateDaemonSet{ 3443 MaxUnavailable: intstr.FromString("101%"), 3444 }, 3445 expectError: true, 3446 }, 3447 3448 "valid: surge and unavailable set": { 3449 ds: &apps.RollingUpdateDaemonSet{ 3450 MaxUnavailable: intstr.FromString("1%"), 3451 MaxSurge: intstr.FromString("1%"), 3452 }, 3453 expectError: true, 3454 }, 3455 3456 "invalid: surge enabled, unavailable zero percent": { 3457 ds: &apps.RollingUpdateDaemonSet{ 3458 MaxUnavailable: intstr.FromString("0%"), 3459 }, 3460 expectError: true, 3461 }, 3462 "invalid: surge enabled, unavailable zero": { 3463 ds: &apps.RollingUpdateDaemonSet{ 3464 MaxUnavailable: intstr.FromInt32(0), 3465 }, 3466 expectError: true, 3467 }, 3468 "valid: surge enabled, unavailable one": { 3469 ds: &apps.RollingUpdateDaemonSet{ 3470 MaxUnavailable: intstr.FromInt32(1), 3471 }, 3472 }, 3473 "valid: surge enabled, unavailable one percent": { 3474 ds: &apps.RollingUpdateDaemonSet{ 3475 MaxUnavailable: intstr.FromString("1%"), 3476 }, 3477 }, 3478 "valid: surge enabled, unavailable 100%": { 3479 ds: &apps.RollingUpdateDaemonSet{ 3480 MaxUnavailable: intstr.FromString("100%"), 3481 }, 3482 }, 3483 "invalid: surge enabled, unavailable greater than 100%": { 3484 ds: &apps.RollingUpdateDaemonSet{ 3485 MaxUnavailable: intstr.FromString("101%"), 3486 }, 3487 expectError: true, 3488 }, 3489 3490 "invalid: surge enabled, surge zero percent": { 3491 ds: &apps.RollingUpdateDaemonSet{ 3492 MaxSurge: intstr.FromString("0%"), 3493 }, 3494 expectError: true, 3495 }, 3496 "invalid: surge enabled, surge zero": { 3497 ds: &apps.RollingUpdateDaemonSet{ 3498 MaxSurge: intstr.FromInt32(0), 3499 }, 3500 expectError: true, 3501 }, 3502 "valid: surge enabled, surge one": { 3503 ds: &apps.RollingUpdateDaemonSet{ 3504 MaxSurge: intstr.FromInt32(1), 3505 }, 3506 }, 3507 "valid: surge enabled, surge one percent": { 3508 ds: &apps.RollingUpdateDaemonSet{ 3509 MaxSurge: intstr.FromString("1%"), 3510 }, 3511 }, 3512 "valid: surge enabled, surge 100%": { 3513 ds: &apps.RollingUpdateDaemonSet{ 3514 MaxSurge: intstr.FromString("100%"), 3515 }, 3516 }, 3517 "invalid: surge enabled, surge greater than 100%": { 3518 ds: &apps.RollingUpdateDaemonSet{ 3519 MaxSurge: intstr.FromString("101%"), 3520 }, 3521 expectError: true, 3522 }, 3523 3524 "invalid: surge enabled, 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, surge and unavailable zero percent": { 3533 ds: &apps.RollingUpdateDaemonSet{ 3534 MaxUnavailable: intstr.FromString("0%"), 3535 MaxSurge: intstr.FromString("0%"), 3536 }, 3537 expectError: true, 3538 }, 3539 "invalid: surge enabled, surge and unavailable zero": { 3540 ds: &apps.RollingUpdateDaemonSet{ 3541 MaxUnavailable: intstr.FromInt32(0), 3542 MaxSurge: intstr.FromInt32(0), 3543 }, 3544 expectError: true, 3545 }, 3546 "invalid: surge enabled, surge and unavailable mixed zero": { 3547 ds: &apps.RollingUpdateDaemonSet{ 3548 MaxUnavailable: intstr.FromInt32(0), 3549 MaxSurge: intstr.FromString("0%"), 3550 }, 3551 expectError: true, 3552 }, 3553 } 3554 for tcName, tc := range testCases { 3555 t.Run(tcName, func(t *testing.T) { 3556 errs := ValidateRollingUpdateDaemonSet(tc.ds, field.NewPath("spec", "updateStrategy", "rollingUpdate")) 3557 if tc.expectError && len(errs) == 0 { 3558 t.Errorf("Unexpected success") 3559 } 3560 if !tc.expectError && len(errs) != 0 { 3561 t.Errorf("Unexpected error(s): %v", errs) 3562 } 3563 }) 3564 } 3565 }