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 (kedacore#1517)

Signed-off-by: Jonas Matos <js.matos@sidi.org.br>

Co-authored-by: Jonas Matos js.matos@sidi.org.br
Co-authored-by: Thiago Teodoro Rodrigues t.teodoro@sidi.org.br
  • Loading branch information
JonasMatos0 authored and ycabrer committed Mar 1, 2021
1 parent 754bed2 commit 23042bf
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Improve error reporting in prometheus scaler ([#1497](https://github.com/kedacore/keda/pull/1497))
- Check that metricNames are unique in ScaledObject ([#1390](https://github.com/kedacore/keda/pull/1390))
- Serve OpenAPI spec from KEDA Metrics Apiserver ([#1512](https://github.com/kedacore/keda/pull/1512))
- Support metrics with multiple dimensions and configurable metricValues on AWS Cloudwatch Scaler ([#1230](https://github.com/kedacore/keda/issues/1230))

### Breaking Changes

Expand Down
75 changes: 58 additions & 17 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 @@ -64,11 +65,44 @@ func NewAwsCloudwatchScaler(config *ScalerConfig) (Scaler, error) {
}, nil
}

func parseMetricValues(config *ScalerConfig) (*awsCloudwatchMetadata, error) {
metricsMeta := awsCloudwatchMetadata{}

if val, ok := config.TriggerMetadata["metricCollectionTime"]; ok && val != "" {
if n, ok := strconv.ParseInt(val, 10, 64); ok == nil {
metricsMeta.metricCollectionTime = n
} else {
return nil, fmt.Errorf("metricCollectionTime not a valid number")
}
} else {
metricsMeta.metricCollectionTime = defaultMetricCollectionTime
}

if val, ok := config.TriggerMetadata["metricStatPeriod"]; ok && val != "" {
if n, ok := strconv.ParseInt(val, 10, 64); ok == nil {
metricsMeta.metricStatPeriod = n
} else {
return nil, fmt.Errorf("metricStatPeriod not a valid number")
}
} else {
metricsMeta.metricStatPeriod = defaultMetricStatPeriod
}

if val, ok := config.TriggerMetadata["metricStat"]; ok && val != "" {
metricsMeta.metricStat = val
} else {
metricsMeta.metricStat = defaultMetricStat
}

return &metricsMeta, nil
}

func parseAwsCloudwatchMetadata(config *ScalerConfig) (*awsCloudwatchMetadata, error) {
meta := awsCloudwatchMetadata{}
meta.metricCollectionTime = defaultMetricCollectionTime
meta.metricStat = defaultMetricStat
meta.metricStatPeriod = defaultMetricStatPeriod
meta, err := parseMetricValues(config)

if err != nil {
return nil, fmt.Errorf("an error occurred when the scaler tried to get the metrics values")
}

if val, ok := config.TriggerMetadata["namespace"]; ok && val != "" {
meta.namespace = val
Expand All @@ -83,17 +117,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 @@ -151,7 +189,7 @@ func parseAwsCloudwatchMetadata(config *ScalerConfig) (*awsCloudwatchMetadata, e

meta.awsAuthorization = auth

return &meta, nil
return meta, nil
}

func (c *awsCloudwatchScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) {
Expand All @@ -175,7 +213,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 +261,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 +277,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
66 changes: 66 additions & 0 deletions pkg/scalers/aws_cloudwatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,72 @@ 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"},
}

var awsCloudwatchMetricIdentifiers = []awsCloudwatchMetricIdentifier{
Expand Down

0 comments on commit 23042bf

Please sign in to comment.