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