nomad: test evaluation of node plan

This commit is contained in:
Armon Dadgar 2015-08-04 18:10:57 -07:00
parent f733ba0efa
commit d758d459f7
3 changed files with 182 additions and 32 deletions

View file

@ -48,6 +48,7 @@ func (s *Server) planApply() {
// which may be partial or if there was an error
func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
defer metrics.MeasureSince([]string{"nomad", "plan", "evaluate"}, time.Now())
// Snapshot the state so that we have a consistent view of the world
snap, err := s.fsm.State().Snapshot()
if err != nil {
@ -61,30 +62,13 @@ func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
}
// Check each allocation to see if it should be allowed
for nodeID, allocList := range plan.NodeAllocation {
// Get the node itself
node, err := snap.GetNodeByID(nodeID)
for nodeID := range plan.NodeAllocation {
// Evaluate the plan for this node
fit, err := evaluateNodePlan(snap, plan, nodeID)
if err != nil {
return nil, fmt.Errorf("failed to get node '%s': %v", node, err)
return nil, err
}
// Get the existing allocations
existingAlloc, err := snap.AllocsByNode(nodeID)
if err != nil {
return nil, fmt.Errorf("failed to get existing allocations for '%s': %v", node, err)
}
// Determine the proposed allocation by first removing allocations
// that are planned evictions and adding the new allocations.
proposed := existingAlloc
evictions := plan.NodeEvict[nodeID]
if len(evictions) > 0 {
proposed = structs.RemoveAllocs(existingAlloc, evictions)
}
proposed = append(proposed, allocList...)
// Determine if everything fits
if !AllocationsFit(node, proposed) {
if !fit {
// Scheduler must have stale data, RefreshIndex should force
// the latest view of allocations and nodes
allocIndex, err := snap.GetIndex("allocs")
@ -108,12 +92,8 @@ func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
}
// Add this to the plan result
if len(evictions) > 0 {
result.NodeEvict[nodeID] = evictions
}
if len(allocList) > 0 {
result.NodeAllocation[nodeID] = allocList
}
result.NodeEvict[nodeID] = plan.NodeEvict[nodeID]
result.NodeAllocation[nodeID] = plan.NodeAllocation[nodeID]
}
return result, nil
}
@ -132,3 +112,35 @@ func (s *Server) applyPlan(result *structs.PlanResult) (uint64, error) {
_, index, err := s.raftApply(structs.AllocUpdateRequestType, &req)
return index, err
}
// evaluateNodePlan is used to evalute the plan for a single node,
// returning if the plan is valid or if an error is encountered
func evaluateNodePlan(snap *StateSnapshot, plan *structs.Plan, nodeID string) (bool, error) {
// Get the node itself
node, err := snap.GetNodeByID(nodeID)
if err != nil {
return false, fmt.Errorf("failed to get node '%s': %v", node, err)
}
// If the node does not exist or is not ready for schduling it is not fit
if node == nil || node.Status != structs.NodeStatusReady {
return false, nil
}
// Get the existing allocations
existingAlloc, err := snap.AllocsByNode(nodeID)
if err != nil {
return false, fmt.Errorf("failed to get existing allocations for '%s': %v", node, err)
}
// Determine the proposed allocation by first removing allocations
// that are planned evictions and adding the new allocations.
proposed := existingAlloc
if evict := plan.NodeEvict[nodeID]; len(evict) > 0 {
proposed = structs.RemoveAllocs(existingAlloc, evict)
}
proposed = append(proposed, plan.NodeAllocation[nodeID]...)
// Check if these allocations fit
return structs.AllocsFit(node, proposed)
}

View file

@ -1,7 +1,131 @@
package nomad
import "testing"
import (
"testing"
func TestPlanApply(t *testing.T) {
// TODO
"github.com/hashicorp/nomad/nomad/structs"
)
func TestPlanApply_EvalNodePlan_Simple(t *testing.T) {
state := testStateStore(t)
node := mockNode()
state.RegisterNode(1000, node)
snap, _ := state.Snapshot()
alloc := mockAlloc()
plan := &structs.Plan{
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
},
}
fit, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
}
func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) {
state := testStateStore(t)
node := mockNode()
node.Status = structs.NodeStatusInit
state.RegisterNode(1000, node)
snap, _ := state.Snapshot()
alloc := mockAlloc()
plan := &structs.Plan{
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
},
}
fit, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
}
func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) {
state := testStateStore(t)
snap, _ := state.Snapshot()
nodeID := "foo"
alloc := mockAlloc()
plan := &structs.Plan{
NodeAllocation: map[string][]*structs.Allocation{
nodeID: []*structs.Allocation{alloc},
},
}
fit, err := evaluateNodePlan(snap, plan, nodeID)
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
}
func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) {
alloc := mockAlloc()
state := testStateStore(t)
node := mockNode()
alloc.NodeID = node.ID
node.Resources = alloc.Resources
node.Reserved = nil
state.RegisterNode(1000, node)
state.UpdateAllocations(1001, nil,
[]*structs.Allocation{alloc})
snap, _ := state.Snapshot()
plan := &structs.Plan{
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
},
}
fit, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if fit {
t.Fatalf("bad")
}
}
func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) {
alloc := mockAlloc()
state := testStateStore(t)
node := mockNode()
alloc.NodeID = node.ID
node.Resources = alloc.Resources
node.Reserved = nil
state.RegisterNode(1000, node)
state.UpdateAllocations(1001, nil,
[]*structs.Allocation{alloc})
snap, _ := state.Snapshot()
alloc2 := mockAlloc()
plan := &structs.Plan{
NodeEvict: map[string][]string{
node.ID: []string{alloc.ID},
},
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc2},
},
}
fit, err := evaluateNodePlan(snap, plan, node.ID)
if err != nil {
t.Fatalf("err: %v", err)
}
if !fit {
t.Fatalf("bad")
}
}

View file

@ -58,7 +58,7 @@ func mockNode() *structs.Node {
"pci-dss": "true",
},
NodeClass: "linux-medium-pci",
Status: structs.NodeStatusInit,
Status: structs.NodeStatusReady,
}
return node
}
@ -125,6 +125,20 @@ func mockAlloc() *structs.Allocation {
alloc := &structs.Allocation{
ID: generateUUID(),
NodeID: "foo",
Resources: &structs.Resources{
CPU: 1.0,
MemoryMB: 1024,
DiskMB: 1024,
IOPS: 10,
Networks: []*structs.NetworkResource{
&structs.NetworkResource{
Public: true,
CIDR: "192.168.0.100/32",
ReservedPorts: []int{12345},
MBits: 100,
},
},
},
}
return alloc
}