github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/scheduler/testing.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "sync" 8 "testing" 9 10 "github.com/hashicorp/nomad/nomad/state" 11 "github.com/hashicorp/nomad/nomad/structs" 12 ) 13 14 // RejectPlan is used to always reject the entire plan and force a state refresh 15 type RejectPlan struct { 16 Harness *Harness 17 } 18 19 func (r *RejectPlan) SubmitPlan(*structs.Plan) (*structs.PlanResult, State, error) { 20 result := new(structs.PlanResult) 21 result.RefreshIndex = r.Harness.NextIndex() 22 return result, r.Harness.State, nil 23 } 24 25 func (r *RejectPlan) UpdateEval(eval *structs.Evaluation) error { 26 return nil 27 } 28 29 func (r *RejectPlan) CreateEval(*structs.Evaluation) error { 30 return nil 31 } 32 33 func (r *RejectPlan) ReblockEval(*structs.Evaluation) error { 34 return nil 35 } 36 37 // Harness is a lightweight testing harness for schedulers. It manages a state 38 // store copy and provides the planner interface. It can be extended for various 39 // testing uses or for invoking the scheduler without side effects. 40 type Harness struct { 41 State *state.StateStore 42 43 Planner Planner 44 planLock sync.Mutex 45 46 Plans []*structs.Plan 47 Evals []*structs.Evaluation 48 CreateEvals []*structs.Evaluation 49 ReblockEvals []*structs.Evaluation 50 51 nextIndex uint64 52 nextIndexLock sync.Mutex 53 } 54 55 // NewHarness is used to make a new testing harness 56 func NewHarness(t *testing.T) *Harness { 57 state, err := state.NewStateStore(os.Stderr) 58 if err != nil { 59 t.Fatalf("err: %v", err) 60 } 61 62 h := &Harness{ 63 State: state, 64 nextIndex: 1, 65 } 66 return h 67 } 68 69 // SubmitPlan is used to handle plan submission 70 func (h *Harness) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, State, error) { 71 // Ensure sequential plan application 72 h.planLock.Lock() 73 defer h.planLock.Unlock() 74 75 // Store the plan 76 h.Plans = append(h.Plans, plan) 77 78 // Check for custom planner 79 if h.Planner != nil { 80 return h.Planner.SubmitPlan(plan) 81 } 82 83 // Get the index 84 index := h.NextIndex() 85 86 // Prepare the result 87 result := new(structs.PlanResult) 88 result.NodeUpdate = plan.NodeUpdate 89 result.NodeAllocation = plan.NodeAllocation 90 result.AllocIndex = index 91 92 // Flatten evicts and allocs 93 var allocs []*structs.Allocation 94 for _, updateList := range plan.NodeUpdate { 95 allocs = append(allocs, updateList...) 96 } 97 for _, allocList := range plan.NodeAllocation { 98 allocs = append(allocs, allocList...) 99 } 100 101 // Attach the plan to all the allocations. It is pulled out in the 102 // payload to avoid the redundancy of encoding, but should be denormalized 103 // prior to being inserted into MemDB. 104 if j := plan.Job; j != nil { 105 for _, alloc := range allocs { 106 if alloc.Job == nil { 107 alloc.Job = j 108 } 109 } 110 } 111 112 // Apply the full plan 113 err := h.State.UpsertAllocs(index, allocs) 114 return result, nil, err 115 } 116 117 func (h *Harness) UpdateEval(eval *structs.Evaluation) error { 118 // Ensure sequential plan application 119 h.planLock.Lock() 120 defer h.planLock.Unlock() 121 122 // Store the eval 123 h.Evals = append(h.Evals, eval) 124 125 // Check for custom planner 126 if h.Planner != nil { 127 return h.Planner.UpdateEval(eval) 128 } 129 return nil 130 } 131 132 func (h *Harness) CreateEval(eval *structs.Evaluation) error { 133 // Ensure sequential plan application 134 h.planLock.Lock() 135 defer h.planLock.Unlock() 136 137 // Store the eval 138 h.CreateEvals = append(h.CreateEvals, eval) 139 140 // Check for custom planner 141 if h.Planner != nil { 142 return h.Planner.CreateEval(eval) 143 } 144 return nil 145 } 146 147 func (h *Harness) ReblockEval(eval *structs.Evaluation) error { 148 // Ensure sequential plan application 149 h.planLock.Lock() 150 defer h.planLock.Unlock() 151 152 // Check that the evaluation was already blocked. 153 old, err := h.State.EvalByID(eval.ID) 154 if err != nil { 155 return err 156 } 157 158 if old == nil { 159 return fmt.Errorf("evaluation does not exist to be reblocked") 160 } 161 if old.Status != structs.EvalStatusBlocked { 162 return fmt.Errorf("evaluation %q is not already in a blocked state", old.ID) 163 } 164 165 h.ReblockEvals = append(h.ReblockEvals, eval) 166 return nil 167 } 168 169 // NextIndex returns the next index 170 func (h *Harness) NextIndex() uint64 { 171 h.nextIndexLock.Lock() 172 defer h.nextIndexLock.Unlock() 173 idx := h.nextIndex 174 h.nextIndex += 1 175 return idx 176 } 177 178 // Snapshot is used to snapshot the current state 179 func (h *Harness) Snapshot() State { 180 snap, _ := h.State.Snapshot() 181 return snap 182 } 183 184 // Scheduler is used to return a new scheduler from 185 // a snapshot of current state using the harness for planning. 186 func (h *Harness) Scheduler(factory Factory) Scheduler { 187 logger := log.New(os.Stderr, "", log.LstdFlags) 188 return factory(logger, h.Snapshot(), h) 189 } 190 191 // Process is used to process an evaluation given a factory 192 // function to create the scheduler 193 func (h *Harness) Process(factory Factory, eval *structs.Evaluation) error { 194 sched := h.Scheduler(factory) 195 return sched.Process(eval) 196 } 197 198 func (h *Harness) AssertEvalStatus(t *testing.T, state string) { 199 if len(h.Evals) != 1 { 200 t.Fatalf("bad: %#v", h.Evals) 201 } 202 update := h.Evals[0] 203 204 if update.Status != state { 205 t.Fatalf("bad: %#v", update) 206 } 207 }