Skip to content

Commit

Permalink
Add error source (#1954)
Browse files Browse the repository at this point in the history
Fixes  #1879
  • Loading branch information
zoltanbedi authored Feb 4, 2025
1 parent 525217d commit c2ffd31
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 21 deletions.
3 changes: 1 addition & 2 deletions pkg/datasource/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
)

var (
ErrFunctionsNotSupported = errors.New("zabbix queries with functions are not supported")
ErrNonMetricQueryNotSupported = errors.New("non-metrics queries are not supported")
)

Expand Down Expand Up @@ -134,7 +133,7 @@ func (ds *ZabbixDatasource) QueryData(ctx context.Context, req *backend.QueryDat
res.Frames = append(res.Frames, frames...)
}
} else {
res.Error = ErrNonMetricQueryNotSupported
res.Error = backend.DownstreamError(ErrNonMetricQueryNotSupported)
}
qdr.Responses[q.RefID] = res
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/datasource/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/alexanderzobnin/grafana-zabbix/pkg/gtime"
"github.com/alexanderzobnin/grafana-zabbix/pkg/timeseries"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)

const RANGE_VARIABLE_VALUE = "range_series"
Expand Down Expand Up @@ -103,26 +104,26 @@ func applyFunctions(series []*timeseries.TimeSeriesData, functions []QueryFuncti
for _, s := range series {
result, err := applyFunc(s.TS, f.Params...)
if err != nil {
return nil, err
return nil, backend.DownstreamError(err)
}
s.TS = result
}
} else if applyAggFunc, ok := aggFuncMap[f.Def.Name]; ok {
result, err := applyAggFunc(series, f.Params...)
if err != nil {
return nil, err
return nil, backend.DownstreamError(err)
}
series = result
} else if applyFilterFunc, ok := filterFuncMap[f.Def.Name]; ok {
result, err := applyFilterFunc(series, f.Params...)
if err != nil {
return nil, err
return nil, backend.DownstreamError(err)
}
series = result
} else if _, ok := skippedFuncMap[f.Def.Name]; ok {
continue
} else {
err := errFunctionNotSupported(f.Def.Name)
err := backend.DownstreamError(errFunctionNotSupported(f.Def.Name))
return series, err
}
}
Expand All @@ -135,7 +136,7 @@ func applyFunctionsPre(query *QueryModel, items []*zabbix.Item) error {
if applyFunc, ok := timeFuncMap[f.Def.Name]; ok {
err := applyFunc(query, items, f.Params...)
if err != nil {
return err
return backend.DownstreamError(err)
}
}
}
Expand Down
128 changes: 128 additions & 0 deletions pkg/datasource/functions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package datasource

import (
"testing"
"time"

"github.com/alexanderzobnin/grafana-zabbix/pkg/timeseries"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"
)

func TestApplyFunctionsFunction(t *testing.T) {
f := new(float64)
*f = 1.0
series := []*timeseries.TimeSeriesData{
{
TS: timeseries.TimeSeries{
{Time: time.Time{}, Value: f},
{Time: time.Time{}, Value: f},
},
},
}

tests := []struct {
name string
functions []QueryFunction
wantErr bool
}{
{
name: "unsupported function",
functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "unsupportedFunction",
},
Params: []interface{}{},
},
},
wantErr: true,
},
{
name: "data processing function with params error",
functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "groupBy",
},
Params: []interface {
}{1},
},
},
wantErr: true,
},
{
name: "aggregate function with params error",
functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "aggregateBy",
},
Params: []interface {
}{1},
},
},
wantErr: true,
},
{
name: "filter function with params error",
functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "top",
},
Params: []interface {
}{"string"},
},
},
wantErr: true,
},
{
name: "skipped function should return no error",
functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "setAlias",
},
Params: []interface {
}{},
},
},
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := applyFunctions(series, tt.functions)
if tt.wantErr {
assert.Error(t, err, "expected error for function")
// Check if the error is a downstream error
assert.Truef(t, backend.IsDownstreamError(err), "error is not a downstream error")
} else {
assert.NoError(t, err)
}
})
}
}
// TestApplyFunctionsPreFunction tests the applyFunctionsPre function for error handling
func TestApplyFunctionsPreFunction(t *testing.T) {
query := QueryModel{
Functions: []QueryFunction{
{
Def: QueryFunctionDef{
Name: "timeShift",
},
Params: []interface{}{1},
},
}}

items := []*zabbix.Item{}
err := applyFunctionsPre(&query, items)

assert.Error(t, err, "expected error for function")
// Check if the error is a downstream error
assert.Truef(t, backend.IsDownstreamError(err), "error is not a downstream error")

}
9 changes: 5 additions & 4 deletions pkg/datasource/response_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/alexanderzobnin/grafana-zabbix/pkg/timeseries"
"github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
)

