API: maxUnavailable for StatefulSet

This commit is contained in:
Mayank Kumar
2019-08-30 00:05:18 -07:00
parent e0ca5cfd73
commit 357203d992
46 changed files with 1369 additions and 484 deletions

View File

@@ -79,6 +79,18 @@ func (statefulSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Obj
pod.DropDisabledTemplateFields(&statefulSet.Spec.Template, nil)
}
// maxUnavailableInUse returns true if StatefulSet's maxUnavailable set(used)
func maxUnavailableInUse(statefulset *apps.StatefulSet) bool {
if statefulset == nil {
return false
}
if statefulset.Spec.UpdateStrategy.RollingUpdate == nil {
return false
}
return statefulset.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable != nil
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (statefulSetStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newStatefulSet := obj.(*apps.StatefulSet)
@@ -115,6 +127,11 @@ func dropStatefulSetDisabledFields(newSS *apps.StatefulSet, oldSS *apps.Stateful
newSS.Spec.PersistentVolumeClaimRetentionPolicy = nil
}
}
if !utilfeature.DefaultFeatureGate.Enabled(features.MaxUnavailableStatefulSet) && !maxUnavailableInUse(oldSS) {
if newSS.Spec.UpdateStrategy.RollingUpdate != nil {
newSS.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = nil
}
}
}
// minReadySecondsFieldsInUse returns true if fields related to StatefulSet minReadySeconds are set and

View File

@@ -17,11 +17,11 @@ limitations under the License.
package statefulset
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
"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"
@@ -353,11 +353,35 @@ func generateStatefulSetWithMinReadySeconds(minReadySeconds int32) *apps.Statefu
}
}
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
}
// TestDropStatefulSetDisabledFields tests if the drop functionality is working fine or not
func TestDropStatefulSetDisabledFields(t *testing.T) {
testCases := []struct {
name string
enableMinReadySeconds bool
enableMaxUnavailable bool
ss *apps.StatefulSet
oldSS *apps.StatefulSet
expectedSS *apps.StatefulSet
@@ -418,21 +442,57 @@ func TestDropStatefulSetDisabledFields(t *testing.T) {
oldSS: generateStatefulSetWithMinReadySeconds(0),
expectedSS: generateStatefulSetWithMinReadySeconds(10),
},
{
name: "MaxUnavailable not enabled, field not used",
enableMaxUnavailable: false,
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)),
},
}
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.StatefulSetMinReadySeconds, tc.enableMinReadySeconds)()
old := tc.oldSS.DeepCopy()
dropStatefulSetDisabledFields(tc.ss, tc.oldSS)
// old obj should never be changed
if !reflect.DeepEqual(tc.oldSS, old) {
t.Fatalf("old ds changed: %v", diff.ObjectReflectDiff(tc.oldSS, old))
if diff := cmp.Diff(tc.oldSS, old); diff != "" {
t.Fatalf("%v: old statefulSet changed: %v", tc.name, diff)
}
if !reflect.DeepEqual(tc.ss, tc.expectedSS) {
t.Fatalf("unexpected ds spec: %v", diff.ObjectReflectDiff(tc.expectedSS, tc.ss))
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)
}
})
}