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,55 @@
|
||||
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/services"
|
||||
)
|
||||
|
||||
func DimensionKeysHandler(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, parameters url.Values) ([]byte, *models.HttpError) {
|
||||
dimensionKeysRequest, err := models.GetDimensionKeysRequest(parameters)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionKeyHandler", http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
service, err := newListMetricsService(pluginCtx, clientFactory, dimensionKeysRequest.Region)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionKeyHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
dimensionKeys := []string{}
|
||||
switch dimensionKeysRequest.Type() {
|
||||
case models.StandardDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetHardCodedDimensionKeysByNamespace(dimensionKeysRequest.Namespace)
|
||||
case models.FilterDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetDimensionKeysByDimensionFilter(dimensionKeysRequest)
|
||||
case models.CustomMetricDimensionKeysRequest:
|
||||
dimensionKeys, err = service.GetDimensionKeysByNamespace(dimensionKeysRequest.Namespace)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionKeyHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
dimensionKeysResponse, err := json.Marshal(dimensionKeys)
|
||||
if err != nil {
|
||||
return nil, models.NewHttpError("error in DimensionKeyHandler", http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return dimensionKeysResponse, nil
|
||||
}
|
||||
|
||||
// newListMetricsService is an list metrics service factory.
|
||||
//
|
||||
// Stubbable by tests.
|
||||
var newListMetricsService = func(pluginCtx backend.PluginContext, clientFactory models.ClientsFactoryFunc, region string) (models.ListMetricsProvider, error) {
|
||||
metricClient, err := clientFactory(pluginCtx, region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return services.NewListMetricsService(metricClient.MetricsClientProvider), nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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_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
|
||||
requestType string
|
||||
}{
|
||||
{
|
||||
url: "/dimension-keys?region=us-east-2&namespace=AWS/EC2&metricName=CPUUtilization",
|
||||
methodName: "GetHardCodedDimensionKeysByNamespace",
|
||||
requestType: "StandardDimensionKeysRequest"},
|
||||
{
|
||||
url: `/dimension-keys?region=us-east-2&namespace=AWS/EC2&metricName=CPUUtilization&dimensionFilters={"NodeID":["Shared"],"stage":["QueryCommit"]}`,
|
||||
methodName: "GetDimensionKeysByDimensionFilter",
|
||||
requestType: "FilterDimensionKeysRequest"},
|
||||
{
|
||||
url: `/dimension-keys?region=us-east-2&namespace=customNamespace&metricName=CPUUtilization`,
|
||||
methodName: "GetDimensionKeysByNamespace",
|
||||
requestType: "CustomMetricDimensionKeysRequest"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("calls %s when a StandardDimensionKeysRequest is passed", tc.requestType), func(t *testing.T) {
|
||||
mockListMetricsService := mocks.ListMetricsServiceMock{}
|
||||
mockListMetricsService.On(tc.methodName).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", tc.url, nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionKeysHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
mockListMetricsService.AssertNumberOfCalls(t, tc.methodName, 1)
|
||||
})
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("return 500 if %s returns an error", tc.requestType), func(t *testing.T) {
|
||||
mockListMetricsService := mocks.ListMetricsServiceMock{}
|
||||
mockListMetricsService.On(tc.methodName).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", tc.url, nil)
|
||||
handler := http.HandlerFunc(ResourceRequestMiddleware(DimensionKeysHandler, nil))
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, http.StatusInternalServerError, rr.Code)
|
||||
assert.Equal(t, `{"Message":"error in DimensionKeyHandler: some error","Error":"some error","StatusCode":500}`, rr.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
)
|
||||
|
||||
func respondWithError(rw http.ResponseWriter, httpError *models.HttpError) {
|
||||
response, err := json.Marshal(httpError)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
rw.WriteHeader(httpError.StatusCode)
|
||||
_, err = rw.Write(response)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/cwlog"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/models"
|
||||
)
|
||||
|
||||
func ResourceRequestMiddleware(handleFunc models.RouteHandlerFunc, clientFactory models.ClientsFactoryFunc) func(rw http.ResponseWriter, req *http.Request) {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" {
|
||||
respondWithError(rw, models.NewHttpError("Invalid method", http.StatusMethodNotAllowed, nil))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
pluginContext := httpadapter.PluginConfigFromContext(ctx)
|
||||
json, httpError := handleFunc(pluginContext, clientFactory, req.URL.Query())
|
||||
if httpError != nil {
|
||||
cwlog.Error("error handling resource request", "error", httpError.Message)
|
||||
respondWithError(rw, httpError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
_, err := rw.Write(json)
|
||||
if err != nil {
|
||||
cwlog.Error("error handling resource request", "error", err)
|
||||
respondWithError(rw, models.NewHttpError("error writing response in resource request middleware", http.StatusInternalServerError, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user