diff --git a/CHANGELOG.md b/CHANGELOG.md index 0756d48d25..6186cf89b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * [FEATURE] Querier: Add timeout classification to classify query timeouts as 4XX (user error) or 5XX (system error) based on phase timing. When enabled, queries that spend most of their time in PromQL evaluation return `422 Unprocessable Entity` instead of `503 Service Unavailable`. #7374 * [FEATURE] Querier: Implement Resource Based Throttling in Querier. #7442 * [FEATURE] Querier: Add resource-based query eviction that automatically cancels the heaviest running query when CPU or heap utilization exceeds configured thresholds. #7488 +* [BUGFIX] Ruler: Register xfunctions (xincrease, xrate, xdelta) in the global parser before loading rule files. #7621 * [ENHANCEMENT] Upgrade prometheus alertmanager version to v0.32.1. #7462 * [ENHANCEMENT] Tenant Federation: Avoid purging the regex resolver LRU cache on user-sync ticks when the set of known users has not changed. #7489 * [ENHANCEMENT] Memberlist: Add `-memberlist.packet-read-timeout`, `-memberlist.max-packet-size`, and `-memberlist.max-concurrent-connections` flags to bound inbound gossip TCP connections, preventing slow-read, OOM, and connection-flood attacks on the gossip port. #7518 diff --git a/pkg/cortex/modules.go b/pkg/cortex/modules.go index aecfed9493..f0ad65d2e7 100644 --- a/pkg/cortex/modules.go +++ b/pkg/cortex/modules.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "log/slog" + "maps" "net/http" "runtime" "runtime/debug" @@ -18,9 +19,11 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/rules" prom_storage "github.com/prometheus/prometheus/storage" "github.com/thanos-io/objstore" + "github.com/thanos-io/promql-engine/execution/parse" "github.com/thanos-io/thanos/pkg/discovery/dns" "github.com/thanos-io/thanos/pkg/querysharding" httpgrpc_server "github.com/weaveworks/common/httpgrpc/server" @@ -661,6 +664,11 @@ func (t *Cortex) initRulerStorage() (serv services.Service, err error) { return } + // Register xfunctions (xincrease, xrate, xdelta) in the global parser + if t.Cfg.Querier.ThanosEngine.EnableXFunctions { + maps.Copy(parser.Functions, parse.XFunctions) + } + t.RulerStorage, err = ruler.NewRuleStore(context.Background(), t.Cfg.RulerStorage, t.OverridesConfig, rules.FileLoader{}, util_log.Logger, prometheus.DefaultRegisterer, t.Cfg.NameValidationScheme) return } diff --git a/pkg/cortex/modules_test.go b/pkg/cortex/modules_test.go index 865658e8b2..2edfa18e55 100644 --- a/pkg/cortex/modules_test.go +++ b/pkg/cortex/modules_test.go @@ -2,6 +2,7 @@ package cortex import ( "context" + "maps" "net/http/httptest" "os" "reflect" @@ -10,6 +11,7 @@ import ( "testing" "github.com/gorilla/mux" + "github.com/prometheus/prometheus/promql/parser" prom_storage "github.com/prometheus/prometheus/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -169,6 +171,66 @@ func (p *myPusher) Push(ctx context.Context, req *cortexpb.WriteRequest) (*corte return nil, nil } +func TestCortex_InitRulerStorage_RegistersXFunctions(t *testing.T) { + tests := map[string]struct { + enableXFunctions bool + expectRegistered bool + }{ + "should register xfunctions when EnableXFunctions is true": { + enableXFunctions: true, + expectRegistered: true, + }, + "should not register xfunctions when EnableXFunctions is false": { + enableXFunctions: false, + expectRegistered: false, + }, + } + + for testName, testData := range tests { + t.Run(testName, func(t *testing.T) { + // Clean up global state after each test + originalFunctions := make(map[string]*parser.Function, len(parser.Functions)) + maps.Copy(originalFunctions, parser.Functions) + defer func() { + // Restore original parser.Functions + for k := range parser.Functions { + if _, ok := originalFunctions[k]; !ok { + delete(parser.Functions, k) + } + } + }() + + cfg := newDefaultConfig() + cfg.Target = []string{"ruler"} + cfg.RulerStorage.Backend = "local" + cfg.RulerStorage.Local.Directory = os.TempDir() + cfg.Querier.ThanosEngine.EnableXFunctions = testData.enableXFunctions + + cortex := &Cortex{ + Server: &server.Server{}, + Cfg: *cfg, + } + + _, err := cortex.initRulerStorage() + require.NoError(t, err) + + _, hasXincrease := parser.Functions["xincrease"] + _, hasXrate := parser.Functions["xrate"] + _, hasXdelta := parser.Functions["xdelta"] + + if testData.expectRegistered { + assert.True(t, hasXincrease, "xincrease should be registered") + assert.True(t, hasXrate, "xrate should be registered") + assert.True(t, hasXdelta, "xdelta should be registered") + } else { + assert.False(t, hasXincrease, "xincrease should not be registered") + assert.False(t, hasXrate, "xrate should not be registered") + assert.False(t, hasXdelta, "xdelta should not be registered") + } + }) + } +} + type myQueryable struct{} func (q *myQueryable) Querier(mint, maxt int64) (prom_storage.Querier, error) {