From b613ed56a2045c8d9e1dbe2d2b63bbdf55cd2b3b Mon Sep 17 00:00:00 2001 From: Shingo Omura Date: Fri, 20 Mar 2026 18:35:34 +0900 Subject: [PATCH] KEP-5491: change ResourceSliceMaxDevicesWithTaintsOrConsumesCounters to ResourceSliceMaxDevicesWithAdvancedFeatures and add testcases with max devices with list attributes --- pkg/apis/resource/types.go | 9 ++- pkg/apis/resource/validation/validation.go | 22 ++++- .../validation_resourceslice_test.go | 80 +++++++++++++++++-- staging/src/k8s.io/api/resource/v1/types.go | 9 ++- .../src/k8s.io/api/resource/v1beta1/types.go | 9 ++- .../src/k8s.io/api/resource/v1beta2/types.go | 9 ++- test/integration/dra/objects.go | 4 +- 7 files changed, 128 insertions(+), 14 deletions(-) diff --git a/pkg/apis/resource/types.go b/pkg/apis/resource/types.go index 1291493fcce..d5506c75316 100644 --- a/pkg/apis/resource/types.go +++ b/pkg/apis/resource/types.go @@ -254,7 +254,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 diff --git a/pkg/apis/resource/validation/validation.go b/pkg/apis/resource/validation/validation.go index 128c8f1fffe..050e7a422c1 100644 --- a/pkg/apis/resource/validation/validation.go +++ b/pkg/apis/resource/validation/validation.go @@ -711,8 +711,8 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat } maxDevices := resource.ResourceSliceMaxDevices - if haveDeviceTaints(spec) || haveConsumesCounters(spec) { - maxDevices = resource.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters + if haveDeviceTaints(spec) || haveConsumesCounters(spec) || haveListAttributes(spec) { + maxDevices = resource.ResourceSliceMaxDevicesWithAdvancedFeatures } allErrs = append(allErrs, validateSet(spec.Devices, maxDevices, func(device resource.Device, fldPath *field.Path) field.ErrorList { @@ -732,6 +732,24 @@ func validateResourceSliceSpec(spec, oldSpec *resource.ResourceSliceSpec, fldPat return allErrs } +func haveListAttributes(spec *resource.ResourceSliceSpec) bool { + if spec == nil { + return false + } + + for _, device := range spec.Devices { + for _, attribute := range device.Attributes { + if attribute.BoolValues != nil || + attribute.IntValues != nil || + attribute.StringValues != nil || + attribute.VersionValues != nil { + return true + } + } + } + return false +} + func haveDeviceTaints(spec *resource.ResourceSliceSpec) bool { if spec == nil { return false diff --git a/pkg/apis/resource/validation/validation_resourceslice_test.go b/pkg/apis/resource/validation/validation_resourceslice_test.go index 557ad4bbfd0..81d3e40f36b 100644 --- a/pkg/apis/resource/validation/validation_resourceslice_test.go +++ b/pkg/apis/resource/validation/validation_resourceslice_test.go @@ -197,7 +197,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "good-taints": { slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) for i := range slice.Spec.Devices { slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}} } @@ -205,9 +205,9 @@ func TestValidateResourceSlice(t *testing.T) { }(), }, "too-large-taints": { - wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)}, + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) for i := range slice.Spec.Devices { slice.Spec.Devices[i].Taints = []resourceapi.DeviceTaint{{Key: "example.com/taint", Effect: resourceapi.DeviceTaintEffectNoExecute}} } @@ -544,6 +544,74 @@ func TestValidateResourceSlice(t *testing.T) { return slice }(), }, + "max-devices-with-list-of-int-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["ints"] = resourceapi.DeviceAttribute{IntValues: []int64{1, 2, 3}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-int-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["ints"] = resourceapi.DeviceAttribute{IntValues: []int64{1, 2, 3}} + return slice + }(), + }, + "max-devices-with-list-of-bool-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["bools"] = resourceapi.DeviceAttribute{BoolValues: []bool{true, false, true}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-bool-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["bools"] = resourceapi.DeviceAttribute{BoolValues: []bool{true, false, true}} + return slice + }(), + }, + "max-devices-with-list-of-string-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["strings"] = resourceapi.DeviceAttribute{StringValues: []string{"a", "b", "c"}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-string-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["strings"] = resourceapi.DeviceAttribute{StringValues: []string{"a", "b", "c"}} + return slice + }(), + }, + "max-devices-with-list-of-version-attributes": { + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) + for i := range slice.Spec.Devices { + slice.Spec.Devices[i].Attributes["versions"] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0", "2.0.0", "3.0.0"}} + } + return slice + }(), + }, + "too-many-devices-with-list-of-version-attributes": { + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, + slice: func() *resourceapi.ResourceSlice { + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) + slice.Spec.Devices[0].Attributes["versions"] = resourceapi.DeviceAttribute{VersionValues: []string{"1.0.0", "2.0.0", "3.0.0"}} + return slice + }(), + }, "good-attribute-names": { slice: func() *resourceapi.ResourceSlice { slice := testResourceSlice(goodName, goodName, goodName, 2) @@ -1170,7 +1238,7 @@ func TestValidateResourceSlice(t *testing.T) { }, "max-number-of-devices-with-consumes-counters": { slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) for i := range slice.Spec.Devices { slice.Spec.Devices[i].ConsumesCounters = []resourceapi.DeviceCounterConsumption{ { @@ -1183,9 +1251,9 @@ func TestValidateResourceSlice(t *testing.T) { }(), }, "too-many-devices-with-consumes-counters": { - wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters)}, + wantFailures: field.ErrorList{field.TooMany(field.NewPath("spec", "devices"), resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures)}, slice: func() *resourceapi.ResourceSlice { - slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters+1) + slice := testResourceSlice(goodName, goodName, goodName, resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures+1) slice.Spec.Devices[0].ConsumesCounters = []resourceapi.DeviceCounterConsumption{ { CounterSet: "counterset-0", diff --git a/staging/src/k8s.io/api/resource/v1/types.go b/staging/src/k8s.io/api/resource/v1/types.go index ee50363d49b..1775777fbd1 100644 --- a/staging/src/k8s.io/api/resource/v1/types.go +++ b/staging/src/k8s.io/api/resource/v1/types.go @@ -265,7 +265,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 diff --git a/staging/src/k8s.io/api/resource/v1beta1/types.go b/staging/src/k8s.io/api/resource/v1beta1/types.go index dc98e451dd4..0d35ac9895e 100644 --- a/staging/src/k8s.io/api/resource/v1beta1/types.go +++ b/staging/src/k8s.io/api/resource/v1beta1/types.go @@ -276,7 +276,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 diff --git a/staging/src/k8s.io/api/resource/v1beta2/types.go b/staging/src/k8s.io/api/resource/v1beta2/types.go index 74bd98f084e..47a46355f0e 100644 --- a/staging/src/k8s.io/api/resource/v1beta2/types.go +++ b/staging/src/k8s.io/api/resource/v1beta2/types.go @@ -268,7 +268,14 @@ type ResourcePool struct { const ResourceSliceMaxSharedCapacity = 128 const ResourceSliceMaxDevices = 128 -const ResourceSliceMaxDevicesWithTaintsOrConsumesCounters = 64 + +// ResourceSliceMaxDevicesWithAdvancedFeatures defines the maximum number of devices in a ResourceSlice +// if any of those devices uses advanced features: +// - device taints (DRADeviceTaints feature gate) +// - consuming counters (DRAPartitionableDevices feature gate) +// - list attributes (DRAListTypeAttributes feature gate) +const ResourceSliceMaxDevicesWithAdvancedFeatures = 64 + const PoolNameMaxLength = validation.DNS1123SubdomainMaxLength // Same as for a single node name. const BindingConditionsMaxSize = 4 const BindingFailureConditionsMaxSize = 4 diff --git a/test/integration/dra/objects.go b/test/integration/dra/objects.go index 79706839ae2..69f1153f7df 100644 --- a/test/integration/dra/objects.go +++ b/test/integration/dra/objects.go @@ -43,7 +43,7 @@ func NewMaxResourceSlices() map[string]*resourceapi.ResourceSlice { } func newResourceSliceWithTaintsAndConsumesCounters() *resourceapi.ResourceSlice { - slice := newBasicResourceSlice(resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters) + slice := newBasicResourceSlice(resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) for i := range slice.Spec.Devices { for range resourceapi.DeviceTaintsMaxLength { slice.Spec.Devices[i].Taints = append(slice.Spec.Devices[i].Taints, @@ -124,7 +124,7 @@ func newSharedCountersResourceSlice() *resourceapi.ResourceSlice { } func newResourceSliceWithListValues() *resourceapi.ResourceSlice { - slice := newBasicResourceSlice(64 /* resourceapi.ResourceSliceMaxDevicesWithTaintsOrConsumesCounters OrListValues */) + slice := newBasicResourceSlice(resourceapi.ResourceSliceMaxDevicesWithAdvancedFeatures) addListValues(slice) return slice }