kubernetes/pkg/registry/apps/statefulset/strategy_test.go

535 lines
22 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package statefulset
import (
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/apps"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
func TestStatefulSetStrategy(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
if !Strategy.NamespaceScoped() {
t.Errorf("StatefulSet must be namespace scoped")
}
if Strategy.AllowCreateOnUpdate() {
t.Errorf("StatefulSet should not allow create on update")
}
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
ps := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
},
Status: apps.StatefulSetStatus{Replicas: 3},
}
Strategy.PrepareForCreate(ctx, ps)
if ps.Status.Replicas != 0 {
t.Error("StatefulSet should not allow setting status.replicas on create")
}
errs := Strategy.Validate(ctx, ps)
if len(errs) != 0 {
t.Errorf("unexpected error validating %v", errs)
}
newMinReadySeconds := int32(50)
// Just Spec.Replicas is allowed to change
validPs := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
MinReadySeconds: newMinReadySeconds,
},
Status: apps.StatefulSetStatus{Replicas: 4},
}
Strategy.PrepareForUpdate(ctx, validPs, ps)
t.Run("StatefulSet minReadySeconds field validations on creation and updation", func(t *testing.T) {
// Test creation
ps := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
MinReadySeconds: int32(-1),
},
}
Strategy.PrepareForCreate(ctx, ps)
errs := Strategy.Validate(ctx, ps)
if len(errs) == 0 {
t.Errorf("expected failure when MinReadySeconds is not positive number but got no error %v", errs)
}
expectedCreateErrorString := "spec.minReadySeconds: Invalid value: -1: must be greater than or equal to 0"
if errs[0].Error() != expectedCreateErrorString {
t.Errorf("mismatched error string %v", errs[0].Error())
}
// Test updation
newMinReadySeconds := int32(50)
// Just Spec.Replicas is allowed to change
validPs := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
MinReadySeconds: newMinReadySeconds,
},
Status: apps.StatefulSetStatus{Replicas: 4},
}
Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("updating spec.Replicas and minReadySeconds is allowed on a statefulset: %v", errs)
}
invalidPs := ps
invalidPs.Spec.MinReadySeconds = int32(-1)
Strategy.PrepareForUpdate(ctx, validPs, invalidPs)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("updating spec.Replicas and minReadySeconds is allowed on a statefulset: %v", errs)
}
if validPs.Spec.MinReadySeconds != newMinReadySeconds {
t.Errorf("expected minReadySeconds to not be changed %v", errs)
}
})
validPs = &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace, ResourceVersion: "1", Generation: 1},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{
WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType,
WhenScaled: apps.DeletePersistentVolumeClaimRetentionPolicyType,
},
},
Status: apps.StatefulSetStatus{Replicas: 4},
}
t.Run("when StatefulSetAutoDeletePVC feature gate is enabled, PersistentVolumeClaimRetentionPolicy should be updated", func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)()
// Test creation
ps := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{
WhenDeleted: apps.PersistentVolumeClaimRetentionPolicyType("invalid policy"),
},
},
}
Strategy.PrepareForCreate(ctx, ps)
errs := Strategy.Validate(ctx, ps)
if len(errs) == 0 {
t.Errorf("expected failure when PersistentVolumeClaimRetentionPolicy is invalid")
}
expectedCreateErrorString := "spec.persistentVolumeClaimRetentionPolicy.whenDeleted: Unsupported value: \"invalid policy\": supported values: \"Retain\", \"Delete\""
if errs[0].Error() != expectedCreateErrorString {
t.Errorf("mismatched error string %v (expected %v)", errs[0].Error(), expectedCreateErrorString)
}
Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("updates to PersistentVolumeClaimRetentionPolicy should be allowed: %v", errs)
}
invalidPs := ps
invalidPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted = apps.PersistentVolumeClaimRetentionPolicyType("invalid type")
Strategy.PrepareForUpdate(ctx, validPs, invalidPs)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("invalid updates to PersistentVolumeClaimRetentionPolicyType should be allowed: %v", errs)
}
if validPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType || validPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled != apps.DeletePersistentVolumeClaimRetentionPolicyType {
t.Errorf("expected PersistentVolumeClaimRetentionPolicy to be updated: %v", errs)
}
})
t.Run("when StatefulSetAutoDeletePVC feature gate is disabled, PersistentVolumeClaimRetentionPolicy should not be updated", func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)()
// Test creation
ps := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: apps.StatefulSetSpec{
PodManagementPolicy: apps.OrderedReadyPodManagement,
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
PersistentVolumeClaimRetentionPolicy: &apps.StatefulSetPersistentVolumeClaimRetentionPolicy{
WhenDeleted: apps.RetainPersistentVolumeClaimRetentionPolicyType,
WhenScaled: apps.DeletePersistentVolumeClaimRetentionPolicyType,
},
},
}
Strategy.PrepareForCreate(ctx, ps)
errs := Strategy.Validate(ctx, ps)
if len(errs) != 0 {
t.Errorf("unexpected failure with PersistentVolumeClaimRetentionPolicy: %v", errs)
}
if ps.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted != apps.RetainPersistentVolumeClaimRetentionPolicyType || ps.Spec.PersistentVolumeClaimRetentionPolicy.WhenScaled != apps.DeletePersistentVolumeClaimRetentionPolicyType {
t.Errorf("expected invalid PersistentVolumeClaimRetentionPolicy to be defaulted to Retain, but got %v", ps.Spec.PersistentVolumeClaimRetentionPolicy)
}
Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("updates to PersistentVolumeClaimRetentionPolicy should be allowed: %v", errs)
}
invalidPs := ps
invalidPs.Spec.PersistentVolumeClaimRetentionPolicy.WhenDeleted = apps.PersistentVolumeClaimRetentionPolicyType("invalid type")
Strategy.PrepareForUpdate(ctx, validPs, invalidPs)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) != 0 {
t.Errorf("should ignore updates to PersistentVolumeClaimRetentionPolicyType")
}
})
validPs.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"a": "bar"}}
Strategy.PrepareForUpdate(ctx, validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) == 0 {
t.Errorf("expected a validation error since updates are disallowed on statefulsets.")
}
}
func TestStatefulSetStatusStrategy(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
if !StatusStrategy.NamespaceScoped() {
t.Errorf("StatefulSet must be namespace scoped")
}
if StatusStrategy.AllowCreateOnUpdate() {
t.Errorf("StatefulSet should not allow create on update")
}
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
oldPS := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"},
Spec: apps.StatefulSetSpec{
Replicas: 3,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
},
Status: apps.StatefulSetStatus{
Replicas: 1,
},
}
newPS := &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"},
Spec: apps.StatefulSetSpec{
Replicas: 1,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
},
Status: apps.StatefulSetStatus{
Replicas: 2,
},
}
StatusStrategy.PrepareForUpdate(ctx, newPS, oldPS)
if newPS.Status.Replicas != 2 {
t.Errorf("StatefulSet status updates should allow change of pods: %v", newPS.Status.Replicas)
}
if newPS.Spec.Replicas != 3 {
t.Errorf("StatefulSet status updates should not clobber spec: %v", newPS.Spec)
}
errs := StatusStrategy.ValidateUpdate(ctx, newPS, oldPS)
if len(errs) != 0 {
t.Errorf("unexpected error %v", errs)
}
}
// generateStatefulSetWithMinReadySeconds generates a StatefulSet with min values
func generateStatefulSetWithMinReadySeconds(minReadySeconds int32) *apps.StatefulSet {
return &apps.StatefulSet{
Spec: apps.StatefulSetSpec{
MinReadySeconds: minReadySeconds,
},
}
}
func makeStatefulSetWithMaxUnavailable(maxUnavailable *int) *apps.StatefulSet {
rollingUpdate := apps.RollingUpdateStatefulSetStrategy{}
if maxUnavailable != nil {
maxUnavailableIntStr := intstr.FromInt(*maxUnavailable)
rollingUpdate = apps.RollingUpdateStatefulSetStrategy{
MaxUnavailable: &maxUnavailableIntStr,
}
}
return &apps.StatefulSet{
Spec: apps.StatefulSetSpec{
UpdateStrategy: apps.StatefulSetUpdateStrategy{
Type: apps.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &rollingUpdate,
},
},
}
}
func getMaxUnavailable(maxUnavailable int) *int {
return &maxUnavailable
}
func createOrdinalsWithStart(start int) *apps.StatefulSetOrdinals {
return &apps.StatefulSetOrdinals{
Start: int32(start),
}
}
func makeStatefulSetWithStatefulSetOrdinals(ordinals *apps.StatefulSetOrdinals) *apps.StatefulSet {
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
return &apps.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "ss", Namespace: metav1.NamespaceDefault},
Spec: apps.StatefulSetSpec{
Ordinals: ordinals,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
PodManagementPolicy: apps.OrderedReadyPodManagement,
},
}
}
// TestDropStatefulSetDisabledFields tests if the drop functionality is working fine or not
func TestDropStatefulSetDisabledFields(t *testing.T) {
testCases := []struct {
name string
enableMaxUnavailable bool
enableStatefulSetStartOrdinal bool
ss *apps.StatefulSet
oldSS *apps.StatefulSet
expectedSS *apps.StatefulSet
}{
{
name: "set minReadySeconds, no update",
ss: generateStatefulSetWithMinReadySeconds(10),
oldSS: generateStatefulSetWithMinReadySeconds(20),
expectedSS: generateStatefulSetWithMinReadySeconds(10),
},
{
name: "set minReadySeconds, oldSS field set to nil",
ss: generateStatefulSetWithMinReadySeconds(10),
oldSS: nil,
expectedSS: generateStatefulSetWithMinReadySeconds(10),
},
{
name: "set minReadySeconds, oldSS field is set to 0",
ss: generateStatefulSetWithMinReadySeconds(10),
oldSS: generateStatefulSetWithMinReadySeconds(0),
expectedSS: generateStatefulSetWithMinReadySeconds(10),
},
{
name: "MaxUnavailable not enabled, field not used",
ss: makeStatefulSetWithMaxUnavailable(nil),
oldSS: nil,
expectedSS: makeStatefulSetWithMaxUnavailable(nil),
},
{
name: "MaxUnavailable not enabled, field used in new, not in old",
enableMaxUnavailable: false,
ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
oldSS: nil,
expectedSS: makeStatefulSetWithMaxUnavailable(nil),
},
{
name: "MaxUnavailable not enabled, field used in old and new",
enableMaxUnavailable: false,
ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
oldSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
},
{
name: "MaxUnavailable enabled, field used in new only",
enableMaxUnavailable: true,
ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
oldSS: nil,
expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
},
{
name: "MaxUnavailable enabled, field used in both old and new",
enableMaxUnavailable: true,
ss: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(1)),
oldSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(3)),
expectedSS: makeStatefulSetWithMaxUnavailable(getMaxUnavailable(1)),
}, {
name: "StatefulSetStartOrdinal disabled, ordinals in use in new only",
enableStatefulSetStartOrdinal: false,
ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
oldSS: nil,
expectedSS: makeStatefulSetWithStatefulSetOrdinals(nil),
},
{
name: "StatefulSetStartOrdinal disabled, ordinals in use in both old and new",
enableStatefulSetStartOrdinal: false,
ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
oldSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(1)),
expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
},
{
name: "StatefulSetStartOrdinal enabled, ordinals in use in new only",
enableStatefulSetStartOrdinal: true,
ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
oldSS: nil,
expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
},
{
name: "StatefulSetStartOrdinal enabled, ordinals in use in both old and new",
enableStatefulSetStartOrdinal: true,
ss: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
oldSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(1)),
expectedSS: makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, tc.enableMaxUnavailable)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, tc.enableStatefulSetStartOrdinal)()
old := tc.oldSS.DeepCopy()
dropStatefulSetDisabledFields(tc.ss, tc.oldSS)
// old obj should never be changed
if diff := cmp.Diff(tc.oldSS, old); diff != "" {
t.Fatalf("%v: old statefulSet changed: %v", tc.name, diff)
}
if diff := cmp.Diff(tc.expectedSS, tc.ss); diff != "" {
t.Fatalf("%v: unexpected statefulSet spec: %v, want %v, got %v", tc.name, diff, tc.expectedSS, tc.ss)
}
})
}
}
func TestStatefulSetStartOrdinalEnablement(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, true)()
ss := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2))
expectedSS := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2))
ss.Spec.Replicas = 1
ctx := genericapirequest.NewDefaultContext()
Strategy.PrepareForCreate(ctx, ss)
if diff := cmp.Diff(expectedSS.Spec.Ordinals, ss.Spec.Ordinals); diff != "" {
t.Fatalf("Strategy.PrepareForCreate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ss, diff)
}
errs := Strategy.Validate(ctx, ss)
if len(errs) != 0 {
t.Errorf("Strategy.Validate(%v) returned error: %v", ss, errs)
}
if ss.Generation != 1 {
t.Errorf("Generation = %v, want = 1 for StatefulSet: %v", ss.Generation, ss)
}
// Validate that the ordinals field is retained when StatefulSetStartOridnal is disabled.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, false)()
ssWhenDisabled := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2))
ssWhenDisabled.Spec.Replicas = 2
Strategy.PrepareForUpdate(ctx, ssWhenDisabled, ss)
if diff := cmp.Diff(expectedSS.Spec.Ordinals, ssWhenDisabled.Spec.Ordinals); diff != "" {
t.Fatalf("Strategy.PrepareForUpdate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ssWhenDisabled, diff)
}
errs = Strategy.Validate(ctx, ssWhenDisabled)
if len(errs) != 0 {
t.Errorf("Strategy.Validate(%v) returned error: %v", ssWhenDisabled, errs)
}
if ssWhenDisabled.Generation != 2 {
t.Errorf("Generation = %v, want = 2 for StatefulSet: %v", ssWhenDisabled.Generation, ssWhenDisabled)
}
// Validate that the ordinal field is after re-enablement.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetStartOrdinal, false)()
ssWhenEnabled := makeStatefulSetWithStatefulSetOrdinals(createOrdinalsWithStart(2))
ssWhenEnabled.Spec.Replicas = 3
Strategy.PrepareForUpdate(ctx, ssWhenEnabled, ssWhenDisabled)
if diff := cmp.Diff(expectedSS.Spec.Ordinals, ssWhenEnabled.Spec.Ordinals); diff != "" {
t.Fatalf("Strategy.PrepareForUpdate(%v) unexpected .spec.ordinals change: (-want, +got):\n%v", ssWhenEnabled, diff)
}
errs = Strategy.Validate(ctx, ssWhenEnabled)
if len(errs) != 0 {
t.Errorf("Strategy.Validate(%v) returned error: %v", ssWhenEnabled, errs)
}
if ssWhenEnabled.Generation != 3 {
t.Errorf("Generation = %v, want = 3 for StatefulSet: %v", ssWhenEnabled.Generation, ssWhenEnabled)
}
}