ef441f02d0
Fix unexpected error when creating a new cloudwatch datasource. Involves a fair amount of refactoring, so if this causes unexpected issues related to region fetching we can turn this off with the cloudwatchNewRegionsHandler feature toggle, although we do not predict it will so we are enabling it to default to true and hope to remove it shortly.
260 lines
9.2 KiB
Go
260 lines
9.2 KiB
Go
package cloudwatch
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
awsclient "github.com/aws/aws-sdk-go/aws/client"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
|
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
|
"github.com/grafana/grafana/pkg/infra/httpclient"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
|
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewInstanceSettings(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
settings backend.DataSourceInstanceSettings
|
|
expectedDS DataSource
|
|
Err require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
name: "creates a request",
|
|
settings: backend.DataSourceInstanceSettings{
|
|
JSONData: []byte(`{
|
|
"profile": "foo",
|
|
"defaultRegion": "us-east2",
|
|
"assumeRoleArn": "role",
|
|
"externalId": "id",
|
|
"endpoint": "bar",
|
|
"customMetricsNamespaces": "ns",
|
|
"authType": "keys"
|
|
}`),
|
|
DecryptedSecureJSONData: map[string]string{
|
|
"accessKey": "A123",
|
|
"secretKey": "secret",
|
|
},
|
|
},
|
|
expectedDS: DataSource{
|
|
Settings: models.CloudWatchSettings{
|
|
AWSDatasourceSettings: awsds.AWSDatasourceSettings{
|
|
Profile: "foo",
|
|
Region: "us-east2",
|
|
AssumeRoleARN: "role",
|
|
ExternalID: "id",
|
|
Endpoint: "bar",
|
|
AuthType: awsds.AuthTypeKeys,
|
|
AccessKey: "A123",
|
|
SecretKey: "secret",
|
|
},
|
|
Namespace: "ns",
|
|
},
|
|
},
|
|
Err: require.NoError,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := NewInstanceSettings(httpclient.NewProvider())
|
|
model, err := f(context.Background(), tt.settings)
|
|
tt.Err(t, err)
|
|
datasourceComparer := cmp.Comparer(func(d1 DataSource, d2 DataSource) bool {
|
|
return d1.Settings.Profile == d2.Settings.Profile &&
|
|
d1.Settings.Region == d2.Settings.Region &&
|
|
d1.Settings.AuthType == d2.Settings.AuthType &&
|
|
d1.Settings.AssumeRoleARN == d2.Settings.AssumeRoleARN &&
|
|
d1.Settings.ExternalID == d2.Settings.ExternalID &&
|
|
d1.Settings.Namespace == d2.Settings.Namespace &&
|
|
d1.Settings.Endpoint == d2.Settings.Endpoint &&
|
|
d1.Settings.AccessKey == d2.Settings.AccessKey &&
|
|
d1.Settings.SecretKey == d2.Settings.SecretKey
|
|
})
|
|
if !cmp.Equal(model.(DataSource), tt.expectedDS, datasourceComparer) {
|
|
t.Errorf("Unexpected result. Expecting\n%v \nGot:\n%v", model, tt.expectedDS)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_CheckHealth(t *testing.T) {
|
|
origNewMetricsAPI := NewMetricsAPI
|
|
origNewCWLogsClient := NewCWLogsClient
|
|
origNewLogsAPI := NewLogsAPI
|
|
|
|
t.Cleanup(func() {
|
|
NewMetricsAPI = origNewMetricsAPI
|
|
NewCWLogsClient = origNewCWLogsClient
|
|
NewLogsAPI = origNewLogsAPI
|
|
})
|
|
|
|
var client fakeCheckHealthClient
|
|
NewMetricsAPI = func(sess *session.Session) models.CloudWatchMetricsAPIProvider {
|
|
return client
|
|
}
|
|
NewLogsAPI = func(sess *session.Session) models.CloudWatchLogsAPIProvider {
|
|
return client
|
|
}
|
|
im := datasource.NewInstanceManager(func(ctx context.Context, s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
|
return DataSource{Settings: models.CloudWatchSettings{
|
|
AWSDatasourceSettings: awsds.AWSDatasourceSettings{
|
|
Region: "us-east-1",
|
|
},
|
|
}}, nil
|
|
})
|
|
|
|
t.Run("successfully query metrics and logs", func(t *testing.T) {
|
|
client = fakeCheckHealthClient{}
|
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures())
|
|
|
|
resp, err := executor.CheckHealth(context.Background(), &backend.CheckHealthRequest{
|
|
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &backend.CheckHealthResult{
|
|
Status: backend.HealthStatusOk,
|
|
Message: "1. Successfully queried the CloudWatch metrics API.\n2. Successfully queried the CloudWatch logs API.",
|
|
}, resp)
|
|
})
|
|
|
|
t.Run("successfully queries metrics, fails during logs query", func(t *testing.T) {
|
|
client = fakeCheckHealthClient{
|
|
describeLogGroups: func(input *cloudwatchlogs.DescribeLogGroupsInput) (*cloudwatchlogs.DescribeLogGroupsOutput, error) {
|
|
return nil, fmt.Errorf("some logs query error")
|
|
}}
|
|
|
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures())
|
|
|
|
resp, err := executor.CheckHealth(context.Background(), &backend.CheckHealthRequest{
|
|
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &backend.CheckHealthResult{
|
|
Status: backend.HealthStatusError,
|
|
Message: "1. Successfully queried the CloudWatch metrics API.\n2. CloudWatch logs query failed: some logs query error",
|
|
}, resp)
|
|
})
|
|
|
|
t.Run("successfully queries logs, fails during metrics query", func(t *testing.T) {
|
|
client = fakeCheckHealthClient{
|
|
listMetricsPages: func(input *cloudwatch.ListMetricsInput, fn func(*cloudwatch.ListMetricsOutput, bool) bool) error {
|
|
return fmt.Errorf("some list metrics error")
|
|
}}
|
|
|
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures())
|
|
|
|
resp, err := executor.CheckHealth(context.Background(), &backend.CheckHealthRequest{
|
|
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &backend.CheckHealthResult{
|
|
Status: backend.HealthStatusError,
|
|
Message: "1. CloudWatch metrics query failed: some list metrics error\n2. Successfully queried the CloudWatch logs API.",
|
|
}, resp)
|
|
})
|
|
|
|
t.Run("fail to get clients", func(t *testing.T) {
|
|
client = fakeCheckHealthClient{}
|
|
|
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{getSession: func(c awsds.SessionConfig) (*session.Session, error) {
|
|
return nil, fmt.Errorf("some sessions error")
|
|
}}, featuremgmt.WithFeatures())
|
|
|
|
resp, err := executor.CheckHealth(context.Background(), &backend.CheckHealthRequest{
|
|
PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}},
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &backend.CheckHealthResult{
|
|
Status: backend.HealthStatusError,
|
|
Message: "1. CloudWatch metrics query failed: some sessions error\n2. CloudWatch logs query failed: some sessions error",
|
|
}, resp)
|
|
})
|
|
}
|
|
|
|
func TestQuery_ResourceRequest_DescribeLogGroups_with_CrossAccountQuerying(t *testing.T) {
|
|
sender := &mockedCallResourceResponseSenderForOauth{}
|
|
origNewMetricsAPI := NewMetricsAPI
|
|
origNewOAMAPI := NewOAMAPI
|
|
origNewLogsAPI := NewLogsAPI
|
|
origNewEC2Client := NewEC2Client
|
|
NewMetricsAPI = func(sess *session.Session) models.CloudWatchMetricsAPIProvider { return nil }
|
|
NewOAMAPI = func(sess *session.Session) models.OAMAPIProvider { return nil }
|
|
NewEC2Client = func(provider awsclient.ConfigProvider) models.EC2APIProvider { return nil }
|
|
t.Cleanup(func() {
|
|
NewOAMAPI = origNewOAMAPI
|
|
NewMetricsAPI = origNewMetricsAPI
|
|
NewLogsAPI = origNewLogsAPI
|
|
NewEC2Client = origNewEC2Client
|
|
})
|
|
|
|
var logsApi mocks.LogsAPI
|
|
NewLogsAPI = func(sess *session.Session) models.CloudWatchLogsAPIProvider {
|
|
return &logsApi
|
|
}
|
|
|
|
im := datasource.NewInstanceManager(func(ctx context.Context, s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
|
return DataSource{Settings: models.CloudWatchSettings{
|
|
AWSDatasourceSettings: awsds.AWSDatasourceSettings{
|
|
Region: "us-east-1",
|
|
},
|
|
}}, nil
|
|
})
|
|
|
|
t.Run("maps log group api response to resource response of log-groups", func(t *testing.T) {
|
|
logsApi = mocks.LogsAPI{}
|
|
logsApi.On("DescribeLogGroups", mock.Anything).Return(&cloudwatchlogs.DescribeLogGroupsOutput{
|
|
LogGroups: []*cloudwatchlogs.LogGroup{
|
|
{Arn: aws.String("arn:aws:logs:us-east-1:111:log-group:group_a"), LogGroupName: aws.String("group_a")},
|
|
},
|
|
}, nil)
|
|
req := &backend.CallResourceRequest{
|
|
Method: "GET",
|
|
Path: `/log-groups?logGroupPattern=some-pattern&accountId=some-account-id`,
|
|
PluginContext: backend.PluginContext{
|
|
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ID: 0},
|
|
PluginID: "cloudwatch",
|
|
},
|
|
}
|
|
|
|
executor := newExecutor(im, newTestConfig(), &fakeSessionCache{}, featuremgmt.WithFeatures(featuremgmt.FlagCloudWatchCrossAccountQuerying))
|
|
err := executor.CallResource(context.Background(), req, sender)
|
|
assert.NoError(t, err)
|
|
|
|
assert.JSONEq(t, `[
|
|
{
|
|
"accountId":"111",
|
|
"value":{
|
|
"arn":"arn:aws:logs:us-east-1:111:log-group:group_a",
|
|
"name":"group_a"
|
|
}
|
|
}
|
|
]`, string(sender.Response.Body))
|
|
|
|
logsApi.AssertCalled(t, "DescribeLogGroups",
|
|
&cloudwatchlogs.DescribeLogGroupsInput{
|
|
AccountIdentifiers: []*string{utils.Pointer("some-account-id")},
|
|
IncludeLinkedAccounts: utils.Pointer(true),
|
|
Limit: utils.Pointer(int64(50)),
|
|
LogGroupNamePrefix: utils.Pointer("some-pattern"),
|
|
})
|
|
})
|
|
}
|