mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
add validation, field disablement and tests
This commit is contained in:
parent
f7c46df665
commit
14783b8a9b
@ -606,6 +606,9 @@ func ValidateDeploymentStatus(status *apps.DeploymentStatus, fldPath *field.Path
|
|||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...)
|
||||||
|
if status.TerminatingReplicas != nil {
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.TerminatingReplicas), fldPath.Child("terminatingReplicas"))...)
|
||||||
|
}
|
||||||
if status.CollisionCount != nil {
|
if status.CollisionCount != nil {
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.CollisionCount), fldPath.Child("collisionCount"))...)
|
||||||
}
|
}
|
||||||
@ -702,6 +705,9 @@ func ValidateReplicaSetStatus(status apps.ReplicaSetStatus, fldPath *field.Path)
|
|||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ReadyReplicas), fldPath.Child("readyReplicas"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...)
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.ObservedGeneration), fldPath.Child("observedGeneration"))...)
|
||||||
|
if status.TerminatingReplicas != nil {
|
||||||
|
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*status.TerminatingReplicas), fldPath.Child("terminatingReplicas"))...)
|
||||||
|
}
|
||||||
msg := "cannot be greater than status.replicas"
|
msg := "cannot be greater than status.replicas"
|
||||||
if status.FullyLabeledReplicas > status.Replicas {
|
if status.FullyLabeledReplicas > status.Replicas {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("fullyLabeledReplicas"), status.FullyLabeledReplicas, msg))
|
||||||
|
@ -2334,59 +2334,84 @@ func TestValidateDeploymentStatus(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
||||||
replicas int32
|
replicas int32
|
||||||
updatedReplicas int32
|
updatedReplicas int32
|
||||||
readyReplicas int32
|
readyReplicas int32
|
||||||
availableReplicas int32
|
availableReplicas int32
|
||||||
observedGeneration int64
|
terminatingReplicas *int32
|
||||||
collisionCount *int32
|
observedGeneration int64
|
||||||
|
collisionCount *int32
|
||||||
|
|
||||||
expectedErr bool
|
expectedErr bool
|
||||||
}{{
|
}{{
|
||||||
name: "valid status",
|
name: "valid status",
|
||||||
replicas: 3,
|
replicas: 3,
|
||||||
updatedReplicas: 3,
|
updatedReplicas: 3,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
observedGeneration: 2,
|
terminatingReplicas: nil,
|
||||||
expectedErr: false,
|
observedGeneration: 2,
|
||||||
|
expectedErr: false,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid replicas",
|
name: "valid status with terminating replicas",
|
||||||
replicas: -1,
|
replicas: 3,
|
||||||
updatedReplicas: 2,
|
updatedReplicas: 3,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
observedGeneration: 2,
|
terminatingReplicas: ptr.To[int32](5),
|
||||||
expectedErr: true,
|
observedGeneration: 2,
|
||||||
|
expectedErr: false,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid updatedReplicas",
|
name: "invalid replicas",
|
||||||
replicas: 2,
|
replicas: -1,
|
||||||
updatedReplicas: -1,
|
updatedReplicas: 2,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
observedGeneration: 2,
|
terminatingReplicas: nil,
|
||||||
expectedErr: true,
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid readyReplicas",
|
name: "invalid updatedReplicas",
|
||||||
replicas: 3,
|
replicas: 2,
|
||||||
readyReplicas: -1,
|
updatedReplicas: -1,
|
||||||
availableReplicas: 1,
|
readyReplicas: 2,
|
||||||
observedGeneration: 2,
|
availableReplicas: 1,
|
||||||
expectedErr: true,
|
terminatingReplicas: nil,
|
||||||
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid availableReplicas",
|
name: "invalid readyReplicas",
|
||||||
replicas: 3,
|
replicas: 3,
|
||||||
readyReplicas: 3,
|
readyReplicas: -1,
|
||||||
availableReplicas: -1,
|
availableReplicas: 1,
|
||||||
observedGeneration: 2,
|
terminatingReplicas: nil,
|
||||||
expectedErr: true,
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "invalid observedGeneration",
|
name: "invalid availableReplicas",
|
||||||
replicas: 3,
|
replicas: 3,
|
||||||
readyReplicas: 3,
|
readyReplicas: 3,
|
||||||
availableReplicas: 3,
|
availableReplicas: -1,
|
||||||
observedGeneration: -1,
|
terminatingReplicas: nil,
|
||||||
expectedErr: true,
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
|
}, {
|
||||||
|
name: "invalid terminatingReplicas",
|
||||||
|
replicas: 3,
|
||||||
|
updatedReplicas: 3,
|
||||||
|
readyReplicas: 2,
|
||||||
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: ptr.To[int32](-1),
|
||||||
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
|
}, {
|
||||||
|
name: "invalid observedGeneration",
|
||||||
|
replicas: 3,
|
||||||
|
readyReplicas: 3,
|
||||||
|
availableReplicas: 3,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
observedGeneration: -1,
|
||||||
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
name: "updatedReplicas greater than replicas",
|
name: "updatedReplicas greater than replicas",
|
||||||
replicas: 3,
|
replicas: 3,
|
||||||
@ -2427,12 +2452,13 @@ func TestValidateDeploymentStatus(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
status := apps.DeploymentStatus{
|
status := apps.DeploymentStatus{
|
||||||
Replicas: test.replicas,
|
Replicas: test.replicas,
|
||||||
UpdatedReplicas: test.updatedReplicas,
|
UpdatedReplicas: test.updatedReplicas,
|
||||||
ReadyReplicas: test.readyReplicas,
|
ReadyReplicas: test.readyReplicas,
|
||||||
AvailableReplicas: test.availableReplicas,
|
AvailableReplicas: test.availableReplicas,
|
||||||
ObservedGeneration: test.observedGeneration,
|
TerminatingReplicas: test.terminatingReplicas,
|
||||||
CollisionCount: test.collisionCount,
|
ObservedGeneration: test.observedGeneration,
|
||||||
|
CollisionCount: test.collisionCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := ValidateDeploymentStatus(&status, field.NewPath("status"))
|
errs := ValidateDeploymentStatus(&status, field.NewPath("status"))
|
||||||
@ -2735,6 +2761,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas int32
|
fullyLabeledReplicas int32
|
||||||
readyReplicas int32
|
readyReplicas int32
|
||||||
availableReplicas int32
|
availableReplicas int32
|
||||||
|
terminatingReplicas *int32
|
||||||
observedGeneration int64
|
observedGeneration int64
|
||||||
|
|
||||||
expectedErr bool
|
expectedErr bool
|
||||||
@ -2744,6 +2771,16 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
observedGeneration: 2,
|
||||||
|
expectedErr: false,
|
||||||
|
}, {
|
||||||
|
name: "valid status with terminating replicas",
|
||||||
|
replicas: 3,
|
||||||
|
fullyLabeledReplicas: 3,
|
||||||
|
readyReplicas: 2,
|
||||||
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: ptr.To[int32](5),
|
||||||
observedGeneration: 2,
|
observedGeneration: 2,
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
}, {
|
}, {
|
||||||
@ -2752,6 +2789,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 2,
|
observedGeneration: 2,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2760,6 +2798,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: -1,
|
fullyLabeledReplicas: -1,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 2,
|
observedGeneration: 2,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2768,6 +2807,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: -1,
|
readyReplicas: -1,
|
||||||
availableReplicas: 1,
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 2,
|
observedGeneration: 2,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2776,6 +2816,16 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 3,
|
readyReplicas: 3,
|
||||||
availableReplicas: -1,
|
availableReplicas: -1,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
observedGeneration: 2,
|
||||||
|
expectedErr: true,
|
||||||
|
}, {
|
||||||
|
name: "invalid terminatingReplicas",
|
||||||
|
replicas: 3,
|
||||||
|
fullyLabeledReplicas: 3,
|
||||||
|
readyReplicas: 2,
|
||||||
|
availableReplicas: 1,
|
||||||
|
terminatingReplicas: ptr.To[int32](-1),
|
||||||
observedGeneration: 2,
|
observedGeneration: 2,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2784,6 +2834,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 3,
|
readyReplicas: 3,
|
||||||
availableReplicas: 3,
|
availableReplicas: 3,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: -1,
|
observedGeneration: -1,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2792,6 +2843,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 4,
|
fullyLabeledReplicas: 4,
|
||||||
readyReplicas: 3,
|
readyReplicas: 3,
|
||||||
availableReplicas: 3,
|
availableReplicas: 3,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 1,
|
observedGeneration: 1,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2800,6 +2852,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 4,
|
readyReplicas: 4,
|
||||||
availableReplicas: 3,
|
availableReplicas: 3,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 1,
|
observedGeneration: 1,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2808,6 +2861,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 3,
|
readyReplicas: 3,
|
||||||
availableReplicas: 4,
|
availableReplicas: 4,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 1,
|
observedGeneration: 1,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
}, {
|
}, {
|
||||||
@ -2816,6 +2870,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
fullyLabeledReplicas: 3,
|
fullyLabeledReplicas: 3,
|
||||||
readyReplicas: 2,
|
readyReplicas: 2,
|
||||||
availableReplicas: 3,
|
availableReplicas: 3,
|
||||||
|
terminatingReplicas: nil,
|
||||||
observedGeneration: 1,
|
observedGeneration: 1,
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
@ -2827,6 +2882,7 @@ func TestValidateReplicaSetStatus(t *testing.T) {
|
|||||||
FullyLabeledReplicas: test.fullyLabeledReplicas,
|
FullyLabeledReplicas: test.fullyLabeledReplicas,
|
||||||
ReadyReplicas: test.readyReplicas,
|
ReadyReplicas: test.readyReplicas,
|
||||||
AvailableReplicas: test.availableReplicas,
|
AvailableReplicas: test.availableReplicas,
|
||||||
|
TerminatingReplicas: test.terminatingReplicas,
|
||||||
ObservedGeneration: test.observedGeneration,
|
ObservedGeneration: test.observedGeneration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,12 @@ import (
|
|||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/api/pod"
|
"k8s.io/kubernetes/pkg/api/pod"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -192,6 +194,7 @@ func (deploymentStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old r
|
|||||||
oldDeployment := old.(*apps.Deployment)
|
oldDeployment := old.(*apps.Deployment)
|
||||||
newDeployment.Spec = oldDeployment.Spec
|
newDeployment.Spec = oldDeployment.Spec
|
||||||
newDeployment.Labels = oldDeployment.Labels
|
newDeployment.Labels = oldDeployment.Labels
|
||||||
|
dropDisabledStatusFields(&newDeployment.Status, &oldDeployment.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user updating status
|
// ValidateUpdate is the default update validation for an end user updating status
|
||||||
@ -203,3 +206,11 @@ func (deploymentStatusStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
|||||||
func (deploymentStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
func (deploymentStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropDisabledStatusFields removes disabled fields from the deployment status.
|
||||||
|
func dropDisabledStatusFields(deploymentStatus, oldDeploymentStatus *apps.DeploymentStatus) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.DeploymentPodReplacementPolicy) &&
|
||||||
|
(oldDeploymentStatus == nil || oldDeploymentStatus.TerminatingReplicas == nil) {
|
||||||
|
deploymentStatus.TerminatingReplicas = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package deployment
|
package deployment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -26,9 +27,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
podtest "k8s.io/kubernetes/pkg/api/pod/testing"
|
podtest "k8s.io/kubernetes/pkg/api/pod/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -62,6 +66,70 @@ func TestStatusUpdates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStatusUpdatesWithDeploymentPodReplacementPolicy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
enableDeploymentPodReplacementPolicy bool
|
||||||
|
terminatingReplicas *int32
|
||||||
|
terminatingReplicasUpdate *int32
|
||||||
|
expectedTerminatingReplicas *int32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should not allow updates when feature gate is disabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: false,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](2),
|
||||||
|
expectedTerminatingReplicas: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should allow update when the field is in use when feature gate is disabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: false,
|
||||||
|
terminatingReplicas: ptr.To[int32](2),
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](5),
|
||||||
|
expectedTerminatingReplicas: ptr.To[int32](5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should allow updates when feature gate is enabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: true,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](2),
|
||||||
|
expectedTerminatingReplicas: ptr.To[int32](2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeploymentPodReplacementPolicy, tc.enableDeploymentPodReplacementPolicy)
|
||||||
|
|
||||||
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
|
validSelector := map[string]string{"a": "b"}
|
||||||
|
oldDeploy := newDeploymentWithSelectorLabels(validSelector)
|
||||||
|
oldDeploy.Spec.Replicas = 3
|
||||||
|
oldDeploy.Status.Replicas = 3
|
||||||
|
oldDeploy.Status.TerminatingReplicas = tc.terminatingReplicas
|
||||||
|
|
||||||
|
newDeploy := newDeploymentWithSelectorLabels(validSelector)
|
||||||
|
newDeploy.Spec.Replicas = 3
|
||||||
|
newDeploy.Status.Replicas = 2
|
||||||
|
newDeploy.Status.TerminatingReplicas = tc.terminatingReplicasUpdate
|
||||||
|
|
||||||
|
StatusStrategy.PrepareForUpdate(ctx, newDeploy, oldDeploy)
|
||||||
|
if newDeploy.Status.Replicas != 2 {
|
||||||
|
t.Errorf("ReplicaSet status updates should allow change of replicas: %v", newDeploy.Status.Replicas)
|
||||||
|
}
|
||||||
|
if !ptr.Equal(newDeploy.Status.TerminatingReplicas, tc.expectedTerminatingReplicas) {
|
||||||
|
t.Errorf("ReplicaSet status updates failed, expected terminating pods: %v, got: %v", ptr.Deref(tc.expectedTerminatingReplicas, -1), ptr.Deref(newDeploy.Status.TerminatingReplicas, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := StatusStrategy.ValidateUpdate(ctx, newDeploy, oldDeploy)
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("Unexpected error %v", errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newDeployment(labels, annotations map[string]string) *apps.Deployment {
|
func newDeployment(labels, annotations map[string]string) *apps.Deployment {
|
||||||
return &apps.Deployment{
|
return &apps.Deployment{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -37,10 +37,12 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
apistorage "k8s.io/apiserver/pkg/storage"
|
apistorage "k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
"k8s.io/kubernetes/pkg/api/pod"
|
"k8s.io/kubernetes/pkg/api/pod"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,6 +233,7 @@ func (rsStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.O
|
|||||||
oldRS := old.(*apps.ReplicaSet)
|
oldRS := old.(*apps.ReplicaSet)
|
||||||
// update is not allowed to set spec
|
// update is not allowed to set spec
|
||||||
newRS.Spec = oldRS.Spec
|
newRS.Spec = oldRS.Spec
|
||||||
|
dropDisabledStatusFields(&newRS.Status, &oldRS.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rsStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (rsStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
@ -241,3 +244,11 @@ func (rsStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
|
|||||||
func (rsStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
func (rsStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropDisabledStatusFields removes disabled fields from the replica set status.
|
||||||
|
func dropDisabledStatusFields(rsStatus, oldRSStatus *apps.ReplicaSetStatus) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.DeploymentPodReplacementPolicy) &&
|
||||||
|
(oldRSStatus == nil || oldRSStatus.TerminatingReplicas == nil) {
|
||||||
|
rsStatus.TerminatingReplicas = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -17,15 +17,19 @@ limitations under the License.
|
|||||||
package replicaset
|
package replicaset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
podtest "k8s.io/kubernetes/pkg/api/pod/testing"
|
podtest "k8s.io/kubernetes/pkg/api/pod/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/apps"
|
"k8s.io/kubernetes/pkg/apis/apps"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -148,6 +152,70 @@ func TestReplicaSetStatusStrategy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReplicaSetStatusStrategyWithDeploymentPodReplacementPolicy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
enableDeploymentPodReplacementPolicy bool
|
||||||
|
terminatingReplicas *int32
|
||||||
|
terminatingReplicasUpdate *int32
|
||||||
|
expectedTerminatingReplicas *int32
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should not allow updates when feature gate is disabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: false,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](2),
|
||||||
|
expectedTerminatingReplicas: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should allow update when the field is in use when feature gate is disabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: false,
|
||||||
|
terminatingReplicas: ptr.To[int32](2),
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](5),
|
||||||
|
expectedTerminatingReplicas: ptr.To[int32](5),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should allow updates when feature gate is enabled",
|
||||||
|
enableDeploymentPodReplacementPolicy: true,
|
||||||
|
terminatingReplicas: nil,
|
||||||
|
terminatingReplicasUpdate: ptr.To[int32](2),
|
||||||
|
expectedTerminatingReplicas: ptr.To[int32](2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeploymentPodReplacementPolicy, tc.enableDeploymentPodReplacementPolicy)
|
||||||
|
|
||||||
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
|
validSelector := map[string]string{"a": "b"}
|
||||||
|
oldRS := newReplicaSetWithSelectorLabels(validSelector)
|
||||||
|
oldRS.Spec.Replicas = 3
|
||||||
|
oldRS.Status.Replicas = 3
|
||||||
|
oldRS.Status.TerminatingReplicas = tc.terminatingReplicas
|
||||||
|
|
||||||
|
newRS := newReplicaSetWithSelectorLabels(validSelector)
|
||||||
|
newRS.Spec.Replicas = 3
|
||||||
|
newRS.Status.Replicas = 2
|
||||||
|
newRS.Status.TerminatingReplicas = tc.terminatingReplicasUpdate
|
||||||
|
|
||||||
|
StatusStrategy.PrepareForUpdate(ctx, newRS, oldRS)
|
||||||
|
if newRS.Status.Replicas != 2 {
|
||||||
|
t.Errorf("ReplicaSet status updates should allow change of replicas: %v", newRS.Status.Replicas)
|
||||||
|
}
|
||||||
|
if !ptr.Equal(newRS.Status.TerminatingReplicas, tc.expectedTerminatingReplicas) {
|
||||||
|
t.Errorf("ReplicaSet status updates failed, expected terminating pods: %v, got: %v", ptr.Deref(tc.expectedTerminatingReplicas, -1), ptr.Deref(newRS.Status.TerminatingReplicas, -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := StatusStrategy.ValidateUpdate(ctx, newRS, oldRS)
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
t.Errorf("Unexpected error %v", errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSelectorImmutability(t *testing.T) {
|
func TestSelectorImmutability(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
requestInfo genericapirequest.RequestInfo
|
requestInfo genericapirequest.RequestInfo
|
||||||
|
Loading…
Reference in New Issue
Block a user