Files
grafana-zabbix/pkg/settings/settings_test.go
ismail simsek a2f8b6433a Introduce query timeout configuration (#2157)
## 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
2026-01-12 15:30:31 +01:00

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)
}
})
}
}