Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API: maxUnavailable for StatefulSet #82162

Merged
merged 2 commits into from Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/openapi-spec/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion api/openapi-spec/v3/apis__apps__v1_openapi.json
Expand Up @@ -746,8 +746,12 @@
"io.k8s.api.apps.v1.RollingUpdateStatefulSetStrategy": {
"description": "RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.",
"properties": {
"maxUnavailable": {
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.util.intstr.IntOrString",
"description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. This can not be 0. Defaults to 1. This field is alpha-level and is only honored by servers that enable the MaxUnavailableStatefulSet feature. The field applies to all pods in the range 0 to Replicas-1. That means if there is any unavailable pod in the range 0 to Replicas-1, it will be counted towards MaxUnavailable."
},
"partition": {
"description": "Partition indicates the ordinal at which the StatefulSet should be partitioned. Default value is 0.",
"description": "Partition indicates the ordinal at which the StatefulSet should be partitioned for updates. During a rolling update, all pods from ordinal Replicas-1 to Partition are updated. All pods from ordinal Partition-1 to 0 remain untouched. This is helpful in being able to do a canary based deployment. The default value is 0.",
"format": "int32",
"type": "integer"
}
Expand Down
15 changes: 13 additions & 2 deletions pkg/apis/apps/types.go
Expand Up @@ -92,9 +92,20 @@ const (

// RollingUpdateStatefulSetStrategy is used to communicate parameter for RollingUpdateStatefulSetStrategyType.
type RollingUpdateStatefulSetStrategy struct {
// Partition indicates the ordinal at which the StatefulSet should be
// partitioned.
// Partition indicates the ordinal at which the StatefulSet should be partitioned
// for updates. During a rolling update, all pods from ordinal Replicas-1 to
// Partition are updated. All pods from ordinal Partition-1 to 0 remain untouched.
// This is helpful in being able to do a canary based deployment. The default value is 0.
Partition int32
// The maximum number of pods that can be unavailable during the update.
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
// Absolute number is calculated from percentage by rounding up. This can not be 0.
// Defaults to 1. This field is alpha-level and is only honored by servers that enable the
lavalamp marked this conversation as resolved.
Show resolved Hide resolved
// MaxUnavailableStatefulSet feature. The field applies to all pods in the range 0 to
// Replicas-1. That means if there is any unavailable pod in the range 0 to Replicas-1, it
// will be counted towards MaxUnavailable.
// +optional
MaxUnavailable *intstr.IntOrString
}

// PersistentVolumeClaimRetentionPolicyType is a string enumeration of the policies that will determine
Expand Down
16 changes: 12 additions & 4 deletions pkg/apis/apps/v1/defaults.go
Expand Up @@ -113,10 +113,18 @@ func SetDefaults_StatefulSet(obj *appsv1.StatefulSet) {
}

if obj.Spec.UpdateStrategy.Type == appsv1.RollingUpdateStatefulSetStrategyType &&
obj.Spec.UpdateStrategy.RollingUpdate != nil &&
obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
obj.Spec.UpdateStrategy.RollingUpdate != nil {

if obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
lavalamp marked this conversation as resolved.
Show resolved Hide resolved
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
}
if utilfeature.DefaultFeatureGate.Enabled(features.MaxUnavailableStatefulSet) {
if obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable == nil {
maxUnavailable := intstr.FromInt(1)
obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
}
}
}

if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetAutoDeletePVC) {
Expand Down
171 changes: 167 additions & 4 deletions pkg/apis/apps/v1/defaults_test.go
Expand Up @@ -175,6 +175,15 @@ func TestSetDefaultDaemonSetSpec(t *testing.T) {
}
}

func getMaxUnavailable(maxUnavailable int) *intstr.IntOrString {
maxUnavailableIntOrStr := intstr.FromInt(maxUnavailable)
return &maxUnavailableIntOrStr
}

func getPartition(partition int32) *int32 {
return &partition
}

func TestSetDefaultStatefulSet(t *testing.T) {
defaultLabels := map[string]string{"foo": "bar"}
var defaultPartition int32 = 0
Expand All @@ -196,10 +205,11 @@ func TestSetDefaultStatefulSet(t *testing.T) {
}

tests := []struct {
name string
original *appsv1.StatefulSet
expected *appsv1.StatefulSet
enablePVCDeletionPolicy bool
name string
original *appsv1.StatefulSet
expected *appsv1.StatefulSet
enablePVCDeletionPolicy bool
enableMaxUnavailablePolicy bool
}{
{
name: "labels and default update strategy",
Expand Down Expand Up @@ -439,12 +449,165 @@ func TestSetDefaultStatefulSet(t *testing.T) {
},
enablePVCDeletionPolicy: false,
},
{
name: "MaxUnavailable disabled, with maxUnavailable not specified",
original: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: defaultTemplate,
},
},
expected: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Labels: defaultLabels,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &defaultReplicas,
Template: defaultTemplate,
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: getPartition(0),
},
},
RevisionHistoryLimit: utilpointer.Int32Ptr(10),
},
},
enableMaxUnavailablePolicy: false,
},
{
name: "MaxUnavailable disabled, with default maxUnavailable specified",
original: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: defaultTemplate,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: &defaultPartition,
MaxUnavailable: getMaxUnavailable(1),
},
},
},
},
expected: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Labels: defaultLabels,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &defaultReplicas,
Template: defaultTemplate,
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: getPartition(0),
MaxUnavailable: getMaxUnavailable(1),
},
},
RevisionHistoryLimit: utilpointer.Int32Ptr(10),
},
},
enableMaxUnavailablePolicy: false,
},
{
name: "MaxUnavailable disabled, with non default maxUnavailable specified",
original: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: defaultTemplate,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: &notTheDefaultPartition,
MaxUnavailable: getMaxUnavailable(3),
},
},
},
},
expected: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Labels: defaultLabels,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &defaultReplicas,
Template: defaultTemplate,
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: getPartition(42),
MaxUnavailable: getMaxUnavailable(3),
},
},
RevisionHistoryLimit: utilpointer.Int32Ptr(10),
},
},
enableMaxUnavailablePolicy: false,
},
{
name: "MaxUnavailable enabled, with no maxUnavailable specified",
original: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: defaultTemplate,
},
},
expected: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Labels: defaultLabels,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &defaultReplicas,
Template: defaultTemplate,
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: getPartition(0),
MaxUnavailable: getMaxUnavailable(1),
},
},
RevisionHistoryLimit: utilpointer.Int32Ptr(10),
},
},
enableMaxUnavailablePolicy: true,
},
{
name: "MaxUnavailable enabled, with non default maxUnavailable specified",
original: &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: defaultTemplate,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: &notTheDefaultPartition,
MaxUnavailable: getMaxUnavailable(3),
},
},
},
},
expected: &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Labels: defaultLabels,
},
Spec: appsv1.StatefulSetSpec{
Replicas: &defaultReplicas,
Template: defaultTemplate,
PodManagementPolicy: appsv1.OrderedReadyPodManagement,
UpdateStrategy: appsv1.StatefulSetUpdateStrategy{
Type: appsv1.RollingUpdateStatefulSetStrategyType,
RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{
Partition: getPartition(42),
MaxUnavailable: getMaxUnavailable(3),
},
},
RevisionHistoryLimit: utilpointer.Int32Ptr(10),
},
},
enableMaxUnavailablePolicy: true,
},
}

