diff --git a/pkg/apis/resource/v1/zz_generated.validations.go b/pkg/apis/resource/v1/zz_generated.validations.go index 053c2ddb815..eed7dc77203 100644 --- a/pkg/apis/resource/v1/zz_generated.validations.go +++ b/pkg/apis/resource/v1/zz_generated.validations.go @@ -39,6 +39,22 @@ 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 DeviceClass + scheme.AddValidationFunc((*resourcev1.DeviceClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClass(ctx, op, nil /* fldPath */, obj.(*resourcev1.DeviceClass), safe.Cast[*resourcev1.DeviceClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type DeviceClassList + scheme.AddValidationFunc((*resourcev1.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1.DeviceClassList), safe.Cast[*resourcev1.DeviceClassList](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ResourceClaim scheme.AddValidationFunc((*resourcev1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -166,6 +182,91 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * return errs } +// Validate_DeviceClass validates an instance of DeviceClass according +// to declarative validation rules in the API schema. +func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClass) (errs field.ErrorList) { + // field resourcev1.DeviceClass.TypeMeta has no validation + // field resourcev1.DeviceClass.ObjectMeta has no validation + + // field resourcev1.DeviceClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1.DeviceClassSpec) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClass) *resourcev1.DeviceClassSpec { return &oldObj.Spec }))...) + + return errs +} + +// Validate_DeviceClassList validates an instance of DeviceClassList according +// to declarative validation rules in the API schema. +func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClassList) (errs field.ErrorList) { + // field resourcev1.DeviceClassList.TypeMeta has no validation + // field resourcev1.DeviceClassList.ListMeta has no validation + + // field resourcev1.DeviceClassList.Items + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceClass) (errs field.ErrorList) { + // don't revalidate unchanged data + if 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_DeviceClass)...) + return + }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClassList) []resourcev1.DeviceClass { return oldObj.Items }))...) + + return errs +} + +// Validate_DeviceClassSpec validates an instance of DeviceClassSpec according +// to declarative validation rules in the API schema. +func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClassSpec) (errs field.ErrorList) { + // field resourcev1.DeviceClassSpec.Selectors + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceSelector) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClassSpec) []resourcev1.DeviceSelector { return oldObj.Selectors }))...) + + // field resourcev1.DeviceClassSpec.Config + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceClassConfiguration) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClassSpec) []resourcev1.DeviceClassConfiguration { return oldObj.Config }))...) + + // field resourcev1.DeviceClassSpec.ExtendedResourceName has no validation + return errs +} + // Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according // to declarative validation rules in the API schema. func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceRequestAllocationResult) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta1/zz_generated.validations.go b/pkg/apis/resource/v1beta1/zz_generated.validations.go index dc47632c819..4755383a69c 100644 --- a/pkg/apis/resource/v1beta1/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta1/zz_generated.validations.go @@ -39,6 +39,22 @@ 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 DeviceClass + scheme.AddValidationFunc((*resourcev1beta1.DeviceClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClass(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.DeviceClass), safe.Cast[*resourcev1beta1.DeviceClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type DeviceClassList + scheme.AddValidationFunc((*resourcev1beta1.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta1.DeviceClassList), safe.Cast[*resourcev1beta1.DeviceClassList](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ResourceClaim scheme.AddValidationFunc((*resourcev1beta1.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -172,6 +188,95 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * return errs } +// Validate_DeviceClass validates an instance of DeviceClass according +// to declarative validation rules in the API schema. +func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClass) (errs field.ErrorList) { + // field resourcev1beta1.DeviceClass.TypeMeta has no validation + // field resourcev1beta1.DeviceClass.ObjectMeta has no validation + + // field resourcev1beta1.DeviceClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClassSpec) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClass) *resourcev1beta1.DeviceClassSpec { return &oldObj.Spec }))...) + + return errs +} + +// Validate_DeviceClassList validates an instance of DeviceClassList according +// to declarative validation rules in the API schema. +func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClassList) (errs field.ErrorList) { + // field resourcev1beta1.DeviceClassList.TypeMeta has no validation + // field resourcev1beta1.DeviceClassList.ListMeta has no validation + + // field resourcev1beta1.DeviceClassList.Items + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceClass) (errs field.ErrorList) { + // don't revalidate unchanged data + if 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_DeviceClass)...) + return + }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClassList) []resourcev1beta1.DeviceClass { return oldObj.Items }))...) + + return errs +} + +// Validate_DeviceClassSpec validates an instance of DeviceClassSpec according +// to declarative validation rules in the API schema. +func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClassSpec) (errs field.ErrorList) { + // field resourcev1beta1.DeviceClassSpec.Selectors + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceSelector) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClassSpec) []resourcev1beta1.DeviceSelector { + return oldObj.Selectors + }))...) + + // field resourcev1beta1.DeviceClassSpec.Config + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceClassConfiguration) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClassSpec) []resourcev1beta1.DeviceClassConfiguration { + return oldObj.Config + }))...) + + // field resourcev1beta1.DeviceClassSpec.ExtendedResourceName has no validation + return errs +} + // Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according // to declarative validation rules in the API schema. func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceRequestAllocationResult) (errs field.ErrorList) { diff --git a/pkg/apis/resource/v1beta2/zz_generated.validations.go b/pkg/apis/resource/v1beta2/zz_generated.validations.go index df1d7265d92..d8796033e4f 100644 --- a/pkg/apis/resource/v1beta2/zz_generated.validations.go +++ b/pkg/apis/resource/v1beta2/zz_generated.validations.go @@ -39,6 +39,22 @@ 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 DeviceClass + scheme.AddValidationFunc((*resourcev1beta2.DeviceClass)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClass(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.DeviceClass), safe.Cast[*resourcev1beta2.DeviceClass](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + // type DeviceClassList + scheme.AddValidationFunc((*resourcev1beta2.DeviceClassList)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_DeviceClassList(ctx, op, nil /* fldPath */, obj.(*resourcev1beta2.DeviceClassList), safe.Cast[*resourcev1beta2.DeviceClassList](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) // type ResourceClaim scheme.AddValidationFunc((*resourcev1beta2.ResourceClaim)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { switch op.Request.SubresourcePath() { @@ -172,6 +188,95 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath * return errs } +// Validate_DeviceClass validates an instance of DeviceClass according +// to declarative validation rules in the API schema. +func Validate_DeviceClass(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClass) (errs field.ErrorList) { + // field resourcev1beta2.DeviceClass.TypeMeta has no validation + // field resourcev1beta2.DeviceClass.ObjectMeta has no validation + + // field resourcev1beta2.DeviceClass.Spec + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClassSpec) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call the type's validation function + errs = append(errs, Validate_DeviceClassSpec(ctx, op, fldPath, obj, oldObj)...) + return + }(fldPath.Child("spec"), &obj.Spec, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClass) *resourcev1beta2.DeviceClassSpec { return &oldObj.Spec }))...) + + return errs +} + +// Validate_DeviceClassList validates an instance of DeviceClassList according +// to declarative validation rules in the API schema. +func Validate_DeviceClassList(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClassList) (errs field.ErrorList) { + // field resourcev1beta2.DeviceClassList.TypeMeta has no validation + // field resourcev1beta2.DeviceClassList.ListMeta has no validation + + // field resourcev1beta2.DeviceClassList.Items + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceClass) (errs field.ErrorList) { + // don't revalidate unchanged data + if 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_DeviceClass)...) + return + }(fldPath.Child("items"), obj.Items, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClassList) []resourcev1beta2.DeviceClass { return oldObj.Items }))...) + + return errs +} + +// Validate_DeviceClassSpec validates an instance of DeviceClassSpec according +// to declarative validation rules in the API schema. +func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClassSpec) (errs field.ErrorList) { + // field resourcev1beta2.DeviceClassSpec.Selectors + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceSelector) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClassSpec) []resourcev1beta2.DeviceSelector { + return oldObj.Selectors + }))...) + + // field resourcev1beta2.DeviceClassSpec.Config + errs = append(errs, + func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceClassConfiguration) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) { + return nil + } + // call field-attached validations + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 32); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + return // do not proceed + } + return + }(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClassSpec) []resourcev1beta2.DeviceClassConfiguration { + return oldObj.Config + }))...) + + // field resourcev1beta2.DeviceClassSpec.ExtendedResourceName has no validation + return errs +} + // Validate_DeviceRequestAllocationResult validates an instance of DeviceRequestAllocationResult according // to declarative validation rules in the API schema. func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceRequestAllocationResult) (errs field.ErrorList) { diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 31ee383698c..f072a41a1ad 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -544,8 +544,9 @@ func validateDeviceClassSpec(spec, oldSpec *resource.DeviceClassSpec, fldPath *f func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList { return validateSelector(selector, fldPath, stored) }, - fldPath.Child("selectors"))...) + fldPath.Child("selectors"), sizeCovered)...) // Same logic as above for configs. + stored = false if oldSpec != nil { stored = apiequality.Semantic.DeepEqual(spec.Config, oldSpec.Config) } @@ -553,7 +554,7 @@ func validateDeviceClassSpec(spec, oldSpec *resource.DeviceClassSpec, fldPath *f func(config resource.DeviceClassConfiguration, fldPath *field.Path) field.ErrorList { return validateDeviceClassConfiguration(config, fldPath, stored) }, - fldPath.Child("config"))...) + fldPath.Child("config"), sizeCovered)...) if spec.ExtendedResourceName != nil && !v1helper.IsExtendedResourceName(corev1.ResourceName(*spec.ExtendedResourceName)) { allErrs = append(allErrs, field.Invalid(fldPath.Child("extendedResourceName"), *spec.ExtendedResourceName, "must be a valid extended resource name")) diff --git a/pkg/apis/resource/validation/validation_deviceclass_test.go b/pkg/apis/resource/validation/validation_deviceclass_test.go index 7ccb8f634e6..0cfa8b0189e 100644 --- a/pkg/apis/resource/validation/validation_deviceclass_test.go +++ b/pkg/apis/resource/validation/validation_deviceclass_test.go @@ -219,7 +219,7 @@ func TestValidateClass(t *testing.T) { }, "too-many-selectors": { wantFailures: field.ErrorList{ - field.TooMany(field.NewPath("spec", "selectors"), resource.DeviceSelectorsMaxSize+1, resource.DeviceSelectorsMaxSize), + field.TooMany(field.NewPath("spec", "selectors"), resource.DeviceSelectorsMaxSize+1, resource.DeviceSelectorsMaxSize).WithOrigin("maxItems").MarkCoveredByDeclarative(), }, class: func() *resource.DeviceClass { class := testClass(goodName) @@ -313,7 +313,7 @@ func TestValidateClass(t *testing.T) { }, "too-many-configs": { wantFailures: field.ErrorList{ - field.TooMany(field.NewPath("spec", "config"), resource.DeviceConfigMaxSize+1, resource.DeviceConfigMaxSize), + field.TooMany(field.NewPath("spec", "config"), resource.DeviceConfigMaxSize+1, resource.DeviceConfigMaxSize).WithOrigin("maxItems").MarkCoveredByDeclarative(), }, class: func() *resource.DeviceClass { class := testClass(goodName) diff --git a/pkg/registry/resource/deviceclass/declarative_validation_test.go b/pkg/registry/resource/deviceclass/declarative_validation_test.go index 8b6f0202f73..0676e875c81 100644 --- a/pkg/registry/resource/deviceclass/declarative_validation_test.go +++ b/pkg/registry/resource/deviceclass/declarative_validation_test.go @@ -17,6 +17,7 @@ limitations under the License. package deviceclass import ( + "fmt" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -48,6 +49,26 @@ func TestDeclarativeValidate(t *testing.T) { "valid": { input: mkDeviceClass(), }, + // spec.selectors. + "valid: at limit selectors": { + input: mkDeviceClass(tweakSelectors(32)), + }, + "too many selectors": { + input: mkDeviceClass(tweakSelectors(33)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems"), + }, + }, + // spec.config + "too many configs": { + input: mkDeviceClass(tweakConfig(33)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems"), + }, + }, + "valid: at limit configs": { + input: mkDeviceClass(tweakConfig(32)), + }, // TODO: Add more test cases } @@ -80,6 +101,28 @@ func TestDeclarativeValidateUpdate(t *testing.T) { old: mkDeviceClass(), update: mkDeviceClass(), }, + "valid update: at limit selectors": { + old: mkDeviceClass(), + update: mkDeviceClass(tweakSelectors(32)), + }, + "update with too many selectors": { + old: mkDeviceClass(), + update: mkDeviceClass(tweakSelectors(33)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "selectors"), 33, 32).WithOrigin("maxItems"), + }, + }, + "valid update: at limit configs": { + old: mkDeviceClass(), + update: mkDeviceClass(tweakConfig(32)), + }, + "update with too many configs": { + old: mkDeviceClass(), + update: mkDeviceClass(tweakConfig(33)), + expectedErrs: field.ErrorList{ + field.TooMany(field.NewPath("spec", "config"), 33, 32).WithOrigin("maxItems"), + }, + }, // TODO: Add more test cases } @@ -127,3 +170,35 @@ func mkDeviceClass(mutators ...func(*resource.DeviceClass)) resource.DeviceClass } return dc } + +func tweakSelectors(count int) func(*resource.DeviceClass) { + return func(dc *resource.DeviceClass) { + dc.Spec.Selectors = []resource.DeviceSelector{} + + for i := 0; i < count; i++ { + dc.Spec.Selectors = append(dc.Spec.Selectors, resource.DeviceSelector{ + CEL: &resource.CELDeviceSelector{ + Expression: fmt.Sprintf("device.driver == \"test.driver.io%d\"", i), + }, + }) + } + } +} + +func tweakConfig(count int) func(*resource.DeviceClass) { + return func(dc *resource.DeviceClass) { + dc.Spec.Config = []resource.DeviceClassConfiguration{} + for i := 0; i < count; i++ { + dc.Spec.Config = append(dc.Spec.Config, resource.DeviceClassConfiguration{ + DeviceConfiguration: resource.DeviceConfiguration{ + Opaque: &resource.OpaqueDeviceConfiguration{ + Driver: "test.driver.io", + Parameters: runtime.RawExtension{ + Raw: []byte(fmt.Sprintf(`{"key":"value%d"}`, i)), + }, + }, + }, + }) + } + } +} diff --git a/pkg/registry/resource/deviceclass/strategy.go b/pkg/registry/resource/deviceclass/strategy.go index 8abe8d061c8..3854b37d081 100644 --- a/pkg/registry/resource/deviceclass/strategy.go +++ b/pkg/registry/resource/deviceclass/strategy.go @@ -84,8 +84,7 @@ func (deviceClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime. newClass := obj.(*resource.DeviceClass) oldClass := old.(*resource.DeviceClass) - errorList := validation.ValidateDeviceClass(newClass) - errorList = append(errorList, validation.ValidateDeviceClassUpdate(newClass, oldClass)...) + errorList := validation.ValidateDeviceClassUpdate(newClass, oldClass) return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClass, oldClass, errorList, operation.Update) } diff --git a/staging/src/k8s.io/api/resource/v1/generated.proto b/staging/src/k8s.io/api/resource/v1/generated.proto index 7c3998d895e..642bbfc2d0d 100644 --- a/staging/src/k8s.io/api/resource/v1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1/generated.proto @@ -642,6 +642,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -652,6 +654,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. diff --git a/staging/src/k8s.io/api/resource/v1/types.go b/staging/src/k8s.io/api/resource/v1/types.go index 5b47a7ffaed..d39a9e0a00a 100644 --- a/staging/src/k8s.io/api/resource/v1/types.go +++ b/staging/src/k8s.io/api/resource/v1/types.go @@ -1672,6 +1672,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1682,6 +1684,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where diff --git a/staging/src/k8s.io/api/resource/v1beta1/generated.proto b/staging/src/k8s.io/api/resource/v1beta1/generated.proto index cc9469bb646..367ea268bbf 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta1/generated.proto @@ -650,6 +650,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -660,6 +662,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index ee8e1d99bac..f0a4767ad93 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -1680,6 +1680,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1690,6 +1692,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where diff --git a/staging/src/k8s.io/api/resource/v1beta2/generated.proto b/staging/src/k8s.io/api/resource/v1beta2/generated.proto index 076a43c93ac..d386bca1873 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/generated.proto +++ b/staging/src/k8s.io/api/resource/v1beta2/generated.proto @@ -642,6 +642,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceSelector selectors = 1; // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -652,6 +654,8 @@ message DeviceClassSpec { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 repeated DeviceClassConfiguration config = 2; // ExtendedResourceName is the extended resource name for the devices of this class. diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go index 1ade5acf1b4..d511bbe9e2f 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types.go @@ -1672,6 +1672,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,1,opt,name=selectors"` // Config defines configuration parameters that apply to each device that is claimed via this class. @@ -1682,6 +1684,8 @@ type DeviceClassSpec struct { // // +optional // +listType=atomic + // +k8s:optional + // +k8s:maxItems=32 Config []DeviceClassConfiguration `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` // SuitableNodes is tombstoned since Kubernetes 1.32 where diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go index c1039c4ecde..f17937c6913 100644 --- a/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/limits_test.go @@ -76,62 +76,57 @@ func TestMaxLength(t *testing.T) { func TestMaxItems(t *testing.T) { cases := []struct { - fn func(op operation.Operation, fp *field.Path) field.ErrorList - err string // regex + name string + items int + max int + err string // regex }{{ - fn: func(op operation.Operation, fp *field.Path) field.ErrorList { - value := make([]string, 0) - max := 0 - return MaxItems(context.Background(), op, fp, value, nil, max) - }, + name: "0 items, max 0", + items: 0, + max: 0, }, { - fn: func(op operation.Operation, fp *field.Path) field.ErrorList { - value := make([]string, 1) - max := 0 - return MaxItems(context.Background(), op, fp, value, nil, max) - }, - err: "fldpath: Too many.*must have at most", + name: "1 item, max 0", + items: 1, + max: 0, + err: "fldpath: Too many.*must have at most", }, { - fn: func(op operation.Operation, fp *field.Path) field.ErrorList { - value := make([]int, 1) - max := 1 - return MaxItems(context.Background(), op, fp, value, nil, max) - }, + name: "1 item, max 1", + items: 1, + max: 1, }, { - fn: func(op operation.Operation, fp *field.Path) field.ErrorList { - value := make([]int, 2) - max := 1 - return MaxItems(context.Background(), op, fp, value, nil, max) - }, - err: "fldpath: Too many.*must have at most", + name: "2 items, max 1", + items: 2, + max: 1, + err: "fldpath: Too many.*must have at most", }, { - fn: func(op operation.Operation, fp *field.Path) field.ErrorList { - value := make([]bool, 0) - max := -1 - return MaxItems(context.Background(), op, fp, value, nil, max) - }, - err: "fldpath: Too many.*too many items", + name: "0 items, max -1", + items: 0, + max: -1, + err: "fldpath: Too many.*too many items", }} - for i, tc := range cases { - result := tc.fn(operation.Operation{}, field.NewPath("fldpath")) - if len(result) > 0 && tc.err == "" { - t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) - continue - } - if len(result) == 0 && tc.err != "" { - t.Errorf("case %d: unexpected success: expected %q", i, tc.err) - continue - } - if len(result) > 0 { - if len(result) > 1 { - t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) - continue + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + value := make([]bool, tc.items) + result := MaxItems(context.Background(), operation.Operation{}, field.NewPath("fldpath"), value, nil, tc.max) + if len(result) > 0 && tc.err == "" { + t.Errorf("unexpected failure: %v", fmtErrs(result)) + return } - if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { - t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result)) + if len(result) == 0 && tc.err != "" { + t.Errorf("unexpected success: expected %q", tc.err) + return } - } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("unexepected multi-error: %v", fmtErrs(result)) + return + } + if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) { + t.Errorf("wrong error\nexpected: %q\n got: %v", tc.err, fmtErrs(result)) + } + } + }) } } diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go index 5a972843385..4992e79400e 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/limits.go @@ -83,10 +83,10 @@ func (mitv maxItemsTagValidator) Docs() TagDoc { return TagDoc{ Tag: mitv.TagName(), Scopes: mitv.ValidScopes().UnsortedList(), - Description: "Indicates that a list field has a limit on its size.", + Description: "Indicates that a list has a limit on its size.", Payloads: []TagPayloadDoc{{ Description: "", - Docs: "This field must be no more than X items long.", + Docs: "This list must be no more than X items long.", }}, PayloadsType: codetags.ValueTypeInt, PayloadsRequired: true,