chore: wire up declarative validation for flowcontrol PriorityLevelConfiguration

This commit is contained in:
Aaron Prindle
2026-03-12 12:59:49 -07:00
parent 802b3f744b
commit 865ef0ca3e
10 changed files with 318 additions and 2 deletions

View File

@@ -18,6 +18,8 @@ limitations under the License.
// +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1
// +groupName=flowcontrol.apiserver.k8s.io

View File

@@ -0,0 +1,34 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by validation-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
return nil
}

View File

@@ -18,6 +18,8 @@ limitations under the License.
// +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta1
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta1
// +groupName=flowcontrol.apiserver.k8s.io

View File

@@ -0,0 +1,34 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by validation-gen. DO NOT EDIT.
package v1beta1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
return nil
}

View File

@@ -18,6 +18,8 @@ limitations under the License.
// +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta2
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta2
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta2
// +groupName=flowcontrol.apiserver.k8s.io

View File

@@ -0,0 +1,34 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by validation-gen. DO NOT EDIT.
package v1beta2
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
return nil
}

View File

@@ -18,6 +18,8 @@ limitations under the License.
// +k8s:conversion-gen-external-types=k8s.io/api/flowcontrol/v1beta3
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/flowcontrol/v1beta3
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/flowcontrol/v1beta3
// +groupName=flowcontrol.apiserver.k8s.io

View File

@@ -0,0 +1,34 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by validation-gen. DO NOT EDIT.
package v1beta3
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *runtime.Scheme) error {
return nil
}

View File

@@ -0,0 +1,166 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package prioritylevelconfiguration
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/apis/flowcontrol"
)
var apiVersions = []string{"v1", "v1beta1", "v1beta2", "v1beta3"}
func TestDeclarativeValidate(t *testing.T) {
for _, apiVersion := range apiVersions {
t.Run(apiVersion, func(t *testing.T) {
testDeclarativeValidate(t, apiVersion)
})
}
}
func testDeclarativeValidate(t *testing.T, apiVersion string) {
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIGroup: "flowcontrol.apiserver.k8s.io",
APIVersion: apiVersion,
})
testCases := map[string]struct {
input flowcontrol.PriorityLevelConfiguration
expectedErrs field.ErrorList
}{}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs)
})
}
}
func TestDeclarativeValidateUpdate(t *testing.T) {
for _, apiVersion := range apiVersions {
t.Run(apiVersion, func(t *testing.T) {
testDeclarativeValidateUpdate(t, apiVersion)
})
}
}
func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
testCases := map[string]struct {
old flowcontrol.PriorityLevelConfiguration
update flowcontrol.PriorityLevelConfiguration
expectedErrs field.ErrorList
}{}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
tc.old.ResourceVersion = "1"
tc.update.ResourceVersion = "2"
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIPrefix: "apis",
APIGroup: "flowcontrol.apiserver.k8s.io",
APIVersion: apiVersion,
Resource: "prioritylevelconfigurations",
Name: tc.old.Name,
IsResourceRequest: true,
Verb: "update",
})
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs)
})
}
}
// mkPLC creates a valid Limited PriorityLevelConfiguration with Reject limit response.
func mkPLC(tweaks ...func(*flowcontrol.PriorityLevelConfiguration)) flowcontrol.PriorityLevelConfiguration {
obj := flowcontrol.PriorityLevelConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "test-limited",
},
Spec: flowcontrol.PriorityLevelConfigurationSpec{
Type: flowcontrol.PriorityLevelEnablementLimited,
Limited: &flowcontrol.LimitedPriorityLevelConfiguration{
NominalConcurrencyShares: 100,
LimitResponse: flowcontrol.LimitResponse{
Type: flowcontrol.LimitResponseTypeReject,
},
},
},
}
for _, tweak := range tweaks {
tweak(&obj)
}
return obj
}
// tweakExempt switches the PLC to Exempt type with the mandatory "exempt" name.
func tweakExempt() func(*flowcontrol.PriorityLevelConfiguration) {
return func(obj *flowcontrol.PriorityLevelConfiguration) {
obj.Name = "exempt"
obj.Spec.Type = flowcontrol.PriorityLevelEnablementExempt
obj.Spec.Limited = nil
}
}
// tweakLimited sets the Limited field directly.
func tweakLimited(limited *flowcontrol.LimitedPriorityLevelConfiguration) func(*flowcontrol.PriorityLevelConfiguration) {
return func(obj *flowcontrol.PriorityLevelConfiguration) {
obj.Spec.Limited = limited
}
}
// tweakExemptConfig sets the Exempt configuration field.
func tweakExemptConfig(exempt *flowcontrol.ExemptPriorityLevelConfiguration) func(*flowcontrol.PriorityLevelConfiguration) {
return func(obj *flowcontrol.PriorityLevelConfiguration) {
obj.Spec.Exempt = exempt
}
}
// tweakLimitResponseType sets the LimitResponse type and auto-populates Queuing if Queue.
func tweakLimitResponseType(lrt flowcontrol.LimitResponseType) func(*flowcontrol.PriorityLevelConfiguration) {
return func(obj *flowcontrol.PriorityLevelConfiguration) {
if obj.Spec.Limited == nil {
return
}
obj.Spec.Limited.LimitResponse.Type = lrt
if lrt == flowcontrol.LimitResponseTypeQueue {
obj.Spec.Limited.LimitResponse.Queuing = &flowcontrol.QueuingConfiguration{
Queues: 64,
HandSize: 8,
QueueLengthLimit: 50,
}
} else {
obj.Spec.Limited.LimitResponse.Queuing = nil
}
}
}
// tweakQueuing sets the Queuing field directly, overriding auto-population.
func tweakQueuing(queuing *flowcontrol.QueuingConfiguration) func(*flowcontrol.PriorityLevelConfiguration) {
return func(obj *flowcontrol.PriorityLevelConfiguration) {
if obj.Spec.Limited == nil {
return
}
obj.Spec.Limited.LimitResponse.Queuing = queuing
}
}
func ptrInt32(v int32) *int32 {
return &v
}

