From 7115527077e58a4ebdb03dc7420922140e303299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Skocze=C5=84?= Date: Mon, 30 Dec 2024 12:43:10 +0000 Subject: [PATCH 1/3] Allow to prefix provisioningClassName to filter provisioning requests --- .../config/autoscaling_options.go | 4 + cluster-autoscaler/main.go | 6 +- .../processors/provreq/injector.go | 77 +++++----- .../processors/provreq/injector_test.go | 33 +++- .../processors/provreq/processor.go | 17 +- .../processors/provreq/processor_test.go | 4 +- .../checkcapacity/provisioningclass.go | 2 +- .../conditions/condition_test.go | 145 +++++++++++++++--- .../conditions/conditions.go | 6 +- .../provisioningrequest/supported_classes.go | 21 ++- 10 files changed, 229 insertions(+), 86 deletions(-) diff --git a/cluster-autoscaler/config/autoscaling_options.go b/cluster-autoscaler/config/autoscaling_options.go index 5f32de514af7..4b626b31b587 100644 --- a/cluster-autoscaler/config/autoscaling_options.go +++ b/cluster-autoscaler/config/autoscaling_options.go @@ -313,6 +313,10 @@ type AutoscalingOptions struct { DynamicResourceAllocationEnabled bool // ClusterSnapshotParallelism is the maximum parallelism of cluster snapshot creation. ClusterSnapshotParallelism int + // CheckCapacityProvisioningClassPrefix is the prefix of provisioningClassName that will be filtered by processors. + // Only ProvisioningRequests with this prefix in their class will be processed by this CA. + // It only refers to check capacity ProvisioningRequests. + CheckCapacityProvisioningClassPrefix string } // KubeClientOptions specify options for kube client diff --git a/cluster-autoscaler/main.go b/cluster-autoscaler/main.go index 1aa36c3b215e..321cd95bca37 100644 --- a/cluster-autoscaler/main.go +++ b/cluster-autoscaler/main.go @@ -283,6 +283,7 @@ var ( forceDeleteLongUnregisteredNodes = flag.Bool("force-delete-unregistered-nodes", false, "Whether to enable force deletion of long unregistered nodes, regardless of the min size of the node group the belong to.") enableDynamicResourceAllocation = flag.Bool("enable-dynamic-resource-allocation", false, "Whether logic for handling DRA (Dynamic Resource Allocation) objects is enabled.") clusterSnapshotParallelism = flag.Int("cluster-snapshot-parallelism", 16, "Maximum parallelism of cluster snapshot creation.") + checkCapacityProvisioningClassPrefix = flag.String("check-capacity-provisioning-class-prefix", "", "Prefix of provisioningClassName that will be filtered by processors. Only ProvisioningRequests with this prefix in their class will be processed by this CA. It refers only to check capacity ProvisioningRequests.") ) func isFlagPassed(name string) bool { @@ -464,6 +465,7 @@ func createAutoscalingOptions() config.AutoscalingOptions { ForceDeleteLongUnregisteredNodes: *forceDeleteLongUnregisteredNodes, DynamicResourceAllocationEnabled: *enableDynamicResourceAllocation, ClusterSnapshotParallelism: *clusterSnapshotParallelism, + CheckCapacityProvisioningClassPrefix: *checkCapacityProvisioningClassPrefix, } } @@ -539,7 +541,7 @@ func buildAutoscaler(context ctx.Context, debuggingSnapshotter debuggingsnapshot return nil, nil, err } - ProvisioningRequestInjector, err = provreq.NewProvisioningRequestPodsInjector(restConfig, opts.ProvisioningRequestInitialBackoffTime, opts.ProvisioningRequestMaxBackoffTime, opts.ProvisioningRequestMaxBackoffCacheSize, opts.CheckCapacityBatchProcessing) + ProvisioningRequestInjector, err = provreq.NewProvisioningRequestPodsInjector(restConfig, opts.ProvisioningRequestInitialBackoffTime, opts.ProvisioningRequestMaxBackoffTime, opts.ProvisioningRequestMaxBackoffCacheSize, opts.CheckCapacityBatchProcessing, opts.CheckCapacityProvisioningClassPrefix) if err != nil { return nil, nil, err } @@ -558,7 +560,7 @@ func buildAutoscaler(context ctx.Context, debuggingSnapshotter debuggingsnapshot scaleUpOrchestrator := provreqorchestrator.NewWrapperOrchestrator(provreqOrchestrator) opts.ScaleUpOrchestrator = scaleUpOrchestrator - provreqProcesor := provreq.NewProvReqProcessor(client) + provreqProcesor := provreq.NewProvReqProcessor(client, opts.CheckCapacityProvisioningClassPrefix) opts.LoopStartNotifier = loopstart.NewObserversList([]loopstart.Observer{provreqProcesor}) podListProcessor.AddProcessor(provreqProcesor) diff --git a/cluster-autoscaler/processors/provreq/injector.go b/cluster-autoscaler/processors/provreq/injector.go index 5ac6203347c2..595bbf2eea5f 100644 --- a/cluster-autoscaler/processors/provreq/injector.go +++ b/cluster-autoscaler/processors/provreq/injector.go @@ -37,13 +37,14 @@ import ( // ProvisioningRequestPodsInjector creates in-memory pods from ProvisioningRequest and inject them to unscheduled pods list. type ProvisioningRequestPodsInjector struct { - initialRetryTime time.Duration - maxBackoffTime time.Duration - backoffDuration *lru.Cache - clock clock.PassiveClock - client *provreqclient.ProvisioningRequestClient - lastProvisioningRequestProcessTime time.Time - checkCapacityBatchProcessing bool + initialRetryTime time.Duration + maxBackoffTime time.Duration + backoffDuration *lru.Cache + clock clock.PassiveClock + client *provreqclient.ProvisioningRequestClient + lastProvisioningRequestProcessTime time.Time + checkCapacityBatchProcessing bool + checkCapacityProvisioningClassPrefix string } // IsAvailableForProvisioning checks if the provisioning request is the correct state for processing and provisioning has not been attempted recently. @@ -93,16 +94,24 @@ func (p *ProvisioningRequestPodsInjector) MarkAsFailed(pr *provreqwrapper.Provis p.UpdateLastProcessTime() } +func (p *ProvisioningRequestPodsInjector) isSupportedClass(pr *provreqwrapper.ProvisioningRequest) bool { + return provisioningrequest.SupportedProvisioningClass(pr.Spec.ProvisioningClassName, p.checkCapacityProvisioningClassPrefix) +} + +func (p *ProvisioningRequestPodsInjector) shouldMarkAsAccepted(pr *provreqwrapper.ProvisioningRequest) bool { + // Don't mark as accepted the check capacity ProvReq when batch processing is enabled. + // It will be marked later, in parallel, during processing the requests. + return !p.checkCapacityBatchProcessing || !p.matchesCheckCapacityClass(pr.Spec.ProvisioningClassName) +} + // GetPodsFromNextRequest picks one ProvisioningRequest meeting the condition passed using isSupportedClass function, marks it as accepted and returns pods from it. -func (p *ProvisioningRequestPodsInjector) GetPodsFromNextRequest( - isSupportedClass func(*provreqwrapper.ProvisioningRequest) bool, -) ([]*apiv1.Pod, error) { +func (p *ProvisioningRequestPodsInjector) GetPodsFromNextRequest() ([]*apiv1.Pod, error) { provReqs, err := p.client.ProvisioningRequests() if err != nil { return nil, err } for _, pr := range provReqs { - if !isSupportedClass(pr) { + if !p.isSupportedClass(pr) { continue } @@ -117,16 +126,13 @@ func (p *ProvisioningRequestPodsInjector) GetPodsFromNextRequest( p.MarkAsFailed(pr, provreqconditions.FailedToCreatePodsReason, err.Error()) continue } - // Don't mark as accepted the check capacity ProvReq when batch processing is enabled. - // It will be marked later, in parallel, during processing the requests. - if pr.Spec.ProvisioningClassName == v1.ProvisioningClassCheckCapacity && p.checkCapacityBatchProcessing { - p.UpdateLastProcessTime() + if p.shouldMarkAsAccepted(pr) { + if err := p.MarkAsAccepted(pr); err != nil { + continue + } return podsFromProvReq, nil } - if err := p.MarkAsAccepted(pr); err != nil { - continue - } - + p.UpdateLastProcessTime() return podsFromProvReq, nil } return nil, nil @@ -139,6 +145,10 @@ type ProvisioningRequestWithPods struct { Pods []*apiv1.Pod } +func (p *ProvisioningRequestPodsInjector) matchesCheckCapacityClass(provisioningClassName string) bool { + return provisioningClassName == p.checkCapacityProvisioningClassPrefix+v1.ProvisioningClassCheckCapacity +} + // GetCheckCapacityBatch returns up to the requested number of ProvisioningRequestWithPods. // We do not mark the PRs as accepted here. // If we fail to get the pods for a PR, we mark the PR as failed and issue an update. @@ -152,7 +162,7 @@ func (p *ProvisioningRequestPodsInjector) GetCheckCapacityBatch(maxPrs int) ([]P if len(prsWithPods) >= maxPrs { break } - if pr.Spec.ProvisioningClassName != v1.ProvisioningClassCheckCapacity { + if !p.matchesCheckCapacityClass(pr.Spec.ProvisioningClassName) { continue } if !p.IsAvailableForProvisioning(pr) { @@ -175,15 +185,7 @@ func (p *ProvisioningRequestPodsInjector) Process( _ *context.AutoscalingContext, unschedulablePods []*apiv1.Pod, ) ([]*apiv1.Pod, error) { - podsFromProvReq, err := p.GetPodsFromNextRequest( - func(pr *provreqwrapper.ProvisioningRequest) bool { - _, found := provisioningrequest.SupportedProvisioningClasses[pr.Spec.ProvisioningClassName] - if !found { - klog.Warningf("Provisioning Class %s is not supported for ProvReq %s/%s", pr.Spec.ProvisioningClassName, pr.Namespace, pr.Name) - } - return found - }) - + podsFromProvReq, err := p.GetPodsFromNextRequest() if err != nil { return unschedulablePods, err } @@ -195,19 +197,20 @@ func (p *ProvisioningRequestPodsInjector) Process( func (p *ProvisioningRequestPodsInjector) CleanUp() {} // NewProvisioningRequestPodsInjector creates a ProvisioningRequest filter processor. -func NewProvisioningRequestPodsInjector(kubeConfig *rest.Config, initialBackoffTime, maxBackoffTime time.Duration, maxCacheSize int, checkCapacityBatchProcessing bool) (*ProvisioningRequestPodsInjector, error) { +func NewProvisioningRequestPodsInjector(kubeConfig *rest.Config, initialBackoffTime, maxBackoffTime time.Duration, maxCacheSize int, checkCapacityBatchProcessing bool, checkCapacityProvisioningClassPrefix string) (*ProvisioningRequestPodsInjector, error) { client, err := provreqclient.NewProvisioningRequestClient(kubeConfig) if err != nil { return nil, err } return &ProvisioningRequestPodsInjector{ - initialRetryTime: initialBackoffTime, - maxBackoffTime: maxBackoffTime, - backoffDuration: lru.New(maxCacheSize), - client: client, - clock: clock.RealClock{}, - lastProvisioningRequestProcessTime: time.Now(), - checkCapacityBatchProcessing: checkCapacityBatchProcessing, + initialRetryTime: initialBackoffTime, + maxBackoffTime: maxBackoffTime, + backoffDuration: lru.New(maxCacheSize), + client: client, + clock: clock.RealClock{}, + lastProvisioningRequestProcessTime: time.Now(), + checkCapacityBatchProcessing: checkCapacityBatchProcessing, + checkCapacityProvisioningClassPrefix: checkCapacityProvisioningClassPrefix, }, nil } diff --git a/cluster-autoscaler/processors/provreq/injector_test.go b/cluster-autoscaler/processors/provreq/injector_test.go index 2e496ddfec25..685971fad507 100644 --- a/cluster-autoscaler/processors/provreq/injector_test.go +++ b/cluster-autoscaler/processors/provreq/injector_test.go @@ -69,6 +69,7 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { podsA := 10 newProvReqA := testProvisioningRequestWithCondition("new", podsA, v1.ProvisioningClassCheckCapacity) newAcceptedProvReqA := testProvisioningRequestWithCondition("new-accepted", podsA, v1.ProvisioningClassCheckCapacity, accepted) + newProvReqAPrefixed := testProvisioningRequestWithCondition("new-prefixed", podsA, "test-prefix.check-capacity.autoscaling.x-k8s.io") podsB := 20 notProvisionedAcceptedProvReqB := testProvisioningRequestWithCondition("provisioned-false-B", podsB, v1.ProvisioningClassBestEffortAtomicScaleUp, notProvisioned, accepted) @@ -79,12 +80,13 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { unknownClass := testProvisioningRequestWithCondition("new-accepted", podsA, "unknown-class", accepted) testCases := []struct { - name string - provReqs []*provreqwrapper.ProvisioningRequest - existingUnsUnschedulablePodCount int - checkCapacityBatchProcessing bool - wantUnscheduledPodCount int - wantUpdatedConditionName string + name string + provReqs []*provreqwrapper.ProvisioningRequest + existingUnsUnschedulablePodCount int + checkCapacityBatchProcessing bool + checkCapacityProvisioningClassPrefix string + wantUnscheduledPodCount int + wantUpdatedConditionName string }{ { name: "New ProvisioningRequest, pods are injected and Accepted condition is added", @@ -92,7 +94,6 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { wantUnscheduledPodCount: podsA, wantUpdatedConditionName: newProvReqA.Name, }, - { name: "New check capacity ProvisioningRequest with batch processing, pods are injected and Accepted condition is not added", provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB}, @@ -106,6 +107,22 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { wantUnscheduledPodCount: podsA, wantUpdatedConditionName: newAcceptedProvReqA.Name, }, + { + name: "New ProvisioningRequest with not matching custom prefix, no pods are injected", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed}, + }, + { + name: "New ProvisioningRequest with not matching prefix, no pods are injected", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB}, + checkCapacityProvisioningClassPrefix: "test-prefix.", + }, + { + name: "New check capacity ProvisioningRequest with matching prefix, pods are injected and Accepted condition is added", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed, provisionedAcceptedProvReqB}, + checkCapacityProvisioningClassPrefix: "test-prefix.", + wantUnscheduledPodCount: podsA, + wantUpdatedConditionName: newProvReqAPrefixed.Name, + }, { name: "Provisioned=False, pods are injected", provReqs: []*provreqwrapper.ProvisioningRequest{notProvisionedAcceptedProvReqB, failedProvReq}, @@ -140,7 +157,7 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { client := provreqclient.NewFakeProvisioningRequestClient(context.Background(), t, tc.provReqs...) backoffTime := lru.New(100) backoffTime.Add(key(notProvisionedRecentlyProvReqB), 2*time.Minute) - injector := ProvisioningRequestPodsInjector{1 * time.Minute, 10 * time.Minute, backoffTime, clock.NewFakePassiveClock(now), client, now, tc.checkCapacityBatchProcessing} + injector := ProvisioningRequestPodsInjector{1 * time.Minute, 10 * time.Minute, backoffTime, clock.NewFakePassiveClock(now), client, now, tc.checkCapacityBatchProcessing, tc.checkCapacityProvisioningClassPrefix} getUnscheduledPods, err := injector.Process(nil, provreqwrapper.BuildTestPods("ns", "pod", tc.existingUnsUnschedulablePodCount)) if err != nil { t.Errorf("%s failed: injector.Process return error %v", tc.name, err) diff --git a/cluster-autoscaler/processors/provreq/processor.go b/cluster-autoscaler/processors/provreq/processor.go index 1463b1e9f6db..49bf54ed59c3 100644 --- a/cluster-autoscaler/processors/provreq/processor.go +++ b/cluster-autoscaler/processors/provreq/processor.go @@ -50,15 +50,16 @@ type injector interface { } type provReqProcessor struct { - now func() time.Time - maxUpdated int - client *provreqclient.ProvisioningRequestClient - injector injector + now func() time.Time + maxUpdated int + client *provreqclient.ProvisioningRequestClient + injector injector + checkCapacityProvisioningClassPrefix string } // NewProvReqProcessor return ProvisioningRequestProcessor. -func NewProvReqProcessor(client *provreqclient.ProvisioningRequestClient) *provReqProcessor { - return &provReqProcessor{now: time.Now, maxUpdated: defaultMaxUpdated, client: client, injector: scheduling.NewHintingSimulator()} +func NewProvReqProcessor(client *provreqclient.ProvisioningRequestClient, checkCapacityProvisioningClassPrefix string) *provReqProcessor { + return &provReqProcessor{now: time.Now, maxUpdated: defaultMaxUpdated, client: client, injector: scheduling.NewHintingSimulator(), checkCapacityProvisioningClassPrefix: checkCapacityProvisioningClassPrefix} } // Refresh implements loop.Observer interface and will be run at the start @@ -84,7 +85,7 @@ func (p *provReqProcessor) refresh(provReqs []*provreqwrapper.ProvisioningReques if len(expiredProvReq) >= p.maxUpdated { break } - if ok, found := provisioningrequest.SupportedProvisioningClasses[provReq.Spec.ProvisioningClassName]; !ok || !found { + if !provisioningrequest.SupportedProvisioningClass(provReq.Spec.ProvisioningClassName, p.checkCapacityProvisioningClassPrefix) { continue } conditions := provReq.Status.Conditions @@ -144,7 +145,7 @@ func (p *provReqProcessor) bookCapacity(ctx *context.AutoscalingContext) error { } podsToCreate := []*apiv1.Pod{} for _, provReq := range provReqs { - if !conditions.ShouldCapacityBeBooked(provReq) { + if !conditions.ShouldCapacityBeBooked(provReq, p.checkCapacityProvisioningClassPrefix) { continue } pods, err := provreq_pods.PodsForProvisioningRequest(provReq) diff --git a/cluster-autoscaler/processors/provreq/processor_test.go b/cluster-autoscaler/processors/provreq/processor_test.go index 11daf0aa2590..35705931dfcb 100644 --- a/cluster-autoscaler/processors/provreq/processor_test.go +++ b/cluster-autoscaler/processors/provreq/processor_test.go @@ -155,7 +155,7 @@ func TestRefresh(t *testing.T) { additionalPr.CreationTimestamp = metav1.NewTime(weekAgo) additionalPr.Spec.ProvisioningClassName = v1.ProvisioningClassCheckCapacity - processor := provReqProcessor{func() time.Time { return now }, 1, provreqclient.NewFakeProvisioningRequestClient(nil, t, pr, additionalPr), nil} + processor := provReqProcessor{func() time.Time { return now }, 1, provreqclient.NewFakeProvisioningRequestClient(nil, t, pr, additionalPr), nil, ""} processor.refresh([]*provreqwrapper.ProvisioningRequest{pr, additionalPr}) assert.ElementsMatch(t, test.wantConditions, pr.Status.Conditions) @@ -215,7 +215,7 @@ func TestDeleteOldProvReqs(t *testing.T) { client := provreqclient.NewFakeProvisioningRequestClient(nil, t, pr, additionalPr, oldFailedPr, oldExpiredPr) - processor := provReqProcessor{func() time.Time { return now }, 1, client, nil} + processor := provReqProcessor{func() time.Time { return now }, 1, client, nil, ""} processor.refresh([]*provreqwrapper.ProvisioningRequest{pr, additionalPr, oldFailedPr, oldExpiredPr}) _, err := client.ProvisioningRequestNoCache(oldFailedPr.Namespace, oldFailedPr.Name) diff --git a/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go b/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go index 62fea869eb5b..3bd6d7e966bd 100644 --- a/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go +++ b/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go @@ -132,7 +132,7 @@ func (o *checkCapacityProvClass) getProvisioningRequestsAndPods(unschedulablePod if !o.isBatchEnabled() { klog.Info("Processing single provisioning request (non-batch)") prs := provreqclient.ProvisioningRequestsForPods(o.client, unschedulablePods) - prs = provreqclient.FilterOutProvisioningClass(prs, v1.ProvisioningClassCheckCapacity) + prs = provreqclient.FilterOutProvisioningClass(prs, o.context.CheckCapacityProvisioningClassPrefix+v1.ProvisioningClassCheckCapacity) if len(prs) == 0 { return nil, nil } diff --git a/cluster-autoscaler/provisioningrequest/conditions/condition_test.go b/cluster-autoscaler/provisioningrequest/conditions/condition_test.go index 081b6b78d8c7..322a69cbaa0b 100644 --- a/cluster-autoscaler/provisioningrequest/conditions/condition_test.go +++ b/cluster-autoscaler/provisioningrequest/conditions/condition_test.go @@ -20,19 +20,21 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" - "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest" + v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper" ) func TestBookCapacity(t *testing.T) { tests := []struct { - name string - prConditions []metav1.Condition - want bool + name string + provisioningClassName string + prConditions []metav1.Condition + checkCapacityProvisioningClassPrefix string + want bool }{ { - name: "BookingExpired", + name: "BookingExpired check capacity", + provisioningClassName: v1.ProvisioningClassCheckCapacity, prConditions: []metav1.Condition{ { Type: v1.Provisioned, @@ -46,7 +48,23 @@ func TestBookCapacity(t *testing.T) { want: false, }, { - name: "Failed", + name: "BookingExpired best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + { + Type: v1.BookingExpired, + Status: metav1.ConditionTrue, + }, + }, + want: false, + }, + { + name: "Failed check capacity", + provisioningClassName: v1.ProvisioningClassCheckCapacity, prConditions: []metav1.Condition{ { Type: v1.Provisioned, @@ -60,11 +78,48 @@ func TestBookCapacity(t *testing.T) { want: false, }, { - name: "empty conditions", + name: "Failed best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + { + Type: v1.Failed, + Status: metav1.ConditionTrue, + }, + }, want: false, }, { - name: "Capacity found and provisioned", + name: "empty conditions for check capacity", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + want: false, + }, + { + name: "empty conditions for best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + want: false, + }, + { + name: "Capacity found and provisioned check capacity", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + }, + want: true, + }, + { + name: "Capacity found and provisioned best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, prConditions: []metav1.Condition{ { Type: v1.Provisioned, @@ -78,7 +133,51 @@ func TestBookCapacity(t *testing.T) { want: true, }, { - name: "Capacity is not found", + name: "Capacity found and provisioned check capacity but prefix not matching", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + }, + checkCapacityProvisioningClassPrefix: "test-", + want: false, + }, + { + name: "Capacity found and provisioned best effort atomic and prefix is ignored", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + { + Type: v1.Provisioned, + Status: metav1.ConditionTrue, + }, + }, + checkCapacityProvisioningClassPrefix: "test-", + want: true, + }, + { + name: "Capacity is not found for check capacity", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + prConditions: []metav1.Condition{ + { + Type: v1.Provisioned, + Status: metav1.ConditionFalse, + }, + }, + want: false, + }, + { + name: "Capacity is not found for best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, prConditions: []metav1.Condition{ { Type: v1.Provisioned, @@ -90,20 +189,18 @@ func TestBookCapacity(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - for class := range provisioningrequest.SupportedProvisioningClasses { - pr := provreqwrapper.NewProvisioningRequest( - &v1.ProvisioningRequest{ - Spec: v1.ProvisioningRequestSpec{ - ProvisioningClassName: class, - }, - Status: v1.ProvisioningRequestStatus{ - Conditions: test.prConditions, - }, - }, nil) - got := ShouldCapacityBeBooked(pr) - if got != test.want { - t.Errorf("Want: %v, got: %v", test.want, got) - } + pr := provreqwrapper.NewProvisioningRequest( + &v1.ProvisioningRequest{ + Spec: v1.ProvisioningRequestSpec{ + ProvisioningClassName: test.provisioningClassName, + }, + Status: v1.ProvisioningRequestStatus{ + Conditions: test.prConditions, + }, + }, nil) + got := ShouldCapacityBeBooked(pr, test.checkCapacityProvisioningClassPrefix) + if got != test.want { + t.Errorf("Want: %v, got: %v", test.want, got) } }) } diff --git a/cluster-autoscaler/provisioningrequest/conditions/conditions.go b/cluster-autoscaler/provisioningrequest/conditions/conditions.go index 33a66d1478a5..efdd3d79014b 100644 --- a/cluster-autoscaler/provisioningrequest/conditions/conditions.go +++ b/cluster-autoscaler/provisioningrequest/conditions/conditions.go @@ -19,7 +19,7 @@ package conditions import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" + v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper" "k8s.io/klog/v2" @@ -59,8 +59,8 @@ const ( ) // ShouldCapacityBeBooked returns whether capacity should be booked. -func ShouldCapacityBeBooked(pr *provreqwrapper.ProvisioningRequest) bool { - if ok, found := provisioningrequest.SupportedProvisioningClasses[pr.Spec.ProvisioningClassName]; !ok || !found { +func ShouldCapacityBeBooked(pr *provreqwrapper.ProvisioningRequest, checkCapacityProvisioningClassPrefix string) bool { + if !provisioningrequest.SupportedProvisioningClass(pr.Spec.ProvisioningClassName, checkCapacityProvisioningClassPrefix) { return false } conditions := pr.Status.Conditions diff --git a/cluster-autoscaler/provisioningrequest/supported_classes.go b/cluster-autoscaler/provisioningrequest/supported_classes.go index 807e15bd7124..8c24320e6250 100644 --- a/cluster-autoscaler/provisioningrequest/supported_classes.go +++ b/cluster-autoscaler/provisioningrequest/supported_classes.go @@ -17,12 +17,31 @@ limitations under the License. package provisioningrequest import ( - "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" + "strings" + + v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" ) // SupportedProvisioningClasses is a set of ProvisioningRequest classes // supported by Cluster Autoscaler. +// This map is exported for testing purposes. +// Checking the support should be done using SupportedProvisioningClass. var SupportedProvisioningClasses = map[string]bool{ v1.ProvisioningClassCheckCapacity: true, v1.ProvisioningClassBestEffortAtomicScaleUp: true, } + +// SupportedProvisioningClass verifies if the provisioningClassName with the given checkCapacityProvisioningClassPrefix is supported. +func SupportedProvisioningClass(provisioningClassName string, checkCapacityProvisioningClassPrefix string) bool { + if checkCapacityProvisioningClassPrefix == "" { + return SupportedProvisioningClasses[provisioningClassName] + } + if SupportedProvisioningClasses[provisioningClassName] { + // Ignore direct ProvisioningClassCheckCapacity when checkCapacityProvisioningClassPrefix is set. + return provisioningClassName != v1.ProvisioningClassCheckCapacity + } + if !strings.HasPrefix(provisioningClassName, checkCapacityProvisioningClassPrefix) { + return false + } + return SupportedProvisioningClasses[provisioningClassName[len(checkCapacityProvisioningClassPrefix):]] +} From 9cac6a49d171e974b5c0db23d980adc520c2d3dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Skocze=C5=84?= Date: Wed, 29 Jan 2025 10:04:58 +0000 Subject: [PATCH 2/3] Update FAQ to reflect recent changes in ProvisioningRequests processing --- cluster-autoscaler/FAQ.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cluster-autoscaler/FAQ.md b/cluster-autoscaler/FAQ.md index 5d5026e05b45..a3f4be617f89 100644 --- a/cluster-autoscaler/FAQ.md +++ b/cluster-autoscaler/FAQ.md @@ -629,6 +629,11 @@ When using this class, Cluster Autoscaler performs following actions: Adds a Provisioned=True condition to the ProvReq if capacity is available. Adds a BookingExpired=True condition when the 10-minute reservation period expires. + Since Cluster Autoscaler version 1.33, it is possible to configure the autoscaler + to process only those check capacity ProvisioningRequests, that have a prefix matching the `--check-capacity-provisioning-class-prefix=` flag. + This allows to run two Cluster Autoscalers in the cluster, but instance with the configured prefix + **should only** handle check capacity ProvisioningRequests and not overlap node groups with the main instance. + * `best-effort-atomic-scale-up.autoscaling.x-k8s.io` (supported from Cluster Autoscaler version 1.30.2 or later). When using this class, Cluster Autoscaler performs following actions: @@ -735,12 +740,12 @@ setting the following flag in your Cluster Autoscaler configuration: 3. **Batch Size**: Set the maximum number of CheckCapacity ProvisioningRequests to process in a single iteration by setting the following flag in your Cluster Autoscaler configuration: -`--max-batch-size=`. The default value is 10. +`--check-capacity-provisioning-request-max-batch-size=`. The default value is 10. 4. **Batch Timebox**: Set the maximum time in seconds that Cluster Autoscaler will spend processing CheckCapacity ProvisioningRequests in a single iteration by setting the following flag in your Cluster Autoscaler configuration: -`--batch-timebox=`. The default value is 10s. +`--check-capacity-provisioning-request-batch-timebox=`. The default value is 10s. **************** @@ -973,6 +978,7 @@ The following startup parameters are supported for cluster autoscaler: | `bulk-mig-instances-listing-enabled` | Fetch GCE mig instances in bulk instead of per mig | | | `bypassed-scheduler-names` | Names of schedulers to bypass. If set to non-empty value, CA will not wait for pods to reach a certain age before triggering a scale-up. | | | `check-capacity-batch-processing` | Whether to enable batch processing for check capacity requests. | | +| `check-capacity-provisioning-class-prefix` | Prefix of provisioningClassName that will be filtered by processors. Only ProvisioningRequests with this prefix in their class will be processed by this CA. It refers only to check capacity ProvisioningRequests. | | | `check-capacity-provisioning-request-batch-timebox` | Maximum time to process a batch of provisioning requests. | 10s | | `check-capacity-provisioning-request-max-batch-size` | Maximum number of provisioning requests to process in a single batch. | 10 | | `cloud-config` | The path to the cloud provider configuration file. Empty string for no configuration file. | | @@ -980,6 +986,7 @@ The following startup parameters are supported for cluster autoscaler: | `cloud-provider-gce-l7lb-src-cidrs` | CIDRs opened in GCE firewall for L7 LB traffic proxy & health checks | 130.211.0.0/22,35.191.0.0/16 | | `cloud-provider-gce-lb-src-cidrs` | CIDRs opened in GCE firewall for L4 LB traffic proxy & health checks | 130.211.0.0/22,209.85.152.0/22,209.85.204.0/22,35.191.0.0/16 | | `cluster-name` | Autoscaled cluster name, if available | | +| `cluster-snapshot-parallelism` | Maximum parallelism of cluster snapshot creation. | 16 | | `clusterapi-cloud-config-authoritative` | Treat the cloud-config flag authoritatively (do not fallback to using kubeconfig flag). ClusterAPI only | | | `cordon-node-before-terminating` | Should CA cordon nodes before terminating during downscale process | | | `cores-total` | Minimum and maximum number of cores in cluster, in the format :. Cluster autoscaler will not scale the cluster beyond these numbers. | "0:320000" | From 90eabc6a4d6cd5b77152b0fe3302e9a1609341d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Skocze=C5=84?= Date: Mon, 10 Feb 2025 16:31:21 +0000 Subject: [PATCH 3/3] Differentiate provisioning requests using Parameters field. Keep prefixing as not recommended approach --- cluster-autoscaler/FAQ.md | 22 ++++- .../config/autoscaling_options.go | 9 +- cluster-autoscaler/main.go | 8 +- .../processors/provreq/injector.go | 48 ++++----- .../processors/provreq/injector_test.go | 49 +++++++--- .../processors/provreq/processor.go | 18 ++-- .../besteffortatomic/provisioning_class.go | 2 +- .../checkcapacity/provisioningclass.go | 2 +- .../conditions/condition_test.go | 25 ++--- .../conditions/conditions.go | 4 +- .../provreqclient/client.go | 16 ++- .../supported_class_test.go | 97 +++++++++++++++++++ .../provisioningrequest/supported_classes.go | 58 ++++++++--- 13 files changed, 263 insertions(+), 95 deletions(-) create mode 100644 cluster-autoscaler/provisioningrequest/supported_class_test.go diff --git a/cluster-autoscaler/FAQ.md b/cluster-autoscaler/FAQ.md index a3f4be617f89..36969df87204 100644 --- a/cluster-autoscaler/FAQ.md +++ b/cluster-autoscaler/FAQ.md @@ -630,9 +630,17 @@ When using this class, Cluster Autoscaler performs following actions: Adds a BookingExpired=True condition when the 10-minute reservation period expires. Since Cluster Autoscaler version 1.33, it is possible to configure the autoscaler - to process only those check capacity ProvisioningRequests, that have a prefix matching the `--check-capacity-provisioning-class-prefix=` flag. - This allows to run two Cluster Autoscalers in the cluster, but instance with the configured prefix + to process only subset of check capacity ProvisioningRequests and ignore the rest. + It should be done with caution by specifying `--check-capacity-processor-instance=` flag. + Then, ProvReq Parameters map should contain a key "processorInstance" with a value equal to the configured instance name. + + This allows to run two Cluster Autoscalers in the cluster, but the second instance (likely this with configured instance name) **should only** handle check capacity ProvisioningRequests and not overlap node groups with the main instance. + It is responsibility of the user to ensure the capacity checks are not overlapping. + Best-effort atomic ProvisioningRequests processing is disabled in the instance that has this flag set. + + For backwards compatibility, it is possible to differentiate the ProvReqs by prefixing provisioningClassName with the instance name, + but it is **not recommended** and will be removed in CA 1.35. * `best-effort-atomic-scale-up.autoscaling.x-k8s.io` (supported from Cluster Autoscaler version 1.30.2 or later). When using this class, Cluster Autoscaler performs following actions: @@ -978,7 +986,7 @@ The following startup parameters are supported for cluster autoscaler: | `bulk-mig-instances-listing-enabled` | Fetch GCE mig instances in bulk instead of per mig | | | `bypassed-scheduler-names` | Names of schedulers to bypass. If set to non-empty value, CA will not wait for pods to reach a certain age before triggering a scale-up. | | | `check-capacity-batch-processing` | Whether to enable batch processing for check capacity requests. | | -| `check-capacity-provisioning-class-prefix` | Prefix of provisioningClassName that will be filtered by processors. Only ProvisioningRequests with this prefix in their class will be processed by this CA. It refers only to check capacity ProvisioningRequests. | | +| `check-capacity-processor-instance` | Name of the processor instance. Only ProvisioningRequests that define this name in their parameters with the key "processorInstance" will be processed by this CA instance. It only refers to check capacity ProvisioningRequests, but if not empty, best-effort atomic ProvisioningRequests processing is disabled in this instance. Not recommended: Until CA 1.35, ProvisioningRequests with this name as prefix in their class will be also processed. | | | `check-capacity-provisioning-request-batch-timebox` | Maximum time to process a batch of provisioning requests. | 10s | | `check-capacity-provisioning-request-max-batch-size` | Maximum number of provisioning requests to process in a single batch. | 10 | | `cloud-config` | The path to the cloud provider configuration file. Empty string for no configuration file. | | @@ -1022,7 +1030,13 @@ The following startup parameters are supported for cluster autoscaler: | `kube-client-qps` | QPS value for kubernetes client. | 5 | | `kubeconfig` | Path to kubeconfig file with authorization and master location information. | | | `kubernetes` | Kubernetes master location. Leave blank for default | | -| `lease-resource-name` | The lease resource to use in leader election. | "cluster-autoscaler" | +| `leader-elect` | Start a leader election client and gain leadership before executing the main loop. Enable this when running replicated components for high availability. | true | +| `leader-elect-lease-duration` | The duration that non-leader candidates will wait after observing a leadership renewal until attempting to acquire leadership of a led but unrenewed leader slot. This is effectively the maximum duration that a leader can be stopped before it is replaced by another candidate. This is only applicable if leader election is enabled. | 15s | +| `leader-elect-renew-deadline` | The interval between attempts by the acting master to renew a leadership slot before it stops leading. This must be less than the lease duration. This is only applicable if leader election is enabled. | 10s | +| `leader-elect-resource-lock` | The type of resource object that is used for locking during leader election. Supported options are 'leases'. | "leases" | +| `leader-elect-resource-name` | The name of resource object that is used for locking during leader election. | "cluster-autoscaler" | +| `leader-elect-resource-namespace` | The namespace of resource object that is used for locking during leader election. | | +| `leader-elect-retry-period` | The duration the clients should wait between attempting acquisition and renewal of a leadership. This is only applicable if leader election is enabled. | 2s | | `log-backtrace-at` | when logging hits line file:N, emit a stack trace | :0 | | `log-dir` | If non-empty, write log files in this directory (no effect when -logtostderr=true) | | | `log-file` | If non-empty, use this log file (no effect when -logtostderr=true) | | diff --git a/cluster-autoscaler/config/autoscaling_options.go b/cluster-autoscaler/config/autoscaling_options.go index 4b626b31b587..5addca6b15d6 100644 --- a/cluster-autoscaler/config/autoscaling_options.go +++ b/cluster-autoscaler/config/autoscaling_options.go @@ -313,10 +313,11 @@ type AutoscalingOptions struct { DynamicResourceAllocationEnabled bool // ClusterSnapshotParallelism is the maximum parallelism of cluster snapshot creation. ClusterSnapshotParallelism int - // CheckCapacityProvisioningClassPrefix is the prefix of provisioningClassName that will be filtered by processors. - // Only ProvisioningRequests with this prefix in their class will be processed by this CA. - // It only refers to check capacity ProvisioningRequests. - CheckCapacityProvisioningClassPrefix string + // CheckCapacityProcessorInstance is the name of the processor instance. + // Only ProvisioningRequests that define this name in their parameters with the key "processorInstance" will be processed by this CA instance. + // It only refers to check capacity ProvisioningRequests, but if not empty, best-effort atomic ProvisioningRequests processing is disabled in this instance. + // Not recommended: Until CA 1.35, ProvisioningRequests with this name as prefix in their class will be also processed. + CheckCapacityProcessorInstance string } // KubeClientOptions specify options for kube client diff --git a/cluster-autoscaler/main.go b/cluster-autoscaler/main.go index 321cd95bca37..8ca0d5a4fe89 100644 --- a/cluster-autoscaler/main.go +++ b/cluster-autoscaler/main.go @@ -283,7 +283,7 @@ var ( forceDeleteLongUnregisteredNodes = flag.Bool("force-delete-unregistered-nodes", false, "Whether to enable force deletion of long unregistered nodes, regardless of the min size of the node group the belong to.") enableDynamicResourceAllocation = flag.Bool("enable-dynamic-resource-allocation", false, "Whether logic for handling DRA (Dynamic Resource Allocation) objects is enabled.") clusterSnapshotParallelism = flag.Int("cluster-snapshot-parallelism", 16, "Maximum parallelism of cluster snapshot creation.") - checkCapacityProvisioningClassPrefix = flag.String("check-capacity-provisioning-class-prefix", "", "Prefix of provisioningClassName that will be filtered by processors. Only ProvisioningRequests with this prefix in their class will be processed by this CA. It refers only to check capacity ProvisioningRequests.") + checkCapacityProcessorInstance = flag.String("check-capacity-processor-instance", "", "Name of the processor instance. Only ProvisioningRequests that define this name in their parameters with the key \"processorInstance\" will be processed by this CA instance. It only refers to check capacity ProvisioningRequests, but if not empty, best-effort atomic ProvisioningRequests processing is disabled in this instance. Not recommended: Until CA 1.35, ProvisioningRequests with this name as prefix in their class will be also processed.") ) func isFlagPassed(name string) bool { @@ -465,7 +465,7 @@ func createAutoscalingOptions() config.AutoscalingOptions { ForceDeleteLongUnregisteredNodes: *forceDeleteLongUnregisteredNodes, DynamicResourceAllocationEnabled: *enableDynamicResourceAllocation, ClusterSnapshotParallelism: *clusterSnapshotParallelism, - CheckCapacityProvisioningClassPrefix: *checkCapacityProvisioningClassPrefix, + CheckCapacityProcessorInstance: *checkCapacityProcessorInstance, } } @@ -541,7 +541,7 @@ func buildAutoscaler(context ctx.Context, debuggingSnapshotter debuggingsnapshot return nil, nil, err } - ProvisioningRequestInjector, err = provreq.NewProvisioningRequestPodsInjector(restConfig, opts.ProvisioningRequestInitialBackoffTime, opts.ProvisioningRequestMaxBackoffTime, opts.ProvisioningRequestMaxBackoffCacheSize, opts.CheckCapacityBatchProcessing, opts.CheckCapacityProvisioningClassPrefix) + ProvisioningRequestInjector, err = provreq.NewProvisioningRequestPodsInjector(restConfig, opts.ProvisioningRequestInitialBackoffTime, opts.ProvisioningRequestMaxBackoffTime, opts.ProvisioningRequestMaxBackoffCacheSize, opts.CheckCapacityBatchProcessing, opts.CheckCapacityProcessorInstance) if err != nil { return nil, nil, err } @@ -560,7 +560,7 @@ func buildAutoscaler(context ctx.Context, debuggingSnapshotter debuggingsnapshot scaleUpOrchestrator := provreqorchestrator.NewWrapperOrchestrator(provreqOrchestrator) opts.ScaleUpOrchestrator = scaleUpOrchestrator - provreqProcesor := provreq.NewProvReqProcessor(client, opts.CheckCapacityProvisioningClassPrefix) + provreqProcesor := provreq.NewProvReqProcessor(client, opts.CheckCapacityProcessorInstance) opts.LoopStartNotifier = loopstart.NewObserversList([]loopstart.Observer{provreqProcesor}) podListProcessor.AddProcessor(provreqProcesor) diff --git a/cluster-autoscaler/processors/provreq/injector.go b/cluster-autoscaler/processors/provreq/injector.go index 595bbf2eea5f..f8e21e9bc820 100644 --- a/cluster-autoscaler/processors/provreq/injector.go +++ b/cluster-autoscaler/processors/provreq/injector.go @@ -37,14 +37,14 @@ import ( // ProvisioningRequestPodsInjector creates in-memory pods from ProvisioningRequest and inject them to unscheduled pods list. type ProvisioningRequestPodsInjector struct { - initialRetryTime time.Duration - maxBackoffTime time.Duration - backoffDuration *lru.Cache - clock clock.PassiveClock - client *provreqclient.ProvisioningRequestClient - lastProvisioningRequestProcessTime time.Time - checkCapacityBatchProcessing bool - checkCapacityProvisioningClassPrefix string + initialRetryTime time.Duration + maxBackoffTime time.Duration + backoffDuration *lru.Cache + clock clock.PassiveClock + client *provreqclient.ProvisioningRequestClient + lastProvisioningRequestProcessTime time.Time + checkCapacityBatchProcessing bool + checkCapacityProcessorInstance string } // IsAvailableForProvisioning checks if the provisioning request is the correct state for processing and provisioning has not been attempted recently. @@ -95,13 +95,17 @@ func (p *ProvisioningRequestPodsInjector) MarkAsFailed(pr *provreqwrapper.Provis } func (p *ProvisioningRequestPodsInjector) isSupportedClass(pr *provreqwrapper.ProvisioningRequest) bool { - return provisioningrequest.SupportedProvisioningClass(pr.Spec.ProvisioningClassName, p.checkCapacityProvisioningClassPrefix) + return provisioningrequest.SupportedProvisioningClass(pr.ProvisioningRequest, p.checkCapacityProcessorInstance) +} + +func (p *ProvisioningRequestPodsInjector) isSupportedCheckCapacityClass(pr *provreqwrapper.ProvisioningRequest) bool { + return provisioningrequest.SupportedCheckCapacityClass(pr.ProvisioningRequest, p.checkCapacityProcessorInstance) } func (p *ProvisioningRequestPodsInjector) shouldMarkAsAccepted(pr *provreqwrapper.ProvisioningRequest) bool { // Don't mark as accepted the check capacity ProvReq when batch processing is enabled. // It will be marked later, in parallel, during processing the requests. - return !p.checkCapacityBatchProcessing || !p.matchesCheckCapacityClass(pr.Spec.ProvisioningClassName) + return !p.checkCapacityBatchProcessing || !p.isSupportedCheckCapacityClass(pr) } // GetPodsFromNextRequest picks one ProvisioningRequest meeting the condition passed using isSupportedClass function, marks it as accepted and returns pods from it. @@ -145,10 +149,6 @@ type ProvisioningRequestWithPods struct { Pods []*apiv1.Pod } -func (p *ProvisioningRequestPodsInjector) matchesCheckCapacityClass(provisioningClassName string) bool { - return provisioningClassName == p.checkCapacityProvisioningClassPrefix+v1.ProvisioningClassCheckCapacity -} - // GetCheckCapacityBatch returns up to the requested number of ProvisioningRequestWithPods. // We do not mark the PRs as accepted here. // If we fail to get the pods for a PR, we mark the PR as failed and issue an update. @@ -162,7 +162,7 @@ func (p *ProvisioningRequestPodsInjector) GetCheckCapacityBatch(maxPrs int) ([]P if len(prsWithPods) >= maxPrs { break } - if !p.matchesCheckCapacityClass(pr.Spec.ProvisioningClassName) { + if !p.isSupportedCheckCapacityClass(pr) { continue } if !p.IsAvailableForProvisioning(pr) { @@ -197,20 +197,20 @@ func (p *ProvisioningRequestPodsInjector) Process( func (p *ProvisioningRequestPodsInjector) CleanUp() {} // NewProvisioningRequestPodsInjector creates a ProvisioningRequest filter processor. -func NewProvisioningRequestPodsInjector(kubeConfig *rest.Config, initialBackoffTime, maxBackoffTime time.Duration, maxCacheSize int, checkCapacityBatchProcessing bool, checkCapacityProvisioningClassPrefix string) (*ProvisioningRequestPodsInjector, error) { +func NewProvisioningRequestPodsInjector(kubeConfig *rest.Config, initialBackoffTime, maxBackoffTime time.Duration, maxCacheSize int, checkCapacityBatchProcessing bool, checkCapacityProcessorInstance string) (*ProvisioningRequestPodsInjector, error) { client, err := provreqclient.NewProvisioningRequestClient(kubeConfig) if err != nil { return nil, err } return &ProvisioningRequestPodsInjector{ - initialRetryTime: initialBackoffTime, - maxBackoffTime: maxBackoffTime, - backoffDuration: lru.New(maxCacheSize), - client: client, - clock: clock.RealClock{}, - lastProvisioningRequestProcessTime: time.Now(), - checkCapacityBatchProcessing: checkCapacityBatchProcessing, - checkCapacityProvisioningClassPrefix: checkCapacityProvisioningClassPrefix, + initialRetryTime: initialBackoffTime, + maxBackoffTime: maxBackoffTime, + backoffDuration: lru.New(maxCacheSize), + client: client, + clock: clock.RealClock{}, + lastProvisioningRequestProcessTime: time.Now(), + checkCapacityBatchProcessing: checkCapacityBatchProcessing, + checkCapacityProcessorInstance: checkCapacityProcessorInstance, }, nil } diff --git a/cluster-autoscaler/processors/provreq/injector_test.go b/cluster-autoscaler/processors/provreq/injector_test.go index 685971fad507..22bdd57ee6b1 100644 --- a/cluster-autoscaler/processors/provreq/injector_test.go +++ b/cluster-autoscaler/processors/provreq/injector_test.go @@ -24,6 +24,7 @@ import ( apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" + "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqclient" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper" clock "k8s.io/utils/clock/testing" @@ -69,6 +70,10 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { podsA := 10 newProvReqA := testProvisioningRequestWithCondition("new", podsA, v1.ProvisioningClassCheckCapacity) newAcceptedProvReqA := testProvisioningRequestWithCondition("new-accepted", podsA, v1.ProvisioningClassCheckCapacity, accepted) + newProvReqAWithInstance := testProvisioningRequestWithCondition("new-instance", podsA, v1.ProvisioningClassCheckCapacity) + newProvReqAWithInstance.Spec.Parameters = map[string]v1.Parameter{ + provisioningrequest.CheckCapacityProcessorInstanceKey: "test-instance", + } newProvReqAPrefixed := testProvisioningRequestWithCondition("new-prefixed", podsA, "test-prefix.check-capacity.autoscaling.x-k8s.io") podsB := 20 @@ -80,13 +85,13 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { unknownClass := testProvisioningRequestWithCondition("new-accepted", podsA, "unknown-class", accepted) testCases := []struct { - name string - provReqs []*provreqwrapper.ProvisioningRequest - existingUnsUnschedulablePodCount int - checkCapacityBatchProcessing bool - checkCapacityProvisioningClassPrefix string - wantUnscheduledPodCount int - wantUpdatedConditionName string + name string + provReqs []*provreqwrapper.ProvisioningRequest + existingUnsUnschedulablePodCount int + checkCapacityBatchProcessing bool + checkCapacityProcessorInstance string + wantUnscheduledPodCount int + wantUpdatedConditionName string }{ { name: "New ProvisioningRequest, pods are injected and Accepted condition is added", @@ -112,16 +117,28 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed}, }, { - name: "New ProvisioningRequest with not matching prefix, no pods are injected", - provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB}, - checkCapacityProvisioningClassPrefix: "test-prefix.", + name: "New ProvisioningRequest with not matching processor instance, no pods are injected", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB}, + checkCapacityProcessorInstance: "test-instance", + }, + { + name: "New check capacity ProvisioningRequest with matching processor instance, pods are injected and Accepted condition is added", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAWithInstance, provisionedAcceptedProvReqB}, + checkCapacityProcessorInstance: "test-instance", + wantUnscheduledPodCount: podsA, + wantUpdatedConditionName: newProvReqAWithInstance.Name, + }, + { + name: "New ProvisioningRequest with not matching prefix, no pods are injected", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqA, provisionedAcceptedProvReqB}, + checkCapacityProcessorInstance: "test-prefix.", }, { - name: "New check capacity ProvisioningRequest with matching prefix, pods are injected and Accepted condition is added", - provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed, provisionedAcceptedProvReqB}, - checkCapacityProvisioningClassPrefix: "test-prefix.", - wantUnscheduledPodCount: podsA, - wantUpdatedConditionName: newProvReqAPrefixed.Name, + name: "New check capacity ProvisioningRequest with matching prefix, pods are injected and Accepted condition is added", + provReqs: []*provreqwrapper.ProvisioningRequest{newProvReqAPrefixed, provisionedAcceptedProvReqB}, + checkCapacityProcessorInstance: "test-prefix.", + wantUnscheduledPodCount: podsA, + wantUpdatedConditionName: newProvReqAPrefixed.Name, }, { name: "Provisioned=False, pods are injected", @@ -157,7 +174,7 @@ func TestProvisioningRequestPodsInjector(t *testing.T) { client := provreqclient.NewFakeProvisioningRequestClient(context.Background(), t, tc.provReqs...) backoffTime := lru.New(100) backoffTime.Add(key(notProvisionedRecentlyProvReqB), 2*time.Minute) - injector := ProvisioningRequestPodsInjector{1 * time.Minute, 10 * time.Minute, backoffTime, clock.NewFakePassiveClock(now), client, now, tc.checkCapacityBatchProcessing, tc.checkCapacityProvisioningClassPrefix} + injector := ProvisioningRequestPodsInjector{1 * time.Minute, 10 * time.Minute, backoffTime, clock.NewFakePassiveClock(now), client, now, tc.checkCapacityBatchProcessing, tc.checkCapacityProcessorInstance} getUnscheduledPods, err := injector.Process(nil, provreqwrapper.BuildTestPods("ns", "pod", tc.existingUnsUnschedulablePodCount)) if err != nil { t.Errorf("%s failed: injector.Process return error %v", tc.name, err) diff --git a/cluster-autoscaler/processors/provreq/processor.go b/cluster-autoscaler/processors/provreq/processor.go index 49bf54ed59c3..c97f95cd2740 100644 --- a/cluster-autoscaler/processors/provreq/processor.go +++ b/cluster-autoscaler/processors/provreq/processor.go @@ -50,16 +50,16 @@ type injector interface { } type provReqProcessor struct { - now func() time.Time - maxUpdated int - client *provreqclient.ProvisioningRequestClient - injector injector - checkCapacityProvisioningClassPrefix string + now func() time.Time + maxUpdated int + client *provreqclient.ProvisioningRequestClient + injector injector + checkCapacityProcessorInstance string } // NewProvReqProcessor return ProvisioningRequestProcessor. -func NewProvReqProcessor(client *provreqclient.ProvisioningRequestClient, checkCapacityProvisioningClassPrefix string) *provReqProcessor { - return &provReqProcessor{now: time.Now, maxUpdated: defaultMaxUpdated, client: client, injector: scheduling.NewHintingSimulator(), checkCapacityProvisioningClassPrefix: checkCapacityProvisioningClassPrefix} +func NewProvReqProcessor(client *provreqclient.ProvisioningRequestClient, checkCapacityProcessorInstance string) *provReqProcessor { + return &provReqProcessor{now: time.Now, maxUpdated: defaultMaxUpdated, client: client, injector: scheduling.NewHintingSimulator(), checkCapacityProcessorInstance: checkCapacityProcessorInstance} } // Refresh implements loop.Observer interface and will be run at the start @@ -85,7 +85,7 @@ func (p *provReqProcessor) refresh(provReqs []*provreqwrapper.ProvisioningReques if len(expiredProvReq) >= p.maxUpdated { break } - if !provisioningrequest.SupportedProvisioningClass(provReq.Spec.ProvisioningClassName, p.checkCapacityProvisioningClassPrefix) { + if !provisioningrequest.SupportedProvisioningClass(provReq.ProvisioningRequest, p.checkCapacityProcessorInstance) { continue } conditions := provReq.Status.Conditions @@ -145,7 +145,7 @@ func (p *provReqProcessor) bookCapacity(ctx *context.AutoscalingContext) error { } podsToCreate := []*apiv1.Pod{} for _, provReq := range provReqs { - if !conditions.ShouldCapacityBeBooked(provReq, p.checkCapacityProvisioningClassPrefix) { + if !conditions.ShouldCapacityBeBooked(provReq, p.checkCapacityProcessorInstance) { continue } pods, err := provreq_pods.PodsForProvisioningRequest(provReq) diff --git a/cluster-autoscaler/provisioningrequest/besteffortatomic/provisioning_class.go b/cluster-autoscaler/provisioningrequest/besteffortatomic/provisioning_class.go index 34d64c4f847d..2d743bfcae27 100644 --- a/cluster-autoscaler/provisioningrequest/besteffortatomic/provisioning_class.go +++ b/cluster-autoscaler/provisioningrequest/besteffortatomic/provisioning_class.go @@ -82,7 +82,7 @@ func (o *bestEffortAtomicProvClass) Provision( return &status.ScaleUpStatus{Result: status.ScaleUpNotTried}, nil } prs := provreqclient.ProvisioningRequestsForPods(o.client, unschedulablePods) - prs = provreqclient.FilterOutProvisioningClass(prs, v1.ProvisioningClassBestEffortAtomicScaleUp) + prs = provreqclient.FilterOutProvisioningClass(prs, v1.ProvisioningClassBestEffortAtomicScaleUp, "") if len(prs) == 0 { return &status.ScaleUpStatus{Result: status.ScaleUpNotTried}, nil } diff --git a/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go b/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go index 3bd6d7e966bd..b89dd40f0390 100644 --- a/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go +++ b/cluster-autoscaler/provisioningrequest/checkcapacity/provisioningclass.go @@ -132,7 +132,7 @@ func (o *checkCapacityProvClass) getProvisioningRequestsAndPods(unschedulablePod if !o.isBatchEnabled() { klog.Info("Processing single provisioning request (non-batch)") prs := provreqclient.ProvisioningRequestsForPods(o.client, unschedulablePods) - prs = provreqclient.FilterOutProvisioningClass(prs, o.context.CheckCapacityProvisioningClassPrefix+v1.ProvisioningClassCheckCapacity) + prs = provreqclient.FilterOutProvisioningClass(prs, v1.ProvisioningClassCheckCapacity, o.context.CheckCapacityProcessorInstance) if len(prs) == 0 { return nil, nil } diff --git a/cluster-autoscaler/provisioningrequest/conditions/condition_test.go b/cluster-autoscaler/provisioningrequest/conditions/condition_test.go index 322a69cbaa0b..7bdd0bab5e7e 100644 --- a/cluster-autoscaler/provisioningrequest/conditions/condition_test.go +++ b/cluster-autoscaler/provisioningrequest/conditions/condition_test.go @@ -26,11 +26,12 @@ import ( func TestBookCapacity(t *testing.T) { tests := []struct { - name string - provisioningClassName string - prConditions []metav1.Condition - checkCapacityProvisioningClassPrefix string - want bool + name string + provisioningClassName string + prConditions []metav1.Condition + prParameters map[string]*v1.Parameter + checkCapacityProcessorInstance string + want bool }{ { name: "BookingExpired check capacity", @@ -133,7 +134,7 @@ func TestBookCapacity(t *testing.T) { want: true, }, { - name: "Capacity found and provisioned check capacity but prefix not matching", + name: "Capacity found and provisioned check capacity but processor instance not matching", provisioningClassName: v1.ProvisioningClassCheckCapacity, prConditions: []metav1.Condition{ { @@ -145,11 +146,11 @@ func TestBookCapacity(t *testing.T) { Status: metav1.ConditionTrue, }, }, - checkCapacityProvisioningClassPrefix: "test-", - want: false, + checkCapacityProcessorInstance: "test", + want: false, }, { - name: "Capacity found and provisioned best effort atomic and prefix is ignored", + name: "Capacity found and provisioned best effort atomic but processor instance means ignore", provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, prConditions: []metav1.Condition{ { @@ -161,8 +162,8 @@ func TestBookCapacity(t *testing.T) { Status: metav1.ConditionTrue, }, }, - checkCapacityProvisioningClassPrefix: "test-", - want: true, + checkCapacityProcessorInstance: "test", + want: false, }, { name: "Capacity is not found for check capacity", @@ -198,7 +199,7 @@ func TestBookCapacity(t *testing.T) { Conditions: test.prConditions, }, }, nil) - got := ShouldCapacityBeBooked(pr, test.checkCapacityProvisioningClassPrefix) + got := ShouldCapacityBeBooked(pr, test.checkCapacityProcessorInstance) if got != test.want { t.Errorf("Want: %v, got: %v", test.want, got) } diff --git a/cluster-autoscaler/provisioningrequest/conditions/conditions.go b/cluster-autoscaler/provisioningrequest/conditions/conditions.go index efdd3d79014b..dc706407019c 100644 --- a/cluster-autoscaler/provisioningrequest/conditions/conditions.go +++ b/cluster-autoscaler/provisioningrequest/conditions/conditions.go @@ -59,8 +59,8 @@ const ( ) // ShouldCapacityBeBooked returns whether capacity should be booked. -func ShouldCapacityBeBooked(pr *provreqwrapper.ProvisioningRequest, checkCapacityProvisioningClassPrefix string) bool { - if !provisioningrequest.SupportedProvisioningClass(pr.Spec.ProvisioningClassName, checkCapacityProvisioningClassPrefix) { +func ShouldCapacityBeBooked(pr *provreqwrapper.ProvisioningRequest, checkCapacityProcessorInstance string) bool { + if !provisioningrequest.SupportedProvisioningClass(pr.ProvisioningRequest, checkCapacityProcessorInstance) { return false } conditions := pr.Status.Conditions diff --git a/cluster-autoscaler/provisioningrequest/provreqclient/client.go b/cluster-autoscaler/provisioningrequest/provreqclient/client.go index 03c19d6e6568..c41601aaad63 100644 --- a/cluster-autoscaler/provisioningrequest/provreqclient/client.go +++ b/cluster-autoscaler/provisioningrequest/provreqclient/client.go @@ -30,6 +30,7 @@ import ( "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/client/clientset/versioned" "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/client/informers/externalversions" listers "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/client/listers/autoscaling.x-k8s.io/v1" + "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest" "k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -214,12 +215,23 @@ func (c *ProvisioningRequestClient) DeleteProvisioningRequest(pr *v1.Provisionin } // FilterOutProvisioningClass filters out ProvReqs that belongs to certain Provisioning Class -func FilterOutProvisioningClass(prList []*provreqwrapper.ProvisioningRequest, class string) []*provreqwrapper.ProvisioningRequest { +func FilterOutProvisioningClass(prList []*provreqwrapper.ProvisioningRequest, class string, checkCapacityProcessorInstance string) []*provreqwrapper.ProvisioningRequest { newPrList := []*provreqwrapper.ProvisioningRequest{} for _, pr := range prList { - if pr.Spec.ProvisioningClassName == class { + if matchesProvisioningClass(pr, class, checkCapacityProcessorInstance) { newPrList = append(newPrList, pr) } } return newPrList } + +func matchesProvisioningClass(pr *provreqwrapper.ProvisioningRequest, class string, checkCapacityProcessorInstance string) bool { + switch class { + case v1.ProvisioningClassCheckCapacity: + return provisioningrequest.SupportedCheckCapacityClass(pr.ProvisioningRequest, checkCapacityProcessorInstance) + case v1.ProvisioningClassBestEffortAtomicScaleUp: + return pr.Spec.ProvisioningClassName == v1.ProvisioningClassBestEffortAtomicScaleUp + default: + return false + } +} diff --git a/cluster-autoscaler/provisioningrequest/supported_class_test.go b/cluster-autoscaler/provisioningrequest/supported_class_test.go new file mode 100644 index 000000000000..6203c5928bf3 --- /dev/null +++ b/cluster-autoscaler/provisioningrequest/supported_class_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2025 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 provisioningrequest + +import ( + "testing" + + v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" +) + +func TestSupportedProvisioningClass(t *testing.T) { + tests := []struct { + name string + provisioningClassName string + processorInstance v1.Parameter + checkCapacityProcessorInstance string + want bool + }{ + { + name: "Check capacity without instance", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + want: true, + }, + { + name: "Check capacity with matching instance param", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + processorInstance: "instance", + checkCapacityProcessorInstance: "instance", + want: true, + }, + { + name: "Check capacity with not matching instance param", + provisioningClassName: v1.ProvisioningClassCheckCapacity, + processorInstance: "instance2", + checkCapacityProcessorInstance: "instance", + want: false, + }, + { + name: "Check capacity with matching instance prefix", + provisioningClassName: "instance" + v1.ProvisioningClassCheckCapacity, + checkCapacityProcessorInstance: "instance", + want: true, + }, + { + name: "Check capacity with not matching instance prefix", + provisioningClassName: "instance2" + v1.ProvisioningClassCheckCapacity, + checkCapacityProcessorInstance: "instance", + want: false, + }, + { + name: "Best effort atomic", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + want: true, + }, + { + name: "Best effort atomic with any instance", + provisioningClassName: v1.ProvisioningClassBestEffortAtomicScaleUp, + checkCapacityProcessorInstance: "instance", + want: false, + }, + { + name: "Invalid class name", + provisioningClassName: "invalid", + want: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + pr := &v1.ProvisioningRequest{ + Spec: v1.ProvisioningRequestSpec{ + ProvisioningClassName: test.provisioningClassName, + Parameters: map[string]v1.Parameter{ + CheckCapacityProcessorInstanceKey: test.processorInstance, + }, + }, + } + got := SupportedProvisioningClass(pr, test.checkCapacityProcessorInstance) + if test.want != got { + t.Errorf("Expected SupportedProvisioningClass result: %v, got: %v", test.want, got) + } + }) + } +} diff --git a/cluster-autoscaler/provisioningrequest/supported_classes.go b/cluster-autoscaler/provisioningrequest/supported_classes.go index 8c24320e6250..ec72f9061972 100644 --- a/cluster-autoscaler/provisioningrequest/supported_classes.go +++ b/cluster-autoscaler/provisioningrequest/supported_classes.go @@ -20,28 +20,54 @@ import ( "strings" v1 "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1" + "k8s.io/klog/v2" ) -// SupportedProvisioningClasses is a set of ProvisioningRequest classes -// supported by Cluster Autoscaler. -// This map is exported for testing purposes. -// Checking the support should be done using SupportedProvisioningClass. -var SupportedProvisioningClasses = map[string]bool{ - v1.ProvisioningClassCheckCapacity: true, - v1.ProvisioningClassBestEffortAtomicScaleUp: true, +const ( + // CheckCapacityProcessorInstanceKey is a a key for ProvReq's Parameters. + // Value for this key defines the processor instance name used to filter ProvReqs + // and if not empty, it should match CheckCapacityProcessorInstance defined in CA's options. + // Unrecommended: Until CA 1.35, ProvReqs with this value as prefix in their class will be also processed. + CheckCapacityProcessorInstanceKey = "processorInstance" +) + +// SupportedProvisioningClass verifies if the ProvisioningRequest with the given checkCapacityProcessorInstance is supported. +func SupportedProvisioningClass(pr *v1.ProvisioningRequest, checkCapacityProcessorInstance string) bool { + if pr.Spec.ProvisioningClassName == v1.ProvisioningClassBestEffortAtomicScaleUp { + if checkCapacityProcessorInstance != "" { + // If processor instance is set, BestEffortAtomicScaleUp should not be processed. + return false + } + return true + } + + return SupportedCheckCapacityClass(pr, checkCapacityProcessorInstance) } -// SupportedProvisioningClass verifies if the provisioningClassName with the given checkCapacityProvisioningClassPrefix is supported. -func SupportedProvisioningClass(provisioningClassName string, checkCapacityProvisioningClassPrefix string) bool { - if checkCapacityProvisioningClassPrefix == "" { - return SupportedProvisioningClasses[provisioningClassName] +// SupportedCheckCapacityClass verifies if the check capacity ProvisioningRequest with the given checkCapacityProcessorInstance is supported. +func SupportedCheckCapacityClass(pr *v1.ProvisioningRequest, checkCapacityProcessorInstance string) bool { + provisioningClassName := pr.Spec.ProvisioningClassName + processorInstance := string(pr.Spec.Parameters[CheckCapacityProcessorInstanceKey]) + + if checkCapacityProcessorInstance == "" { + if processorInstance != "" { + // Processor instance should match + return false + } + // If instance setting not set, just check the name + return provisioningClassName == v1.ProvisioningClassCheckCapacity } - if SupportedProvisioningClasses[provisioningClassName] { - // Ignore direct ProvisioningClassCheckCapacity when checkCapacityProvisioningClassPrefix is set. - return provisioningClassName != v1.ProvisioningClassCheckCapacity + + if processorInstance != "" { + // If both instances exist, check if they match and the provisioningClassName + return checkCapacityProcessorInstance == processorInstance && provisioningClassName == v1.ProvisioningClassCheckCapacity } - if !strings.HasPrefix(provisioningClassName, checkCapacityProvisioningClassPrefix) { + + // If instances not set, check the prefix of provisioningClassName + if !strings.HasPrefix(provisioningClassName, checkCapacityProcessorInstance) { return false } - return SupportedProvisioningClasses[provisioningClassName[len(checkCapacityProvisioningClassPrefix):]] + klog.Warningf("ProvReq %s/%s has prefixed provisioningClassName %q that is not recommended and will be removed in CA 1.35. Parameters should be used instead", pr.Namespace, pr.Name, provisioningClassName) + + return provisioningClassName[len(checkCapacityProcessorInstance):] == v1.ProvisioningClassCheckCapacity }