Promote minReadySeconds to GA
This commit is contained in:
@@ -129,9 +129,8 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, op
|
||||
allErrs = append(allErrs, ValidatePersistentVolumeClaimRetentionPolicy(spec.PersistentVolumeClaimRetentionPolicy, fldPath.Child("persistentVolumeClaimRetentionPolicy"))...)
|
||||
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
|
||||
}
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
|
||||
|
||||
if spec.Selector == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||
} else {
|
||||
@@ -175,19 +174,14 @@ func ValidateStatefulSetUpdate(statefulSet, oldStatefulSet *apps.StatefulSet, op
|
||||
// statefulset updates aren't super common and general updates are likely to be touching spec, so we'll do this
|
||||
// deep copy right away. This avoids mutating our inputs
|
||||
newStatefulSetClone := statefulSet.DeepCopy()
|
||||
newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone
|
||||
newStatefulSetClone.Spec.Template = oldStatefulSet.Spec.Template // +k8s:verify-mutation:reason=clone
|
||||
newStatefulSetClone.Spec.UpdateStrategy = oldStatefulSet.Spec.UpdateStrategy // +k8s:verify-mutation:reason=clone
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
|
||||
newStatefulSetClone.Spec.MinReadySeconds = oldStatefulSet.Spec.MinReadySeconds // +k8s:verify-mutation:reason=clone
|
||||
}
|
||||
newStatefulSetClone.Spec.Replicas = oldStatefulSet.Spec.Replicas // +k8s:verify-mutation:reason=clone
|
||||
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
|
||||
|
||||
newStatefulSetClone.Spec.PersistentVolumeClaimRetentionPolicy = oldStatefulSet.Spec.PersistentVolumeClaimRetentionPolicy // +k8s:verify-mutation:reason=clone
|
||||
if !apiequality.Semantic.DeepEqual(newStatefulSetClone.Spec, oldStatefulSet.Spec) {
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec"), "updates to statefulset spec for fields other than 'replicas', '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' and 'persistentVolumeClaimRetentionPolicy' are forbidden"))
|
||||
}
|
||||
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
|
||||
@@ -201,9 +195,7 @@ func ValidateStatefulSetStatus(status *apps.StatefulSetStatus, fieldPath *field.
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fieldPath.Child("readyReplicas"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), fieldPath.Child("currentReplicas"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fieldPath.Child("updatedReplicas"))...)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fieldPath.Child("availableReplicas"))...)
|
||||
}
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fieldPath.Child("availableReplicas"))...)
|
||||
if status.ObservedGeneration != nil {
|
||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.ObservedGeneration), fieldPath.Child("observedGeneration"))...)
|
||||
}
|
||||
@@ -221,15 +213,12 @@ func ValidateStatefulSetStatus(status *apps.StatefulSetStatus, fieldPath *field.
|
||||
if status.UpdatedReplicas > status.Replicas {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("updatedReplicas"), status.UpdatedReplicas, msg))
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.StatefulSetMinReadySeconds) {
|
||||
if status.AvailableReplicas > status.Replicas {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
|
||||
}
|
||||
if status.AvailableReplicas > status.ReadyReplicas {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than readyReplicas"))
|
||||
}
|
||||
if status.AvailableReplicas > status.Replicas {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, msg))
|
||||
}
|
||||
if status.AvailableReplicas > status.ReadyReplicas {
|
||||
allErrs = append(allErrs, field.Invalid(fieldPath.Child("availableReplicas"), status.AvailableReplicas, "cannot be greater than status.readyReplicas"))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@@ -690,39 +690,28 @@ func generateStatefulSetSpec(minSeconds int32) *apps.StatefulSetSpec {
|
||||
// TestValidateStatefulSetMinReadySeconds tests the StatefulSet Spec's minReadySeconds field
|
||||
func TestValidateStatefulSetMinReadySeconds(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
ss *apps.StatefulSetSpec
|
||||
enableMinReadySeconds bool
|
||||
expectErr bool
|
||||
ss *apps.StatefulSetSpec
|
||||
expectErr bool
|
||||
}{
|
||||
"valid : minReadySeconds enabled, zero": {
|
||||
ss: generateStatefulSetSpec(0),
|
||||
enableMinReadySeconds: true,
|
||||
expectErr: false,
|
||||
ss: generateStatefulSetSpec(0),
|
||||
expectErr: false,
|
||||
},
|
||||
"invalid : minReadySeconds enabled, negative": {
|
||||
ss: generateStatefulSetSpec(-1),
|
||||
enableMinReadySeconds: true,
|
||||
expectErr: true,
|
||||
ss: generateStatefulSetSpec(-1),
|
||||
expectErr: true,
|
||||
},
|
||||
"valid : minReadySeconds enabled, very large value": {
|
||||
ss: generateStatefulSetSpec(2147483647),
|
||||
enableMinReadySeconds: true,
|
||||
expectErr: false,
|
||||
ss: generateStatefulSetSpec(2147483647),
|
||||
expectErr: false,
|
||||
},
|
||||
"invalid : minReadySeconds enabled, large negative": {
|
||||
ss: generateStatefulSetSpec(-2147483648),
|
||||
enableMinReadySeconds: true,
|
||||
expectErr: true,
|
||||
},
|
||||
"valid : minReadySeconds disabled, we don't validate anything": {
|
||||
ss: generateStatefulSetSpec(-2147483648),
|
||||
enableMinReadySeconds: false,
|
||||
expectErr: false,
|
||||
ss: generateStatefulSetSpec(-2147483648),
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
for tcName, tc := range testCases {
|
||||
t.Run(tcName, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetMinReadySeconds, tc.enableMinReadySeconds)()
|
||||
errs := ValidateStatefulSetSpec(tc.ss, field.NewPath("spec", "minReadySeconds"),
|
||||
corevalidation.PodValidationOptions{})
|
||||
if tc.expectErr && len(errs) == 0 {
|
||||
@@ -739,16 +728,15 @@ func TestValidateStatefulSetStatus(t *testing.T) {
|
||||
observedGenerationMinusOne := int64(-1)
|
||||
collisionCountMinusOne := int32(-1)
|
||||
tests := []struct {
|
||||
name string
|
||||
replicas int32
|
||||
readyReplicas int32
|
||||
currentReplicas int32
|
||||
updatedReplicas int32
|
||||
availableReplicas int32
|
||||
enableMinReadySeconds bool
|
||||
observedGeneration *int64
|
||||
collisionCount *int32
|
||||
expectedErr bool
|
||||
name string
|
||||
replicas int32
|
||||
readyReplicas int32
|
||||
currentReplicas int32
|
||||
updatedReplicas int32
|
||||
availableReplicas int32
|
||||
observedGeneration *int64
|
||||
collisionCount *int32
|
||||
expectedErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid status",
|
||||
@@ -833,64 +821,33 @@ func TestValidateStatefulSetStatus(t *testing.T) {
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: number of available replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(-1),
|
||||
expectedErr: true,
|
||||
enableMinReadySeconds: true,
|
||||
name: "invalid: number of available replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(-1),
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: available replicas greater than replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(4),
|
||||
expectedErr: true,
|
||||
enableMinReadySeconds: true,
|
||||
name: "invalid: available replicas greater than replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(4),
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid: available replicas greater than ready replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 2,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(3),
|
||||
expectedErr: true,
|
||||
enableMinReadySeconds: true,
|
||||
},
|
||||
{
|
||||
name: "minReadySeconds flag not set, no validation: number of available replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(-1),
|
||||
expectedErr: false,
|
||||
enableMinReadySeconds: false,
|
||||
},
|
||||
{
|
||||
name: "minReadySeconds flag not set, no validation: available replicas greater than replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 3,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(4),
|
||||
expectedErr: false,
|
||||
enableMinReadySeconds: false,
|
||||
},
|
||||
{
|
||||
name: "minReadySeconds flag not set, no validation: available replicas greater than ready replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 2,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(3),
|
||||
expectedErr: false,
|
||||
enableMinReadySeconds: false,
|
||||
name: "invalid: available replicas greater than ready replicas",
|
||||
replicas: 3,
|
||||
readyReplicas: 2,
|
||||
currentReplicas: 2,
|
||||
availableReplicas: int32(3),
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetMinReadySeconds, test.enableMinReadySeconds)()
|
||||
status := apps.StatefulSetStatus{
|
||||
Replicas: test.replicas,
|
||||
ReadyReplicas: test.readyReplicas,
|
||||
|
Reference in New Issue
Block a user