Cloudwatch: Refactor dimension keys resource request (#57148)
* use new layered architecture in get dimension keys request * go lint fixes * pr feedback * more pr feedback * remove not used code * refactor route middleware * change signature * add integration tests for the dimension keys route * use request suffix instead of query * use typed args also in frontend * remove unused import * harmonize naming * fix merge conflict
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/constants"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
)
|
||||
|
||||
type ListMetricsService struct {
|
||||
models.MetricsClientProvider
|
||||
}
|
||||
|
||||
func NewListMetricsService(metricsClient models.MetricsClientProvider) models.ListMetricsProvider {
|
||||
return &ListMetricsService{metricsClient}
|
||||
}
|
||||
|
||||
func (*ListMetricsService) GetHardCodedDimensionKeysByNamespace(namespace string) ([]string, error) {
|
||||
var dimensionKeys []string
|
||||
exists := false
|
||||
if dimensionKeys, exists = constants.NamespaceDimensionKeysMap[namespace]; !exists {
|
||||
return nil, fmt.Errorf("unable to find dimensions for namespace '%q'", namespace)
|
||||
}
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
|
||||
func (l *ListMetricsService) GetDimensionKeysByDimensionFilter(r *models.DimensionKeysRequest) ([]string, error) {
|
||||
input := &cloudwatch.ListMetricsInput{}
|
||||
if r.Namespace != "" {
|
||||
input.Namespace = aws.String(r.Namespace)
|
||||
}
|
||||
if r.MetricName != "" {
|
||||
input.MetricName = aws.String(r.MetricName)
|
||||
}
|
||||
for _, dimension := range r.DimensionFilter {
|
||||
df := &cloudwatch.DimensionFilter{
|
||||
Name: aws.String(dimension.Name),
|
||||
}
|
||||
if dimension.Value != "" {
|
||||
df.Value = aws.String(dimension.Value)
|
||||
}
|
||||
input.Dimensions = append(input.Dimensions, df)
|
||||
}
|
||||
|
||||
metrics, err := l.ListMetricsWithPageLimit(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", "unable to call AWS API", err)
|
||||
}
|
||||
|
||||
var dimensionKeys []string
|
||||
// remove duplicates
|
||||
dupCheck := make(map[string]struct{})
|
||||
for _, metric := range metrics {
|
||||
for _, dim := range metric.Dimensions {
|
||||
if _, exists := dupCheck[*dim.Name]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// keys in the dimension filter should not be included
|
||||
dimensionFilterExist := false
|
||||
for _, d := range r.DimensionFilter {
|
||||
if d.Name == *dim.Name {
|
||||
dimensionFilterExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dimensionFilterExist {
|
||||
continue
|
||||
}
|
||||
|
||||
dupCheck[*dim.Name] = struct{}{}
|
||||
dimensionKeys = append(dimensionKeys, *dim.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
|
||||
func (l *ListMetricsService) GetDimensionKeysByNamespace(namespace string) ([]string, error) {
|
||||
metrics, err := l.ListMetricsWithPageLimit(&cloudwatch.ListMetricsInput{Namespace: aws.String(namespace)})
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
var dimensionKeys []string
|
||||
dupCheck := make(map[string]struct{})
|
||||
for _, metric := range metrics {
|
||||
for _, dim := range metric.Dimensions {
|
||||
if _, exists := dupCheck[*dim.Name]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
dupCheck[*dim.Name] = struct{}{}
|
||||
dimensionKeys = append(dimensionKeys, *dim.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var metricResponse = []*cloudwatch.Metric{
|
||||
{
|
||||
MetricName: aws.String("CPUUtilization"),
|
||||
Namespace: aws.String("AWS/EC2"),
|
||||
Dimensions: []*cloudwatch.Dimension{
|
||||
{Name: aws.String("InstanceId"), Value: aws.String("i-1234567890abcdef0")},
|
||||
{Name: aws.String("InstanceType"), Value: aws.String("t2.micro")},
|
||||
},
|
||||
},
|
||||
{
|
||||
MetricName: aws.String("CPUUtilization"),
|
||||
Namespace: aws.String("AWS/EC2"),
|
||||
Dimensions: []*cloudwatch.Dimension{
|
||||
{Name: aws.String("InstanceId"), Value: aws.String("i-5234567890abcdef0")},
|
||||
{Name: aws.String("InstanceType"), Value: aws.String("t2.micro")},
|
||||
{Name: aws.String("AutoScalingGroupName"), Value: aws.String("my-asg")},
|
||||
},
|
||||
},
|
||||
{
|
||||
MetricName: aws.String("CPUUtilization"),
|
||||
Namespace: aws.String("AWS/EC2"),
|
||||
Dimensions: []*cloudwatch.Dimension{
|
||||
{Name: aws.String("InstanceId"), Value: aws.String("i-64234567890abcdef0")},
|
||||
{Name: aws.String("InstanceType"), Value: aws.String("t3.micro")},
|
||||
{Name: aws.String("AutoScalingGroupName"), Value: aws.String("my-asg2")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestListMetricsService_GetHardCodedDimensionKeysByNamespace(t *testing.T) {
|
||||
t.Run("Should return an error in case namespace doesnt exist in map", func(t *testing.T) {
|
||||
listMetricsService := NewListMetricsService(&mocks.FakeMetricsClient{})
|
||||
resp, err := listMetricsService.GetHardCodedDimensionKeysByNamespace("unknownNamespace")
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, resp)
|
||||
assert.Equal(t, err.Error(), "unable to find dimensions for namespace '\"unknownNamespace\"'")
|
||||
})
|
||||
|
||||
t.Run("Should return keys if namespace exist", func(t *testing.T) {
|
||||
listMetricsService := NewListMetricsService(&mocks.FakeMetricsClient{})
|
||||
resp, err := listMetricsService.GetHardCodedDimensionKeysByNamespace("AWS/EC2")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListMetricsService_GetDimensionKeysByDimensionFilter(t *testing.T) {
|
||||
t.Run("Should filter out duplicates and keys matching dimension filter keys", func(t *testing.T) {
|
||||
fakeMetricsClient := &mocks.FakeMetricsClient{}
|
||||
fakeMetricsClient.On("ListMetricsWithPageLimit", mock.Anything).Return(metricResponse, nil)
|
||||
listMetricsService := NewListMetricsService(fakeMetricsClient)
|
||||
|
||||
resp, err := listMetricsService.GetDimensionKeysByDimensionFilter(&models.DimensionKeysRequest{
|
||||
Region: "us-east-1",
|
||||
Namespace: "AWS/EC2",
|
||||
MetricName: "CPUUtilization",
|
||||
DimensionFilter: []*models.Dimension{
|
||||
{Name: "InstanceId", Value: ""},
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"InstanceType", "AutoScalingGroupName"}, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListMetricsService_GetDimensionKeysByNamespace(t *testing.T) {
|
||||
t.Run("Should filter out duplicates and keys matching dimension filter keys", func(t *testing.T) {
|
||||
fakeMetricsClient := &mocks.FakeMetricsClient{}
|
||||
fakeMetricsClient.On("ListMetricsWithPageLimit", mock.Anything).Return(metricResponse, nil)
|
||||
listMetricsService := NewListMetricsService(fakeMetricsClient)
|
||||
|
||||
resp, err := listMetricsService.GetDimensionKeysByNamespace("AWS/EC2")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"InstanceId", "InstanceType", "AutoScalingGroupName"}, resp)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user