2019-11-14 03:59:41 -06:00
package cloudwatch
import (
2020-01-17 06:22:43 -06:00
"context"
2021-06-10 03:23:17 -05:00
"encoding/json"
2019-11-14 03:59:41 -06:00
"testing"
2021-03-23 10:32:12 -05:00
"time"
2021-06-10 03:23:17 -05:00
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
2022-10-24 07:57:32 -05:00
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
2021-03-23 10:32:12 -05:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
2021-06-10 03:23:17 -05:00
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
2022-10-24 07:57:32 -05:00
"github.com/stretchr/testify/mock"
2022-10-20 04:21:13 -05:00
2022-04-29 10:47:46 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-10-20 05:53:28 -05:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
2022-10-20 04:21:13 -05:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
2019-11-14 03:59:41 -06:00
2020-05-18 05:25:58 -05:00
"github.com/stretchr/testify/assert"
2021-06-10 03:23:17 -05:00
"github.com/stretchr/testify/require"
2019-11-14 03:59:41 -06:00
)
2020-01-17 06:22:43 -06:00
func TestTimeSeriesQuery ( t * testing . T ) {
2022-04-29 10:47:46 -05:00
executor := newExecutor ( nil , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-03-23 10:32:12 -05:00
now := time . Now ( )
2019-11-14 03:59:41 -06:00
2021-06-10 03:23:17 -05:00
origNewCWClient := NewCWClient
t . Cleanup ( func ( ) {
NewCWClient = origNewCWClient
} )
2022-11-08 03:28:50 -06:00
var api mocks . MetricsAPI
2021-06-10 03:23:17 -05:00
NewCWClient = func ( sess * session . Session ) cloudwatchiface . CloudWatchAPI {
2022-10-20 05:53:28 -05:00
return & api
2021-06-10 03:23:17 -05:00
}
t . Run ( "Custom metrics" , func ( t * testing . T ) {
2022-11-08 03:28:50 -06:00
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput {
MetricDataResults : [ ] * cloudwatch . MetricDataResult {
{
StatusCode : aws . String ( "Complete" ) , Id : aws . String ( "a" ) , Label : aws . String ( "NetworkOut" ) , Values : [ ] * float64 { aws . Float64 ( 1.0 ) } , Timestamps : [ ] * time . Time { & now } ,
2021-06-10 03:23:17 -05:00
} ,
2022-11-08 03:28:50 -06:00
{
StatusCode : aws . String ( "Complete" ) , Id : aws . String ( "b" ) , Label : aws . String ( "NetworkIn" ) , Values : [ ] * float64 { aws . Float64 ( 1.0 ) } , Timestamps : [ ] * time . Time { & now } ,
} } } , nil )
2021-06-10 03:23:17 -05:00
im := datasource . NewInstanceManager ( func ( s backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
2022-11-22 08:59:11 -06:00
return DataSource { Settings : models . CloudWatchSettings { } } , nil
2021-06-10 03:23:17 -05:00
} )
2022-04-29 10:47:46 -05:00
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2021-06-10 03:23:17 -05:00
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange {
From : now . Add ( time . Hour * - 2 ) ,
To : now . Add ( time . Hour * - 1 ) ,
} ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"expression" : "" ,
"dimensions" : {
"InstanceId" : "i-00645d91ed77d87ac"
} ,
"region" : "us-east-2" ,
"id" : "a" ,
"alias" : "NetworkOut" ,
"statistics" : [
"Maximum"
] ,
"period" : "300" ,
"hide" : false ,
"matchExact" : true ,
"refId" : "A"
} ` ) ,
} ,
{
RefID : "B" ,
TimeRange : backend . TimeRange {
From : now . Add ( time . Hour * - 2 ) ,
To : now . Add ( time . Hour * - 1 ) ,
} ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkIn" ,
"expression" : "" ,
"dimensions" : {
"InstanceId" : "i-00645d91ed77d87ac"
} ,
"region" : "us-east-2" ,
"id" : "b" ,
"alias" : "NetworkIn" ,
"statistics" : [
"Maximum"
] ,
"period" : "300" ,
"matchExact" : true ,
"refId" : "B"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
assert . Equal ( t , "NetworkOut" , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
assert . Equal ( t , "NetworkIn" , resp . Responses [ "B" ] . Frames [ 0 ] . Name )
} )
2020-05-18 05:25:58 -05:00
t . Run ( "End time before start time should result in error" , func ( t * testing . T ) {
2022-11-02 09:14:02 -05:00
_ , err := executor . executeTimeSeriesQuery ( context . Background ( ) , logger , & backend . QueryDataRequest { Queries : [ ] backend . DataQuery { { TimeRange : backend . TimeRange {
2021-03-23 10:32:12 -05:00
From : now . Add ( time . Hour * - 1 ) ,
To : now . Add ( time . Hour * - 2 ) ,
} } } } )
2020-05-18 05:25:58 -05:00
assert . EqualError ( t , err , "invalid time range: start time must be before end time" )
} )
2019-11-14 03:59:41 -06:00
2020-05-18 05:25:58 -05:00
t . Run ( "End time equals start time should result in error" , func ( t * testing . T ) {
2022-11-02 09:14:02 -05:00
_ , err := executor . executeTimeSeriesQuery ( context . Background ( ) , logger , & backend . QueryDataRequest { Queries : [ ] backend . DataQuery { { TimeRange : backend . TimeRange {
2021-03-23 10:32:12 -05:00
From : now . Add ( time . Hour * - 1 ) ,
To : now . Add ( time . Hour * - 1 ) ,
} } } } )
2020-05-18 05:25:58 -05:00
assert . EqualError ( t , err , "invalid time range: start time must be before end time" )
2019-11-14 03:59:41 -06:00
} )
}
2022-04-21 08:11:23 -05:00
2022-10-24 07:57:32 -05:00
func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMetricData_is_called_once_per_grouping_of_queries_by_region ( t * testing . T ) {
/ * TODO : This test aims to verify the logic to group regions which has been extracted from ParseMetricDataQueries .
It should be replaced by a test at a lower level when grouping by regions is incorporated into a separate business logic layer * /
origNewCWClient := NewCWClient
t . Cleanup ( func ( ) {
NewCWClient = origNewCWClient
} )
2022-11-08 03:28:50 -06:00
var mockMetricClient mocks . MetricsAPI
2022-10-24 07:57:32 -05:00
NewCWClient = func ( sess * session . Session ) cloudwatchiface . CloudWatchAPI {
return & mockMetricClient
}
im := datasource . NewInstanceManager ( func ( s backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
2022-11-22 08:59:11 -06:00
return DataSource { Settings : models . CloudWatchSettings { } } , nil
2022-10-24 07:57:32 -05:00
} )
t . Run ( "Queries with the same region should call GetSession with that region 1 time and call GetMetricDataWithContext 1 time" , func ( t * testing . T ) {
mockSessionCache := & mockSessionCache { }
mockSessionCache . On ( "GetSession" , mock . MatchedBy (
func ( config awsds . SessionConfig ) bool { return config . Settings . Region == "us-east-1" } ) ) . // region from queries is asserted here
Return ( & session . Session { Config : & aws . Config { } } , nil ) . Once ( )
2022-11-08 03:28:50 -06:00
mockMetricClient = mocks . MetricsAPI { }
2022-10-24 07:57:32 -05:00
mockMetricClient . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil , nil )
executor := newExecutor ( im , newTestConfig ( ) , mockSessionCache , featuremgmt . WithFeatures ( ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"region" : "us-east-1" ,
"statistic" : "Maximum" ,
"period" : "300"
} ` ) ,
} ,
{
RefID : "B" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkIn" ,
"region" : "us-east-1" ,
"statistic" : "Maximum" ,
"period" : "300"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
mockSessionCache . AssertExpectations ( t ) // method is defined to only return "Once()",
// AssertExpectations will fail if those methods were not called Once(), so expected number of calls is asserted by this line
mockMetricClient . AssertNumberOfCalls ( t , "GetMetricDataWithContext" , 1 )
// GetMetricData is asserted to have been called 1 time for the 1 region present in the queries
} )
t . Run ( "3 queries with 2 regions calls GetSession 2 times and calls GetMetricDataWithContext 2 times" , func ( t * testing . T ) {
sessionCache := & mockSessionCache { }
sessionCache . On ( "GetSession" , mock . MatchedBy (
func ( config awsds . SessionConfig ) bool { return config . Settings . Region == "us-east-1" } ) ) .
Return ( & session . Session { Config : & aws . Config { } } , nil , nil ) . Once ( )
sessionCache . On ( "GetSession" , mock . MatchedBy (
func ( config awsds . SessionConfig ) bool { return config . Settings . Region == "us-east-2" } ) ) .
Return ( & session . Session { Config : & aws . Config { } } , nil , nil ) . Once ( )
2022-11-08 03:28:50 -06:00
mockMetricClient = mocks . MetricsAPI { }
2022-10-24 07:57:32 -05:00
mockMetricClient . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( nil , nil )
executor := newExecutor ( im , newTestConfig ( ) , sessionCache , featuremgmt . WithFeatures ( ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"region" : "us-east-2" ,
"statistic" : "Maximum" ,
"period" : "300"
} ` ) ,
} ,
{
RefID : "A2" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"region" : "us-east-2" ,
"statistic" : "Maximum" ,
"period" : "300"
} ` ) ,
} ,
{
RefID : "B" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkIn" ,
"region" : "us-east-1" ,
"statistic" : "Maximum" ,
"period" : "300"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
sessionCache . AssertExpectations ( t ) // method is defined to only return "Once()" for each region.
// AssertExpectations will fail if those methods were not called Once(), so expected number of calls is asserted by this line
mockMetricClient . AssertNumberOfCalls ( t , "GetMetricDataWithContext" , 2 )
// GetMetricData is asserted to have been called 2 times, presumably once for each group of regions (2 regions total)
} )
}
2022-04-21 08:11:23 -05:00
type queryDimensions struct {
InstanceID [ ] string ` json:"InstanceId,omitempty" `
}
type queryParameters struct {
2022-10-20 04:21:13 -05:00
MetricQueryType models . MetricQueryType ` json:"metricQueryType" `
MetricEditorMode models . MetricEditorMode ` json:"metricEditorMode" `
Dimensions queryDimensions ` json:"dimensions" `
Expression string ` json:"expression" `
Alias string ` json:"alias" `
Label * string ` json:"label" `
Statistic string ` json:"statistic" `
Period string ` json:"period" `
MatchExact bool ` json:"matchExact" `
MetricName string ` json:"metricName" `
2022-04-21 08:11:23 -05:00
}
var queryId = "query id"
func newTestQuery ( t testing . TB , p queryParameters ) json . RawMessage {
t . Helper ( )
tsq := struct {
2022-10-20 04:21:13 -05:00
Type string ` json:"type" `
MetricQueryType models . MetricQueryType ` json:"metricQueryType" `
MetricEditorMode models . MetricEditorMode ` json:"metricEditorMode" `
Namespace string ` json:"namespace" `
MetricName string ` json:"metricName" `
2022-04-21 08:11:23 -05:00
Dimensions struct {
InstanceID [ ] string ` json:"InstanceId,omitempty" `
} ` json:"dimensions" `
2022-05-05 06:59:23 -05:00
Expression string ` json:"expression" `
Region string ` json:"region" `
ID string ` json:"id" `
Alias string ` json:"alias" `
Label * string ` json:"label" `
Statistic string ` json:"statistic" `
Period string ` json:"period" `
MatchExact bool ` json:"matchExact" `
RefID string ` json:"refId" `
2022-04-21 08:11:23 -05:00
} {
Type : "timeSeriesQuery" ,
Region : "us-east-2" ,
ID : queryId ,
RefID : "A" ,
MatchExact : p . MatchExact ,
MetricQueryType : p . MetricQueryType ,
MetricEditorMode : p . MetricEditorMode ,
Dimensions : p . Dimensions ,
Expression : p . Expression ,
Alias : p . Alias ,
2022-05-05 06:59:23 -05:00
Label : p . Label ,
2022-04-21 08:11:23 -05:00
Statistic : p . Statistic ,
Period : p . Period ,
MetricName : p . MetricName ,
}
marshalled , err := json . Marshal ( tsq )
require . NoError ( t , err )
return marshalled
}
2022-05-05 06:59:23 -05:00
func Test_QueryData_timeSeriesQuery_GetMetricDataWithContext ( t * testing . T ) {
origNewCWClient := NewCWClient
t . Cleanup ( func ( ) {
NewCWClient = origNewCWClient
} )
2022-10-20 05:53:28 -05:00
2022-11-08 03:28:50 -06:00
var api mocks . MetricsAPI
2022-10-20 05:53:28 -05:00
2022-05-05 06:59:23 -05:00
NewCWClient = func ( sess * session . Session ) cloudwatchiface . CloudWatchAPI {
2022-10-20 05:53:28 -05:00
return & api
2022-05-05 06:59:23 -05:00
}
im := datasource . NewInstanceManager ( func ( s backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
2022-11-22 08:59:11 -06:00
return DataSource { Settings : models . CloudWatchSettings { } } , nil
2022-05-05 06:59:23 -05:00
} )
t . Run ( "passes query label as GetMetricData label when dynamic labels feature toggle is enabled" , func ( t * testing . T ) {
2022-11-08 03:28:50 -06:00
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
2022-05-05 06:59:23 -05:00
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchDynamicLabels ) )
query := newTestQuery ( t , queryParameters {
Label : aws . String ( "${PROP('Period')} some words ${PROP('Dim.InstanceId')}" ) ,
} )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange {
From : time . Now ( ) . Add ( time . Hour * - 2 ) ,
To : time . Now ( ) . Add ( time . Hour * - 1 ) ,
} ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
2022-11-08 03:28:50 -06:00
require . Len ( t , api . Calls , 1 )
getMetricDataInput , ok := api . Calls [ 0 ] . Arguments . Get ( 1 ) . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , getMetricDataInput . MetricDataQueries , 1 )
require . NotNil ( t , getMetricDataInput . MetricDataQueries [ 0 ] . Label )
assert . Equal ( t , "${PROP('Period')} some words ${PROP('Dim.InstanceId')}" , * getMetricDataInput . MetricDataQueries [ 0 ] . Label )
2022-05-05 06:59:23 -05:00
} )
testCases := map [ string ] struct {
feature * featuremgmt . FeatureManager
parameters queryParameters
} {
"should not pass GetMetricData label when query label is empty, dynamic labels is enabled" : {
feature : featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchDynamicLabels ) ,
} ,
"should not pass GetMetricData label when query label is empty string, dynamic labels is enabled" : {
feature : featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchDynamicLabels ) ,
parameters : queryParameters { Label : aws . String ( "" ) } ,
} ,
"should not pass GetMetricData label when dynamic labels is disabled" : {
feature : featuremgmt . WithFeatures ( ) ,
parameters : queryParameters { Label : aws . String ( "${PROP('Period')} some words ${PROP('Dim.InstanceId')}" ) } ,
} ,
}
for name , tc := range testCases {
t . Run ( name , func ( t * testing . T ) {
2022-11-08 03:28:50 -06:00
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
2022-05-05 06:59:23 -05:00
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , tc . feature )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange {
From : time . Now ( ) . Add ( time . Hour * - 2 ) ,
To : time . Now ( ) . Add ( time . Hour * - 1 ) ,
} ,
JSON : newTestQuery ( t , tc . parameters ) ,
} ,
} ,
} )
assert . NoError ( t , err )
2022-11-08 03:28:50 -06:00
assert . NoError ( t , err )
require . Len ( t , api . Calls , 1 )
getMetricDataInput , ok := api . Calls [ 0 ] . Arguments . Get ( 1 ) . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , getMetricDataInput . MetricDataQueries , 1 )
require . Nil ( t , getMetricDataInput . MetricDataQueries [ 0 ] . Label )
2022-05-05 06:59:23 -05:00
} )
}
}
2022-04-21 08:11:23 -05:00
func Test_QueryData_response_data_frame_names ( t * testing . T ) {
origNewCWClient := NewCWClient
t . Cleanup ( func ( ) {
NewCWClient = origNewCWClient
} )
2022-11-08 03:28:50 -06:00
var api mocks . MetricsAPI
2022-10-20 05:53:28 -05:00
2022-04-21 08:11:23 -05:00
NewCWClient = func ( sess * session . Session ) cloudwatchiface . CloudWatchAPI {
2022-10-20 05:53:28 -05:00
return & api
2022-04-21 08:11:23 -05:00
}
2022-10-20 05:53:28 -05:00
2022-04-21 08:11:23 -05:00
labelFromGetMetricData := "some label"
2022-11-08 03:28:50 -06:00
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) .
Return ( & cloudwatch . GetMetricDataOutput {
2022-04-21 08:11:23 -05:00
MetricDataResults : [ ] * cloudwatch . MetricDataResult {
{ StatusCode : aws . String ( "Complete" ) , Id : aws . String ( queryId ) , Label : aws . String ( labelFromGetMetricData ) ,
Values : [ ] * float64 { aws . Float64 ( 1.0 ) } , Timestamps : [ ] * time . Time { { } } } ,
2022-11-08 03:28:50 -06:00
} } , nil )
2022-04-21 08:11:23 -05:00
im := datasource . NewInstanceManager ( func ( s backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
2022-11-22 08:59:11 -06:00
return DataSource { Settings : models . CloudWatchSettings { } } , nil
2022-04-21 08:11:23 -05:00
} )
2022-04-29 10:47:46 -05:00
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
2022-04-21 08:11:23 -05:00
t . Run ( "where user defines search expression and alias is defined, then frame name prioritizes period and stat from expression over input" , func ( t * testing . T ) {
query := newTestQuery ( t , queryParameters {
2022-10-20 04:21:13 -05:00
MetricQueryType : models . MetricQueryTypeSearch , // contributes to isUserDefinedSearchExpression = true
MetricEditorMode : models . MetricEditorModeRaw , // contributes to isUserDefinedSearchExpression = true
2022-04-21 08:11:23 -05:00
Alias : "{{period}} {{stat}}" ,
Expression : ` SEARCH(' { AWS/EC2,InstanceId} MetricName="CPUUtilization"', 'Average', 300) ` , // period 300 and stat 'Average' parsed from this expression
Statistic : "Maximum" , // stat parsed from expression takes precedence over 'Maximum'
Period : "1200" , // period parsed from expression takes precedence over 1200
} )
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , "300 Average" , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
} )
t . Run ( "where no alias is provided and query is math expression, then frame name is queryId" , func ( t * testing . T ) {
query := newTestQuery ( t , queryParameters {
2022-10-20 04:21:13 -05:00
MetricQueryType : models . MetricQueryTypeSearch ,
MetricEditorMode : models . MetricEditorModeRaw ,
2022-04-21 08:11:23 -05:00
} )
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , queryId , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
} )
t . Run ( "where no alias provided and query type is MetricQueryTypeQuery, then frame name is label" , func ( t * testing . T ) {
query := newTestQuery ( t , queryParameters {
2022-10-20 04:21:13 -05:00
MetricQueryType : models . MetricQueryTypeQuery ,
2022-04-21 08:11:23 -05:00
} )
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , labelFromGetMetricData , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
} )
// where query is inferred search expression and not multivalued dimension expression, then frame name is label
testCasesReturningLabel := map [ string ] queryParameters {
"with specific dimensions, matchExact false" : { Dimensions : queryDimensions { [ ] string { "some-instance" } } , MatchExact : false } ,
"with wildcard dimensions, matchExact false" : { Dimensions : queryDimensions { [ ] string { "*" } } , MatchExact : false } ,
"with wildcard dimensions, matchExact true" : { Dimensions : queryDimensions { [ ] string { "*" } } , MatchExact : true } ,
"no dimension, matchExact false" : { Dimensions : queryDimensions { } , MatchExact : false } ,
}
for name , parameters := range testCasesReturningLabel {
t . Run ( name , func ( t * testing . T ) {
query := newTestQuery ( t , parameters )
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , labelFromGetMetricData , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
} )
}
// complementary test cases to above return default of "metricName_stat"
testCasesReturningMetricStat := map [ string ] queryParameters {
"with specific dimensions, matchExact true" : {
Dimensions : queryDimensions { [ ] string { "some-instance" } } ,
MatchExact : true ,
MetricName : "CPUUtilization" ,
Statistic : "Maximum" ,
} ,
"no dimensions, matchExact true" : {
Dimensions : queryDimensions { } ,
MatchExact : true ,
MetricName : "CPUUtilization" ,
Statistic : "Maximum" ,
} ,
"multivalued dimensions, matchExact true" : {
Dimensions : queryDimensions { [ ] string { "some-instance" , "another-instance" } } ,
MatchExact : true ,
MetricName : "CPUUtilization" ,
Statistic : "Maximum" ,
} ,
"multivalued dimensions, matchExact false" : {
Dimensions : queryDimensions { [ ] string { "some-instance" , "another-instance" } } ,
MatchExact : false ,
MetricName : "CPUUtilization" ,
Statistic : "Maximum" ,
} ,
}
for name , parameters := range testCasesReturningMetricStat {
t . Run ( name , func ( t * testing . T ) {
query := newTestQuery ( t , parameters )
resp , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : query ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , "CPUUtilization_Maximum" , resp . Responses [ "A" ] . Frames [ 0 ] . Name )
} )
}
}
2022-11-28 05:39:12 -06:00
func TestTimeSeriesQuery_CrossAccountQuerying ( t * testing . T ) {
origNewCWClient := NewCWClient
t . Cleanup ( func ( ) {
NewCWClient = origNewCWClient
} )
var api mocks . MetricsAPI
NewCWClient = func ( sess * session . Session ) cloudwatchiface . CloudWatchAPI {
return & api
}
im := datasource . NewInstanceManager ( func ( s backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
return DataSource { Settings : models . CloudWatchSettings { } } , nil
} )
t . Run ( "should call GetMetricDataInput with AccountId nil when no AccountId is provided" , func ( t * testing . T ) {
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchCrossAccountQuerying ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"dimensions" : {
"InstanceId" : "i-00645d91ed77d87ac"
} ,
"region" : "us-east-2" ,
"id" : "a" ,
"alias" : "NetworkOut" ,
"statistic" : "Maximum" ,
"period" : "300" ,
"hide" : false ,
"matchExact" : true ,
"refId" : "A"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
actualInput , ok := api . Calls [ 0 ] . Arguments [ 1 ] . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , actualInput . MetricDataQueries , 1 )
assert . Nil ( t , actualInput . MetricDataQueries [ 0 ] . Expression )
assert . Nil ( t , actualInput . MetricDataQueries [ 0 ] . AccountId )
} )
t . Run ( "should call GetMetricDataInput with AccountId nil when feature flag is false" , func ( t * testing . T ) {
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"dimensions" : {
"InstanceId" : "i-00645d91ed77d87ac"
} ,
"region" : "us-east-2" ,
"id" : "a" ,
"alias" : "NetworkOut" ,
"statistic" : "Maximum" ,
"period" : "300" ,
"hide" : false ,
"matchExact" : true ,
"refId" : "A" ,
"accountId" : "some account Id"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
actualInput , ok := api . Calls [ 0 ] . Arguments [ 1 ] . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , actualInput . MetricDataQueries , 1 )
assert . Nil ( t , actualInput . MetricDataQueries [ 0 ] . Expression )
assert . Nil ( t , actualInput . MetricDataQueries [ 0 ] . AccountId )
} )
t . Run ( "should call GetMetricDataInput with AccountId in a MetricStat query" , func ( t * testing . T ) {
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchCrossAccountQuerying ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"dimensions" : {
"InstanceId" : "i-00645d91ed77d87ac"
} ,
"region" : "us-east-2" ,
"id" : "a" ,
"alias" : "NetworkOut" ,
"statistic" : "Maximum" ,
"period" : "300" ,
"hide" : false ,
"matchExact" : true ,
"refId" : "A" ,
"accountId" : "some account Id"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
actualInput , ok := api . Calls [ 0 ] . Arguments [ 1 ] . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , actualInput . MetricDataQueries , 1 )
require . NotNil ( t , actualInput . MetricDataQueries [ 0 ] . AccountId )
assert . Equal ( t , "some account Id" , * actualInput . MetricDataQueries [ 0 ] . AccountId )
} )
t . Run ( "should GetMetricDataInput with AccountId in an inferred search expression query" , func ( t * testing . T ) {
api = mocks . MetricsAPI { }
api . On ( "GetMetricDataWithContext" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatch . GetMetricDataOutput { } , nil )
executor := newExecutor ( im , newTestConfig ( ) , & fakeSessionCache { } , featuremgmt . WithFeatures ( featuremgmt . FlagCloudWatchCrossAccountQuerying ) )
_ , err := executor . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Now ( ) . Add ( time . Hour * - 2 ) , To : time . Now ( ) . Add ( time . Hour * - 1 ) } ,
JSON : json . RawMessage ( ` {
"type" : "timeSeriesQuery" ,
"subtype" : "metrics" ,
"namespace" : "AWS/EC2" ,
"metricName" : "NetworkOut" ,
"dimensions" : {
"InstanceId" : "*"
} ,
"region" : "us-east-2" ,
"id" : "a" ,
"alias" : "NetworkOut" ,
"statistic" : "Maximum" ,
"period" : "300" ,
"hide" : false ,
"matchExact" : true ,
"refId" : "A" ,
"accountId" : "some account Id"
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
actualInput , ok := api . Calls [ 0 ] . Arguments [ 1 ] . ( * cloudwatch . GetMetricDataInput )
require . True ( t , ok )
require . Len ( t , actualInput . MetricDataQueries , 1 )
require . NotNil ( t , actualInput . MetricDataQueries [ 0 ] . Expression )
assert . Equal ( t , ` REMOVE_EMPTY(SEARCH(' { "AWS/EC2","InstanceId"} MetricName="NetworkOut" :aws.AccountId="some account Id"', 'Maximum', 300)) ` , * actualInput . MetricDataQueries [ 0 ] . Expression )
} )
}