Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Backend: implement alerting query #847

Merged
merged 34 commits into from
Jan 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
263e0a0
Unit tests for backend
Dec 12, 2019
4536c44
minor change
Dec 13, 2019
ee30444
Implemented querNumericItems in backend
Dec 17, 2019
b2a8d52
Added query type
Dec 17, 2019
afc3076
Set alerting to true
Dec 17, 2019
44ea860
Return TimeSeries from History
Dec 17, 2019
ffcafd8
Updated alerting feature
Dec 18, 2019
d03cbf1
Fix params marshal error
Dec 19, 2019
cbd66ca
Fix params more
Dec 19, 2019
e0ffdff
Update zabbixAPIConnector.js
Dec 19, 2019
549b122
Numbers, I guess
Dec 19, 2019
98f1041
Params Output Type
Dec 19, 2019
19b3c97
Output marshaling
Dec 19, 2019
dacfc9f
Unmarshal and decoder error catch
Dec 19, 2019
47d17c1
HistoryPoint and Unmarshal fixes
Dec 19, 2019
8bcf10c
Unmarshal to History
Dec 19, 2019
1131ab7
Revert "Update zabbixAPIConnector.js"
Dec 19, 2019
fc9ba2c
Fix unmarshaling for real
Dec 19, 2019
d71efc4
Time range integer
Dec 19, 2019
1b48a40
Use more zabbix.Items
Dec 19, 2019
a41678d
Update response_models.go
Dec 19, 2019
eafbd90
Update zabbix_api.go
Dec 19, 2019
fbd635c
Update models.go
Dec 19, 2019
2c03ab0
Update zabbix_api.go
Dec 19, 2019
589e771
Tests
Dec 19, 2019
d00e4d5
Adding more unit tests and cleaning up additional logging
Dec 21, 2019
9511524
Make history request param a pointer
Jan 2, 2020
74b97b6
Actually store datasource in cache
Jan 2, 2020
d1e9b37
Debug logs and timings
Jan 2, 2020
2f76604
Handle panics gracefully
Jan 2, 2020
8bb98ae
Updated Regex filter parsing
Jan 3, 2020
04b1b2a
Removed must compile
Jan 3, 2020
b6c2116
Clean up regex filter
Jan 3, 2020
0f384d7
Merge branch 'alerting-new-historymetrics' of https://github.com/dnet…
Jan 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/kr/pretty v0.1.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.8.1 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/text v0.3.2 // indirect
Expand Down
37 changes: 33 additions & 4 deletions pkg/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"runtime/debug"

simplejson "github.com/bitly/go-simplejson"
"github.com/grafana/grafana_plugin_model/go/datasource"
Expand All @@ -27,7 +28,16 @@ func (b *ZabbixBackend) newZabbixDatasource() *ZabbixDatasource {

// Query receives requests from the Grafana backend. Requests are filtered by query type and sent to the
// applicable ZabbixDatasource.
func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (resp *datasource.DatasourceResponse, _e error) {
defer func() {
if r := recover(); r != nil {
pErr, _ := r.(error)
b.logger.Error("Fatal error in Zabbix Plugin Backend", "Error", pErr)
b.logger.Error(string(debug.Stack()))
resp = BuildErrorResponse(fmt.Errorf("Unrecoverable error in grafana-zabbix plugin backend"))
}
}()

zabbixDs := b.getCachedDatasource(tsdbReq)

queryType, err := GetQueryType(tsdbReq)
Expand All @@ -37,13 +47,17 @@ func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.Datasourc

switch queryType {
case "zabbixAPI":
return zabbixDs.ZabbixAPIQuery(ctx, tsdbReq)
resp, err = zabbixDs.ZabbixAPIQuery(ctx, tsdbReq)
case "query":
resp, err = zabbixDs.queryNumericItems(ctx, tsdbReq)
case "connectionTest":
return zabbixDs.TestConnection(ctx, tsdbReq)
resp, err = zabbixDs.TestConnection(ctx, tsdbReq)
default:
err = errors.New("Query not implemented")
return BuildErrorResponse(err), nil
}

return
}

func (b *ZabbixBackend) getCachedDatasource(tsdbReq *datasource.DatasourceRequest) *ZabbixDatasource {
Expand All @@ -59,7 +73,10 @@ func (b *ZabbixBackend) getCachedDatasource(tsdbReq *datasource.DatasourceReques
dsInfo := tsdbReq.GetDatasource()
b.logger.Debug(fmt.Sprintf("Datasource cache miss (Org %d Id %d '%s' %s)", dsInfo.GetOrgId(), dsInfo.GetId(), dsInfo.GetName(), dsInfoHash))
}
return b.newZabbixDatasource()

ds := b.newZabbixDatasource()
b.datasourceCache.Set(dsInfoHash, ds)
return ds
}

// GetQueryType determines the query type from a query or list of queries
Expand Down Expand Up @@ -104,3 +121,15 @@ func BuildErrorResponse(err error) *datasource.DatasourceResponse {
},
}
}