View File

@@ -20,10 +20,12 @@ import (
"context"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/flowcontrol"
@@ -94,8 +96,10 @@ func (priorityLevelConfigurationStrategy) Validate(ctx context.Context, obj runt
// That means we should not allow 0 values to be introduced, either
// via v1 or v1beta3(with the roundtrip annotation) until we know
// all servers are at 1.29+ and will honor the zero value correctly.
plc := obj.(*flowcontrol.PriorityLevelConfiguration)
opts := validation.PriorityLevelValidationOptions{}
return validation.ValidatePriorityLevelConfiguration(obj.(*flowcontrol.PriorityLevelConfiguration), getRequestGroupVersion(ctx), opts)
allErrs := validation.ValidatePriorityLevelConfiguration(plc, getRequestGroupVersion(ctx), opts)
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, plc, nil, allErrs, operation.Create)
}
// WarningsOnCreate returns warnings for the creation of the given object.
@@ -119,6 +123,7 @@ func (priorityLevelConfigurationStrategy) AllowCreateOnUpdate() bool {
// ValidateUpdate is the default update validation for an end user.
func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newPL := obj.(*flowcontrol.PriorityLevelConfiguration)
oldPL := old.(*flowcontrol.PriorityLevelConfiguration)
// 1.28 server is not aware of the roundtrip annotation, and will
// default any 0 value persisted (for the NominalConcurrencyShares
@@ -128,7 +133,8 @@ func (priorityLevelConfigurationStrategy) ValidateUpdate(ctx context.Context, ob
// via v1 or v1beta3(with the roundtrip annotation) until we know
// all servers are at 1.29+ and will honor the zero value correctly.
opts := validation.PriorityLevelValidationOptions{}
return validation.ValidatePriorityLevelConfiguration(newPL, getRequestGroupVersion(ctx), opts)
allErrs := validation.ValidatePriorityLevelConfiguration(newPL, getRequestGroupVersion(ctx), opts)
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newPL, oldPL, allErrs, operation.Update)
}
// WarningsOnUpdate returns warnings for the given update.