Detect and alert on stuck jobs
CI / Test (rest) (pull_request) Successful in 39s
CI / Test (store) (pull_request) Successful in 40s
CI / Lint (pull_request) Failing after 11s
CI / Build (linux/amd64) (pull_request) Successful in 8s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Build (windows/amd64) (pull_request) Successful in 25s
CI / Test (server-http) (pull_request) Successful in 1m39s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m16s
CI / Test (rest) (pull_request) Successful in 39s
CI / Test (store) (pull_request) Successful in 40s
CI / Lint (pull_request) Failing after 11s
CI / Build (linux/amd64) (pull_request) Successful in 8s
CI / Build (linux/arm64) (pull_request) Successful in 7s
CI / Build (windows/amd64) (pull_request) Successful in 25s
CI / Test (server-http) (pull_request) Successful in 1m39s
e2e / Playwright vs docker-compose (pull_request) Successful in 1m16s
This commit is contained in:
@@ -3,6 +3,7 @@ package alert
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -69,6 +70,65 @@ func TestEngineBackupFailedRaisesThenResolves(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineStuckJobRaisesDeduplicatesAndResolves(t *testing.T) {
|
||||
t.Parallel()
|
||||
eng, st, hostID := setupEngine(t)
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
started := now.Add(-7 * time.Hour)
|
||||
if err := st.CreateJob(ctx, store.Job{ID: "stuck-job", HostID: hostID, Kind: "prune", ActorKind: "user", CreatedAt: started}); err != nil {
|
||||
t.Fatalf("create job: %v", err)
|
||||
}
|
||||
if err := st.MarkJobStarted(ctx, "stuck-job", started); err != nil {
|
||||
t.Fatalf("start job: %v", err)
|
||||
}
|
||||
|
||||
eng.evaluateStuckJobs(ctx, now)
|
||||
eng.evaluateStuckJobs(ctx, now.Add(time.Minute))
|
||||
open, err := st.ListAlerts(ctx, store.AlertFilter{Status: "open", HostID: hostID})
|
||||
if err != nil {
|
||||
t.Fatalf("list alerts: %v", err)
|
||||
}
|
||||
if len(open) != 1 || open[0].Kind != KindJobStuck || open[0].DedupKey != "stuck-job" {
|
||||
t.Fatalf("expected one deduplicated stuck alert, got %+v", open)
|
||||
}
|
||||
if !strings.Contains(open[0].Message, started.Format(time.RFC3339)) {
|
||||
t.Errorf("alert does not identify start/activity time: %q", open[0].Message)
|
||||
}
|
||||
|
||||
if err := st.MarkJobFinished(ctx, "stuck-job", "succeeded", 0, nil, "", now); err != nil {
|
||||
t.Fatalf("finish job: %v", err)
|
||||
}
|
||||
eng.evaluateStuckJobs(ctx, now.Add(2*time.Minute))
|
||||
open, _ = st.ListAlerts(ctx, store.AlertFilter{Status: "open", HostID: hostID})
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("expected terminal job alert resolved, got %+v", open)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineStuckJobUsesLatestLogActivity(t *testing.T) {
|
||||
t.Parallel()
|
||||
eng, st, hostID := setupEngine(t)
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
started := now.Add(-7 * time.Hour)
|
||||
if err := st.CreateJob(ctx, store.Job{ID: "active-job", HostID: hostID, Kind: "forget", ActorKind: "user", CreatedAt: started}); err != nil {
|
||||
t.Fatalf("create job: %v", err)
|
||||
}
|
||||
if err := st.MarkJobStarted(ctx, "active-job", started); err != nil {
|
||||
t.Fatalf("start job: %v", err)
|
||||
}
|
||||
if err := st.AppendJobLog(ctx, "active-job", 1, now.Add(-time.Hour), "stdout", "active"); err != nil {
|
||||
t.Fatalf("append log: %v", err)
|
||||
}
|
||||
|
||||
eng.evaluateStuckJobs(ctx, now)
|
||||
open, _ := st.ListAlerts(ctx, store.AlertFilter{Status: "open", HostID: hostID})
|
||||
if len(open) != 0 {
|
||||
t.Fatalf("recent activity should suppress stuck alert, got %+v", open)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineCheckFailedSeverityCritical(t *testing.T) {
|
||||
t.Parallel()
|
||||
eng, st, hostID := setupEngine(t)
|
||||
|
||||
Reference in New Issue
Block a user