## 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.1 KiB
Go
105 lines
2.1 KiB
Go
package settings
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestParseTimeoutValue(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value interface{}
|
|
defaultValue int64
|
|
fieldName string
|
|
want int64
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid string",
|
|
value: "45",
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 45,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "empty string returns default",
|
|
value: "",
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 30,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid string returns error",
|
|
value: "not-a-number",
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 0,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "float64 value",
|
|
value: float64(60),
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 60,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "int64 value",
|
|
value: int64(90),
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 90,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "int value",
|
|
value: int(120),
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 120,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nil returns default",
|
|
value: nil,
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 30,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "unknown type returns default",
|
|
value: []string{"invalid"},
|
|
defaultValue: 60,
|
|
fieldName: "queryTimeout",
|
|
want: 60,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "zero string value",
|
|
value: "0",
|
|
defaultValue: 30,
|
|
fieldName: "timeout",
|
|
want: 0,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := parseTimeoutValue(tt.value, tt.defaultValue, tt.fieldName)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), tt.fieldName)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|