2019-11-14 03:59:41 -06:00
package cloudwatch
import (
"testing"
2022-11-28 05:39:12 -06:00
"github.com/aws/aws-sdk-go/aws"
2022-11-02 09:14:02 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
2022-11-28 05:39:12 -06:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2019-11-14 03:59:41 -06:00
)
2021-11-30 03:53:31 -06:00
func TestMetricDataQueryBuilder ( t * testing . T ) {
2021-09-23 09:03:58 -05:00
t . Run ( "buildMetricDataQuery" , func ( t * testing . T ) {
2021-11-30 03:53:31 -06:00
t . Run ( "should use metric stat" , func ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-11-30 03:53:31 -06:00
query := getBaseQuery ( )
2022-10-20 04:21:13 -05:00
query . MetricEditorMode = models . MetricEditorModeBuilder
query . MetricQueryType = models . MetricQueryTypeSearch
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2021-11-30 03:53:31 -06:00
require . NoError ( t , err )
require . Empty ( t , mdq . Expression )
assert . Equal ( t , query . MetricName , * mdq . MetricStat . Metric . MetricName )
assert . Equal ( t , query . Namespace , * mdq . MetricStat . Metric . Namespace )
} )
2022-11-28 05:39:12 -06:00
t . Run ( "should pass AccountId in metric stat query" , func ( t * testing . T ) {
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
query := getBaseQuery ( )
query . MetricEditorMode = models . MetricEditorModeBuilder
query . MetricQueryType = models . MetricQueryTypeSearch
query . AccountId = aws . String ( "some account id" )
mdq , err := executor . buildMetricDataQuery ( logger , query )
require . NoError ( t , err )
assert . Equal ( t , "some account id" , * mdq . AccountId )
} )
t . Run ( "should leave AccountId in metric stat query" , func ( t * testing . T ) {
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
query := getBaseQuery ( )
query . MetricEditorMode = models . MetricEditorModeBuilder
query . MetricQueryType = models . MetricQueryTypeSearch
mdq , err := executor . buildMetricDataQuery ( logger , query )
require . NoError ( t , err )
assert . Nil ( t , mdq . AccountId )
} )
2021-11-30 03:53:31 -06:00
t . Run ( "should use custom built expression" , func ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-11-30 03:53:31 -06:00
query := getBaseQuery ( )
2022-10-20 04:21:13 -05:00
query . MetricEditorMode = models . MetricEditorModeBuilder
query . MetricQueryType = models . MetricQueryTypeSearch
2021-11-30 03:53:31 -06:00
query . MatchExact = false
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2021-11-30 03:53:31 -06:00
require . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "LoadBalancer"="lb1"', '', 300)) ` , * mdq . Expression )
} )
t . Run ( "should use sql expression" , func ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-11-30 03:53:31 -06:00
query := getBaseQuery ( )
2022-10-20 04:21:13 -05:00
query . MetricEditorMode = models . MetricEditorModeRaw
query . MetricQueryType = models . MetricQueryTypeQuery
2021-11-30 03:53:31 -06:00
query . SqlExpression = ` SELECT SUM(CPUUTilization) FROM "AWS/EC2" `
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2021-11-30 03:53:31 -06:00
require . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , query . SqlExpression , * mdq . Expression )
} )
t . Run ( "should use user defined math expression" , func ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-11-30 03:53:31 -06:00
query := getBaseQuery ( )
2022-10-20 04:21:13 -05:00
query . MetricEditorMode = models . MetricEditorModeRaw
query . MetricQueryType = models . MetricQueryTypeSearch
2021-11-30 03:53:31 -06:00
query . Expression = ` SUM(x+y) `
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2021-11-30 03:53:31 -06:00
require . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , query . Expression , * mdq . Expression )
} )
2021-09-23 09:03:58 -05:00
t . Run ( "should set period in user defined expression" , func ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-11-30 03:53:31 -06:00
query := getBaseQuery ( )
2022-10-20 04:21:13 -05:00
query . MetricEditorMode = models . MetricEditorModeRaw
query . MetricQueryType = models . MetricQueryTypeSearch
2021-09-23 09:03:58 -05:00
query . MatchExact = false
2021-11-30 03:53:31 -06:00
query . Expression = ` SUM([a,b]) `
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2021-09-23 09:03:58 -05:00
require . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , int64 ( 300 ) , * mdq . Period )
assert . Equal ( t , ` SUM([a,b]) ` , * mdq . Expression )
} )
2022-05-05 06:59:23 -05:00
2023-04-27 04:19:45 -05:00
t . Run ( "should set label" , func ( t * testing . T ) {
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2022-05-05 06:59:23 -05:00
query := getBaseQuery ( )
query . Label = "some label"
2022-11-02 09:14:02 -05:00
mdq , err := executor . buildMetricDataQuery ( logger , query )
2022-05-05 06:59:23 -05:00
assert . NoError ( t , err )
require . NotNil ( t , mdq . Label )
assert . Equal ( t , "some label" , * mdq . Label )
} )
2023-04-27 04:19:45 -05:00
t . Run ( "should not set label for empty string query label" , func ( t * testing . T ) {
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
query := getBaseQuery ( )
query . Label = ""
mdq , err := executor . buildMetricDataQuery ( logger , query )
assert . NoError ( t , err )
assert . Nil ( t , mdq . Label )
} )
2022-11-28 05:39:12 -06:00
t . Run ( ` should not specify accountId when it is "all" ` , func ( t * testing . T ) {
2023-04-27 04:19:45 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2022-11-28 05:39:12 -06:00
query := & models . CloudWatchQuery {
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Statistic : "Average" ,
Period : 60 ,
MatchExact : false ,
AccountId : aws . String ( "all" ) ,
}
mdq , err := executor . buildMetricDataQuery ( logger , query )
assert . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization"', 'Average', 60)) ` , * mdq . Expression )
} )
t . Run ( "should set accountId when it is specified" , func ( t * testing . T ) {
2023-04-27 04:19:45 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2022-11-28 05:39:12 -06:00
query := & models . CloudWatchQuery {
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Statistic : "Average" ,
Period : 60 ,
MatchExact : false ,
AccountId : aws . String ( "12345" ) ,
}
mdq , err := executor . buildMetricDataQuery ( logger , query )
assert . NoError ( t , err )
require . Nil ( t , mdq . MetricStat )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" :aws.AccountId="12345"', 'Average', 60)) ` , * mdq . Expression )
} )
2021-09-23 09:03:58 -05:00
} )
2020-10-02 12:40:15 -05:00
t . Run ( "Query should be matched exact" , func ( t * testing . T ) {
const matchExact = true
t . Run ( "Query has three dimension values for a given dimension key" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
2020-12-08 05:37:07 -06:00
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","LoadBalancer"} MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
2019-11-14 03:59:41 -06:00
} )
2020-10-02 12:40:15 -05:00
t . Run ( "Query has three dimension values for two given dimension keys" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2019-11-14 03:59:41 -06:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
2020-10-02 12:40:15 -05:00
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "i-456" , "i-789" } ,
2019-11-14 03:59:41 -06:00
} ,
Period : 300 ,
Expression : "" ,
2020-10-02 12:40:15 -05:00
MatchExact : matchExact ,
2019-11-14 03:59:41 -06:00
}
2020-10-02 12:40:15 -05:00
2019-11-14 03:59:41 -06:00
res := buildSearchExpression ( query , "Average" )
2020-12-08 05:37:07 -06:00
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","InstanceId","LoadBalancer"} MetricName="CPUUtilization" "InstanceId"=("i-123" OR "i-456" OR "i-789") "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
2020-10-02 12:40:15 -05:00
} )
t . Run ( "No OR operator was added if a star was used for dimension value" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "*" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
2019-11-14 03:59:41 -06:00
2020-10-02 12:40:15 -05:00
res := buildSearchExpression ( query , "Average" )
assert . NotContains ( t , res , "OR" )
2019-11-14 03:59:41 -06:00
} )
2020-10-02 12:40:15 -05:00
t . Run ( "Query has one dimension key with a * value" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "*" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
2020-12-08 05:37:07 -06:00
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","LoadBalancer"} MetricName="CPUUtilization"', 'Average', 300)) ` , res )
2020-10-02 12:40:15 -05:00
} )
t . Run ( "Query has three dimension values for two given dimension keys, and one value is a star" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "*" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
2020-12-08 05:37:07 -06:00
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","InstanceId","LoadBalancer"} MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
2020-10-02 12:40:15 -05:00
} )
2022-11-28 05:39:12 -06:00
t . Run ( "Query has multiple dimensions and an account Id" , func ( t * testing . T ) {
query := & models . CloudWatchQuery {
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "*" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
AccountId : aws . String ( "some account id" ) ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","InstanceId","LoadBalancer"} MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3") :aws.AccountId="some account id"', 'Average', 300)) ` , res )
} )
2020-10-02 12:40:15 -05:00
t . Run ( "Query has a dimension key with a space" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/Kafka" ,
MetricName : "CpuUser" ,
Dimensions : map [ string ] [ ] string {
"Cluster Name" : { "dev-cluster" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
2020-12-08 05:37:07 -06:00
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/Kafka","Cluster Name"} MetricName="CpuUser" "Cluster Name"="dev-cluster"', 'Average', 300)) ` , res )
} )
t . Run ( "Query has a custom namespace contains spaces" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-12-08 05:37:07 -06:00
Namespace : "Test-API Cache by Minute" ,
MetricName : "CpuUser" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "*" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "Test-API Cache by Minute","InstanceId","LoadBalancer"} MetricName="CpuUser" "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
2020-10-02 12:40:15 -05:00
} )
} )
t . Run ( "Query should not be matched exact" , func ( t * testing . T ) {
const matchExact = false
t . Run ( "Query has three dimension values for a given dimension key" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
} )
t . Run ( "Query has three dimension values for two given dimension keys" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "i-456" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "InstanceId"=("i-123" OR "i-456" OR "i-789") "LoadBalancer"=("lb1" OR "lb2" OR "lb3")', 'Average', 300)) ` , res )
} )
t . Run ( "Query has one dimension key with a * value" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "*" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "LoadBalancer"', 'Average', 300)) ` , res )
} )
t . Run ( "query has three dimension values for two given dimension keys, and one value is a star" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "*" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3") "InstanceId"', 'Average', 300)) ` , res )
} )
2022-11-28 05:39:12 -06:00
t . Run ( "query has multiple dimensions and an account Id" , func ( t * testing . T ) {
query := & models . CloudWatchQuery {
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" , "lb2" , "lb3" } ,
"InstanceId" : { "i-123" , "*" , "i-789" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : matchExact ,
AccountId : aws . String ( "some account id" ) ,
}
res := buildSearchExpression ( query , "Average" )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH('Namespace="AWS/EC2" MetricName="CPUUtilization" "LoadBalancer"=("lb1" OR "lb2" OR "lb3") "InstanceId" :aws.AccountId="some account id"', 'Average', 300)) ` , res )
} )
2020-10-02 12:40:15 -05:00
} )
t . Run ( "Query has invalid characters in dimension values" , func ( t * testing . T ) {
2022-10-20 04:21:13 -05:00
query := & models . CloudWatchQuery {
2020-10-02 12:40:15 -05:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"lb4" : { ` lb4"" ` } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : true ,
}
res := buildSearchExpression ( query , "Average" )
assert . Contains ( t , res , ` lb4\"\" ` , "Expected escape double quotes" )
2019-11-14 03:59:41 -06:00
} )
}
2021-11-30 03:53:31 -06:00
2022-10-20 04:21:13 -05:00
func getBaseQuery ( ) * models . CloudWatchQuery {
query := & models . CloudWatchQuery {
2021-11-30 03:53:31 -06:00
Namespace : "AWS/EC2" ,
MetricName : "CPUUtilization" ,
Dimensions : map [ string ] [ ] string {
"LoadBalancer" : { "lb1" } ,
} ,
Period : 300 ,
Expression : "" ,
MatchExact : true ,
}
return query
}