diff --git a/go.mod b/go.mod index 143af98..7c69f80 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/google/go-cmp v0.3.1 // indirect github.com/grafana/grafana-plugin-sdk-go v0.65.0 - github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d - github.com/hashicorp/go-hclog v0.9.2 + github.com/grafana/grafana_plugin_model v0.0.0-20180518082423-84176c64269d // indirect + github.com/hashicorp/go-hclog v0.9.2 // indirect github.com/hashicorp/go-plugin v1.2.2 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/pkg/datasource.go b/pkg/datasource/datasource.go similarity index 96% rename from pkg/datasource.go rename to pkg/datasource/datasource.go index 985a730..c9ad963 100644 --- a/pkg/datasource.go +++ b/pkg/datasource/datasource.go @@ -1,4 +1,4 @@ -package main +package datasource import ( "context" @@ -31,6 +31,13 @@ type ZabbixDatasourceInstance struct { logger log.Logger } +func NewZabbixDatasource() *ZabbixDatasource { + return &ZabbixDatasource{ + datasourceCache: cache.NewCache(10*time.Minute, 10*time.Minute), + logger: log.New(), + } +} + // NewZabbixDatasourceInstance returns an initialized zabbix datasource instance func NewZabbixDatasourceInstance(dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) { zabbixAPI, err := zabbixapi.New(dsInfo.URL) diff --git a/pkg/datasource_test.go b/pkg/datasource/datasource_test.go similarity index 99% rename from pkg/datasource_test.go rename to pkg/datasource/datasource_test.go index 12f7b1b..48d4e3b 100644 --- a/pkg/datasource_test.go +++ b/pkg/datasource/datasource_test.go @@ -1,4 +1,4 @@ -package main +package datasource import ( "fmt" diff --git a/pkg/models.go b/pkg/datasource/models.go similarity index 99% rename from pkg/models.go rename to pkg/datasource/models.go index 16471d0..03813d9 100644 --- a/pkg/models.go +++ b/pkg/datasource/models.go @@ -1,4 +1,4 @@ -package main +package datasource import ( "encoding/json" @@ -39,17 +39,17 @@ type ZabbixAPIRequest struct { Params ZabbixAPIParams `json:"params,omitempty"` } +func (r *ZabbixAPIRequest) String() string { + jsonRequest, _ := json.Marshal(r.Params) + return r.Method + string(jsonRequest) +} + type ZabbixAPIParams = map[string]interface{} type ZabbixAPIResourceResponse struct { Result interface{} `json:"result,omitempty"` } -func (r *ZabbixAPIRequest) String() string { - jsonRequest, _ := json.Marshal(r.Params) - return r.Method + string(jsonRequest) -} - // QueryModel model type QueryModel struct { Mode int64 `json:"mode"` diff --git a/pkg/resource_handler.go b/pkg/datasource/resource_handler.go similarity index 74% rename from pkg/resource_handler.go rename to pkg/datasource/resource_handler.go index a4d4512..3f35536 100644 --- a/pkg/resource_handler.go +++ b/pkg/datasource/resource_handler.go @@ -1,4 +1,4 @@ -package main +package datasource import ( "encoding/json" @@ -8,14 +8,14 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" ) -func (ds *ZabbixDatasource) rootHandler(rw http.ResponseWriter, req *http.Request) { +func (ds *ZabbixDatasource) RootHandler(rw http.ResponseWriter, req *http.Request) { ds.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method) rw.Write([]byte("Hello from Zabbix data source!")) rw.WriteHeader(http.StatusOK) } -func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.Request) { +func (ds *ZabbixDatasource) ZabbixAPIHandler(rw http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { return } @@ -23,7 +23,7 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R body, err := ioutil.ReadAll(req.Body) defer req.Body.Close() if err != nil || len(body) == 0 { - WriteError(rw, http.StatusBadRequest, err) + writeError(rw, http.StatusBadRequest, err) return } @@ -31,7 +31,7 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R err = json.Unmarshal(body, &reqData) if err != nil { ds.logger.Error("Cannot unmarshal request", "error", err.Error()) - WriteError(rw, http.StatusInternalServerError, err) + writeError(rw, http.StatusInternalServerError, err) return } @@ -39,7 +39,7 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R dsInstance, err := ds.GetDatasource(pluginCxt) if err != nil { ds.logger.Error("Error loading datasource", "error", err) - WriteError(rw, http.StatusInternalServerError, err) + writeError(rw, http.StatusInternalServerError, err) return } @@ -48,17 +48,17 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R result, err := dsInstance.ZabbixAPIQuery(req.Context(), apiReq) if err != nil { ds.logger.Error("Zabbix API request error", "error", err) - WriteError(rw, http.StatusInternalServerError, err) + writeError(rw, http.StatusInternalServerError, err) return } - WriteResponse(rw, result) + writeResponse(rw, result) } -func WriteResponse(rw http.ResponseWriter, result *ZabbixAPIResourceResponse) { +func writeResponse(rw http.ResponseWriter, result *ZabbixAPIResourceResponse) { resultJson, err := json.Marshal(*result) if err != nil { - WriteError(rw, http.StatusInternalServerError, err) + writeError(rw, http.StatusInternalServerError, err) } rw.Header().Add("Content-Type", "application/json") @@ -66,7 +66,7 @@ func WriteResponse(rw http.ResponseWriter, result *ZabbixAPIResourceResponse) { rw.Write(resultJson) } -func WriteError(rw http.ResponseWriter, statusCode int, err error) { +func writeError(rw http.ResponseWriter, statusCode int, err error) { data := make(map[string]interface{}) data["error"] = "Internal Server Error" diff --git a/pkg/zabbix/response_models.go b/pkg/datasource/response_models.go similarity index 99% rename from pkg/zabbix/response_models.go rename to pkg/datasource/response_models.go index a39bc03..0a761b1 100644 --- a/pkg/zabbix/response_models.go +++ b/pkg/datasource/response_models.go @@ -1,4 +1,4 @@ -package zabbix +package datasource import ( "fmt" diff --git a/pkg/zabbix.go b/pkg/datasource/zabbix.go similarity index 96% rename from pkg/zabbix.go rename to pkg/datasource/zabbix.go index b719fbf..622e2f0 100644 --- a/pkg/zabbix.go +++ b/pkg/datasource/zabbix.go @@ -1,4 +1,4 @@ -package main +package datasource import ( "encoding/json" @@ -7,7 +7,6 @@ import ( "time" "github.com/alexanderzobnin/grafana-zabbix/pkg/cache" - "github.com/alexanderzobnin/grafana-zabbix/pkg/zabbix" "github.com/alexanderzobnin/grafana-zabbix/pkg/zabbixapi" simplejson "github.com/bitly/go-simplejson" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -156,7 +155,7 @@ func (ds *ZabbixDatasourceInstance) queryNumericItems(ctx context.Context, query return frames, nil } -func (ds *ZabbixDatasourceInstance) getItems(ctx context.Context, groupFilter string, hostFilter string, appFilter string, itemFilter string, itemType string) (zabbix.Items, error) { +func (ds *ZabbixDatasourceInstance) getItems(ctx context.Context, groupFilter string, hostFilter string, appFilter string, itemFilter string, itemType string) (Items, error) { hosts, err := ds.getHosts(ctx, groupFilter, hostFilter) if err != nil { return nil, err @@ -182,10 +181,10 @@ func (ds *ZabbixDatasourceInstance) getItems(ctx context.Context, groupFilter st allItems, err = ds.getAllItems(ctx, nil, appids, itemType) } - var items zabbix.Items + var items Items if allItems == nil { - items = zabbix.Items{} + items = Items{} } else { itemsJSON, err := allItems.MarshalJSON() if err != nil { @@ -203,7 +202,7 @@ func (ds *ZabbixDatasourceInstance) getItems(ctx context.Context, groupFilter st return nil, err } - filteredItems := zabbix.Items{} + filteredItems := Items{} for _, item := range items { itemName := item.ExpandItem() if item.Status == "0" { @@ -361,7 +360,7 @@ func (ds *ZabbixDatasourceInstance) getAllGroups(ctx context.Context) (*simplejs return ds.ZabbixQuery(ctx, &ZabbixAPIRequest{Method: "hostgroup.get", Params: params}) } -func (ds *ZabbixDatasourceInstance) queryNumericDataForItems(ctx context.Context, query *QueryModel, items zabbix.Items) (*data.Frame, error) { +func (ds *ZabbixDatasourceInstance) queryNumericDataForItems(ctx context.Context, query *QueryModel, items Items) (*data.Frame, error) { valueType := ds.getTrendValueType(query) consolidateBy := ds.getConsolidateBy(query) @@ -400,12 +399,12 @@ func (ds *ZabbixDatasourceInstance) getConsolidateBy(query *QueryModel) string { return consolidateBy } -func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, query *QueryModel, items zabbix.Items) (zabbix.History, error) { +func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, query *QueryModel, items Items) (History, error) { timeRange := query.TimeRange useTrend := ds.isUseTrend(timeRange) - allHistory := zabbix.History{} + allHistory := History{} - groupedItems := map[int]zabbix.Items{} + groupedItems := map[int]Items{} for _, j := range items { groupedItems[j.ValueType] = append(groupedItems[j.ValueType], j) @@ -444,7 +443,7 @@ func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, query return nil, fmt.Errorf("Internal error parsing response JSON: %w", err) } - history := zabbix.History{} + history := History{} err = json.Unmarshal(pointJSON, &history) if err != nil { ds.logger.Warn(fmt.Sprintf("Could not map Zabbix response to History: %s", err.Error())) @@ -472,7 +471,7 @@ func (ds *ZabbixDatasourceInstance) isUseTrend(timeRange backend.TimeRange) bool return false } -func convertHistory(history zabbix.History, items zabbix.Items) *data.Frame { +func convertHistory(history History, items Items) *data.Frame { timeFileld := data.NewFieldFromFieldType(data.FieldTypeTime, 0) timeFileld.Name = "time" frame := data.NewFrame("History", timeFileld) diff --git a/pkg/datasource/zabbix_test.go b/pkg/datasource/zabbix_test.go new file mode 100644 index 0000000..e5b6ba7 --- /dev/null +++ b/pkg/datasource/zabbix_test.go @@ -0,0 +1,134 @@ +package datasource + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/alexanderzobnin/grafana-zabbix/pkg/cache" + "github.com/alexanderzobnin/grafana-zabbix/pkg/zabbixapi" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/stretchr/testify/assert" +) + +var emptyParams = map[string]interface{}{} + +type RoundTripFunc func(req *http.Request) *http.Response + +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +//NewTestClient returns *http.Client with Transport replaced to avoid making real calls +func NewTestClient(fn RoundTripFunc) *http.Client { + return &http.Client{ + Transport: RoundTripFunc(fn), + } +} + +var basicDatasourceInfo = &backend.DataSourceInstanceSettings{ + ID: 1, + Name: "TestDatasource", + URL: "http://zabbix.org/zabbix", + JSONData: []byte(`{"username":"username", "password":"password"}}`), +} + +func mockZabbixQuery(method string, params ZabbixAPIParams) *ZabbixAPIRequest { + return &ZabbixAPIRequest{ + Method: method, + Params: params, + } +} + +func MockZabbixDataSource(body string, statusCode int) *ZabbixDatasourceInstance { + zabbixAPI, _ := zabbixapi.MockZabbixAPI(body, statusCode) + zabbixSettings, _ := readZabbixSettings(basicDatasourceInfo) + + return &ZabbixDatasourceInstance{ + dsInfo: basicDatasourceInfo, + zabbixAPI: zabbixAPI, + Settings: zabbixSettings, + queryCache: cache.NewCache(cache.NoExpiration, 10*time.Minute), + logger: log.New(), + } +} + +func MockZabbixDataSourceResponse(dsInstance *ZabbixDatasourceInstance, body string, statusCode int) *ZabbixDatasourceInstance { + zabbixAPI, _ := zabbixapi.MockZabbixAPI(body, statusCode) + dsInstance.zabbixAPI = zabbixAPI + + return dsInstance +} + +func TestLogin(t *testing.T) { + dsInstance := MockZabbixDataSource(`{"result":"secretauth"}`, 200) + err := dsInstance.login(context.Background()) + + assert.Nil(t, err) + assert.Equal(t, "secretauth", dsInstance.zabbixAPI.GetAuth()) +} + +func TestLoginError(t *testing.T) { + dsInstance := MockZabbixDataSource(`{"result":""}`, 500) + err := dsInstance.login(context.Background()) + + assert.NotNil(t, err) + assert.Equal(t, "", dsInstance.zabbixAPI.GetAuth()) +} + +func TestZabbixAPIQuery(t *testing.T) { + dsInstance := MockZabbixDataSource(`{"result":"test"}`, 200) + resp, err := dsInstance.ZabbixAPIQuery(context.Background(), mockZabbixQuery("test.get", emptyParams)) + + assert.Nil(t, err) + + result, ok := resp.Result.(string) + assert.True(t, ok) + assert.Equal(t, "test", result) +} + +func TestCachedQuery(t *testing.T) { + // Using methods with caching enabled + query := mockZabbixQuery("host.get", emptyParams) + dsInstance := MockZabbixDataSource(`{"result":"testOld"}`, 200) + + // Run query first time + resp, err := dsInstance.ZabbixAPIQuery(context.Background(), query) + + assert.Nil(t, err) + result, _ := resp.Result.(string) + assert.Equal(t, "testOld", result) + + // Mock request with new value + dsInstance = MockZabbixDataSourceResponse(dsInstance, `{"result":"testNew"}`, 200) + // Should not run actual API query and return first result + resp, err = dsInstance.ZabbixAPIQuery(context.Background(), query) + + assert.Nil(t, err) + result, _ = resp.Result.(string) + assert.Equal(t, "testOld", result) +} + +func TestNonCachedQuery(t *testing.T) { + // Using methods with caching disabled + query := mockZabbixQuery("history.get", emptyParams) + dsInstance := MockZabbixDataSource(`{"result":"testOld"}`, 200) + + // Run query first time + resp, err := dsInstance.ZabbixAPIQuery(context.Background(), query) + + assert.Nil(t, err) + result, _ := resp.Result.(string) + assert.Equal(t, "testOld", result) + + // Mock request with new value + dsInstance = MockZabbixDataSourceResponse(dsInstance, `{"result":"testNew"}`, 200) + // Should not run actual API query and return first result + resp, err = dsInstance.ZabbixAPIQuery(context.Background(), query) + + assert.Nil(t, err) + result, _ = resp.Result.(string) + assert.Equal(t, "testNew", result) +} diff --git a/pkg/plugin.go b/pkg/plugin.go index e74d10c..47e0b5c 100644 --- a/pkg/plugin.go +++ b/pkg/plugin.go @@ -3,9 +3,8 @@ package main import ( "net/http" "os" - "time" - "github.com/alexanderzobnin/grafana-zabbix/pkg/cache" + "github.com/alexanderzobnin/grafana-zabbix/pkg/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" @@ -33,7 +32,7 @@ func main() { } } -func Init(logger log.Logger, mux *http.ServeMux) *ZabbixDatasource { +func Init(logger log.Logger, mux *http.ServeMux) *datasource.ZabbixDatasource { variableName := "GFX_ZABBIX_DATA_PATH" path, exist := os.LookupEnv(variableName) if !exist { @@ -42,13 +41,10 @@ func Init(logger log.Logger, mux *http.ServeMux) *ZabbixDatasource { logger.Debug("environment variable for storage found", "variable", variableName, "value", path) } - ds := &ZabbixDatasource{ - logger: logger, - datasourceCache: cache.NewCache(10*time.Minute, 10*time.Minute), - } + ds := datasource.NewZabbixDatasource() - mux.HandleFunc("/", ds.rootHandler) - mux.HandleFunc("/zabbix-api", ds.zabbixAPIHandler) + mux.HandleFunc("/", ds.RootHandler) + mux.HandleFunc("/zabbix-api", ds.ZabbixAPIHandler) // mux.Handle("/scenarios", getScenariosHandler(logger)) return ds diff --git a/pkg/zabbix_test.go b/pkg/zabbix_test.go deleted file mode 100644 index d4aeef7..0000000 --- a/pkg/zabbix_test.go +++ /dev/null @@ -1,446 +0,0 @@ -package main - -import ( - "bytes" - "io/ioutil" - "net/http" - "net/url" - "regexp" - "testing" - "time" - - simplejson "github.com/bitly/go-simplejson" - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana_plugin_model/go/datasource" - hclog "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/assert" - "golang.org/x/net/context" -) - -type RoundTripFunc func(req *http.Request) *http.Response - -func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -//NewTestClient returns *http.Client with Transport replaced to avoid making real calls -func NewTestClient(fn RoundTripFunc) *http.Client { - return &http.Client{ - Transport: RoundTripFunc(fn), - } -} - -var basicDatasourceInfo = &backend.DataSourceInstanceSettings{ - ID: 1, - Name: "TestDatasource", - URL: "http://zabbix.org/zabbix", - JSONData: []byte(`{"username":"username", "password":"password"}}`), -} - -func mockDataSourceRequest(modelJSON string) *datasource.DatasourceRequest { - return &datasource.DatasourceRequest{ - Datasource: basicDatasourceInfo, - Queries: []*datasource.Query{ - { - ModelJson: modelJSON, - }, - }, - } -} - -func mockZabbixDataSource(body string, statusCode int) ZabbixDatasourceInstance { - apiUrl, _ := url.Parse(basicDatasourceInfo.Url) - return ZabbixDatasourceInstance{ - url: apiUrl, - dsInfo: basicDatasourceInfo, - queryCache: NewCache(10*time.Minute, 10*time.Minute), - httpClient: NewTestClient(func(req *http.Request) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Body: ioutil.NopCloser(bytes.NewBufferString(body)), - Header: make(http.Header), - } - }), - authToken: "sampleAuthToken", - logger: hclog.Default(), - } -} - -func TestZabbixAPIQuery(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.ZabbixAPIQuery(context.Background(), mockDataSourceRequest(`{"target":{"method":"Method","params":{"param1" : "Param1"}}}`)) - - assert.Equal(t, "\"sampleResult\"", resp.GetResults()[0].GetMetaJson()) - assert.Equal(t, "zabbixAPI", resp.GetResults()[0].GetRefId()) - assert.Nil(t, err) -} - -func TestZabbixAPIQueryEmptyQuery(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.ZabbixAPIQuery(context.Background(), mockDataSourceRequest(``)) - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestZabbixAPIQueryNoQueries(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - basicDatasourceRequest := &datasource.DatasourceRequest{ - Datasource: &datasource.DatasourceInfo{ - Id: 1, - Name: "TestDatasource", - }, - } - resp, err := zabbixDatasource.ZabbixAPIQuery(context.Background(), basicDatasourceRequest) - - assert.Nil(t, resp) - assert.Equal(t, "At least one query should be provided", err.Error()) -} - -func TestZabbixAPIQueryError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 500) - resp, err := zabbixDatasource.ZabbixAPIQuery(context.Background(), mockDataSourceRequest(`{"target":{"method":"Method","params":{"param1" : "Param1"}}}`)) - - assert.Nil(t, resp) - assert.Equal(t, "ZabbixAPIQuery is not implemented yet", err.Error()) -} - -func TestLogin(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.login(context.Background(), "username", "password") - - assert.Equal(t, "sampleResult", resp) - assert.Nil(t, err) -} - -func TestLoginError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 500) - resp, err := zabbixDatasource.login(context.Background(), "username", "password") - - assert.Equal(t, "", resp) - assert.NotNil(t, err) -} - -func TestLoginWithDs(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - err := zabbixDatasource.login(context.Background()) - - assert.Equal(t, "sampleResult", zabbixDatasource.authToken) - assert.Nil(t, err) -} - -func TestLoginWithDsError(t *testing.T) { - errResponse := `{"error":{"code":-32500,"message":"Application error.","data":"Login name or password is incorrect."}}` - zabbixDatasource := mockZabbixDataSource(errResponse, 200) - err := zabbixDatasource.login(context.Background()) - - assert.Equal(t, "", zabbixDatasource.authToken) - assert.NotNil(t, err) -} - -func TestZabbixRequest(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.ZabbixRequest(context.Background(), "method", ZabbixAPIParams{}) - assert.Equal(t, "sampleResult", resp.MustString()) - assert.Nil(t, err) -} - -func TestZabbixRequestWithNoAuthToken(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"auth"}`, 200) - resp, err := zabbixDatasource.ZabbixRequest(context.Background(), "method", ZabbixAPIParams{}) - assert.Equal(t, "auth", resp.MustString()) - assert.Nil(t, err) -} - -func TestZabbixRequestError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 500) - resp, err := zabbixDatasource.ZabbixRequest(context.Background(), "method", ZabbixAPIParams{}) - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestZabbixAPIRequest(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.ZabbixAPIRequest(context.Background(), "item.get", ZabbixAPIParams{}, "auth") - - assert.Equal(t, "sampleResult", resp.MustString()) - assert.Nil(t, err) -} - -func TestZabbixAPIRequestError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 500) - resp, err := zabbixDatasource.ZabbixAPIRequest(context.Background(), "item.get", ZabbixAPIParams{}, "auth") - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestTestConnection(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp, err := zabbixDatasource.TestConnection(context.Background(), mockDataSourceRequest(``)) - - assert.Equal(t, "{\"zabbixVersion\":\"sampleResult\",\"dbConnectorStatus\":null}", resp.Results[0].GetMetaJson()) - assert.Nil(t, err) -} - -func TestTestConnectionError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 500) - resp, err := zabbixDatasource.TestConnection(context.Background(), mockDataSourceRequest(``)) - - assert.Equal(t, "", resp.Results[0].GetMetaJson()) - assert.NotNil(t, resp.Results[0].GetError()) - assert.Nil(t, err) -} - -func TestIsNotAuthorized(t *testing.T) { - testPositive := isNotAuthorized("Not authorised.") - assert.True(t, testPositive) - - testNegative := isNotAuthorized("testNegative") - assert.False(t, testNegative) -} - -func TestHandleAPIResult(t *testing.T) { - expectedResponse, err := handleAPIResult([]byte(`{"result":"sampleResult"}`)) - - assert.Equal(t, "sampleResult", expectedResponse.MustString()) - assert.Nil(t, err) -} - -func TestHandleAPIResultFormatError(t *testing.T) { - expectedResponse, err := handleAPIResult([]byte(`{"result"::"sampleResult"}`)) - - assert.NotNil(t, err) - assert.Nil(t, expectedResponse) -} - -func TestHandleAPIResultError(t *testing.T) { - expectedResponse, err := handleAPIResult([]byte(`{"result":"sampleResult", "error":{"message":"Message", "data":"Data"}}`)) - - assert.Equal(t, "Message Data", err.Error()) - assert.Nil(t, expectedResponse) -} - -func TestGetAllGroups(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "name": "name1"},{"groupid": "46489127", "name":"name2"}]}`, 200) - resp, err := zabbixDatasource.getAllGroups(context.Background(), basicDatasourceInfo) - - assert.Equal(t, "46489126", resp.MustArray()[0].(map[string]interface{})["groupid"]) - assert.Equal(t, "46489127", resp.MustArray()[1].(map[string]interface{})["groupid"]) - assert.Nil(t, err) -} - -func TestGetAllHosts(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"hostid": "46489126", "name": "hostname1"},{"hostid": "46489127", "name":"hostname2"}]}`, 200) - resp, err := zabbixDatasource.getAllHosts(context.Background(), basicDatasourceInfo, []string{"46489127", "46489127"}) - - assert.Equal(t, "46489126", resp.MustArray()[0].(map[string]interface{})["hostid"]) - assert.Equal(t, "46489127", resp.MustArray()[1].(map[string]interface{})["hostid"]) - assert.Nil(t, err) -} - -func TestGetAllApps(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"applicationid": "46489126", "name": "hostname1"},{"applicationid": "46489127", "name":"hostname2"}]}`, 200) - resp, err := zabbixDatasource.getAllApps(context.Background(), basicDatasourceInfo, []string{"46489127", "46489127"}) - - assert.Equal(t, "46489126", resp.MustArray()[0].(map[string]interface{})["applicationid"]) - assert.Equal(t, "46489127", resp.MustArray()[1].(map[string]interface{})["applicationid"]) - assert.Nil(t, err) -} - -func TestGetAllItems(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"itemid": "46489126", "name": "hostname1"},{"itemid": "46489127", "name":"hostname2"}]}`, 200) - resp, err := zabbixDatasource.getAllItems(context.Background(), basicDatasourceInfo, []string{"46489127", "46489127"}, []string{"7947934", "9182763"}, "num") - - assert.Equal(t, "46489126", resp.MustArray()[0].(map[string]interface{})["itemid"]) - assert.Equal(t, "46489127", resp.MustArray()[1].(map[string]interface{})["itemid"]) - assert.Nil(t, err) -} - -func TestGetGroups(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "name": "name1"},{"groupid": "46489127", "name":"name2"}]}`, 200) - resp, err := zabbixDatasource.getGroups(context.Background(), basicDatasourceInfo, "name1") - - assert.Equal(t, "46489126", resp[0]["groupid"]) - assert.Equal(t, "name1", resp[0]["name"]) - assert.Nil(t, err) -} - -func TestGetGroupsError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "name": "name1"},{"groupid": "46489127", "name":"name2"}]}`, 500) - resp, err := zabbixDatasource.getGroups(context.Background(), basicDatasourceInfo, "name1") - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestGetHosts(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "name": "hostname1"},{"groupid": "46489127","hostid": "846586", "name":"hostname2"}]}`, 200) - resp, err := zabbixDatasource.getHosts(context.Background(), basicDatasourceInfo, "nam", "hostname1") - - assert.Equal(t, "7468763", resp[0]["hostid"]) - assert.Equal(t, "hostname1", resp[0]["name"]) - assert.Nil(t, err) -} - -func TestGetHostsError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "name": "hostname1"},{"groupid": "46489127","hostid": "846586", "name":"hostname2"}]}`, 500) - resp, err := zabbixDatasource.getHosts(context.Background(), basicDatasourceInfo, "nam", "hostna") - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestGetApps(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "applicationid": "7947934", "name": "appname1"}, - {"groupid": "46489127","hostid": "846586", "applicationid": "9182763", "name": "appname2"}]}`, 200) - resp, err := zabbixDatasource.getApps(context.Background(), basicDatasourceInfo, "nam", "hostnam", "appname1") - - assert.Equal(t, "7947934", resp[0]["applicationid"]) - assert.Equal(t, "appname1", resp[0]["name"]) - assert.Nil(t, err) -} - -func TestGetAppsError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "applicationid": "7947934", "name": "appname1"}, - {"groupid": "46489127","hostid": "846586", "applicationid": "9182763", "name": "appname2"}]}`, 500) - resp, err := zabbixDatasource.getApps(context.Background(), basicDatasourceInfo, "nam", "hostnam", "appname1") - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestGetItems(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "applicationid": "7947934", "itemid": "837465", "name": "itemname1", "status": "0"}, - {"groupid": "46489127","hostid": "846586", "applicationid": "9182763", "itemid" : "0288374", "name": "itemname2", "status": "0"}]}`, 200) - resp, err := zabbixDatasource.getItems(context.Background(), basicDatasourceInfo, "itemname1", "itemname1", "itemname1", "itemname1", "num") - - assert.Equal(t, "837465", resp[0].ID) - assert.Equal(t, "itemname1", resp[0].Name) - assert.Nil(t, err) -} - -func TestGetItemsError(t *testing.T) { - zabbixDatasource := mockZabbixDataSource(`{"result":[{"groupid": "46489126", "hostid": "7468763", "applicationid": "7947934", "itemid": "837465", "name": "itemname1", "status": "0"}, - {"groupid": "46489127","hostid": "846586", "applicationid": "9182763", "itemid" : "0288374", "name": "itemname2", "status": "0"}]}`, 500) - resp, err := zabbixDatasource.getItems(context.Background(), basicDatasourceInfo, "name", "name", "name", "name", "num") - - assert.Nil(t, resp) - assert.NotNil(t, err) -} - -func TestGetTrendValueType(t *testing.T) { - json1, _ := simplejson.NewJson([]byte(`{"functions":[{"def":{"name":"name1"}},{"def":{"name":"name2"}}]}`)) - json2, _ := simplejson.NewJson([]byte(`{"functions":[{"def":{"name":"name1"}},{"def":{"name":"name2"}}]}`)) - jsonQueries := []*simplejson.Json{json1, json2} - - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp := zabbixDatasource.getTrendValueType(jsonQueries) - - assert.Equal(t, "avg", resp) -} - -func TestGetConsolidateBy(t *testing.T) { - json1, _ := simplejson.NewJson([]byte(`{"functions":[{"def":{"name":"consolidateBy", "params":["sum"]}},{"def":{"name":"name2"}}]}`)) - json2, _ := simplejson.NewJson([]byte(`{"functions":[{"def":{"name":"name1"}},{"def":{"name":"name2"}}]}`)) - jsonQueries := []*simplejson.Json{json1, json2} - - zabbixDatasource := mockZabbixDataSource(`{"result":"sampleResult"}`, 200) - resp := zabbixDatasource.getConsolidateBy(jsonQueries) - - assert.Equal(t, "sum", resp) - -} - -func Test_isUseTrend(t *testing.T) { - tests := []struct { - name string - timeRange *datasource.TimeRange - want bool - }{ - { - name: "History time", - timeRange: &datasource.TimeRange{ - FromEpochMs: time.Now().Add(-time.Hour*48).Unix() * 1000, - ToEpochMs: time.Now().Add(-time.Hour*12).Unix() * 1000, - }, - want: false, - }, - { - name: "Trend time (past 7 days)", - timeRange: &datasource.TimeRange{ - FromEpochMs: time.Now().Add(-time.Hour*24*14).Unix() * 1000, - ToEpochMs: time.Now().Add(-time.Hour*24*13).Unix() * 1000, - }, - want: true, - }, - { - name: "Trend time (longer than 4 days)", - timeRange: &datasource.TimeRange{ - FromEpochMs: time.Now().Add(-time.Hour*24*8).Unix() * 1000, - ToEpochMs: time.Now().Add(-time.Hour*24*1).Unix() * 1000, - }, - want: true, - }, - } - for _, tt := range tests { - got := isUseTrend(tt.timeRange) - assert.Equal(t, tt.want, got, tt.name, tt.timeRange) - } - -} - -func Test_parseFilter(t *testing.T) { - tests := []struct { - name string - filter string - want *regexp.Regexp - wantErr string - }{ - { - name: "Non-regex filter", - filter: "foobar", - want: nil, - }, - { - name: "Non-regex filter (would-be invalid regex)", - filter: "fooba(r", - want: nil, - }, - { - name: "Regex filter", - filter: "/^foo.+/", - want: regexp.MustCompile("^foo.+"), - }, - { - name: "Regex filter with flags", - filter: "/^foo.+/s", - want: regexp.MustCompile("(?s)^foo.+"), - }, - { - name: "Invalid regex", - filter: "/fooba(r/", - wantErr: "error parsing regexp: missing closing ): `fooba(r`", - }, - { - name: "Unsupported flag", - filter: "/foo.+/z", - wantErr: "error parsing regexp: unsupported flags `z` (expected [imsU])", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseFilter(tt.filter) - - if tt.wantErr != "" { - assert.Error(t, err) - assert.EqualError(t, err, tt.wantErr) - assert.Nil(t, got) - return - } - - assert.NoError(t, err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/zabbixapi/testing.go b/pkg/zabbixapi/testing.go new file mode 100644 index 0000000..e55ef5a --- /dev/null +++ b/pkg/zabbixapi/testing.go @@ -0,0 +1,44 @@ +package zabbixapi + +import ( + "bytes" + "io/ioutil" + "net/http" + "net/url" + + "github.com/grafana/grafana-plugin-sdk-go/backend/log" +) + +type RoundTripFunc func(req *http.Request) *http.Response + +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +//NewTestClient returns *http.Client with Transport replaced to avoid making real calls +func NewTestClient(fn RoundTripFunc) *http.Client { + return &http.Client{ + Transport: RoundTripFunc(fn), + } +} + +func MockZabbixAPI(body string, statusCode int) (*ZabbixAPI, error) { + apiLogger := log.New() + zabbixURL, err := url.Parse("http://zabbix.org/zabbix") + if err != nil { + return nil, err + } + + return &ZabbixAPI{ + url: zabbixURL, + logger: apiLogger, + + httpClient: NewTestClient(func(req *http.Request) *http.Response { + return &http.Response{ + StatusCode: statusCode, + Body: ioutil.NopCloser(bytes.NewBufferString(body)), + Header: make(http.Header), + } + }), + }, nil +} diff --git a/pkg/zabbixapi/zabbix_api.go b/pkg/zabbixapi/zabbix_api.go index 3912f94..a07009b 100644 --- a/pkg/zabbixapi/zabbix_api.go +++ b/pkg/zabbixapi/zabbix_api.go @@ -81,6 +81,11 @@ func (api *ZabbixAPI) SetUrl(api_url string) error { return nil } +// GetAuth returns API authentication token +func (api *ZabbixAPI) GetAuth() string { + return api.auth +} + // SetAuth sets API authentication token func (api *ZabbixAPI) SetAuth(auth string) { api.auth = auth diff --git a/pkg/zabbixapi/zabbix_api_test.go b/pkg/zabbixapi/zabbix_api_test.go index f24c58e..625f184 100644 --- a/pkg/zabbixapi/zabbix_api_test.go +++ b/pkg/zabbixapi/zabbix_api_test.go @@ -1,53 +1,14 @@ package zabbixapi import ( - "bytes" "context" - "io/ioutil" - "net/http" - "net/url" "testing" - "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/stretchr/testify/assert" ) -type RoundTripFunc func(req *http.Request) *http.Response - -func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -//NewTestClient returns *http.Client with Transport replaced to avoid making real calls -func NewTestClient(fn RoundTripFunc) *http.Client { - return &http.Client{ - Transport: RoundTripFunc(fn), - } -} - -func mockZabbixAPI(body string, statusCode int) (*ZabbixAPI, error) { - apiLogger := log.New() - zabbixURL, err := url.Parse("http://zabbix.org/zabbix") - if err != nil { - return nil, err - } - - return &ZabbixAPI{ - url: zabbixURL, - logger: apiLogger, - - httpClient: NewTestClient(func(req *http.Request) *http.Response { - return &http.Response{ - StatusCode: statusCode, - Body: ioutil.NopCloser(bytes.NewBufferString(body)), - Header: make(http.Header), - } - }), - }, nil -} - func TestZabbixAPIUnauthenticatedQuery(t *testing.T) { - zabbixApi, _ := mockZabbixAPI(`{"result":"sampleResult"}`, 200) + zabbixApi, _ := MockZabbixAPI(`{"result":"sampleResult"}`, 200) resp, err := zabbixApi.RequestUnauthenticated(context.Background(), "test.get", map[string]interface{}{}) assert.Equal(t, "sampleResult", resp.MustString()) @@ -55,7 +16,7 @@ func TestZabbixAPIUnauthenticatedQuery(t *testing.T) { } func TestLogin(t *testing.T) { - zabbixApi, _ := mockZabbixAPI(`{"result":"secretauth"}`, 200) + zabbixApi, _ := MockZabbixAPI(`{"result":"secretauth"}`, 200) err := zabbixApi.Authenticate(context.Background(), "user", "password") assert.Nil(t, err) @@ -91,7 +52,7 @@ func TestZabbixAPI(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - zabbixApi, _ := mockZabbixAPI(tt.mockApiResponse, tt.mockApiResponseCode) + zabbixApi, _ := MockZabbixAPI(tt.mockApiResponse, tt.mockApiResponseCode) zabbixApi.auth = tt.auth resp, err := zabbixApi.Request(context.Background(), "test.get", map[string]interface{}{})