This commit is contained in:
Alec Sears
2019-10-17 16:06:35 -05:00
parent 7c3f26a7f4
commit 05243a9701
9 changed files with 158 additions and 33 deletions

View File

@@ -26,6 +26,10 @@
}, },
"devDependencies": { "devDependencies": {
"@types/classnames": "^2.2.6", "@types/classnames": "^2.2.6",
"@grafana/data": "^6.4.3",
"@grafana/runtime": "^6.4.3",
"@grafana/toolkit": "^6.4.3",
"@grafana/ui": "^6.4.3",
"@types/grafana": "github:CorpGlory/types-grafana", "@types/grafana": "github:CorpGlory/types-grafana",
"@types/jest": "^23.1.1", "@types/jest": "^23.1.1",
"@types/jquery": "^3.3.0", "@types/jquery": "^3.3.0",

View File

@@ -37,6 +37,8 @@ func (b *ZabbixBackend) Query(ctx context.Context, tsdbReq *datasource.Datasourc
switch queryType { switch queryType {
case "zabbixAPI": case "zabbixAPI":
return zabbixDs.ZabbixAPIQuery(ctx, tsdbReq) return zabbixDs.ZabbixAPIQuery(ctx, tsdbReq)
case "zabbixConnectionTest":
return zabbixDs.TestConnection(ctx, tsdbReq)
default: default:
return nil, errors.New("Query is not implemented yet") return nil, errors.New("Query is not implemented yet")
} }

View File

@@ -1 +1,11 @@
package main package main
type connectionTestResponse struct {
ZabbixVersion string `json:"zabbixVersion"`
DbConnectorStatus dbConnectionStatus `json:"dbConnectorStatus"`
}
type dbConnectionStatus struct {
dsType string
dsName string
}

View File

@@ -79,7 +79,7 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
for _, query := range tsdbReq.Queries { for _, query := range tsdbReq.Queries {
json, err := simplejson.NewJson([]byte(query.ModelJson)) json, err := simplejson.NewJson([]byte(query.ModelJson))
apiMethod := json.GetPath("target", "method").MustString() apiMethod := json.GetPath("target", "method").MustString()
apiParams := json.GetPath("target", "params") apiParams := json.GetPath("target", "params").MustMap()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -96,10 +96,11 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
jsonQuery := jsonQueries[0].Get("target") jsonQuery := jsonQueries[0].Get("target")
apiMethod := jsonQuery.Get("method").MustString() apiMethod := jsonQuery.Get("method").MustString()
apiParams := jsonQuery.Get("params") apiParams := jsonQuery.Get("params").MustMap()
result, err := ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams) response, err := ds.ZabbixRequest(ctx, dsInfo, apiMethod, apiParams)
ds.queryCache.Set(HashString(tsdbReq.String()), result) ds.queryCache.Set(HashString(tsdbReq.String()), response)
result = response
if err != nil { if err != nil {
ds.logger.Debug("ZabbixAPIQuery", "error", err) ds.logger.Debug("ZabbixAPIQuery", "error", err)
return nil, errors.New("ZabbixAPIQuery is not implemented yet") return nil, errors.New("ZabbixAPIQuery is not implemented yet")
@@ -112,6 +113,41 @@ func (ds *ZabbixDatasource) ZabbixAPIQuery(ctx context.Context, tsdbReq *datasou
return ds.BuildResponse(result.(*simplejson.Json)) return ds.BuildResponse(result.(*simplejson.Json))
} }
// 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) {
dsInfo := tsdbReq.GetDatasource()
auth, err := ds.loginWithDs(ctx, dsInfo)
if err != nil {
return nil, err
}
ds.authToken = auth
response, err := ds.zabbixAPIRequest(ctx, dsInfo.GetUrl(), "apiinfo.version", map[string]interface{}{}, "")
if err != nil {
ds.logger.Debug("TestConnection", "error", err)
return nil, err
}
resultByte, _ := response.MarshalJSON()
ds.logger.Debug("TestConnection", "result", string(resultByte))
testResponse := connectionTestResponse{
ZabbixVersion: response.MustString(),
}
responseJSON, err := json.Marshal(testResponse)
if err != nil {
return nil, err
}
responseSimpleJSON, err := simplejson.NewJson(responseJSON)
if err != nil {
return nil, err
}
return ds.BuildResponse(responseSimpleJSON)
}
// BuildResponse transforms a Zabbix API response to a DatasourceResponse // BuildResponse transforms a Zabbix API response to a DatasourceResponse
func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource.DatasourceResponse, error) { func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource.DatasourceResponse, error) {
resultByte, err := result.MarshalJSON() resultByte, err := result.MarshalJSON()
@@ -130,7 +166,7 @@ func (ds *ZabbixDatasource) BuildResponse(result *simplejson.Json) (*datasource.
} }
// ZabbixRequest checks authentication and makes a request to the Zabbix API // ZabbixRequest checks authentication and makes a request to the Zabbix API
func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params *simplejson.Json) (*simplejson.Json, error) { func (ds *ZabbixDatasource) ZabbixRequest(ctx context.Context, dsInfo *datasource.DatasourceInfo, method string, params map[string]interface{}) (*simplejson.Json, error) {
zabbixURL := dsInfo.GetUrl() zabbixURL := dsInfo.GetUrl()
// Authenticate first // Authenticate first
@@ -170,17 +206,12 @@ func (ds *ZabbixDatasource) loginWithDs(ctx context.Context, dsInfo *datasource.
return auth, nil return auth, nil
} }
func (ds *ZabbixDatasource) login(ctx context.Context, apiUrl string, username string, password string) (string, error) { func (ds *ZabbixDatasource) login(ctx context.Context, apiURL string, username string, password string) (string, error) {
params := map[string]interface{}{ params := map[string]interface{}{
"user": username, "user": username,
"password": password, "password": password,
} }
paramsJson, err := json.Marshal(params) auth, err := ds.zabbixAPIRequest(ctx, apiURL, "user.login", params, "")
if err != nil {
return "", err
}
data, _ := simplejson.NewJson(paramsJson)
auth, err := ds.zabbixAPIRequest(ctx, apiUrl, "user.login", data, "")
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -188,8 +219,8 @@ func (ds *ZabbixDatasource) login(ctx context.Context, apiUrl string, username s
return auth.MustString(), nil return auth.MustString(), nil
} }
func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiUrl string, method string, params *simplejson.Json, auth string) (*simplejson.Json, error) { func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiURL string, method string, params map[string]interface{}, auth string) (*simplejson.Json, error) {
zabbixUrl, err := url.Parse(apiUrl) zabbixURL, err := url.Parse(apiURL)
// TODO: inject auth token (obtain from 'user.login' first) // TODO: inject auth token (obtain from 'user.login' first)
apiRequest := map[string]interface{}{ apiRequest := map[string]interface{}{
@@ -217,7 +248,7 @@ func (ds *ZabbixDatasource) zabbixAPIRequest(ctx context.Context, apiUrl string,
req := &http.Request{ req := &http.Request{
Method: "POST", Method: "POST",
URL: zabbixUrl, URL: zabbixURL,
Header: map[string][]string{ Header: map[string][]string{
"Content-Type": {"application/json"}, "Content-Type": {"application/json"},
}, },

View File

@@ -9,13 +9,35 @@ import dataProcessor from './dataProcessor';
import responseHandler from './responseHandler'; import responseHandler from './responseHandler';
import { Zabbix } from './zabbix/zabbix'; import { Zabbix } from './zabbix/zabbix';
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore';
import {
PluginMeta,
DataSourceApi,
DataSourceInstanceSettings,
DataQueryError,
DataQueryRequest,
DataQueryResponse,
AnnotationQueryRequest,
MetricFindValue,
} from '@grafana/ui';
import { BackendSrv, DataSourceSrv } from '@grafana/runtime';
import { ZabbixAlertingService } from './zabbixAlerting.service'
import { ZabbixConnectionTestQuery, ZabbixMetricsQuery, ZabbixConnectionInfo, TemplateSrv } from './types'
const DEFAULT_ZABBIX_VERSION = 3; const DEFAULT_ZABBIX_VERSION = 3
export class ZabbixDatasource { export class ZabbixDatasource extends DataSourceApi {
/** @ngInject */ /**
* @ngInject
* @param {DataSourceInstanceSettings} instanceSettings
* @param {TemplateSrv} templateSrv
* @param {BackendSrv} backendSrv
* @param {DataSourceSrv} datasourceSrv
* @param {ZabbixAlertingService} zabbixAlertingSrv
*/
constructor(instanceSettings, templateSrv, backendSrv, datasourceSrv, zabbixAlertingSrv) { constructor(instanceSettings, templateSrv, backendSrv, datasourceSrv, zabbixAlertingSrv) {
super(instanceSettings)
this.type = 'zabbix'
this.templateSrv = templateSrv; this.templateSrv = templateSrv;
this.backendSrv = backendSrv; this.backendSrv = backendSrv;
this.zabbixAlertingSrv = zabbixAlertingSrv; this.zabbixAlertingSrv = zabbixAlertingSrv;
@@ -185,11 +207,26 @@ export class ZabbixDatasource {
tsdbRequestData.to = options.range.to.valueOf().toString(); tsdbRequestData.to = options.range.to.valueOf().toString();
} }
return this.backendSrv.datasourceRequest({ return this.backendSrv.post('/api/tsdb/query', tsdbRequestData);
url: '/api/tsdb/query', }
method: 'POST',
data: tsdbRequestData /**
}); * @returns {Promise<ZabbixConnectionInfo>}
*/
doTSDBConnectionTest() {
/**
* @type {{ queries: ZabbixConnectionTestQuery[] }}
*/
const tsdbRequestData = {
queries: [
{
datasourceId: this.datasourceId,
queryType: 'connectionTest'
}
]
};
return this.backendSrv.post('/api/tsdb/query', tsdbRequestData);
} }
/** /**
@@ -386,8 +423,7 @@ export class ZabbixDatasource {
* Test connection to Zabbix API and external history DB. * Test connection to Zabbix API and external history DB.
*/ */
testDatasource() { testDatasource() {
return this.zabbix.testDataSource() return this.doTSDBConnectionTest().then(result => {
.then(result => {
const { zabbixVersion, dbConnectorStatus } = result; const { zabbixVersion, dbConnectorStatus } = result;
let message = `Zabbix API version: ${zabbixVersion}`; let message = `Zabbix API version: ${zabbixVersion}`;
if (dbConnectorStatus) { if (dbConnectorStatus) {
@@ -398,8 +434,7 @@ export class ZabbixDatasource {
title: "Success", title: "Success",
message: message message: message
}; };
}) }).catch(error => {
.catch(error => {
if (error instanceof ZabbixAPIError) { if (error instanceof ZabbixAPIError) {
return { return {
status: "error", status: "error",

View File

@@ -1,4 +1,5 @@
import _ from 'lodash'; import _ from 'lodash';
import { ZabbixMetricsQuery } from './types';
/** /**
* Query format migration. * Query format migration.
@@ -19,7 +20,7 @@ export function isGrafana2target(target) {
} }
} }
export function migrateFrom2To3version(target) { export function migrateFrom2To3version(target: ZabbixMetricsQuery) {
target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name; target.group.filter = target.group.name === "*" ? "/.*/" : target.group.name;
target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name; target.host.filter = target.host.name === "*" ? convertToRegex(target.hostFilter) : target.host.name;
target.application.filter = target.application.name === "*" ? "" : target.application.name; target.application.filter = target.application.name === "*" ? "" : target.application.name;

View File

@@ -1,4 +1,4 @@
import { loadPluginCss } from 'grafana/app/plugins/sdk'; import { loadPluginCss } from '@grafana/runtime';
import { ZabbixDatasource } from './datasource'; import { ZabbixDatasource } from './datasource';
import { ZabbixQueryController } from './query.controller'; import { ZabbixQueryController } from './query.controller';
import { ZabbixDSConfigController } from './config.controller'; import { ZabbixDSConfigController } from './config.controller';

View File

@@ -0,0 +1,35 @@
import { DataQuery } from '@grafana/ui';
export interface ZabbixConnectionInfo {
zabbixVersion: string;
dbConnectorStatus: {
dsType: string;
dsName: string;
};
}
export interface ZabbixConnectionTestQuery {
datasourceId: number;
queryType: string;
}
export interface ZabbixMetricsQuery extends DataQuery {
triggers: { minSeverity: string; acknowledged: boolean; count: number; };
queryType: string;
datasourceId: number;
functions: { name: string; params: any; def: { name: string; params: any; } }[];
options: any;
textFilter: string;
mode: number;
itemids: number[];
useCaptureGroups: boolean;
group: { filter: string; name: string; };
host: { filter: string; name: string; };
hostFilter: string;
application: { filter: string; name: string; };
item: { filter: string; name: string; };
itemFilter: string;
}
export { TemplateSrv } from 'grafana/app/features/templating/template_srv';
export { DashboardSrv } from 'grafana/app/features/dashboard/dashboard_srv';

View File

@@ -18,6 +18,14 @@ jest.mock('angular', () => {
}; };
}, {virtual: true}); }, {virtual: true});
jest.mock('grafana/app/features/templating/template_srv', () => {
return {};
}, {virtual: true});
jest.mock('grafana/app/features/dashboard/dashboard_srv', () => {
return {};
}, {virtual: true});
jest.mock('grafana/app/core/core_module', () => { jest.mock('grafana/app/core/core_module', () => {
return { return {
directive: function() {}, directive: function() {},
@@ -28,7 +36,6 @@ let mockPanelCtrl = PanelCtrl;
jest.mock('grafana/app/plugins/sdk', () => { jest.mock('grafana/app/plugins/sdk', () => {
return { return {
QueryCtrl: null, QueryCtrl: null,
loadPluginCss: () => {},
PanelCtrl: mockPanelCtrl PanelCtrl: mockPanelCtrl
}; };
}, {virtual: true}); }, {virtual: true});
@@ -74,9 +81,9 @@ jest.mock('grafana/app/core/config', () => {
jest.mock('jquery', () => 'module not found', {virtual: true}); jest.mock('jquery', () => 'module not found', {virtual: true});
jest.mock('@grafana/ui', () => { jest.mock('@grafana/ui');
return {};
}, {virtual: true}); jest.mock('@grafana/runtime');
// Required for loading angularjs // Required for loading angularjs
let dom = new JSDOM('<html><head><script></script></head><body></body></html>'); let dom = new JSDOM('<html><head><script></script></head><body></body></html>');