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