Refactor API calls
This commit is contained in:
@@ -170,6 +170,12 @@ func GetQueryType(tsdbReq *datasource.DatasourceRequest) (string, error) {
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
func BuildAPIResponse(responseData *interface{}) (*ZabbixAPIResourceResponse, error) {
|
||||
return &ZabbixAPIResourceResponse{
|
||||
Result: *responseData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildResponse transforms a Zabbix API response to a DatasourceResponse
|
||||
func BuildResponse(responseData interface{}) (*datasource.DatasourceResponse, error) {
|
||||
jsonBytes, err := json.Marshal(responseData)
|
||||
|
||||
@@ -115,6 +115,10 @@ type ZabbixAPIRequest struct {
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type ZabbixAPIResourceResponse struct {
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ZabbixAPIRequest) String() string {
|
||||
jsonRequest, _ := json.Marshal(r.Params)
|
||||
return r.Method + string(jsonRequest)
|
||||
|
||||
@@ -32,16 +32,17 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R
|
||||
var reqData ZabbixAPIResourceRequest
|
||||
err = json.Unmarshal(body, &reqData)
|
||||
if err != nil {
|
||||
ds.logger.Error("Cannot unmarshal request", "error", err.Error())
|
||||
WriteError(rw, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
ds.logger.Debug("Invoke Zabbix API call", "ds", pluginCxt.DataSourceInstanceSettings.Name, "method", reqData.Method)
|
||||
apiReq := &ZabbixAPIRequest{Method: reqData.Method, Params: reqData.Params}
|
||||
result, err := dsInstance.ZabbixAPIQuery(req.Context(), apiReq)
|
||||
if err != nil {
|
||||
@@ -52,7 +53,7 @@ func (ds *ZabbixDatasource) zabbixAPIHandler(rw http.ResponseWriter, req *http.R
|
||||
WriteResponse(rw, result)
|
||||
}
|
||||
|
||||
func WriteResponse(rw http.ResponseWriter, result *interface{}) {
|
||||
func WriteResponse(rw http.ResponseWriter, result *ZabbixAPIResourceResponse) {
|
||||
resultJson, err := json.Marshal(*result)
|
||||
if err != nil {
|
||||
WriteError(rw, http.StatusInternalServerError, err)
|
||||
|
||||
@@ -25,7 +25,7 @@ type FunctionCategories struct {
|
||||
}
|
||||
|
||||
// ZabbixAPIQuery handles query requests to Zabbix
|
||||
func (dsInstance *ZabbixDatasourceInstance) ZabbixAPIQuery(ctx context.Context, apiReq *ZabbixAPIRequest) (*interface{}, error) {
|
||||
func (dsInstance *ZabbixDatasourceInstance) ZabbixAPIQuery(ctx context.Context, apiReq *ZabbixAPIRequest) (*ZabbixAPIResourceResponse, error) {
|
||||
var result interface{}
|
||||
var err error
|
||||
var queryExistInCache bool
|
||||
@@ -41,8 +41,7 @@ func (dsInstance *ZabbixDatasourceInstance) ZabbixAPIQuery(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
// return BuildResponse(result)
|
||||
return &result, nil
|
||||
return BuildAPIResponse(&result)
|
||||
}
|
||||
|
||||
func (ds *ZabbixDatasourceInstance) ZabbixAPIQueryOld(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (*datasource.DatasourceResponse, error) {
|
||||
|
||||
@@ -68,6 +68,11 @@ func (ds *ZabbixDatasourceInstance) ZabbixRequest(ctx context.Context, method st
|
||||
var result *simplejson.Json
|
||||
var err error
|
||||
|
||||
// Skip auth for methods that are not required it
|
||||
if method == "apiinfo.version" {
|
||||
return ds.ZabbixAPIRequest(ctx, method, params, ds.authToken)
|
||||
}
|
||||
|
||||
for attempt := 0; attempt <= 3; attempt++ {
|
||||
if ds.authToken == "" {
|
||||
// Authenticate
|
||||
|
||||
@@ -10,19 +10,16 @@ import dataProcessor from './dataProcessor';
|
||||
import responseHandler from './responseHandler';
|
||||
import problemsHandler from './problemsHandler';
|
||||
import { Zabbix } from './zabbix/zabbix';
|
||||
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPICore';
|
||||
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector';
|
||||
import { VariableQueryTypes, ShowProblemTypes } from './types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DataSourceApi, DataSourceInstanceSettings } from '@grafana/data';
|
||||
|
||||
export class ZabbixDatasource extends DataSourceApi {
|
||||
name: string;
|
||||
url: string;
|
||||
basicAuth: any;
|
||||
withCredentials: any;
|
||||
|
||||
username: string;
|
||||
password: string;
|
||||
trends: boolean;
|
||||
trendsFrom: string;
|
||||
trendsRange: string;
|
||||
@@ -56,16 +53,11 @@ export class ZabbixDatasource extends DataSourceApi {
|
||||
// General data source settings
|
||||
this.datasourceId = instanceSettings.id;
|
||||
this.name = instanceSettings.name;
|
||||
this.url = instanceSettings.url;
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.withCredentials = instanceSettings.withCredentials;
|
||||
|
||||
const jsonData = migrations.migrateDSConfig(instanceSettings.jsonData);
|
||||
|
||||
// Zabbix API credentials
|
||||
this.username = jsonData.username;
|
||||
this.password = jsonData.password;
|
||||
|
||||
// Use trends instead history since specified time
|
||||
this.trends = jsonData.trends;
|
||||
this.trendsFrom = jsonData.trendsFrom || '7d';
|
||||
@@ -90,9 +82,6 @@ export class ZabbixDatasource extends DataSourceApi {
|
||||
this.dbConnectionRetentionPolicy = jsonData.dbConnectionRetentionPolicy;
|
||||
|
||||
const zabbixOptions = {
|
||||
url: this.url,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
basicAuth: this.basicAuth,
|
||||
withCredentials: this.withCredentials,
|
||||
cacheTTL: this.cacheTTL,
|
||||
|
||||
@@ -2,10 +2,9 @@ import _ from 'lodash';
|
||||
import semver from 'semver';
|
||||
import kbn from 'grafana/app/core/utils/kbn';
|
||||
import * as utils from '../../../utils';
|
||||
import { ZabbixAPICore } from './zabbixAPICore';
|
||||
import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ACK, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants';
|
||||
import { ZBX_ACK_ACTION_NONE, ZBX_ACK_ACTION_ADD_MESSAGE, MIN_SLA_INTERVAL } from '../../../constants';
|
||||
import { ShowProblemTypes, ZBXProblem } from '../../../types';
|
||||
import { JSONRPCRequestParams } from './types';
|
||||
import { GFHTTPRequest, JSONRPCError } from './types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
const DEFAULT_ZABBIX_VERSION = '3.0.0';
|
||||
@@ -16,39 +15,22 @@ const DEFAULT_ZABBIX_VERSION = '3.0.0';
|
||||
* Wraps API calls and provides high-level methods.
|
||||
*/
|
||||
export class ZabbixAPIConnector {
|
||||
url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
auth: string;
|
||||
backendAPIUrl: string;
|
||||
requestOptions: { basicAuth: any; withCredentials: boolean; };
|
||||
loginPromise: Promise<string>;
|
||||
loginErrorCount: number;
|
||||
maxLoginAttempts: number;
|
||||
zabbixAPICore: ZabbixAPICore;
|
||||
getTrend: (items: any, timeFrom: any, timeTill: any) => Promise<any[]>;
|
||||
version: string;
|
||||
getVersionPromise: Promise<string>;
|
||||
datasourceId: number;
|
||||
|
||||
constructor(api_url: string, username: string, password: string, basicAuth: any, withCredentials: boolean, datasourceId: number) {
|
||||
this.url = api_url;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.auth = '';
|
||||
constructor(basicAuth: any, withCredentials: boolean, datasourceId: number) {
|
||||
this.datasourceId = datasourceId;
|
||||
this.backendAPIUrl = `/api/datasources/${this.datasourceId}/resources/zabbix-api`;
|
||||
|
||||
this.requestOptions = {
|
||||
basicAuth: basicAuth,
|
||||
withCredentials: withCredentials
|
||||
};
|
||||
|
||||
this.datasourceId = datasourceId;
|
||||
|
||||
this.loginPromise = null;
|
||||
this.loginErrorCount = 0;
|
||||
this.maxLoginAttempts = 3;
|
||||
|
||||
this.zabbixAPICore = new ZabbixAPICore();
|
||||
|
||||
this.getTrend = this.getTrend_ZBXNEXT1193;
|
||||
//getTrend = getTrend_30;
|
||||
|
||||
@@ -59,113 +41,42 @@ export class ZabbixAPIConnector {
|
||||
// Core method wrappers //
|
||||
//////////////////////////
|
||||
|
||||
request(method, params) {
|
||||
return this.tsdbRequest(method, params).then(response => {
|
||||
// const result = this.handleTsdbResponse(response);
|
||||
const result = this.handleZabbixAPIResourceResponse(response);
|
||||
|
||||
return result;
|
||||
request(method: string, params?: any) {
|
||||
return this.backendAPIRequest(method, params).then(response => {
|
||||
return response?.data?.result;
|
||||
});
|
||||
}
|
||||
|
||||
tsdbRequest(method, params) {
|
||||
const tsdbRequestData = {
|
||||
queries: [{
|
||||
datasourceId: this.datasourceId,
|
||||
queryType: 'zabbixAPI',
|
||||
target: {
|
||||
method,
|
||||
params,
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
return getBackendSrv().datasourceRequest({
|
||||
url: `/api/datasources/${this.datasourceId}/resources/zabbix-api`,
|
||||
backendAPIRequest(method: string, params: any = {}) {
|
||||
const requestOptions: GFHTTPRequest = {
|
||||
url: this.backendAPIUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: {
|
||||
datasourceId: this.datasourceId,
|
||||
method,
|
||||
params,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// return getBackendSrv().datasourceRequest({
|
||||
// url: '/api/tsdb/query',
|
||||
// method: 'POST',
|
||||
// data: tsdbRequestData
|
||||
// });
|
||||
// Set request options for basic auth
|
||||
if (this.requestOptions.basicAuth || this.requestOptions.withCredentials) {
|
||||
requestOptions.withCredentials = true;
|
||||
}
|
||||
if (this.requestOptions.basicAuth) {
|
||||
requestOptions.headers.Authorization = this.requestOptions.basicAuth;
|
||||
}
|
||||
|
||||
_request(method: string, params: JSONRPCRequestParams): Promise<any> {
|
||||
if (!this.version) {
|
||||
return this.initVersion().then(() => this.request(method, params));
|
||||
}
|
||||
|
||||
return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth)
|
||||
.catch(error => {
|
||||
if (isNotInitialized(error.data)) {
|
||||
// If API not initialized yet (auth is empty), login first
|
||||
return this.loginOnce()
|
||||
.then(() => this.request(method, params));
|
||||
} else if (isNotAuthorized(error.data)) {
|
||||
// Handle auth errors
|
||||
this.loginErrorCount++;
|
||||
if (this.loginErrorCount > this.maxLoginAttempts) {
|
||||
this.loginErrorCount = 0;
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.loginOnce()
|
||||
.then(() => this.request(method, params));
|
||||
}
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleTsdbResponse(response) {
|
||||
if (!response || !response.data || !response.data.results) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.results['zabbixAPI'].meta;
|
||||
}
|
||||
|
||||
handleZabbixAPIResourceResponse(response) {
|
||||
return response?.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* When API unauthenticated or auth token expired each request produce login()
|
||||
* call. But auth token is common to all requests. This function wraps login() method
|
||||
* and call it once. If login() already called just wait for it (return its promise).
|
||||
*/
|
||||
loginOnce(): Promise<string> {
|
||||
if (!this.loginPromise) {
|
||||
this.loginPromise = Promise.resolve(
|
||||
this.login().then(auth => {
|
||||
this.auth = auth;
|
||||
this.loginPromise = null;
|
||||
return auth;
|
||||
})
|
||||
);
|
||||
}
|
||||
return this.loginPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication token.
|
||||
*/
|
||||
login(): Promise<string> {
|
||||
return this.zabbixAPICore.login(this.url, this.username, this.password, this.requestOptions);
|
||||
return getBackendSrv().datasourceRequest(requestOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zabbix API version
|
||||
*/
|
||||
getVersion() {
|
||||
return this.zabbixAPICore.getVersion(this.url, this.requestOptions);
|
||||
return this.request('apiinfo.version');
|
||||
}
|
||||
|
||||
initVersion(): Promise<string> {
|
||||
@@ -730,18 +641,6 @@ function filterTriggersByAcknowledge(triggers, acknowledged) {
|
||||
}
|
||||
}
|
||||
|
||||
function isNotAuthorized(message) {
|
||||
return (
|
||||
message === "Session terminated, re-login, please." ||
|
||||
message === "Not authorised." ||
|
||||
message === "Not authorized."
|
||||
);
|
||||
}
|
||||
|
||||
function isNotInitialized(message) {
|
||||
return message === "Not initialized";
|
||||
}
|
||||
|
||||
function getSLAInterval(intervalMs) {
|
||||
// Too many intervals may cause significant load on the database, so decrease number of resulting points
|
||||
const resolutionRatio = 100;
|
||||
@@ -767,3 +666,22 @@ function buildSLAIntervals(timeRange, interval) {
|
||||
|
||||
return intervals;
|
||||
}
|
||||
|
||||
// Define zabbix API exception type
|
||||
export class ZabbixAPIError {
|
||||
code: number;
|
||||
name: string;
|
||||
data: string;
|
||||
message: string;
|
||||
|
||||
constructor(error: JSONRPCError) {
|
||||
this.code = error.code || null;
|
||||
this.name = error.message || "";
|
||||
this.data = error.data || "";
|
||||
this.message = "Zabbix API Error: " + this.name + " " + this.data;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name + " " + this.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* General Zabbix API methods
|
||||
*/
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { JSONRPCRequest, ZabbixRequestResponse, JSONRPCError, APILoginResponse, GFHTTPRequest, GFRequestOptions } from './types';
|
||||
|
||||
export class ZabbixAPICore {
|
||||
/**
|
||||
* Request data from Zabbix API
|
||||
* @return {object} response.result
|
||||
*/
|
||||
request(api_url: string, method: string, params: any, options: GFRequestOptions, auth?: string) {
|
||||
const requestData: JSONRPCRequest = {
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: 1
|
||||
};
|
||||
|
||||
if (auth === "") {
|
||||
// Reject immediately if not authenticated
|
||||
return Promise.reject(new ZabbixAPIError({data: "Not initialized"}));
|
||||
} else if (auth) {
|
||||
// Set auth parameter only if it needed
|
||||
requestData.auth = auth;
|
||||
}
|
||||
|
||||
const requestOptions: GFHTTPRequest = {
|
||||
method: 'POST',
|
||||
url: api_url,
|
||||
data: requestData,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
// Set request options for basic auth
|
||||
if (options.basicAuth || options.withCredentials) {
|
||||
requestOptions.withCredentials = true;
|
||||
}
|
||||
if (options.basicAuth) {
|
||||
requestOptions.headers.Authorization = options.basicAuth;
|
||||
}
|
||||
|
||||
return this.datasourceRequest(requestOptions);
|
||||
}
|
||||
|
||||
datasourceRequest(requestOptions) {
|
||||
return getBackendSrv().datasourceRequest(requestOptions)
|
||||
.then((response: ZabbixRequestResponse) => {
|
||||
if (!response?.data) {
|
||||
return Promise.reject(new ZabbixAPIError({data: "General Error, no data"}));
|
||||
} else if (response?.data.error) {
|
||||
|
||||
// Handle Zabbix API errors
|
||||
return Promise.reject(new ZabbixAPIError(response.data.error));
|
||||
}
|
||||
|
||||
// Success
|
||||
return response?.data.result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication token.
|
||||
* @return {string} auth token
|
||||
*/
|
||||
login(api_url: string, username: string, password: string, options: GFRequestOptions): Promise<APILoginResponse> {
|
||||
const params = {
|
||||
user: username,
|
||||
password: password
|
||||
};
|
||||
return this.request(api_url, 'user.login', params, options, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Zabbix API version
|
||||
* Matches the version of Zabbix starting from Zabbix 2.0.4
|
||||
*/
|
||||
getVersion(api_url: string, options: GFRequestOptions): Promise<string> {
|
||||
return this.request(api_url, 'apiinfo.version', [], options).catch(err => {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Define zabbix API exception type
|
||||
export class ZabbixAPIError {
|
||||
code: number;
|
||||
name: string;
|
||||
data: string;
|
||||
message: string;
|
||||
|
||||
constructor(error: JSONRPCError) {
|
||||
this.code = error.code || null;
|
||||
this.name = error.message || "";
|
||||
this.data = error.data || "";
|
||||
this.message = "Zabbix API Error: " + this.name + " " + this.data;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name + " " + this.data;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ export interface ZabbixConnector {
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getVersion: () => Promise<string>;
|
||||
login: () => Promise<any>;
|
||||
|
||||
getGroups: (groupFilter?) => any;
|
||||
getHosts: (groupFilter?, hostFilter?) => any;
|
||||
|
||||
@@ -29,7 +29,7 @@ const REQUESTS_TO_CACHE = [
|
||||
|
||||
const REQUESTS_TO_BIND = [
|
||||
'getHistory', 'getTrend', 'getMacros', 'getItemsByIDs', 'getEvents', 'getAlerts', 'getHostAlerts',
|
||||
'getAcknowledges', 'getITService', 'getVersion', 'login', 'acknowledgeEvent', 'getProxies', 'getEventAlerts',
|
||||
'getAcknowledges', 'getITService', 'getVersion', 'acknowledgeEvent', 'getProxies', 'getEventAlerts',
|
||||
'getExtendedEventData'
|
||||
];
|
||||
|
||||
@@ -55,13 +55,9 @@ export class Zabbix implements ZabbixConnector {
|
||||
getExtendedEventData: (eventids) => Promise<any>;
|
||||
getMacros: (hostids: any[]) => Promise<any>;
|
||||
getVersion: () => Promise<string>;
|
||||
login: () => Promise<any>;
|
||||
|
||||
constructor(options) {
|
||||
const {
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
basicAuth,
|
||||
withCredentials,
|
||||
cacheTTL,
|
||||
@@ -81,7 +77,7 @@ export class Zabbix implements ZabbixConnector {
|
||||
};
|
||||
this.cachingProxy = new CachingProxy(cacheOptions);
|
||||
|
||||
this.zabbixAPI = new ZabbixAPIConnector(url, username, password, basicAuth, withCredentials, datasourceId);
|
||||
this.zabbixAPI = new ZabbixAPIConnector(basicAuth, withCredentials, datasourceId);
|
||||
|
||||
this.proxyfyRequests();
|
||||
this.cacheRequests();
|
||||
|
||||
Reference in New Issue
Block a user