Use eachKey DV in DRA resources.

This commit is contained in:
Lalit Chauhan 2025-10-24 00:35:10 +00:00
parent d915ef1660
commit 8f0a6583ca
13 changed files with 370 additions and 21 deletions

View file

@ -193,6 +193,34 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP
return errs
}
// Validate_CounterSet validates an instance of CounterSet according
// to declarative validation rules in the API schema.
func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.CounterSet) (errs field.ErrorList) {
// field resourcev1.CounterSet.Name has no validation
// field resourcev1.CounterSet.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1.CounterSet) map[string]resourcev1.Counter { return oldObj.Counters }))...)
return errs
}
// Validate_Device validates an instance of Device according
// to declarative validation rules in the API schema.
func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.Device) (errs field.ErrorList) {
@ -213,7 +241,19 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field
}))...)
// field resourcev1.Device.Capacity has no validation
// field resourcev1.Device.ConsumesCounters has no validation
// field resourcev1.Device.ConsumesCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.DeviceCounterConsumption) (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_DeviceCounterConsumption)...)
return
}(fldPath.Child("consumesCounters"), obj.ConsumesCounters, safe.Field(oldObj, func(oldObj *resourcev1.Device) []resourcev1.DeviceCounterConsumption { return oldObj.ConsumesCounters }))...)
// field resourcev1.Device.NodeName has no validation
// field resourcev1.Device.NodeSelector has no validation
// field resourcev1.Device.AllNodes has no validation
@ -811,6 +851,36 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP
return errs
}
// Validate_DeviceCounterConsumption validates an instance of DeviceCounterConsumption according
// to declarative validation rules in the API schema.
func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1.DeviceCounterConsumption) (errs field.ErrorList) {
// field resourcev1.DeviceCounterConsumption.CounterSet has no validation
// field resourcev1.DeviceCounterConsumption.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1.DeviceCounterConsumption) map[string]resourcev1.Counter {
return oldObj.Counters
}))...)
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) {
@ -1615,6 +1685,18 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1.ResourceSliceSpec) []resourcev1.Device { return oldObj.Devices }))...)
// field resourcev1.ResourceSliceSpec.PerDeviceNodeSelection has no validation
// field resourcev1.ResourceSliceSpec.SharedCounters has no validation
// field resourcev1.ResourceSliceSpec.SharedCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1.CounterSet) (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_CounterSet)...)
return
}(fldPath.Child("sharedCounters"), obj.SharedCounters, safe.Field(oldObj, func(oldObj *resourcev1.ResourceSliceSpec) []resourcev1.CounterSet { return oldObj.SharedCounters }))...)
return errs
}

View file

@ -213,7 +213,21 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath *
}))...)
// field resourcev1beta1.BasicDevice.Capacity has no validation
// field resourcev1beta1.BasicDevice.ConsumesCounters has no validation
// field resourcev1beta1.BasicDevice.ConsumesCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.DeviceCounterConsumption) (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_DeviceCounterConsumption)...)
return
}(fldPath.Child("consumesCounters"), obj.ConsumesCounters, safe.Field(oldObj, func(oldObj *resourcev1beta1.BasicDevice) []resourcev1beta1.DeviceCounterConsumption {
return oldObj.ConsumesCounters
}))...)
// field resourcev1beta1.BasicDevice.NodeName has no validation
// field resourcev1beta1.BasicDevice.NodeSelector has no validation
// field resourcev1beta1.BasicDevice.AllNodes has no validation
@ -280,6 +294,34 @@ func Validate_BasicDevice(ctx context.Context, op operation.Operation, fldPath *
return errs
}
// Validate_CounterSet validates an instance of CounterSet according
// to declarative validation rules in the API schema.
func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.CounterSet) (errs field.ErrorList) {
// field resourcev1beta1.CounterSet.Name has no validation
// field resourcev1beta1.CounterSet.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta1.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta1.CounterSet) map[string]resourcev1beta1.Counter { return oldObj.Counters }))...)
return errs
}
// Validate_Device validates an instance of Device according
// to declarative validation rules in the API schema.
func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.Device) (errs field.ErrorList) {
@ -839,6 +881,36 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP
return errs
}
// Validate_DeviceCounterConsumption validates an instance of DeviceCounterConsumption according
// to declarative validation rules in the API schema.
func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta1.DeviceCounterConsumption) (errs field.ErrorList) {
// field resourcev1beta1.DeviceCounterConsumption.CounterSet has no validation
// field resourcev1beta1.DeviceCounterConsumption.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta1.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta1.DeviceCounterConsumption) map[string]resourcev1beta1.Counter {
return oldObj.Counters
}))...)
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) {
@ -1647,6 +1719,20 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceSliceSpec) []resourcev1beta1.Device { return oldObj.Devices }))...)
// field resourcev1beta1.ResourceSliceSpec.PerDeviceNodeSelection has no validation
// field resourcev1beta1.ResourceSliceSpec.SharedCounters has no validation
// field resourcev1beta1.ResourceSliceSpec.SharedCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta1.CounterSet) (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_CounterSet)...)
return
}(fldPath.Child("sharedCounters"), obj.SharedCounters, safe.Field(oldObj, func(oldObj *resourcev1beta1.ResourceSliceSpec) []resourcev1beta1.CounterSet {
return oldObj.SharedCounters
}))...)
return errs
}

