Public source validation / validate (push) Failing after 3m8s
122 lines
4.1 KiB
Go
122 lines
4.1 KiB
Go
package alertcontrol
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/database"
|
|
)
|
|
|
|
func TestPostgreSQLControlsAreExpiringAuditedAndIdempotent(t *testing.T) {
|
|
dsn := os.Getenv("PULSE_TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("PULSE_TEST_DATABASE_URL is not set")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second)
|
|
defer cancel()
|
|
pool, err := database.NewPool(ctx, database.Config{URL: dsn, MaxConns: 8, MinConns: 1})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer pool.Close()
|
|
if err := database.Migrate(ctx, pool); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := Repository{Pool: pool}
|
|
now := time.Now().UTC().Truncate(time.Microsecond)
|
|
silenceID, maintenanceID, expiringID := NewID(), NewID(), NewID()
|
|
t.Cleanup(func() {
|
|
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cleanupCancel()
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM alert_silences WHERE id = ANY($1::uuid[])`, []string{silenceID, expiringID})
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM maintenance_windows WHERE id = $1::uuid`, maintenanceID)
|
|
})
|
|
|
|
silence, err := repo.CreateSilence(ctx, "operator-1", Silence{ID: silenceID, Name: "planned deploy", Reason: "change window", Owner: "operator-1", Matchers: Matcher{Severities: []string{"critical"}}, StartsAt: now.Add(-time.Minute), ExpiresAt: now.Add(time.Hour)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if silence.State != StateActive || silence.Revision != 1 {
|
|
t.Fatalf("unexpected silence: %#v", silence)
|
|
}
|
|
window, err := repo.CreateMaintenance(ctx, "operator-1", MaintenanceWindow{ID: maintenanceID, Name: "maintenance", Reason: "firmware", Selector: Matcher{EntityTypes: []string{"host"}}, StartsAt: now.Add(-time.Minute), EndsAt: now.Add(time.Hour)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if window.State != StateActive {
|
|
t.Fatalf("maintenance state = %s", window.State)
|
|
}
|
|
items, err := repo.ListSilences(ctx, 100, now)
|
|
if err != nil || len(items) != 1 {
|
|
t.Fatalf("list silences: %d, %v", len(items), err)
|
|
}
|
|
|
|
expiring, err := repo.CreateSilence(ctx, "operator-1", Silence{ID: expiringID, Name: "short", Reason: "test expiry", Owner: "operator-1", Matchers: Matcher{}, StartsAt: now.Add(-time.Minute), ExpiresAt: now.Add(-time.Second)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if expiring.State != StateExpired {
|
|
t.Fatalf("expired control before job = %s", expiring.State)
|
|
}
|
|
first, err := repo.Expire(ctx, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := repo.Expire(ctx, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.Silences != 1 || first.MaintenanceWindows != 0 || second != (ExpiryResult{}) {
|
|
t.Fatalf("expiry not idempotent: first=%#v second=%#v", first, second)
|
|
}
|
|
|
|
concurrentID := NewID()
|
|
defer func() {
|
|
_, _ = pool.Exec(context.Background(), `DELETE FROM alert_silences WHERE id=$1::uuid`, concurrentID)
|
|
}()
|
|
if _, err := repo.CreateSilence(ctx, "operator-1", Silence{ID: concurrentID, Name: "concurrent", Reason: "test", Owner: "operator-1", Matchers: Matcher{}, StartsAt: now.Add(-time.Minute), ExpiresAt: now.Add(-time.Second)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var wg sync.WaitGroup
|
|
results := make(chan ExpiryResult, 2)
|
|
errorsCh := make(chan error, 2)
|
|
for i := 0; i < 2; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
result, err := repo.Expire(ctx, now.Add(time.Second))
|
|
if err != nil {
|
|
errorsCh <- err
|
|
return
|
|
}
|
|
results <- result
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
close(results)
|
|
close(errorsCh)
|
|
total := 0
|
|
for result := range results {
|
|
total += result.Silences + result.MaintenanceWindows
|
|
}
|
|
for err := range errorsCh {
|
|
t.Fatal(err)
|
|
}
|
|
if total != 1 {
|
|
t.Fatalf("concurrent expiry count = %d, want 1", total)
|
|
}
|
|
|
|
if _, err := repo.CreateSilence(ctx, "operator-1", Silence{ID: silenceID, Name: "duplicate", Reason: "duplicate", Owner: "operator-1", Matchers: Matcher{}, StartsAt: now, ExpiresAt: now.Add(time.Hour)}); err == nil {
|
|
t.Fatal("duplicate silence should fail")
|
|
}
|
|
if _, err := repo.RevokeSilence(ctx, silenceID, "operator-1", silence.Revision, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := repo.RevokeSilence(ctx, silenceID, "operator-1", silence.Revision+1, now); err == nil {
|
|
t.Fatal("repeated revoke should conflict")
|
|
}
|
|
}
|