From 8abf64eb9804abff02a7edc4bb64edf139021dbe Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Sun, 20 Feb 2022 12:46:47 +0200 Subject: [PATCH 01/13] feat: Add a scaler based on a GCP Stackdriver metric Add a scaler based on a metric value obtained from GCP Stackdriver Fixes: 2661 Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 199 +++++++++++++++++++ pkg/scalers/gcp_stackdriver_scaler_test.go | 79 ++++++++ pkg/scaling/scale_handler.go | 2 + tests/scalers/gcp-stackdriver.test.ts | 210 +++++++++++++++++++++ 4 files changed, 490 insertions(+) create mode 100644 pkg/scalers/gcp_stackdriver_scaler.go create mode 100644 pkg/scalers/gcp_stackdriver_scaler_test.go create mode 100644 tests/scalers/gcp-stackdriver.test.ts diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go new file mode 100644 index 00000000000..bd9864e001e --- /dev/null +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -0,0 +1,199 @@ +package scalers + +import ( + "context" + "fmt" + "strconv" + + "k8s.io/api/autoscaling/v2beta2" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/metrics/pkg/apis/external_metrics" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + kedautil "github.com/kedacore/keda/v2/pkg/util" +) + +const ( + defaultStackdriverTargetValue = 5 +) + +type stackdriverScaler struct { + client *StackDriverClient + metadata *stackdriverMetadata +} + +type stackdriverMetadata struct { + projectId string + filter string + targetValue int + metricName string + + gcpAuthorization gcpAuthorizationMetadata + scalerIndex int +} + +var gcpStackdriverLog = logf.Log.WithName("gcp_stackdriver_scaler") + +// NewStackdriverScaler creates a new stackdriverScaler +func NewStackdriverScaler(config *ScalerConfig) (Scaler, error) { + meta, err := parseStackdriverMetadata(config) + if err != nil { + return nil, fmt.Errorf("error parsing Stackdriver metadata: %s", err) + } + + return &stackdriverScaler{ + metadata: meta, + }, nil +} + +func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error) { + meta := stackdriverMetadata{} + meta.targetValue = defaultStackdriverTargetValue + + if val, ok := config.TriggerMetadata["projectId"]; ok { + if val == "" { + return nil, fmt.Errorf("no projectId name given") + } + + meta.projectId = val + } else { + return nil, fmt.Errorf("no projectId name given") + } + + if val, ok := config.TriggerMetadata["filter"]; ok { + if val == "" { + return nil, fmt.Errorf("no filter given") + } + + meta.filter = val + } else { + return nil, fmt.Errorf("no filter given") + } + + if val, ok := config.TriggerMetadata["metricName"]; ok { + if val == "" { + return nil, fmt.Errorf("no metricName given") + } + + meta.metricName = kedautil.NormalizeString(fmt.Sprintf("gcp-stackdriver-%s", val)) + } else { + return nil, fmt.Errorf("no metricName given") + } + + if val, ok := config.TriggerMetadata["targetValue"]; ok { + targetValue, err := strconv.Atoi(val) + if err != nil { + gcpStackdriverLog.Error(err, "Error parsing targetValue") + return nil, fmt.Errorf("error parsing targetValue: %s", err.Error()) + } + + meta.targetValue = targetValue + } + + auth, err := getGcpAuthorization(config, config.ResolvedEnv) + if err != nil { + return nil, err + } + meta.gcpAuthorization = *auth + meta.scalerIndex = config.ScalerIndex + return &meta, nil +} + +func (s *stackdriverScaler) IsActive(ctx context.Context) (bool, error) { + value, err := s.getMetrics(ctx) + if err != nil { + gcpStackdriverLog.Error(err, "error getting metric value") + return false, err + } + return value > 0, nil +} + +func (s *stackdriverScaler) Close(context.Context) error { + if s.client != nil { + err := s.client.metricsClient.Close() + s.client = nil + if err != nil { + gcpStackdriverLog.Error(err, "error closing StackDriver client") + } + } + + return nil +} + +// GetMetricSpecForScaling returns the metric spec for the HPA +func (s *stackdriverScaler) GetMetricSpecForScaling(context.Context) []v2beta2.MetricSpec { + // Construct the target value as a quantity + targetValueQty := resource.NewQuantity(int64(s.metadata.targetValue), resource.DecimalSI) + + externalMetric := &v2beta2.ExternalMetricSource{ + Metric: v2beta2.MetricIdentifier{ + Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, s.metadata.metricName), + }, + Target: v2beta2.MetricTarget{ + Type: v2beta2.AverageValueMetricType, + AverageValue: targetValueQty, + }, + } + + // Create the metric spec for the HPA + metricSpec := v2beta2.MetricSpec{ + External: externalMetric, + Type: externalMetricType, + } + + return []v2beta2.MetricSpec{metricSpec} +} + +// GetMetrics connects to Stack Driver and retrieves the metric +func (s *stackdriverScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { + value, err := s.getMetrics(ctx) + if err != nil { + gcpStackdriverLog.Error(err, "error getting metric value") + return []external_metrics.ExternalMetricValue{}, err + } + + metric := external_metrics.ExternalMetricValue{ + MetricName: metricName, + Value: *resource.NewQuantity(value, resource.DecimalSI), + Timestamp: metav1.Now(), + } + + return append([]external_metrics.ExternalMetricValue{}, metric), nil +} + +func (s *stackdriverScaler) setStackdriverClient(ctx context.Context) error { + var client *StackDriverClient + var err error + if s.metadata.gcpAuthorization.podIdentityProviderEnabled { + client, err = NewStackDriverClientPodIdentity(ctx) + } else { + client, err = NewStackDriverClient(ctx, s.metadata.gcpAuthorization.GoogleApplicationCredentials) + } + + if err != nil { + gcpStackdriverLog.Error(err, "Failed to create stack driver client") + return err + } + s.client = client + return nil +} + +// getMetrics gets metric type value from stackdriver api +func (s *stackdriverScaler) getMetrics(ctx context.Context) (int64, error) { + if s.client == nil { + err := s.setStackdriverClient(ctx) + if err != nil { + return 0, err + } + } + + val, err := s.client.GetMetrics(ctx, s.metadata.filter, s.metadata.projectId) + if err == nil { + gcpStackdriverLog.V(1).Info( + fmt.Sprintf("Getting metrics for project %s and filter %s. Result: %d", s.metadata.projectId, s.metadata.filter, val)) + } + + return val, err +} diff --git a/pkg/scalers/gcp_stackdriver_scaler_test.go b/pkg/scalers/gcp_stackdriver_scaler_test.go new file mode 100644 index 00000000000..8a189c2a2c4 --- /dev/null +++ b/pkg/scalers/gcp_stackdriver_scaler_test.go @@ -0,0 +1,79 @@ +package scalers + +import ( + "context" + "testing" +) + +var testStackdriverResolvedEnv = map[string]string{ + "SAMPLE_CREDS": "{}", +} + +type parseStackdriverMetadataTestData struct { + authParams map[string]string + metadata map[string]string + isError bool +} + +type gcpStackdriverMetricIdentifier struct { + metadataTestData *parseStackdriverMetadataTestData + scalerIndex int + name string +} + +var filter = "metric.type=\"storage.googleapis.com/storage/object_count\" resource.type=\"gcs_bucket\"" + +var testStackdriverMetadata = []parseStackdriverMetadataTestData{ + {map[string]string{}, map[string]string{}, true}, + // all properly formed + {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + // all required properly formed + {nil, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + // missing projectId + {nil, map[string]string{"filter": filter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + // missing filter + {nil, map[string]string{"projectId": "myProject", "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + // missing metric name + {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + // missing credentials + {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "metricName": "gcs_bucket"}, true}, + // malformed targetValue + {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "aa", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + // Credentials from AuthParams + {map[string]string{"GoogleApplicationCredentials": "Creds", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket"}, false}, + // Credentials from AuthParams with empty creds + {map[string]string{"GoogleApplicationCredentials": "", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket"}, true}, +} + +var gcpStackdriverMetricIdentifiers = []gcpStackdriverMetricIdentifier{ + {&testStackdriverMetadata[1], 0, "s0-gcp-stackdriver-gcs_bucket"}, + {&testStackdriverMetadata[1], 1, "s1-gcp-stackdriver-gcs_bucket"}, +} + +func TestStackdriverParseMetadata(t *testing.T) { + for _, testData := range testStackdriverMetadata { + _, err := parseStackdriverMetadata(&ScalerConfig{AuthParams: testData.authParams, TriggerMetadata: testData.metadata, ResolvedEnv: testStackdriverResolvedEnv}) + if err != nil && !testData.isError { + t.Error("Expected success but got error", err) + } + if testData.isError && err == nil { + t.Error("Expected error but got success") + } + } +} + +func TestGcpStackdriverGetMetricSpecForScaling(t *testing.T) { + for _, testData := range gcpStackdriverMetricIdentifiers { + meta, err := parseStackdriverMetadata(&ScalerConfig{TriggerMetadata: testData.metadataTestData.metadata, ResolvedEnv: testStackdriverResolvedEnv, ScalerIndex: testData.scalerIndex}) + if err != nil { + t.Fatal("Could not parse metadata:", err) + } + mockGcpStackdriverScaler := stackdriverScaler{nil, meta} + + metricSpec := mockGcpStackdriverScaler.GetMetricSpecForScaling(context.Background()) + metricName := metricSpec[0].External.Metric.Name + if metricName != testData.name { + t.Error("Wrong External metric source name:", metricName) + } + } +} diff --git a/pkg/scaling/scale_handler.go b/pkg/scaling/scale_handler.go index a87bbd7cda5..7ebeb52910f 100644 --- a/pkg/scaling/scale_handler.go +++ b/pkg/scaling/scale_handler.go @@ -394,6 +394,8 @@ func buildScaler(ctx context.Context, client client.Client, triggerType string, return scalers.NewExternalPushScaler(config) case "gcp-pubsub": return scalers.NewPubSubScaler(config) + case "gcp-stackdriver": + return scalers.NewStackdriverScaler(config) case "gcp-storage": return scalers.NewGcsScaler(config) case "graphite": diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts new file mode 100644 index 00000000000..ec8fd4ec440 --- /dev/null +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -0,0 +1,210 @@ +import * as async from 'async' +import * as fs from 'fs' +import * as http from 'http' +import fetch from 'node-fetch' +import * as sh from 'shelljs' +import * as tmp from 'tmp' +import test from 'ava' + +const testNamespace = 'gcp-stackdriver-test' +const bucketName = 'keda-test-stackdriver-bucket' +const deploymentName = 'dummy-consumer' +const maxReplicaCount = '3' +const projectId = 'nth-hybrid-341214' +const gsPrefix = `kubectl exec --namespace ${testNamespace} deploy/gcp-sdk -- ` + +test.before(t => { + sh.exec(`kubectl create namespace ${testNamespace}`) + + // deploy dummy consumer app, scaled object etc. + const tmpFile = tmp.fileSync() + fs.writeFileSync(tmpFile.name, deployYaml.replace("{{GCP_CREDS}}", Buffer.from(serviceAccountJson).toString("base64"))) + + t.is( + 0, + sh.exec(`kubectl apply -f ${tmpFile.name} --namespace ${testNamespace}`).code, + 'creating a deployment should work..' + ) + +}) + +test.serial('Deployment should have 0 replicas on start', t => { + const replicaCount = sh.exec( + `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + t.is(replicaCount, '0', 'replica count should start out as 0') +}) + +test.serial('creating the gcp-sdk pod should work..', t => { + let tmpFile = tmp.fileSync() + fs.writeFileSync(tmpFile.name, gcpSdkYaml) + t.is( + 0, + sh.exec(`kubectl apply -f ${tmpFile.name} --namespace ${testNamespace}`).code, + 'creating the gcp-sdk pod should work..' + ) + + // wait for the gcp-sdkpod to be ready + let gcpSdkReadyReplicaCount = '0' + for (let i = 0; i < 30; i++) { + gcpSdkReadyReplicaCount = sh.exec(`kubectl get deploy/gcp-sdk -n ${testNamespace} -o jsonpath='{.status.readyReplicas}'`).stdout + if (gcpSdkReadyReplicaCount != '1') { + sh.exec('sleep 2s') + } + } + t.is('1', gcpSdkReadyReplicaCount, 'GCP-SDK pod is not in a ready state') +}) + +test.serial('initializing the gcp-sdk pod should work..', t => { + // Authenticate to GCP + t.is( + 0, + sh.exec(gsPrefix + `gcloud auth activate-service-account --key-file /etc/secret-volume/GOOGLE_APPLICATION_CREDENTIALS_JSON`).code, + 'Executing remote command on gc-sdk should work..' + ) + + // Set project id + sh.exec(gsPrefix + `gcloud config set project ${projectId}`) + + // Create bucket + sh.exec(gsPrefix + `gsutil mb gs://${bucketName}`) +}) + +test.serial(`Deployment should scale to ${maxReplicaCount} (the max) then back to 0`, t => { + let replicaCount = '0' + + // Wait for the number of replicas to be scaled up to maxReplicaCount + for (let i = 0; i < 60 && replicaCount != maxReplicaCount; i++) { + // Upload a file to generate traffic + t.is( + 0, + sh.exec(gsPrefix + `gsutil cp /usr/lib/google-cloud-sdk/bin/gsutil gs://${bucketName}`).code, + 'Copying an object should work..' + ) + replicaCount = sh.exec( + `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + if (replicaCount != maxReplicaCount) { + sh.exec('sleep 2s') + } + } + + t.is(maxReplicaCount, replicaCount, `Replica count should be ${maxReplicaCount} after 120 seconds but is ${replicaCount}`) + + // Wait for the number of replicas to be scaled down to 0 + for (let i = 0; i < 30 && replicaCount !== '0'; i++) { + replicaCount = sh.exec( + `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` + ).stdout + if (replicaCount != '0') { + sh.exec('sleep 10s') + } + } + + t.is('0', replicaCount, 'Replica count should be 0 after 3 minutes') +}) + +test.after.always.cb('clean up fake GCS deployment', t => { + // Cleanup the bucket + sh.exec(gsPrefix + `gsutil rm -r gs://${bucketName}`) + + sh.exec(`kubectl delete deployment.apps/${deploymentName} --namespace ${testNamespace}`) + sh.exec(`kubectl delete namespace ${testNamespace}`) + + t.end() +}) + + +const deployYaml = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: ${deploymentName} + namespace: ${testNamespace} + labels: + app: ${deploymentName} +spec: + replicas: 0 + selector: + matchLabels: + app: ${deploymentName} + template: + metadata: + labels: + app: ${deploymentName} + spec: + containers: + - name: noop-processor + image: ubuntu:20.04 + command: ["/bin/bash", "-c", "--"] + args: ["sleep 10"] + env: + - name: GOOGLE_APPLICATION_CREDENTIALS_JSON + valueFrom: + secretKeyRef: + name: stackdriver-secrets + key: GOOGLE_APPLICATION_CREDENTIALS_JSON +--- +apiVersion: v1 +kind: Secret +metadata: + name: stackdriver-secrets +type: Opaque +data: + GOOGLE_APPLICATION_CREDENTIALS_JSON: {{GCP_CREDS}} +--- +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: test-scaledobject +spec: + scaleTargetRef: + name: ${deploymentName} + pollingInterval: 5 + maxReplicaCount: ${maxReplicaCount} + cooldownPeriod: 10 + triggers: + - type: gcp-stackdriver + metadata: + projectId: ${projectId} + filter: 'metric.type="storage.googleapis.com/network/received_bytes_count" AND resource.type="gcs_bucket" AND metric.label.method="WriteObject" AND resource.label.bucket_name="${bucketName}"' + metricName: ${bucketName} + targetValue: '5' + credentialsFromEnv: GOOGLE_APPLICATION_CREDENTIALS_JSON +` + +const gcpSdkYaml = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: gcp-sdk + namespace: ${testNamespace} + labels: + app: gcp-sdk +spec: + replicas: 1 + selector: + matchLabels: + app: gcp-sdk + template: + metadata: + labels: + app: gcp-sdk + spec: + containers: + - name: gcp-sdk-container + image: google/cloud-sdk:slim + # Just spin & wait forever + command: [ "/bin/bash", "-c", "--" ] + args: [ "ls /tmp && while true; do sleep 30; done;" ] + volumeMounts: + - name: secret-volume + mountPath: /etc/secret-volume + volumes: + - name: secret-volume + secret: + secretName: stackdriver-secrets +` + +const serviceAccountJson = ` +{ + "type": "service_account", +}` From 7cf0851ecaffb7e7aefb84c120a0b8c42a8986f9 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Tue, 1 Mar 2022 17:00:10 +0200 Subject: [PATCH 02/13] Make e2e test work with the test GCP project Signed-off-by: Ram Cohen --- tests/scalers/gcp-stackdriver.test.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts index ec8fd4ec440..fe7f34be16d 100644 --- a/tests/scalers/gcp-stackdriver.test.ts +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -1,7 +1,4 @@ -import * as async from 'async' import * as fs from 'fs' -import * as http from 'http' -import fetch from 'node-fetch' import * as sh from 'shelljs' import * as tmp from 'tmp' import test from 'ava' @@ -10,15 +7,20 @@ const testNamespace = 'gcp-stackdriver-test' const bucketName = 'keda-test-stackdriver-bucket' const deploymentName = 'dummy-consumer' const maxReplicaCount = '3' -const projectId = 'nth-hybrid-341214' const gsPrefix = `kubectl exec --namespace ${testNamespace} deploy/gcp-sdk -- ` +const gcpKey = process.env['GCP_SP_KEY'] + +var projectId: string test.before(t => { sh.exec(`kubectl create namespace ${testNamespace}`) + const creds = JSON.parse(gcpKey) + projectId = creds.project_id + // deploy dummy consumer app, scaled object etc. const tmpFile = tmp.fileSync() - fs.writeFileSync(tmpFile.name, deployYaml.replace("{{GCP_CREDS}}", Buffer.from(serviceAccountJson).toString("base64"))) + fs.writeFileSync(tmpFile.name, deployYaml.replace("{{GCP_CREDS}}", Buffer.from(gcpKey).toString("base64"))) t.is( 0, @@ -203,8 +205,3 @@ spec: secret: secretName: stackdriver-secrets ` - -const serviceAccountJson = ` -{ - "type": "service_account", -}` From e89407a569ba3c08908e0a944be9cfc524247005 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Tue, 1 Mar 2022 18:49:58 +0200 Subject: [PATCH 03/13] Use methods from helpers utilities Signed-off-by: Ram Cohen --- tests/scalers/gcp-stackdriver.test.ts | 62 ++++++++------------------- 1 file changed, 19 insertions(+), 43 deletions(-) diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts index fe7f34be16d..f697555dd83 100644 --- a/tests/scalers/gcp-stackdriver.test.ts +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -2,21 +2,18 @@ import * as fs from 'fs' import * as sh from 'shelljs' import * as tmp from 'tmp' import test from 'ava' +import { createNamespace, waitForDeploymentReplicaCount } from './helpers'; +const gcpKey = process.env['GCP_SP_KEY'] +const projectId = JSON.parse(gcpKey).project_id const testNamespace = 'gcp-stackdriver-test' const bucketName = 'keda-test-stackdriver-bucket' const deploymentName = 'dummy-consumer' const maxReplicaCount = '3' const gsPrefix = `kubectl exec --namespace ${testNamespace} deploy/gcp-sdk -- ` -const gcpKey = process.env['GCP_SP_KEY'] - -var projectId: string test.before(t => { - sh.exec(`kubectl create namespace ${testNamespace}`) - - const creds = JSON.parse(gcpKey) - projectId = creds.project_id + createNamespace(testNamespace) // deploy dummy consumer app, scaled object etc. const tmpFile = tmp.fileSync() @@ -30,14 +27,11 @@ test.before(t => { }) -test.serial('Deployment should have 0 replicas on start', t => { - const replicaCount = sh.exec( - `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` - ).stdout - t.is(replicaCount, '0', 'replica count should start out as 0') +test.serial('Deployment should have 0 replicas on start', async t => { + t.true(await waitForDeploymentReplicaCount(0, deploymentName, testNamespace, 30, 2000), 'replica count should start out as 0') }) -test.serial('creating the gcp-sdk pod should work..', t => { +test.serial('creating the gcp-sdk pod should work..', async t => { let tmpFile = tmp.fileSync() fs.writeFileSync(tmpFile.name, gcpSdkYaml) t.is( @@ -46,15 +40,8 @@ test.serial('creating the gcp-sdk pod should work..', t => { 'creating the gcp-sdk pod should work..' ) - // wait for the gcp-sdkpod to be ready - let gcpSdkReadyReplicaCount = '0' - for (let i = 0; i < 30; i++) { - gcpSdkReadyReplicaCount = sh.exec(`kubectl get deploy/gcp-sdk -n ${testNamespace} -o jsonpath='{.status.readyReplicas}'`).stdout - if (gcpSdkReadyReplicaCount != '1') { - sh.exec('sleep 2s') - } - } - t.is('1', gcpSdkReadyReplicaCount, 'GCP-SDK pod is not in a ready state') + // wait for the gcp-sdk pod to be ready + t.true(await waitForDeploymentReplicaCount(1, 'gcp-sdk', testNamespace, 30, 2000), 'GCP-SDK pod is not in a ready state') }) test.serial('initializing the gcp-sdk pod should work..', t => { @@ -72,41 +59,30 @@ test.serial('initializing the gcp-sdk pod should work..', t => { sh.exec(gsPrefix + `gsutil mb gs://${bucketName}`) }) -test.serial(`Deployment should scale to ${maxReplicaCount} (the max) then back to 0`, t => { - let replicaCount = '0' - +test.serial(`Deployment should scale to ${maxReplicaCount} (the max) then back to 0`, async t => { // Wait for the number of replicas to be scaled up to maxReplicaCount - for (let i = 0; i < 60 && replicaCount != maxReplicaCount; i++) { + var haveAllReplicas = false + for (let i = 0; i < 60 && !haveAllReplicas; i++) { // Upload a file to generate traffic t.is( 0, sh.exec(gsPrefix + `gsutil cp /usr/lib/google-cloud-sdk/bin/gsutil gs://${bucketName}`).code, 'Copying an object should work..' ) - replicaCount = sh.exec( - `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` - ).stdout - if (replicaCount != maxReplicaCount) { - sh.exec('sleep 2s') + if (await waitForDeploymentReplicaCount(parseInt(maxReplicaCount, 10), deploymentName, testNamespace, 1, 2000)) { + haveAllReplicas = true } } - t.is(maxReplicaCount, replicaCount, `Replica count should be ${maxReplicaCount} after 120 seconds but is ${replicaCount}`) + t.true(haveAllReplicas, `Replica count should be ${maxReplicaCount} after 120 seconds`) // Wait for the number of replicas to be scaled down to 0 - for (let i = 0; i < 30 && replicaCount !== '0'; i++) { - replicaCount = sh.exec( - `kubectl get deployment.apps/${deploymentName} --namespace ${testNamespace} -o jsonpath="{.spec.replicas}"` - ).stdout - if (replicaCount != '0') { - sh.exec('sleep 10s') - } - } - - t.is('0', replicaCount, 'Replica count should be 0 after 3 minutes') + t.true( + await waitForDeploymentReplicaCount(0, deploymentName, testNamespace, 30, 10000), + `Replica count should be 0 after 5 minutes`) }) -test.after.always.cb('clean up fake GCS deployment', t => { +test.after.always.cb('clean up', t => { // Cleanup the bucket sh.exec(gsPrefix + `gsutil rm -r gs://${bucketName}`) From 7bebc0a6c9fb6019580245e353e44cc511b07ced Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Thu, 3 Mar 2022 15:37:15 +0200 Subject: [PATCH 04/13] Wait for gcp-sdk container to be ready in tests Signed-off-by: Ram Cohen --- tests/scalers/gcp-stackdriver.test.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts index f697555dd83..0146235f854 100644 --- a/tests/scalers/gcp-stackdriver.test.ts +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -45,16 +45,17 @@ test.serial('creating the gcp-sdk pod should work..', async t => { }) test.serial('initializing the gcp-sdk pod should work..', t => { + sh.exec(`kubectl wait --for=condition=ready --namespace ${testNamespace} pod -l app=gcp-sdk --timeout=30s`) + sh.exec('sleep 5s') + // Authenticate to GCP + const creds = JSON.parse(gcpKey) t.is( 0, - sh.exec(gsPrefix + `gcloud auth activate-service-account --key-file /etc/secret-volume/GOOGLE_APPLICATION_CREDENTIALS_JSON`).code, - 'Executing remote command on gc-sdk should work..' + sh.exec(gsPrefix + `gcloud auth activate-service-account ${creds.client_email} --key-file /etc/secret-volume/creds.json --project=${creds.project_id}`).code, + 'Setting GCP authentication on gcp-sdk should work..' ) - // Set project id - sh.exec(gsPrefix + `gcloud config set project ${projectId}`) - // Create bucket sh.exec(gsPrefix + `gsutil mb gs://${bucketName}`) }) @@ -84,12 +85,12 @@ test.serial(`Deployment should scale to ${maxReplicaCount} (the max) then back t test.after.always.cb('clean up', t => { // Cleanup the bucket - sh.exec(gsPrefix + `gsutil rm -r gs://${bucketName}`) + sh.exec(gsPrefix + `gsutil -m rm -r gs://${bucketName}`) sh.exec(`kubectl delete deployment.apps/${deploymentName} --namespace ${testNamespace}`) sh.exec(`kubectl delete namespace ${testNamespace}`) - t.end() + t.end() }) @@ -120,7 +121,7 @@ spec: valueFrom: secretKeyRef: name: stackdriver-secrets - key: GOOGLE_APPLICATION_CREDENTIALS_JSON + key: creds.json --- apiVersion: v1 kind: Secret @@ -128,7 +129,7 @@ metadata: name: stackdriver-secrets type: Opaque data: - GOOGLE_APPLICATION_CREDENTIALS_JSON: {{GCP_CREDS}} + creds.json: {{GCP_CREDS}} --- apiVersion: keda.sh/v1alpha1 kind: ScaledObject From 79568a3a95738ce3d685916b2cf719be1ba81978 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Mon, 14 Mar 2022 17:50:31 +0200 Subject: [PATCH 05/13] Calculate metric name beforehand Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go index bd9864e001e..1ea76f26376 100644 --- a/pkg/scalers/gcp_stackdriver_scaler.go +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -30,8 +30,7 @@ type stackdriverMetadata struct { targetValue int metricName string - gcpAuthorization gcpAuthorizationMetadata - scalerIndex int + gcpAuthorization *gcpAuthorizationMetadata } var gcpStackdriverLog = logf.Log.WithName("gcp_stackdriver_scaler") @@ -77,7 +76,8 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error return nil, fmt.Errorf("no metricName given") } - meta.metricName = kedautil.NormalizeString(fmt.Sprintf("gcp-stackdriver-%s", val)) + name := kedautil.NormalizeString(fmt.Sprintf("gcp-stackdriver-%s", val)) + meta.metricName = GenerateMetricNameWithIndex(config.ScalerIndex, name) } else { return nil, fmt.Errorf("no metricName given") } @@ -96,8 +96,7 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error if err != nil { return nil, err } - meta.gcpAuthorization = *auth - meta.scalerIndex = config.ScalerIndex + meta.gcpAuthorization = auth return &meta, nil } @@ -129,7 +128,7 @@ func (s *stackdriverScaler) GetMetricSpecForScaling(context.Context) []v2beta2.M externalMetric := &v2beta2.ExternalMetricSource{ Metric: v2beta2.MetricIdentifier{ - Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, s.metadata.metricName), + Name: s.metadata.metricName, }, Target: v2beta2.MetricTarget{ Type: v2beta2.AverageValueMetricType, From 3961fab35277a07d1fab7ace178d35866bf8c0ea Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Mon, 14 Mar 2022 18:49:36 +0200 Subject: [PATCH 06/13] Rename filter var in test Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler_test.go b/pkg/scalers/gcp_stackdriver_scaler_test.go index 8a189c2a2c4..7b200197772 100644 --- a/pkg/scalers/gcp_stackdriver_scaler_test.go +++ b/pkg/scalers/gcp_stackdriver_scaler_test.go @@ -21,28 +21,28 @@ type gcpStackdriverMetricIdentifier struct { name string } -var filter = "metric.type=\"storage.googleapis.com/storage/object_count\" resource.type=\"gcs_bucket\"" +var sdFilter = "metric.type=\"storage.googleapis.com/storage/object_count\" resource.type=\"gcs_bucket\"" var testStackdriverMetadata = []parseStackdriverMetadataTestData{ {map[string]string{}, map[string]string{}, true}, // all properly formed - {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, // all required properly formed - {nil, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, // missing projectId - {nil, map[string]string{"filter": filter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // missing filter {nil, map[string]string{"projectId": "myProject", "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // missing metric name - {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // missing credentials - {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "7", "metricName": "gcs_bucket"}, true}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket"}, true}, // malformed targetValue - {nil, map[string]string{"projectId": "myProject", "filter": filter, "targetValue": "aa", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "aa", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // Credentials from AuthParams - {map[string]string{"GoogleApplicationCredentials": "Creds", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket"}, false}, + {map[string]string{"GoogleApplicationCredentials": "Creds", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket"}, false}, // Credentials from AuthParams with empty creds - {map[string]string{"GoogleApplicationCredentials": "", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": filter, "metricName": "gcs_bucket"}, true}, + {map[string]string{"GoogleApplicationCredentials": "", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket"}, true}, } var gcpStackdriverMetricIdentifiers = []gcpStackdriverMetricIdentifier{ From 7484f706841046f5e667aba97c81157ff40f0bdd Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Mon, 14 Mar 2022 20:05:03 +0200 Subject: [PATCH 07/13] Rename projectId to projectID Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go index 1ea76f26376..6df3cf6d0c5 100644 --- a/pkg/scalers/gcp_stackdriver_scaler.go +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -25,7 +25,7 @@ type stackdriverScaler struct { } type stackdriverMetadata struct { - projectId string + projectID string filter string targetValue int metricName string @@ -56,7 +56,7 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error return nil, fmt.Errorf("no projectId name given") } - meta.projectId = val + meta.projectID = val } else { return nil, fmt.Errorf("no projectId name given") } @@ -188,10 +188,10 @@ func (s *stackdriverScaler) getMetrics(ctx context.Context) (int64, error) { } } - val, err := s.client.GetMetrics(ctx, s.metadata.filter, s.metadata.projectId) + val, err := s.client.GetMetrics(ctx, s.metadata.filter, s.metadata.projectID) if err == nil { gcpStackdriverLog.V(1).Info( - fmt.Sprintf("Getting metrics for project %s and filter %s. Result: %d", s.metadata.projectId, s.metadata.filter, val)) + fmt.Sprintf("Getting metrics for project %s and filter %s. Result: %d", s.metadata.projectID, s.metadata.filter, val)) } return val, err From d4a58e81ef286caaafc1c7d582530e98e23e3ed9 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Mon, 14 Mar 2022 20:28:12 +0200 Subject: [PATCH 08/13] Remove metricName parameter Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 12 ++---------- pkg/scalers/gcp_stackdriver_scaler_test.go | 22 ++++++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go index 6df3cf6d0c5..b349dc834ce 100644 --- a/pkg/scalers/gcp_stackdriver_scaler.go +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -71,16 +71,8 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error return nil, fmt.Errorf("no filter given") } - if val, ok := config.TriggerMetadata["metricName"]; ok { - if val == "" { - return nil, fmt.Errorf("no metricName given") - } - - name := kedautil.NormalizeString(fmt.Sprintf("gcp-stackdriver-%s", val)) - meta.metricName = GenerateMetricNameWithIndex(config.ScalerIndex, name) - } else { - return nil, fmt.Errorf("no metricName given") - } + name := kedautil.NormalizeString(fmt.Sprintf("gcp-stackdriver-%s", meta.projectID)) + meta.metricName = GenerateMetricNameWithIndex(config.ScalerIndex, name) if val, ok := config.TriggerMetadata["targetValue"]; ok { targetValue, err := strconv.Atoi(val) diff --git a/pkg/scalers/gcp_stackdriver_scaler_test.go b/pkg/scalers/gcp_stackdriver_scaler_test.go index 7b200197772..ed278bdf7c7 100644 --- a/pkg/scalers/gcp_stackdriver_scaler_test.go +++ b/pkg/scalers/gcp_stackdriver_scaler_test.go @@ -26,28 +26,26 @@ var sdFilter = "metric.type=\"storage.googleapis.com/storage/object_count\" reso var testStackdriverMetadata = []parseStackdriverMetadataTestData{ {map[string]string{}, map[string]string{}, true}, // all properly formed - {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, // all required properly formed - {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, false}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "credentialsFromEnv": "SAMPLE_CREDS"}, false}, // missing projectId - {nil, map[string]string{"filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"filter": sdFilter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // missing filter - {nil, map[string]string{"projectId": "myProject", "targetValue": "7", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, - // missing metric name - {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"projectId": "myProject", "targetValue": "7", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // missing credentials - {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7", "metricName": "gcs_bucket"}, true}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "7"}, true}, // malformed targetValue - {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "aa", "metricName": "gcs_bucket", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, + {nil, map[string]string{"projectId": "myProject", "filter": sdFilter, "targetValue": "aa", "credentialsFromEnv": "SAMPLE_CREDS"}, true}, // Credentials from AuthParams - {map[string]string{"GoogleApplicationCredentials": "Creds", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket"}, false}, + {map[string]string{"GoogleApplicationCredentials": "Creds", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter}, false}, // Credentials from AuthParams with empty creds - {map[string]string{"GoogleApplicationCredentials": "", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter, "metricName": "gcs_bucket"}, true}, + {map[string]string{"GoogleApplicationCredentials": "", "podIdentityOwner": ""}, map[string]string{"projectId": "myProject", "filter": sdFilter}, true}, } var gcpStackdriverMetricIdentifiers = []gcpStackdriverMetricIdentifier{ - {&testStackdriverMetadata[1], 0, "s0-gcp-stackdriver-gcs_bucket"}, - {&testStackdriverMetadata[1], 1, "s1-gcp-stackdriver-gcs_bucket"}, + {&testStackdriverMetadata[1], 0, "s0-gcp-stackdriver-myProject"}, + {&testStackdriverMetadata[1], 1, "s1-gcp-stackdriver-myProject"}, } func TestStackdriverParseMetadata(t *testing.T) { From 147ef4ea1d491524aed104c63d125bab3889a7d1 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Wed, 16 Mar 2022 16:36:44 +0200 Subject: [PATCH 09/13] Update changelog Signed-off-by: Ram Cohen --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c55997c86..0b8fd0421de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - **General:** Introduce new AWS DynamoDB Scaler ([#2486](https://github.com/kedacore/keda/issues/2482)) - **General:** Introduce new Azure Data Explorer Scaler ([#1488](https://github.com/kedacore/keda/issues/1488)) +- **General:** Introduce new GCP Stackdriver Scaler ([#2661](https://github.com/kedacore/keda/issues/2661)) - **General:** Introduce new GCP Storage Scaler ([#2628](https://github.com/kedacore/keda/issues/2628)) - **General:** Introduce ARM-based container image for KEDA ([#2263](https://github.com/kedacore/keda/issues/2263)|[#2262](https://github.com/kedacore/keda/issues/2262)) - **General:** Provide support for authentication via Azure Key Vault ([#900](https://github.com/kedacore/keda/issues/900)) From 67ee1d276705449f9b71b4364d99d99412443bfa Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Tue, 22 Mar 2022 15:57:36 +0200 Subject: [PATCH 10/13] Move stackdriver initialization to scaler init Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 49 +++++++++++++-------------- pkg/scaling/scale_handler.go | 2 +- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go index b349dc834ce..42604ffad14 100644 --- a/pkg/scalers/gcp_stackdriver_scaler.go +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -36,14 +36,21 @@ type stackdriverMetadata struct { var gcpStackdriverLog = logf.Log.WithName("gcp_stackdriver_scaler") // NewStackdriverScaler creates a new stackdriverScaler -func NewStackdriverScaler(config *ScalerConfig) (Scaler, error) { +func NewStackdriverScaler(ctx context.Context, config *ScalerConfig) (Scaler, error) { meta, err := parseStackdriverMetadata(config) if err != nil { return nil, fmt.Errorf("error parsing Stackdriver metadata: %s", err) } + client, err := initializeStackdriverClient(ctx, meta.gcpAuthorization) + if err != nil { + gcpStackdriverLog.Error(err, "Failed to create stack driver client") + return nil, err + } + return &stackdriverScaler{ metadata: meta, + client: client, }, nil } @@ -92,6 +99,22 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error return &meta, nil } +func initializeStackdriverClient(ctx context.Context, gcpAuthorization *gcpAuthorizationMetadata) (*StackDriverClient, error) { + var client *StackDriverClient + var err error + if gcpAuthorization.podIdentityProviderEnabled { + client, err = NewStackDriverClientPodIdentity(ctx) + } else { + client, err = NewStackDriverClient(ctx, gcpAuthorization.GoogleApplicationCredentials) + } + + if err != nil { + gcpStackdriverLog.Error(err, "Failed to create stack driver client") + return nil, err + } + return client, nil +} + func (s *stackdriverScaler) IsActive(ctx context.Context) (bool, error) { value, err := s.getMetrics(ctx) if err != nil { @@ -154,32 +177,8 @@ func (s *stackdriverScaler) GetMetrics(ctx context.Context, metricName string, m return append([]external_metrics.ExternalMetricValue{}, metric), nil } -func (s *stackdriverScaler) setStackdriverClient(ctx context.Context) error { - var client *StackDriverClient - var err error - if s.metadata.gcpAuthorization.podIdentityProviderEnabled { - client, err = NewStackDriverClientPodIdentity(ctx) - } else { - client, err = NewStackDriverClient(ctx, s.metadata.gcpAuthorization.GoogleApplicationCredentials) - } - - if err != nil { - gcpStackdriverLog.Error(err, "Failed to create stack driver client") - return err - } - s.client = client - return nil -} - // getMetrics gets metric type value from stackdriver api func (s *stackdriverScaler) getMetrics(ctx context.Context) (int64, error) { - if s.client == nil { - err := s.setStackdriverClient(ctx) - if err != nil { - return 0, err - } - } - val, err := s.client.GetMetrics(ctx, s.metadata.filter, s.metadata.projectID) if err == nil { gcpStackdriverLog.V(1).Info( diff --git a/pkg/scaling/scale_handler.go b/pkg/scaling/scale_handler.go index 7ebeb52910f..acb3e5bcef4 100644 --- a/pkg/scaling/scale_handler.go +++ b/pkg/scaling/scale_handler.go @@ -395,7 +395,7 @@ func buildScaler(ctx context.Context, client client.Client, triggerType string, case "gcp-pubsub": return scalers.NewPubSubScaler(config) case "gcp-stackdriver": - return scalers.NewStackdriverScaler(config) + return scalers.NewStackdriverScaler(ctx, config) case "gcp-storage": return scalers.NewGcsScaler(config) case "graphite": From 43d64501e75324ea90dcea48e23cdb0d2a763147 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Tue, 1 Mar 2022 17:00:10 +0200 Subject: [PATCH 11/13] Make e2e test work with the test GCP project Signed-off-by: Ram Cohen --- tests/scalers/gcp-stackdriver.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts index 0146235f854..7db22ef3a1b 100644 --- a/tests/scalers/gcp-stackdriver.test.ts +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -11,10 +11,16 @@ const bucketName = 'keda-test-stackdriver-bucket' const deploymentName = 'dummy-consumer' const maxReplicaCount = '3' const gsPrefix = `kubectl exec --namespace ${testNamespace} deploy/gcp-sdk -- ` +const gcpKey = process.env['GCP_SP_KEY'] + +var projectId: string test.before(t => { createNamespace(testNamespace) + const creds = JSON.parse(gcpKey) + projectId = creds.project_id + // deploy dummy consumer app, scaled object etc. const tmpFile = tmp.fileSync() fs.writeFileSync(tmpFile.name, deployYaml.replace("{{GCP_CREDS}}", Buffer.from(gcpKey).toString("base64"))) From bf5f477411ba5f0bf802a14828802077d0b8616c Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Tue, 1 Mar 2022 18:49:58 +0200 Subject: [PATCH 12/13] Use methods from helpers utilities Signed-off-by: Ram Cohen --- tests/scalers/gcp-stackdriver.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/scalers/gcp-stackdriver.test.ts b/tests/scalers/gcp-stackdriver.test.ts index 7db22ef3a1b..0146235f854 100644 --- a/tests/scalers/gcp-stackdriver.test.ts +++ b/tests/scalers/gcp-stackdriver.test.ts @@ -11,16 +11,10 @@ const bucketName = 'keda-test-stackdriver-bucket' const deploymentName = 'dummy-consumer' const maxReplicaCount = '3' const gsPrefix = `kubectl exec --namespace ${testNamespace} deploy/gcp-sdk -- ` -const gcpKey = process.env['GCP_SP_KEY'] - -var projectId: string test.before(t => { createNamespace(testNamespace) - const creds = JSON.parse(gcpKey) - projectId = creds.project_id - // deploy dummy consumer app, scaled object etc. const tmpFile = tmp.fileSync() fs.writeFileSync(tmpFile.name, deployYaml.replace("{{GCP_CREDS}}", Buffer.from(gcpKey).toString("base64"))) From 5b96ba77976807c3e1e999fd57b968421f9403e9 Mon Sep 17 00:00:00 2001 From: Ram Cohen Date: Wed, 23 Mar 2022 09:58:28 +0200 Subject: [PATCH 13/13] Change targetValue to int64 Signed-off-by: Ram Cohen --- pkg/scalers/gcp_stackdriver_scaler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/scalers/gcp_stackdriver_scaler.go b/pkg/scalers/gcp_stackdriver_scaler.go index 42604ffad14..df2c7990a08 100644 --- a/pkg/scalers/gcp_stackdriver_scaler.go +++ b/pkg/scalers/gcp_stackdriver_scaler.go @@ -27,7 +27,7 @@ type stackdriverScaler struct { type stackdriverMetadata struct { projectID string filter string - targetValue int + targetValue int64 metricName string gcpAuthorization *gcpAuthorizationMetadata @@ -82,7 +82,7 @@ func parseStackdriverMetadata(config *ScalerConfig) (*stackdriverMetadata, error meta.metricName = GenerateMetricNameWithIndex(config.ScalerIndex, name) if val, ok := config.TriggerMetadata["targetValue"]; ok { - targetValue, err := strconv.Atoi(val) + targetValue, err := strconv.ParseInt(val, 10, 64) if err != nil { gcpStackdriverLog.Error(err, "Error parsing targetValue") return nil, fmt.Errorf("error parsing targetValue: %s", err.Error()) @@ -139,7 +139,7 @@ func (s *stackdriverScaler) Close(context.Context) error { // GetMetricSpecForScaling returns the metric spec for the HPA func (s *stackdriverScaler) GetMetricSpecForScaling(context.Context) []v2beta2.MetricSpec { // Construct the target value as a quantity - targetValueQty := resource.NewQuantity(int64(s.metadata.targetValue), resource.DecimalSI) + targetValueQty := resource.NewQuantity(s.metadata.targetValue, resource.DecimalSI) externalMetric := &v2beta2.ExternalMetricSource{ Metric: v2beta2.MetricIdentifier{