diff --git a/scheduler/context_test.go b/scheduler/context_test.go index fda445f2f..740ef3705 100644 --- a/scheduler/context_test.go +++ b/scheduler/context_test.go @@ -14,7 +14,10 @@ func testContext(t *testing.T) (*state.StateStore, *EvalContext) { if err != nil { t.Fatalf("err: %v", err) } - plan := new(structs.Plan) + plan := &structs.Plan{ + NodeEvict: make(map[string][]string), + NodeAllocation: make(map[string][]*structs.Allocation), + } logger := log.New(os.Stderr, "", log.LstdFlags) diff --git a/scheduler/rank.go b/scheduler/rank.go index 9ce93c439..1f4fdb326 100644 --- a/scheduler/rank.go +++ b/scheduler/rank.go @@ -1,6 +1,10 @@ package scheduler -import "github.com/hashicorp/nomad/nomad/structs" +import ( + "fmt" + + "github.com/hashicorp/nomad/nomad/structs" +) // Rank is used to provide a score and various ranking metadata // along with a node when iterating. This state can be modified as @@ -10,6 +14,10 @@ type RankedNode struct { Score float64 } +func (r *RankedNode) GoString() string { + return fmt.Sprintf("", r.Node.ID, r.Score) +} + // RankFeasibleIterator is used to iteratively yield nodes along // with ranking metadata. The iterators may manage some state for // performance optimizations. diff --git a/scheduler/rank_test.go b/scheduler/rank_test.go index 33c20fd59..185c41edc 100644 --- a/scheduler/rank_test.go +++ b/scheduler/rank_test.go @@ -23,7 +23,7 @@ func TestFeasibleRankIterator(t *testing.T) { } } -func TestBinPackIterator_Simple_NoExistingAlloc(t *testing.T) { +func TestBinPackIterator_NoExistingAlloc(t *testing.T) { _, ctx := testContext(t) nodes := []*RankedNode{ &RankedNode{ @@ -90,6 +90,209 @@ func TestBinPackIterator_Simple_NoExistingAlloc(t *testing.T) { } } +func TestBinPackIterator_PlannedAlloc(t *testing.T) { + _, ctx := testContext(t) + nodes := []*RankedNode{ + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + } + static := NewStaticRankIterator(ctx, nodes) + + // Add a planned alloc to node1 that fills it + plan := ctx.Plan() + plan.NodeAllocation[nodes[0].Node.ID] = []*structs.Allocation{ + &structs.Allocation{ + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + } + + // Add a planned alloc to node2 that half fills it + plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{ + &structs.Allocation{ + Resources: &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + }, + }, + } + + resources := &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + } + binp := NewBinPackIterator(ctx, static, resources, false, 0) + + out := collectRanked(binp) + if len(out) != 1 { + t.Fatalf("Bad: %#v", out) + } + if out[0] != nodes[1] { + t.Fatalf("Bad: %v", out) + } + + if out[0].Score != 18 { + t.Fatalf("Bad: %v", out[0]) + } +} + +func TestBinPackIterator_ExistingAlloc(t *testing.T) { + state, ctx := testContext(t) + nodes := []*RankedNode{ + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + } + static := NewStaticRankIterator(ctx, nodes) + + // Add existing allocations + alloc1 := &structs.Allocation{ + ID: mock.GenerateUUID(), + NodeID: nodes[0].Node.ID, + JobID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + } + alloc2 := &structs.Allocation{ + ID: mock.GenerateUUID(), + NodeID: nodes[1].Node.ID, + JobID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + }, + } + noErr(t, state.UpdateAllocations(1000, nil, []*structs.Allocation{alloc1, alloc2})) + + resources := &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + } + binp := NewBinPackIterator(ctx, static, resources, false, 0) + + out := collectRanked(binp) + if len(out) != 1 { + t.Fatalf("Bad: %#v", out) + } + if out[0] != nodes[1] { + t.Fatalf("Bad: %v", out) + } + if out[0].Score != 18 { + t.Fatalf("Bad: %v", out[0]) + } +} + +func TestBinPackIterator_ExistingAlloc_PlannedEvict(t *testing.T) { + state, ctx := testContext(t) + nodes := []*RankedNode{ + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + &RankedNode{ + Node: &structs.Node{ + // Perfect fit + ID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + }, + }, + } + static := NewStaticRankIterator(ctx, nodes) + + // Add existing allocations + alloc1 := &structs.Allocation{ + ID: mock.GenerateUUID(), + NodeID: nodes[0].Node.ID, + JobID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 2048, + MemoryMB: 2048, + }, + } + alloc2 := &structs.Allocation{ + ID: mock.GenerateUUID(), + NodeID: nodes[1].Node.ID, + JobID: mock.GenerateUUID(), + Resources: &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + }, + } + noErr(t, state.UpdateAllocations(1000, nil, []*structs.Allocation{alloc1, alloc2})) + + // Add a planned eviction to alloc1 + plan := ctx.Plan() + plan.NodeEvict[nodes[0].Node.ID] = []string{alloc1.ID} + + resources := &structs.Resources{ + CPU: 1024, + MemoryMB: 1024, + } + binp := NewBinPackIterator(ctx, static, resources, false, 0) + + out := collectRanked(binp) + if len(out) != 2 { + t.Fatalf("Bad: %#v", out) + } + if out[0] != nodes[0] || out[1] != nodes[1] { + t.Fatalf("Bad: %v", out) + } + if out[0].Score < 10 || out[0].Score > 16 { + t.Fatalf("Bad: %v", out[0]) + } + if out[1].Score != 18 { + t.Fatalf("Bad: %v", out[0]) + } +} + func collectRanked(iter RankIterator) (out []*RankedNode) { for { next := iter.Next()