Expand Down Expand Up @@ -154,19 +155,19 @@ func getTrendPointValue(point zabbix.TrendPoint, valueType string) (float64, err

value, err := strconv.ParseFloat(valueStr, 64)
if err != nil {
return 0, fmt.Errorf("error parsing trend value: %s", err)
return 0, backend.DownstreamError(fmt.Errorf("error parsing trend value: %s", err))
}
return value, nil
} else if valueType == "sum" {
avgStr := point.ValueAvg
avg, err := strconv.ParseFloat(avgStr, 64)
if err != nil {
return 0, fmt.Errorf("error parsing trend value: %s", err)
return 0, backend.DownstreamError(fmt.Errorf("error parsing trend value: %s", err))
}
countStr := point.Num
count, err := strconv.ParseFloat(countStr, 64)
if err != nil {
return 0, fmt.Errorf("error parsing trend value: %s", err)
return 0, backend.DownstreamError(fmt.Errorf("error parsing trend value: %s", err))
}
if count > 0 {
return avg * count, nil
Expand All @@ -175,7 +176,7 @@ func getTrendPointValue(point zabbix.TrendPoint, valueType string) (float64, err
}
}

return 0, fmt.Errorf("failed to get trend value, unknown value type: %s", valueType)
return 0, backend.DownstreamError(fmt.Errorf("failed to get trend value, unknown value type: %s", valueType))
}

var fixedUpdateIntervalPattern = regexp.MustCompile(`^(\d+)([smhdw]?)$`)
Expand Down
2 changes: 2 additions & 0 deletions pkg/zabbix/type_converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"

"github.com/bitly/go-simplejson"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)

