mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-02-22 07:03:28 +00:00
Merge pull request #134408 from yongruilin/vg_resourceclaim
feat(validation-gen): add path normalization options & migration k8s:maxItem on ResourceClaimSpec fields
This commit is contained in:
@@ -156,6 +156,28 @@ func convertToInternal(t *testing.T, scheme *runtime.Scheme, obj runtime.Object)
|
||||
return scheme.ConvertToVersion(obj, schema.GroupVersion{Group: gvk.Group, Version: runtime.APIVersionInternal})
|
||||
}
|
||||
|
||||
type ValidationTestConfig func(*validationOption)
|
||||
|
||||
// validationOptions encapsulates optional parameters for validation equivalence tests.
|
||||
type validationOption struct {
|
||||
// SubResources are the subresources to validate.
|
||||
SubResources []string
|
||||
// NormalizationRules are the rules to apply to field paths before comparison.
|
||||
NormalizationRules []field.NormalizationRule
|
||||
}
|
||||
|
||||
func WithSubResources(subResources ...string) ValidationTestConfig {
|
||||
return func(o *validationOption) {
|
||||
o.SubResources = subResources
|
||||
}
|
||||
}
|
||||
|
||||
func WithNormalizationRules(rules ...field.NormalizationRule) ValidationTestConfig {
|
||||
return func(o *validationOption) {
|
||||
o.NormalizationRules = rules
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyValidationEquivalence provides a helper for testing the migration from
|
||||
// hand-written imperative validation to declarative validation. It ensures that
|
||||
// the validation logic remains consistent before and after the feature is enabled.
|
||||
@@ -169,12 +191,16 @@ func convertToInternal(t *testing.T, scheme *runtime.Scheme, obj runtime.Object)
|
||||
// guaranteeing a safe migration. It also checks the errors against an expected set.
|
||||
// It compares errors by field, origin and type; all three should match to be called equivalent.
|
||||
// It also make sure all versions of the given API returns equivalent errors.
|
||||
func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime.Object, validateFn ValidateFunc, expectedErrs field.ErrorList, subResources ...string) {
|
||||
func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime.Object, validateFn ValidateFunc, expectedErrs field.ErrorList, testConfigs ...ValidationTestConfig) {
|
||||
t.Helper()
|
||||
opts := &validationOption{}
|
||||
for _, testcfg := range testConfigs {
|
||||
testcfg(opts)
|
||||
}
|
||||
verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList {
|
||||
return validateFn(ctx, obj)
|
||||
})
|
||||
VerifyVersionedValidationEquivalence(t, obj, nil, subResources...)
|
||||
}, opts)
|
||||
VerifyVersionedValidationEquivalence(t, obj, nil, opts.SubResources...)
|
||||
}
|
||||
|
||||
// VerifyUpdateValidationEquivalence provides a helper for testing the migration from
|
||||
@@ -190,22 +216,26 @@ func VerifyValidationEquivalence(t *testing.T, ctx context.Context, obj runtime.
|
||||
// guaranteeing a safe migration. It also checks the errors against an expected set.
|
||||
// It compares errors by field, origin and type; all three should match to be called equivalent.
|
||||
// It also make sure all versions of the given API returns equivalent errors.
|
||||
func VerifyUpdateValidationEquivalence(t *testing.T, ctx context.Context, obj, old runtime.Object, validateUpdateFn ValidateUpdateFunc, expectedErrs field.ErrorList, subResources ...string) {
|
||||
func VerifyUpdateValidationEquivalence(t *testing.T, ctx context.Context, obj, old runtime.Object, validateUpdateFn ValidateUpdateFunc, expectedErrs field.ErrorList, testConfigs ...ValidationTestConfig) {
|
||||
t.Helper()
|
||||
opts := &validationOption{}
|
||||
for _, testcfg := range testConfigs {
|
||||
testcfg(opts)
|
||||
}
|
||||
verifyValidationEquivalence(t, expectedErrs, func() field.ErrorList {
|
||||
return validateUpdateFn(ctx, obj, old)
|
||||
})
|
||||
VerifyVersionedValidationEquivalence(t, obj, old, subResources...)
|
||||
}, opts)
|
||||
VerifyVersionedValidationEquivalence(t, obj, old, opts.SubResources...)
|
||||
}
|
||||
|
||||
// verifyValidationEquivalence is a generic helper that verifies validation equivalence with and without declarative validation.
|
||||
func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, runValidations func() field.ErrorList) {
|
||||
func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, runValidations func() field.ErrorList, opt *validationOption) {
|
||||
t.Helper()
|
||||
var declarativeTakeoverErrs field.ErrorList
|
||||
var imperativeErrs field.ErrorList
|
||||
|
||||
// The errOutputMatcher is used to verify the output matches the expected errors in test cases.
|
||||
errOutputMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
errOutputMatcher := field.ErrorMatcher{}.ByType().ByOrigin().ByFieldNormalized(opt.NormalizationRules)
|
||||
|
||||
// We only need to test both gate enabled and disabled together, because
|
||||
// 1) the DeclarativeValidationTakeover won't take effect if DeclarativeValidation is disabled.
|
||||
@@ -241,7 +271,12 @@ func verifyValidationEquivalence(t *testing.T, expectedErrs field.ErrorList, run
|
||||
|
||||
// The equivalenceMatcher is used to verify the output errors from hand-written imperative validation
|
||||
// are equivalent to the output errors when DeclarativeValidationTakeover is enabled.
|
||||
equivalenceMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin()
|
||||
equivalenceMatcher := field.ErrorMatcher{}.ByType().ByOrigin()
|
||||
if len(opt.NormalizationRules) > 0 {
|
||||
equivalenceMatcher = equivalenceMatcher.ByFieldNormalized(opt.NormalizationRules)
|
||||
} else {
|
||||
equivalenceMatcher = equivalenceMatcher.ByField()
|
||||
}
|
||||
|
||||
// The imperative validation may produce duplicate errors, which is not supported by the ErrorMatcher.
|
||||
// TODO: remove this once ErrorMatcher has been extended to handle this form of deduplication.
|
||||
|
||||
148
pkg/apis/resource/v1/zz_generated.validations.go
generated
148
pkg/apis/resource/v1/zz_generated.validations.go
generated
@@ -146,6 +146,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequest)...)
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClaim) []resourcev1.DeviceRequest { return oldObj.Requests }))...)
|
||||
|
||||
@@ -161,6 +163,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceConstraint)...)
|
||||
return
|
||||
}(fldPath.Child("constraints"), obj.Constraints, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClaim) []resourcev1.DeviceConstraint { return oldObj.Constraints }))...)
|
||||
|
||||
@@ -176,12 +180,36 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClaimConfiguration)...)
|
||||
return
|
||||
}(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClaim) []resourcev1.DeviceClaimConfiguration { return oldObj.Config }))...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceClaimConfiguration validates an instance of DeviceClaimConfiguration according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceClaimConfiguration) (errs field.ErrorList) {
|
||||
// field resourcev1.DeviceClaimConfiguration.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceClaimConfiguration) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1.DeviceClaimConfiguration.DeviceConfiguration has no validation
|
||||
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) {
|
||||
@@ -267,6 +295,70 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceConstraint validates an instance of DeviceConstraint according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceConstraint) (errs field.ErrorList) {
|
||||
// field resourcev1.DeviceConstraint.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1.DeviceConstraint) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1.DeviceConstraint.MatchAttribute has no validation
|
||||
// field resourcev1.DeviceConstraint.DistinctAttribute has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceRequest validates an instance of DeviceRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceRequest) (errs field.ErrorList) {
|
||||
// field resourcev1.DeviceRequest.Name has no validation
|
||||
|
||||
// field resourcev1.DeviceRequest.Exactly
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *resourcev1.ExactDeviceRequest) (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.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ExactDeviceRequest(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("exactly"), obj.Exactly, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequest) *resourcev1.ExactDeviceRequest { return oldObj.Exactly }))...)
|
||||
|
||||
// field resourcev1.DeviceRequest.FirstAvailable
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceSubRequest) (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, 8); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceSubRequest)...)
|
||||
return
|
||||
}(fldPath.Child("firstAvailable"), obj.FirstAvailable, safe.Field(oldObj, func(oldObj *resourcev1.DeviceRequest) []resourcev1.DeviceSubRequest { return oldObj.FirstAvailable }))...)
|
||||
|
||||
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) {
|
||||
@@ -299,6 +391,62 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceSubRequest validates an instance of DeviceSubRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceSubRequest) (errs field.ErrorList) {
|
||||
// field resourcev1.DeviceSubRequest.Name has no validation
|
||||
// field resourcev1.DeviceSubRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1.DeviceSubRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1.DeviceSubRequest) []resourcev1.DeviceSelector { return oldObj.Selectors }))...)
|
||||
|
||||
// field resourcev1.DeviceSubRequest.AllocationMode has no validation
|
||||
// field resourcev1.DeviceSubRequest.Count has no validation
|
||||
// field resourcev1.DeviceSubRequest.Tolerations has no validation
|
||||
// field resourcev1.DeviceSubRequest.Capacity has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_ExactDeviceRequest validates an instance of ExactDeviceRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ExactDeviceRequest) (errs field.ErrorList) {
|
||||
// field resourcev1.ExactDeviceRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1.ExactDeviceRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1.ExactDeviceRequest) []resourcev1.DeviceSelector { return oldObj.Selectors }))...)
|
||||
|
||||
// field resourcev1.ExactDeviceRequest.AllocationMode has no validation
|
||||
// field resourcev1.ExactDeviceRequest.Count has no validation
|
||||
// field resourcev1.ExactDeviceRequest.AdminAccess has no validation
|
||||
// field resourcev1.ExactDeviceRequest.Tolerations has no validation
|
||||
// field resourcev1.ExactDeviceRequest.Capacity has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_ResourceClaim validates an instance of ResourceClaim according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.ResourceClaim) (errs field.ErrorList) {
|
||||
|
||||
149
pkg/apis/resource/v1beta1/zz_generated.validations.go
generated
149
pkg/apis/resource/v1beta1/zz_generated.validations.go
generated
@@ -148,6 +148,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequest)...)
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClaim) []resourcev1beta1.DeviceRequest { return oldObj.Requests }))...)
|
||||
|
||||
@@ -163,6 +165,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceConstraint)...)
|
||||
return
|
||||
}(fldPath.Child("constraints"), obj.Constraints, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClaim) []resourcev1beta1.DeviceConstraint {
|
||||
return oldObj.Constraints
|
||||
@@ -180,6 +184,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClaimConfiguration)...)
|
||||
return
|
||||
}(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClaim) []resourcev1beta1.DeviceClaimConfiguration {
|
||||
return oldObj.Config
|
||||
@@ -188,6 +194,28 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceClaimConfiguration validates an instance of DeviceClaimConfiguration according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceClaimConfiguration) (errs field.ErrorList) {
|
||||
// field resourcev1beta1.DeviceClaimConfiguration.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceClaimConfiguration) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1beta1.DeviceClaimConfiguration.DeviceConfiguration has no validation
|
||||
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) {
|
||||
@@ -277,6 +305,78 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceConstraint validates an instance of DeviceConstraint according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceConstraint) (errs field.ErrorList) {
|
||||
// field resourcev1beta1.DeviceConstraint.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceConstraint) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1beta1.DeviceConstraint.MatchAttribute has no validation
|
||||
// field resourcev1beta1.DeviceConstraint.DistinctAttribute has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceRequest validates an instance of DeviceRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceRequest) (errs field.ErrorList) {
|
||||
// field resourcev1beta1.DeviceRequest.Name has no validation
|
||||
// field resourcev1beta1.DeviceRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1beta1.DeviceRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequest) []resourcev1beta1.DeviceSelector { return oldObj.Selectors }))...)
|
||||
|
||||
// field resourcev1beta1.DeviceRequest.AllocationMode has no validation
|
||||
// field resourcev1beta1.DeviceRequest.Count has no validation
|
||||
// field resourcev1beta1.DeviceRequest.AdminAccess has no validation
|
||||
|
||||
// field resourcev1beta1.DeviceRequest.FirstAvailable
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceSubRequest) (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, 8); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceSubRequest)...)
|
||||
return
|
||||
}(fldPath.Child("firstAvailable"), obj.FirstAvailable, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequest) []resourcev1beta1.DeviceSubRequest {
|
||||
return oldObj.FirstAvailable
|
||||
}))...)
|
||||
|
||||
// field resourcev1beta1.DeviceRequest.Tolerations has no validation
|
||||
// field resourcev1beta1.DeviceRequest.Capacity 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) {
|
||||
@@ -301,7 +401,24 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op
|
||||
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.Device has no validation
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.AdminAccess has no validation
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.Tolerations has no validation
|
||||
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.Tolerations
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceToleration) (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, 16); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("tolerations"), obj.Tolerations, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceRequestAllocationResult) []resourcev1beta1.DeviceToleration {
|
||||
return oldObj.Tolerations
|
||||
}))...)
|
||||
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.BindingConditions has no validation
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.BindingFailureConditions has no validation
|
||||
// field resourcev1beta1.DeviceRequestAllocationResult.ShareID has no validation
|
||||
@@ -309,6 +426,36 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceSubRequest validates an instance of DeviceSubRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceSubRequest) (errs field.ErrorList) {
|
||||
// field resourcev1beta1.DeviceSubRequest.Name has no validation
|
||||
// field resourcev1beta1.DeviceSubRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1beta1.DeviceSubRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceSubRequest) []resourcev1beta1.DeviceSelector {
|
||||
return oldObj.Selectors
|
||||
}))...)
|
||||
|
||||
// field resourcev1beta1.DeviceSubRequest.AllocationMode has no validation
|
||||
// field resourcev1beta1.DeviceSubRequest.Count has no validation
|
||||
// field resourcev1beta1.DeviceSubRequest.Tolerations has no validation
|
||||
// field resourcev1beta1.DeviceSubRequest.Capacity has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_ResourceClaim validates an instance of ResourceClaim according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.ResourceClaim) (errs field.ErrorList) {
|
||||
|
||||
154
pkg/apis/resource/v1beta2/zz_generated.validations.go
generated
154
pkg/apis/resource/v1beta2/zz_generated.validations.go
generated
@@ -148,6 +148,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceRequest)...)
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClaim) []resourcev1beta2.DeviceRequest { return oldObj.Requests }))...)
|
||||
|
||||
@@ -163,6 +165,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceConstraint)...)
|
||||
return
|
||||
}(fldPath.Child("constraints"), obj.Constraints, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClaim) []resourcev1beta2.DeviceConstraint {
|
||||
return oldObj.Constraints
|
||||
@@ -180,6 +184,8 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceClaimConfiguration)...)
|
||||
return
|
||||
}(fldPath.Child("config"), obj.Config, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClaim) []resourcev1beta2.DeviceClaimConfiguration {
|
||||
return oldObj.Config
|
||||
@@ -188,6 +194,28 @@ func Validate_DeviceClaim(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceClaimConfiguration validates an instance of DeviceClaimConfiguration according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceClaimConfiguration(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceClaimConfiguration) (errs field.ErrorList) {
|
||||
// field resourcev1beta2.DeviceClaimConfiguration.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceClaimConfiguration) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1beta2.DeviceClaimConfiguration.DeviceConfiguration has no validation
|
||||
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) {
|
||||
@@ -277,6 +305,72 @@ func Validate_DeviceClassSpec(ctx context.Context, op operation.Operation, fldPa
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceConstraint validates an instance of DeviceConstraint according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceConstraint) (errs field.ErrorList) {
|
||||
// field resourcev1beta2.DeviceConstraint.Requests
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []string) (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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("requests"), obj.Requests, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceConstraint) []string { return oldObj.Requests }))...)
|
||||
|
||||
// field resourcev1beta2.DeviceConstraint.MatchAttribute has no validation
|
||||
// field resourcev1beta2.DeviceConstraint.DistinctAttribute has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceRequest validates an instance of DeviceRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceRequest) (errs field.ErrorList) {
|
||||
// field resourcev1beta2.DeviceRequest.Name has no validation
|
||||
|
||||
// field resourcev1beta2.DeviceRequest.Exactly
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *resourcev1beta2.ExactDeviceRequest) (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.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ExactDeviceRequest(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("exactly"), obj.Exactly, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequest) *resourcev1beta2.ExactDeviceRequest { return oldObj.Exactly }))...)
|
||||
|
||||
// field resourcev1beta2.DeviceRequest.FirstAvailable
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceSubRequest) (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, 8); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// iterate the list and call the type's validation function
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, Validate_DeviceSubRequest)...)
|
||||
return
|
||||
}(fldPath.Child("firstAvailable"), obj.FirstAvailable, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceRequest) []resourcev1beta2.DeviceSubRequest {
|
||||
return oldObj.FirstAvailable
|
||||
}))...)
|
||||
|
||||
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) {
|
||||
@@ -309,6 +403,66 @@ func Validate_DeviceRequestAllocationResult(ctx context.Context, op operation.Op
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_DeviceSubRequest validates an instance of DeviceSubRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_DeviceSubRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceSubRequest) (errs field.ErrorList) {
|
||||
// field resourcev1beta2.DeviceSubRequest.Name has no validation
|
||||
// field resourcev1beta2.DeviceSubRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1beta2.DeviceSubRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceSubRequest) []resourcev1beta2.DeviceSelector {
|
||||
return oldObj.Selectors
|
||||
}))...)
|
||||
|
||||
// field resourcev1beta2.DeviceSubRequest.AllocationMode has no validation
|
||||
// field resourcev1beta2.DeviceSubRequest.Count has no validation
|
||||
// field resourcev1beta2.DeviceSubRequest.Tolerations has no validation
|
||||
// field resourcev1beta2.DeviceSubRequest.Capacity has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_ExactDeviceRequest validates an instance of ExactDeviceRequest according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ExactDeviceRequest(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ExactDeviceRequest) (errs field.ErrorList) {
|
||||
// field resourcev1beta2.ExactDeviceRequest.DeviceClassName has no validation
|
||||
|
||||
// field resourcev1beta2.ExactDeviceRequest.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
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("selectors"), obj.Selectors, safe.Field(oldObj, func(oldObj *resourcev1beta2.ExactDeviceRequest) []resourcev1beta2.DeviceSelector {
|
||||
return oldObj.Selectors
|
||||
}))...)
|
||||
|
||||
// field resourcev1beta2.ExactDeviceRequest.AllocationMode has no validation
|
||||
// field resourcev1beta2.ExactDeviceRequest.Count has no validation
|
||||
// field resourcev1beta2.ExactDeviceRequest.AdminAccess has no validation
|
||||
// field resourcev1beta2.ExactDeviceRequest.Tolerations has no validation
|
||||
// field resourcev1beta2.ExactDeviceRequest.Capacity has no validation
|
||||
return errs
|
||||
}
|
||||
|
||||
// Validate_ResourceClaim validates an instance of ResourceClaim according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ResourceClaim(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.ResourceClaim) (errs field.ErrorList) {
|
||||
|
||||
@@ -203,7 +203,7 @@ func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path,
|
||||
func(subRequest resource.DeviceSubRequest) (string, string) {
|
||||
return subRequest.Name, "name"
|
||||
},
|
||||
fldPath.Child("firstAvailable"))...)
|
||||
fldPath.Child("firstAvailable"), sizeCovered)...)
|
||||
}
|
||||
|
||||
if request.Exactly != nil {
|
||||
@@ -275,7 +275,7 @@ func validateSelectorSlice(selectors []resource.DeviceSelector, fldPath *field.P
|
||||
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
|
||||
return validateSelector(selector, fldPath, stored)
|
||||
},
|
||||
fldPath)
|
||||
fldPath, sizeCovered)
|
||||
}
|
||||
|
||||
func validateSelector(selector resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
|
||||
@@ -331,7 +331,7 @@ func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *fie
|
||||
func(name string, fldPath *field.Path) field.ErrorList {
|
||||
return validateRequestNameRef(name, fldPath, requestNames)
|
||||
},
|
||||
stringKey, fldPath.Child("requests"))...)
|
||||
stringKey, fldPath.Child("requests"), sizeCovered)...)
|
||||
if constraint.MatchAttribute != nil {
|
||||
allErrs = append(allErrs, validateFullyQualifiedName(*constraint.MatchAttribute, fldPath.Child("matchAttribute"))...)
|
||||
} else if constraint.DistinctAttribute != nil {
|
||||
@@ -349,7 +349,7 @@ func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration,
|
||||
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
|
||||
func(name string, fldPath *field.Path) field.ErrorList {
|
||||
return validateRequestNameRef(name, fldPath, requestNames)
|
||||
}, stringKey, fldPath.Child("requests"))...)
|
||||
}, stringKey, fldPath.Child("requests"), sizeCovered)...)
|
||||
allErrs = append(allErrs, validateDeviceConfiguration(config.DeviceConfiguration, fldPath, stored)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@@ -648,7 +648,7 @@ func TestValidateClaim(t *testing.T) {
|
||||
},
|
||||
"prioritized-list-too-many-subrequests": {
|
||||
wantFailures: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8),
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8).MarkCoveredByDeclarative(),
|
||||
},
|
||||
claim: func() *resource.ResourceClaim {
|
||||
claim := testClaim(goodName, goodNS, validClaimSpec)
|
||||
|
||||
@@ -84,11 +84,58 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
|
||||
field.TooMany(field.NewPath("spec", "devices", "config"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid firstAvailable, too many": {
|
||||
input: mkValidResourceClaim(tweakFirstAvailable(9)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid selectors, too many": {
|
||||
input: mkValidResourceClaim(tweakExactlySelectors(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("exactly", "selectors"), 33, 32).WithOrigin("maxItems").MarkCoveredByDeclarative(),
|
||||
},
|
||||
},
|
||||
"invalid subrequest selectors, too many": {
|
||||
input: mkValidResourceClaim(tweakSubRequestSelectors(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("selectors"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid constraint requests, too many": {
|
||||
input: mkValidResourceClaim(tweakConstraintRequests(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"),
|
||||
field.TooMany(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"invalid config requests, too many": {
|
||||
input: mkValidResourceClaim(tweakConfigRequests(33)),
|
||||
expectedErrs: field.ErrorList{
|
||||
field.TooMany(field.NewPath("spec", "devices", "requests"), 33, 32).WithOrigin("maxItems"),
|
||||
field.TooMany(field.NewPath("spec", "devices", "config").Index(0).Child("requests"), 33, 32).WithOrigin("maxItems"),
|
||||
},
|
||||
},
|
||||
"valid firstAvailable, max allowed": {
|
||||
input: mkValidResourceClaim(tweakFirstAvailable(8)),
|
||||
},
|
||||
"valid selectors, max allowed": {
|
||||
input: mkValidResourceClaim(tweakExactlySelectors(32)),
|
||||
},
|
||||
"valid subrequest selectors, max allowed": {
|
||||
input: mkValidResourceClaim(tweakSubRequestSelectors(32)),
|
||||
},
|
||||
"valid constraint requests, max allowed": {
|
||||
input: mkValidResourceClaim(tweakConstraintRequests(32)),
|
||||
},
|
||||
"valid config requests, max allowed": {
|
||||
input: mkValidResourceClaim(tweakConfigRequests(32)),
|
||||
},
|
||||
// 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)
|
||||
apitesting.VerifyValidationEquivalence(t, ctx, &tc.input, Strategy.Validate, tc.expectedErrs, apitesting.WithNormalizationRules(resourceClaimNormalizationRules...))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -118,6 +165,83 @@ func tweakDevicesRequests(items int) func(*resource.ResourceClaim) {
|
||||
}
|
||||
}
|
||||
|
||||
func tweakExactlySelectors(items int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
for i := 0; i < items; i++ {
|
||||
rc.Spec.Devices.Requests[0].Exactly.Selectors = append(rc.Spec.Devices.Requests[0].Exactly.Selectors,
|
||||
resource.DeviceSelector{
|
||||
CEL: &resource.CELDeviceSelector{
|
||||
Expression: fmt.Sprintf("device.driver == \"test.driver.io%d\"", i),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tweakSubRequestSelectors(items int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
rc.Spec.Devices.Requests[0].Exactly = nil
|
||||
rc.Spec.Devices.Requests[0].FirstAvailable = []resource.DeviceSubRequest{
|
||||
{
|
||||
Name: "sub-0",
|
||||
DeviceClassName: "class",
|
||||
AllocationMode: resource.DeviceAllocationModeAll,
|
||||
},
|
||||
}
|
||||
for i := 0; i < items; i++ {
|
||||
rc.Spec.Devices.Requests[0].FirstAvailable[0].Selectors = append(rc.Spec.Devices.Requests[0].FirstAvailable[0].Selectors,
|
||||
resource.DeviceSelector{
|
||||
CEL: &resource.CELDeviceSelector{
|
||||
Expression: fmt.Sprintf("device.driver == \"test.driver.io%d\"", i),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tweakConstraintRequests(count int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
tweakDevicesRequests(count)(rc)
|
||||
if len(rc.Spec.Devices.Constraints) == 0 {
|
||||
rc.Spec.Devices.Constraints = append(rc.Spec.Devices.Constraints, mkDeviceConstraint())
|
||||
}
|
||||
rc.Spec.Devices.Constraints[0].Requests = []string{}
|
||||
for i := 0; i < count; i++ {
|
||||
rc.Spec.Devices.Constraints[0].Requests = append(rc.Spec.Devices.Constraints[0].Requests, fmt.Sprintf("req-%d", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tweakConfigRequests(count int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
tweakDevicesRequests(count)(rc)
|
||||
if len(rc.Spec.Devices.Config) == 0 {
|
||||
rc.Spec.Devices.Config = append(rc.Spec.Devices.Config, mkDeviceClaimConfiguration())
|
||||
}
|
||||
rc.Spec.Devices.Config[0].Requests = []string{}
|
||||
for i := 0; i < count; i++ {
|
||||
rc.Spec.Devices.Config[0].Requests = append(rc.Spec.Devices.Config[0].Requests, fmt.Sprintf("req-%d", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tweakFirstAvailable(items int) func(*resource.ResourceClaim) {
|
||||
return func(rc *resource.ResourceClaim) {
|
||||
rc.Spec.Devices.Requests[0].Exactly = nil
|
||||
for i := 0; i < items; i++ {
|
||||
rc.Spec.Devices.Requests[0].FirstAvailable = append(rc.Spec.Devices.Requests[0].FirstAvailable,
|
||||
resource.DeviceSubRequest{
|
||||
Name: fmt.Sprintf("sub-%d", i),
|
||||
DeviceClassName: "class",
|
||||
AllocationMode: resource.DeviceAllocationModeAll,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mkDeviceClaimConfiguration() resource.DeviceClaimConfiguration {
|
||||
return resource.DeviceClaimConfiguration{
|
||||
Requests: []string{"req-0"},
|
||||
@@ -181,7 +305,7 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
|
||||
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)
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, Strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithNormalizationRules(resourceClaimNormalizationRules...))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -258,7 +382,7 @@ func TestValidateStatusUpdateForDeclarative(t *testing.T) {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
tc.old.ObjectMeta.ResourceVersion = "1"
|
||||
tc.update.ObjectMeta.ResourceVersion = "1"
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, strategy.ValidateUpdate, tc.expectedErrs, "status")
|
||||
apitesting.VerifyUpdateValidationEquivalence(t, ctx, &tc.update, &tc.old, strategy.ValidateUpdate, tc.expectedErrs, apitesting.WithSubResources("status"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package resourceclaim
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
|
||||
|
||||
@@ -52,6 +53,15 @@ type resourceclaimStrategy struct {
|
||||
nsClient v1.NamespaceInterface
|
||||
}
|
||||
|
||||
var resourceClaimNormalizationRules = []field.NormalizationRule{
|
||||
{
|
||||
// The "exactly" struct was added in v1beta2. In earlier API
|
||||
// versions, its fields were directly part of the DeviceRequest.
|
||||
Regexp: regexp.MustCompile(`spec\.devices\.requests\[(\d+)\]\.selectors`),
|
||||
Replacement: "spec.devices.requests[$1].exactly.selectors",
|
||||
},
|
||||
}
|
||||
|
||||
// NewStrategy is the default logic that applies when creating and updating ResourceClaim objects.
|
||||
func NewStrategy(nsClient v1.NamespaceInterface) *resourceclaimStrategy {
|
||||
return &resourceclaimStrategy{
|
||||
@@ -100,7 +110,7 @@ func (s *resourceclaimStrategy) Validate(ctx context.Context, obj runtime.Object
|
||||
|
||||
allErrs := resourceutils.AuthorizedForAdmin(ctx, claim.Spec.Devices.Requests, claim.Namespace, s.nsClient)
|
||||
allErrs = append(allErrs, validation.ValidateResourceClaim(claim)...)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, claim, nil, allErrs, operation.Create)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, claim, nil, allErrs, operation.Create, rest.WithNormalizationRules(resourceClaimNormalizationRules))
|
||||
}
|
||||
|
||||
func (*resourceclaimStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||
@@ -128,7 +138,7 @@ func (s *resourceclaimStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
||||
// AuthorizedForAdmin isn't needed here because the spec is immutable.
|
||||
errorList := validation.ValidateResourceClaim(newClaim)
|
||||
errorList = append(errorList, validation.ValidateResourceClaimUpdate(newClaim, oldClaim)...)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClaim, oldClaim, errorList, operation.Update)
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClaim, oldClaim, errorList, operation.Update, rest.WithNormalizationRules(resourceClaimNormalizationRules))
|
||||
}
|
||||
|
||||
func (*resourceclaimStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||
|
||||
@@ -592,6 +592,7 @@ message DeviceClaimConfiguration {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
optional DeviceConfiguration deviceConfiguration = 2;
|
||||
@@ -698,6 +699,7 @@ message DeviceConstraint {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -780,6 +782,7 @@ message DeviceRequest {
|
||||
//
|
||||
// +optional
|
||||
// +oneOf=deviceRequestType
|
||||
// +k8s:optional
|
||||
optional ExactDeviceRequest exactly = 2;
|
||||
|
||||
// FirstAvailable contains subrequests, of which exactly one will be
|
||||
@@ -800,6 +803,7 @@ message DeviceRequest {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
repeated DeviceSubRequest firstAvailable = 3;
|
||||
}
|
||||
|
||||
@@ -962,6 +966,7 @@ message DeviceSubRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 3;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -1133,6 +1138,7 @@ message ExactDeviceRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 2;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
|
||||
@@ -792,6 +792,7 @@ type DeviceRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +oneOf=deviceRequestType
|
||||
// +k8s:optional
|
||||
Exactly *ExactDeviceRequest `json:"exactly,omitempty" protobuf:"bytes,2,name=exactly"`
|
||||
|
||||
// FirstAvailable contains subrequests, of which exactly one will be
|
||||
@@ -812,6 +813,7 @@ type DeviceRequest struct {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,3,name=firstAvailable"`
|
||||
}
|
||||
|
||||
@@ -839,6 +841,7 @@ type ExactDeviceRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,2,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -965,6 +968,7 @@ type DeviceSubRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -1190,6 +1194,7 @@ type DeviceConstraint struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -1247,6 +1252,7 @@ type DeviceClaimConfiguration struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"`
|
||||
|
||||
@@ -600,6 +600,7 @@ message DeviceClaimConfiguration {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
optional DeviceConfiguration deviceConfiguration = 2;
|
||||
@@ -706,6 +707,7 @@ message DeviceConstraint {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -804,6 +806,7 @@ message DeviceRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 3;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -878,6 +881,7 @@ message DeviceRequest {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
repeated DeviceSubRequest firstAvailable = 7;
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
@@ -987,6 +991,7 @@ message DeviceRequestAllocationResult {
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
// +k8s:maxItems=16
|
||||
repeated DeviceToleration tolerations = 6;
|
||||
|
||||
// BindingConditions contains a copy of the BindingConditions
|
||||
@@ -1084,6 +1089,7 @@ message DeviceSubRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 3;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
|
||||
@@ -812,6 +812,7 @@ type DeviceRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -886,6 +887,7 @@ type DeviceRequest struct {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,7,name=firstAvailable"`
|
||||
|
||||
// If specified, the request's tolerations.
|
||||
@@ -973,6 +975,7 @@ type DeviceSubRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -1198,6 +1201,7 @@ type DeviceConstraint struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -1255,6 +1259,7 @@ type DeviceClaimConfiguration struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"`
|
||||
@@ -1552,6 +1557,7 @@ type DeviceRequestAllocationResult struct {
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +featureGate=DRADeviceTaints
|
||||
// +k8s:maxItems=16
|
||||
Tolerations []DeviceToleration `json:"tolerations,omitempty" protobuf:"bytes,6,opt,name=tolerations"`
|
||||
|
||||
// BindingConditions contains a copy of the BindingConditions
|
||||
|
||||
@@ -592,6 +592,7 @@ message DeviceClaimConfiguration {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
optional DeviceConfiguration deviceConfiguration = 2;
|
||||
@@ -698,6 +699,7 @@ message DeviceConstraint {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated string requests = 1;
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -780,6 +782,7 @@ message DeviceRequest {
|
||||
//
|
||||
// +optional
|
||||
// +oneOf=deviceRequestType
|
||||
// +k8s:optional
|
||||
optional ExactDeviceRequest exactly = 2;
|
||||
|
||||
// FirstAvailable contains subrequests, of which exactly one will be
|
||||
@@ -800,6 +803,7 @@ message DeviceRequest {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
repeated DeviceSubRequest firstAvailable = 3;
|
||||
}
|
||||
|
||||
@@ -962,6 +966,7 @@ message DeviceSubRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 3;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -1133,6 +1138,7 @@ message ExactDeviceRequest {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
repeated DeviceSelector selectors = 2;
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
|
||||
@@ -792,6 +792,7 @@ type DeviceRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +oneOf=deviceRequestType
|
||||
// +k8s:optional
|
||||
Exactly *ExactDeviceRequest `json:"exactly,omitempty" protobuf:"bytes,2,name=exactly"`
|
||||
|
||||
// FirstAvailable contains subrequests, of which exactly one will be
|
||||
@@ -812,6 +813,7 @@ type DeviceRequest struct {
|
||||
// +oneOf=deviceRequestType
|
||||
// +listType=atomic
|
||||
// +featureGate=DRAPrioritizedList
|
||||
// +k8s:maxItems=8
|
||||
FirstAvailable []DeviceSubRequest `json:"firstAvailable,omitempty" protobuf:"bytes,3,name=firstAvailable"`
|
||||
}
|
||||
|
||||
@@ -839,6 +841,7 @@ type ExactDeviceRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,2,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -965,6 +968,7 @@ type DeviceSubRequest struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Selectors []DeviceSelector `json:"selectors,omitempty" protobuf:"bytes,3,name=selectors"`
|
||||
|
||||
// AllocationMode and its related fields define how devices are allocated
|
||||
@@ -1190,6 +1194,7 @@ type DeviceConstraint struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
// MatchAttribute requires that all devices in question have this
|
||||
@@ -1247,6 +1252,7 @@ type DeviceClaimConfiguration struct {
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
// +k8s:maxItems=32
|
||||
Requests []string `json:"requests,omitempty" protobuf:"bytes,1,opt,name=requests"`
|
||||
|
||||
DeviceConfiguration `json:",inline" protobuf:"bytes,2,name=deviceConfiguration"`
|
||||
|
||||
@@ -68,12 +68,20 @@ func WithSubresourceMapper(subresourceMapper GroupVersionKindProvider) Validatio
|
||||
}
|
||||
}
|
||||
|
||||
// WithNormalizationRules sets the normalization rules for validation.
|
||||
func WithNormalizationRules(rules []field.NormalizationRule) ValidationConfig {
|
||||
return func(config *validationConfigOption) {
|
||||
config.normalizationRules = rules
|
||||
}
|
||||
}
|
||||
|
||||
type validationConfigOption struct {
|
||||
opType operation.Type
|
||||
options []string
|
||||
takeover bool
|
||||
subresourceGVKMapper GroupVersionKindProvider
|
||||
validationIdentifier string
|
||||
normalizationRules []field.NormalizationRule
|
||||
}
|
||||
|
||||
// validateDeclaratively validates obj and oldObj against declarative
|
||||
@@ -146,9 +154,9 @@ func parseSubresourcePath(subresourcePath string) ([]string, error) {
|
||||
|
||||
// compareDeclarativeErrorsAndEmitMismatches checks for mismatches between imperative and declarative validation
|
||||
// and logs + emits metrics when inconsistencies are found
|
||||
func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeErrs, declarativeErrs field.ErrorList, takeover bool, validationIdentifier string) {
|
||||
func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeErrs, declarativeErrs field.ErrorList, takeover bool, validationIdentifier string, normalizationRules []field.NormalizationRule) {
|
||||
logger := klog.FromContext(ctx)
|
||||
mismatchDetails := gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs, takeover)
|
||||
mismatchDetails := gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs, takeover, normalizationRules)
|
||||
for _, detail := range mismatchDetails {
|
||||
// Log information about the mismatch using contextual logger
|
||||
logger.Error(nil, detail)
|
||||
@@ -160,7 +168,7 @@ func compareDeclarativeErrorsAndEmitMismatches(ctx context.Context, imperativeEr
|
||||
|
||||
// gatherDeclarativeValidationMismatches compares imperative and declarative validation errors
|
||||
// and returns detailed information about any mismatches found. Errors are compared via type, field, and origin
|
||||
func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field.ErrorList, takeover bool) []string {
|
||||
func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field.ErrorList, takeover bool, normalizationRules []field.NormalizationRule) []string {
|
||||
var mismatchDetails []string
|
||||
// short circuit here to minimize allocs for usual case of 0 validation errors
|
||||
if len(imperativeErrs) == 0 && len(declarativeErrs) == 0 {
|
||||
@@ -171,7 +179,7 @@ func gatherDeclarativeValidationMismatches(imperativeErrs, declarativeErrs field
|
||||
if takeover {
|
||||
recommendation = "Consider disabling the DeclarativeValidationTakeover feature gate to keep data persisted in etcd consistent with prior versions of Kubernetes."
|
||||
}
|
||||
fuzzyMatcher := field.ErrorMatcher{}.ByType().ByField().ByOrigin().RequireOriginWhenInvalid()
|
||||
fuzzyMatcher := field.ErrorMatcher{}.ByType().ByOrigin().RequireOriginWhenInvalid().ByFieldNormalized(normalizationRules)
|
||||
exactMatcher := field.ErrorMatcher{}.Exactly()
|
||||
|
||||
// Dedupe imperative errors of exact error matches as they are
|
||||
@@ -353,8 +361,7 @@ func ValidateDeclarativelyWithMigrationChecks(ctx context.Context, scheme *runti
|
||||
// Call the panic-safe wrapper with the real validation function.
|
||||
declarativeErrs := panicSafeValidateFunc(validateDeclaratively, cfg.takeover, cfg.validationIdentifier)(ctx, scheme, obj, oldObj, cfg)
|
||||
|
||||
compareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
compareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier, cfg.normalizationRules)
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ func TestGatherDeclarativeValidationMismatches(t *testing.T) {
|
||||
errB := field.Invalid(minReadySecondsPath, -1, "covered error B").WithOrigin("minimum")
|
||||
coveredErrB := field.Invalid(minReadySecondsPath, -1, "covered error B").WithOrigin("minimum")
|
||||
errBWithDiffDetail := field.Invalid(minReadySecondsPath, -1, "covered error B - different detail").WithOrigin("minimum")
|
||||
errBWithDiffPath := field.Invalid(field.NewPath("spec").Child("fakeminReadySeconds"), -1, "covered error B").WithOrigin("minimum")
|
||||
coveredErrB.CoveredByDeclarative = true
|
||||
errC := field.Invalid(replicasPath, nil, "covered error C").WithOrigin("minimum")
|
||||
coveredErrC := field.Invalid(replicasPath, nil, "covered error C").WithOrigin("minimum")
|
||||
@@ -227,6 +228,7 @@ func TestGatherDeclarativeValidationMismatches(t *testing.T) {
|
||||
takeover bool
|
||||
expectMismatches bool
|
||||
expectDetailsContaining []string
|
||||
normalizedRules []field.NormalizationRule
|
||||
}{
|
||||
{
|
||||
name: "Declarative and imperative return 0 errors - no mismatch",
|
||||
@@ -358,11 +360,29 @@ func TestGatherDeclarativeValidationMismatches(t *testing.T) {
|
||||
expectMismatches: false,
|
||||
expectDetailsContaining: []string{},
|
||||
},
|
||||
{
|
||||
name: "Field normalization, errors don't match - mismatch",
|
||||
imperativeErrors: field.ErrorList{
|
||||
coveredErrB,
|
||||
},
|
||||
declarativeErrors: field.ErrorList{
|
||||
errBWithDiffPath,
|
||||
},
|
||||
normalizedRules: []field.NormalizationRule{
|
||||
{
|
||||
Regexp: regexp.MustCompile(`spec.fakeminReadySeconds`),
|
||||
Replacement: "spec.minReadySeconds",
|
||||
},
|
||||
},
|
||||
takeover: false,
|
||||
expectMismatches: false,
|
||||
expectDetailsContaining: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
details := gatherDeclarativeValidationMismatches(tc.imperativeErrors, tc.declarativeErrors, tc.takeover)
|
||||
details := gatherDeclarativeValidationMismatches(tc.imperativeErrors, tc.declarativeErrors, tc.takeover, tc.normalizedRules)
|
||||
// Check if mismatches were found if expected
|
||||
if tc.expectMismatches && len(details) == 0 {
|
||||
t.Errorf("Expected mismatches but got none")
|
||||
@@ -429,7 +449,7 @@ func TestCompareDeclarativeErrorsAndEmitMismatches(t *testing.T) {
|
||||
defer klog.LogToStderr(true)
|
||||
ctx := context.Background()
|
||||
|
||||
compareDeclarativeErrorsAndEmitMismatches(ctx, tc.imperativeErrs, tc.declarativeErrs, tc.takeover, "test_validationIdentifier")
|
||||
compareDeclarativeErrorsAndEmitMismatches(ctx, tc.imperativeErrs, tc.declarativeErrs, tc.takeover, "test_validationIdentifier", nil)
|
||||
|
||||
klog.Flush()
|
||||
logOutput := buf.String()
|
||||
|
||||
Reference in New Issue
Block a user