mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: Refactor dimension values resource request (#57346)
* refactor dimension values backend * fix test file name
This commit is contained in:
@@ -480,6 +480,42 @@ func Test_CloudWatch_CallResource_Integration_Test(t *testing.T) {
|
||||
return datasourceInfo{}, nil
|
||||
})
|
||||
|
||||
t.Run("Should handle dimension value request and return values from the api", func(t *testing.T) {
|
||||
pageLimit := 100
|
||||
api = mocks.FakeMetricsAPI{Metrics: []*cloudwatch.Metric{
|
||||
{MetricName: aws.String("Test_MetricName1"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName1"), Value: aws.String("Value1")}, {Name: aws.String("Test_DimensionName2"), Value: aws.String("Value2")}}},
|
||||
{MetricName: aws.String("Test_MetricName2"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName1"), Value: aws.String("Value3")}}},
|
||||
{MetricName: aws.String("Test_MetricName3"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName2"), Value: aws.String("Value1")}}},
|
||||
{MetricName: aws.String("Test_MetricName10"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName4"), Value: aws.String("Value2")}, {Name: aws.String("Test_DimensionName5")}}},
|
||||
{MetricName: aws.String("Test_MetricName4"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName2"), Value: aws.String("Value3")}}},
|
||||
{MetricName: aws.String("Test_MetricName5"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName1"), Value: aws.String("Value4")}}},
|
||||
{MetricName: aws.String("Test_MetricName6"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName1"), Value: aws.String("Value6")}}},
|
||||
{MetricName: aws.String("Test_MetricName7"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName4"), Value: aws.String("Value7")}}},
|
||||
{MetricName: aws.String("Test_MetricName8"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName4"), Value: aws.String("Value1")}}},
|
||||
{MetricName: aws.String("Test_MetricName9"), Dimensions: []*cloudwatch.Dimension{{Name: aws.String("Test_DimensionName1"), Value: aws.String("Value2")}}},
|
||||
}, MetricsPerPage: 100}
|
||||
executor := newExecutor(im, &setting.Cfg{AWSListMetricsPageLimit: pageLimit}, &fakeSessionCache{}, featuremgmt.WithFeatures())
|
||||
|
||||
req := &backend.CallResourceRequest{
|
||||
Method: "GET",
|
||||
Path: `/dimension-values?region=us-east-2&dimensionKey=Test_DimensionName4&namespace=AWS/EC2&metricName=CPUUtilization`,
|
||||
PluginContext: backend.PluginContext{
|
||||
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ID: 0},
|
||||
PluginID: "cloudwatch",
|
||||
},
|
||||
}
|
||||
err := executor.CallResource(context.Background(), req, sender)
|
||||
|
||||
require.NoError(t, err)
|
||||
sent := sender.Response
|
||||
require.NotNil(t, sent)
|
||||
require.Equal(t, http.StatusOK, sent.Status)
|
||||
res := []string{}
|
||||
err = json.Unmarshal(sent.Body, &res)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []string{"Value1", "Value2", "Value7"}, res)
|
||||
})
|
||||
|
||||
t.Run("Should handle dimension key filter query and return keys from the api", func(t *testing.T) {
|
||||
pageLimit := 3
|
||||
api = mocks.FakeMetricsAPI{Metrics: []*cloudwatch.Metric{
|
||||
|
||||
@@ -166,79 +166,6 @@ func (e *cloudWatchExecutor) handleGetAllMetrics(pluginCtx backend.PluginContext
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// handleGetDimensionValues returns a slice of suggestData structs with dimension values.
|
||||
// A call to the list metrics api is issued to retrieve the dimension values. All parameters are used as input args to the list metrics call.
|
||||
func (e *cloudWatchExecutor) handleGetDimensionValues(pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) {
|
||||
region := parameters.Get("region")
|
||||
namespace := parameters.Get("namespace")
|
||||
metricName := parameters.Get("metricName")
|
||||
dimensionKey := parameters.Get("dimensionKey")
|
||||
dimensionsJson := parameters.Get("dimensions")
|
||||
|
||||
dimensionsValues := map[string]interface{}{}
|
||||
err := json.Unmarshal([]byte(dimensionsJson), &dimensionsValues)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling dimension: %v", err)
|
||||
}
|
||||
|
||||
var dimensions []*cloudwatch.DimensionFilter
|
||||
addDimension := func(key string, value string) {
|
||||
filter := &cloudwatch.DimensionFilter{
|
||||
Name: aws.String(key),
|
||||
}
|
||||
// if value is not specified or a wildcard is used, simply don't use the value field
|
||||
if value != "" && value != "*" {
|
||||
filter.Value = aws.String(value)
|
||||
}
|
||||
dimensions = append(dimensions, filter)
|
||||
}
|
||||
|
||||
for k, v := range dimensionsValues {
|
||||
if vv, ok := v.(string); ok {
|
||||
addDimension(k, vv)
|
||||
} else if vv, ok := v.([]interface{}); ok {
|
||||
for _, v := range vv {
|
||||
addDimension(k, v.(string))
|
||||
}
|
||||
} else if v == nil {
|
||||
addDimension(k, "")
|
||||
}
|
||||
}
|
||||
|
||||
params := &cloudwatch.ListMetricsInput{
|
||||
Namespace: aws.String(namespace),
|
||||
Dimensions: dimensions,
|
||||
}
|
||||
if metricName != "" {
|
||||
params.MetricName = aws.String(metricName)
|
||||
}
|
||||
metrics, err := e.listMetrics(pluginCtx, region, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]suggestData, 0)
|
||||
dupCheck := make(map[string]bool)
|
||||
for _, metric := range metrics {
|
||||
for _, dim := range metric.Dimensions {
|
||||
if *dim.Name == dimensionKey {
|
||||
if _, exists := dupCheck[*dim.Value]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
dupCheck[*dim.Value] = true
|
||||
result = append(result, suggestData{Text: *dim.Value, Value: *dim.Value, Label: *dim.Value})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Text < result[j].Text
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (e *cloudWatchExecutor) handleGetEbsVolumeIds(pluginCtx backend.PluginContext, parameters url.Values) ([]suggestData, error) {
|
||||
region := parameters.Get("region")
|
||||
instanceId := parameters.Get("instanceId")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@@ -9,7 +9,13 @@ type ListMetricsServiceMock struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (a *ListMetricsServiceMock) GetDimensionKeysByDimensionFilter(*models.DimensionKeysRequest) ([]string, error) {
|
||||
func (a *ListMetricsServiceMock) GetDimensionKeysByDimensionFilter(*request.DimensionKeysRequest) ([]string, error) {
|
||||
args := a.Called()
|
||||
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (a *ListMetricsServiceMock) GetDimensionValuesByDimensionFilter(r *request.DimensionValuesRequest) ([]string, error) {
|
||||
args := a.Called()
|
||||
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/constants"
|
||||
)
|
||||
|
||||
type DimensionKeysRequestType uint32
|
||||
|
||||
const (
|
||||
StandardDimensionKeysRequest DimensionKeysRequestType = iota
|
||||
FilterDimensionKeysRequest
|
||||
CustomMetricDimensionKeysRequest
|
||||
)
|
||||
|
||||
type Dimension struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
|
||||
type DimensionKeysRequest struct {
|
||||
Region string `json:"region"`
|
||||
Namespace string `json:"namespace"`
|
||||
MetricName string `json:"metricName"`
|
||||
DimensionFilter []*Dimension
|
||||
}
|
||||
|
||||
func (q *DimensionKeysRequest) Type() DimensionKeysRequestType {
|
||||
if _, exist := constants.NamespaceMetricsMap[q.Namespace]; !exist {
|
||||
return CustomMetricDimensionKeysRequest
|
||||
}
|
||||
|
||||
if len(q.DimensionFilter) > 0 {
|
||||
return FilterDimensionKeysRequest
|
||||
}
|
||||
|
||||
return StandardDimensionKeysRequest
|
||||
}
|
||||
|
||||
func GetDimensionKeysRequest(parameters url.Values) (*DimensionKeysRequest, error) {
|
||||
req := &DimensionKeysRequest{
|
||||
Region: parameters.Get("region"),
|
||||
Namespace: parameters.Get("namespace"),
|
||||
MetricName: parameters.Get("metricName"),
|
||||
DimensionFilter: []*Dimension{},
|
||||
}
|
||||
|
||||
if req.Region == "" {
|
||||
return nil, fmt.Errorf("region is required")
|
||||
}
|
||||
|
||||
dimensionFilters := map[string]interface{}{}
|
||||
dimensionFilterJson := []byte(parameters.Get("dimensionFilters"))
|
||||
if len(dimensionFilterJson) > 0 {
|
||||
err := json.Unmarshal(dimensionFilterJson, &dimensionFilters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling dimensionFilters: %v", err)
|
||||
}
|
||||
}
|
||||
addDimension := func(key string, value string) {
|
||||
d := &Dimension{
|
||||
Name: key,
|
||||
}
|
||||
// if value is not specified or a wildcard is used, simply don't use the value field
|
||||
if value != "" && value != "*" {
|
||||
d.Value = value
|
||||
}
|
||||
req.DimensionFilter = append(req.DimensionFilter, d)
|
||||
}
|
||||
|
||||
for k, v := range dimensionFilters {
|
||||
// due to legacy, value can be a string, a string slice or nil
|
||||
if vv, ok := v.(string); ok {
|
||||
addDimension(k, vv)
|
||||
} else if vv, ok := v.([]interface{}); ok {
|
||||
for _, v := range vv {
|
||||
addDimension(k, v.(string))
|
||||
}
|
||||
} else if v == nil {
|
||||
addDimension(k, "")
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDimensionKeyQuery(t *testing.T) {
|
||||
t.Run("Should parse parameters without dimension filter", func(t *testing.T) {
|
||||
req, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", req.Region)
|
||||
assert.Equal(t, "AWS/EC2", req.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", req.MetricName)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with single valued dimension filter", func(t *testing.T) {
|
||||
req, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": \"i-1234567890abcdef0\"}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", req.Region)
|
||||
assert.Equal(t, "AWS/EC2", req.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", req.MetricName)
|
||||
assert.Equal(t, 1, len(req.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", req.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", req.DimensionFilter[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with multi-valued dimension filter", func(t *testing.T) {
|
||||
req, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"i-1234567890abcdef0\", \"i-1234567890abcdef1\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", req.Region)
|
||||
assert.Equal(t, "AWS/EC2", req.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", req.MetricName)
|
||||
assert.Equal(t, 2, len(req.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", req.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", req.DimensionFilter[0].Value)
|
||||
assert.Equal(t, "InstanceId", req.DimensionFilter[1].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef1", req.DimensionFilter[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with wildcard dimension filter", func(t *testing.T) {
|
||||
req, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"*\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", req.Region)
|
||||
assert.Equal(t, "AWS/EC2", req.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", req.MetricName)
|
||||
assert.Equal(t, 1, len(req.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", req.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "", req.DimensionFilter[0].Value)
|
||||
})
|
||||
}
|
||||
@@ -2,12 +2,14 @@ package models
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
)
|
||||
|
||||
type ListMetricsProvider interface {
|
||||
GetDimensionKeysByDimensionFilter(*DimensionKeysRequest) ([]string, error)
|
||||
GetDimensionKeysByDimensionFilter(*request.DimensionKeysRequest) ([]string, error)
|
||||
GetHardCodedDimensionKeysByNamespace(string) ([]string, error)
|
||||
GetDimensionKeysByNamespace(string) ([]string, error)
|
||||
GetDimensionValuesByDimensionFilter(*request.DimensionValuesRequest) ([]string, error)
|
||||
}
|
||||
|
||||
type MetricsClientProvider interface {
|
||||
|
||||
57
pkg/tsdb/cloudwatch/models/request/dimension_keys_request.go
Normal file
57
pkg/tsdb/cloudwatch/models/request/dimension_keys_request.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/constants"
|
||||
)
|
||||
|
||||
type DimensionKeysRequestType uint32
|
||||
|
||||
const (
|
||||
StandardDimensionKeysRequest DimensionKeysRequestType = iota
|
||||
FilterDimensionKeysRequest
|
||||
CustomMetricDimensionKeysRequest
|
||||
)
|
||||
|
||||
type DimensionKeysRequest struct {
|
||||
*ResourceRequest
|
||||
Namespace string
|
||||
MetricName string
|
||||
DimensionFilter []*Dimension
|
||||
}
|
||||
|
||||
func (q *DimensionKeysRequest) Type() DimensionKeysRequestType {
|
||||
if _, exist := constants.NamespaceMetricsMap[q.Namespace]; !exist {
|
||||
return CustomMetricDimensionKeysRequest
|
||||
}
|
||||
|
||||
if len(q.DimensionFilter) > 0 {
|
||||
return FilterDimensionKeysRequest
|
||||
}
|
||||
|
||||
return StandardDimensionKeysRequest
|
||||
}
|
||||
|
||||
func GetDimensionKeysRequest(parameters url.Values) (*DimensionKeysRequest, error) {
|
||||
resourceRequest, err := getResourceRequest(parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &DimensionKeysRequest{
|
||||
ResourceRequest: resourceRequest,
|
||||
Namespace: parameters.Get("namespace"),
|
||||
MetricName: parameters.Get("metricName"),
|
||||
DimensionFilter: []*Dimension{},
|
||||
}
|
||||
|
||||
dimensions, err := parseDimensionFilter(parameters.Get("dimensionFilters"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.DimensionFilter = dimensions
|
||||
|
||||
return request, nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDimensionKeyRequest(t *testing.T) {
|
||||
t.Run("Should parse parameters without dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with single valued dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": \"i-1234567890abcdef0\"}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 1, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", request.DimensionFilter[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with multi-valued dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"i-1234567890abcdef0\", \"i-1234567890abcdef1\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 2, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", request.DimensionFilter[0].Value)
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[1].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef1", request.DimensionFilter[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with wildcard dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionKeysRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"*\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 1, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "", request.DimensionFilter[0].Value)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type DimensionValuesRequest struct {
|
||||
*ResourceRequest
|
||||
Namespace string
|
||||
MetricName string
|
||||
DimensionKey string
|
||||
DimensionFilter []*Dimension
|
||||
}
|
||||
|
||||
func GetDimensionValuesRequest(parameters url.Values) (*DimensionValuesRequest, error) {
|
||||
resourceRequest, err := getResourceRequest(parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &DimensionValuesRequest{
|
||||
ResourceRequest: resourceRequest,
|
||||
Namespace: parameters.Get("namespace"),
|
||||
MetricName: parameters.Get("metricName"),
|
||||
DimensionKey: parameters.Get("dimensionKey"),
|
||||
DimensionFilter: []*Dimension{},
|
||||
}
|
||||
|
||||
dimensions, err := parseDimensionFilter(parameters.Get("dimensionFilters"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.DimensionFilter = dimensions
|
||||
|
||||
return request, nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDimensionValuesRequest(t *testing.T) {
|
||||
t.Run("Should parse parameters without dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionValuesRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionKey": {"InstanceId"}},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, "InstanceId", request.DimensionKey)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with single valued dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionValuesRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionKey": {"InstanceId"},
|
||||
"dimensionFilters": {"{\"InstanceId\": \"i-1234567890abcdef0\"}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 1, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionKey)
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", request.DimensionFilter[0].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with multi-valued dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionValuesRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionKey": {"InstanceId"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"i-1234567890abcdef0\", \"i-1234567890abcdef1\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 2, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionKey)
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef0", request.DimensionFilter[0].Value)
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[1].Name)
|
||||
assert.Equal(t, "i-1234567890abcdef1", request.DimensionFilter[1].Value)
|
||||
})
|
||||
|
||||
t.Run("Should parse parameters with wildcard dimension filter", func(t *testing.T) {
|
||||
request, err := GetDimensionValuesRequest(map[string][]string{
|
||||
"region": {"us-east-1"},
|
||||
"namespace": {"AWS/EC2"},
|
||||
"metricName": {"CPUUtilization"},
|
||||
"dimensionFilters": {"{\"InstanceId\": [\"*\"]}"},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "us-east-1", request.Region)
|
||||
assert.Equal(t, "AWS/EC2", request.Namespace)
|
||||
assert.Equal(t, "CPUUtilization", request.MetricName)
|
||||
assert.Equal(t, 1, len(request.DimensionFilter))
|
||||
assert.Equal(t, "InstanceId", request.DimensionFilter[0].Name)
|
||||
assert.Equal(t, "", request.DimensionFilter[0].Value)
|
||||
})
|
||||
}
|
||||
22
pkg/tsdb/cloudwatch/models/request/resource_request.go
Normal file
22
pkg/tsdb/cloudwatch/models/request/resource_request.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type ResourceRequest struct {
|
||||
Region string
|
||||
}
|
||||
|
||||
func getResourceRequest(parameters url.Values) (*ResourceRequest, error) {
|
||||
request := &ResourceRequest{
|
||||
Region: parameters.Get("region"),
|
||||
}
|
||||
|
||||
if request.Region == "" {
|
||||
return nil, fmt.Errorf("region is required")
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
16
pkg/tsdb/cloudwatch/models/request/resource_request_test.go
Normal file
16
pkg/tsdb/cloudwatch/models/request/resource_request_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResourceRequest(t *testing.T) {
|
||||
t.Run("Should return an error if region is not provided", func(t *testing.T) {
|
||||
request, err := GetDimensionValuesRequest(map[string][]string{})
|
||||
require.Nil(t, request)
|
||||
assert.Equal(t, "region is required", err.Error())
|
||||
})
|
||||
}
|
||||
6
pkg/tsdb/cloudwatch/models/request/types.go
Normal file
6
pkg/tsdb/cloudwatch/models/request/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package request
|
||||
|
||||
type Dimension struct {
|
||||
Name string
|
||||
Value string
|
||||
}
|
||||
44
pkg/tsdb/cloudwatch/models/request/utils.go
Normal file
44
pkg/tsdb/cloudwatch/models/request/utils.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func parseDimensionFilter(dimensionFilter string) ([]*Dimension, error) {
|
||||
dimensionFilters := map[string]interface{}{}
|
||||
dimensionFilterJson := []byte(dimensionFilter)
|
||||
if len(dimensionFilterJson) > 0 {
|
||||
err := json.Unmarshal(dimensionFilterJson, &dimensionFilters)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling dimensionFilters: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
dimensions := []*Dimension{}
|
||||
addDimension := func(key string, value string) {
|
||||
d := &Dimension{
|
||||
Name: key,
|
||||
}
|
||||
// if value is not specified or a wildcard is used, simply don't use the value field
|
||||
if value != "" && value != "*" {
|
||||
d.Value = value
|
||||
}
|
||||
dimensions = append(dimensions, d)
|
||||
}
|
||||
|
||||
for k, v := range dimensionFilters {
|
||||
// due to legacy, value can be a string, a string slice or nil
|
||||
if vv, ok := v.(string); ok {
|
||||
addDimension(k, vv)
|
||||
} else if vv, ok := v.([]interface{}); ok {
|
||||
for _, v := range vv {
|
||||
addDimension(k, v.(string))
|
||||
}
|
||||
} else if v == nil {
|
||||
addDimension(k, "")
|
||||
}
|
||||
}
|
||||
|
||||
return dimensions, nil
|
||||
}
|
||||
@@ -18,12 +18,12 @@ func (e *cloudWatchExecutor) newResourceMux() *http.ServeMux {
|
||||
mux.HandleFunc("/namespaces", handleResourceReq(e.handleGetNamespaces))
|
||||
mux.HandleFunc("/metrics", handleResourceReq(e.handleGetMetrics))
|
||||
mux.HandleFunc("/all-metrics", handleResourceReq(e.handleGetAllMetrics))
|
||||
mux.HandleFunc("/dimension-values", handleResourceReq(e.handleGetDimensionValues))
|
||||
mux.HandleFunc("/ebs-volume-ids", handleResourceReq(e.handleGetEbsVolumeIds))
|
||||
mux.HandleFunc("/ec2-instance-attribute", handleResourceReq(e.handleGetEc2InstanceAttribute))
|
||||
mux.HandleFunc("/resource-arns", handleResourceReq(e.handleGetResourceArns))
|
||||
mux.HandleFunc("/log-groups", handleResourceReq(e.handleGetLogGroups))
|
||||
mux.HandleFunc("/all-log-groups", handleResourceReq(e.handleGetAllLogGroups))
|
||||
mux.HandleFunc("/dimension-values", routes.ResourceRequestMiddleware(routes.DimensionValuesHandler, e.getClients))
|
||||
mux.HandleFunc("/dimension-keys", routes.ResourceRequestMiddleware(routes.DimensionKeysHandler, e.getClients))
|
||||
return mux
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/services"
|
||||
)
|
||||
|
||||
func DimensionKeysHandler(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
dimensionKeysRequest, err := models.GetDimensionKeysRequest(parameters)
|
||||
dimensionKeysRequest, err := request.GetDimensionKeysRequest(parameters)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionKeyHandler", http.StatusBadRequest, err)
|
||||
}
|
||||
@@ -23,11 +24,11 @@ func DimensionKeysHandler(pluginCtx backend.PluginContext, clientFactory models.
|
||||
|
||||
dimensionKeys := []string{}
|
||||
switch dimensionKeysRequest.Type() {
|
||||
case models.StandardDimensionKeysRequest:
|
||||
case request.StandardDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetHardCodedDimensionKeysByNamespace(dimensionKeysRequest.Namespace)
|
||||
case models.FilterDimensionKeysRequest:
|
||||
case request.FilterDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetDimensionKeysByDimensionFilter(dimensionKeysRequest)
|
||||
case models.CustomMetricDimensionKeysRequest:
|
||||
case request.CustomMetricDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetDimensionKeysByNamespace(dimensionKeysRequest.Namespace)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -13,22 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_DimensionKeys_Route(t *testing.T) {
|
||||
t.Run("rejects POST method", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/dimension-keys?region=us-east-1", nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionKeysHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("requires region query value", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/dimension-keys", nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionKeysHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
url string
|
||||
methodName string
|
||||
|
||||
35
pkg/tsdb/cloudwatch/routes/dimension_values.go
Normal file
35
pkg/tsdb/cloudwatch/routes/dimension_values.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
)
|
||||
|
||||
func DimensionValuesHandler(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
dimensionValuesRequest, err := request.GetDimensionValuesRequest(parameters)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionValuesHandler", http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
service, err := newListMetricsService(pluginCtx, clientFactory, dimensionValuesRequest.Region)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionValuesHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
dimensionValues, err := service.GetDimensionValuesByDimensionFilter(dimensionValuesRequest)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionValuesHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
dimensionValuesResponse, err := json.Marshal(dimensionValues)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionValuesHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return dimensionValuesResponse, nil
|
||||
}
|
||||
41
pkg/tsdb/cloudwatch/routes/dimension_values_test.go
Normal file
41
pkg/tsdb/cloudwatch/routes/dimension_values_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_DimensionValues_Route(t *testing.T) {
|
||||
t.Run("Calls GetDimensionValuesByDimensionFilter when a valid request is passed", func(t *testing.T) {
|
||||
mockListMetricsService := mocks.ListMetricsServiceMock{}
|
||||
mockListMetricsService.On("GetDimensionValuesByDimensionFilter").Return([]string{}, nil)
|
||||
newListMetricsService = func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, region string) (models.ListMetricsProvider, error) {
|
||||
return &mockListMetricsService, nil
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", `/dimension-values?region=us-east-2&dimensionKey=instanceId&namespace=AWS/EC2&metricName=CPUUtilization&dimensionFilters={"NodeID":["Shared"],"stage":["QueryCommit"]}`, nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionValuesHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
})
|
||||
|
||||
t.Run("returns 500 if GetDimensionValuesByDimensionFilter returns an error", func(t *testing.T) {
|
||||
mockListMetricsService := mocks.ListMetricsServiceMock{}
|
||||
mockListMetricsService.On("GetDimensionValuesByDimensionFilter").Return([]string{}, fmt.Errorf("some error"))
|
||||
newListMetricsService = func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, region string) (models.ListMetricsProvider, error) {
|
||||
return &mockListMetricsService, nil
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", `/dimension-values?region=us-east-2&dimensionKey=instanceId&namespace=AWS/EC2&metricName=CPUUtilization&dimensionFilters={"NodeID":["Shared"],"stage":["QueryCommit"]}`, nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionValuesHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
assert.Equal(t, `{"Message":"error in DimensionValuesHandler: some error","Error":"some error","StatusCode":500}`, rr.Body.String())
|
||||
})
|
||||
}
|
||||
48
pkg/tsdb/cloudwatch/routes/middleware_test.go
Normal file
48
pkg/tsdb/cloudwatch/routes/middleware_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Middleware(t *testing.T) {
|
||||
t.Run("rejects POST method", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/dimension-keys?region=us-east-1", nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
return []byte{}, nil
|
||||
}, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusMethodNotAllowed, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("injects plugincontext to handler", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/some-path", nil)
|
||||
var testPluginContext backend.PluginContext
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
testPluginContext = pluginCtx
|
||||
return []byte{}, nil
|
||||
}, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.NotNil(t, testPluginContext)
|
||||
})
|
||||
|
||||
t.Run("should propagate handler error to response", func(t *testing.T) {
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/some-path", nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
return []byte{}, models.NewHttpError("error", http.StatusBadRequest, fmt.Errorf("error from handler"))
|
||||
}, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Equal(t, `{"Message":"error: error from handler","Error":"error from handler","StatusCode":400}`, rr.Body.String())
|
||||
})
|
||||
}
|
||||
@@ -2,11 +2,13 @@ package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"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"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
)
|
||||
|
||||
type ListMetricsService struct {
|
||||
@@ -26,7 +28,7 @@ func (*ListMetricsService) GetHardCodedDimensionKeysByNamespace(namespace string
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
|
||||
func (l *ListMetricsService) GetDimensionKeysByDimensionFilter(r *models.DimensionKeysRequest) ([]string, error) {
|
||||
func (l *ListMetricsService) GetDimensionKeysByDimensionFilter(r *request.DimensionKeysRequest) ([]string, error) {
|
||||
input := &cloudwatch.ListMetricsInput{}
|
||||
if r.Namespace != "" {
|
||||
input.Namespace = aws.String(r.Namespace)
|
||||
@@ -34,15 +36,7 @@ func (l *ListMetricsService) GetDimensionKeysByDimensionFilter(r *models.Dimensi
|
||||
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)
|
||||
}
|
||||
setDimensionFilter(input, r.DimensionFilter)
|
||||
|
||||
metrics, err := l.ListMetricsWithPageLimit(input)
|
||||
if err != nil {
|
||||
@@ -79,6 +73,37 @@ func (l *ListMetricsService) GetDimensionKeysByDimensionFilter(r *models.Dimensi
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
|
||||
func (l *ListMetricsService) GetDimensionValuesByDimensionFilter(r *request.DimensionValuesRequest) ([]string, error) {
|
||||
input := &cloudwatch.ListMetricsInput{
|
||||
Namespace: aws.String(r.Namespace),
|
||||
MetricName: aws.String(r.MetricName),
|
||||
}
|
||||
setDimensionFilter(input, r.DimensionFilter)
|
||||
|
||||
metrics, err := l.ListMetricsWithPageLimit(input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", "unable to call AWS API", err)
|
||||
}
|
||||
|
||||
var dimensionValues []string
|
||||
dupCheck := make(map[string]bool)
|
||||
for _, metric := range metrics {
|
||||
for _, dim := range metric.Dimensions {
|
||||
if *dim.Name == r.DimensionKey {
|
||||
if _, exists := dupCheck[*dim.Value]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
dupCheck[*dim.Value] = true
|
||||
dimensionValues = append(dimensionValues, *dim.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(dimensionValues)
|
||||
return dimensionValues, nil
|
||||
}
|
||||
|
||||
func (l *ListMetricsService) GetDimensionKeysByNamespace(namespace string) ([]string, error) {
|
||||
metrics, err := l.ListMetricsWithPageLimit(&cloudwatch.ListMetricsInput{Namespace: aws.String(namespace)})
|
||||
if err != nil {
|
||||
@@ -100,3 +125,15 @@ func (l *ListMetricsService) GetDimensionKeysByNamespace(namespace string) ([]st
|
||||
|
||||
return dimensionKeys, nil
|
||||
}
|
||||
|
||||
func setDimensionFilter(input *cloudwatch.ListMetricsInput, dimensionFilter []*request.Dimension) {
|
||||
for _, dimension := range dimensionFilter {
|
||||
df := &cloudwatch.DimensionFilter{
|
||||
Name: aws.String(dimension.Name),
|
||||
}
|
||||
if dimension.Value != "" {
|
||||
df.Value = aws.String(dimension.Value)
|
||||
}
|
||||
input.Dimensions = append(input.Dimensions, df)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"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/grafana/grafana/pkg/tsdb/cloudwatch/models/request"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -64,11 +64,11 @@ func TestListMetricsService_GetDimensionKeysByDimensionFilter(t *testing.T) {
|
||||
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{
|
||||
resp, err := listMetricsService.GetDimensionKeysByDimensionFilter(&request.DimensionKeysRequest{
|
||||
ResourceRequest: &request.ResourceRequest{Region: "us-east-1"},
|
||||
Namespace: "AWS/EC2",
|
||||
MetricName: "CPUUtilization",
|
||||
DimensionFilter: []*request.Dimension{
|
||||
{Name: "InstanceId", Value: ""},
|
||||
},
|
||||
})
|
||||
@@ -90,3 +90,24 @@ func TestListMetricsService_GetDimensionKeysByNamespace(t *testing.T) {
|
||||
assert.Equal(t, []string{"InstanceId", "InstanceType", "AutoScalingGroupName"}, resp)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListMetricsService_GetDimensionValuesByDimensionFilter(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.GetDimensionValuesByDimensionFilter(&request.DimensionValuesRequest{
|
||||
ResourceRequest: &request.ResourceRequest{Region: "us-east-1"},
|
||||
Namespace: "AWS/EC2",
|
||||
MetricName: "CPUUtilization",
|
||||
DimensionKey: "InstanceId",
|
||||
DimensionFilter: []*request.Dimension{
|
||||
{Name: "InstanceId", Value: ""},
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"i-1234567890abcdef0", "i-5234567890abcdef0", "i-64234567890abcdef0"}, resp)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,19 +88,19 @@ export class CloudWatchAPI extends CloudWatchRequest {
|
||||
namespace: string | undefined,
|
||||
metricName: string | undefined,
|
||||
dimensionKey: string,
|
||||
filterDimensions: {}
|
||||
dimensionFilters: {}
|
||||
) {
|
||||
if (!namespace || !metricName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = await this.memoizedGetRequest<SelectableResourceValue[]>('dimension-values', {
|
||||
const values = await this.memoizedGetRequest<string[]>('dimension-values', {
|
||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||
namespace: this.templateSrv.replace(namespace),
|
||||
metricName: this.templateSrv.replace(metricName.trim()),
|
||||
dimensionKey: this.templateSrv.replace(dimensionKey),
|
||||
dimensions: JSON.stringify(this.convertDimensionFormat(filterDimensions, {})),
|
||||
});
|
||||
dimensionFilters: JSON.stringify(this.convertDimensionFormat(dimensionFilters, {})),
|
||||
}).then((dimensionValues) => dimensionValues.map(toOption));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user