View file

@ -195,6 +195,34 @@ func Validate_AllocationResult(ctx context.Context, op operation.Operation, fldP
return errs
}
// Validate_CounterSet validates an instance of CounterSet according
// to declarative validation rules in the API schema.
func Validate_CounterSet(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.CounterSet) (errs field.ErrorList) {
// field resourcev1beta2.CounterSet.Name has no validation
// field resourcev1beta2.CounterSet.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta2.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta2.CounterSet) map[string]resourcev1beta2.Counter { return oldObj.Counters }))...)
return errs
}
// Validate_Device validates an instance of Device according
// to declarative validation rules in the API schema.
func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.Device) (errs field.ErrorList) {
@ -215,7 +243,21 @@ func Validate_Device(ctx context.Context, op operation.Operation, fldPath *field
}))...)
// field resourcev1beta2.Device.Capacity has no validation
// field resourcev1beta2.Device.ConsumesCounters has no validation
// field resourcev1beta2.Device.ConsumesCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.DeviceCounterConsumption) (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_DeviceCounterConsumption)...)
return
}(fldPath.Child("consumesCounters"), obj.ConsumesCounters, safe.Field(oldObj, func(oldObj *resourcev1beta2.Device) []resourcev1beta2.DeviceCounterConsumption {
return oldObj.ConsumesCounters
}))...)
// field resourcev1beta2.Device.NodeName has no validation
// field resourcev1beta2.Device.NodeSelector has no validation
// field resourcev1beta2.Device.AllNodes has no validation
@ -821,6 +863,36 @@ func Validate_DeviceConstraint(ctx context.Context, op operation.Operation, fldP
return errs
}
// Validate_DeviceCounterConsumption validates an instance of DeviceCounterConsumption according
// to declarative validation rules in the API schema.
func Validate_DeviceCounterConsumption(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *resourcev1beta2.DeviceCounterConsumption) (errs field.ErrorList) {
// field resourcev1beta2.DeviceCounterConsumption.CounterSet has no validation
// field resourcev1beta2.DeviceCounterConsumption.Counters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj map[string]resourcev1beta2.Counter) (errs field.ErrorList) {
// don't revalidate unchanged data
if op.Type == operation.Update && equality.Semantic.DeepEqual(obj, oldObj) {
return nil
}
// call field-attached validations
earlyReturn := false
if e := validate.RequiredMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
errs = append(errs, validate.EachMapKey(ctx, op, fldPath, obj, oldObj, validate.ShortName)...)
return
}(fldPath.Child("counters"), obj.Counters, safe.Field(oldObj, func(oldObj *resourcev1beta2.DeviceCounterConsumption) map[string]resourcev1beta2.Counter {
return oldObj.Counters
}))...)
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) {
@ -1649,6 +1721,20 @@ func Validate_ResourceSliceSpec(ctx context.Context, op operation.Operation, fld
}(fldPath.Child("devices"), obj.Devices, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceSliceSpec) []resourcev1beta2.Device { return oldObj.Devices }))...)
// field resourcev1beta2.ResourceSliceSpec.PerDeviceNodeSelection has no validation
// field resourcev1beta2.ResourceSliceSpec.SharedCounters has no validation
// field resourcev1beta2.ResourceSliceSpec.SharedCounters
errs = append(errs,
func(fldPath *field.Path, obj, oldObj []resourcev1beta2.CounterSet) (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_CounterSet)...)
return
}(fldPath.Child("sharedCounters"), obj.SharedCounters, safe.Field(oldObj, func(oldObj *resourcev1beta2.ResourceSliceSpec) []resourcev1beta2.CounterSet {
return oldObj.SharedCounters
}))...)
return errs
}

View file