func convertTo(value *simplejson.Json, result interface{}) error {
Expand All @@ -14,6 +15,7 @@ func convertTo(value *simplejson.Json, result interface{}) error {

err = json.Unmarshal(valueJSON, result)
if err != nil {
backend.Logger.Debug("Error unmarshalling JSON", "error", err, "result", result)
return err
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/zabbix/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/dlclark/regexp2"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)

func (item *Item) ExpandItemName() string {
Expand Down Expand Up @@ -79,7 +80,7 @@ func parseFilter(filter string) (*regexp2.Regexp, error) {
if flagRE.MatchString(matches[2]) {
pattern += "(?" + matches[2] + ")"
} else {
return nil, fmt.Errorf("error parsing regexp: unsupported flags `%s` (expected [%s])", matches[2], vaildREModifiers)
return nil, backend.DownstreamError(fmt.Errorf("error parsing regexp: unsupported flags `%s` (expected [%s])", matches[2], vaildREModifiers))
}
}
pattern += matches[1]
Expand Down
4 changes: 2 additions & 2 deletions pkg/zabbix/zabbix.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (zabbix *Zabbix) request(ctx context.Context, method string, params ZabbixA
result, err := zabbix.api.Request(ctx, method, params, zabbix.version)
notAuthorized := isNotAuthorized(err)
isTokenAuth := zabbix.settings.AuthType == settings.AuthTypeToken
if err == zabbixapi.ErrNotAuthenticated || (notAuthorized && !isTokenAuth) {
if err == backend.DownstreamError(zabbixapi.ErrNotAuthenticated) || (notAuthorized && !isTokenAuth) {
if notAuthorized {
zabbix.logger.Debug("Authentication token expired, performing re-login")
}
Expand All @@ -121,7 +121,7 @@ func (zabbix *Zabbix) Authenticate(ctx context.Context) error {
if authType == settings.AuthTypeToken {
token, exists := zabbix.dsInfo.DecryptedSecureJSONData["apiToken"]
if !exists {
return errors.New("cannot find Zabbix API token")
return backend.DownstreamError(errors.New("cannot find Zabbix API token"))
}
err = zabbix.api.AuthenticateWithToken(ctx, token)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion pkg/zabbix/zabbix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package zabbix

import (
"context"
"github.com/alexanderzobnin/grafana-zabbix/pkg/settings"
"testing"

"github.com/alexanderzobnin/grafana-zabbix/pkg/settings"

"github.com/stretchr/testify/assert"

"github.com/grafana/grafana-plugin-sdk-go/backend"
Expand Down
19 changes: 14 additions & 5 deletions pkg/zabbixapi/zabbix_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/bitly/go-simplejson"
"golang.org/x/net/context/ctxhttp"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

Expand Down Expand Up @@ -76,7 +77,7 @@ func (api *ZabbixAPI) SetAuth(auth string) {
// Request performs API request
func (api *ZabbixAPI) Request(ctx context.Context, method string, params ZabbixAPIParams, version int) (*simplejson.Json, error) {
if api.auth == "" {
return nil, ErrNotAuthenticated
return nil, backend.DownstreamError(ErrNotAuthenticated)
}

return api.request(ctx, method, params, api.auth, version)
Expand Down Expand Up @@ -177,7 +178,7 @@ func (api *ZabbixAPI) Authenticate(ctx context.Context, username string, passwor
// AuthenticateWithToken performs authentication with API token.
func (api *ZabbixAPI) AuthenticateWithToken(ctx context.Context, token string) error {
if token == "" {
return errors.New("API token is empty")
return backend.DownstreamError(errors.New("API token is empty"))
}
api.SetAuth(token)
return nil
Expand All @@ -198,8 +199,8 @@ func handleAPIResult(response []byte) (*simplejson.Json, error) {
return nil, err
}
if errJSON, isError := jsonResp.CheckGet("error"); isError {
errMessage := fmt.Sprintf("%s %s", errJSON.Get("message").MustString(), errJSON.Get("data").MustString())
return nil, errors.New(errMessage)
errMessage := fmt.Errorf("%s %s", errJSON.Get("message").MustString(), errJSON.Get("data").MustString())
return nil, backend.DownstreamError(errMessage)
}
jsonResult := jsonResp.Get("result")
return jsonResult, nil
Expand All @@ -211,12 +212,20 @@ func makeHTTPRequest(ctx context.Context, httpClient *http.Client, req *http.Req

res, err := ctxhttp.Do(ctx, httpClient, req)
if err != nil {
if backend.IsDownstreamHTTPError(err) {
return nil, backend.DownstreamError(err)
}
return nil, err
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("request failed, status: %v", res.Status)
err = fmt.Errorf("request failed, status: %v", res.Status)
if backend.ErrorSourceFromHTTPStatus(res.StatusCode) == backend.ErrorSourceDownstream {
return nil, backend.DownstreamError(err)
}

return nil, err
}

body, err := io.ReadAll(res.Body)
Expand Down
3 changes: 2 additions & 1 deletion pkg/zabbixapi/zabbix_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -50,7 +51,7 @@ func TestZabbixAPI(t *testing.T) {
mockApiResponse: `{"result":"sampleResult"}`,
mockApiResponseCode: 200,
expectedResult: "",
expectedError: ErrNotAuthenticated,
expectedError: backend.DownstreamError(ErrNotAuthenticated),
},
}

Expand Down

0 comments on commit c2ffd31

Please sign in to comment.