Public source validation / validate (push) Failing after 3m8s
258 lines
9.0 KiB
Go
258 lines
9.0 KiB
Go
package metricquery
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/auth"
|
|
"github.com/itworx/pulse/internal/metriccatalog"
|
|
"github.com/itworx/pulse/internal/prometheus"
|
|
"github.com/itworx/pulse/internal/queryplan"
|
|
)
|
|
|
|
type fakeSource struct {
|
|
mu sync.Mutex
|
|
rangeCalls int
|
|
instantCalls int
|
|
started chan struct{}
|
|
release chan struct{}
|
|
result prometheus.QueryResult
|
|
err error
|
|
}
|
|
|
|
func (f *fakeSource) Query(ctx context.Context, _ string, _ *time.Time) (prometheus.QueryResult, error) {
|
|
f.mu.Lock()
|
|
f.instantCalls++
|
|
f.mu.Unlock()
|
|
return f.result, f.err
|
|
}
|
|
func (f *fakeSource) QueryRange(ctx context.Context, _ string, _, _ time.Time, _ time.Duration) (prometheus.QueryResult, error) {
|
|
f.mu.Lock()
|
|
f.rangeCalls++
|
|
started := f.started
|
|
release := f.release
|
|
f.mu.Unlock()
|
|
if started != nil {
|
|
select {
|
|
case started <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
if release != nil {
|
|
select {
|
|
case <-release:
|
|
case <-ctx.Done():
|
|
return prometheus.QueryResult{}, ctx.Err()
|
|
}
|
|
}
|
|
return f.result, f.err
|
|
}
|
|
func (f *fakeSource) calls() int { f.mu.Lock(); defer f.mu.Unlock(); return f.rangeCalls }
|
|
func testService(t *testing.T, source Source, cache *Cache) *Service {
|
|
t.Helper()
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return NewService(queryplan.NewPlanner(registry, queryplan.Limits{}), source, cache)
|
|
}
|
|
func testRequest() queryplan.Request {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
return queryplan.Request{Metric: "container.cpu.utilization", Scope: map[string]string{"containerId": "media_server"}, Range: queryplan.Range{From: now.Add(-time.Hour), To: now, StepSeconds: 60}, GroupBy: []string{"container"}}
|
|
}
|
|
func userContext(subject string) context.Context {
|
|
return auth.WithPrincipal(context.Background(), auth.Principal{Subject: subject, Role: auth.RoleViewer})
|
|
}
|
|
|
|
func TestServiceDeduplicatesConcurrentEquivalentQueriesAndCachesBySubject(t *testing.T) {
|
|
source := &fakeSource{started: make(chan struct{}, 1), release: make(chan struct{}), result: prometheus.QueryResult{Status: "success", Data: []byte(`{"resultType":"matrix","result":[]}`)}}
|
|
service := testService(t, source, NewCache(8, 1<<20))
|
|
firstDone := make(chan struct{})
|
|
var firstErr error
|
|
go func() { _, firstErr = service.ExecuteRange(userContext("alice"), testRequest()); close(firstDone) }()
|
|
<-source.started
|
|
equivalent := testRequest()
|
|
equivalent.GroupBy = []string{"container"}
|
|
secondDone := make(chan struct{})
|
|
var second Response
|
|
go func() { second, _ = service.ExecuteRange(userContext("alice"), equivalent); close(secondDone) }()
|
|
close(source.release)
|
|
<-firstDone
|
|
<-secondDone
|
|
if firstErr != nil {
|
|
t.Fatal(firstErr)
|
|
}
|
|
if source.calls() != 1 {
|
|
t.Fatalf("source calls=%d", source.calls())
|
|
}
|
|
if second.CacheHit != true {
|
|
t.Fatal("deduplicated waiter was not marked cache hit")
|
|
}
|
|
cached, err := service.ExecuteRange(userContext("alice"), testRequest())
|
|
if err != nil || !cached.CacheHit || source.calls() != 1 {
|
|
t.Fatalf("cache=%+v err=%v calls=%d", cached, err, source.calls())
|
|
}
|
|
_, err = service.ExecuteRange(userContext("bob"), testRequest())
|
|
if err != nil || source.calls() != 2 {
|
|
t.Fatalf("subject cache isolation err=%v calls=%d", err, source.calls())
|
|
}
|
|
}
|
|
|
|
func TestServiceMapsSourceErrorsAndDoesNotCacheFailures(t *testing.T) {
|
|
source := &fakeSource{err: errors.New("upstream private detail")}
|
|
service := testService(t, source, NewCache(8, 1<<20))
|
|
_, err := service.ExecuteRange(userContext("alice"), testRequest())
|
|
if err == nil || !errors.Is(err, ErrSourceUnavailable) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
if source.calls() != 1 {
|
|
t.Fatal("unexpected calls")
|
|
}
|
|
_, err = service.ExecuteRange(userContext("alice"), testRequest())
|
|
if err == nil || source.calls() != 2 {
|
|
t.Fatalf("failure was cached err=%v calls=%d", err, source.calls())
|
|
}
|
|
}
|
|
|
|
func TestServiceFollowerHonorsCancellation(t *testing.T) {
|
|
source := &fakeSource{started: make(chan struct{}, 1), release: make(chan struct{}), result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}}
|
|
service := testService(t, source, nil)
|
|
go func() { _, _ = service.ExecuteRange(userContext("alice"), testRequest()) }()
|
|
<-source.started
|
|
ctx, cancel := context.WithCancel(userContext("alice"))
|
|
cancel()
|
|
if _, err := service.ExecuteRange(ctx, testRequest()); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
close(source.release)
|
|
}
|
|
|
|
func TestServiceInstantReturnsProvenanceAndFreshness(t *testing.T) {
|
|
source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{"resultType":"vector","result":[]`)}}
|
|
service := testService(t, source, nil)
|
|
response, err := service.ExecuteInstant(userContext("alice"), InstantRequest{Metric: "host.cpu.utilization", Scope: map[string]string{"serverId": "server-1"}, At: time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if response.Provenance.Source != "prometheus" || response.Provenance.CatalogVersion == "" || response.Freshness != "fresh" || source.instantCalls != 1 {
|
|
t.Fatalf("response=%+v calls=%d", response, source.instantCalls)
|
|
}
|
|
}
|
|
|
|
func TestClassifyFreshnessNeverReportsMissingOrStaleAsFresh(t *testing.T) {
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition, ok := registry.Find("container.cpu.utilization")
|
|
if !ok {
|
|
t.Fatal("seed metric is missing")
|
|
}
|
|
window := FreshnessWindow(definition)
|
|
if window != 30*time.Second {
|
|
t.Fatalf("window=%s", window)
|
|
}
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
if state := ClassifyFreshness(now.Add(-window), now, window); state != FreshnessFresh {
|
|
t.Fatalf("state=%s", state)
|
|
}
|
|
if state := ClassifyFreshness(now.Add(-window-time.Second), now, window); state != FreshnessStale {
|
|
t.Fatalf("state=%s", state)
|
|
}
|
|
if state := ClassifyFreshness(time.Time{}, now, window); state != FreshnessUnknown {
|
|
t.Fatalf("state=%s", state)
|
|
}
|
|
if state := ClassifyFreshness(now, now, 0); state != FreshnessUnknown {
|
|
t.Fatalf("state=%s", state)
|
|
}
|
|
}
|
|
|
|
func TestCacheIsBoundedByEntriesAndBytes(t *testing.T) {
|
|
cache := NewCache(1, 1024)
|
|
response := Response{Data: []byte(`{"large":"payload"}`)}
|
|
cache.Put("a", response, time.Minute)
|
|
cache.Put("b", response, time.Minute)
|
|
if cache.Len() != 1 || cache.Bytes() <= 0 {
|
|
t.Fatalf("len=%d bytes=%d", cache.Len(), cache.Bytes())
|
|
}
|
|
}
|
|
|
|
func operatorContext(subject string) context.Context {
|
|
return auth.WithPrincipal(context.Background(), auth.Principal{Subject: subject, Role: auth.RoleOperator})
|
|
}
|
|
|
|
func TestInspectorIsPermissionedAndWarningsAreRedacted(t *testing.T) {
|
|
source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte("{}"), Warnings: []string{"authorization=Bearer abc123"}}}
|
|
service := testService(t, source, NewCache(8, 1<<20))
|
|
viewer, err := service.ExecuteRange(userContext("viewer"), testRequest())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if viewer.Inspector != nil {
|
|
t.Fatal("viewer received query inspector")
|
|
}
|
|
if len(viewer.Warnings) != 1 || strings.Contains(viewer.Warnings[0], "abc123") {
|
|
t.Fatalf("warnings leaked: %#v", viewer.Warnings)
|
|
}
|
|
operator, err := service.ExecuteRange(operatorContext("operator"), testRequest())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if operator.Inspector == nil || operator.Inspector.SemanticMetric != "container.cpu.utilization" || operator.Inspector.GeneratedQuery == "" {
|
|
t.Fatalf("inspector missing: %+v", operator.Inspector)
|
|
}
|
|
if operator.Inspector.Cost.Series < 1 || operator.Inspector.Cost.Points < 1 {
|
|
t.Fatalf("unexpected cost: %+v", operator.Inspector.Cost)
|
|
}
|
|
if strings.Contains(operator.Inspector.GeneratedQuery, "abc123") || strings.Contains(strings.Join(operator.Warnings, " "), "abc123") {
|
|
t.Fatal("secret fixture leaked")
|
|
}
|
|
}
|
|
|
|
func BenchmarkServiceCacheHit(b *testing.B) {
|
|
source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}}
|
|
registry, err := metriccatalog.DefaultRegistry()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
service := NewService(queryplan.NewPlanner(registry, queryplan.Limits{}), source, NewCache(8, 1<<20))
|
|
ctx := userContext("benchmark")
|
|
request := testRequest()
|
|
if _, err := service.ExecuteRange(ctx, request); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if _, err := service.ExecuteRange(ctx, request); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServiceCacheHitP95Budget(t *testing.T) {
|
|
source := &fakeSource{result: prometheus.QueryResult{Status: "success", Data: []byte(`{}`)}}
|
|
service := testService(t, source, NewCache(8, 1<<20))
|
|
ctx := userContext("p95")
|
|
request := testRequest()
|
|
if _, err := service.ExecuteRange(ctx, request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
durations := make([]time.Duration, 200)
|
|
for i := range durations {
|
|
started := time.Now()
|
|
if _, err := service.ExecuteRange(ctx, request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
durations[i] = time.Since(started)
|
|
}
|
|
sort.Slice(durations, func(i, j int) bool { return durations[i] < durations[j] })
|
|
if p95 := durations[189]; p95 > 750*time.Millisecond {
|
|
t.Fatalf("local cache p95=%s exceeds 750ms budget", p95)
|
|
}
|
|
}
|