2020-04-26 04:48:20 +08:00
package cloudwatch
import (
"context"
2021-03-23 23:32:12 +08:00
"encoding/json"
2020-04-26 04:48:20 +08:00
"testing"
"time"
2025-09-19 18:46:40 +08:00
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
cloudwatchlogstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
2021-03-23 23:32:12 +08:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
2020-04-26 04:48:20 +08:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2024-01-30 20:11:52 +08:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/features"
2023-04-14 22:21:16 +08:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
2022-10-25 15:52:12 +08:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
2023-04-14 22:21:16 +08:00
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
2020-04-26 04:48:20 +08:00
"github.com/stretchr/testify/assert"
2023-09-26 02:19:12 +08:00
"github.com/stretchr/testify/mock"
2020-05-18 18:25:58 +08:00
"github.com/stretchr/testify/require"
2020-04-26 04:48:20 +08:00
)
2023-04-14 22:21:16 +08:00
func TestQuery_handleGetLogEvents_passes_nil_start_and_end_times_to_GetLogEvents ( t * testing . T ) {
2022-07-20 01:59:30 +08:00
origNewCWLogsClient := NewCWLogsClient
t . Cleanup ( func ( ) {
NewCWLogsClient = origNewCWLogsClient
} )
var cli fakeCWLogsClient
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( cfg aws . Config ) models . CWLogsClient {
2022-07-20 01:59:30 +08:00
return & cli
}
const refID = "A"
testCases := map [ string ] struct {
query string
expectedInput [ ] * cloudwatchlogs . GetLogEventsInput
} {
"Nil startTime" : {
query : ` {
"type" : "logAction" ,
"subtype" : "GetLogEvents" ,
"logGroupName" : "foo" ,
"logStreamName" : "bar" ,
"endTime" : 1 ,
"startFromHead" : false
} ` ,
expectedInput : [ ] * cloudwatchlogs . GetLogEventsInput {
{
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 10 ) ,
2022-07-20 01:59:30 +08:00
LogGroupName : aws . String ( "foo" ) ,
LogStreamName : aws . String ( "bar" ) ,
StartFromHead : aws . Bool ( false ) ,
} ,
} ,
} ,
"Nil endTime" : {
query : ` {
"type" : "logAction" ,
"subtype" : "GetLogEvents" ,
"logGroupName" : "foo" ,
"logStreamName" : "bar" ,
"startTime" : 1 ,
"startFromHead" : true
} ` ,
expectedInput : [ ] * cloudwatchlogs . GetLogEventsInput {
{
StartTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 10 ) ,
2022-07-20 01:59:30 +08:00
LogGroupName : aws . String ( "foo" ) ,
LogStreamName : aws . String ( "bar" ) ,
StartFromHead : aws . Bool ( true ) ,
} ,
} ,
} ,
}
for name , test := range testCases {
t . Run ( name , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
_ , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2022-07-20 01:59:30 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : refID ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( test . query ) ,
} ,
} ,
} )
require . NoError ( t , err )
2025-09-19 18:46:40 +08:00
require . Len ( t , cli . calls . getEvents , 1 )
assert . Equal ( t , test . expectedInput , cli . calls . getEvents )
2022-07-20 01:59:30 +08:00
} )
}
}
2023-04-14 22:21:16 +08:00
func TestQuery_GetLogEvents_returns_response_from_GetLogEvents_to_data_frame_field ( t * testing . T ) {
origNewCWLogsClient := NewCWLogsClient
t . Cleanup ( func ( ) {
NewCWLogsClient = origNewCWLogsClient
} )
var cli * mocks . MockLogEvents
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( cfg aws . Config ) models . CWLogsClient {
2023-04-14 22:21:16 +08:00
return cli
}
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
2023-04-14 22:21:16 +08:00
cli = & mocks . MockLogEvents { }
2025-09-19 18:46:40 +08:00
cli . On ( "GetLogEvents" , mock . Anything , mock . Anything , mock . Anything ) . Return ( & cloudwatchlogs . GetLogEventsOutput {
Events : [ ] cloudwatchlogstypes . OutputLogEvent { {
2023-04-14 22:21:16 +08:00
Message : utils . Pointer ( "some message" ) ,
Timestamp : utils . Pointer ( int64 ( 15 ) ) ,
} } } , nil )
2025-09-19 18:46:40 +08:00
resp , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2023-04-14 22:21:16 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "GetLogEvents" ,
"logGroupName" : "foo" ,
"logStreamName" : "bar" ,
"endTime" : 1 ,
"startFromHead" : false
} ` ) ,
} ,
} ,
} )
require . NoError ( t , err )
respA , ok := resp . Responses [ "A" ]
assert . True ( t , ok )
expectedTsField := data . NewField ( "ts" , nil , [ ] time . Time { time . UnixMilli ( 15 ) . UTC ( ) } )
expectedMessageField := data . NewField ( "line" , nil , [ ] * string { utils . Pointer ( "some message" ) } )
expectedTsField . SetConfig ( & data . FieldConfig { DisplayName : "Time" } )
assert . Equal ( t , [ ] * data . Field { expectedTsField , expectedMessageField } , respA . Frames [ 0 ] . Fields )
}
2020-07-24 00:52:22 +08:00
func TestQuery_StartQuery ( t * testing . T ) {
2021-01-07 18:36:13 +08:00
origNewCWLogsClient := NewCWLogsClient
2020-07-24 00:52:22 +08:00
t . Cleanup ( func ( ) {
2021-01-07 18:36:13 +08:00
NewCWLogsClient = origNewCWLogsClient
2020-04-26 04:48:20 +08:00
} )
2022-03-03 16:42:51 +08:00
var cli fakeCWLogsClient
2020-04-26 04:48:20 +08:00
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( cfg aws . Config ) models . CWLogsClient {
2022-02-25 21:14:59 +08:00
return & cli
2020-04-26 04:48:20 +08:00
}
2020-07-24 00:52:22 +08:00
t . Run ( "invalid time range" , func ( t * testing . T ) {
2024-06-04 20:43:36 +08:00
const refID = "A"
2022-03-03 16:42:51 +08:00
cli = fakeCWLogsClient {
2020-07-24 00:52:22 +08:00
logGroupFields : cloudwatchlogs . GetLogGroupFieldsOutput {
2025-09-19 18:46:40 +08:00
LogGroupFields : [ ] cloudwatchlogstypes . LogGroupField {
2020-07-24 00:52:22 +08:00
{
Name : aws . String ( "field_a" ) ,
2025-09-19 18:46:40 +08:00
Percent : 100 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_b" ) ,
2025-09-19 18:46:40 +08:00
Percent : 30 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_c" ) ,
2025-09-19 18:46:40 +08:00
Percent : 55 ,
2020-07-24 00:52:22 +08:00
} ,
} ,
} ,
}
2021-03-23 23:32:12 +08:00
timeRange := backend . TimeRange {
From : time . Unix ( 1584873443 , 0 ) ,
To : time . Unix ( 1584700643 , 0 ) ,
2020-07-24 00:52:22 +08:00
}
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( func ( ds * DataSource ) {
ds . Settings . Region = "us-east-2"
2025-04-09 03:40:15 +08:00
} )
2025-09-19 18:46:40 +08:00
resp , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2021-03-23 23:32:12 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
2020-07-24 00:52:22 +08:00
{
2024-06-04 20:43:36 +08:00
RefID : refID ,
2021-03-23 23:32:12 +08:00
TimeRange : timeRange ,
JSON : json . RawMessage ( ` {
2020-07-24 00:52:22 +08:00
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 50 ,
"region" : "default" ,
2021-03-23 23:32:12 +08:00
"queryString" : "fields @message"
} ` ) ,
2020-07-24 00:52:22 +08:00
} ,
} ,
} )
2024-06-04 20:43:36 +08:00
require . NoError ( t , err )
2020-04-26 04:48:20 +08:00
2024-06-04 20:43:36 +08:00
assert . Equal ( t , "failed to execute log action with subtype: StartQuery: invalid time range: start time must be before end time" , resp . Responses [ refID ] . Error . Error ( ) )
2020-04-26 04:48:20 +08:00
} )
2020-07-24 00:52:22 +08:00
t . Run ( "valid time range" , func ( t * testing . T ) {
const refID = "A"
2022-03-03 16:42:51 +08:00
cli = fakeCWLogsClient {
2020-07-24 00:52:22 +08:00
logGroupFields : cloudwatchlogs . GetLogGroupFieldsOutput {
2025-09-19 18:46:40 +08:00
LogGroupFields : [ ] cloudwatchlogstypes . LogGroupField {
2020-07-24 00:52:22 +08:00
{
Name : aws . String ( "field_a" ) ,
2025-09-19 18:46:40 +08:00
Percent : 100 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_b" ) ,
2025-09-19 18:46:40 +08:00
Percent : 30 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_c" ) ,
2025-09-19 18:46:40 +08:00
Percent : 55 ,
2020-07-24 00:52:22 +08:00
} ,
} ,
} ,
}
2021-03-23 23:32:12 +08:00
timeRange := backend . TimeRange {
From : time . Unix ( 1584700643000 , 0 ) ,
To : time . Unix ( 1584873443000 , 0 ) ,
2020-07-24 00:52:22 +08:00
}
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( func ( ds * DataSource ) {
ds . Settings . Region = "us-east-2"
2025-04-09 03:40:15 +08:00
} )
2025-09-19 18:46:40 +08:00
resp , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2021-03-23 23:32:12 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
2020-07-24 00:52:22 +08:00
{
2021-03-23 23:32:12 +08:00
RefID : refID ,
TimeRange : timeRange ,
JSON : json . RawMessage ( ` {
2020-07-24 00:52:22 +08:00
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 50 ,
"region" : "default" ,
2021-03-23 23:32:12 +08:00
"queryString" : "fields @message"
} ` ) ,
2020-07-24 00:52:22 +08:00
} ,
} ,
} )
require . NoError ( t , err )
expFrame := data . NewFrame (
refID ,
data . NewField ( "queryId" , nil , [ ] string { "abcd-efgh-ijkl-mnop" } ) ,
)
expFrame . RefID = refID
expFrame . Meta = & data . FrameMeta {
2023-08-30 23:46:47 +08:00
Custom : map [ string ] any {
2020-07-24 00:52:22 +08:00
"Region" : "default" ,
} ,
}
2021-03-23 23:32:12 +08:00
assert . Equal ( t , & backend . QueryDataResponse { Responses : backend . Responses {
refID : {
Frames : data . Frames { expFrame } ,
2020-07-24 00:52:22 +08:00
} ,
2021-03-23 23:32:12 +08:00
} ,
2020-07-24 00:52:22 +08:00
} , resp )
} )
}
2020-04-26 04:48:20 +08:00
2022-02-25 21:14:59 +08:00
func Test_executeStartQuery ( t * testing . T ) {
origNewCWLogsClient := NewCWLogsClient
t . Cleanup ( func ( ) {
NewCWLogsClient = origNewCWLogsClient
} )
2022-03-03 16:42:51 +08:00
var cli fakeCWLogsClient
2022-02-25 21:14:59 +08:00
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( cfg aws . Config ) models . CWLogsClient {
2022-02-25 21:14:59 +08:00
return & cli
}
2025-09-19 18:46:40 +08:00
t . Run ( "successfully parses information from JSON to StartQuery for language" , func ( t * testing . T ) {
2024-12-06 22:27:19 +08:00
testCases := map [ string ] struct {
queries [ ] backend . DataQuery
expectedOutput [ ] * cloudwatchlogs . StartQueryInput
2025-09-19 18:46:40 +08:00
queryLanguage cloudwatchlogstypes . QueryLanguage
2024-12-06 22:27:19 +08:00
} {
"not defined" : {
queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryString" : "fields @message" ,
"logGroupNames" : [ "some name" , "another name" ]
} ` ) ,
} ,
} ,
expectedOutput : [ ] * cloudwatchlogs . StartQueryInput { {
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2024-12-06 22:27:19 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupNames : [ ] string { "some name" , "another name" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2024-12-06 22:27:19 +08:00
} } ,
2025-09-19 18:46:40 +08:00
queryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2024-12-06 22:27:19 +08:00
} ,
"CWLI" : {
queries : [ ] backend . DataQuery { {
2022-02-25 21:14:59 +08:00
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
2024-12-06 22:27:19 +08:00
"queryLanguage" : "CWLI" ,
2022-02-25 21:14:59 +08:00
"queryString" : "fields @message" ,
"logGroupNames" : [ "some name" , "another name" ]
} ` ) ,
2024-12-06 22:27:19 +08:00
} } ,
expectedOutput : [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2024-12-06 22:27:19 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupNames : [ ] string { "some name" , "another name" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2024-12-06 22:27:19 +08:00
} ,
} ,
2025-09-19 18:46:40 +08:00
queryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2024-12-06 22:27:19 +08:00
} ,
"PPL" : {
queries : [ ] backend . DataQuery { {
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryLanguage" : "PPL" ,
"queryString" : "source logs | fields @message" ,
"logGroupNames" : [ "some name" , "another name" ]
} ` ) ,
} } ,
expectedOutput : [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2024-12-06 22:27:19 +08:00
QueryString : aws . String ( "source logs | fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupNames : [ ] string { "some name" , "another name" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguagePpl ,
2024-12-06 22:27:19 +08:00
} ,
2022-02-25 21:14:59 +08:00
} ,
2025-09-19 18:46:40 +08:00
queryLanguage : cloudwatchlogstypes . QueryLanguagePpl ,
2022-02-25 21:14:59 +08:00
} ,
2024-12-06 22:27:19 +08:00
"SQL" : {
queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryLanguage" : "SQL" ,
"queryString" : "SELECT * FROM logs" ,
"logGroupNames" : [ "some name" , "another name" ]
} ` ) ,
} ,
} ,
expectedOutput : [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2024-12-06 22:27:19 +08:00
QueryString : aws . String ( "SELECT * FROM logs" ) ,
LogGroupNames : nil ,
2025-09-19 18:46:40 +08:00
QueryLanguage : cloudwatchlogstypes . QueryLanguageSql ,
2024-12-06 22:27:19 +08:00
} ,
} ,
2025-09-19 18:46:40 +08:00
queryLanguage : cloudwatchlogstypes . QueryLanguageSql ,
2022-02-25 21:14:59 +08:00
} ,
2024-12-06 22:27:19 +08:00
}
for name , test := range testCases {
t . Run ( name , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
_ , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2024-12-06 22:27:19 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : test . queries ,
} )
assert . NoError ( t , err )
2025-09-19 18:46:40 +08:00
assert . Equal ( t , test . expectedOutput , cli . calls . startQuery )
2024-12-06 22:27:19 +08:00
} )
}
2022-02-25 21:14:59 +08:00
} )
t . Run ( "does not populate StartQueryInput.limit when no limit provided" , func ( t * testing . T ) {
2022-03-03 16:42:51 +08:00
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
2022-02-25 21:14:59 +08:00
2025-09-19 18:46:40 +08:00
_ , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2022-02-25 21:14:59 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery"
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
2025-09-19 18:46:40 +08:00
require . Len ( t , cli . calls . startQuery , 1 )
assert . Nil ( t , cli . calls . startQuery [ 0 ] . Limit )
2022-02-25 21:14:59 +08:00
} )
2022-11-28 19:39:12 +08:00
t . Run ( "attaches logGroupIdentifiers if the crossAccount feature is enabled" , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
2022-11-28 19:39:12 +08:00
2025-09-19 18:46:40 +08:00
_ , err := ds . QueryData ( contextWithFeaturesEnabled ( features . FlagCloudWatchCrossAccountQuerying ) , & backend . QueryDataRequest {
2022-11-28 19:39:12 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
2024-12-06 22:27:19 +08:00
"queryLanguage" : "CWLI" ,
2022-11-28 19:39:12 +08:00
"queryString" : "fields @message" ,
2023-01-04 17:07:03 +08:00
"logGroups" : [ { "arn" : "fakeARN" } ]
2022-11-28 19:39:12 +08:00
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2022-11-28 19:39:12 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupIdentifiers : [ ] string { "fakeARN" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2022-11-28 19:39:12 +08:00
} ,
2025-09-19 18:46:40 +08:00
} , cli . calls . startQuery )
2022-11-28 19:39:12 +08:00
} )
t . Run ( "attaches logGroupIdentifiers if the crossAccount feature is enabled and strips out trailing *" , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
2022-11-28 19:39:12 +08:00
2025-09-19 18:46:40 +08:00
_ , err := ds . QueryData ( contextWithFeaturesEnabled ( features . FlagCloudWatchCrossAccountQuerying ) , & backend . QueryDataRequest {
2022-11-28 19:39:12 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryString" : "fields @message" ,
2023-01-04 17:07:03 +08:00
"logGroups" : [ { "arn" : "*fake**ARN*" } ]
2022-11-28 19:39:12 +08:00
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
2023-09-29 02:25:03 +08:00
assert . Equal ( t , [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2023-09-29 02:25:03 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupIdentifiers : [ ] string { "*fake**ARN" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2023-09-29 02:25:03 +08:00
} ,
2025-09-19 18:46:40 +08:00
} , cli . calls . startQuery )
2023-09-29 02:25:03 +08:00
} )
t . Run ( "uses LogGroupNames if the cross account feature flag is not enabled, and log group names is present" , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
_ , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2023-09-29 02:25:03 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryString" : "fields @message" ,
"logGroups" : [ { "arn" : "*fake**ARN*" } ] ,
"LogGroupNames" : [ "/log-group-name" ]
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2023-09-29 02:25:03 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupNames : [ ] string { "/log-group-name" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2023-09-29 02:25:03 +08:00
} ,
2025-09-19 18:46:40 +08:00
} , cli . calls . startQuery )
2023-09-29 02:25:03 +08:00
} )
t . Run ( "ignores logGroups if feature flag is disabled even if logGroupNames is not present" , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
_ , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2023-09-29 02:25:03 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryString" : "fields @message" ,
"logGroups" : [ { "arn" : "*fake**ARN*" } ]
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
assert . Equal ( t , [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2023-09-29 02:25:03 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupNames : nil ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2023-09-29 02:25:03 +08:00
} ,
2025-09-19 18:46:40 +08:00
} , cli . calls . startQuery )
2023-09-29 02:25:03 +08:00
} )
t . Run ( "it always uses logGroups when feature flag is enabled and ignores log group names" , func ( t * testing . T ) {
cli = fakeCWLogsClient { }
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
_ , err := ds . QueryData ( contextWithFeaturesEnabled ( features . FlagCloudWatchCrossAccountQuerying ) , & backend . QueryDataRequest {
2023-09-29 02:25:03 +08:00
PluginContext : backend . PluginContext { DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } } ,
Queries : [ ] backend . DataQuery {
{
RefID : "A" ,
TimeRange : backend . TimeRange { From : time . Unix ( 0 , 0 ) , To : time . Unix ( 1 , 0 ) } ,
JSON : json . RawMessage ( ` {
"type" : "logAction" ,
"subtype" : "StartQuery" ,
"limit" : 12 ,
"queryString" : "fields @message" ,
"logGroups" : [ { "arn" : "*fake**ARN*" } ] ,
"logGroupNames" : [ "/log-group" ]
} ` ) ,
} ,
} ,
} )
assert . NoError ( t , err )
2022-11-28 19:39:12 +08:00
assert . Equal ( t , [ ] * cloudwatchlogs . StartQueryInput {
{
StartTime : aws . Int64 ( 0 ) ,
EndTime : aws . Int64 ( 1 ) ,
2025-09-19 18:46:40 +08:00
Limit : aws . Int32 ( 12 ) ,
2022-11-28 19:39:12 +08:00
QueryString : aws . String ( "fields @timestamp,ltrim(@log) as __log__grafana_internal__,ltrim(@logStream) as __logstream__grafana_internal__|fields @message" ) ,
2025-09-19 18:46:40 +08:00
LogGroupIdentifiers : [ ] string { "*fake**ARN" } ,
QueryLanguage : cloudwatchlogstypes . QueryLanguageCwli ,
2022-11-28 19:39:12 +08:00
} ,
2025-09-19 18:46:40 +08:00
} , cli . calls . startQuery )
2022-11-28 19:39:12 +08:00
} )
2022-02-25 21:14:59 +08:00
}
2020-07-24 00:52:22 +08:00
func TestQuery_StopQuery ( t * testing . T ) {
2021-01-07 18:36:13 +08:00
origNewCWLogsClient := NewCWLogsClient
2020-07-24 00:52:22 +08:00
t . Cleanup ( func ( ) {
2021-01-07 18:36:13 +08:00
NewCWLogsClient = origNewCWLogsClient
2020-07-24 00:52:22 +08:00
} )
2020-04-26 04:48:20 +08:00
2022-03-03 16:42:51 +08:00
var cli fakeCWLogsClient
2020-04-26 04:48:20 +08:00
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( aws . Config ) models . CWLogsClient {
2022-02-25 21:14:59 +08:00
return & cli
2020-07-24 00:52:22 +08:00
}
2020-04-26 04:48:20 +08:00
2022-03-03 16:42:51 +08:00
cli = fakeCWLogsClient {
2020-07-24 00:52:22 +08:00
logGroupFields : cloudwatchlogs . GetLogGroupFieldsOutput {
2025-09-19 18:46:40 +08:00
LogGroupFields : [ ] cloudwatchlogstypes . LogGroupField {
2020-07-24 00:52:22 +08:00
{
Name : aws . String ( "field_a" ) ,
2025-09-19 18:46:40 +08:00
Percent : 100 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_b" ) ,
2025-09-19 18:46:40 +08:00
Percent : 30 ,
2020-07-24 00:52:22 +08:00
} ,
{
Name : aws . String ( "field_c" ) ,
2025-09-19 18:46:40 +08:00
Percent : 55 ,
2020-07-24 00:52:22 +08:00
} ,
} ,
2020-04-26 04:48:20 +08:00
} ,
}
2021-03-23 23:32:12 +08:00
timeRange := backend . TimeRange {
From : time . Unix ( 1584873443 , 0 ) ,
To : time . Unix ( 1584700643 , 0 ) ,
2020-04-26 04:48:20 +08:00
}
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
resp , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2021-03-23 23:32:12 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
2020-07-24 00:52:22 +08:00
{
2021-03-23 23:32:12 +08:00
TimeRange : timeRange ,
JSON : json . RawMessage ( ` {
2020-07-24 00:52:22 +08:00
"type" : "logAction" ,
"subtype" : "StopQuery" ,
2021-03-23 23:32:12 +08:00
"queryId" : "abcd-efgh-ijkl-mnop"
} ` ) ,
2020-07-24 00:52:22 +08:00
} ,
} ,
2020-04-26 04:48:20 +08:00
} )
2020-07-24 00:52:22 +08:00
require . NoError ( t , err )
2020-04-26 04:48:20 +08:00
2020-07-24 00:52:22 +08:00
expFrame := & data . Frame {
Name : "StopQueryResponse" ,
Fields : [ ] * data . Field {
data . NewField ( "success" , nil , [ ] bool { true } ) ,
2020-04-26 04:48:20 +08:00
} ,
}
2021-03-23 23:32:12 +08:00
assert . Equal ( t , & backend . QueryDataResponse { Responses : backend . Responses {
"" : {
Frames : data . Frames { expFrame } ,
2020-07-24 00:52:22 +08:00
} ,
2021-03-23 23:32:12 +08:00
} ,
2020-07-24 00:52:22 +08:00
} , resp )
}
2020-04-26 04:48:20 +08:00
2020-07-24 00:52:22 +08:00
func TestQuery_GetQueryResults ( t * testing . T ) {
2021-01-07 18:36:13 +08:00
origNewCWLogsClient := NewCWLogsClient
2020-07-24 00:52:22 +08:00
t . Cleanup ( func ( ) {
2021-01-07 18:36:13 +08:00
NewCWLogsClient = origNewCWLogsClient
2020-04-26 04:48:20 +08:00
} )
2022-03-03 16:42:51 +08:00
var cli fakeCWLogsClient
2020-04-26 04:48:20 +08:00
2025-09-19 18:46:40 +08:00
NewCWLogsClient = func ( aws . Config ) models . CWLogsClient {
2022-02-25 21:14:59 +08:00
return & cli
2020-07-24 00:52:22 +08:00
}
2020-04-26 04:48:20 +08:00
2020-07-24 00:52:22 +08:00
const refID = "A"
2022-03-03 16:42:51 +08:00
cli = fakeCWLogsClient {
2020-07-24 00:52:22 +08:00
queryResults : cloudwatchlogs . GetQueryResultsOutput {
2025-09-19 18:46:40 +08:00
Results : [ ] [ ] cloudwatchlogstypes . ResultField {
2020-07-24 00:52:22 +08:00
{
{
Field : aws . String ( "@timestamp" ) ,
Value : aws . String ( "2020-03-20 10:37:23.000" ) ,
} ,
{
Field : aws . String ( "field_b" ) ,
Value : aws . String ( "b_1" ) ,
} ,
{
Field : aws . String ( "@ptr" ) ,
Value : aws . String ( "abcdefg" ) ,
} ,
} ,
{
{
Field : aws . String ( "@timestamp" ) ,
Value : aws . String ( "2020-03-20 10:40:43.000" ) ,
} ,
{
Field : aws . String ( "field_b" ) ,
Value : aws . String ( "b_2" ) ,
} ,
{
Field : aws . String ( "@ptr" ) ,
Value : aws . String ( "hijklmnop" ) ,
} ,
} ,
} ,
2025-09-19 18:46:40 +08:00
Statistics : & cloudwatchlogstypes . QueryStatistics {
BytesScanned : 512 ,
RecordsMatched : 256 ,
RecordsScanned : 1024 ,
2020-07-24 00:52:22 +08:00
} ,
2025-09-19 18:46:40 +08:00
Status : "Complete" ,
2020-04-26 04:48:20 +08:00
} ,
}
2025-09-19 18:46:40 +08:00
ds := newTestDatasource ( )
resp , err := ds . QueryData ( context . Background ( ) , & backend . QueryDataRequest {
2021-03-23 23:32:12 +08:00
PluginContext : backend . PluginContext {
DataSourceInstanceSettings : & backend . DataSourceInstanceSettings { } ,
} ,
Queries : [ ] backend . DataQuery {
2020-07-24 00:52:22 +08:00
{
2021-03-08 14:02:49 +08:00
RefID : refID ,
2021-03-23 23:32:12 +08:00
JSON : json . RawMessage ( ` {
2020-07-24 00:52:22 +08:00
"type" : "logAction" ,
"subtype" : "GetQueryResults" ,
2021-03-23 23:32:12 +08:00
"queryId" : "abcd-efgh-ijkl-mnop"
} ` ) ,
2020-07-24 00:52:22 +08:00
} ,
} ,
2020-04-26 04:48:20 +08:00
} )
2020-05-18 18:25:58 +08:00
require . NoError ( t , err )
2020-07-24 00:52:22 +08:00
time1 , err := time . Parse ( "2006-01-02 15:04:05.000" , "2020-03-20 10:37:23.000" )
2020-05-18 18:25:58 +08:00
require . NoError ( t , err )
2020-07-24 00:52:22 +08:00
time2 , err := time . Parse ( "2006-01-02 15:04:05.000" , "2020-03-20 10:40:43.000" )
2020-05-18 18:25:58 +08:00
require . NoError ( t , err )
2020-07-24 00:52:22 +08:00
expField1 := data . NewField ( "@timestamp" , nil , [ ] * time . Time {
aws . Time ( time1 ) , aws . Time ( time2 ) ,
2020-04-26 04:48:20 +08:00
} )
2020-07-24 00:52:22 +08:00
expField1 . SetConfig ( & data . FieldConfig { DisplayName : "Time" } )
expField2 := data . NewField ( "field_b" , nil , [ ] * string {
2020-04-26 04:48:20 +08:00
aws . String ( "b_1" ) , aws . String ( "b_2" ) ,
} )
2020-07-24 00:52:22 +08:00
expFrame := data . NewFrame ( refID , expField1 , expField2 )
expFrame . RefID = refID
expFrame . Meta = & data . FrameMeta {
2023-08-30 23:46:47 +08:00
Custom : map [ string ] any {
2020-04-26 04:48:20 +08:00
"Status" : "Complete" ,
2020-08-19 22:18:04 +08:00
} ,
Stats : [ ] data . QueryStat {
{
FieldConfig : data . FieldConfig { DisplayName : "Bytes scanned" } ,
Value : 512 ,
} ,
{
FieldConfig : data . FieldConfig { DisplayName : "Records scanned" } ,
Value : 1024 ,
} ,
{
FieldConfig : data . FieldConfig { DisplayName : "Records matched" } ,
Value : 256 ,
2020-04-26 04:48:20 +08:00
} ,
} ,
2020-07-24 00:52:22 +08:00
PreferredVisualization : "logs" ,
2020-04-26 04:48:20 +08:00
}
2021-03-23 23:32:12 +08:00
assert . Equal ( t , & backend . QueryDataResponse { Responses : backend . Responses {
refID : {
Frames : data . Frames { expFrame } ,
2020-07-24 00:52:22 +08:00
} ,
2021-03-23 23:32:12 +08:00
} ,
2020-07-24 00:52:22 +08:00
} , resp )
2020-04-26 04:48:20 +08:00
}
2022-12-02 17:21:46 +08:00
func TestGroupResponseFrame ( t * testing . T ) {
t . Run ( "Doesn't group results without time field" , func ( t * testing . T ) {
frame := data . NewFrameOfFieldTypes ( "test" , 0 , data . FieldTypeString , data . FieldTypeInt32 )
frame . AppendRow ( "val1" , int32 ( 10 ) )
frame . AppendRow ( "val2" , int32 ( 20 ) )
frame . AppendRow ( "val3" , int32 ( 30 ) )
groupedFrame , err := groupResponseFrame ( frame , [ ] string { "something" } )
require . NoError ( t , err )
require . Equal ( t , 3 , groupedFrame [ 0 ] . Rows ( ) )
2023-08-30 23:46:47 +08:00
require . Equal ( t , [ ] any { "val1" , "val2" , "val3" } , asArray ( groupedFrame [ 0 ] . Fields [ 0 ] ) )
require . Equal ( t , [ ] any { int32 ( 10 ) , int32 ( 20 ) , int32 ( 30 ) } , asArray ( groupedFrame [ 0 ] . Fields [ 1 ] ) )
2022-12-02 17:21:46 +08:00
} )
}
2023-08-30 23:46:47 +08:00
func asArray ( field * data . Field ) [ ] any {
var vals [ ] any
2022-12-02 17:21:46 +08:00
for i := 0 ; i < field . Len ( ) ; i ++ {
vals = append ( vals , field . At ( i ) )
}
return vals
}