nomad: testing plan evaluation

This commit is contained in:
Armon Dadgar 2015-08-04 18:30:05 -07:00
parent 2382993b78
commit 3aba600230
2 changed files with 115 additions and 23 deletions

View file

@ -19,8 +19,16 @@ func (s *Server) planApply() {
return
}
// Snapshot the state so that we have a consistent view of the world
snap, err := s.fsm.State().Snapshot()
if err != nil {
s.logger.Printf("[ERR] nomad: failed to snapshot state: %v", err)
pending.respond(nil, err)
continue
}
// Evaluate the plan
result, err := s.evaluatePlan(pending.plan)
result, err := evaluatePlan(snap, pending.plan)
if err != nil {
s.logger.Printf("[ERR] nomad: failed to evaluate plan: %v", err)
pending.respond(nil, err)
@ -43,18 +51,27 @@ func (s *Server) planApply() {
}
}
// applyPlan is used to apply the plan result and to return the alloc index
func (s *Server) applyPlan(result *structs.PlanResult) (uint64, error) {
defer metrics.MeasureSince([]string{"nomad", "plan", "apply"}, time.Now())
req := structs.AllocUpdateRequest{}
for _, evictList := range result.NodeEvict {
req.Evict = append(req.Evict, evictList...)
}
for _, allocList := range result.NodeAllocation {
req.Alloc = append(req.Alloc, allocList...)
}
_, index, err := s.raftApply(structs.AllocUpdateRequestType, &req)
return index, err
}
// evaluatePlan is used to determine what portions of a plan
// can be applied if any. Returns if there should be a plan application
// which may be partial or if there was an error
func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
func evaluatePlan(snap *StateSnapshot, 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 {
return nil, fmt.Errorf("failed to snapshot state: %v", err)
}
// Create a result holder for the plan
result := &structs.PlanResult{
NodeEvict: make(map[string][]string),
@ -84,6 +101,8 @@ func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
// If we require all-at-once scheduling, there is no point
// to continue the evaluation, as we've already failed.
if plan.AllAtOnce {
result.NodeEvict = nil
result.NodeAllocation = nil
return result, nil
}
@ -98,21 +117,6 @@ func (s *Server) evaluatePlan(plan *structs.Plan) (*structs.PlanResult, error) {
return result, nil
}
// applyPlan is used to apply the plan result and to return the alloc index
func (s *Server) applyPlan(result *structs.PlanResult) (uint64, error) {
defer metrics.MeasureSince([]string{"nomad", "plan", "apply"}, time.Now())
req := structs.AllocUpdateRequest{}
for _, evictList := range result.NodeEvict {
req.Evict = append(req.Evict, evictList...)
}
for _, allocList := range result.NodeAllocation {
req.Alloc = append(req.Alloc, allocList...)
}
_, 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) {

View file

@ -101,6 +101,94 @@ func TestPlanApply_applyPlan(t *testing.T) {
}
}
func TestPlanApply_EvalPlan_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},
},
}
result, err := evaluatePlan(snap, plan)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
}
func TestPlanApply_EvalPlan_Partial(t *testing.T) {
state := testStateStore(t)
node := mockNode()
state.RegisterNode(1000, node)
node2 := mockNode()
state.RegisterNode(1001, node2)
snap, _ := state.Snapshot()
alloc := mockAlloc()
alloc2 := mockAlloc() // Ensure alloc2 does not fit
alloc2.Resources = node2.Resources
plan := &structs.Plan{
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
node2.ID: []*structs.Allocation{alloc2},
},
}
result, err := evaluatePlan(snap, plan)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
if _, ok := result.NodeAllocation[node.ID]; !ok {
t.Fatalf("should allow alloc")
}
if _, ok := result.NodeAllocation[node2.ID]; ok {
t.Fatalf("should not allow alloc2")
}
}
func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) {
state := testStateStore(t)
node := mockNode()
state.RegisterNode(1000, node)
node2 := mockNode()
state.RegisterNode(1001, node2)
snap, _ := state.Snapshot()
alloc := mockAlloc()
alloc2 := mockAlloc() // Ensure alloc2 does not fit
alloc2.Resources = node2.Resources
plan := &structs.Plan{
AllAtOnce: true, // Require all to make progress
NodeAllocation: map[string][]*structs.Allocation{
node.ID: []*structs.Allocation{alloc},
node2.ID: []*structs.Allocation{alloc2},
},
}
result, err := evaluatePlan(snap, plan)
if err != nil {
t.Fatalf("err: %v", err)
}
if result == nil {
t.Fatalf("missing result")
}
if len(result.NodeAllocation) != 0 {
t.Fatalf("should not alloc: %v", result.NodeAllocation)
}
}
func TestPlanApply_EvalNodePlan_Simple(t *testing.T) {
state := testStateStore(t)
node := mockNode()