mirror of https://github.com/grafana/grafana
Cloudwatch: Refactor metrics resource request (#57424)
* refactor metrics request * Update pkg/tsdb/cloudwatch/routes/dimension_keys_test.go Co-authored-by: Shirley <4163034+fridgepoet@users.noreply.github.com> * return metric struct value intead of pointer * make it possible to test hard coded metrics service * test all paths in route * fix broken test * fix one more broken test * add integration test Co-authored-by: Shirley <4163034+fridgepoet@users.noreply.github.com>pull/57600/head
parent
53d7404e2b
commit
4c654ddb76
@ -0,0 +1,42 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"net/url" |
||||
) |
||||
|
||||
type MetricsRequestType uint32 |
||||
|
||||
const ( |
||||
MetricsByNamespaceRequestType MetricsRequestType = iota |
||||
AllMetricsRequestType |
||||
CustomNamespaceRequestType |
||||
) |
||||
|
||||
type MetricsRequest struct { |
||||
*ResourceRequest |
||||
Namespace string |
||||
} |
||||
|
||||
func GetMetricsRequest(parameters url.Values) (*MetricsRequest, error) { |
||||
resourceRequest, err := getResourceRequest(parameters) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &MetricsRequest{ |
||||
ResourceRequest: resourceRequest, |
||||
Namespace: parameters.Get("namespace"), |
||||
}, nil |
||||
} |
||||
|
||||
func (r *MetricsRequest) Type() MetricsRequestType { |
||||
if r.Namespace == "" { |
||||
return AllMetricsRequestType |
||||
} |
||||
|
||||
if isCustomNamespace(r.Namespace) { |
||||
return CustomNamespaceRequestType |
||||
} |
||||
|
||||
return MetricsByNamespaceRequestType |
||||
} |
@ -0,0 +1,48 @@ |
||||
package request |
||||
|
||||
import ( |
||||
"net/url" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestMetricsRequest(t *testing.T) { |
||||
t.Run("Should parse parameters", func(t *testing.T) { |
||||
request, err := GetMetricsRequest(map[string][]string{"region": {"us-east-1"}, "namespace": {"AWS/EC2"}}) |
||||
require.NoError(t, err) |
||||
assert.Equal(t, "us-east-1", request.Region) |
||||
assert.Equal(t, "AWS/EC2", request.Namespace) |
||||
}) |
||||
|
||||
tests := []struct { |
||||
reqType MetricsRequestType |
||||
params url.Values |
||||
}{ |
||||
{ |
||||
params: map[string][]string{"region": {"us-east-1"}, "namespace": {"AWS/EC2"}}, |
||||
reqType: MetricsByNamespaceRequestType, |
||||
}, |
||||
{ |
||||
params: map[string][]string{"region": {"us-east-1"}}, |
||||
reqType: AllMetricsRequestType, |
||||
}, |
||||
{ |
||||
params: map[string][]string{"region": {"us-east-1"}, "namespace": {""}}, |
||||
reqType: AllMetricsRequestType, |
||||
}, |
||||
{ |
||||
params: map[string][]string{"region": {"us-east-1"}, "namespace": {"custom-namespace"}}, |
||||
reqType: CustomNamespaceRequestType, |
||||
}, |
||||
} |
||||
|
||||
for _, tc := range tests { |
||||
t.Run("Should resolve the correct type", func(t *testing.T) { |
||||
request, err := GetMetricsRequest(tc.params) |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tc.reqType, request.Type()) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
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" |
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/services" |
||||
) |
||||
|
||||
func MetricsHandler(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) { |
||||
metricsRequest, err := request.GetMetricsRequest(parameters) |
||||
if err != nil { |
||||
return nil, models.NewHttpError("error in MetricsHandler", http.StatusBadRequest, err) |
||||
} |
||||
|
||||
service, err := newListMetricsService(pluginCtx, clientFactory, metricsRequest.Region) |
||||
if err != nil { |
||||
return nil, models.NewHttpError("error in MetricsHandler", http.StatusInternalServerError, err) |
||||
} |
||||
|
||||
var metrics []models.Metric |
||||
switch metricsRequest.Type() { |
||||
case request.AllMetricsRequestType: |
||||
metrics = services.GetAllHardCodedMetrics() |
||||
case request.MetricsByNamespaceRequestType: |
||||
metrics, err = services.GetHardCodedMetricsByNamespace(metricsRequest.Namespace) |
||||
case request.CustomNamespaceRequestType: |
||||
metrics, err = service.GetMetricsByNamespace(metricsRequest.Namespace) |
||||
} |
||||
if err != nil { |
||||
return nil, models.NewHttpError("error in MetricsHandler", http.StatusInternalServerError, err) |
||||
} |
||||
|
||||
metricsResponse, err := json.Marshal(metrics) |
||||
if err != nil { |
||||
return nil, models.NewHttpError("error in MetricsHandler", http.StatusInternalServerError, err) |
||||
} |
||||
|
||||
return metricsResponse, nil |
||||
} |
@ -0,0 +1,88 @@ |
||||
package routes |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"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/grafana/grafana/pkg/tsdb/cloudwatch/services" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func Test_Metrics_Route(t *testing.T) { |
||||
t.Run("calls GetMetricsByNamespace when a CustomNamespaceRequestType is passed", func(t *testing.T) { |
||||
mockListMetricsService := mocks.ListMetricsServiceMock{} |
||||
mockListMetricsService.On("GetMetricsByNamespace").Return([]models.Metric{}, nil) |
||||
newListMetricsService = func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, region string) (models.ListMetricsProvider, error) { |
||||
return &mockListMetricsService, nil |
||||
} |
||||
rr := httptest.NewRecorder() |
||||
req := httptest.NewRequest("GET", "/metrics?region=us-east-2&namespace=customNamespace", nil) |
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(MetricsHandler, nil)) |
||||
handler.ServeHTTP(rr, req) |
||||
mockListMetricsService.AssertNumberOfCalls(t, "GetMetricsByNamespace", 1) |
||||
}) |
||||
|
||||
t.Run("calls GetAllHardCodedMetrics when a AllMetricsRequestType is passed", func(t *testing.T) { |
||||
origGetAllHardCodedMetrics := services.GetAllHardCodedMetrics |
||||
t.Cleanup(func() { |
||||
services.GetAllHardCodedMetrics = origGetAllHardCodedMetrics |
||||
}) |
||||
haveBeenCalled := false |
||||
services.GetAllHardCodedMetrics = func() []models.Metric { |
||||
haveBeenCalled = true |
||||
return []models.Metric{} |
||||
} |
||||
rr := httptest.NewRecorder() |
||||
req := httptest.NewRequest("GET", "/metrics?region=us-east-2", nil) |
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(MetricsHandler, nil)) |
||||
handler.ServeHTTP(rr, req) |
||||
res := []models.Metric{} |
||||
err := json.Unmarshal(rr.Body.Bytes(), &res) |
||||
require.Nil(t, err) |
||||
assert.True(t, haveBeenCalled) |
||||
}) |
||||
|
||||
t.Run("calls GetHardCodedMetricsByNamespace when a MetricsByNamespaceRequestType is passed", func(t *testing.T) { |
||||
origGetHardCodedMetricsByNamespace := services.GetHardCodedMetricsByNamespace |
||||
t.Cleanup(func() { |
||||
services.GetHardCodedMetricsByNamespace = origGetHardCodedMetricsByNamespace |
||||
}) |
||||
haveBeenCalled := false |
||||
usedNamespace := "" |
||||
services.GetHardCodedMetricsByNamespace = func(namespace string) ([]models.Metric, error) { |
||||
haveBeenCalled = true |
||||
usedNamespace = namespace |
||||
return []models.Metric{}, nil |
||||
} |
||||
rr := httptest.NewRecorder() |
||||
req := httptest.NewRequest("GET", "/metrics?region=us-east-2&namespace=AWS/DMS", nil) |
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(MetricsHandler, nil)) |
||||
handler.ServeHTTP(rr, req) |
||||
res := []models.Metric{} |
||||
err := json.Unmarshal(rr.Body.Bytes(), &res) |
||||
require.Nil(t, err) |
||||
assert.True(t, haveBeenCalled) |
||||
assert.Equal(t, "AWS/DMS", usedNamespace) |
||||
}) |
||||
|
||||
t.Run("returns 500 if GetMetricsByNamespace returns an error", func(t *testing.T) { |
||||
mockListMetricsService := mocks.ListMetricsServiceMock{} |
||||
mockListMetricsService.On("GetMetricsByNamespace").Return([]models.Metric{}, 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", "/metrics?region=us-east-2&namespace=customNamespace", nil) |
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(MetricsHandler, nil)) |
||||
handler.ServeHTTP(rr, req) |
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code) |
||||
assert.Equal(t, `{"Message":"error in MetricsHandler: some error","Error":"some error","StatusCode":500}`, rr.Body.String()) |
||||
}) |
||||
} |
@ -0,0 +1,43 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/constants" |
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" |
||||
) |
||||
|
||||
var GetHardCodedDimensionKeysByNamespace = func(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 |
||||
} |
||||
|
||||
var GetHardCodedMetricsByNamespace = func(namespace string) ([]models.Metric, error) { |
||||
response := []models.Metric{} |
||||
exists := false |
||||
var metrics []string |
||||
if metrics, exists = constants.NamespaceMetricsMap[namespace]; !exists { |
||||
return nil, fmt.Errorf("unable to find metrics for namespace '%q'", namespace) |
||||
} |
||||
|
||||
for _, metric := range metrics { |
||||
response = append(response, models.Metric{Namespace: namespace, Name: metric}) |
||||
} |
||||
|
||||
return response, nil |
||||
} |
||||
|
||||
var GetAllHardCodedMetrics = func() []models.Metric { |
||||
response := []models.Metric{} |
||||
for namespace, metrics := range constants.NamespaceMetricsMap { |
||||
for _, metric := range metrics { |
||||
response = append(response, models.Metric{Namespace: namespace, Name: metric}) |
||||
} |
||||
} |
||||
|
||||
return response |
||||
} |
@ -0,0 +1,39 @@ |
||||
package services |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestHardcodedMetrics_GetHardCodedDimensionKeysByNamespace(t *testing.T) { |
||||
t.Run("Should return an error in case namespace doesnt exist in map", func(t *testing.T) { |
||||
resp, err := 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) { |
||||
resp, err := GetHardCodedDimensionKeysByNamespace("AWS/EC2") |
||||
require.NoError(t, err) |
||||
assert.Equal(t, []string{"AutoScalingGroupName", "ImageId", "InstanceId", "InstanceType"}, resp) |
||||
}) |
||||
} |
||||
|
||||
func TestHardcodedMetrics_GetHardCodedMetricsByNamespace(t *testing.T) { |
||||
t.Run("Should return an error in case namespace doesnt exist in map", func(t *testing.T) { |
||||
resp, err := GetHardCodedMetricsByNamespace("unknownNamespace") |
||||
require.Error(t, err) |
||||
assert.Nil(t, resp) |
||||
assert.Equal(t, err.Error(), "unable to find metrics for namespace '\"unknownNamespace\"'") |
||||
}) |
||||
|
||||
t.Run("Should return metrics if namespace exist", func(t *testing.T) { |
||||
resp, err := GetHardCodedMetricsByNamespace("AWS/IoTAnalytics") |
||||
require.NoError(t, err) |
||||
assert.Equal(t, []models.Metric{{Name: "ActionExecution", Namespace: "AWS/IoTAnalytics"}, {Name: "ActivityExecutionError", Namespace: "AWS/IoTAnalytics"}, {Name: "IncomingMessages", Namespace: "AWS/IoTAnalytics"}}, resp) |
||||
}) |
||||
} |
Loading…
Reference in new issue