From e0bc33a78e5813ea1b0e26be6d0588eccdf11b4e Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Thu, 17 Mar 2016 11:02:59 -0700 Subject: [PATCH 1/2] Allow count zero --- nomad/structs/structs.go | 4 +- scheduler/generic_sched_test.go | 141 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 97ff2c32f15..21153f9f63b 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1344,8 +1344,8 @@ func (tg *TaskGroup) Validate() error { if tg.Name == "" { mErr.Errors = append(mErr.Errors, errors.New("Missing task group name")) } - if tg.Count <= 0 { - mErr.Errors = append(mErr.Errors, errors.New("Task group count must be positive")) + if tg.Count < 0 { + mErr.Errors = append(mErr.Errors, errors.New("Task group count can't be negative")) } if len(tg.Tasks) == 0 { mErr.Errors = append(mErr.Errors, errors.New("Missing tasks for task group")) diff --git a/scheduler/generic_sched_test.go b/scheduler/generic_sched_test.go index 390b5518242..bb36f5c91a1 100644 --- a/scheduler/generic_sched_test.go +++ b/scheduler/generic_sched_test.go @@ -76,6 +76,51 @@ func TestServiceSched_JobRegister(t *testing.T) { h.AssertEvalStatus(t, structs.EvalStatusComplete) } +func TestServiceSched_JobRegister_CountZero(t *testing.T) { + h := NewHarness(t) + + // Create some nodes + for i := 0; i < 10; i++ { + node := mock.Node() + noErr(t, h.State.UpsertNode(h.NextIndex(), node)) + } + + // Create a job and set the task group count to zero. + job := mock.Job() + job.TaskGroups[0].Count = 0 + noErr(t, h.State.UpsertJob(h.NextIndex(), job)) + + // Create a mock evaluation to register the job + eval := &structs.Evaluation{ + ID: structs.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 there was no plan + if len(h.Plans) != 0 { + t.Fatalf("bad: %#v", h.Plans) + } + + // Lookup the allocations by JobID + out, err := h.State.AllocsByJob(job.ID) + noErr(t, err) + + // Ensure no allocations placed + if len(out) != 0 { + t.Fatalf("bad: %#v", out) + } + + h.AssertEvalStatus(t, structs.EvalStatusComplete) +} + func TestServiceSched_JobRegister_AllocFail(t *testing.T) { h := NewHarness(t) @@ -391,6 +436,102 @@ func TestServiceSched_JobModify(t *testing.T) { h.AssertEvalStatus(t, structs.EvalStatusComplete) } +func TestServiceSched_JobModify_CountZero(t *testing.T) { + h := NewHarness(t) + + // Create some nodes + var nodes []*structs.Node + for i := 0; i < 10; i++ { + node := mock.Node() + nodes = append(nodes, node) + noErr(t, h.State.UpsertNode(h.NextIndex(), node)) + } + + // Generate a fake job with allocations + job := mock.Job() + noErr(t, h.State.UpsertJob(h.NextIndex(), job)) + + var allocs []*structs.Allocation + for i := 0; i < 10; i++ { + alloc := mock.Alloc() + alloc.Job = job + alloc.JobID = job.ID + alloc.NodeID = nodes[i].ID + alloc.Name = fmt.Sprintf("my-job.web[%d]", i) + allocs = append(allocs, alloc) + } + noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) + + // Add a few terminal status allocations, these should be ignored + var terminal []*structs.Allocation + for i := 0; i < 5; i++ { + alloc := mock.Alloc() + alloc.Job = job + alloc.JobID = job.ID + alloc.NodeID = nodes[i].ID + alloc.Name = fmt.Sprintf("my-job.web[%d]", i) + alloc.DesiredStatus = structs.AllocDesiredStatusFailed + terminal = append(terminal, alloc) + } + noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) + + // Update the job to be count zero + job2 := mock.Job() + job2.ID = job.ID + job2.TaskGroups[0].Count = 0 + noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) + + // Create a mock evaluation to deal with drain + eval := &structs.Evaluation{ + ID: structs.GenerateUUID(), + Priority: 50, + 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 evicted all allocs + var update []*structs.Allocation + for _, updateList := range plan.NodeUpdate { + update = append(update, updateList...) + } + if len(update) != len(allocs) { + t.Fatalf("bad: %#v", plan) + } + + // Ensure the plan didn't allocated + var planned []*structs.Allocation + for _, allocList := range plan.NodeAllocation { + planned = append(planned, allocList...) + } + if len(planned) != 0 { + t.Fatalf("bad: %#v", plan) + } + + // Lookup the allocations by JobID + out, err := h.State.AllocsByJob(job.ID) + noErr(t, err) + + // Ensure all allocations placed + out = structs.FilterTerminalAllocs(out) + if len(out) != 0 { + t.Fatalf("bad: %#v", out) + } + + h.AssertEvalStatus(t, structs.EvalStatusComplete) +} + func TestServiceSched_JobModify_Rolling(t *testing.T) { h := NewHarness(t) From e47cac6d7f85e20a6fc5d325c40fe9633b98243b Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Thu, 17 Mar 2016 11:29:41 -0700 Subject: [PATCH 2/2] Fix validate test --- nomad/structs/structs_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 7b1e0d2b356..1b2ce95173a 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -196,6 +196,7 @@ func TestJob_IsPeriodic(t *testing.T) { func TestTaskGroup_Validate(t *testing.T) { tg := &TaskGroup{ + Count: -1, RestartPolicy: &RestartPolicy{ Interval: 5 * time.Minute, Delay: 10 * time.Second, @@ -208,7 +209,7 @@ func TestTaskGroup_Validate(t *testing.T) { if !strings.Contains(mErr.Errors[0].Error(), "group name") { t.Fatalf("err: %s", err) } - if !strings.Contains(mErr.Errors[1].Error(), "count must be positive") { + if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") { t.Fatalf("err: %s", err) } if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {