k8s.io/kubernetes@v1.29.3/pkg/registry/apps/statefulset/strategy_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 statefulset 18 19 import ( 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 "k8s.io/apimachinery/pkg/util/intstr" 25 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 26 utilfeature "k8s.io/apiserver/pkg/util/feature" 27 featuregatetesting "k8s.io/component-base/featuregate/testing" 28 "k8s.io/kubernetes/pkg/apis/apps" 29 api "k8s.io/kubernetes/pkg/apis/core" 30 "k8s.io/kubernetes/pkg/features" 31 ) 32 33 func TestStatefulSetStrategy(t *testing.T) { 34 ctx := genericapirequest.NewDefaultContext() 35 if !Strategy.NamespaceScoped() { 36 t.Errorf("StatefulSet must be namespace scoped") 37 } 38 if Strategy.AllowCreateOnUpdate() { 39 t.Errorf("StatefulSet should not allow create on update") 40 } 41 42 validSelector := map[string]string{"a": "b"} 43 validPodTemplate := api.PodTemplate{ 44 Template: api.PodTemplateSpec{ 45 ObjectMeta: metav1.ObjectMeta{ 46 Labels: validSelector, 47 }, 48 Spec: api.PodSpec{ 49 RestartPolicy: api.RestartPolicyAlways, 50 DNSPolicy: api.DNSClusterFirst, 51 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, 52 }, 53 }, 54 } 55 ps := &apps.StatefulSet{ 56 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 57 Spec: apps.StatefulSetSpec{ 58 PodManagementPolicy: apps.OrderedReadyPodManagement, 59 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 60 Template: validPodTemplate.Template, 61 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 62 }, 63 Status: apps.StatefulSetStatus{Replicas: 3}, 64 } 65 66 Strategy.PrepareForCreate(ctx, ps) 67 if ps.Status.Replicas != 0 { 68 t.Error("StatefulSet should not allow setting status.replicas on create") 69 } 70 errs := Strategy.Validate(ctx, ps) 71 if len(errs) != 0 { 72 t.Errorf("unexpected error validating %v", errs) 73 } 74 newMinReadySeconds := int32(50) 75 // Just Spec.Replicas is allowed to change 76 validPs := &apps.StatefulSet{ 77 ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1}, 78 Spec: apps.StatefulSetSpec{ 79 PodManagementPolicy: apps.OrderedReadyPodManagement, 80 Selector: ps.Spec.Selector, 81 Template: validPodTemplate.Template, 82 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 83 MinReadySeconds: newMinReadySeconds, 84 }, 85 Status: apps.StatefulSetStatus{Replicas: 4}, 86 } 87 Strategy.PrepareForUpdate(ctx, validPs, ps) 88 t.Run("StatefulSet minReadySeconds field validations on creation and updation", func(t *testing.T) { 89 // Test creation 90 ps := &apps.StatefulSet{ 91 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 92 Spec: apps.StatefulSetSpec{ 93 PodManagementPolicy: apps.OrderedReadyPodManagement, 94 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 95 Template: validPodTemplate.Template, 96 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 97 MinReadySeconds: int32(-1), 98 }, 99 } 100 Strategy.PrepareForCreate(ctx, ps) 101 errs := Strategy.Validate(ctx, ps) 102 if len(errs) == 0 { 103 t.Errorf("expected failure when MinReadySeconds is not positive number but got no error %v", errs) 104 } 105 expectedCreateErrorString := "spec.minReadySeconds: Invalid value: -1: must be greater than or equal to 0" 106 if errs[0].Error() != expectedCreateErrorString { 107 t.Errorf("mismatched error string %v", errs[0].Error()) 108 } 109 // Test updation 110 newMinReadySeconds := int32(50) 111 // Just Spec.Replicas is allowed to change 112 validPs := &apps.StatefulSet{ 113 ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1}, 114 Spec: apps.StatefulSetSpec{ 115 PodManagementPolicy: apps.OrderedReadyPodManagement, 116 Selector: ps.Spec.Selector, 117 Template: validPodTemplate.Template, 118 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 119 MinReadySeconds: newMinReadySeconds, 120 }, 121 Status: apps.StatefulSetStatus{Replicas: 4}, 122 } 123 Strategy.PrepareForUpdate(ctx, validPs, ps) 124 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 125 if len(errs) != 0 { 126 t.Errorf("updating spec.Replicas and minReadySeconds is allowed on a statefulset: %v", errs) 127 } 128 invalidPs := ps 129 invalidPs.Spec.MinReadySeconds = int32(-1) 130 Strategy.PrepareForUpdate(ctx, validPs, invalidPs) 131 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 132 if len(errs) != 0 { 133 t.Errorf("updating spec.Replicas and minReadySeconds is allowed on a statefulset: %v", errs) 134 } 135 if validPs.Spec.MinReadySeconds != newMinReadySeconds { 136 t.Errorf("expected minReadySeconds to not be changed %v", errs) 137 } 138 }) 139 140 validPs = &apps.StatefulSet{ 141 ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1}, 142 Spec: apps.StatefulSetSpec{ 143 PodManagementPolicy: apps.OrderedReadyPodManagement, 144 Selector: ps.Spec.Selector, 145 Template: validPodTemplate.Template, 146 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 147 PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 148 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 149 WhenScaled: apps.DeletePersistentVolumeClaimRetentionPolicyType, 150 }, 151 }, 152 Status: apps.StatefulSetStatus{Replicas: 4}, 153 } 154 155 t.Run("when StatefulSetAutoDeletePVC feature gate is enabled, PersistentVolumeClaimRetentionPolicy should be updated", func(t *testing.T) { 156 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() 157 // Test creation 158 ps := &apps.StatefulSet{ 159 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 160 Spec: apps.StatefulSetSpec{ 161 PodManagementPolicy: apps.OrderedReadyPodManagement, 162 Selector: ps.Spec.Selector, 163 Template: validPodTemplate.Template, 164 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 165 PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 166 WhenDeleted: apps.PersistentVolumeClaimRetentionPolicyType("invalid policy"), 167 }, 168 }, 169 } 170 Strategy.PrepareForCreate(ctx, ps) 171 errs := Strategy.Validate(ctx, ps) 172 if len(errs) == 0 { 173 t.Errorf("expected failure when PersistentVolumeClaimRetentionPolicy is invalid") 174 } 175 expectedCreateErrorString := "spec.persistentVolumeClaimRetentionPolicy.whenDeleted: Unsupported value: \"invalid policy\": supported values: \"Retain\", \"Delete\"" 176 if errs[0].Error() != expectedCreateErrorString { 177 t.Errorf("mismatched error string %v (expected %v)", errs[0].Error(), expectedCreateErrorString) 178 } 179 Strategy.PrepareForUpdate(ctx, validPs, ps) 180 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 181 if len(errs) != 0 { 182 t.Errorf("updates to PersistentVolumeClaimRetentionPolicy should be allowed: %v", errs) 183 } 184 invalidPs := ps 185 invalidPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted = apps.PersistentVolumeClaimRetentionPolicyType("invalid type") 186 Strategy.PrepareForUpdate(ctx, validPs, invalidPs) 187 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 188 if len(errs) != 0 { 189 t.Errorf("invalid updates to PersistentVolumeClaimRetentionPolicyType should be allowed: %v", errs) 190 } 191 if validPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType || validPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled != apps.DeletePersistentVolumeClaimRetentionPolicyType { 192 t.Errorf("expected PersistentVolumeClaimRetentionPolicy to be updated: %v", errs) 193 } 194 }) 195 t.Run("when StatefulSetAutoDeletePVC feature gate is disabled, PersistentVolumeClaimRetentionPolicy should not be updated", func(t *testing.T) { 196 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)() 197 // Test creation 198 ps := &apps.StatefulSet{ 199 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 200 Spec: apps.StatefulSetSpec{ 201 PodManagementPolicy: apps.OrderedReadyPodManagement, 202 Selector: ps.Spec.Selector, 203 Template: validPodTemplate.Template, 204 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 205 PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{ 206 WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType, 207 WhenScaled: apps.DeletePersistentVolumeClaimRetentionPolicyType, 208 }, 209 }, 210 } 211 Strategy.PrepareForCreate(ctx, ps) 212 errs := Strategy.Validate(ctx, ps) 213 if len(errs) != 0 { 214 t.Errorf("unexpected failure with PersistentVolumeClaimRetentionPolicy: %v", errs) 215 } 216 if ps.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType || ps.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled != apps.DeletePersistentVolumeClaimRetentionPolicyType { 217 t.Errorf("expected invalid PersistentVolumeClaimRetentionPolicy to be defaulted to Retain, but got %v", ps.Spec.PersistentVolumeClaimRetentionPolicy) 218 } 219 Strategy.PrepareForUpdate(ctx, validPs, ps) 220 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 221 if len(errs) != 0 { 222 t.Errorf("updates to PersistentVolumeClaimRetentionPolicy should be allowed: %v", errs) 223 } 224 invalidPs := ps 225 invalidPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted = apps.PersistentVolumeClaimRetentionPolicyType("invalid type") 226 Strategy.PrepareForUpdate(ctx, validPs, invalidPs) 227 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 228 if len(errs) != 0 { 229 t.Errorf("should ignore updates to PersistentVolumeClaimRetentionPolicyType") 230 } 231 }) 232 233 validPs.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"a": "bar"}} 234 Strategy.PrepareForUpdate(ctx, validPs, ps) 235 errs = Strategy.ValidateUpdate(ctx, validPs, ps) 236 if len(errs) == 0 { 237 t.Errorf("expected a validation error since updates are disallowed on statefulsets.") 238 } 239 } 240 241 func TestStatefulSetStatusStrategy(t *testing.T) { 242 ctx := genericapirequest.NewDefaultContext() 243 if !StatusStrategy.NamespaceScoped() { 244 t.Errorf("StatefulSet must be namespace scoped") 245 } 246 if StatusStrategy.AllowCreateOnUpdate() { 247 t.Errorf("StatefulSet should not allow create on update") 248 } 249 validSelector := map[string]string{"a": "b"} 250 validPodTemplate := api.PodTemplate{ 251 Template: api.PodTemplateSpec{ 252 ObjectMeta: metav1.ObjectMeta{ 253 Labels: validSelector, 254 }, 255 Spec: api.PodSpec{ 256 RestartPolicy: api.RestartPolicyAlways, 257 DNSPolicy: api.DNSClusterFirst, 258 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, 259 }, 260 }, 261 } 262 oldPS := &apps.StatefulSet{ 263 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"}, 264 Spec: apps.StatefulSetSpec{ 265 Replicas: 3, 266 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 267 Template: validPodTemplate.Template, 268 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 269 }, 270 Status: apps.StatefulSetStatus{ 271 Replicas: 1, 272 }, 273 } 274 newPS := &apps.StatefulSet{ 275 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"}, 276 Spec: apps.StatefulSetSpec{ 277 Replicas: 1, 278 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 279 Template: validPodTemplate.Template, 280 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 281 }, 282 Status: apps.StatefulSetStatus{ 283 Replicas: 2, 284 }, 285 } 286 StatusStrategy.PrepareForUpdate(ctx, newPS, oldPS) 287 if newPS.Status.Replicas != 2 { 288 t.Errorf("StatefulSet status updates should allow change of pods: %v", newPS.Status.Replicas) 289 } 290 if newPS.Spec.Replicas != 3 { 291 t.Errorf("StatefulSet status updates should not clobber spec: %v", newPS.Spec) 292 } 293 errs := StatusStrategy.ValidateUpdate(ctx, newPS, oldPS) 294 if len(errs) != 0 { 295 t.Errorf("unexpected error %v", errs) 296 } 297 } 298 299 // generateStatefulSetWithMinReadySeconds generates a StatefulSet with min values 300 func generateStatefulSetWithMinReadySeconds(minReadySeconds int32) *apps.StatefulSet { 301 return &apps.StatefulSet{ 302 Spec: apps.StatefulSetSpec{ 303 MinReadySeconds: minReadySeconds, 304 }, 305 } 306 } 307 308 func makeStatefulSetWithMaxUnavailable(maxUnavailable *int) *apps.StatefulSet { 309 rollingUpdate := apps.RollingUpdateStatefulSetStrategy{} 310 if maxUnavailable != nil { 311 maxUnavailableIntStr := intstr.FromInt32(int32(*maxUnavailable)) 312 rollingUpdate = apps.RollingUpdateStatefulSetStrategy{ 313 MaxUnavailable: &maxUnavailableIntStr, 314 } 315 } 316 317 return &apps.StatefulSet{ 318 Spec: apps.StatefulSetSpec{ 319 UpdateStrategy: apps.StatefulSetUpdateStrategy{ 320 Type: apps.RollingUpdateStatefulSetStrategyType, 321 RollingUpdate: &rollingUpdate, 322 }, 323 }, 324 } 325 } 326 327 func getMaxUnavailable(maxUnavailable int) *int { 328 return &maxUnavailable 329 } 330 331 func createOrdinalsWithStart(start int) *apps.StatefulSetOrdinals { 332 return &apps.StatefulSetOrdinals{ 333 Start: int32(start), 334 } 335 } 336 337 func makeStatefulSetWithStatefulSetOrdinals(ordinals *apps.StatefulSetOrdinals) *apps.StatefulSet { 338 validSelector := map[string]string{"a": "b"} 339 validPodTemplate := api.PodTemplate{ 340 Template: api.PodTemplateSpec{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Labels: validSelector, 343 }, 344 Spec: api.PodSpec{ 345 RestartPolicy: api.RestartPolicyAlways, 346 DNSPolicy: api.DNSClusterFirst, 347 Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, 348 }, 349 }, 350 } 351 return &apps.StatefulSet{ 352 ObjectMeta: metav1.ObjectMeta{Name: "ss", Namespace: metav1.NamespaceDefault}, 353 Spec: apps.StatefulSetSpec{ 354 Ordinals: ordinals, 355 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 356 Template: validPodTemplate.Template, 357 UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType}, 358 PodManagementPolicy: apps.OrderedReadyPodManagement, 359 }, 360 } 361 } 362 363 // TestDropStatefulSetDisabledFields tests if the drop functionality is working fine or not 364 func TestDropStatefulSetDisabledFields(t *testing.T) { 365 testCases := []struct { 366 name string 367 enableMaxUnavailable bool 368 enableStatefulSetStartOrdinal bool 369 ss *apps.StatefulSet 370 oldSS *apps.StatefulSet 371 expectedSS *apps.StatefulSet 372 }{ 373 { 374 name: "set minReadySeconds, no update", 375 ss: generateStatefulSetWithMinReadySeconds(10), 376 oldSS: generateStatefulSetWithMinReadySeconds(20), 377 expectedSS: generateStatefulSetWithMinReadySeconds(10), 378 }, 379 { 380 name: "set minReadySeconds, oldSS field set to nil", 381 ss: generateStatefulSetWithMinReadySeconds(10), 382 oldSS: nil, 383 expectedSS: generateStatefulSetWithMinReadySeconds(10), 384 }, 385 { 386 name: "set minReadySeconds, oldSS field is set to 0", 387 ss: generateStatefulSetWithMinReadySeconds(10), 388 oldSS: generateStatefulSetWithMinReadySeconds(0), 389 expectedSS: generateStatefulSetWithMinReadySeconds(10), 390 }, 391 { 392 name: "MaxUnavailable not enabled, field not used", 393 ss: makeStatefulSetWithMaxUnavailable(nil), 394 oldSS: nil, 395 expectedSS: makeStatefulSetWithMaxUnavailable(nil), 396 }, 397 { 398 name: "MaxUnavailable not enabled, field used in new, not in old", 399 enableMaxUnavailable: false, 400 ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 401 oldSS: nil, 402 expectedSS: makeStatefulSetWithMaxUnavailable(nil), 403 }, 404 { 405 name: "MaxUnavailable not enabled, field used in old and new", 406 enableMaxUnavailable: false, 407 ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 408 oldSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 409 expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 410 }, 411 { 412 name: "MaxUnavailable enabled, field used in new only", 413 enableMaxUnavailable: true, 414 ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 415 oldSS: nil, 416 expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 417 }, 418 { 419 name: "MaxUnavailable enabled, field used in both old and new", 420 enableMaxUnavailable: true, 421 ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(1)), 422 oldSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)), 423 expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(1)), 424 }, { 425 name: "StatefulSetStartOrdinal disabled, ordinals in use in new only", 426 enableStatefulSetStartOrdinal: false, 427 ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 428 oldSS: nil, 429 expectedSS: makeStatefulSetWithStatefulSetOrdinals(nil), 430 }, 431 { 432 name: "StatefulSetStartOrdinal disabled, ordinals in use in both old and new", 433 enableStatefulSetStartOrdinal: false, 434 ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 435 oldSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(1)), 436 expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 437 }, 438 { 439 name: "StatefulSetStartOrdinal enabled, ordinals in use in new only", 440 enableStatefulSetStartOrdinal: true, 441 ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 442 oldSS: nil, 443 expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 444 }, 445 { 446 name: "StatefulSetStartOrdinal enabled, ordinals in use in both old and new", 447 enableStatefulSetStartOrdinal: true, 448 ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 449 oldSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(1)), 450 expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)), 451 }, 452 } 453 for _, tc := range testCases { 454 t.Run(tc.name, func(t *testing.T) { 455 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, tc.enableMaxUnavailable)() 456 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, tc.enableStatefulSetStartOrdinal)() 457 old := tc.oldSS.DeepCopy() 458 459 dropStatefulSetDisabledFields(tc.ss, tc.oldSS) 460 461 // old obj should never be changed 462 if diff := cmp.Diff(tc.oldSS, old); diff != "" { 463 t.Fatalf("%v: old statefulSet changed: %v", tc.name, diff) 464 } 465 466 if diff := cmp.Diff(tc.expectedSS, tc.ss); diff != "" { 467 t.Fatalf("%v: unexpected statefulSet spec: %v, want %v, got %v", tc.name, diff, tc.expectedSS, tc.ss) 468 } 469 }) 470 } 471 } 472 473 func TestStatefulSetStartOrdinalEnablement(t *testing.T) { 474 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, true)() 475 ss := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)) 476 expectedSS := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)) 477 ss.Spec.Replicas = 1 478 479 ctx := genericapirequest.NewDefaultContext() 480 Strategy.PrepareForCreate(ctx, ss) 481 482 if diff := cmp.Diff(expectedSS.Spec.Ordinals, ss.Spec.Ordinals); diff != "" { 483 t.Fatalf("Strategy.PrepareForCreate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ss, diff) 484 } 485 486 errs := Strategy.Validate(ctx, ss) 487 if len(errs) != 0 { 488 t.Errorf("Strategy.Validate(%v) returned error: %v", ss, errs) 489 } 490 491 if ss.Generation != 1 { 492 t.Errorf("Generation = %v, want = 1 for StatefulSet: %v", ss.Generation, ss) 493 } 494 495 // Validate that the ordinals field is retained when StatefulSetStartOridnal is disabled. 496 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, false)() 497 ssWhenDisabled := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)) 498 ssWhenDisabled.Spec.Replicas = 2 499 500 Strategy.PrepareForUpdate(ctx, ssWhenDisabled, ss) 501 502 if diff := cmp.Diff(expectedSS.Spec.Ordinals, ssWhenDisabled.Spec.Ordinals); diff != "" { 503 t.Fatalf("Strategy.PrepareForUpdate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ssWhenDisabled, diff) 504 } 505 506 errs = Strategy.Validate(ctx, ssWhenDisabled) 507 if len(errs) != 0 { 508 t.Errorf("Strategy.Validate(%v) returned error: %v", ssWhenDisabled, errs) 509 } 510 511 if ssWhenDisabled.Generation != 2 { 512 t.Errorf("Generation = %v, want = 2 for StatefulSet: %v", ssWhenDisabled.Generation, ssWhenDisabled) 513 } 514 515 // Validate that the ordinal field is after re-enablement. 516 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, false)() 517 ssWhenEnabled := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)) 518 ssWhenEnabled.Spec.Replicas = 3 519 520 Strategy.PrepareForUpdate(ctx, ssWhenEnabled, ssWhenDisabled) 521 522 if diff := cmp.Diff(expectedSS.Spec.Ordinals, ssWhenEnabled.Spec.Ordinals); diff != "" { 523 t.Fatalf("Strategy.PrepareForUpdate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ssWhenEnabled, diff) 524 } 525 526 errs = Strategy.Validate(ctx, ssWhenEnabled) 527 if len(errs) != 0 { 528 t.Errorf("Strategy.Validate(%v) returned error: %v", ssWhenEnabled, errs) 529 } 530 531 if ssWhenEnabled.Generation != 3 { 532 t.Errorf("Generation = %v, want = 3 for StatefulSet: %v", ssWhenEnabled.Generation, ssWhenEnabled) 533 } 534 }