// BuildMetricsResponse builds a response object using a given TimeSeries array
func BuildMetricsResponse(metrics []*datasource.TimeSeries) (*datasource.DatasourceResponse, error) {
return &datasource.DatasourceResponse{
Results: []*datasource.QueryResult{
&datasource.QueryResult{
RefId: "zabbixMetrics",
Series: metrics,
},
},
}, nil
}
5 changes: 5 additions & 0 deletions pkg/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ func TestZabbixBackend_getCachedDatasource(t *testing.T) {

// Only checking the authToken, being the easiest value to, and guarantee equality for
assert.Equal(t, tt.want.authToken, got.authToken)

// Ensure the datasource is in the cache
cacheds, ok := tt.cache.Get(HashDatasourceInfo(tt.request.GetDatasource()))
assert.Equal(t, true, ok)
assert.Equal(t, got, cacheds)
})
}
}
Expand Down
79 changes: 79 additions & 0 deletions pkg/models.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package main

import (
"encoding/json"
"fmt"
)

type connectionTestResponse struct {
ZabbixVersion string `json:"zabbixVersion"`
DbConnectorStatus *dbConnectionStatus `json:"dbConnectorStatus"`
Expand All @@ -9,3 +14,77 @@ type dbConnectionStatus struct {
DsType string `json:"dsType"`
DsName string `json:"dsName"`
}

type requestModel struct {
Target queryRequest `json:"target,omitempty"`
}

type queryRequest struct {
Method string `json:"method,omitempty"`
Params zabbixParams `json:"params,omitempty"`
}

type zabbixParamOutput struct {
Mode string
Fields []string
}

func (p *zabbixParamOutput) MarshalJSON() ([]byte, error) {
if p.Mode != "" {
return json.Marshal(p.Mode)
}

return json.Marshal(p.Fields)
}

func (p *zabbixParamOutput) UnmarshalJSON(data []byte) error {
if p == nil {
return fmt.Errorf("zabbixParamOutput: UnmarshalJSON on nil pointer")
}

var strArray []string
err := json.Unmarshal(data, &strArray)
if err == nil {
p.Fields = strArray
return nil
}

var str string
err = json.Unmarshal(data, &str)
if err == nil {
p.Mode = str
return nil
}

return fmt.Errorf("Unsupported type: %w", err)

}

type zabbixParams struct {
Output *zabbixParamOutput `json:"output,omitempty"`
SortField string `json:"sortfield,omitempty"`
SortOrder string `json:"sortorder,omitempty"`
Filter map[string][]int `json:"filter,omitempty"`

// Login
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`

// Item GET
WebItems bool `json:"webitems,omitempty"`
SelectHosts []string `json:"selectHosts,omitempty"`
ItemIDs []string `json:"itemids,omitempty"`
GroupIDs []string `json:"groupids,omitempty"`
HostIDs []string `json:"hostids,omitempty"`
AppIDs []string `json:"applicationids,omitempty"`

// Host Group GET
RealHosts bool `json:"real_hosts,omitempty"`

// History GET
History *int `json:"history,omitempty,string"`

// History/Trends GET
TimeFrom int64 `json:"time_from,omitempty"`
TimeTill int64 `json:"time_till,omitempty"`
}
60 changes: 60 additions & 0 deletions pkg/models_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_zabbixParamOutput(t *testing.T) {
tests := []struct {
name string
input zabbixParams
want string
}{
{
name: "Mode extend",
input: zabbixParams{
Output: &zabbixParamOutput{
Mode: "extend",
},
GroupIDs: []string{"test1", "test2"},
},
want: `{ "output": "extend", "groupids": ["test1", "test2"] }`,
},
{
name: "Fields",
input: zabbixParams{
Output: &zabbixParamOutput{
Fields: []string{"name", "key_", "hostid"},
},
GroupIDs: []string{"test1", "test2"},
},
want: `{ "output": ["name", "key_", "hostid"], "groupids": ["test1", "test2"] }`,
},
{
name: "No Output",
input: zabbixParams{
GroupIDs: []string{"test1", "test2"},
},
want: `{ "groupids": ["test1", "test2"] }`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jsonOut, err := json.Marshal(tt.input)
fmt.Printf("Output: %s\n", jsonOut)
assert.NoError(t, err)
if !assert.JSONEq(t, tt.want, string(jsonOut)) {
return
}

objOut := zabbixParams{}
err = json.Unmarshal(jsonOut, &objOut)
assert.NoError(t, err)
assert.Equal(t, tt.input, objOut)
})
}
}
35 changes: 35 additions & 0 deletions pkg/zabbix/response_models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package zabbix

type Items []Item
type Item struct {
ID string `json:"itemid,omitempty"`
Key string `json:"key_,omitempty"`
Name string `json:"name,omitempty"`
ValueType int `json:"value_type,omitempty,string"`
HostID string `json:"hostid,omitempty"`
Hosts []ItemHost `json:"hosts,omitempty"`
Status string `json:"status,omitempty"`
State string `json:"state,omitempty"`
}
type ItemHost struct {
ID string `json:"hostid,omitempty"`
Name string `json:"name,omitempty"`
}

type Trend []TrendPoint
type TrendPoint struct {
ItemID string `json:"itemid,omitempty"`
Clock int64 `json:"clock,omitempty,string"`
Num string `json:"num,omitempty"`
ValueMin string `json:"value_min,omitempty"`
ValueAvg string `json:"value_avg,omitempty"`
ValueMax string `json:"value_max,omitempty"`
}

type History []HistoryPoint
type HistoryPoint struct {
ItemID string `json:"itemid,omitempty"`
Clock int64 `json:"clock,omitempty,string"`
Value float64 `json:"value,omitempty,string"`
NS int64 `json:"ns,omitempty,string"`
}
Loading