@ -751,7 +751,7 @@ func validateCounterSet(counterSet resource.CounterSet, fldPath *field.Path) fie
} else {
// The size limit is enforced for across all sets by the caller.
allErrs = append(allErrs, validateMap(counterSet.Counters, -1, validation.DNS1123LabelMaxLength,
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
validateCounterName, validateDeviceCounter, fldPath.Child("counters"), keysCovered)...)
}
return allErrs
@ -876,7 +876,7 @@ func validateDeviceCounterConsumption(deviceCounterConsumption resource.DeviceCo
} else {
// The size limit is enforced for the entire device.
allErrs = append(allErrs, validateMap(deviceCounterConsumption.Counters, -1, validation.DNS1123LabelMaxLength,
validateCounterName, validateDeviceCounter, fldPath.Child("counters"))...)
validateCounterName, validateDeviceCounter, fldPath.Child("counters"), keysCovered)...)
}
return allErrs
}
@ -1142,6 +1142,8 @@ const (
sizeCovered
// The uniqueness check is covered by declarative validation.
uniquenessCovered
// key validation is covered by declarative validation.
keysCovered
)
// validateSlice ensures that a slice does not exceed a certain maximum size
@ -1210,14 +1212,19 @@ func quantityKey(item apiresource.Quantity) string {
// small limit gets increased because it is okay to include more details.
// This is not used for validation of keys, which has to be done by
// the callback function.
func validateMap[K ~string, T any](m map[K]T, maxSize, truncateKeyLen int, validateKey func(K, *field.Path) field.ErrorList, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path) field.ErrorList {
func validateMap[K ~string, T any](m map[K]T, maxSize, truncateKeyLen int, validateKey func(K, *field.Path) field.ErrorList, validateItem func(T, *field.Path) field.ErrorList, fldPath *field.Path, opts ...validationOption) field.ErrorList {
var allErrs field.ErrorList
if maxSize >= 0 && len(m) > maxSize {
allErrs = append(allErrs, field.TooMany(fldPath, len(m), maxSize))
}
for key, item := range m {
keyPath := fldPath.Key(truncateIfTooLong(string(key), truncateKeyLen))
allErrs = append(allErrs, validateKey(key, keyPath)...)
keyValidationErrors := validateKey(key, fldPath)
if slices.Contains(opts, keysCovered) {
keyValidationErrors = keyValidationErrors.MarkCoveredByDeclarative()
}
allErrs = append(allErrs, keyValidationErrors...)
allErrs = append(allErrs, validateItem(item, keyPath)...)
}
return allErrs

View file

@ -335,7 +335,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute": {
wantFailures: field.ErrorList{
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), badName, "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(badName), "", "exactly one value must be specified").MarkCoveredByDeclarative(),
field.Invalid(field.NewPath("spec", "devices").Index(2).Child("attributes").Key(goodName), resourceapi.DeviceAttribute{StringValue: ptr.To("x"), VersionValue: ptr.To("1.2.3")}, "exactly one value must be specified").MarkCoveredByDeclarative(),
field.Invalid(field.NewPath("spec", "devices").Index(3).Child("attributes").Key(goodName).Child("version"), strings.Repeat("x", resourceapi.DeviceAttributeMaxValueLength+1), "must be a string compatible with semver.org spec 2.0.0"),
@ -371,8 +371,8 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-c-identifier": {
wantFailures: field.ErrorList{
field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat(".", resourceapi.DeviceMaxIDLength+1)), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat(".", resourceapi.DeviceMaxIDLength+1), "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
@ -384,8 +384,8 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-domain": {
wantFailures: field.ErrorList{
field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes").Key(strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1)+"/y"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
field.Invalid(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("_", resourceapi.DeviceMaxDomainLength+1), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
@ -397,8 +397,8 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-key-too-long": {
wantFailures: field.ErrorList{
field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
field.TooLong(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("x", resourceapi.DeviceMaxDomainLength+1), resourceapi.DeviceMaxDomainLength),
field.TooLongMaxLength(field.NewPath("spec", "devices").Index(1).Child("attributes"), strings.Repeat("y", resourceapi.DeviceMaxIDLength+1), resourceapi.DeviceMaxIDLength),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
@ -410,8 +410,8 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-attribute-empty-domain-and-c-identifier": {
wantFailures: field.ErrorList{
field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "the domain must not be empty"),
field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes").Key("/"), "the name must not be empty"),
field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes"), "the domain must not be empty"),
field.Required(field.NewPath("spec", "devices").Index(1).Child("attributes"), "the name must not be empty"),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, goodName, 2)
@ -663,7 +663,7 @@ func TestValidateResourceSlice(t *testing.T) {
},
"bad-countername-shared-counters": {
wantFailures: field.ErrorList{
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters").Key(badName), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')").MarkCoveredByDeclarative(),
},
slice: func() *resourceapi.ResourceSlice {
slice := testResourceSlice(goodName, goodName, driverName, 1)

View file

@ -122,6 +122,27 @@ func TestDeclarativeValidate(t *testing.T) {
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", ""),
},
},
// spec.sharedCounters.counters
"invalid: shared counter key with uppercase": {
input: mkResourceSlice(tweakSharedCounter("InvalidKey")),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
},
},
"valid: shared counter key": {
input: mkResourceSlice(tweakSharedCounter("valid-key")),
},
// spec.devices.consumesCounters.counters
"invalid: device counter key with uppercase": {
input: mkResourceSlice(tweakSharedCounter("InvalidKey"), tweakDeviceCounter("InvalidKey")),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
},
},
"valid: device counter key": {
input: mkResourceSlice(tweakSharedCounter("valid-key"), tweakDeviceCounter("valid-key")),
},
// TODO: Add more test cases
}
@ -212,6 +233,23 @@ func TestDeclarativeValidateUpdate(t *testing.T) {
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("attributes").Key("test.io/multiple"), "", "may have only one of the following fields set: bool, int, string, version"),
},
},
// spec.sharedCounters.counters
"invalid update: shared counter key with uppercase": {
old: mkResourceSlice(),
update: mkResourceSlice(tweakSharedCounter("InvalidKey")),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
},
},
// spec.devices.consumesCounters.counters
"invalid update: device counter key with uppercase": {
old: mkResourceSlice(),
update: mkResourceSlice(tweakSharedCounter("InvalidKey"), tweakDeviceCounter("InvalidKey")),
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "sharedCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
field.Invalid(field.NewPath("spec", "devices").Index(0).Child("consumesCounters").Index(0).Child("counters"), "InvalidKey", "").WithOrigin("format=k8s-short-name"),
},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
@ -294,3 +332,29 @@ func tweakDeviceAttribute(name resource.QualifiedName, value resource.DeviceAttr
rs.Spec.Devices[0].Attributes[name] = value
}
}
func tweakSharedCounter(key string) func(*resource.ResourceSlice) {
return func(rs *resource.ResourceSlice) {
rs.Spec.SharedCounters = []resource.CounterSet{
{
Name: "shared-counter-set",
Counters: map[string]resource.Counter{
key: {},
},
},
}
}
}
func tweakDeviceCounter(key string) func(*resource.ResourceSlice) {
return func(rs *resource.ResourceSlice) {
rs.Spec.Devices[0].ConsumesCounters = []resource.DeviceCounterConsumption{
{
CounterSet: "shared-counter-set",
Counters: map[string]resource.Counter{
key: {},
},
},
}
}
}

