DRA: adding tag to node module

move DRA to a update method

fix boilerplate code

apply node normalize code at fuzz logic

adding normalization fix

fix nit
This commit is contained in:
darshansreenivas
2025-11-24 11:30:28 -08:00
parent 5bcb759973
commit 897adb7b48
16 changed files with 532 additions and 5 deletions

View File

@@ -23,7 +23,9 @@ import (
"k8s.io/apimachinery/pkg/api/apitesting/fuzzer"
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api/legacyscheme"
nodevalidation "k8s.io/kubernetes/pkg/apis/node/validation"
resourcevalidation "k8s.io/kubernetes/pkg/apis/resource/validation"
)
@@ -42,6 +44,9 @@ func TestVersionedValidationByFuzzing(t *testing.T) {
{Group: "storage.k8s.io", Version: "v1"},
{Group: "storage.k8s.io", Version: "v1beta1"},
{Group: "storage.k8s.io", Version: "v1alpha1"},
{Group: "node.k8s.io", Version: "v1beta1"},
{Group: "node.k8s.io", Version: "v1"},
{Group: "node.k8s.io", Version: "v1alpha1"},
}
fuzzIters := *roundtrip.FuzzIters / 10 // TODO: Find a better way to manage test running time
@@ -59,7 +64,12 @@ func TestVersionedValidationByFuzzing(t *testing.T) {
f.Fill(obj)
var opts []ValidationTestConfig
opts = append(opts, WithNormalizationRules(resourcevalidation.ResourceNormalizationRules...))
// TODO(API group level configuration): Consider configuring normalization rules at the
// API group level to avoid potential collisions when multiple rule sets are combined.
// This would allow each API group to register its own normalization rules independently.
allRules := append([]field.NormalizationRule{}, resourcevalidation.ResourceNormalizationRules...)
allRules = append(allRules, nodevalidation.NodeNormalizationRules...)
opts = append(opts, WithNormalizationRules(allRules...))
VerifyVersionedValidationEquivalence(t, obj, nil, opts...)

View File

@@ -19,4 +19,7 @@ limitations under the License.
// +groupName=node.k8s.io
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/node/v1
package v1

View File

@@ -0,0 +1,111 @@
//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 (
context "context"
fmt "fmt"
nodev1 "k8s.io/api/node/v1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
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 {
// type RuntimeClass
scheme.AddValidationFunc((*nodev1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1.RuntimeClass), safe.Cast[*nodev1.RuntimeClass](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RuntimeClassList
scheme.AddValidationFunc((*nodev1.RuntimeClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClassList(ctx, op, nil /* fldPath */, obj.(*nodev1.RuntimeClassList), safe.Cast[*nodev1.RuntimeClassList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_RuntimeClass validates an instance of RuntimeClass according
// to declarative validation rules in the API schema.
func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1.RuntimeClass) (errs field.ErrorList) {
// field nodev1.RuntimeClass.TypeMeta has no validation
// field nodev1.RuntimeClass.ObjectMeta has no validation
// field nodev1.RuntimeClass.Handler
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("handler"), &obj.Handler, safe.Field(oldObj, func(oldObj *nodev1.RuntimeClass) *string { return &oldObj.Handler }), oldObj != nil)...)
// field nodev1.RuntimeClass.Overhead has no validation
// field nodev1.RuntimeClass.Scheduling has no validation
return errs
}
// Validate_RuntimeClassList validates an instance of RuntimeClassList according
// to declarative validation rules in the API schema.
func Validate_RuntimeClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1.RuntimeClassList) (errs field.ErrorList) {
// field nodev1.RuntimeClassList.TypeMeta has no validation
// field nodev1.RuntimeClassList.ListMeta has no validation
// field nodev1.RuntimeClassList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []nodev1.RuntimeClass, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RuntimeClass)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *nodev1.RuntimeClassList) []nodev1.RuntimeClass { return oldObj.Items }), oldObj != nil)...)
return errs
}

View File

@@ -19,4 +19,7 @@ limitations under the License.
// +groupName=node.k8s.io
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/node/v1alpha1
package v1alpha1

View File

@@ -0,0 +1,129 @@
//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 v1alpha1
import (
context "context"
fmt "fmt"
nodev1alpha1 "k8s.io/api/node/v1alpha1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
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 {
// type RuntimeClass
scheme.AddValidationFunc((*nodev1alpha1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1alpha1.RuntimeClass), safe.Cast[*nodev1alpha1.RuntimeClass](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RuntimeClassList
scheme.AddValidationFunc((*nodev1alpha1.RuntimeClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClassList(ctx, op, nil /* fldPath */, obj.(*nodev1alpha1.RuntimeClassList), safe.Cast[*nodev1alpha1.RuntimeClassList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_RuntimeClass validates an instance of RuntimeClass according
// to declarative validation rules in the API schema.
func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClass) (errs field.ErrorList) {
// field nodev1alpha1.RuntimeClass.TypeMeta has no validation
// field nodev1alpha1.RuntimeClass.ObjectMeta has no validation
// field nodev1alpha1.RuntimeClass.Spec
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClassSpec, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call the type's validation function
errs = append(errs, Validate_RuntimeClassSpec(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *nodev1alpha1.RuntimeClass) *nodev1alpha1.RuntimeClassSpec { return &oldObj.Spec }), oldObj != nil)...)
return errs
}
// Validate_RuntimeClassList validates an instance of RuntimeClassList according
// to declarative validation rules in the API schema.
func Validate_RuntimeClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClassList) (errs field.ErrorList) {
// field nodev1alpha1.RuntimeClassList.TypeMeta has no validation
// field nodev1alpha1.RuntimeClassList.ListMeta has no validation
// field nodev1alpha1.RuntimeClassList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []nodev1alpha1.RuntimeClass, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RuntimeClass)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *nodev1alpha1.RuntimeClassList) []nodev1alpha1.RuntimeClass { return oldObj.Items }), oldObj != nil)...)
return errs
}
// Validate_RuntimeClassSpec validates an instance of RuntimeClassSpec according
// to declarative validation rules in the API schema.
func Validate_RuntimeClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1alpha1.RuntimeClassSpec) (errs field.ErrorList) {
// field nodev1alpha1.RuntimeClassSpec.RuntimeHandler
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("runtimeHandler"), &obj.RuntimeHandler, safe.Field(oldObj, func(oldObj *nodev1alpha1.RuntimeClassSpec) *string { return &oldObj.RuntimeHandler }), oldObj != nil)...)
// field nodev1alpha1.RuntimeClassSpec.Overhead has no validation
// field nodev1alpha1.RuntimeClassSpec.Scheduling has no validation
return errs
}

View File

@@ -19,4 +19,7 @@ limitations under the License.
// +groupName=node.k8s.io
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-input=k8s.io/api/node/v1beta1
package v1beta1

View File

@@ -0,0 +1,111 @@
//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 (
context "context"
fmt "fmt"
nodev1beta1 "k8s.io/api/node/v1beta1"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
runtime "k8s.io/apimachinery/pkg/runtime"
field "k8s.io/apimachinery/pkg/util/validation/field"
)
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 {
// type RuntimeClass
scheme.AddValidationFunc((*nodev1beta1.RuntimeClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClass(ctx, op, nil /* fldPath */, obj.(*nodev1beta1.RuntimeClass), safe.Cast[*nodev1beta1.RuntimeClass](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type RuntimeClassList
scheme.AddValidationFunc((*nodev1beta1.RuntimeClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_RuntimeClassList(ctx, op, nil /* fldPath */, obj.(*nodev1beta1.RuntimeClassList), safe.Cast[*nodev1beta1.RuntimeClassList](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_RuntimeClass validates an instance of RuntimeClass according
// to declarative validation rules in the API schema.
func Validate_RuntimeClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1beta1.RuntimeClass) (errs field.ErrorList) {
// field nodev1beta1.RuntimeClass.TypeMeta has no validation
// field nodev1beta1.RuntimeClass.ObjectMeta has no validation
// field nodev1beta1.RuntimeClass.Handler
errs = append(errs,
func(fldPath *field.Path, obj, oldObj *string, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.ShortName(ctx, op, fldPath, obj, oldObj)...)
return
}(fldPath.Child("handler"), &obj.Handler, safe.Field(oldObj, func(oldObj *nodev1beta1.RuntimeClass) *string { return &oldObj.Handler }), oldObj != nil)...)
// field nodev1beta1.RuntimeClass.Overhead has no validation
// field nodev1beta1.RuntimeClass.Scheduling has no validation
return errs
}
// Validate_RuntimeClassList validates an instance of RuntimeClassList according
// to declarative validation rules in the API schema.
func Validate_RuntimeClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *nodev1beta1.RuntimeClassList) (errs field.ErrorList) {
// field nodev1beta1.RuntimeClassList.TypeMeta has no validation
// field nodev1beta1.RuntimeClassList.ListMeta has no validation
// field nodev1beta1.RuntimeClassList.Items
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []nodev1beta1.RuntimeClass, oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// iterate the list and call the type's validation function
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_RuntimeClass)...)
return
}(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *nodev1beta1.RuntimeClassList) []nodev1beta1.RuntimeClass { return oldObj.Items }), oldObj != nil)...)
return errs
}

View File

@@ -23,14 +23,24 @@ import (
"k8s.io/kubernetes/pkg/apis/core"
corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/apis/node"
"regexp"
)
// Define normalization rules to handle field name differences across API versions
// v1alpha1 uses "spec.runtimeHandler" while v1/v1beta1 use "handler"
var NodeNormalizationRules = []field.NormalizationRule{
{
Regexp: regexp.MustCompile(`^spec\.runtimeHandler(.*)$`),
Replacement: "handler$1",
},
}
// ValidateRuntimeClass validates the RuntimeClass
func ValidateRuntimeClass(rc *node.RuntimeClass) field.ErrorList {
allErrs := apivalidation.ValidateObjectMeta(&rc.ObjectMeta, false, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
for _, msg := range apivalidation.NameIsDNSLabel(rc.Handler, false) {
allErrs = append(allErrs, field.Invalid(field.NewPath("handler"), rc.Handler, msg))
allErrs = append(allErrs, field.Invalid(field.NewPath("handler"), rc.Handler, msg)).MarkCoveredByDeclarative().WithOrigin("format=k8s-short-name")
}
if rc.Overhead != nil {
@@ -47,7 +57,7 @@ func ValidateRuntimeClass(rc *node.RuntimeClass) field.ErrorList {
func ValidateRuntimeClassUpdate(new, old *node.RuntimeClass) field.ErrorList {
allErrs := apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))
allErrs = append(allErrs, apivalidation.ValidateImmutableField(new.Handler, old.Handler, field.NewPath("handler"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(new.Handler, old.Handler, field.NewPath("handler"))...).MarkCoveredByDeclarative().WithOrigin("immutable")
return allErrs
}

View File

@@ -0,0 +1,130 @@
/*
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 runtimeclass
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"
node "k8s.io/kubernetes/pkg/apis/node"
"k8s.io/kubernetes/pkg/apis/node/validation"
)
var apiVersions = []string{"v1", "v1beta1", "v1alpha1"}
func mkRuntimeClassHandlerOnly(tweaks ...func(*node.RuntimeClass)) node.RuntimeClass {
rc := node.RuntimeClass{
ObjectMeta: metav1.ObjectMeta{
Name: "myrc",
},
Handler: "runc",
}
for _, f := range tweaks {
f(&rc)
}
return rc
}
func TestRuntimeClass_DeclarativeValidate_Create(t *testing.T) {
for _, apiVersion := range apiVersions {
t.Run(apiVersion, func(t *testing.T) {
ctx := genericapirequest.WithRequestInfo(
genericapirequest.NewDefaultContext(),
&genericapirequest.RequestInfo{
APIGroup: "node.k8s.io",
APIVersion: apiVersion,
},
)
tests := map[string]struct {
obj node.RuntimeClass
expectedErrs field.ErrorList
}{
"valid handler": {
obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) {
rc.Handler = "test"
}),
expectedErrs: field.ErrorList{},
},
"invalid handler dns label": {
obj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) {
rc.Handler = ""
}),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("handler"),
"", "").WithOrigin("format=k8s-short-name"),
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
apitesting.VerifyValidationEquivalence(t, ctx, &tc.obj, Strategy.Validate, tc.expectedErrs,
apitesting.WithNormalizationRules(validation.NodeNormalizationRules...))
})
}
})
}
}
func TestRuntimeClass_DeclarativeValidate_Update(t *testing.T) {
for _, apiVersion := range apiVersions {
t.Run(apiVersion, func(t *testing.T) {
ctx := genericapirequest.WithRequestInfo(
genericapirequest.NewDefaultContext(),
&genericapirequest.RequestInfo{
APIGroup: "node.k8s.io",
APIVersion: apiVersion,
},
)
tests := map[string]struct {
oldObj, newObj node.RuntimeClass
expectedErrs field.ErrorList
}{
"no-op update": {
oldObj: mkRuntimeClassHandlerOnly(),
newObj: mkRuntimeClassHandlerOnly(),
expectedErrs: field.ErrorList{},
},
"handler changed (immutable)": {
oldObj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) {
rc.Handler = "runc"
}),
newObj: mkRuntimeClassHandlerOnly(func(rc *node.RuntimeClass) {
rc.Handler = "asadsasdas"
}),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("handler"), "",
"").WithOrigin("immutable"),
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
tc.oldObj.ObjectMeta.ResourceVersion = "1"
tc.newObj.ObjectMeta.ResourceVersion = "1"
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.newObj, &tc.oldObj, Strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithNormalizationRules(validation.NodeNormalizationRules...))
})
}
})
}
}

View File

@@ -18,6 +18,7 @@ package runtimeclass
import (
"context"
"k8s.io/apimachinery/pkg/api/operation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -70,7 +71,9 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
// Validate validates a new RuntimeClass. Validation must check for a correct signature.
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
runtimeClass := obj.(*node.RuntimeClass)
return validation.ValidateRuntimeClass(runtimeClass)
allErrs := validation.ValidateRuntimeClass(runtimeClass)
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, runtimeClass, nil, allErrs, operation.Create, rest.WithNormalizationRules(validation.NodeNormalizationRules))
}
// WarningsOnCreate returns warnings for the creation of the given object.
@@ -87,7 +90,9 @@ func (strategy) Canonicalize(obj runtime.Object) {
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
newObj := obj.(*node.RuntimeClass)
errorList := validation.ValidateRuntimeClass(newObj)
return append(errorList, validation.ValidateRuntimeClassUpdate(newObj, old.(*node.RuntimeClass))...)
errorList = append(errorList, validation.ValidateRuntimeClassUpdate(newObj, old.(*node.RuntimeClass))...)
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newObj, old, errorList, operation.Update, rest.WithNormalizationRules(validation.NodeNormalizationRules))
}
// WarningsOnUpdate returns warnings for the given update.

View File

@@ -59,6 +59,8 @@ message RuntimeClass {
// in a pod.
// The Handler must be lowercase, conform to the DNS Label (RFC 1123) requirements,
// and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
optional string handler = 2;
// overhead represents the resource overhead associated with running a pod for a

View File

@@ -50,6 +50,8 @@ type RuntimeClass struct {
// in a pod.
// The Handler must be lowercase, conform to the DNS Label (RFC 1123) requirements,
// and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
Handler string `json:"handler" protobuf:"bytes,2,opt,name=handler"`
// overhead represents the resource overhead associated with running a pod for a

View File

@@ -80,6 +80,8 @@ message RuntimeClassSpec {
// in a pod.
// The runtimeHandler must be lowercase, conform to the DNS Label (RFC 1123)
// requirements, and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
optional string runtimeHandler = 1;
// overhead represents the resource overhead associated with running a pod for a

View File

@@ -59,6 +59,8 @@ type RuntimeClassSpec struct {
// in a pod.
// The runtimeHandler must be lowercase, conform to the DNS Label (RFC 1123)
// requirements, and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
RuntimeHandler string `json:"runtimeHandler" protobuf:"bytes,1,opt,name=runtimeHandler"`
// overhead represents the resource overhead associated with running a pod for a

View File

@@ -59,6 +59,8 @@ message RuntimeClass {
// in a pod.
// The handler must be lowercase, conform to the DNS Label (RFC 1123) requirements,
// and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
optional string handler = 2;
// overhead represents the resource overhead associated with running a pod for a

View File

@@ -51,6 +51,8 @@ type RuntimeClass struct {
// in a pod.
// The handler must be lowercase, conform to the DNS Label (RFC 1123) requirements,
// and is immutable.
// +k8s:format="k8s-short-name"
// +k8s:immutable
Handler string `json:"handler" protobuf:"bytes,2,opt,name=handler"`
// overhead represents the resource overhead associated with running a pod for a