...

Source file src/k8s.io/kubernetes/pkg/registry/apps/statefulset/strategy_test.go

Documentation: k8s.io/kubernetes/pkg/registry/apps/statefulset

     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  }
   535  

View as plain text