2015-08-11 21:54:21 +00:00
|
|
|
package scheduler
|
|
|
|
|
|
|
|
import (
|
2015-09-07 02:47:02 +00:00
|
|
|
"fmt"
|
2015-08-11 21:54:21 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2015-08-14 01:51:08 +00:00
|
|
|
func TestServiceSched_JobRegister(t *testing.T) {
|
2015-08-14 05:07:01 +00:00
|
|
|
h := NewHarness(t)
|
|
|
|
|
|
|
|
// Create some nodes
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
node := mock.Node()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertNode(h.NextIndex(), node))
|
2015-08-14 05:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a job
|
|
|
|
job := mock.Job()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job))
|
2015-08-14 05:07:01 +00:00
|
|
|
|
|
|
|
// 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]
|
|
|
|
|
2015-08-14 05:11:32 +00:00
|
|
|
// Ensure the plan allocated
|
2015-08-14 05:07:01 +00:00
|
|
|
var planned []*structs.Allocation
|
|
|
|
for _, allocList := range plan.NodeAllocation {
|
|
|
|
planned = append(planned, allocList...)
|
|
|
|
}
|
|
|
|
if len(planned) != 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)
|
|
|
|
}
|
2015-08-15 21:47:13 +00:00
|
|
|
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
2015-08-14 01:51:08 +00:00
|
|
|
}
|
|
|
|
|
2015-08-15 20:40:13 +00:00
|
|
|
func TestServiceSched_JobRegister_AllocFail(t *testing.T) {
|
|
|
|
h := NewHarness(t)
|
|
|
|
|
|
|
|
// Create NO nodes
|
|
|
|
// Create a job
|
|
|
|
job := mock.Job()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job))
|
2015-08-15 20:40:13 +00:00
|
|
|
|
|
|
|
// 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
|
2015-08-16 17:03:21 +00:00
|
|
|
if len(plan.FailedAllocs) != 1 {
|
2015-08-15 20:40:13 +00:00
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup the allocations by JobID
|
|
|
|
out, err := h.State.AllocsByJob(job.ID)
|
|
|
|
noErr(t, err)
|
|
|
|
|
|
|
|
// Ensure all allocations placed
|
2015-08-16 17:03:21 +00:00
|
|
|
if len(out) != 1 {
|
2015-08-15 20:40:13 +00:00
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
2015-08-15 21:47:13 +00:00
|
|
|
|
2015-08-16 17:03:21 +00:00
|
|
|
// Check the coalesced failures
|
|
|
|
if out[0].Metrics.CoalescedFailures != 9 {
|
|
|
|
t.Fatalf("bad: %#v", out[0].Metrics)
|
|
|
|
}
|
|
|
|
|
2015-08-15 21:47:13 +00:00
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
2015-08-15 20:40:13 +00:00
|
|
|
}
|
|
|
|
|
2015-08-14 01:51:08 +00:00
|
|
|
func TestServiceSched_JobModify(t *testing.T) {
|
2015-08-14 05:14:37 +00:00
|
|
|
h := NewHarness(t)
|
|
|
|
|
|
|
|
// Create some nodes
|
|
|
|
var nodes []*structs.Node
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
node := mock.Node()
|
|
|
|
nodes = append(nodes, node)
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertNode(h.NextIndex(), node))
|
2015-08-14 05:14:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a fake job with allocations
|
|
|
|
job := mock.Job()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job))
|
2015-08-14 05:14:37 +00:00
|
|
|
|
|
|
|
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
|
2015-09-07 19:27:12 +00:00
|
|
|
alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
|
2015-08-14 05:14:37 +00:00
|
|
|
allocs = append(allocs, alloc)
|
|
|
|
}
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
|
2015-08-14 05:14:37 +00:00
|
|
|
|
|
|
|
// Update the job
|
|
|
|
job2 := mock.Job()
|
|
|
|
job2.ID = job.ID
|
2015-09-07 19:27:12 +00:00
|
|
|
|
|
|
|
// Update the task, such that it cannot be done in-place
|
|
|
|
job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
|
2015-08-14 05:14:37 +00:00
|
|
|
|
|
|
|
// Create a mock evaluation to deal with drain
|
|
|
|
eval := &structs.Evaluation{
|
|
|
|
ID: mock.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
|
2015-08-26 00:06:06 +00:00
|
|
|
var update []*structs.Allocation
|
|
|
|
for _, updateList := range plan.NodeUpdate {
|
|
|
|
update = append(update, updateList...)
|
2015-08-14 05:14:37 +00:00
|
|
|
}
|
2015-08-26 00:06:06 +00:00
|
|
|
if len(update) != len(allocs) {
|
2015-08-14 05:14:37 +00:00
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the plan allocated
|
|
|
|
var planned []*structs.Allocation
|
|
|
|
for _, allocList := range plan.NodeAllocation {
|
|
|
|
planned = append(planned, allocList...)
|
|
|
|
}
|
|
|
|
if len(planned) != 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
|
2015-08-23 23:30:57 +00:00
|
|
|
out = structs.FilterTerminalAllocs(out)
|
2015-08-14 05:14:37 +00:00
|
|
|
if len(out) != 10 {
|
2015-08-26 00:06:06 +00:00
|
|
|
t.Fatalf("bad: %#v", out)
|
2015-08-14 05:14:37 +00:00
|
|
|
}
|
2015-08-15 21:47:13 +00:00
|
|
|
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
2015-08-14 01:51:08 +00:00
|
|
|
}
|
|
|
|
|
2015-09-07 19:27:12 +00:00
|
|
|
func TestServiceSched_JobModify_InPlace(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))
|
|
|
|
|
|
|
|
// Update the job
|
|
|
|
job2 := mock.Job()
|
|
|
|
job2.ID = job.ID
|
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
|
|
|
|
|
|
|
|
// Create a mock evaluation to deal with drain
|
|
|
|
eval := &structs.Evaluation{
|
|
|
|
ID: mock.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 did not evict any allocs
|
|
|
|
var update []*structs.Allocation
|
|
|
|
for _, updateList := range plan.NodeUpdate {
|
|
|
|
update = append(update, updateList...)
|
|
|
|
}
|
|
|
|
if len(update) != 0 {
|
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the plan updated the existing allocs
|
|
|
|
var planned []*structs.Allocation
|
|
|
|
for _, allocList := range plan.NodeAllocation {
|
|
|
|
planned = append(planned, allocList...)
|
|
|
|
}
|
|
|
|
if len(planned) != 10 {
|
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
for _, p := range planned {
|
|
|
|
if p.Job != job2 {
|
|
|
|
t.Fatalf("should update job")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
|
|
|
}
|
|
|
|
|
2015-08-11 21:54:21 +00:00
|
|
|
func TestServiceSched_JobDeregister(t *testing.T) {
|
|
|
|
h := NewHarness(t)
|
|
|
|
|
|
|
|
// Generate a fake job with allocations
|
|
|
|
job := mock.Job()
|
|
|
|
|
|
|
|
var allocs []*structs.Allocation
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
alloc := mock.Alloc()
|
|
|
|
alloc.Job = job
|
|
|
|
alloc.JobID = job.ID
|
|
|
|
allocs = append(allocs, alloc)
|
|
|
|
}
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
|
2015-08-11 21:54:21 +00:00
|
|
|
|
|
|
|
// Create a mock evaluation to deregister the job
|
|
|
|
eval := &structs.Evaluation{
|
|
|
|
ID: mock.GenerateUUID(),
|
|
|
|
Priority: 50,
|
|
|
|
TriggeredBy: structs.EvalTriggerJobDeregister,
|
|
|
|
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 nodes
|
2015-08-26 00:06:06 +00:00
|
|
|
if len(plan.NodeUpdate["foo"]) != len(allocs) {
|
2015-08-11 21:54:21 +00:00
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup the allocations by JobID
|
|
|
|
out, err := h.State.AllocsByJob(job.ID)
|
|
|
|
noErr(t, err)
|
|
|
|
|
|
|
|
// Ensure no remaining allocations
|
2015-08-23 23:30:57 +00:00
|
|
|
out = structs.FilterTerminalAllocs(out)
|
2015-08-11 21:54:21 +00:00
|
|
|
if len(out) != 0 {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
2015-08-15 21:47:13 +00:00
|
|
|
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
2015-08-11 21:54:21 +00:00
|
|
|
}
|
2015-08-14 01:51:08 +00:00
|
|
|
|
|
|
|
func TestServiceSched_NodeDrain(t *testing.T) {
|
2015-08-14 05:11:32 +00:00
|
|
|
h := NewHarness(t)
|
|
|
|
|
|
|
|
// Register a draining node
|
|
|
|
node := mock.Node()
|
2015-09-07 02:47:02 +00:00
|
|
|
node.Drain = true
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertNode(h.NextIndex(), node))
|
2015-08-14 05:11:32 +00:00
|
|
|
|
|
|
|
// Create some nodes
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
node := mock.Node()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertNode(h.NextIndex(), node))
|
2015-08-14 05:11:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a fake job with allocations
|
|
|
|
job := mock.Job()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job))
|
2015-08-14 05:11:32 +00:00
|
|
|
|
|
|
|
var allocs []*structs.Allocation
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
alloc := mock.Alloc()
|
|
|
|
alloc.Job = job
|
|
|
|
alloc.JobID = job.ID
|
|
|
|
alloc.NodeID = node.ID
|
2015-09-07 02:47:02 +00:00
|
|
|
alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
|
2015-08-14 05:11:32 +00:00
|
|
|
allocs = append(allocs, alloc)
|
|
|
|
}
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
|
2015-08-14 05:11:32 +00:00
|
|
|
|
|
|
|
// Create a mock evaluation to deal with drain
|
|
|
|
eval := &structs.Evaluation{
|
|
|
|
ID: mock.GenerateUUID(),
|
|
|
|
Priority: 50,
|
|
|
|
TriggeredBy: structs.EvalTriggerNodeUpdate,
|
|
|
|
JobID: job.ID,
|
|
|
|
NodeID: node.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
|
2015-08-26 00:06:06 +00:00
|
|
|
if len(plan.NodeUpdate[node.ID]) != len(allocs) {
|
2015-08-14 05:11:32 +00:00
|
|
|
t.Fatalf("bad: %#v", plan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the plan allocated
|
|
|
|
var planned []*structs.Allocation
|
|
|
|
for _, allocList := range plan.NodeAllocation {
|
|
|
|
planned = append(planned, allocList...)
|
|
|
|
}
|
|
|
|
if len(planned) != 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
|
2015-08-23 23:30:57 +00:00
|
|
|
out = structs.FilterTerminalAllocs(out)
|
2015-08-14 05:11:32 +00:00
|
|
|
if len(out) != 10 {
|
|
|
|
t.Fatalf("bad: %#v", out)
|
|
|
|
}
|
2015-08-15 21:47:13 +00:00
|
|
|
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestServiceSched_RetryLimit(t *testing.T) {
|
|
|
|
h := NewHarness(t)
|
|
|
|
h.Planner = &RejectPlan{h}
|
|
|
|
|
|
|
|
// Create some nodes
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
node := mock.Node()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertNode(h.NextIndex(), node))
|
2015-08-15 21:47:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a job
|
|
|
|
job := mock.Job()
|
2015-09-07 03:47:42 +00:00
|
|
|
noErr(t, h.State.UpsertJob(h.NextIndex(), job))
|
2015-08-15 21:47:13 +00:00
|
|
|
|
|
|
|
// 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 multiple plans
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should hit the retry limit
|
|
|
|
h.AssertEvalStatus(t, structs.EvalStatusFailed)
|
2015-08-14 01:51:08 +00:00
|
|
|
}
|