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