for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, test.enablePVCDeletionPolicy)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy)()

obj2 := roundTrip(t, runtime.Object(test.original))
got, ok := obj2.(*appsv1.StatefulSet)
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/apps/v1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions pkg/apis/apps/v1beta1/defaults.go
Expand Up @@ -70,10 +70,18 @@ func SetDefaults_StatefulSet(obj *appsv1beta1.StatefulSet) {
*obj.Spec.RevisionHistoryLimit = 10
}
if obj.Spec.UpdateStrategy.Type == appsv1beta1.RollingUpdateStatefulSetStrategyType &&
obj.Spec.UpdateStrategy.RollingUpdate != nil &&
obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
obj.Spec.UpdateStrategy.RollingUpdate != nil {

if obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
}
if utilfeature.DefaultFeatureGate.Enabled(features.MaxUnavailableStatefulSet) {
if obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable == nil {
maxUnavailable := intstr.FromInt(1)
obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/apps/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 16 additions & 6 deletions pkg/apis/apps/v1beta2/defaults.go
Expand Up @@ -63,15 +63,25 @@ func SetDefaults_StatefulSet(obj *appsv1beta2.StatefulSet) {
if obj.Spec.UpdateStrategy.Type == "" {
obj.Spec.UpdateStrategy.Type = appsv1beta2.RollingUpdateStatefulSetStrategyType

// UpdateStrategy.RollingUpdate will take default values below.
obj.Spec.UpdateStrategy.RollingUpdate = &appsv1beta2.RollingUpdateStatefulSetStrategy{}
if obj.Spec.UpdateStrategy.RollingUpdate == nil {
// UpdateStrategy.RollingUpdate will take default values below.
obj.Spec.UpdateStrategy.RollingUpdate = &appsv1beta2.RollingUpdateStatefulSetStrategy{}
}
}

if obj.Spec.UpdateStrategy.Type == appsv1beta2.RollingUpdateStatefulSetStrategyType &&
obj.Spec.UpdateStrategy.RollingUpdate != nil &&
obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
obj.Spec.UpdateStrategy.RollingUpdate != nil {

if obj.Spec.UpdateStrategy.RollingUpdate.Partition == nil {
obj.Spec.UpdateStrategy.RollingUpdate.Partition = new(int32)
*obj.Spec.UpdateStrategy.RollingUpdate.Partition = 0
}
if utilfeature.DefaultFeatureGate.Enabled(features.MaxUnavailableStatefulSet) {
if obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable == nil {
maxUnavailable := intstr.FromInt(1)
obj.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable = &maxUnavailable
}
}
}

if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetAutoDeletePVC) {
Expand Down