diff --git a/pkg/scalers/aws_cloudwatch_scaler.go b/pkg/scalers/aws_cloudwatch_scaler.go index 554d53bac06..9bc3973c168 100644 --- a/pkg/scalers/aws_cloudwatch_scaler.go +++ b/pkg/scalers/aws_cloudwatch_scaler.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strconv" + "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -35,8 +36,8 @@ type awsCloudwatchScaler struct { type awsCloudwatchMetadata struct { namespace string metricsName string - dimensionName string - dimensionValue string + dimensionName []string + dimensionValue []string targetMetricValue float64 minMetricValue float64 @@ -66,9 +67,32 @@ func NewAwsCloudwatchScaler(config *ScalerConfig) (Scaler, error) { func parseAwsCloudwatchMetadata(config *ScalerConfig) (*awsCloudwatchMetadata, error) { meta := awsCloudwatchMetadata{} - meta.metricCollectionTime = defaultMetricCollectionTime - meta.metricStat = defaultMetricStat - meta.metricStatPeriod = defaultMetricStatPeriod + + if val, ok := config.TriggerMetadata["metricCollectionTime"]; ok && val != "" { + if n, ok := strconv.ParseInt(val, 10, 64); ok == nil { + meta.metricCollectionTime = n + } else { + return nil, fmt.Errorf("metricCollectionTime not a valid number") + } + } else { + meta.metricCollectionTime = defaultMetricCollectionTime + } + + if val, ok := config.TriggerMetadata["metricStatPeriod"]; ok && val != "" { + if n, ok := strconv.ParseInt(val, 10, 64); ok == nil { + meta.metricStatPeriod = n + } else { + return nil, fmt.Errorf("metricStatPeriod not a valid number") + } + } else { + meta.metricStatPeriod = defaultMetricStatPeriod + } + + if val, ok := config.TriggerMetadata["metricStat"]; ok && val != "" { + meta.metricStat = val + } else { + meta.metricStat = defaultMetricStat + } if val, ok := config.TriggerMetadata["namespace"]; ok && val != "" { meta.namespace = val @@ -83,17 +107,21 @@ func parseAwsCloudwatchMetadata(config *ScalerConfig) (*awsCloudwatchMetadata, e } if val, ok := config.TriggerMetadata["dimensionName"]; ok && val != "" { - meta.dimensionName = val + meta.dimensionName = strings.Split(val, ";") } else { return nil, fmt.Errorf("dimension name not given") } if val, ok := config.TriggerMetadata["dimensionValue"]; ok && val != "" { - meta.dimensionValue = val + meta.dimensionValue = strings.Split(val, ";") } else { return nil, fmt.Errorf("dimension value not given") } + if len(meta.dimensionName) != len(meta.dimensionValue) { + return nil, fmt.Errorf("dimensionName and dimensionValue are not matching in size") + } + if val, ok := config.TriggerMetadata["targetMetricValue"]; ok && val != "" { targetMetricValue, err := strconv.ParseFloat(val, 64) if err != nil { @@ -175,7 +203,7 @@ func (c *awsCloudwatchScaler) GetMetricSpecForScaling() []v2beta2.MetricSpec { targetMetricValue := resource.NewQuantity(int64(c.metadata.targetMetricValue), resource.DecimalSI) externalMetric := &v2beta2.ExternalMetricSource{ Metric: v2beta2.MetricIdentifier{ - Name: kedautil.NormalizeString(fmt.Sprintf("%s-%s-%s-%s", "aws-cloudwatch", c.metadata.namespace, c.metadata.dimensionName, c.metadata.dimensionValue)), + Name: kedautil.NormalizeString(fmt.Sprintf("%s-%s-%s-%s", "aws-cloudwatch", c.metadata.namespace, c.metadata.dimensionName[0], c.metadata.dimensionValue[0])), }, Target: v2beta2.MetricTarget{ Type: v2beta2.AverageValueMetricType, @@ -223,6 +251,14 @@ func (c *awsCloudwatchScaler) GetCloudwatchMetrics() (float64, error) { }) } + dimensions := []*cloudwatch.Dimension{} + for i := range c.metadata.dimensionName { + dimensions = append(dimensions, &cloudwatch.Dimension{ + Name: &c.metadata.dimensionName[i], + Value: &c.metadata.dimensionValue[i], + }) + } + input := cloudwatch.GetMetricDataInput{ StartTime: aws.Time(time.Now().Add(time.Second * -1 * time.Duration(c.metadata.metricCollectionTime))), EndTime: aws.Time(time.Now()), @@ -231,13 +267,8 @@ func (c *awsCloudwatchScaler) GetCloudwatchMetrics() (float64, error) { Id: aws.String("c1"), MetricStat: &cloudwatch.MetricStat{ Metric: &cloudwatch.Metric{ - Namespace: aws.String(c.metadata.namespace), - Dimensions: []*cloudwatch.Dimension{ - { - Name: aws.String(c.metadata.dimensionName), - Value: aws.String(c.metadata.dimensionValue), - }, - }, + Namespace: aws.String(c.metadata.namespace), + Dimensions: dimensions, MetricName: aws.String(c.metadata.metricsName), }, Period: aws.Int64(c.metadata.metricStatPeriod), diff --git a/pkg/scalers/aws_cloudwatch_test.go b/pkg/scalers/aws_cloudwatch_test.go index b2520e6e5f6..fe5bb2da196 100644 --- a/pkg/scalers/aws_cloudwatch_test.go +++ b/pkg/scalers/aws_cloudwatch_test.go @@ -45,6 +45,18 @@ var testAWSCloudwatchMetadata = []parseAWSCloudwatchMetadataTestData{ testAWSAuthentication, false, "properly formed cloudwatch query and awsRegion"}, + // properly formed cloudwatch query with multiple dimensions + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName;QueueName", + "dimensionValue": "keda;keda2", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "awsRegion": "eu-west-1"}, + testAWSAuthentication, + false, + "properly formed cloudwatch query with multiple dimensions"}, // Properly formed cloudwatch query with optional parameters {map[string]string{ "namespace": "AWS/SQS", @@ -164,6 +176,84 @@ var testAWSCloudwatchMetadata = []parseAWSCloudwatchMetadataTestData{ map[string]string{}, false, "with AWS Role assigned on KEDA operator itself"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricCollectionTime": "a", + "metricStat": "Average", + "metricStatPeriod": "300", + "awsRegion": "eu-west-1", + "identityOwner": "operator"}, + map[string]string{}, + true, + "if metricCollectionTime assigned with a string, need to be a number"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricCollectionTime": "300", + "metricStat": "Average", + "metricStatPeriod": "a", + "awsRegion": "eu-west-1", + "identityOwner": "operator"}, + map[string]string{}, + true, + "if metricStatPeriod assigned with a string, need to be a number"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricStat": "Average", + "metricStatPeriod": "300", + "awsRegion": "eu-west-1"}, + testAWSAuthentication, false, + "Missing metricCollectionTime not generate error because will get the default value"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricCollectionTime": "300", + "metricStatPeriod": "300", + "awsRegion": "eu-west-1"}, + testAWSAuthentication, false, + "Missing metricStat not generate error because will get the default value"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricCollectionTime": "300", + "metricStat": "Average", + "awsRegion": "eu-west-1"}, + testAWSAuthentication, false, + "Missing metricStatPeriod not generate error because will get the default value"}, + {map[string]string{ + "namespace": "AWS/SQS", + "dimensionName": "QueueName", + "dimensionValue": "keda", + "metricName": "ApproximateNumberOfMessagesVisible", + "targetMetricValue": "2", + "minMetricValue": "0", + "metricCollectionTime": "300", + "metricStat": "Average", + "awsRegion": "eu-west-1"}, + testAWSAuthentication, false, + "Missing metricStatPeriod not generate error because will get the default value"}, } var awsCloudwatchMetricIdentifiers = []awsCloudwatchMetricIdentifier{