From c4482a8d8f71550f9889356654970b90e31089dd Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Sat, 4 May 2024 10:00:30 +0200 Subject: [PATCH] feat: azure-pipelines allows to configure demandsToIgnore to ignore/accept demands but not require them for scaling (#5579) Signed-off-by: jan-mrm <67435696+jan-mrm@users.noreply.github.com> --- CHANGELOG.md | 1 + pkg/scalers/azure_pipelines_scaler.go | 27 ++++++++++- pkg/scalers/azure_pipelines_scaler_test.go | 54 ++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd671c7516b..d46b06d89af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Here is an overview of all new **experimental** features: ### Improvements - TODO ([#XXX](https://github.com/kedacore/keda/issues/XXX)) +- **Azure Pipelines Scaler**: New configuration parameter `demandsToIgnore` to ignore certain demands especially ones added automatically by pipeline tasks, useful when using `requireAllDemands: true` ([#5579](https://github.com/kedacore/keda/issues/5579)) ### Fixes diff --git a/pkg/scalers/azure_pipelines_scaler.go b/pkg/scalers/azure_pipelines_scaler.go index 6bdf5c3fdec..3ddbc696e20 100644 --- a/pkg/scalers/azure_pipelines_scaler.go +++ b/pkg/scalers/azure_pipelines_scaler.go @@ -144,6 +144,7 @@ type azurePipelinesMetadata struct { authContext authContext parent string demands string + demandsToIgnore string poolID int targetPipelinesQueueLength int64 activationTargetPipelinesQueueLength int64 @@ -267,6 +268,12 @@ func parseAzurePipelinesMetadata(ctx context.Context, logger logr.Logger, config meta.demands = "" } + if val, ok := config.TriggerMetadata["demandsToIgnore"]; ok && val != "" { + meta.demandsToIgnore = config.TriggerMetadata["demandsToIgnore"] + } else { + meta.demandsToIgnore = "" + } + meta.jobsToFetch = 250 if val, ok := config.TriggerMetadata["jobsToFetch"]; ok && val != "" { jobsToFetch, err := strconv.ParseInt(val, 10, 64) @@ -480,10 +487,28 @@ func stripAgentVFromArray(array []string) []string { return result } +func stripValuesFromArray(array []string, valuesToStrip []string) []string { + if len(valuesToStrip) > 0 { + var result []string + OUTER: + for _, item := range array { + for _, valueToStrip := range valuesToStrip { + if item == valueToStrip { + continue OUTER + } + } + result = append(result, item) + } + return result + } + return array +} + // Determine if the scaledjob has the right demands to spin up func getCanAgentDemandFulfilJob(jr JobRequest, metadata *azurePipelinesMetadata) bool { countDemands := 0 - demandsInJob := stripAgentVFromArray(jr.Demands) + demandsToIgnore := strings.Split(metadata.demandsToIgnore, ",") + demandsInJob := stripValuesFromArray(stripAgentVFromArray(jr.Demands), demandsToIgnore) demandsInScaler := stripAgentVFromArray(strings.Split(metadata.demands, ",")) for _, demandInJob := range demandsInJob { diff --git a/pkg/scalers/azure_pipelines_scaler_test.go b/pkg/scalers/azure_pipelines_scaler_test.go index f65460ac3b4..d5f45417580 100644 --- a/pkg/scalers/azure_pipelines_scaler_test.go +++ b/pkg/scalers/azure_pipelines_scaler_test.go @@ -337,6 +337,60 @@ func TestAzurePipelinesNotMatchedPartialRequiredTriggerDemands(t *testing.T) { } } +func TestAzurePipelinesMatchedDemandAgentWithRequireAllDemandsAndOneIgnoredDemand(t *testing.T) { + var apiStub = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(buildLoadJSON()) + })) + + meta := getDemandJobMetaData(apiStub.URL) + meta.requireAllDemands = true + meta.demands = "dotnet60" + meta.demandsToIgnore = "java" + + mockAzurePipelinesScaler := azurePipelinesScaler{ + metadata: meta, + httpClient: http.DefaultClient, + } + + queuelen, err := mockAzurePipelinesScaler.GetAzurePipelinesQueueLength(context.TODO()) + + if err != nil { + t.Fail() + } + + if queuelen < 1 { + t.Fail() + } +} + +func TestAzurePipelinesMatchedDemandAgentWithRequireAllDemandsAndTwoIgnoredDemand(t *testing.T) { + var apiStub = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(buildLoadJSON()) + })) + + meta := getDemandJobMetaData(apiStub.URL) + meta.requireAllDemands = true + meta.demands = "dotnet60" + meta.demandsToIgnore = "someOtherDemand,java" + + mockAzurePipelinesScaler := azurePipelinesScaler{ + metadata: meta, + httpClient: http.DefaultClient, + } + + queuelen, err := mockAzurePipelinesScaler.GetAzurePipelinesQueueLength(context.TODO()) + + if err != nil { + t.Fail() + } + + if queuelen < 1 { + t.Fail() + } +} + func buildLoadJSON() []byte { output := testJobRequestResponse[0 : len(testJobRequestResponse)-2] for i := 1; i < loadCount; i++ {