Merge pull request #134072 from yongruilin/master_vg_enable-resourceclaim

Enable Declarative Validation for resource.k8s.io v1/v1beta1/v1beta2
This commit is contained in:
Kubernetes Prow Robot 2025-09-17 12:50:14 -07:00 committed by GitHub
commit ec4e321f00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 259 additions and 2 deletions

View file

@ -35,6 +35,9 @@ func TestVersionedValidationByFuzzing(t *testing.T) {
{Group: "certificates.k8s.io", Version: "v1"},
{Group: "certificates.k8s.io", Version: "v1alpha1"},
{Group: "certificates.k8s.io", Version: "v1beta1"},
{Group: "resource.k8s.io", Version: "v1beta1"},
{Group: "resource.k8s.io", Version: "v1beta2"},
{Group: "resource.k8s.io", Version: "v1"},
}
for _, gv := range typesWithDeclarativeValidation {

View file

@ -18,6 +18,8 @@ limitations under the License.
// +k8s:conversion-gen-external-types=k8s.io/api/resource/v1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/resource/v1
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/resource/v1
// Package v1 is the v1 version of the resource API.
package v1

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/resource/v1beta1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/resource/v1beta1
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/resource/v1beta1
// Package v1beta1 is the v1beta1 version of the resource API.
package v1beta1

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/resource/v1beta2
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=k8s.io/api/resource/v1beta2
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/resource/v1beta2
// Package v1beta2 is the v1beta2 version of the resource API.
package v1beta2

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

@ -0,0 +1,123 @@
/*
Copyright 2025 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 resourceclaim
import (
"testing"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/kubernetes/fake"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/apis/resource"
)
var apiVersions = []string{"v1beta1", "v1beta2", "v1"} // "v1alpha3" is excluded because it doesn't have ResourceClaim
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: "resource.k8s.io",
APIVersion: apiVersion,
Resource: "resourceclaims",
})
fakeClient := fake.NewClientset()
mockNSClient := fakeClient.CoreV1().Namespaces()
Strategy := NewStrategy(mockNSClient)
testCases := map[string]struct {
input resource.ResourceClaim
expectedErrs field.ErrorList
}{
"valid": {
input: mkValidResourceClaim(),
},
// TODO: Add more test cases
}
for k, tc := range testCases {
t.Run(k, 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) {
ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), &genericapirequest.RequestInfo{
APIGroup: "resource.k8s.io",
APIVersion: apiVersion,
Resource: "resourceclaims",
})
fakeClient := fake.NewClientset()
mockNSClient := fakeClient.CoreV1().Namespaces()
Strategy := NewStrategy(mockNSClient)
validClaim := mkValidResourceClaim()
testCases := map[string]struct {
update resource.ResourceClaim
old resource.ResourceClaim
expectedErrs field.ErrorList
}{
"valid": {
update: validClaim,
old: validClaim,
},
// TODO: Add more test cases
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
tc.old.ResourceVersion = "1"
tc.update.ResourceVersion = "2"
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs)
})
}
}
func mkValidResourceClaim() resource.ResourceClaim {
return resource.ResourceClaim{
ObjectMeta: v1.ObjectMeta{
Name: "valid-claim",
Namespace: "default",
},
Spec: resource.ResourceClaimSpec{
Devices: resource.DeviceClaim{
Requests: []resource.DeviceRequest{
{
Name: "req-0",
Exactly: &resource.ExactDeviceRequest{
DeviceClassName: "class",
AllocationMode: resource.DeviceAllocationModeAll,
},
},
},
},
},
}
}

View file

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
@ -96,7 +97,18 @@ func (s *resourceclaimStrategy) Validate(ctx context.Context, obj runtime.Object
claim := obj.(*resource.ResourceClaim)
allErrs := resourceutils.AuthorizedForAdmin(ctx, claim.Spec.Devices.Requests, claim.Namespace, s.nsClient)
return append(allErrs, validation.ValidateResourceClaim(claim)...)
allErrs = append(allErrs, validation.ValidateResourceClaim(claim)...)
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
const validationIdentifier = "resourceclaim_create"
declarativeErrs := rest.ValidateDeclaratively(ctx, legacyscheme.Scheme, claim, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, allErrs, declarativeErrs, takeover, validationIdentifier)
if takeover {
allErrs = append(allErrs.RemoveCoveredByDeclarative(), declarativeErrs...)
}
}
return allErrs
}
func (*resourceclaimStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
@ -123,7 +135,18 @@ func (s *resourceclaimStrategy) ValidateUpdate(ctx context.Context, obj, old run
oldClaim := old.(*resource.ResourceClaim)
// AuthorizedForAdmin isn't needed here because the spec is immutable.
errorList := validation.ValidateResourceClaim(newClaim)
return append(errorList, validation.ValidateResourceClaimUpdate(newClaim, oldClaim)...)
errorList = append(errorList, validation.ValidateResourceClaimUpdate(newClaim, oldClaim)...)
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
const validationIdentifier = "resourceclaim_update"
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newClaim, oldClaim, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errorList, declarativeErrs, takeover, validationIdentifier)
if takeover {
errorList = append(errorList.RemoveCoveredByDeclarative(), declarativeErrs...)
}
}
return errorList
}
func (*resourceclaimStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {