Add selectAccounts filter and accountIDs param to orchestrator.Run
This commit is contained in:
@@ -69,7 +69,7 @@ func (s *Server) handleRun(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "bad id", http.StatusBadRequest)
|
http.Error(w, "bad id", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runID, err := s.orch.Run(r.Context(), taskID, "manual")
|
runID, err := s.orch.Run(r.Context(), taskID, "manual", nil)
|
||||||
if errors.Is(err, orchestrator.ErrNotTested) {
|
if errors.Is(err, orchestrator.ErrNotTested) {
|
||||||
http.Error(w, "accounts must pass connection tests first", http.StatusConflict)
|
http.Error(w, "accounts must pass connection tests first", http.StatusConflict)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
var ErrNotTested = errors.New("accounts not fully tested")
|
var ErrNotTested = errors.New("accounts not fully tested")
|
||||||
var ErrAlreadyRunning = errors.New("task already running")
|
var ErrAlreadyRunning = errors.New("task already running")
|
||||||
|
var ErrNoAccountsSelected = errors.New("no matching accounts selected")
|
||||||
|
|
||||||
// maxAccountErrors caps how many individual error rows one account records per
|
// maxAccountErrors caps how many individual error rows one account records per
|
||||||
// run, so a corrupt mailbox producing thousands of failures can't bloat the
|
// run, so a corrupt mailbox producing thousands of failures can't bloat the
|
||||||
@@ -99,6 +100,26 @@ func (o *Orchestrator) unregisterCancel(accountID int64) {
|
|||||||
o.mu.Unlock()
|
o.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// selectAccounts narrows accs to those whose ID is in ids, preserving input
|
||||||
|
// order. An empty or nil ids means "all accounts" — the scheduler and the
|
||||||
|
// unfiltered manual run rely on this.
|
||||||
|
func selectAccounts(accs []store.Account, ids []int64) []store.Account {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return accs
|
||||||
|
}
|
||||||
|
want := make(map[int64]struct{}, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
want[id] = struct{}{}
|
||||||
|
}
|
||||||
|
out := make([]store.Account, 0, len(ids))
|
||||||
|
for _, a := range accs {
|
||||||
|
if _, ok := want[a.ID]; ok {
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func gateOK(accs []store.Account) bool {
|
func gateOK(accs []store.Account) bool {
|
||||||
if len(accs) == 0 {
|
if len(accs) == 0 {
|
||||||
return false
|
return false
|
||||||
@@ -173,7 +194,7 @@ func shouldBreak(trigger string, totErr int64) bool {
|
|||||||
return trigger == "scheduled" && totErr > 0
|
return trigger == "scheduled" && totErr > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Orchestrator) Run(ctx context.Context, taskID int64, trigger string) (int64, error) {
|
func (o *Orchestrator) Run(ctx context.Context, taskID int64, trigger string, accountIDs []int64) (int64, error) {
|
||||||
task, err := o.store.GetTask(ctx, taskID)
|
task, err := o.store.GetTask(ctx, taskID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -182,6 +203,12 @@ func (o *Orchestrator) Run(ctx context.Context, taskID int64, trigger string) (i
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
accs = selectAccounts(accs, accountIDs)
|
||||||
|
// A non-empty request that matched nothing is a client error, distinct
|
||||||
|
// from "not tested".
|
||||||
|
if len(accountIDs) > 0 && len(accs) == 0 {
|
||||||
|
return 0, ErrNoAccountsSelected
|
||||||
|
}
|
||||||
if !gateOK(accs) {
|
if !gateOK(accs) {
|
||||||
return 0, ErrNotTested
|
return 0, ErrNotTested
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,23 @@ func TestGateOK(t *testing.T) {
|
|||||||
t.Fatal("empty accounts must fail gate")
|
t.Fatal("empty accounts must fail gate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelectAccounts(t *testing.T) {
|
||||||
|
accs := []store.Account{{ID: 1}, {ID: 2}, {ID: 3}}
|
||||||
|
|
||||||
|
if got := selectAccounts(accs, nil); len(got) != 3 {
|
||||||
|
t.Fatalf("nil ids must return all, got %d", len(got))
|
||||||
|
}
|
||||||
|
if got := selectAccounts(accs, []int64{}); len(got) != 3 {
|
||||||
|
t.Fatalf("empty ids must return all, got %d", len(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
got := selectAccounts(accs, []int64{3, 1})
|
||||||
|
if len(got) != 2 || got[0].ID != 1 || got[1].ID != 3 {
|
||||||
|
t.Fatalf("must keep matching ids in input order, got %+v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := selectAccounts(accs, []int64{99}); len(got) != 0 {
|
||||||
|
t.Fatalf("unknown ids must yield empty, got %d", len(got))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ func (s *Scheduler) tick(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, id := range dueTaskIDs(tasks, time.Now()) {
|
for _, id := range dueTaskIDs(tasks, time.Now()) {
|
||||||
if _, err := s.orch.Run(ctx, id, "scheduled"); err != nil {
|
if _, err := s.orch.Run(ctx, id, "scheduled", nil); err != nil {
|
||||||
// ErrAlreadyRunning / ErrNotTested are expected races/edge cases, not fatal.
|
// ErrAlreadyRunning / ErrNotTested are expected races/edge cases, not fatal.
|
||||||
slog.Info("scheduler: run skipped", "task", id, "err", err)
|
slog.Info("scheduler: run skipped", "task", id, "err", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user