From e4e45ed589520448e283e41315f7d8cc63b43126 Mon Sep 17 00:00:00 2001 From: Shingo Omura Date: Mon, 16 Mar 2026 19:05:04 +0900 Subject: [PATCH] KEP-5491: strategy(dropDisableFields) for list fields in DeviceAttribute --- .../resource/resourceslice/strategy.go | 45 +++++++++++++ .../resource/resourceslice/strategy_test.go | 65 +++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/pkg/registry/resource/resourceslice/strategy.go b/pkg/registry/resource/resourceslice/strategy.go index 12dc2567edb..9c51468246a 100644 --- a/pkg/registry/resource/resourceslice/strategy.go +++ b/pkg/registry/resource/resourceslice/strategy.go @@ -197,6 +197,7 @@ func dropDisabledFields(newSlice, oldSlice *resource.ResourceSlice) { dropDisabledDRADeviceBindingConditionsFields(newSlice, oldSlice) dropDisabledDRAConsumableCapacityFields(newSlice, oldSlice) dropDisabledDRANodeAllocatableResourcesFields(newSlice, oldSlice) + dropDisableDRAListTypeAttributesFields(newSlice, oldSlice) } func dropDisabledDRADeviceTaintsFields(newSlice, oldSlice *resource.ResourceSlice) { @@ -348,3 +349,47 @@ func draNodeAllocatableResourcesFeatureInUse(slice *resource.ResourceSlice) bool } return false } + +func dropDisableDRAListTypeAttributesFields(newSlice, oldSlice *resource.ResourceSlice) { + if utilfeature.DefaultFeatureGate.Enabled(features.DRAListTypeAttributes) || + draListTypeAttributesFeatureInUse(oldSlice) { + return + } + + for i := range newSlice.Spec.Devices { + for k, deviceAttribute := range newSlice.Spec.Devices[i].Attributes { + if deviceAttribute.BoolValues != nil { + deviceAttribute.BoolValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.IntValues != nil { + deviceAttribute.IntValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.StringValues != nil { + deviceAttribute.StringValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + if deviceAttribute.VersionValues != nil { + deviceAttribute.VersionValues = nil + newSlice.Spec.Devices[i].Attributes[k] = deviceAttribute + } + } + } +} + +func draListTypeAttributesFeatureInUse(slice *resource.ResourceSlice) bool { + if slice == nil { + return false + } + + for _, device := range slice.Spec.Devices { + for _, deviceAttribute := range device.Attributes { + if deviceAttribute.BoolValues != nil || deviceAttribute.IntValues != nil || deviceAttribute.StringValues != nil || deviceAttribute.VersionValues != nil { + return true + } + } + } + + return false +} diff --git a/pkg/registry/resource/resourceslice/strategy_test.go b/pkg/registry/resource/resourceslice/strategy_test.go index 28fc56c2773..2eb24743ec8 100644 --- a/pkg/registry/resource/resourceslice/strategy_test.go +++ b/pkg/registry/resource/resourceslice/strategy_test.go @@ -199,6 +199,16 @@ var sliceWithNodeAllocatableResources = func() *resource.ResourceSlice { return obj }() +var sliceWithListTypeAttributes = func() *resource.ResourceSlice { + obj := slice.DeepCopy() + obj.Spec.Devices[0].Attributes = map[resource.QualifiedName]resource.DeviceAttribute{ + resource.QualifiedName("list_attribute"): { + StringValues: []string{"value1", "value2"}, + }, + } + return obj +}() + func TestResourceSliceStrategy(t *testing.T) { if Strategy.NamespaceScoped() { t.Errorf("ResourceSlice must not be namespace scoped") @@ -218,6 +228,7 @@ func TestResourceSliceStrategyCreate(t *testing.T) { deviceStatus bool consumableCapacity bool draNodeAllocatableResources bool + listTypeAttributes bool expectedValidationError bool expectObj *resource.ResourceSlice }{ @@ -382,6 +393,20 @@ func TestResourceSliceStrategyCreate(t *testing.T) { return obj }(), }, + "keep-fields-list-type-attributes": { + obj: sliceWithListTypeAttributes, + listTypeAttributes: true, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.Generation = 1 + return obj + }(), + }, + "drop-fields-list-type-attributes": { + obj: sliceWithListTypeAttributes, + listTypeAttributes: false, + expectedValidationError: true, + }, } for name, tc := range testCases { @@ -393,6 +418,7 @@ func TestResourceSliceStrategyCreate(t *testing.T) { features.DRAResourceClaimDeviceStatus: tc.deviceStatus, features.DRAConsumableCapacity: tc.consumableCapacity, features.DRANodeAllocatableResources: tc.draNodeAllocatableResources, + features.DRAListTypeAttributes: tc.listTypeAttributes, }) obj := tc.obj.DeepCopy() @@ -424,6 +450,7 @@ func TestResourceSliceStrategyUpdate(t *testing.T) { deviceStatus bool bindingConditions bool consumableCapacity bool + listTypeAttributes bool expectValidationError bool expectObj *resource.ResourceSlice }{ @@ -782,6 +809,44 @@ func TestResourceSliceStrategyUpdate(t *testing.T) { return obj }(), }, + "drop-list-type-attributes": { + oldObj: slice.DeepCopy(), + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: false, + expectValidationError: true, + }, + "keep-list-type-attributes": { + oldObj: sliceWithListTypeAttributes, + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: true, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + }, + "keep-existing-list-type-attributes-without-featuregate-enabled": { + oldObj: sliceWithListTypeAttributes, + newObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + listTypeAttributes: false, + expectObj: func() *resource.ResourceSlice { + obj := sliceWithListTypeAttributes.DeepCopy() + obj.ResourceVersion = "4" + return obj + }(), + }, } for name, tc := range testcases {