diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index e7f54aac3..7141aabc2 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -870,9 +870,13 @@ func (p *Plan) AppendAlloc(alloc *Allocation) { p.NodeAllocation[node] = append(existing, alloc) } +func (p *Plan) AppendFailed(alloc *Allocation) { + p.FailedAllocs = append(p.FailedAllocs, alloc) +} + // IsNoOp checks if this plan would do nothing func (p *Plan) IsNoOp() bool { - return len(p.NodeEvict) == 0 && len(p.NodeAllocation) == 0 + return len(p.NodeEvict) == 0 && len(p.NodeAllocation) == 0 && len(p.FailedAllocs) == 0 } // PlanResult is the result of a plan submitted to the leader. diff --git a/scheduler/generic_sched.go b/scheduler/generic_sched.go index 9b69ac957..b1731dfd8 100644 --- a/scheduler/generic_sched.go +++ b/scheduler/generic_sched.go @@ -196,25 +196,33 @@ func (s *GenericScheduler) computePlacements(job *structs.Job, place []allocTupl for _, missing := range place { option, size := stack.Select(missing.TaskGroup) + var nodeID, status, desc string if option == nil { - s.logger.Printf("[DEBUG] sched: %#v: failed to place alloc %s: %#v", - s.eval, missing.Name, ctx.Metrics()) - continue + status = structs.AllocStatusFailed + desc = "failed to find a node for placement" + } else { + nodeID = option.Node.ID + status = structs.AllocStatusPending } // Create an allocation for this alloc := &structs.Allocation{ - ID: mock.GenerateUUID(), - EvalID: s.eval.ID, - Name: missing.Name, - NodeID: option.Node.ID, - JobID: job.ID, - Job: job, - Resources: size, - Metrics: ctx.Metrics(), - Status: structs.AllocStatusPending, + ID: mock.GenerateUUID(), + EvalID: s.eval.ID, + Name: missing.Name, + NodeID: nodeID, + JobID: job.ID, + Job: job, + Resources: size, + Metrics: ctx.Metrics(), + Status: status, + StatusDescription: desc, + } + if nodeID != "" { + s.plan.AppendAlloc(alloc) + } else { + s.plan.AppendFailed(alloc) } - s.plan.AppendAlloc(alloc) } return nil } diff --git a/scheduler/generic_sched_test.go b/scheduler/generic_sched_test.go index 5d9031669..24f9119e7 100644 --- a/scheduler/generic_sched_test.go +++ b/scheduler/generic_sched_test.go @@ -59,6 +59,49 @@ func TestServiceSched_JobRegister(t *testing.T) { } } +func TestServiceSched_JobRegister_AllocFail(t *testing.T) { + h := NewHarness(t) + + // Create NO nodes + // Create a job + job := mock.Job() + noErr(t, h.State.RegisterJob(h.NextIndex(), job)) + + // Create a mock evaluation to deregister the job + eval := &structs.Evaluation{ + ID: mock.GenerateUUID(), + Priority: job.Priority, + TriggeredBy: structs.EvalTriggerJobRegister, + JobID: job.ID, + } + + // Process the evaluation + err := h.Process(NewServiceScheduler, eval) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Ensure a single plan + if len(h.Plans) != 1 { + t.Fatalf("bad: %#v", h.Plans) + } + plan := h.Plans[0] + + // Ensure the plan failed to alloc + if len(plan.FailedAllocs) != 10 { + t.Fatalf("bad: %#v", plan) + } + + // Lookup the allocations by JobID + out, err := h.State.AllocsByJob(job.ID) + noErr(t, err) + + // Ensure all allocations placed + if len(out) != 10 { + t.Fatalf("bad: %#v", out) + } +} + func TestServiceSched_JobModify(t *testing.T) { h := NewHarness(t) diff --git a/scheduler/scheduler_test.go b/scheduler/scheduler_test.go index 3130a0c43..e931a742b 100644 --- a/scheduler/scheduler_test.go +++ b/scheduler/scheduler_test.go @@ -71,6 +71,7 @@ func (h *Harness) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, State, er for _, allocList := range plan.NodeAllocation { allocs = append(allocs, allocList...) } + allocs = append(allocs, plan.FailedAllocs...) // Apply the full plan err := h.State.UpdateAllocations(index, evicts, allocs)