github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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 // NewHarnessWithState creates a new harness with the given state for testing 70 // purposes. 71 func NewHarnessWithState(t *testing.T, state *state.StateStore) *Harness { 72 return &Harness{ 73 State: state, 74 nextIndex: 1, 75 } 76 } 77 78 // SubmitPlan is used to handle plan submission 79 func (h *Harness) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, State, error) { 80 // Ensure sequential plan application 81 h.planLock.Lock() 82 defer h.planLock.Unlock() 83 84 // Store the plan 85 h.Plans = append(h.Plans, plan) 86 87 // Check for custom planner 88 if h.Planner != nil { 89 return h.Planner.SubmitPlan(plan) 90 } 91 92 // Get the index 93 index := h.NextIndex() 94 95 // Prepare the result 96 result := new(structs.PlanResult) 97 result.NodeUpdate = plan.NodeUpdate 98 result.NodeAllocation = plan.NodeAllocation 99 result.AllocIndex = index 100 101 // Flatten evicts and allocs 102 var allocs []*structs.Allocation 103 for _, updateList := range plan.NodeUpdate { 104 allocs = append(allocs, updateList...) 105 } 106 for _, allocList := range plan.NodeAllocation { 107 allocs = append(allocs, allocList...) 108 } 109 110 // Attach the plan to all the allocations. It is pulled out in the 111 // payload to avoid the redundancy of encoding, but should be denormalized 112 // prior to being inserted into MemDB. 113 if j := plan.Job; j != nil { 114 for _, alloc := range allocs { 115 if alloc.Job == nil { 116 alloc.Job = j 117 } 118 } 119 } 120 121 // Apply the full plan 122 err := h.State.UpsertAllocs(index, allocs) 123 return result, nil, err 124 } 125 126 func (h *Harness) UpdateEval(eval *structs.Evaluation) error { 127 // Ensure sequential plan application 128 h.planLock.Lock() 129 defer h.planLock.Unlock() 130 131 // Store the eval 132 h.Evals = append(h.Evals, eval) 133 134 // Check for custom planner 135 if h.Planner != nil { 136 return h.Planner.UpdateEval(eval) 137 } 138 return nil 139 } 140 141 func (h *Harness) CreateEval(eval *structs.Evaluation) error { 142 // Ensure sequential plan application 143 h.planLock.Lock() 144 defer h.planLock.Unlock() 145 146 // Store the eval 147 h.CreateEvals = append(h.CreateEvals, eval) 148 149 // Check for custom planner 150 if h.Planner != nil { 151 return h.Planner.CreateEval(eval) 152 } 153 return nil 154 } 155 156 func (h *Harness) ReblockEval(eval *structs.Evaluation) error { 157 // Ensure sequential plan application 158 h.planLock.Lock() 159 defer h.planLock.Unlock() 160 161 // Check that the evaluation was already blocked. 162 old, err := h.State.EvalByID(eval.ID) 163 if err != nil { 164 return err 165 } 166 167 if old == nil { 168 return fmt.Errorf("evaluation does not exist to be reblocked") 169 } 170 if old.Status != structs.EvalStatusBlocked { 171 return fmt.Errorf("evaluation %q is not already in a blocked state", old.ID) 172 } 173 174 h.ReblockEvals = append(h.ReblockEvals, eval) 175 return nil 176 } 177 178 // NextIndex returns the next index 179 func (h *Harness) NextIndex() uint64 { 180 h.nextIndexLock.Lock() 181 defer h.nextIndexLock.Unlock() 182 idx := h.nextIndex 183 h.nextIndex += 1 184 return idx 185 } 186 187 // Snapshot is used to snapshot the current state 188 func (h *Harness) Snapshot() State { 189 snap, _ := h.State.Snapshot() 190 return snap 191 } 192 193 // Scheduler is used to return a new scheduler from 194 // a snapshot of current state using the harness for planning. 195 func (h *Harness) Scheduler(factory Factory) Scheduler { 196 logger := log.New(os.Stderr, "", log.LstdFlags) 197 return factory(logger, h.Snapshot(), h) 198 } 199 200 // Process is used to process an evaluation given a factory 201 // function to create the scheduler 202 func (h *Harness) Process(factory Factory, eval *structs.Evaluation) error { 203 sched := h.Scheduler(factory) 204 return sched.Process(eval) 205 } 206 207 func (h *Harness) AssertEvalStatus(t *testing.T, state string) { 208 if len(h.Evals) != 1 { 209 t.Fatalf("bad: %#v", h.Evals) 210 } 211 update := h.Evals[0] 212 213 if update.Status != state { 214 t.Fatalf("bad: %#v", update) 215 } 216 }