Migrate to new backend sdk, use /zabbix-api endpoint for API queries

This commit is contained in:
Alexander Zobnin
2020-05-28 17:04:49 +03:00
parent 9f93faaebf
commit f0daa9fcb9
12 changed files with 478 additions and 141 deletions

View File

@@ -6,7 +6,7 @@ import (
"encoding/json"
"time"
"github.com/grafana/grafana_plugin_model/go/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend"
cache "github.com/patrickmn/go-cache"
)
@@ -40,7 +40,7 @@ func HashString(text string) string {
}
// HashDatasourceInfo converts the given datasource info to hash string
func HashDatasourceInfo(dsInfo *datasource.DatasourceInfo) string {
func HashDatasourceInfo(dsInfo *backend.DataSourceInstanceSettings) string {
digester := sha1.New()
if err := json.NewEncoder(digester).Encode(dsInfo); err != nil {
panic(err) // This shouldn't be possible but just in case DatasourceInfo changes

View File

@@ -3,82 +3,141 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"time"
simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
// ZabbixPlugin implements the Grafana backend interface and forwards queries to the ZabbixDatasource
// ZabbixPlugin implements the Grafana backend interface and forwards queries to the ZabbixDatasourceInstance
type ZabbixPlugin struct {
plugin.NetRPCUnsupportedPlugin
logger hclog.Logger
datasourceCache *Cache
}
func (p *ZabbixPlugin) NewZabbixDatasource(dsInfo *datasource.DatasourceInfo) (*ZabbixDatasource, error) {
ds, err := newZabbixDatasource(dsInfo)
type ZabbixDatasource struct {
datasourceCache *Cache
logger log.Logger
}
func NewDatasource(logger log.Logger, mux *http.ServeMux) *ZabbixDatasource {
variableName := "GFX_ZABBIX_DATA_PATH"
path, exist := os.LookupEnv(variableName)
if !exist {
logger.Error("could not read environment variable", variableName)
} else {
logger.Debug("environment variable for storage found", "variable", variableName, "value", path)
}
ds := &ZabbixDatasource{
logger: logger,
datasourceCache: NewCache(10*time.Minute, 10*time.Minute),
}
mux.HandleFunc("/", ds.rootHandler)
mux.HandleFunc("/zabbix-api", ds.zabbixAPIHandler)
// mux.Handle("/scenarios", getScenariosHandler(logger))
return ds
}
// CheckHealth checks if the plugin is running properly
func (ds *ZabbixDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
res := &backend.CheckHealthResult{}
// Just checking that the plugin exe is alive and running
if req.PluginContext.DataSourceInstanceSettings == nil {
res.Status = backend.HealthStatusOk
res.Message = "Plugin is running"
return res, nil
}
// TODO? actually check datasource settings?
res.Status = backend.HealthStatusOk
res.Message = "Success"
return res, nil
}
func (gds *ZabbixDatasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
qdr := backend.NewQueryDataResponse()
for _, q := range req.Queries {
res := backend.DataResponse{}
qdr.Responses[q.RefID] = res
}
return qdr, nil
}
// func (p *ZabbixPlugin) GetDatasourceById(datasourceId int64) (*ZabbixDatasourceInstance, error) {
// }
func (ds *ZabbixDatasource) NewZabbixDatasource(dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) {
dsInstance, err := newZabbixDatasource(dsInfo)
if err != nil {
return nil, err
}
ds.logger = p.logger
return ds, nil
dsInstance.logger = ds.logger
return dsInstance, nil
}
// Query receives requests from the Grafana backend. Requests are filtered by query type and sent to the
// applicable ZabbixDatasource.
func (p *ZabbixPlugin) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (resp *datasource.DatasourceResponse, err error) {
zabbixDS, err := p.GetDatasource(tsdbReq)
if err != nil {
return nil, err
}
// func (p *ZabbixPlugin) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (resp *datasource.DatasourceResponse, err error) {
// zabbixDS, err := p.GetDatasource(tsdbReq)
// if err != nil {
// return nil, err
// }
queryType, err := GetQueryType(tsdbReq)
if err != nil {
return nil, err
}
// queryType, err := GetQueryType(tsdbReq)
// if err != nil {
// return nil, err
// }
switch queryType {
case "zabbixAPI":
resp, err = zabbixDS.ZabbixAPIQuery(ctx, tsdbReq)
case "query":
resp, err = zabbixDS.queryNumericItems(ctx, tsdbReq)
case "connectionTest":
resp, err = zabbixDS.TestConnection(ctx, tsdbReq)
default:
err = errors.New("Query not implemented")
return BuildErrorResponse(err), nil
}
// switch queryType {
// case "zabbixAPI":
// resp, err = zabbixDS.ZabbixAPIQuery(ctx, tsdbReq)
// case "query":
// resp, err = zabbixDS.queryNumericItems(ctx, tsdbReq)
// case "connectionTest":
// resp, err = zabbixDS.TestConnection(ctx, tsdbReq)
// default:
// err = errors.New("Query not implemented")
// return BuildErrorResponse(err), nil
// }
return
}
// return
// }
// GetDatasource Returns cached datasource or creates new one
func (p *ZabbixPlugin) GetDatasource(tsdbReq *datasource.DatasourceRequest) (*ZabbixDatasource, error) {
dsInfoHash := HashDatasourceInfo(tsdbReq.GetDatasource())
func (ds *ZabbixDatasource) GetDatasource(orgID int64, dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) {
dsInfoHash := HashDatasourceInfo(dsInfo)
if cachedData, ok := p.datasourceCache.Get(dsInfoHash); ok {
if cachedDS, ok := cachedData.(*ZabbixDatasource); ok {
if cachedData, ok := ds.datasourceCache.Get(dsInfoHash); ok {
if cachedDS, ok := cachedData.(*ZabbixDatasourceInstance); ok {
return cachedDS, nil
}
}
dsInfo := tsdbReq.GetDatasource()
if p.logger.IsDebug() {
p.logger.Debug(fmt.Sprintf("Datasource cache miss (Org %d Id %d '%s' %s)", dsInfo.GetOrgId(), dsInfo.GetId(), dsInfo.GetName(), dsInfoHash))
}
ds.logger.Debug(fmt.Sprintf("Datasource cache miss (Org %d Id %d '%s' %s)", orgID, dsInfo.ID, dsInfo.Name, dsInfoHash))
ds, err := p.NewZabbixDatasource(dsInfo)
dsInstance, err := ds.NewZabbixDatasource(dsInfo)
if err != nil {
return nil, err
}
p.datasourceCache.Set(dsInfoHash, ds)
return ds, nil
ds.datasourceCache.Set(dsInfoHash, dsInstance)
return dsInstance, nil
}
// GetQueryType determines the query type from a query or list of queries
@@ -95,6 +154,22 @@ func GetQueryType(tsdbReq *datasource.DatasourceRequest) (string, error) {
return queryType, nil
}
// // BuildDataResponse transforms a Zabbix API response to the QueryDataResponse
// func BuildDataResponse(responseData interface{}) (*backend.QueryDataResponse, error) {
// jsonBytes, err := json.Marshal(responseData)
// if err != nil {
// return nil, err
// }
// return &backend.QueryDataResponse{
// Responses: map[string]backend.DataResponse{
// "zabbixAPI": {
// Frames: ,
// }
// },
// }, nil
// }
// BuildResponse transforms a Zabbix API response to a DatasourceResponse
func BuildResponse(responseData interface{}) (*datasource.DatasourceResponse, error) {
jsonBytes, err := json.Marshal(responseData)
@@ -104,7 +179,7 @@ func BuildResponse(responseData interface{}) (*datasource.DatasourceResponse, er
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
{
RefId: "zabbixAPI",
MetaJson: string(jsonBytes),
},
@@ -116,7 +191,7 @@ func BuildResponse(responseData interface{}) (*datasource.DatasourceResponse, er
func BuildErrorResponse(err error) *datasource.DatasourceResponse {
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
{
RefId: "zabbixAPI",
Error: err.Error(),
},
@@ -128,7 +203,7 @@ func BuildErrorResponse(err error) *datasource.DatasourceResponse {
func BuildMetricsResponse(metrics []*datasource.TimeSeries) (*datasource.DatasourceResponse, error) {
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
{
RefId: "zabbixMetrics",
Series: metrics,
},

View File

@@ -34,7 +34,7 @@ func TestZabbixBackend_getCachedDatasource(t *testing.T) {
name string
cache *cache.Cache
request *datasource.DatasourceRequest
want *ZabbixDatasource
want *ZabbixDatasourceInstance
}{
{
name: "Uncached Datasource (nothing in cache)",
@@ -46,7 +46,7 @@ func TestZabbixBackend_getCachedDatasource(t *testing.T) {
{
name: "Uncached Datasource (cache miss)",
cache: cache.NewFrom(cache.NoExpiration, cache.NoExpiration, map[string]cache.Item{
basicDatasourceInfoHash: cache.Item{Object: modifiedDatasource},
basicDatasourceInfoHash: {Object: modifiedDatasource},
}),
request: &datasource.DatasourceRequest{
Datasource: altDatasourceInfo,
@@ -56,8 +56,8 @@ func TestZabbixBackend_getCachedDatasource(t *testing.T) {
{
name: "Cached Datasource",
cache: cache.NewFrom(cache.NoExpiration, cache.NoExpiration, map[string]cache.Item{
altDatasourceInfoHash: cache.Item{Object: basicDS},
basicDatasourceInfoHash: cache.Item{Object: modifiedDatasource},
altDatasourceInfoHash: {Object: basicDS},
basicDatasourceInfoHash: {Object: modifiedDatasource},
}),
request: &datasource.DatasourceRequest{
Datasource: basicDatasourceInfo,
@@ -105,7 +105,7 @@ func TestBuildResponse(t *testing.T) {
responseData: jsonData,
want: &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
{
RefId: "zabbixAPI",
MetaJson: `{"testing":[5,12,75]}`,
},
@@ -123,7 +123,7 @@ func TestBuildResponse(t *testing.T) {
},
want: &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
{
RefId: "zabbixAPI",
MetaJson: `{"zabbixVersion":"2.4","dbConnectorStatus":{"dsType":"mysql","dsName":"MyDatabase"}}`,
},

View File

@@ -60,7 +60,9 @@ func (p *zabbixParamOutput) UnmarshalJSON(data []byte) error {
}
type ZabbixAPIParams struct {
type ZabbixAPIParams = map[string]interface{}
type ZabbixAPIParamsLegacy struct {
Output *zabbixParamOutput `json:"output,omitempty"`
SortField string `json:"sortfield,omitempty"`
SortOrder string `json:"sortorder,omitempty"`
@@ -101,3 +103,19 @@ type ZabbixAPIParams struct {
TimeFrom int64 `json:"time_from,omitempty"`
TimeTill int64 `json:"time_till,omitempty"`
}
type ZabbixAPIResourceRequest struct {
DatasourceId int64 `json:"datasourceId"`
Method string `json:"method"`
Params map[string]interface{} `json:"params,omitempty"`
}
type ZabbixAPIRequest struct {
Method string `json:"method"`
Params map[string]interface{} `json:"params,omitempty"`
}
func (r *ZabbixAPIRequest) String() string {
jsonRequest, _ := json.Marshal(r.Params)
return r.Method + string(jsonRequest)
}

View File

@@ -1,36 +1,49 @@
package main
import (
"time"
"net/http"
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"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"
)
var pluginLogger = hclog.New(&hclog.LoggerOptions{
Name: "zabbix-datasource",
Level: hclog.LevelFromString("DEBUG"),
})
const ZABBIX_PLUGIN_ID = "alexanderzobnin-zabbix-datasource"
func main() {
pluginLogger.Debug("Running Zabbix backend datasource")
backend.SetupPluginEnvironment(ZABBIX_PLUGIN_ID)
plugin.Serve(&plugin.ServeConfig{
pluginLogger := log.New()
mux := http.NewServeMux()
ds := NewDatasource(pluginLogger, mux)
httpResourceHandler := httpadapter.New(mux)
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "grafana_plugin_type",
MagicCookieValue: "datasource",
},
Plugins: map[string]plugin.Plugin{
"zabbix-backend-datasource": &datasource.DatasourcePluginImpl{Plugin: &ZabbixPlugin{
datasourceCache: NewCache(10*time.Minute, 10*time.Minute),
logger: pluginLogger,
}},
},
pluginLogger.Debug("Starting Zabbix backend datasource")
// A non-nil value here enables gRPC serving for this plugin...
GRPCServer: plugin.DefaultGRPCServer,
err := backend.Serve(backend.ServeOpts{
CallResourceHandler: httpResourceHandler,
QueryDataHandler: ds,
CheckHealthHandler: ds,
})
if err != nil {
pluginLogger.Error(err.Error())
}
// plugin.Serve(&plugin.ServeConfig{
// HandshakeConfig: plugin.HandshakeConfig{
// ProtocolVersion: 1,
// MagicCookieKey: "grafana_plugin_type",
// MagicCookieValue: "datasource",
// },
// Plugins: map[string]plugin.Plugin{
// "zabbix-backend-datasource": &datasource.DatasourcePluginImpl{Plugin: &ZabbixPlugin{
// datasourceCache: NewCache(10*time.Minute, 10*time.Minute),
// logger: pluginLogger,
// }},
// },
// // A non-nil value here enables gRPC serving for this plugin...
// GRPCServer: plugin.DefaultGRPCServer,
// })
}

49
pkg/resource_handler.go Normal file
View File

@@ -0,0 +1,49 @@
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)
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) {
ds.logger.Debug("Received resource call", "url", req.URL.String(), "method", req.Method)
if req.Method != http.MethodPost {
return
}
body, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil || len(body) == 0 {
rw.WriteHeader(http.StatusBadRequest)
}
var reqData ZabbixAPIResourceRequest
err = json.Unmarshal(body, &reqData)
pluginCxt := httpadapter.PluginConfigFromContext(req.Context())
ds.logger.Debug("Received Zabbix API call", "ds", pluginCxt.DataSourceInstanceSettings.Name)
dsInstance, err := ds.GetDatasource(pluginCxt.OrgID, pluginCxt.DataSourceInstanceSettings)
ds.logger.Debug("Data source found", "ds", dsInstance.dsInfo.Name)
apiReq := &ZabbixAPIRequest{Method: reqData.Method, Params: reqData.Params}
result, err := dsInstance.ZabbixAPIQuery(req.Context(), apiReq)
resultJson, err := json.Marshal(*result)
ds.logger.Debug("Got response", "result", result)
ds.logger.Debug("Received Zabbix API call", "ds", reqData.DatasourceId, "method", reqData.Method, "params", reqData.Params)
rw.Write(resultJson)
rw.WriteHeader(http.StatusOK)
}

View File

@@ -25,7 +25,27 @@ type FunctionCategories struct {
}
// ZabbixAPIQuery handles query requests to Zabbix
func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
func (dsInstance *ZabbixDatasourceInstance) ZabbixAPIQuery(ctx context.Context, apiReq *ZabbixAPIRequest) (*interface{}, error) {
var result interface{}
var err error
var queryExistInCache bool
result, queryExistInCache = dsInstance.queryCache.Get(HashString(apiReq.String()))
if !queryExistInCache {
result, err = dsInstance.ZabbixRequest(ctx, apiReq.Method, apiReq.Params)
dsInstance.logger.Debug("ZabbixAPIQuery", "result", result)
dsInstance.queryCache.Set(HashString(apiReq.String()), result)
if err != nil {
dsInstance.logger.Debug("ZabbixAPIQuery", "error", err)
return nil, errors.New("ZabbixAPIQuery is not implemented yet")
}
}
// return BuildResponse(result)
return &result, nil
}
func (ds *ZabbixDatasourceInstance) ZabbixAPIQueryOld(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
result, queryExistInCache := ds.queryCache.Get(HashString(tsdbReq.String()))
if !queryExistInCache {
@@ -58,7 +78,7 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
}
// TestConnection checks authentication and version of the Zabbix API and returns that info
func (ds *ZabbixDatasource) TestConnection(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
func (ds *ZabbixDatasourceInstance) TestConnection(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
err := ds.loginWithDs(ctx)
if err != nil {
return BuildErrorResponse(fmt.Errorf("Authentication failed: %s", err)), nil
@@ -80,7 +100,7 @@ func (ds *ZabbixDatasource) TestConnection(ctx context.Context, tsdbReq *datasou
return BuildResponse(testResponse)
}
func (ds *ZabbixDatasource) queryNumericItems(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
func (ds *ZabbixDatasourceInstance) queryNumericItems(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
tStart := time.Now()
jsonQueries := make([]*simplejson.Json, 0)
for _, query := range tsdbReq.Queries {
@@ -124,7 +144,7 @@ func (ds *ZabbixDatasource) queryNumericItems(ctx context.Context, tsdbReq *data
return BuildMetricsResponse(metrics)
}
func (ds *ZabbixDatasource) getItems(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string, appFilter string, itemFilter string, itemType string) (zabbix.Items, error) {
func (ds *ZabbixDatasourceInstance) getItems(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string, appFilter string, itemFilter string, itemType string) (zabbix.Items, error) {
tStart := time.Now()
hosts, err := ds.getHosts(ctx, dsInfo, groupFilter, hostFilter)
@@ -195,7 +215,7 @@ func (ds *ZabbixDatasource) getItems(ctx context.Context, dsInfo *datasource.Dat
return filteredItems, nil
}
func (ds *ZabbixDatasource) getApps(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string, appFilter string) ([]map[string]interface{}, error) {
func (ds *ZabbixDatasourceInstance) getApps(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string, appFilter string) ([]map[string]interface{}, error) {
hosts, err := ds.getHosts(ctx, dsInfo, groupFilter, hostFilter)
if err != nil {
return nil, err
@@ -229,7 +249,7 @@ func (ds *ZabbixDatasource) getApps(ctx context.Context, dsInfo *datasource.Data
return apps, nil
}
func (ds *ZabbixDatasource) getHosts(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string) ([]map[string]interface{}, error) {
func (ds *ZabbixDatasourceInstance) getHosts(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string, hostFilter string) ([]map[string]interface{}, error) {
groups, err := ds.getGroups(ctx, dsInfo, groupFilter)
if err != nil {
return nil, err
@@ -264,7 +284,7 @@ func (ds *ZabbixDatasource) getHosts(ctx context.Context, dsInfo *datasource.Dat
return hosts, nil
}
func (ds *ZabbixDatasource) getGroups(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string) ([]map[string]interface{}, error) {
func (ds *ZabbixDatasourceInstance) getGroups(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupFilter string) ([]map[string]interface{}, error) {
allGroups, err := ds.getAllGroups(ctx, dsInfo)
if err != nil {
return nil, err
@@ -288,45 +308,46 @@ func (ds *ZabbixDatasource) getGroups(ctx context.Context, dsInfo *datasource.Da
return groups, nil
}
func (ds *ZabbixDatasource) getAllItems(ctx context.Context, dsInfo *datasource.DatasourceInfo, hostids []string, appids []string, itemtype string) (*simplejson.Json, error) {
func (ds *ZabbixDatasourceInstance) getAllItems(ctx context.Context, dsInfo *datasource.DatasourceInfo, hostids []string, appids []string, itemtype string) (*simplejson.Json, error) {
params := ZabbixAPIParams{
Output: &zabbixParamOutput{Fields: []string{"itemid", "name", "key_", "value_type", "hostid", "status", "state"}},
SortField: "name",
WebItems: true,
Filter: map[string]interface{}{},
SelectHosts: []string{"hostid", "name"},
HostIDs: hostids,
AppIDs: appids,
"output": &zabbixParamOutput{Fields: []string{"itemid", "name", "key_", "value_type", "hostid", "status", "state"}},
"sortField": "name",
"webItems": true,
"filter": map[string]interface{}{},
"selectHosts": []string{"hostid", "name"},
"hostIDs": hostids,
"appIDs": appids,
}
filter := params["filter"].(map[string]interface{})
if itemtype == "num" {
params.Filter["value_type"] = []int{0, 3}
filter["value_type"] = []int{0, 3}
} else if itemtype == "text" {
params.Filter["value_type"] = []int{1, 2, 4}
filter["value_type"] = []int{1, 2, 4}
}
return ds.ZabbixRequest(ctx, "item.get", params)
}
func (ds *ZabbixDatasource) getAllApps(ctx context.Context, dsInfo *datasource.DatasourceInfo, hostids []string) (*simplejson.Json, error) {
params := ZabbixAPIParams{Output: &zabbixParamOutput{Mode: "extend"}, HostIDs: hostids}
func (ds *ZabbixDatasourceInstance) getAllApps(ctx context.Context, dsInfo *datasource.DatasourceInfo, hostids []string) (*simplejson.Json, error) {
params := ZabbixAPIParams{"output": &zabbixParamOutput{Mode: "extend"}, "hostIDs": hostids}
return ds.ZabbixRequest(ctx, "application.get", params)
}
func (ds *ZabbixDatasource) getAllHosts(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupids []string) (*simplejson.Json, error) {
params := ZabbixAPIParams{Output: &zabbixParamOutput{Fields: []string{"name", "host"}}, SortField: "name", GroupIDs: groupids}
func (ds *ZabbixDatasourceInstance) getAllHosts(ctx context.Context, dsInfo *datasource.DatasourceInfo, groupids []string) (*simplejson.Json, error) {
params := ZabbixAPIParams{"output": &zabbixParamOutput{Fields: []string{"name", "host"}}, "sortField": "name", "groupIDs": groupids}
return ds.ZabbixRequest(ctx, "host.get", params)
}
func (ds *ZabbixDatasource) getAllGroups(ctx context.Context, dsInfo *datasource.DatasourceInfo) (*simplejson.Json, error) {
params := ZabbixAPIParams{Output: &zabbixParamOutput{Fields: []string{"name"}}, SortField: "name", RealHosts: true}
func (ds *ZabbixDatasourceInstance) getAllGroups(ctx context.Context, dsInfo *datasource.DatasourceInfo) (*simplejson.Json, error) {
params := ZabbixAPIParams{"output": &zabbixParamOutput{Fields: []string{"name"}}, "sortField": "name", "realHosts": true}
return ds.ZabbixRequest(ctx, "hostgroup.get", params)
}
func (ds *ZabbixDatasource) queryNumericDataForItems(ctx context.Context, tsdbReq *datasource.DatasourceRequest, items zabbix.Items, jsonQueries []*simplejson.Json, useTrend bool) ([]*datasource.TimeSeries, error) {
func (ds *ZabbixDatasourceInstance) queryNumericDataForItems(ctx context.Context, tsdbReq *datasource.DatasourceRequest, items zabbix.Items, jsonQueries []*simplejson.Json, useTrend bool) ([]*datasource.TimeSeries, error) {
valueType := ds.getTrendValueType(jsonQueries)
consolidateBy := ds.getConsolidateBy(jsonQueries)
@@ -342,7 +363,7 @@ func (ds *ZabbixDatasource) queryNumericDataForItems(ctx context.Context, tsdbRe
return convertHistory(history, items)
}
func (ds *ZabbixDatasource) getTrendValueType(jsonQueries []*simplejson.Json) string {
func (ds *ZabbixDatasourceInstance) getTrendValueType(jsonQueries []*simplejson.Json) string {
var trendFunctions []string
var trendValueFunc string
@@ -365,7 +386,7 @@ func (ds *ZabbixDatasource) getTrendValueType(jsonQueries []*simplejson.Json) st
return trendValueFunc
}
func (ds *ZabbixDatasource) getConsolidateBy(jsonQueries []*simplejson.Json) string {
func (ds *ZabbixDatasourceInstance) getConsolidateBy(jsonQueries []*simplejson.Json) string {
var consolidateBy string
for _, k := range jsonQueries[0].Get("functions").MustArray() {
@@ -379,7 +400,7 @@ func (ds *ZabbixDatasource) getConsolidateBy(jsonQueries []*simplejson.Json) str
return consolidateBy
}
func (ds *ZabbixDatasource) getHistotyOrTrend(ctx context.Context, tsdbReq *datasource.DatasourceRequest, items zabbix.Items, useTrend bool) (zabbix.History, error) {
func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, tsdbReq *datasource.DatasourceRequest, items zabbix.Items, useTrend bool) (zabbix.History, error) {
allHistory := zabbix.History{}
timeRange := tsdbReq.GetTimeRange()
@@ -396,12 +417,12 @@ func (ds *ZabbixDatasource) getHistotyOrTrend(ctx context.Context, tsdbReq *data
}
params := ZabbixAPIParams{
Output: &zabbixParamOutput{Mode: "extend"},
SortField: "clock",
SortOrder: "ASC",
ItemIDs: itemids,
TimeFrom: timeRange.GetFromEpochMs() / 1000,
TimeTill: timeRange.GetToEpochMs() / 1000,
"output": &zabbixParamOutput{Mode: "extend"},
"sortField": "clock",
"sortOrder": "ASC",
"itemIDs": itemids,
"timeFrom": timeRange.GetFromEpochMs() / 1000,
"timeTill": timeRange.GetToEpochMs() / 1000,
}
var response *simplejson.Json
@@ -409,7 +430,7 @@ func (ds *ZabbixDatasource) getHistotyOrTrend(ctx context.Context, tsdbReq *data
if useTrend {
response, err = ds.ZabbixRequest(ctx, "trend.get", params)
} else {
params.History = &k
params["history"] = &k
response, err = ds.ZabbixRequest(ctx, "history.get", params)
}

View File

@@ -15,31 +15,31 @@ import (
"time"
simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource"
hclog "github.com/hashicorp/go-hclog"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"golang.org/x/net/context/ctxhttp"
)
// ZabbixDatasource stores state about a specific datasource and provides methods to make
// ZabbixDatasourceInstance stores state about a specific datasource and provides methods to make
// requests to the Zabbix API
type ZabbixDatasource struct {
type ZabbixDatasourceInstance struct {
url *url.URL
authToken string
dsInfo *datasource.DatasourceInfo
dsInfo *backend.DataSourceInstanceSettings
queryCache *Cache
logger hclog.Logger
httpClient *http.Client
logger log.Logger
}
// newZabbixDatasource returns an initialized ZabbixDatasource
func newZabbixDatasource(dsInfo *datasource.DatasourceInfo) (*ZabbixDatasource, error) {
zabbixURLStr := dsInfo.GetUrl()
func newZabbixDatasource(dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) {
zabbixURLStr := dsInfo.URL
zabbixURL, err := url.Parse(zabbixURLStr)
if err != nil {
return nil, err
}
return &ZabbixDatasource{
return &ZabbixDatasourceInstance{
url: zabbixURL,
dsInfo: dsInfo,
queryCache: NewCache(10*time.Minute, 10*time.Minute),
@@ -64,7 +64,7 @@ func newZabbixDatasource(dsInfo *datasource.DatasourceInfo) (*ZabbixDatasource,
}
// ZabbixRequest checks authentication and makes a request to the Zabbix API
func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, method string, params ZabbixAPIParams) (*simplejson.Json, error) {
func (ds *ZabbixDatasourceInstance) ZabbixRequest(ctx context.Context, method string, params ZabbixAPIParams) (*simplejson.Json, error) {
var result *simplejson.Json
var err error
@@ -86,16 +86,16 @@ func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, method string, pa
return result, err
}
func (ds *ZabbixDatasource) loginWithDs(ctx context.Context) error {
jsonDataStr := ds.dsInfo.GetJsonData()
jsonData, err := simplejson.NewJson([]byte(jsonDataStr))
func (ds *ZabbixDatasourceInstance) loginWithDs(ctx context.Context) error {
jsonDataStr := ds.dsInfo.JSONData
jsonData, err := simplejson.NewJson(jsonDataStr)
if err != nil {
return err
}
zabbixLogin := jsonData.Get("username").MustString()
var zabbixPassword string
if securePassword, exists := ds.dsInfo.GetDecryptedSecureJsonData()["password"]; exists {
if securePassword, exists := ds.dsInfo.DecryptedSecureJSONData["password"]; exists {
zabbixPassword = securePassword
} else {
zabbixPassword = jsonData.Get("password").MustString()
@@ -113,10 +113,10 @@ func (ds *ZabbixDatasource) loginWithDs(ctx context.Context) error {
return nil
}
func (ds *ZabbixDatasource) login(ctx context.Context, username string, password string) (string, error) {
func (ds *ZabbixDatasourceInstance) login(ctx context.Context, username string, password string) (string, error) {
params := ZabbixAPIParams{
User: username,
Password: password,
"user": username,
"password": password,
}
auth, err := ds.ZabbixAPIRequest(ctx, "user.login", params, "")
if err != nil {
@@ -126,7 +126,7 @@ func (ds *ZabbixDatasource) login(ctx context.Context, username string, password
return auth.MustString(), nil
}
func (ds *ZabbixDatasource) ZabbixAPIRequest(ctx context.Context, method string, params ZabbixAPIParams, auth string) (*simplejson.Json, error) {
func (ds *ZabbixDatasourceInstance) ZabbixAPIRequest(ctx context.Context, method string, params ZabbixAPIParams, auth string) (*simplejson.Json, error) {
apiRequest := map[string]interface{}{
"jsonrpc": "2.0",
"id": 2,

View File

@@ -10,6 +10,7 @@ import (
"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"
@@ -29,27 +30,27 @@ func NewTestClient(fn RoundTripFunc) *http.Client {
}
}
var basicDatasourceInfo = &datasource.DatasourceInfo{
Id: 1,
var basicDatasourceInfo = &backend.DataSourceInstanceSettings{
ID: 1,
Name: "TestDatasource",
Url: "http://zabbix.org/zabbix",
JsonData: `{"username":"username", "password":"password"}}`,
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{
&datasource.Query{
{
ModelJson: modelJSON,
},
},
}
}
func mockZabbixDataSource(body string, statusCode int) ZabbixDatasource {
func mockZabbixDataSource(body string, statusCode int) ZabbixDatasourceInstance {
apiUrl, _ := url.Parse(basicDatasourceInfo.Url)
return ZabbixDatasource{
return ZabbixDatasourceInstance{
url: apiUrl,
dsInfo: basicDatasourceInfo,
queryCache: NewCache(10*time.Minute, 10*time.Minute),