View file

@ -315,6 +315,8 @@ message CounterSet {
// The maximum number of counters in all sets is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}
@ -779,6 +781,8 @@ message DeviceCounterConsumption {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}

View file

@ -202,6 +202,8 @@ type CounterSet struct {
// The maximum number of counters in all sets is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"`
}
@ -417,6 +419,8 @@ type DeviceCounterConsumption struct {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
}

View file

@ -451,6 +451,8 @@ message CounterSet {
// The maximum number of counters is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}
@ -787,6 +789,8 @@ message DeviceCounterConsumption {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}

View file

@ -202,6 +202,8 @@ type CounterSet struct {
// The maximum number of counters is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"`
}
@ -429,6 +431,8 @@ type DeviceCounterConsumption struct {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
}

View file

@ -315,6 +315,8 @@ message CounterSet {
// The maximum number of counters in all sets is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}
@ -779,6 +781,8 @@ message DeviceCounterConsumption {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
map<string, Counter> counters = 2;
}

View file

@ -202,6 +202,8 @@ type CounterSet struct {
// The maximum number of counters in all sets is 32.
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,name=counters"`
}
@ -417,6 +419,8 @@ type DeviceCounterConsumption struct {
// 16 counters each).
//
// +required
// +k8s:required
// +k8s:eachKey=+k8s:format=k8s-short-name
Counters map[string]Counter `json:"counters,omitempty" protobuf:"bytes,2,opt,name=counters"`
}

View file

@ -269,7 +269,7 @@ func (ektv eachKeyTagValidator) GetValidations(context Context, tag codetags.Tag
elemContext := Context{
Scope: ScopeMapKey,
Type: nt.Elem,
Type: nt.Key,
Path: context.Path.Key("(keys)"),
Member: nil, // NA for map keys
ParentPath: context.Path,