Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remote graphite trigger validation #1127

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
27 changes: 21 additions & 6 deletions api/handler/triggers.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,15 @@ func getTriggerFromRequest(request *http.Request) (*dto.Trigger, *api.ErrorRespo
return nil, api.ErrorInvalidRequest(fmt.Errorf("invalid expression: %s", err.Error()))
case api.ErrInvalidRequestContent:
return nil, api.ErrorInvalidRequest(err)
case remote.ErrRemoteTriggerResponse:
case remote.ErrRemoteUnavailable:
response := api.ErrorRemoteServerUnavailable(err)
middleware.GetLoggerEntry(request).Error().
String("status", response.StatusText).
Error(err).
Msg("Remote server unavailable")
return nil, response
case remote.ErrRemoteTriggerResponse:
return nil, api.ErrorInvalidRequest(fmt.Errorf("error from graphite remote: %w", err))
case *json.UnmarshalTypeError:
return nil, api.ErrorInvalidRequest(fmt.Errorf("invalid payload: %s", err.Error()))
case *prometheus.Error:
Expand Down Expand Up @@ -232,10 +234,11 @@ func getMetricTTLByTrigger(request *http.Request, trigger *dto.Trigger) (time.Du
// @tags trigger
// @accept json
// @produce json
// @param trigger body dto.Trigger true "Trigger data"
// @success 200 {object} dto.TriggerCheckResponse "Validation is done, see response body for validation result"
// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client"
// @failure 500 {object} api.ErrorInternalServerExample "Internal server error"
// @param trigger body dto.Trigger true "Trigger data"
// @success 200 {object} dto.TriggerCheckResponse "Validation is done, see response body for validation result"
// @failure 400 {object} api.ErrorInvalidRequestExample "Bad request from client"
// @failure 500 {object} api.ErrorInternalServerExample "Internal server error"
// @failure 503 {object} api.ErrorRemoteServerUnavailableExample "Remote server unavailable"
// @router /trigger/check [put]
func triggerCheck(writer http.ResponseWriter, request *http.Request) {
trigger := &dto.Trigger{}
Expand All @@ -246,10 +249,22 @@ func triggerCheck(writer http.ResponseWriter, request *http.Request) {
case expression.ErrInvalidExpression, local.ErrParseExpr, local.ErrEvalExpr, local.ErrUnknownFunction:
// TODO: move ErrInvalidExpression to separate case

// These errors are skipped because if there are error from local source then it will be caught in
// Errors above are skipped because if there are error from local source then it will be caught in
AleksandrMatsko marked this conversation as resolved.
Show resolved Hide resolved
// dto.TargetVerification and will be explained in detail.
case remote.ErrRemoteUnavailable:
errRsp := api.ErrorRemoteServerUnavailable(err)
middleware.GetLoggerEntry(request).Error().
String("status", errRsp.StatusText).
Error(err).
Msg("Remote server unavailable")
render.Render(writer, request, errRsp) //nolint
return
case remote.ErrRemoteTriggerResponse:
render.Render(writer, request, api.ErrorInvalidRequest(fmt.Errorf("error from graphite remote: %w", err))) //nolint
return
case *prometheus.Error:
render.Render(writer, request, errorResponseOnPrometheusError(typedErr)) //nolint
return
default:
render.Render(writer, request, api.ErrorInvalidRequest(err)) //nolint
return
Expand Down
67 changes: 46 additions & 21 deletions api/handler/triggers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ func TestGetTriggerFromRequest(t *testing.T) {
fetchResult.EXPECT().GetPatterns().Return(make([]string, 0), nil).AnyTimes()
fetchResult.EXPECT().GetMetricsData().Return([]metricSource.MetricData{*metricSource.MakeMetricData("", []float64{}, 0, 0)}).AnyTimes()

setValuesToRequestCtx := func(
ctx context.Context,
metricSourceProvider *metricSource.SourceProvider,
limits api.LimitsConfig,
) context.Context {
ctx = middleware.SetContextValueForTest(ctx, "metricSourceProvider", metricSourceProvider)
ctx = middleware.SetContextValueForTest(ctx, "limits", limits)

return ctx
}

Convey("Given a correct payload", t, func() {
triggerWarnValue := 0.0
triggerErrorValue := 1.0
Expand Down Expand Up @@ -106,8 +117,7 @@ func TestGetTriggerFromRequest(t *testing.T) {

request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "metricSourceProvider", sourceProvider))
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", api.GetTestLimitsConfig()))
request = request.WithContext(setValuesToRequestCtx(request.Context(), sourceProvider, api.GetTestLimitsConfig()))

triggerDTO.Schedule.Days = moira.GetFilledScheduleDataDays(false)
triggerDTO.Schedule.Days[0].Enabled = true
Expand Down Expand Up @@ -148,8 +158,7 @@ func TestGetTriggerFromRequest(t *testing.T) {

request := httptest.NewRequest(http.MethodPut, "/trigger", strings.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "metricSourceProvider", sourceProvider))
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", api.GetTestLimitsConfig()))
request = request.WithContext(setValuesToRequestCtx(request.Context(), sourceProvider, api.GetTestLimitsConfig()))

Convey("Parser should return en error", func() {
_, err := getTriggerFromRequest(request)
Expand Down Expand Up @@ -198,25 +207,43 @@ func TestGetTriggerFromRequest(t *testing.T) {
Convey("for graphite remote", func() {
triggerDTO.TriggerSource = moira.GraphiteRemote
body, _ := json.Marshal(triggerDTO)
testLogger, _ := logging.GetLogger("Test")

request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "metricSourceProvider", allSourceProvider))
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", api.GetTestLimitsConfig()))
Convey("when ErrRemoteTriggerResponse returned", func() {
request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(setValuesToRequestCtx(request.Context(), allSourceProvider, api.GetTestLimitsConfig()))

testLogger, _ := logging.GetLogger("Test")
request = middleware.WithLogEntry(request, middleware.NewLogEntry(testLogger, request))

request = middleware.WithLogEntry(request, middleware.NewLogEntry(testLogger, request))
var returnedErr error = remote.ErrRemoteTriggerResponse{
InternalError: fmt.Errorf(""),
}

var returnedErr error = remote.ErrRemoteTriggerResponse{
InternalError: fmt.Errorf(""),
}
graphiteRemoteSrc.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil, returnedErr)

graphiteRemoteSrc.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil, returnedErr)
_, errRsp := getTriggerFromRequest(request)
So(errRsp, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("error from graphite remote: %w", returnedErr)))
})

Convey("when ErrRemoteUnavailable", func() {
request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(setValuesToRequestCtx(request.Context(), allSourceProvider, api.GetTestLimitsConfig()))

request = middleware.WithLogEntry(request, middleware.NewLogEntry(testLogger, request))

_, errRsp := getTriggerFromRequest(request)
So(errRsp, ShouldResemble, api.ErrorRemoteServerUnavailable(returnedErr))
var returnedErr error = remote.ErrRemoteUnavailable{
InternalError: fmt.Errorf(""),
}

graphiteRemoteSrc.EXPECT().Fetch(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Return(nil, returnedErr)

_, errRsp := getTriggerFromRequest(request)
So(errRsp, ShouldResemble, api.ErrorRemoteServerUnavailable(returnedErr))
})
})

Convey("for prometheus remote", func() {
Expand All @@ -226,8 +253,7 @@ func TestGetTriggerFromRequest(t *testing.T) {
Convey("with error type = bad_data got bad request", func() {
request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "metricSourceProvider", allSourceProvider))
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", api.GetTestLimitsConfig()))
request = request.WithContext(setValuesToRequestCtx(request.Context(), allSourceProvider, api.GetTestLimitsConfig()))

var returnedErr error = &prometheus.Error{
Type: prometheus.ErrBadData,
Expand All @@ -253,8 +279,7 @@ func TestGetTriggerFromRequest(t *testing.T) {
for _, errType := range otherTypes {
request := httptest.NewRequest(http.MethodPut, "/trigger", bytes.NewReader(body))
request.Header.Add("content-type", "application/json")
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "metricSourceProvider", allSourceProvider))
request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "limits", api.GetTestLimitsConfig()))
request = request.WithContext(setValuesToRequestCtx(request.Context(), allSourceProvider, api.GetTestLimitsConfig()))

var returnedErr error = &prometheus.Error{
Type: errType,
Expand Down
Loading