github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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 memdb "github.com/hashicorp/go-memdb" 12 "github.com/hashicorp/nomad/helper/uuid" 13 "github.com/hashicorp/nomad/nomad/mock" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/scheduler" 16 "github.com/hashicorp/nomad/testutil" 17 ) 18 19 type NoopScheduler struct { 20 state scheduler.State 21 planner scheduler.Planner 22 eval *structs.Evaluation 23 err error 24 } 25 26 func (n *NoopScheduler) Process(eval *structs.Evaluation) error { 27 if n.state == nil { 28 panic("missing state") 29 } 30 if n.planner == nil { 31 panic("missing planner") 32 } 33 n.eval = eval 34 return n.err 35 } 36 37 func init() { 38 scheduler.BuiltinSchedulers["noop"] = func(logger *log.Logger, s scheduler.State, p scheduler.Planner) scheduler.Scheduler { 39 n := &NoopScheduler{ 40 state: s, 41 planner: p, 42 } 43 return n 44 } 45 } 46 47 func TestWorker_dequeueEvaluation(t *testing.T) { 48 t.Parallel() 49 s1 := TestServer(t, func(c *Config) { 50 c.NumSchedulers = 0 51 c.EnabledSchedulers = []string{structs.JobTypeService} 52 }) 53 defer s1.Shutdown() 54 testutil.WaitForLeader(t, s1.RPC) 55 56 // Create the evaluation 57 eval1 := mock.Eval() 58 s1.evalBroker.Enqueue(eval1) 59 60 // Create a worker 61 w := &Worker{srv: s1, logger: s1.logger} 62 63 // Attempt dequeue 64 eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 65 if shutdown { 66 t.Fatalf("should not shutdown") 67 } 68 if token == "" { 69 t.Fatalf("should get token") 70 } 71 if waitIndex != eval1.ModifyIndex { 72 t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex) 73 } 74 75 // Ensure we get a sane eval 76 if !reflect.DeepEqual(eval, eval1) { 77 t.Fatalf("bad: %#v %#v", eval, eval1) 78 } 79 } 80 81 // Test that the worker picks up the correct wait index when there are multiple 82 // evals for the same job. 83 func TestWorker_dequeueEvaluation_SerialJobs(t *testing.T) { 84 t.Parallel() 85 s1 := TestServer(t, func(c *Config) { 86 c.NumSchedulers = 0 87 c.EnabledSchedulers = []string{structs.JobTypeService} 88 }) 89 defer s1.Shutdown() 90 testutil.WaitForLeader(t, s1.RPC) 91 92 // Create the evaluation 93 eval1 := mock.Eval() 94 eval2 := mock.Eval() 95 eval2.JobID = eval1.JobID 96 97 // Insert the evals into the state store 98 if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1, eval2}); err != nil { 99 t.Fatal(err) 100 } 101 102 s1.evalBroker.Enqueue(eval1) 103 s1.evalBroker.Enqueue(eval2) 104 105 // Create a worker 106 w := &Worker{srv: s1, logger: s1.logger} 107 108 // Attempt dequeue 109 eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 110 if shutdown { 111 t.Fatalf("should not shutdown") 112 } 113 if token == "" { 114 t.Fatalf("should get token") 115 } 116 if waitIndex != eval1.ModifyIndex { 117 t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex) 118 } 119 120 // Ensure we get a sane eval 121 if !reflect.DeepEqual(eval, eval1) { 122 t.Fatalf("bad: %#v %#v", eval, eval1) 123 } 124 125 // Update the modify index of the first eval 126 if err := s1.fsm.State().UpsertEvals(2000, []*structs.Evaluation{eval1}); err != nil { 127 t.Fatal(err) 128 } 129 130 // Send the Ack 131 w.sendAck(eval1.ID, token, true) 132 133 // Attempt second dequeue 134 eval, token, waitIndex, shutdown = w.dequeueEvaluation(10 * time.Millisecond) 135 if shutdown { 136 t.Fatalf("should not shutdown") 137 } 138 if token == "" { 139 t.Fatalf("should get token") 140 } 141 if waitIndex != 2000 { 142 t.Fatalf("bad wait index; got %d; want 2000", eval2.ModifyIndex) 143 } 144 145 // Ensure we get a sane eval 146 if !reflect.DeepEqual(eval, eval2) { 147 t.Fatalf("bad: %#v %#v", eval, eval2) 148 } 149 } 150 151 func TestWorker_dequeueEvaluation_paused(t *testing.T) { 152 t.Parallel() 153 s1 := TestServer(t, func(c *Config) { 154 c.NumSchedulers = 0 155 c.EnabledSchedulers = []string{structs.JobTypeService} 156 }) 157 defer s1.Shutdown() 158 testutil.WaitForLeader(t, s1.RPC) 159 160 // Create the evaluation 161 eval1 := mock.Eval() 162 s1.evalBroker.Enqueue(eval1) 163 164 // Create a worker 165 w := &Worker{srv: s1, logger: s1.logger} 166 w.pauseCond = sync.NewCond(&w.pauseLock) 167 168 // PAUSE the worker 169 w.SetPause(true) 170 171 go func() { 172 time.Sleep(100 * time.Millisecond) 173 w.SetPause(false) 174 }() 175 176 // Attempt dequeue 177 start := time.Now() 178 eval, token, waitIndex, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 179 if diff := time.Since(start); diff < 100*time.Millisecond { 180 t.Fatalf("should have paused: %v", diff) 181 } 182 if shutdown { 183 t.Fatalf("should not shutdown") 184 } 185 if token == "" { 186 t.Fatalf("should get token") 187 } 188 if waitIndex != eval1.ModifyIndex { 189 t.Fatalf("bad wait index; got %d; want %d", waitIndex, eval1.ModifyIndex) 190 } 191 192 // Ensure we get a sane eval 193 if !reflect.DeepEqual(eval, eval1) { 194 t.Fatalf("bad: %#v %#v", eval, eval1) 195 } 196 } 197 198 func TestWorker_dequeueEvaluation_shutdown(t *testing.T) { 199 t.Parallel() 200 s1 := TestServer(t, func(c *Config) { 201 c.NumSchedulers = 0 202 c.EnabledSchedulers = []string{structs.JobTypeService} 203 }) 204 defer s1.Shutdown() 205 testutil.WaitForLeader(t, s1.RPC) 206 207 // Create a worker 208 w := &Worker{srv: s1, logger: s1.logger} 209 210 go func() { 211 time.Sleep(10 * time.Millisecond) 212 s1.Shutdown() 213 }() 214 215 // Attempt dequeue 216 eval, _, _, shutdown := w.dequeueEvaluation(10 * time.Millisecond) 217 if !shutdown { 218 t.Fatalf("should not shutdown") 219 } 220 221 // Ensure we get a sane eval 222 if eval != nil { 223 t.Fatalf("bad: %#v", eval) 224 } 225 } 226 227 func TestWorker_sendAck(t *testing.T) { 228 t.Parallel() 229 s1 := TestServer(t, func(c *Config) { 230 c.NumSchedulers = 0 231 c.EnabledSchedulers = []string{structs.JobTypeService} 232 }) 233 defer s1.Shutdown() 234 testutil.WaitForLeader(t, s1.RPC) 235 236 // Create the evaluation 237 eval1 := mock.Eval() 238 s1.evalBroker.Enqueue(eval1) 239 240 // Create a worker 241 w := &Worker{srv: s1, logger: s1.logger} 242 243 // Attempt dequeue 244 eval, token, _, _ := w.dequeueEvaluation(10 * time.Millisecond) 245 246 // Check the depth is 0, 1 unacked 247 stats := s1.evalBroker.Stats() 248 if stats.TotalReady != 0 && stats.TotalUnacked != 1 { 249 t.Fatalf("bad: %#v", stats) 250 } 251 252 // Send the Nack 253 w.sendAck(eval.ID, token, false) 254 255 // Check the depth is 1, nothing unacked 256 stats = s1.evalBroker.Stats() 257 if stats.TotalReady != 1 && stats.TotalUnacked != 0 { 258 t.Fatalf("bad: %#v", stats) 259 } 260 261 // Attempt dequeue 262 eval, token, _, _ = w.dequeueEvaluation(10 * time.Millisecond) 263 264 // Send the Ack 265 w.sendAck(eval.ID, token, true) 266 267 // Check the depth is 0 268 stats = s1.evalBroker.Stats() 269 if stats.TotalReady != 0 && stats.TotalUnacked != 0 { 270 t.Fatalf("bad: %#v", stats) 271 } 272 } 273 274 func TestWorker_waitForIndex(t *testing.T) { 275 t.Parallel() 276 s1 := TestServer(t, func(c *Config) { 277 c.NumSchedulers = 0 278 c.EnabledSchedulers = []string{structs.JobTypeService} 279 }) 280 defer s1.Shutdown() 281 testutil.WaitForLeader(t, s1.RPC) 282 283 // Get the current index 284 index := s1.raft.AppliedIndex() 285 286 // Cause an increment 287 go func() { 288 time.Sleep(10 * time.Millisecond) 289 n := mock.Node() 290 if err := s1.fsm.state.UpsertNode(index+1, n); err != nil { 291 t.Fatalf("failed to upsert node: %v", err) 292 } 293 }() 294 295 // Wait for a future index 296 w := &Worker{srv: s1, logger: s1.logger} 297 err := w.waitForIndex(index+1, time.Second) 298 if err != nil { 299 t.Fatalf("err: %v", err) 300 } 301 302 // Cause a timeout 303 err = w.waitForIndex(index+100, 10*time.Millisecond) 304 if err == nil || !strings.Contains(err.Error(), "timeout") { 305 t.Fatalf("err: %v", err) 306 } 307 } 308 309 func TestWorker_invokeScheduler(t *testing.T) { 310 t.Parallel() 311 s1 := TestServer(t, func(c *Config) { 312 c.NumSchedulers = 0 313 c.EnabledSchedulers = []string{structs.JobTypeService} 314 }) 315 defer s1.Shutdown() 316 317 w := &Worker{srv: s1, logger: s1.logger} 318 eval := mock.Eval() 319 eval.Type = "noop" 320 321 err := w.invokeScheduler(eval, uuid.Generate()) 322 if err != nil { 323 t.Fatalf("err: %v", err) 324 } 325 } 326 327 func TestWorker_SubmitPlan(t *testing.T) { 328 t.Parallel() 329 s1 := TestServer(t, func(c *Config) { 330 c.NumSchedulers = 0 331 c.EnabledSchedulers = []string{structs.JobTypeService} 332 }) 333 defer s1.Shutdown() 334 testutil.WaitForLeader(t, s1.RPC) 335 336 // Register node 337 node := mock.Node() 338 testRegisterNode(t, s1, node) 339 340 job := mock.Job() 341 eval1 := mock.Eval() 342 eval1.JobID = job.ID 343 s1.fsm.State().UpsertJob(1000, job) 344 s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}) 345 346 // Create the register request 347 s1.evalBroker.Enqueue(eval1) 348 349 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 350 if err != nil { 351 t.Fatalf("err: %v", err) 352 } 353 if evalOut != eval1 { 354 t.Fatalf("Bad eval") 355 } 356 357 // Create an allocation plan 358 alloc := mock.Alloc() 359 plan := &structs.Plan{ 360 Job: job, 361 EvalID: eval1.ID, 362 NodeAllocation: map[string][]*structs.Allocation{ 363 node.ID: {alloc}, 364 }, 365 } 366 367 // Attempt to submit a plan 368 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 369 result, state, err := w.SubmitPlan(plan) 370 if err != nil { 371 t.Fatalf("err: %v", err) 372 } 373 374 // Should have no update 375 if state != nil { 376 t.Fatalf("unexpected state update") 377 } 378 379 // Result should have allocated 380 if result == nil { 381 t.Fatalf("missing result") 382 } 383 384 if result.AllocIndex == 0 { 385 t.Fatalf("Bad: %#v", result) 386 } 387 if len(result.NodeAllocation) != 1 { 388 t.Fatalf("Bad: %#v", result) 389 } 390 } 391 392 func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) { 393 t.Parallel() 394 s1 := TestServer(t, func(c *Config) { 395 c.NumSchedulers = 0 396 c.EnabledSchedulers = []string{structs.JobTypeService} 397 }) 398 defer s1.Shutdown() 399 testutil.WaitForLeader(t, s1.RPC) 400 401 // Register node 402 node := mock.Node() 403 testRegisterNode(t, s1, node) 404 405 // Create the job 406 job := mock.Job() 407 s1.fsm.State().UpsertJob(1000, job) 408 409 // Create the register request 410 eval1 := mock.Eval() 411 eval1.JobID = job.ID 412 s1.evalBroker.Enqueue(eval1) 413 414 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 415 if err != nil { 416 t.Fatalf("err: %v", err) 417 } 418 if evalOut != eval1 { 419 t.Fatalf("Bad eval") 420 } 421 422 // Create an allocation plan, with unregistered node 423 node2 := mock.Node() 424 alloc := mock.Alloc() 425 plan := &structs.Plan{ 426 Job: job, 427 EvalID: eval1.ID, 428 NodeAllocation: map[string][]*structs.Allocation{ 429 node2.ID: {alloc}, 430 }, 431 } 432 433 // Attempt to submit a plan 434 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 435 result, state, err := w.SubmitPlan(plan) 436 if err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 440 // Result should have allocated 441 if result == nil { 442 t.Fatalf("missing result") 443 } 444 445 // Expect no allocation and forced refresh 446 if result.AllocIndex != 0 { 447 t.Fatalf("Bad: %#v", result) 448 } 449 if result.RefreshIndex == 0 { 450 t.Fatalf("Bad: %#v", result) 451 } 452 if len(result.NodeAllocation) != 0 { 453 t.Fatalf("Bad: %#v", result) 454 } 455 456 // Should have an update 457 if state == nil { 458 t.Fatalf("expected state update") 459 } 460 } 461 462 func TestWorker_UpdateEval(t *testing.T) { 463 t.Parallel() 464 s1 := TestServer(t, func(c *Config) { 465 c.NumSchedulers = 0 466 c.EnabledSchedulers = []string{structs.JobTypeService} 467 }) 468 defer s1.Shutdown() 469 testutil.WaitForLeader(t, s1.RPC) 470 471 // Register node 472 node := mock.Node() 473 testRegisterNode(t, s1, node) 474 475 // Create the register request 476 eval1 := mock.Eval() 477 s1.evalBroker.Enqueue(eval1) 478 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 479 if err != nil { 480 t.Fatalf("err: %v", err) 481 } 482 if evalOut != eval1 { 483 t.Fatalf("Bad eval") 484 } 485 486 eval2 := evalOut.Copy() 487 eval2.Status = structs.EvalStatusComplete 488 489 // Attempt to update eval 490 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 491 err = w.UpdateEval(eval2) 492 if err != nil { 493 t.Fatalf("err: %v", err) 494 } 495 496 ws := memdb.NewWatchSet() 497 out, err := s1.fsm.State().EvalByID(ws, eval2.ID) 498 if err != nil { 499 t.Fatalf("err: %v", err) 500 } 501 if out.Status != structs.EvalStatusComplete { 502 t.Fatalf("bad: %v", out) 503 } 504 if out.SnapshotIndex != w.snapshotIndex { 505 t.Fatalf("bad: %v", out) 506 } 507 } 508 509 func TestWorker_CreateEval(t *testing.T) { 510 t.Parallel() 511 s1 := TestServer(t, func(c *Config) { 512 c.NumSchedulers = 0 513 c.EnabledSchedulers = []string{structs.JobTypeService} 514 }) 515 defer s1.Shutdown() 516 testutil.WaitForLeader(t, s1.RPC) 517 518 // Register node 519 node := mock.Node() 520 testRegisterNode(t, s1, node) 521 522 // Create the register request 523 eval1 := mock.Eval() 524 s1.evalBroker.Enqueue(eval1) 525 526 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 527 if err != nil { 528 t.Fatalf("err: %v", err) 529 } 530 if evalOut != eval1 { 531 t.Fatalf("Bad eval") 532 } 533 534 eval2 := mock.Eval() 535 eval2.PreviousEval = eval1.ID 536 537 // Attempt to create eval 538 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 539 err = w.CreateEval(eval2) 540 if err != nil { 541 t.Fatalf("err: %v", err) 542 } 543 544 ws := memdb.NewWatchSet() 545 out, err := s1.fsm.State().EvalByID(ws, eval2.ID) 546 if err != nil { 547 t.Fatalf("err: %v", err) 548 } 549 if out.PreviousEval != eval1.ID { 550 t.Fatalf("bad: %v", out) 551 } 552 if out.SnapshotIndex != w.snapshotIndex { 553 t.Fatalf("bad: %v", out) 554 } 555 } 556 557 func TestWorker_ReblockEval(t *testing.T) { 558 t.Parallel() 559 s1 := TestServer(t, func(c *Config) { 560 c.NumSchedulers = 0 561 c.EnabledSchedulers = []string{structs.JobTypeService} 562 }) 563 defer s1.Shutdown() 564 testutil.WaitForLeader(t, s1.RPC) 565 566 // Create the blocked eval 567 eval1 := mock.Eval() 568 eval1.Status = structs.EvalStatusBlocked 569 eval1.QueuedAllocations = map[string]int{"cache": 100} 570 571 // Insert it into the state store 572 if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil { 573 t.Fatal(err) 574 } 575 576 // Create the job summary 577 js := mock.JobSummary(eval1.JobID) 578 tg := js.Summary["web"] 579 tg.Queued = 100 580 js.Summary["web"] = tg 581 if err := s1.fsm.State().UpsertJobSummary(1001, js); err != nil { 582 t.Fatal(err) 583 } 584 585 // Enqueue the eval and then dequeue 586 s1.evalBroker.Enqueue(eval1) 587 evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second) 588 if err != nil { 589 t.Fatalf("err: %v", err) 590 } 591 if evalOut != eval1 { 592 t.Fatalf("Bad eval") 593 } 594 595 eval2 := evalOut.Copy() 596 eval2.QueuedAllocations = map[string]int{"web": 50} 597 598 // Attempt to reblock eval 599 w := &Worker{srv: s1, logger: s1.logger, evalToken: token} 600 err = w.ReblockEval(eval2) 601 if err != nil { 602 t.Fatalf("err: %v", err) 603 } 604 605 // Ack the eval 606 w.sendAck(evalOut.ID, token, true) 607 608 // Check that it is blocked 609 bStats := s1.blockedEvals.Stats() 610 if bStats.TotalBlocked+bStats.TotalEscaped != 1 { 611 t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats) 612 } 613 614 // Check that the eval was updated 615 ws := memdb.NewWatchSet() 616 eval, err := s1.fsm.State().EvalByID(ws, eval2.ID) 617 if err != nil { 618 t.Fatal(err) 619 } 620 if !reflect.DeepEqual(eval.QueuedAllocations, eval2.QueuedAllocations) { 621 t.Fatalf("expected: %#v, actual: %#v", eval2.QueuedAllocations, eval.QueuedAllocations) 622 } 623 624 // Check that the snapshot index was set properly by unblocking the eval and 625 // then dequeuing. 626 s1.blockedEvals.Unblock("foobar", 1000) 627 628 reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second) 629 if err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 if reblockedEval == nil { 633 t.Fatalf("Nil eval") 634 } 635 if reblockedEval.ID != eval1.ID { 636 t.Fatalf("Bad eval") 637 } 638 639 // Check that the SnapshotIndex is set 640 if reblockedEval.SnapshotIndex != w.snapshotIndex { 641 t.Fatalf("incorrect snapshot index; got %d; want %d", 642 reblockedEval.SnapshotIndex, w.snapshotIndex) 643 } 644 }