From 865ef0ca3e36fb5234985f4e1de411b977145b8a Mon Sep 17 00:00:00 2001 From: Aaron Prindle Date: Thu, 12 Mar 2026 12:59:49 -0700 Subject: [PATCH] chore: wire up declarative validation for flowcontrol PriorityLevelConfiguration --- pkg/apis/flowcontrol/v1/doc.go | 2 + .../v1/zz_generated.validations.go | 34 ++++ pkg/apis/flowcontrol/v1beta1/doc.go | 2 + .../v1beta1/zz_generated.validations.go | 34 ++++ pkg/apis/flowcontrol/v1beta2/doc.go | 2 + .../v1beta2/zz_generated.validations.go | 34 ++++ pkg/apis/flowcontrol/v1beta3/doc.go | 2 + .../v1beta3/zz_generated.validations.go | 34 ++++ .../declarative_validation_test.go | 166 ++++++++++++++++++ .../prioritylevelconfiguration/strategy.go | 10 +- 10 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 pkg/apis/flowcontrol/v1/zz_generated.validations.go create mode 100644 pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go create mode 100644 pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go create mode 100644 pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go create mode 100644 pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go diff --git a/pkg/apis/flowcontrol/v1/doc.go b/pkg/apis/flowcontrol/v1/doc.go index cf32c99fc7f..a0bcdc59708 100644 --- a/pkg/apis/flowcontrol/v1/doc.go +++ b/pkg/apis/flowcontrol/v1/doc.go @@ -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 diff --git a/pkg/apis/flowcontrol/v1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1/zz_generated.validations.go new file mode 100644 index 00000000000..73e28035dba --- /dev/null +++ b/pkg/apis/flowcontrol/v1/zz_generated.validations.go @@ -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 +} diff --git a/pkg/apis/flowcontrol/v1beta1/doc.go b/pkg/apis/flowcontrol/v1beta1/doc.go index e4d5cebebc2..12c41c18d11 100644 --- a/pkg/apis/flowcontrol/v1beta1/doc.go +++ b/pkg/apis/flowcontrol/v1beta1/doc.go @@ -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 diff --git a/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go new file mode 100644 index 00000000000..0b9e874b635 --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta1/zz_generated.validations.go @@ -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 +} diff --git a/pkg/apis/flowcontrol/v1beta2/doc.go b/pkg/apis/flowcontrol/v1beta2/doc.go index 7f38044a8cc..75bac74f354 100644 --- a/pkg/apis/flowcontrol/v1beta2/doc.go +++ b/pkg/apis/flowcontrol/v1beta2/doc.go @@ -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 diff --git a/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go new file mode 100644 index 00000000000..2b709ed6643 --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta2/zz_generated.validations.go @@ -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 +} diff --git a/pkg/apis/flowcontrol/v1beta3/doc.go b/pkg/apis/flowcontrol/v1beta3/doc.go index 24081d457af..67a2e4b596c 100644 --- a/pkg/apis/flowcontrol/v1beta3/doc.go +++ b/pkg/apis/flowcontrol/v1beta3/doc.go @@ -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 diff --git a/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go new file mode 100644 index 00000000000..ab2e8319c4b --- /dev/null +++ b/pkg/apis/flowcontrol/v1beta3/zz_generated.validations.go @@ -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 +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go new file mode 100644 index 00000000000..699b5f75080 --- /dev/null +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/declarative_validation_test.go @@ -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 +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go index 575287bb7bf..0c1ec288255 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go @@ -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.