From f47d065eb102dec2b7a45c13004d759a8e970efe Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Tue, 4 Aug 2015 18:30:05 -0700 Subject: [PATCH] nomad: testing plan evaluation --- nomad/plan_apply.go | 50 ++++++++++++----------- nomad/plan_apply_test.go | 88 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 23 deletions(-) diff --git a/nomad/plan_apply.go b/nomad/plan_apply.go index f496ca151..21573f405 100644 --- a/nomad/plan_apply.go +++ b/nomad/plan_apply.go @@ -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) { diff --git a/nomad/plan_apply_test.go b/nomad/plan_apply_test.go index fb0828ae0..45a3fc5c7 100644 --- a/nomad/plan_apply_test.go +++ b/nomad/plan_apply_test.go @@ -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()