Skip to content

Commit

Permalink
Support multi dimension values and configurable metricValues on AWS C…
Browse files Browse the repository at this point in the history
…loudwatch scaler

Signed-off-by: Jonas Matos <js.matos@sidi.org.br>
Signed-off-by: Thiago Teodoro Rodrigues <t.teodoro@sidi.org.br>
  • Loading branch information
Jonas Matos committed Jan 20, 2021
1 parent 0250181 commit 8c46e76
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 15 deletions.
61 changes: 46 additions & 15 deletions pkg/scalers/aws_cloudwatch_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()),
Expand All @@ -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),
Expand Down
90 changes: 90 additions & 0 deletions pkg/scalers/aws_cloudwatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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{
Expand Down

0 comments on commit 8c46e76

Please sign in to comment.