diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index ad99c88b6d7..96e2abfec2b 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -30,7 +30,6 @@ import ( v1 "k8s.io/api/core/v1" genericfeatures "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/quota/v1/generic" - utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/discovery" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/metadata" @@ -42,7 +41,6 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/cmd/kube-controller-manager/names" pkgcontroller "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/controller/devicetainteviction" endpointcontroller "k8s.io/kubernetes/pkg/controller/endpoint" "k8s.io/kubernetes/pkg/controller/garbagecollector" namespacecontroller "k8s.io/kubernetes/pkg/controller/namespace" @@ -52,8 +50,6 @@ import ( lifecyclecontroller "k8s.io/kubernetes/pkg/controller/nodelifecycle" "k8s.io/kubernetes/pkg/controller/podgc" replicationcontroller "k8s.io/kubernetes/pkg/controller/replication" - "k8s.io/kubernetes/pkg/controller/resourceclaim" - "k8s.io/kubernetes/pkg/controller/resourcepoolstatusrequest" resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" "k8s.io/kubernetes/pkg/controller/storageversiongc" @@ -249,41 +245,6 @@ func newTaintEvictionController(ctx context.Context, controllerContext Controlle return newControllerLoop(tec.Run, controllerName), nil } -func newDeviceTaintEvictionControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.DeviceTaintEvictionController, - constructor: newDeviceTaintEvictionController, - requiredFeatureGates: []featuregate.Feature{ - // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing these feature gates. - features.DynamicResourceAllocation, - features.DRADeviceTaints, - }, - } -} - -func newDeviceTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { - client, err := controllerContext.NewClient(names.DeviceTaintEvictionController) - if err != nil { - return nil, err - } - - deviceTaintEvictionController := devicetainteviction.New( - client, - controllerContext.InformerFactory.Core().V1().Pods(), - controllerContext.InformerFactory.Resource().V1().ResourceClaims(), - controllerContext.InformerFactory.Resource().V1().ResourceSlices(), - controllerContext.InformerFactory.Resource().V1beta2().DeviceTaintRules(), - controllerContext.InformerFactory.Resource().V1().DeviceClasses(), - controllerName, - ) - return newControllerLoop(func(ctx context.Context) { - if err := deviceTaintEvictionController.Run(ctx, int(controllerContext.ComponentConfig.DeviceTaintEvictionController.ConcurrentSyncs)); err != nil { - klog.FromContext(ctx).Error(err, "Device taint processing leading to Pod eviction failed and is now paused") - } - <-ctx.Done() - }, controllerName), nil -} - func newCloudNodeLifecycleControllerDescriptor() *ControllerDescriptor { return &ControllerDescriptor{ name: cpnames.CloudNodeLifecycleController, @@ -461,72 +422,6 @@ func newEphemeralVolumeController(ctx context.Context, controllerContext Control }, controllerName), nil } -func newResourceClaimControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.ResourceClaimController, - aliases: []string{"resource-claim-controller"}, - constructor: newResourceClaimController, - requiredFeatureGates: []featuregate.Feature{ - features.DynamicResourceAllocation, // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing this feature - }, - } -} - -func newResourceClaimController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { - client, err := controllerContext.NewClient("resource-claim-controller") - if err != nil { - return nil, err - } - - ephemeralController, err := resourceclaim.NewController( - klog.FromContext(ctx), - resourceclaim.Features{ - AdminAccess: utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), - PrioritizedList: utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), - }, - client, - controllerContext.InformerFactory.Core().V1().Pods(), - controllerContext.InformerFactory.Resource().V1().ResourceClaims(), - controllerContext.InformerFactory.Resource().V1().ResourceClaimTemplates()) - if err != nil { - return nil, fmt.Errorf("failed to init resource claim controller: %w", err) - } - - return newControllerLoop(func(ctx context.Context) { - ephemeralController.Run(ctx, int(controllerContext.ComponentConfig.ResourceClaimController.ConcurrentSyncs)) - }, controllerName), nil -} - -func newResourcePoolStatusRequestControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.ResourcePoolStatusRequestController, - constructor: newResourcePoolStatusRequestController, - requiredFeatureGates: []featuregate.Feature{ - features.DRAResourcePoolStatus, - }, - } -} - -func newResourcePoolStatusRequestController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { - client, err := controllerContext.NewClient("resourcepoolstatusrequest-controller") - if err != nil { - return nil, err - } - - controller, err := resourcepoolstatusrequest.NewController( - ctx, - client, - controllerContext.InformerFactory, - ) - if err != nil { - return nil, fmt.Errorf("failed to init resourcepoolstatusrequest controller: %w", err) - } - - return newControllerLoop(func(ctx context.Context) { - controller.Run(ctx, 1) // Single worker is sufficient for this controller - }, controllerName), nil -} - func newEndpointsControllerDescriptor() *ControllerDescriptor { return &ControllerDescriptor{ name: names.EndpointsController, diff --git a/cmd/kube-controller-manager/app/resource.go b/cmd/kube-controller-manager/app/resource.go new file mode 100644 index 00000000000..15c96520c39 --- /dev/null +++ b/cmd/kube-controller-manager/app/resource.go @@ -0,0 +1,134 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package app + +import ( + "context" + "fmt" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" + "k8s.io/klog/v2" + "k8s.io/kubernetes/cmd/kube-controller-manager/names" + "k8s.io/kubernetes/pkg/controller/devicetainteviction" + "k8s.io/kubernetes/pkg/controller/resourceclaim" + "k8s.io/kubernetes/pkg/controller/resourcepoolstatusrequest" + "k8s.io/kubernetes/pkg/features" +) + +func newDeviceTaintEvictionControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.DeviceTaintEvictionController, + constructor: newDeviceTaintEvictionController, + requiredFeatureGates: []featuregate.Feature{ + // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing these feature gates. + features.DynamicResourceAllocation, + features.DRADeviceTaints, + }, + } +} + +func newDeviceTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient(names.DeviceTaintEvictionController) + if err != nil { + return nil, err + } + + deviceTaintEvictionController := devicetainteviction.New( + client, + controllerContext.InformerFactory.Core().V1().Pods(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + controllerContext.InformerFactory.Resource().V1().ResourceSlices(), + controllerContext.InformerFactory.Resource().V1beta2().DeviceTaintRules(), + controllerContext.InformerFactory.Resource().V1().DeviceClasses(), + controllerName, + ) + return newControllerLoop(func(ctx context.Context) { + if err := deviceTaintEvictionController.Run(ctx, int(controllerContext.ComponentConfig.DeviceTaintEvictionController.ConcurrentSyncs)); err != nil { + klog.FromContext(ctx).Error(err, "Device taint processing leading to Pod eviction failed and is now paused") + } + <-ctx.Done() + }, controllerName), nil +} + +func newResourceClaimControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.ResourceClaimController, + aliases: []string{"resource-claim-controller"}, + constructor: newResourceClaimController, + requiredFeatureGates: []featuregate.Feature{ + features.DynamicResourceAllocation, // TODO update app.TestFeatureGatedControllersShouldNotDefineAliases when removing this feature + }, + } +} + +func newResourceClaimController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient("resource-claim-controller") + if err != nil { + return nil, err + } + + ephemeralController, err := resourceclaim.NewController( + klog.FromContext(ctx), + resourceclaim.Features{ + AdminAccess: utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess), + PrioritizedList: utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList), + }, + client, + controllerContext.InformerFactory.Core().V1().Pods(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + controllerContext.InformerFactory.Resource().V1().ResourceClaimTemplates()) + if err != nil { + return nil, fmt.Errorf("failed to init resource claim controller: %w", err) + } + + return newControllerLoop(func(ctx context.Context) { + ephemeralController.Run(ctx, int(controllerContext.ComponentConfig.ResourceClaimController.ConcurrentSyncs)) + }, controllerName), nil +} + +func newResourcePoolStatusRequestControllerDescriptor() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.ResourcePoolStatusRequestController, + constructor: newResourcePoolStatusRequestController, + requiredFeatureGates: []featuregate.Feature{ + features.DRAResourcePoolStatus, + }, + } +} + +func newResourcePoolStatusRequestController(ctx context.Context, controllerContext ControllerContext, controllerName string) (Controller, error) { + client, err := controllerContext.NewClient("resourcepoolstatusrequest-controller") + if err != nil { + return nil, err + } + + controller, err := resourcepoolstatusrequest.NewController( + ctx, + client, + controllerContext.InformerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + controllerContext.InformerFactory.Resource().V1().ResourceSlices(), + controllerContext.InformerFactory.Resource().V1().ResourceClaims(), + ) + if err != nil { + return nil, fmt.Errorf("failed to init resourcepoolstatusrequest controller: %w", err) + } + + return newControllerLoop(func(ctx context.Context) { + controller.Run(ctx, 1) // Single worker is sufficient for this controller + }, controllerName), nil +} diff --git a/pkg/controller/resourcepoolstatusrequest/controller.go b/pkg/controller/resourcepoolstatusrequest/controller.go index 51be74c39ca..752e991ccf2 100644 --- a/pkg/controller/resourcepoolstatusrequest/controller.go +++ b/pkg/controller/resourcepoolstatusrequest/controller.go @@ -28,7 +28,8 @@ import ( "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/informers" + resourcev1informers "k8s.io/client-go/informers/resource/v1" + resourcev1alpha3informers "k8s.io/client-go/informers/resource/v1alpha3" clientset "k8s.io/client-go/kubernetes" resourcev1listers "k8s.io/client-go/listers/resource/v1" resourcev1alpha3listers "k8s.io/client-go/listers/resource/v1alpha3" @@ -87,14 +88,12 @@ type Controller struct { func NewController( ctx context.Context, client clientset.Interface, - informerFactory informers.SharedInformerFactory, + requestInformer resourcev1alpha3informers.ResourcePoolStatusRequestInformer, + sliceInformer resourcev1informers.ResourceSliceInformer, + claimInformer resourcev1informers.ResourceClaimInformer, ) (*Controller, error) { logger := klog.FromContext(ctx) - requestInformer := informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests() - sliceInformer := informerFactory.Resource().V1().ResourceSlices() - claimInformer := informerFactory.Resource().V1().ResourceClaims() - c := &Controller{ client: client, requestLister: requestInformer.Lister(), @@ -115,10 +114,7 @@ func NewController( c.enqueueRequest(logger, obj) }, UpdateFunc: func(old, new interface{}) { - newReq := new.(*resourcev1alpha3.ResourcePoolStatusRequest) - if newReq.Status == nil { - c.enqueueRequest(logger, new) - } + c.enqueueRequest(logger, new) }, }) if err != nil { @@ -235,7 +231,7 @@ func (c *Controller) syncRequest(ctx context.Context, key string) error { break } } - if hasIncomplete && c.workqueue.NumRequeues(key) < maxRetries { + if hasIncomplete { return fmt.Errorf("incomplete pools detected, requeueing") } diff --git a/pkg/controller/resourcepoolstatusrequest/controller_test.go b/pkg/controller/resourcepoolstatusrequest/controller_test.go index a781efe85ed..4524b4c6403 100644 --- a/pkg/controller/resourcepoolstatusrequest/controller_test.go +++ b/pkg/controller/resourcepoolstatusrequest/controller_test.go @@ -332,7 +332,11 @@ func TestCalculatePoolStatus(t *testing.T) { informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) // Create controller - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } @@ -456,7 +460,11 @@ func TestSyncRequest(t *testing.T) { informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) // Create controller - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } @@ -507,7 +515,11 @@ func TestSyncRequestRequeuesIncompletePool(t *testing.T) { fakeClient := fake.NewClientset(request) informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } @@ -525,7 +537,8 @@ func TestSyncRequestRequeuesIncompletePool(t *testing.T) { t.Fatalf("Failed to add slice to informer: %v", err) } - // First sync should return error to trigger requeue (NumRequeues=0 < maxRetries) + // syncRequest should always return an error for incomplete pools, + // letting processNextWorkItem handle retry counting and drop logic. err = controller.syncRequest(ctx, "test-request") if err == nil { t.Fatal("Expected syncRequest to return error for incomplete pool requeue, got nil") @@ -538,31 +551,18 @@ func TestSyncRequestRequeuesIncompletePool(t *testing.T) { } } - // Simulate exhausted retries by adding the key to the workqueue enough times + // Even after retries are exhausted, syncRequest still returns an error; + // it is processNextWorkItem that decides to drop the key. for range maxRetries { controller.workqueue.AddRateLimited("test-request") - // Get and Done to process each add key, _ := controller.workqueue.Get() controller.workqueue.Done(key) } - // Now NumRequeues >= maxRetries, syncRequest should succeed and set status fakeClient.ClearActions() err = controller.syncRequest(ctx, "test-request") - if err != nil { - t.Fatalf("Expected syncRequest to succeed after retries exhausted, got: %v", err) - } - - // Verify status was updated with incomplete pools having ValidationError - var foundUpdate bool - for _, action := range fakeClient.Actions() { - if action.GetVerb() == "update" && action.GetSubresource() == "status" { - foundUpdate = true - break - } - } - if !foundUpdate { - t.Error("Expected status update after retries exhausted") + if err == nil { + t.Fatal("Expected syncRequest to still return error for incomplete pools after retries exhausted") } } @@ -593,7 +593,11 @@ func TestSkipProcessedRequest(t *testing.T) { fakeClient := fake.NewClientset(request) informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } @@ -729,7 +733,11 @@ func TestShouldDeleteRequest(t *testing.T) { fakeClient := fake.NewClientset() informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } @@ -880,7 +888,11 @@ func TestCleanupExpiredRequests(t *testing.T) { fakeClient := fake.NewClientset(expiredRequest, activeRequest) informerFactory := informers.NewSharedInformerFactory(fakeClient, 0) - controller, err := NewController(ctx, fakeClient, informerFactory) + controller, err := NewController(ctx, fakeClient, + informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + informerFactory.Resource().V1().ResourceSlices(), + informerFactory.Resource().V1().ResourceClaims(), + ) if err != nil { t.Fatalf("Failed to create controller: %v", err) } diff --git a/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go b/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go index a47e27ca1f4..5bba13ba2a1 100644 --- a/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go +++ b/pkg/controller/resourcepoolstatusrequest/metrics/metrics_test.go @@ -67,52 +67,36 @@ func TestRequestProcessingDuration(t *testing.T) { RequestProcessingDuration.WithLabelValues("driver-a.example.com").Observe(0.5) RequestProcessingDuration.WithLabelValues("driver-b.example.com").Observe(1.0) - // Validate metric metadata and label structure; histogram bucket values - // are deterministic given the observations above. + // Use gatherWithoutDurations to strip timing-dependent histogram fields + // (SampleSum and Bucket), verifying only _count lines. want := `# HELP resourcepoolstatusrequest_controller_request_processing_duration_seconds [ALPHA] Time taken to process a ResourcePoolStatusRequest # TYPE resourcepoolstatusrequest_controller_request_processing_duration_seconds histogram -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.001"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.002"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.004"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.008"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.016"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.032"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.064"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.128"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.256"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="0.512"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="1.024"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="2.048"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="4.096"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="8.192"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="16.384"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-a.example.com",le="+Inf"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_sum{driver_name="driver-a.example.com"} 0.5 resourcepoolstatusrequest_controller_request_processing_duration_seconds_count{driver_name="driver-a.example.com"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.001"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.002"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.004"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.008"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.016"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.032"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.064"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.128"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.256"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="0.512"} 0 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="1.024"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="2.048"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="4.096"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="8.192"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="16.384"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_bucket{driver_name="driver-b.example.com",le="+Inf"} 1 -resourcepoolstatusrequest_controller_request_processing_duration_seconds_sum{driver_name="driver-b.example.com"} 1 resourcepoolstatusrequest_controller_request_processing_duration_seconds_count{driver_name="driver-b.example.com"} 1 ` - if err := testutil.GatherAndCompare(registry, strings.NewReader(want), "resourcepoolstatusrequest_controller_request_processing_duration_seconds"); err != nil { + if err := testutil.GatherAndCompare(gatherWithoutDurations(registry), strings.NewReader(want), "resourcepoolstatusrequest_controller_request_processing_duration_seconds"); err != nil { t.Errorf("unexpected metric output: %v", err) } } +// gatherWithoutDurations wraps a registry and strips timing-dependent fields +// (SampleSum and Bucket) from histograms so that tests only verify _count. +func gatherWithoutDurations(registry metrics.KubeRegistry) testutil.GathererFunc { + return func() ([]*testutil.MetricFamily, error) { + got, err := registry.Gather() + for _, mf := range got { + for _, m := range mf.Metric { + if m.Histogram == nil { + continue + } + m.Histogram.SampleSum = nil + m.Histogram.Bucket = nil + } + } + return got, err + } +} + func TestRegister(t *testing.T) { // Verify Register does not panic when called multiple times. Register() diff --git a/test/integration/dra/resourcepoolstatusrequest_test.go b/test/integration/dra/resourcepoolstatusrequest_test.go index 661e2f4e17f..2c66ae0f41c 100644 --- a/test/integration/dra/resourcepoolstatusrequest_test.go +++ b/test/integration/dra/resourcepoolstatusrequest_test.go @@ -132,7 +132,9 @@ func (c *resourcePoolStatusRequestControllerSingleton) start(tCtx ktesting.TCont controller, err := resourcepoolstatusrequest.NewController( controllerCtx, client, - c.informerFactory, + c.informerFactory.Resource().V1alpha3().ResourcePoolStatusRequests(), + c.informerFactory.Resource().V1().ResourceSlices(), + c.informerFactory.Resource().V1().ResourceClaims(), ) tCtx.ExpectNoError(err, "create ResourcePoolStatusRequest controller")