Migrate to new backend sdk, use /zabbix-api endpoint for API queries
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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"}}`,
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
49
pkg/resource_handler.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user