github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/nomad/worker_test.go (about) 1 package nomad 2 3 import ( 4 "log" 5 "reflect" 6 "strings" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/scheduler" 14 "github.com/hashicorp/nomad/testutil" 15 ) 16 17 type NoopScheduler struct { 18 state scheduler.State 19 planner scheduler.Planner 20 eval *structs.Evaluation 21 err error 22 } 23 24 func (n *NoopScheduler) Process(eval *structs.Evaluation) error { 25 if n.state == nil { 26 panic("missing state") 27 } 28 if n.planner == nil { 29 panic("missing planner") 30 } 31 n.eval = eval 32 return n.err 33 } 34 35 func init() { 36 scheduler.BuiltinSchedulers["noop"] = func(logger *log.Logger, s scheduler.State, p scheduler.Planner) scheduler.Scheduler { 37 n := &NoopScheduler{ 38 state: s, 39 planner: p, 40 } 41 return n 42 } 43 } 44 45 func TestWorker_dequeueEvaluation(t *testing.T) { 46 s1 := testServer(t, func(c *Config) { 47 c.NumSchedulers = 0 48 c.EnabledSchedulers = []string{structs.JobTypeService} 49 }) 50 defer s1.Shutdown() 51 testutil.WaitForLeader(t, s1.RPC) 52 53 // Create the evaluation 54 eval1 := mock.Eval() 55 s1.evalBroker.Enqueue(eval1) 56 57 // Create a worker 58 w := &Worker{srv: s1, logger: s1.logger} 59 60 // Attempt dequeue 61 eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 62 if shutdown { 63 t.Fatalf("should not shutdown") 64 } 65 if token == "" { 66 t.Fatalf("should get token") 67 } 68 69 // Ensure we get a sane eval 70 if !reflect.DeepEqual(eval, eval1) { 71 t.Fatalf("bad: %#v %#v", eval, eval1) 72 } 73 } 74 75 func TestWorker_dequeueEvaluation_paused(t *testing.T) { 76 s1 := testServer(t, func(c *Config) { 77 c.NumSchedulers = 0 78 c.EnabledSchedulers = []string{structs.JobTypeService} 79 }) 80 defer s1.Shutdown() 81 testutil.WaitForLeader(t, s1.RPC) 82 83 // Create the evaluation 84 eval1 := mock.Eval() 85 s1.evalBroker.Enqueue(eval1) 86 87 // Create a worker 88 w := &Worker{srv: s1, logger: s1.logger} 89 w.pauseCond = sync.NewCond(&w.pauseLock) 90 91 // PAUSE the worker 92 w.SetPause(true) 93 94 go func() { 95 time.Sleep(100 * time.Millisecond) 96 w.SetPause(false) 97 }() 98 99 // Attempt dequeue 100 start := time.Now() 101 eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 102 if diff := time.Since(start); diff < 100*time.Millisecond { 103 t.Fatalf("should have paused: %v", diff) 104 } 105 if shutdown { 106 t.Fatalf("should not shutdown") 107 } 108 if token == "" { 109 t.Fatalf("should get token") 110 } 111 112 // Ensure we get a sane eval 113 if !reflect.DeepEqual(eval, eval1) { 114 t.Fatalf("bad: %#v %#v", eval, eval1) 115 } 116 } 117 118 func TestWorker_dequeueEvaluation_shutdown(t *testing.T) { 119 s1 := testServer(t, func(c *Config) { 120 c.NumSchedulers = 0 121 c.EnabledSchedulers = []string{structs.JobTypeService} 122 }) 123 defer s1.Shutdown() 124 testutil.WaitForLeader(t, s1.RPC) 125 126 // Create a worker 127 w := &Worker{srv: s1, logger: s1.logger} 128 129 go func() { 130 time.Sleep(10 * time.Millisecond) 131 s1.Shutdown() 132 }() 133 134 // Attempt dequeue 135 eval, _, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 136 if !shutdown { 137 t.Fatalf("should not shutdown") 138 } 139 140 // Ensure we get a sane eval 141 if eval != nil { 142 t.Fatalf("bad: %#v", eval) 143 } 144 } 145 146 func TestWorker_sendAck(t *testing.T) { 147 s1 := testServer(t, func(c *Config) { 148 c.NumSchedulers = 0 149 c.EnabledSchedulers = []string{structs.JobTypeService} 150 }) 151 defer s1.Shutdown() 152 testutil.WaitForLeader(t, s1.RPC) 153 154 // Create the evaluation 155 eval1 := mock.Eval() 156 s1.evalBroker.Enqueue(eval1) 157 158 // Create a worker 159 w := &Worker{srv: s1, logger: s1.logger} 160 161 // Attempt dequeue 162 eval, token, _ := w.dequeueEvaluation(10 * time.Millisecond) 163 164 // Check the depth is 0, 1 unacked 165 stats := s1.evalBroker.Stats() 166 if stats.TotalReady != 0 && stats.TotalUnacked != 1 { 167 t.Fatalf("bad: %#v", stats) 168 } 169 170 // Send the Nack 171 w.sendAck(eval.ID, token, false) 172 173 // Check the depth is 1, nothing unacked 174 stats = s1.evalBroker.Stats() 175 if stats.TotalReady != 1 && stats.TotalUnacked != 0 { 176 t.Fatalf("bad: %#v", stats) 177 } 178 179 // Attempt dequeue 180 eval, token, _ = w.dequeueEvaluation(10 * time.Millisecond) 181 182 // Send the Ack 183 w.sendAck(eval.ID, token, true) 184 185 // Check the depth is 0 186 stats = s1.evalBroker.Stats() 187 if stats.TotalReady != 0 && stats.TotalUnacked != 0 { 188 t.Fatalf("bad: %#v", stats) 189 } 190 } 191 192 func TestWorker_waitForIndex(t *testing.T) { 193 s1 := testServer(t, func(c *Config) { 194 c.NumSchedulers = 0 195 c.EnabledSchedulers = []string{structs.JobTypeService} 196 }) 197 defer s1.Shutdown() 198 testutil.WaitForLeader(t, s1.RPC) 199 200 // Get the current index 201 index := s1.raft.AppliedIndex() 202 203 // Cause an increment 204 go func() { 205 time.Sleep(10 * time.Millisecond) 206 s1.raft.Barrier(0) 207 }() 208 209 // Wait for a future index 210 w := &Worker{srv: s1, logger: s1.logger} 211 err := w.waitForIndex(index+1, time.Second) 212 if err != nil { 213 t.Fatalf("err: %v", err) 214 } 215 216 // Cause a timeout 217 err = w.waitForIndex(index+100, 10*time.Millisecond) 218 if err == nil || !strings.Contains(err.Error(), "timeout") { 219 t.Fatalf("err: %v", err) 220 } 221 } 222 223 func TestWorker_invokeScheduler(t *testing.T) { 224 s1 := testServer(t, func(c *Config) { 225 c.NumSchedulers = 0 226 c.EnabledSchedulers = []string{structs.JobTypeService} 227 }) 228 defer s1.Shutdown() 229 230 w := &Worker{srv: s1, logger: s1.logger} 231 eval := mock.Eval() 232 eval.Type = "noop" 233 234 err := w.invokeScheduler(eval, structs.GenerateUUID()) 235 if err != nil { 236 t.Fatalf("err: %v", err) 237 } 238 } 239 240 func TestWorker_SubmitPlan(t *testing.T) { 241 s1 := testServer(t, func(c *Config) { 242 c.NumSchedulers = 0 243 c.EnabledSchedulers = []string{structs.JobTypeService} 244 }) 245 defer s1.Shutdown() 246 testutil.WaitForLeader(t, s1.RPC) 247 248 // Register node 249 node := mock.Node() 250 testRegisterNode(t, s1, node) 251 252 // Create the register request 253 eval1 := mock.Eval() 254 s1.evalBroker.Enqueue(eval1) 255 256 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 257 if err != nil { 258 t.Fatalf("err: %v", err) 259 } 260 if evalOut != eval1 { 261 t.Fatalf("Bad eval") 262 } 263 264 // Create an allocation plan 265 alloc := mock.Alloc() 266 plan := &structs.Plan{ 267 EvalID: eval1.ID, 268 NodeAllocation: map[string][]*structs.Allocation{ 269 node.ID: []*structs.Allocation{alloc}, 270 }, 271 } 272 273 // Attempt to submit a plan 274 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 275 result, state, err := w.SubmitPlan(plan) 276 if err != nil { 277 t.Fatalf("err: %v", err) 278 } 279 280 // Should have no update 281 if state != nil { 282 t.Fatalf("unexpected state update") 283 } 284 285 // Result should have allocated 286 if result == nil { 287 t.Fatalf("missing result") 288 } 289 290 if result.AllocIndex == 0 { 291 t.Fatalf("Bad: %#v", result) 292 } 293 if len(result.NodeAllocation) != 1 { 294 t.Fatalf("Bad: %#v", result) 295 } 296 } 297 298 func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) { 299 s1 := testServer(t, func(c *Config) { 300 c.NumSchedulers = 0 301 c.EnabledSchedulers = []string{structs.JobTypeService} 302 }) 303 defer s1.Shutdown() 304 testutil.WaitForLeader(t, s1.RPC) 305 306 // Register node 307 node := mock.Node() 308 testRegisterNode(t, s1, node) 309 310 // Create the register request 311 eval1 := mock.Eval() 312 s1.evalBroker.Enqueue(eval1) 313 314 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 315 if err != nil { 316 t.Fatalf("err: %v", err) 317 } 318 if evalOut != eval1 { 319 t.Fatalf("Bad eval") 320 } 321 322 // Create an allocation plan, with unregistered node 323 node2 := mock.Node() 324 alloc := mock.Alloc() 325 plan := &structs.Plan{ 326 EvalID: eval1.ID, 327 NodeAllocation: map[string][]*structs.Allocation{ 328 node2.ID: []*structs.Allocation{alloc}, 329 }, 330 } 331 332 // Attempt to submit a plan 333 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 334 result, state, err := w.SubmitPlan(plan) 335 if err != nil { 336 t.Fatalf("err: %v", err) 337 } 338 339 // Result should have allocated 340 if result == nil { 341 t.Fatalf("missing result") 342 } 343 344 // Expect no allocation and forced refresh 345 if result.AllocIndex != 0 { 346 t.Fatalf("Bad: %#v", result) 347 } 348 if result.RefreshIndex == 0 { 349 t.Fatalf("Bad: %#v", result) 350 } 351 if len(result.NodeAllocation) != 0 { 352 t.Fatalf("Bad: %#v", result) 353 } 354 355 // Should have an update 356 if state == nil { 357 t.Fatalf("expected state update") 358 } 359 } 360 361 func TestWorker_UpdateEval(t *testing.T) { 362 s1 := testServer(t, func(c *Config) { 363 c.NumSchedulers = 0 364 c.EnabledSchedulers = []string{structs.JobTypeService} 365 }) 366 defer s1.Shutdown() 367 testutil.WaitForLeader(t, s1.RPC) 368 369 // Register node 370 node := mock.Node() 371 testRegisterNode(t, s1, node) 372 373 // Create the register request 374 eval1 := mock.Eval() 375 s1.evalBroker.Enqueue(eval1) 376 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 377 if err != nil { 378 t.Fatalf("err: %v", err) 379 } 380 if evalOut != eval1 { 381 t.Fatalf("Bad eval") 382 } 383 384 eval2 := evalOut.Copy() 385 eval2.Status = structs.EvalStatusComplete 386 387 // Attempt to update eval 388 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 389 err = w.UpdateEval(eval2) 390 if err != nil { 391 t.Fatalf("err: %v", err) 392 } 393 394 out, err := s1.fsm.State().EvalByID(eval2.ID) 395 if err != nil { 396 t.Fatalf("err: %v", err) 397 } 398 if out.Status != structs.EvalStatusComplete { 399 t.Fatalf("bad: %v", out) 400 } 401 if out.SnapshotIndex != w.snapshotIndex { 402 t.Fatalf("bad: %v", out) 403 } 404 } 405 406 func TestWorker_CreateEval(t *testing.T) { 407 s1 := testServer(t, func(c *Config) { 408 c.NumSchedulers = 0 409 c.EnabledSchedulers = []string{structs.JobTypeService} 410 }) 411 defer s1.Shutdown() 412 testutil.WaitForLeader(t, s1.RPC) 413 414 // Register node 415 node := mock.Node() 416 testRegisterNode(t, s1, node) 417 418 // Create the register request 419 eval1 := mock.Eval() 420 s1.evalBroker.Enqueue(eval1) 421 422 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 423 if err != nil { 424 t.Fatalf("err: %v", err) 425 } 426 if evalOut != eval1 { 427 t.Fatalf("Bad eval") 428 } 429 430 eval2 := mock.Eval() 431 eval2.PreviousEval = eval1.ID 432 433 // Attempt to create eval 434 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 435 err = w.CreateEval(eval2) 436 if err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 440 out, err := s1.fsm.State().EvalByID(eval2.ID) 441 if err != nil { 442 t.Fatalf("err: %v", err) 443 } 444 if out.PreviousEval != eval1.ID { 445 t.Fatalf("bad: %v", out) 446 } 447 if out.SnapshotIndex != w.snapshotIndex { 448 t.Fatalf("bad: %v", out) 449 } 450 } 451 452 func TestWorker_ReblockEval(t *testing.T) { 453 s1 := testServer(t, func(c *Config) { 454 c.NumSchedulers = 0 455 c.EnabledSchedulers = []string{structs.JobTypeService} 456 }) 457 defer s1.Shutdown() 458 testutil.WaitForLeader(t, s1.RPC) 459 460 // Create the blocked eval 461 eval1 := mock.Eval() 462 eval1.Status = structs.EvalStatusBlocked 463 464 // Insert it into the state store 465 if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil { 466 t.Fatal(err) 467 } 468 469 // Enqueue the eval and then dequeue 470 s1.evalBroker.Enqueue(eval1) 471 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 472 if err != nil { 473 t.Fatalf("err: %v", err) 474 } 475 if evalOut != eval1 { 476 t.Fatalf("Bad eval") 477 } 478 479 eval2 := evalOut.Copy() 480 481 // Attempt to reblock eval 482 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 483 err = w.ReblockEval(eval2) 484 if err != nil { 485 t.Fatalf("err: %v", err) 486 } 487 488 // Ack the eval 489 w.sendAck(evalOut.ID, token, true) 490 491 // Check that it is blocked 492 bStats := s1.blockedEvals.Stats() 493 if bStats.TotalBlocked+bStats.TotalEscaped != 1 { 494 t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats) 495 } 496 497 // Check that the snapshot index was set properly by unblocking the eval and 498 // then dequeuing. 499 s1.blockedEvals.Unblock("foobar", 1000) 500 501 reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second) 502 if err != nil { 503 t.Fatalf("err: %v", err) 504 } 505 if reblockedEval == nil { 506 t.Fatalf("Nil eval") 507 } 508 if reblockedEval.ID != eval1.ID { 509 t.Fatalf("Bad eval") 510 } 511 512 // Check that the SnapshotIndex is set 513 if reblockedEval.SnapshotIndex != w.snapshotIndex { 514 t.Fatalf("incorrect snapshot index; got %d; want %d", 515 reblockedEval.SnapshotIndex, w.snapshotIndex) 516 } 517 }