## Summary Implements configurable query execution timeout controls to prevent poorly optimized or excessive queries from consuming excessive server resources, causing performance degradation, or crashing the Zabbix server. Fixes: https://github.com/grafana/oss-big-tent-squad/issues/127 ## Problem Previously, the plugin only had an HTTP connection timeout (`timeout`) that controlled individual API request timeouts. However, a complete query execution could involve multiple API calls and run indefinitely if not properly controlled, potentially causing resource exhaustion. ## Solution Added a new `queryTimeout` setting that enforces a maximum execution time for entire database queries initiated by the plugin. Queries exceeding this limit are automatically terminated with proper error handling and logging. ## Testing 1. Configure a datasource with `queryTimeout` set to a low value (e.g., 5 seconds) 2. Execute a query that would normally take longer than the timeout 3. Verify that: - Query is terminated after the timeout period - Error message indicates timeout occurred - Logs contain timeout warning with query details - Other queries in the same request continue to execute ## Notes - `queryTimeout` is separate from `timeout` (HTTP connection timeout) - `queryTimeout` applies to the entire query execution, which may involve multiple API calls - Default value of 60 seconds ensures reasonable protection while allowing normal queries to complete - Timeout errors are logged with query refId, queryType, timeout duration, and datasourceId for troubleshooting
105 lines
2.7 KiB
Go
105 lines
2.7 KiB
Go
package settings
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/alexanderzobnin/grafana-zabbix/pkg/gtime"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
)
|
|
|
|
// parseTimeoutValue parses a timeout value from various types (string, float64, int64, int)
|
|
// and returns it as int64. If the value is empty or invalid, it returns the default value.
|
|
// The fieldName parameter is used for error messages.
|
|
func parseTimeoutValue(value interface{}, defaultValue int64, fieldName string) (int64, error) {
|
|
switch t := value.(type) {
|
|
case string:
|
|
if t == "" {
|
|
return defaultValue, nil
|
|
}
|
|
timeoutInt, err := strconv.Atoi(t)
|
|
if err != nil {
|
|
return 0, errors.New("failed to parse " + fieldName + ": " + err.Error())
|
|
}
|
|
return int64(timeoutInt), nil
|
|
case float64:
|
|
return int64(t), nil
|
|
case int64:
|
|
return t, nil
|
|
case int:
|
|
return int64(t), nil
|
|
default:
|
|
return defaultValue, nil
|
|
}
|
|
}
|
|
|
|
func ReadZabbixSettings(dsInstanceSettings *backend.DataSourceInstanceSettings) (*ZabbixDatasourceSettings, error) {
|
|
zabbixSettingsDTO := &ZabbixDatasourceSettingsDTO{}
|
|
|
|
err := json.Unmarshal(dsInstanceSettings.JSONData, &zabbixSettingsDTO)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if zabbixSettingsDTO.AuthType == "" {
|
|
zabbixSettingsDTO.AuthType = AuthTypeUserLogin
|
|
}
|
|
|
|
if zabbixSettingsDTO.TrendsFrom == "" {
|
|
zabbixSettingsDTO.TrendsFrom = "7d"
|
|
}
|
|
if zabbixSettingsDTO.TrendsRange == "" {
|
|
zabbixSettingsDTO.TrendsRange = "4d"
|
|
}
|
|
if zabbixSettingsDTO.CacheTTL == "" {
|
|
zabbixSettingsDTO.CacheTTL = "1h"
|
|
}
|
|
|
|
trendsFrom, err := gtime.ParseInterval(zabbixSettingsDTO.TrendsFrom)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
trendsRange, err := gtime.ParseInterval(zabbixSettingsDTO.TrendsRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cacheTTL, err := gtime.ParseInterval(zabbixSettingsDTO.CacheTTL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
timeout, err := parseTimeoutValue(zabbixSettingsDTO.Timeout, 30, "timeout")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
queryTimeout, err := parseTimeoutValue(zabbixSettingsDTO.QueryTimeout, 60, "queryTimeout")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Default to 60 seconds if queryTimeout is 0 or negative
|
|
if queryTimeout <= 0 {
|
|
queryTimeout = 60
|
|
}
|
|
|
|
zabbixSettings := &ZabbixDatasourceSettings{
|
|
AuthType: zabbixSettingsDTO.AuthType,
|
|
Trends: zabbixSettingsDTO.Trends,
|
|
TrendsFrom: trendsFrom,
|
|
TrendsRange: trendsRange,
|
|
CacheTTL: cacheTTL,
|
|
Timeout: time.Duration(timeout) * time.Second,
|
|
QueryTimeout: time.Duration(queryTimeout) * time.Second,
|
|
DisableDataAlignment: zabbixSettingsDTO.DisableDataAlignment,
|
|
DisableReadOnlyUsersAck: zabbixSettingsDTO.DisableReadOnlyUsersAck,
|
|
}
|
|
|
|
return zabbixSettings, nil
|
|
}
|