Add worker stats to the wizard, artifact pruning and offline-worker removal to the admin console
This commit is contained in:
@@ -3,6 +3,7 @@ package postgres
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@@ -153,3 +154,46 @@ func (r *JobRepo) UpdateStatus(ctx context.Context, id uuid.UUID,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCompletedBefore returns jobs whose completion timestamp is older than
|
||||
// the cutoff (completed and failed both count as finished).
|
||||
func (r *JobRepo) ListCompletedBefore(ctx context.Context, cutoff time.Time) ([]domain.Job, error) {
|
||||
sql, args, err := psql.Select(jobColumns...).From("jobs").
|
||||
Where(sq.NotEq{"completed_at": nil}).
|
||||
Where(sq.Lt{"completed_at": cutoff}).
|
||||
OrderBy("completed_at ASC").
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := conn(ctx, r.pool).Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list completed jobs: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var jobs []domain.Job
|
||||
for rows.Next() {
|
||||
var j domain.Job
|
||||
var status string
|
||||
if err := rows.Scan(
|
||||
&j.ID, &j.Workload, &j.InputURI, &j.Parameters, &status, &j.CreatedAt, &j.CompletedAt,
|
||||
&j.InputArtifactID, &j.ResultArtifactID, &j.ErrorCode, &j.ErrorMessage, &j.ReducerStartedAt,
|
||||
&j.OwnerID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
j.Status = domain.JobStatus(status)
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
return jobs, rows.Err()
|
||||
}
|
||||
|
||||
// Delete removes the job row; tasks, artifacts and task_results cascade.
|
||||
func (r *JobRepo) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
sql, args, err := psql.Delete("jobs").Where(sq.Eq{"id": id}).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = conn(ctx, r.pool).Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -123,3 +123,19 @@ func scanWorker(row pgx.Row) (*domain.Worker, error) {
|
||||
w.TrustLevel = domain.WorkerTrust(trust)
|
||||
return &w, nil
|
||||
}
|
||||
|
||||
// Delete removes a worker from the registry.
|
||||
func (r *WorkerRepo) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
sql, args, err := psql.Delete("workers").Where(sq.Eq{"id": id}).ToSql()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tag, err := conn(ctx, r.pool).Exec(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete worker: %w", err)
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return domain.ErrWorkerNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -88,3 +88,47 @@ func TestWorkerSetTrust(t *testing.T) {
|
||||
t.Errorf("unknown worker trust err = %v, want ErrWorkerNotFound", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobRepoListCompletedBeforeAndDelete(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
ctx := context.Background()
|
||||
repo := NewJobRepo(db)
|
||||
|
||||
old := seedJob(t, db, 2)
|
||||
oldTime := fixedTime().Add(-40 * 24 * time.Hour)
|
||||
if err := repo.UpdateStatus(ctx, old.ID, domain.JobCompleted, &oldTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fresh := seedJob(t, db, 2)
|
||||
freshTime := fixedTime().Add(-2 * time.Hour)
|
||||
if err := repo.UpdateStatus(ctx, fresh.ID, domain.JobCompleted, &freshTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The failing check constraint needs no result artifact for completed; the
|
||||
// UpdateStatus path is fine, but tasks stay pending — irrelevant here.
|
||||
|
||||
list, err := repo.ListCompletedBefore(ctx, fixedTime().Add(-7*24*time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(list) != 1 || list[0].ID != old.ID {
|
||||
t.Errorf("list = %d jobs, want only the old one", len(list))
|
||||
}
|
||||
if err := repo.Delete(ctx, old.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var n int
|
||||
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM jobs WHERE id = ?", old.ID.String()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Error("job row must be gone after Delete")
|
||||
}
|
||||
// Tasks cascaded away with the job.
|
||||
if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM tasks WHERE job_id = ?", old.ID.String()).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Error("tasks must cascade with the job")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package sqlite
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -166,3 +167,30 @@ func nullableUUID(id *uuid.UUID) any {
|
||||
}
|
||||
return id.String()
|
||||
}
|
||||
|
||||
// ListCompletedBefore returns jobs whose completion timestamp is older than
|
||||
// the cutoff (completed and failed both count as finished).
|
||||
func (r *JobRepo) ListCompletedBefore(ctx context.Context, cutoff time.Time) ([]domain.Job, error) {
|
||||
rows, err := conn(ctx, r.db).QueryContext(ctx,
|
||||
"SELECT "+jobColumns+" FROM jobs WHERE completed_at IS NOT NULL AND completed_at < ? ORDER BY completed_at ASC",
|
||||
encodeTime(cutoff))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list completed jobs: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
var jobs []domain.Job
|
||||
for rows.Next() {
|
||||
job, err := scanJob(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobs = append(jobs, *job)
|
||||
}
|
||||
return jobs, rows.Err()
|
||||
}
|
||||
|
||||
// Delete removes the job row; tasks, artifacts and task_results cascade.
|
||||
func (r *JobRepo) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := conn(ctx, r.db).ExecContext(ctx, "DELETE FROM jobs WHERE id = ?", id.String())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -107,3 +107,19 @@ func (r *WorkerRepo) SetTrust(ctx context.Context, id uuid.UUID, trust domain.Wo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a worker from the registry.
|
||||
func (r *WorkerRepo) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
res, err := conn(ctx, r.db).ExecContext(ctx, "DELETE FROM workers WHERE id = ?", id.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected == 0 {
|
||||
return domain.ErrWorkerNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user