diff --git a/nomad/core_sched_test.go b/nomad/core_sched_test.go index 527e94c75..bf1db2eab 100644 --- a/nomad/core_sched_test.go +++ b/nomad/core_sched_test.go @@ -459,106 +459,253 @@ func TestCoreScheduler_NodeGC_Force(t *testing.T) { } } -func TestCoreScheduler_JobGC(t *testing.T) { - tests := []struct { - test, evalStatus, allocStatus string - shouldExist bool - }{ - { - test: "Terminal", - evalStatus: structs.EvalStatusFailed, - allocStatus: structs.AllocDesiredStatusStop, - shouldExist: false, - }, - { - test: "Has Alloc", - evalStatus: structs.EvalStatusFailed, - allocStatus: structs.AllocDesiredStatusStop, - shouldExist: true, - }, - { - test: "Has Eval", - evalStatus: structs.EvalStatusPending, - allocStatus: structs.AllocDesiredStatusStop, - shouldExist: true, - }, +func TestCoreScheduler_JobGC_OutstandingEvals(t *testing.T) { + s1 := testServer(t, nil) + defer s1.Shutdown() + testutil.WaitForLeader(t, s1.RPC) + + // Insert job. + state := s1.fsm.State() + job := mock.Job() + job.Type = structs.JobTypeBatch + job.Status = structs.JobStatusDead + err := state.UpsertJob(1000, job) + if err != nil { + t.Fatalf("err: %v", err) } - for _, test := range tests { - s1 := testServer(t, nil) - defer s1.Shutdown() - testutil.WaitForLeader(t, s1.RPC) + // Insert two evals, one terminal and one not + eval := mock.Eval() + eval.JobID = job.ID + eval.Status = structs.EvalStatusComplete - // Insert job. - state := s1.fsm.State() - job := mock.Job() - job.Type = structs.JobTypeBatch - err := state.UpsertJob(1000, job) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + eval2 := mock.Eval() + eval2.JobID = job.ID + eval2.Status = structs.EvalStatusPending + err = state.UpsertEvals(1001, []*structs.Evaluation{eval, eval2}) + if err != nil { + t.Fatalf("err: %v", err) + } - // Insert eval - eval := mock.Eval() - eval.JobID = job.ID - eval.Status = test.evalStatus - err = state.UpsertEvals(1001, []*structs.Evaluation{eval}) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + // Update the time tables to make this work + tt := s1.fsm.TimeTable() + tt.Witness(2000, time.Now().UTC().Add(-1*s1.config.JobGCThreshold)) - // Insert alloc - alloc := mock.Alloc() - alloc.JobID = job.ID - alloc.EvalID = eval.ID - alloc.DesiredStatus = test.allocStatus - err = state.UpsertAllocs(1002, []*structs.Allocation{alloc}) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + // Create a core scheduler + snap, err := state.Snapshot() + if err != nil { + t.Fatalf("err: %v", err) + } + core := NewCoreScheduler(s1, snap) - // Update the time tables to make this work - tt := s1.fsm.TimeTable() - tt.Witness(2000, time.Now().UTC().Add(-1*s1.config.JobGCThreshold)) + // Attempt the GC + gc := s1.coreJobEval(structs.CoreJobJobGC, 2000) + err = core.Process(gc) + if err != nil { + t.Fatalf("err: %v", err) + } - // Create a core scheduler - snap, err := state.Snapshot() - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - core := NewCoreScheduler(s1, snap) + // Should still exist + out, err := state.JobByID(job.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out == nil { + t.Fatalf("bad: %v", out) + } - // Attempt the GC - gc := s1.coreJobEval(structs.CoreJobJobGC, 2000) - err = core.Process(gc) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + outE, err := state.EvalByID(eval.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outE == nil { + t.Fatalf("bad: %v", outE) + } - // Should still exist - out, err := state.JobByID(job.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && out == nil) || (!test.shouldExist && out != nil) { - t.Fatalf("test(%s) bad: %v", test.test, out) - } + outE2, err := state.EvalByID(eval2.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outE2 == nil { + t.Fatalf("bad: %v", outE2) + } - outE, err := state.EvalByID(eval.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && outE == nil) || (!test.shouldExist && outE != nil) { - t.Fatalf("test(%s) bad: %v", test.test, out) - } + // Update the second eval to be terminal + eval2.Status = structs.EvalStatusComplete + err = state.UpsertEvals(1003, []*structs.Evaluation{eval2}) + if err != nil { + t.Fatalf("err: %v", err) + } - outA, err := state.AllocByID(alloc.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && outA == nil) || (!test.shouldExist && outA != nil) { - t.Fatalf("test(%s) bad: %v", test.test, outA) - } + // Create a core scheduler + snap, err = state.Snapshot() + if err != nil { + t.Fatalf("err: %v", err) + } + core = NewCoreScheduler(s1, snap) + + // Attempt the GC + gc = s1.coreJobEval(structs.CoreJobJobGC, 2000) + err = core.Process(gc) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should not still exist + out, err = state.JobByID(job.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out != nil { + t.Fatalf("bad: %v", out) + } + + outE, err = state.EvalByID(eval.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outE != nil { + t.Fatalf("bad: %v", outE) + } + + outE2, err = state.EvalByID(eval2.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outE2 != nil { + t.Fatalf("bad: %v", outE2) + } +} + +func TestCoreScheduler_JobGC_OutstandingAllocs(t *testing.T) { + s1 := testServer(t, nil) + defer s1.Shutdown() + testutil.WaitForLeader(t, s1.RPC) + + // Insert job. + state := s1.fsm.State() + job := mock.Job() + job.Type = structs.JobTypeBatch + job.Status = structs.JobStatusDead + err := state.UpsertJob(1000, job) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Insert an eval + eval := mock.Eval() + eval.JobID = job.ID + eval.Status = structs.EvalStatusComplete + err = state.UpsertEvals(1001, []*structs.Evaluation{eval}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Insert two allocs, one terminal and one not + alloc := mock.Alloc() + alloc.JobID = job.ID + alloc.EvalID = eval.ID + alloc.DesiredStatus = structs.AllocDesiredStatusRun + alloc.ClientStatus = structs.AllocClientStatusComplete + + alloc2 := mock.Alloc() + alloc2.JobID = job.ID + alloc2.EvalID = eval.ID + alloc2.DesiredStatus = structs.AllocDesiredStatusRun + alloc2.ClientStatus = structs.AllocClientStatusRunning + + err = state.UpsertAllocs(1002, []*structs.Allocation{alloc, alloc2}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Update the time tables to make this work + tt := s1.fsm.TimeTable() + tt.Witness(2000, time.Now().UTC().Add(-1*s1.config.JobGCThreshold)) + + // Create a core scheduler + snap, err := state.Snapshot() + if err != nil { + t.Fatalf("err: %v", err) + } + core := NewCoreScheduler(s1, snap) + + // Attempt the GC + gc := s1.coreJobEval(structs.CoreJobJobGC, 2000) + err = core.Process(gc) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should still exist + out, err := state.JobByID(job.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out == nil { + t.Fatalf("bad: %v", out) + } + + outA, err := state.AllocByID(alloc.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outA == nil { + t.Fatalf("bad: %v", outA) + } + + outA2, err := state.AllocByID(alloc2.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outA2 == nil { + t.Fatalf("bad: %v", outA2) + } + + // Update the second alloc to be terminal + alloc2.ClientStatus = structs.AllocClientStatusComplete + err = state.UpsertAllocs(1003, []*structs.Allocation{alloc2}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Create a core scheduler + snap, err = state.Snapshot() + if err != nil { + t.Fatalf("err: %v", err) + } + core = NewCoreScheduler(s1, snap) + + // Attempt the GC + gc = s1.coreJobEval(structs.CoreJobJobGC, 2000) + err = core.Process(gc) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Should not still exist + out, err = state.JobByID(job.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out != nil { + t.Fatalf("bad: %v", out) + } + + outA, err = state.AllocByID(alloc.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outA != nil { + t.Fatalf("bad: %v", outA) + } + + outA2, err = state.AllocByID(alloc2.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outA2 != nil { + t.Fatalf("bad: %v", outA2) } } @@ -671,101 +818,58 @@ func TestCoreScheduler_JobGC_OneShot(t *testing.T) { } func TestCoreScheduler_JobGC_Force(t *testing.T) { - tests := []struct { - test, evalStatus, allocStatus string - shouldExist bool - }{ - { - test: "Terminal", - evalStatus: structs.EvalStatusFailed, - allocStatus: structs.AllocDesiredStatusStop, - shouldExist: false, - }, - { - test: "Has Alloc", - evalStatus: structs.EvalStatusFailed, - allocStatus: structs.AllocDesiredStatusRun, - shouldExist: true, - }, - { - test: "Has Eval", - evalStatus: structs.EvalStatusPending, - allocStatus: structs.AllocDesiredStatusStop, - shouldExist: true, - }, + s1 := testServer(t, nil) + defer s1.Shutdown() + testutil.WaitForLeader(t, s1.RPC) + + // Insert job. + state := s1.fsm.State() + job := mock.Job() + job.Type = structs.JobTypeBatch + job.Status = structs.JobStatusDead + err := state.UpsertJob(1000, job) + if err != nil { + t.Fatalf("err: %v", err) } - for _, test := range tests { - s1 := testServer(t, nil) - defer s1.Shutdown() - testutil.WaitForLeader(t, s1.RPC) + // Insert a terminal eval + eval := mock.Eval() + eval.JobID = job.ID + eval.Status = structs.EvalStatusComplete + err = state.UpsertEvals(1001, []*structs.Evaluation{eval}) + if err != nil { + t.Fatalf("err: %v", err) + } - // Insert job. - state := s1.fsm.State() - job := mock.Job() - job.Type = structs.JobTypeBatch - err := state.UpsertJob(1000, job) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + // Create a core scheduler + snap, err := state.Snapshot() + if err != nil { + t.Fatalf("err: %v", err) + } + core := NewCoreScheduler(s1, snap) - // Insert eval - eval := mock.Eval() - eval.JobID = job.ID - eval.Status = test.evalStatus - err = state.UpsertEvals(1001, []*structs.Evaluation{eval}) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + // Attempt the GC + gc := s1.coreJobEval(structs.CoreJobForceGC, 1002) + err = core.Process(gc) + if err != nil { + t.Fatalf("err: %v", err) + } - // Insert alloc - alloc := mock.Alloc() - alloc.JobID = job.ID - alloc.EvalID = eval.ID - alloc.DesiredStatus = test.allocStatus - err = state.UpsertAllocs(1002, []*structs.Allocation{alloc}) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } + // Shouldn't still exist + out, err := state.JobByID(job.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out != nil { + t.Fatalf("bad: %v", out) + } - // Create a core scheduler - snap, err := state.Snapshot() - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - core := NewCoreScheduler(s1, snap) - - // Attempt the GC - gc := s1.coreJobEval(structs.CoreJobForceGC, 1002) - err = core.Process(gc) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - - // Should still exist - out, err := state.JobByID(job.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && out == nil) || (!test.shouldExist && out != nil) { - t.Fatalf("test(%s) bad: %v", test.test, out) - } - - outE, err := state.EvalByID(eval.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && outE == nil) || (!test.shouldExist && outE != nil) { - t.Fatalf("test(%s) bad: %v", test.test, out) - } - - outA, err := state.AllocByID(alloc.ID) - if err != nil { - t.Fatalf("test(%s) err: %v", test.test, err) - } - if (test.shouldExist && outA == nil) || (!test.shouldExist && outA != nil) { - t.Fatalf("test(%s) bad: %v", test.test, outA) - } + outE, err := state.EvalByID(eval.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if outE != nil { + t.Fatalf("bad: %v", outE) } }