mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-02 03:08:15 +00:00
Adding implementation of KEP-3335, StatefulSetSlice
This commit is contained in:
@@ -143,6 +143,17 @@ type StatefulSetPersistentVolumeClaimRetentionPolicy struct {
|
||||
WhenScaled PersistentVolumeClaimRetentionPolicyType
|
||||
}
|
||||
|
||||
// StatefulSetOrdinals describes the policy used for replica ordinal assignment
|
||||
// in this StatefulSet.
|
||||
type StatefulSetOrdinals struct {
|
||||
// Start is the number representing the first index that is used to represent
|
||||
// replica ordinals. Defaults to 0.
|
||||
// If set, replica ordinals will be numbered
|
||||
// [.spec.ordinals.start, .spec.ordinals.start - .spec.replicas).
|
||||
// +optional
|
||||
Start int32
|
||||
}
|
||||
|
||||
// A StatefulSetSpec is the specification of a StatefulSet.
|
||||
type StatefulSetSpec struct {
|
||||
// Replicas is the desired number of replicas of the given Template.
|
||||
@@ -215,6 +226,14 @@ type StatefulSetSpec struct {
|
||||
// StatefulSetAutoDeletePVC feature gate to be enabled, which is alpha.
|
||||
// +optional
|
||||
PersistentVolumeClaimRetentionPolicy *StatefulSetPersistentVolumeClaimRetentionPolicy
|
||||
|
||||
// Ordinals controls how the stateful set creates pod and persistent volume
|
||||
// claim names.
|
||||
// The default behavior assigns a number starting with zero and incremented by
|
||||
// one for each additional replica requested. This requires the
|
||||
// StatefulSetSlice feature gate to be enabled, which is alpha.
|
||||
// +optional
|
||||
Ordinals *StatefulSetOrdinals
|
||||
}
|
||||
|
||||
// StatefulSetStatus represents the current state of a StatefulSet.
|
||||
|
||||
2
pkg/apis/apps/v1/zz_generated.conversion.go
generated
2
pkg/apis/apps/v1/zz_generated.conversion.go
generated
@@ -1206,6 +1206,7 @@ func autoConvert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1.StatefulSetSp
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*apps.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.Ordinals requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1226,6 +1227,7 @@ func autoConvert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(in *apps.StatefulSet
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*v1.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.ReplicaStartOrdinal requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
2
pkg/apis/apps/v1beta1/zz_generated.conversion.go
generated
2
pkg/apis/apps/v1beta1/zz_generated.conversion.go
generated
@@ -870,6 +870,7 @@ func autoConvert_v1beta1_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1beta1.Sta
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*apps.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.Ordinals requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -890,6 +891,7 @@ func autoConvert_apps_StatefulSetSpec_To_v1beta1_StatefulSetSpec(in *apps.Statef
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*v1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.ReplicaStartOrdinal requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
2
pkg/apis/apps/v1beta2/zz_generated.conversion.go
generated
2
pkg/apis/apps/v1beta2/zz_generated.conversion.go
generated
@@ -1302,6 +1302,7 @@ func autoConvert_v1beta2_StatefulSetSpec_To_apps_StatefulSetSpec(in *v1beta2.Sta
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*apps.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.Ordinals requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1322,6 +1323,7 @@ func autoConvert_apps_StatefulSetSpec_To_v1beta2_StatefulSetSpec(in *apps.Statef
|
||||
out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit))
|
||||
out.MinReadySeconds = in.MinReadySeconds
|
||||
out.PersistentVolumeClaimRetentionPolicy = (*v1beta2.StatefulSetPersistentVolumeClaimRetentionPolicy)(unsafe.Pointer(in.PersistentVolumeClaimRetentionPolicy))
|
||||
// WARNING: in.ReplicaStartOrdinal requires manual conversion: does not exist in peer-type
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/apis/apps"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// ValidateStatefulSetName can be used to check whether the given StatefulSet name is valid.
|
||||
@@ -128,6 +130,12 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, op
|
||||
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetSlice) {
|
||||
if spec.Ordinals != nil {
|
||||
replicaStartOrdinal := spec.Ordinals.Start
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(replicaStartOrdinal), fldPath.Child("ordinals.start"))...)
|
||||
}
|
||||
}
|
||||
|
||||
if spec.Selector == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||
@@ -177,10 +185,17 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, op
|
||||
newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone
|
||||
newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone
|
||||
newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetSlice) {
|
||||
newStatefulSetClone.Spec.Ordinals = oldStatefulSet.Spec.Ordinals // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
|
||||
newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone
|
||||
if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden"))
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetSlice) {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden"))
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden"))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
|
||||
@@ -86,6 +86,8 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
|
||||
const enableStatefulSetAutoDeletePVC = "[enable StatefulSetAutoDeletePVC]"
|
||||
|
||||
const enableStatefulSetSlice = "[enable StatefulSetSlice]"
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
set apps.StatefulSet
|
||||
@@ -193,6 +195,20 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ordinals.start positive value " + enableStatefulSetSlice,
|
||||
set: apps.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
|
||||
Spec: apps.StatefulSetSpec{
|
||||
PodManagementPolicy: apps.ParallelPodManagement,
|
||||
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
|
||||
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
|
||||
Template: validPodTemplate.Template,
|
||||
Replicas: 3,
|
||||
Ordinals: &apps.StatefulSetOrdinals{Start: 2},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errorCases := []testCase{
|
||||
@@ -635,6 +651,23 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
field.Invalid(field.NewPath("spec", "updateStrategy", "rollingUpdate", "maxUnavailable"), nil, ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid ordinals.start " + enableStatefulSetSlice,
|
||||
set: apps.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "abc-123", Namespace: metav1.NamespaceDefault},
|
||||
Spec: apps.StatefulSetSpec{
|
||||
PodManagementPolicy: apps.ParallelPodManagement,
|
||||
UpdateStrategy: apps.StatefulSetUpdateStrategy{Type: apps.RollingUpdateStatefulSetStrategyType},
|
||||
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
|
||||
Template: validPodTemplate.Template,
|
||||
Replicas: 3,
|
||||
Ordinals: &apps.StatefulSetOrdinals{Start: -2},
|
||||
},
|
||||
},
|
||||
errs: field.ErrorList{
|
||||
field.Invalid(field.NewPath("spec", "ordinals.start"), nil, ""),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cmpOpts := []cmp.Option{cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail"), cmpopts.SortSlices(func(a, b *field.Error) bool { return a.Error() < b.Error() })}
|
||||
@@ -651,6 +684,9 @@ func TestValidateStatefulSet(t *testing.T) {
|
||||
if strings.Contains(name, enableStatefulSetAutoDeletePVC) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, true)()
|
||||
}
|
||||
if strings.Contains(name, enableStatefulSetSlice) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetSlice, true)()
|
||||
}
|
||||
|
||||
errs := ValidateStatefulSet(&testCase.set, pod.GetValidationOptionsFromPodTemplate(&testCase.set.Spec.Template, nil))
|
||||
wantErrs := testCase.errs
|
||||
|
||||
Reference in New Issue
Block a user