github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/eval_broker_test.go (about) 1 package nomad 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/nomad/nomad/mock" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/hashicorp/nomad/testutil" 11 "github.com/stretchr/testify/require" 12 ) 13 14 var ( 15 defaultSched = []string{ 16 structs.JobTypeService, 17 structs.JobTypeBatch, 18 } 19 ) 20 21 func testBrokerConfig() *Config { 22 config := DefaultConfig() 23 24 // Tune the Nack timeout 25 config.EvalNackTimeout = 5 * time.Second 26 27 // Tune the Nack delay 28 config.EvalNackInitialReenqueueDelay = 5 * time.Millisecond 29 config.EvalNackSubsequentReenqueueDelay = 50 * time.Millisecond 30 return config 31 } 32 33 func testBroker(t *testing.T, timeout time.Duration) *EvalBroker { 34 config := testBrokerConfig() 35 36 if timeout != 0 { 37 config.EvalNackTimeout = timeout 38 } 39 40 return testBrokerFromConfig(t, config) 41 } 42 43 func testBrokerFromConfig(t *testing.T, c *Config) *EvalBroker { 44 b, err := NewEvalBroker(c.EvalNackTimeout, c.EvalNackInitialReenqueueDelay, c.EvalNackSubsequentReenqueueDelay, 3) 45 if err != nil { 46 t.Fatalf("err: %v", err) 47 } 48 49 return b 50 } 51 52 func TestEvalBroker_Enqueue_Dequeue_Nack_Ack(t *testing.T) { 53 t.Parallel() 54 b := testBroker(t, 0) 55 56 // Enqueue, but broker is disabled! 57 eval := mock.Eval() 58 b.Enqueue(eval) 59 60 // Verify nothing was done 61 stats := b.Stats() 62 if stats.TotalReady != 0 { 63 t.Fatalf("bad: %#v", stats) 64 } 65 66 if b.Enabled() { 67 t.Fatalf("should not be enabled") 68 } 69 70 // Enable the broker, and enqueue 71 b.SetEnabled(true) 72 b.Enqueue(eval) 73 74 // Double enqueue is a no-op 75 b.Enqueue(eval) 76 77 if !b.Enabled() { 78 t.Fatalf("should be enabled") 79 } 80 81 // Verify enqueue is done 82 stats = b.Stats() 83 if stats.TotalReady != 1 { 84 t.Fatalf("bad: %#v", stats) 85 } 86 if stats.ByScheduler[eval.Type].Ready != 1 { 87 t.Fatalf("bad: %#v", stats) 88 } 89 90 // Dequeue should work 91 out, token, err := b.Dequeue(defaultSched, time.Second) 92 if err != nil { 93 t.Fatalf("err: %v", err) 94 } 95 if out != eval { 96 t.Fatalf("bad : %#v", out) 97 } 98 99 tokenOut, ok := b.Outstanding(out.ID) 100 if !ok { 101 t.Fatalf("should be outstanding") 102 } 103 if tokenOut != token { 104 t.Fatalf("Bad: %#v %#v", token, tokenOut) 105 } 106 107 // OutstandingReset should verify the token 108 err = b.OutstandingReset("nope", "foo") 109 if err != ErrNotOutstanding { 110 t.Fatalf("err: %v", err) 111 } 112 err = b.OutstandingReset(out.ID, "foo") 113 if err != ErrTokenMismatch { 114 t.Fatalf("err: %v", err) 115 } 116 err = b.OutstandingReset(out.ID, tokenOut) 117 if err != nil { 118 t.Fatalf("err: %v", err) 119 } 120 121 // Check the stats 122 stats = b.Stats() 123 if stats.TotalReady != 0 { 124 t.Fatalf("bad: %#v", stats) 125 } 126 if stats.TotalUnacked != 1 { 127 t.Fatalf("bad: %#v", stats) 128 } 129 if stats.ByScheduler[eval.Type].Ready != 0 { 130 t.Fatalf("bad: %#v", stats) 131 } 132 if stats.ByScheduler[eval.Type].Unacked != 1 { 133 t.Fatalf("bad: %#v", stats) 134 } 135 136 // Nack with wrong token should fail 137 err = b.Nack(eval.ID, "foobarbaz") 138 if err == nil { 139 t.Fatalf("should fail to nack") 140 } 141 142 // Nack back into the queue 143 err = b.Nack(eval.ID, token) 144 if err != nil { 145 t.Fatalf("err: %v", err) 146 } 147 148 if _, ok := b.Outstanding(out.ID); ok { 149 t.Fatalf("should not be outstanding") 150 } 151 152 // Check the stats 153 testutil.WaitForResult(func() (bool, error) { 154 stats = b.Stats() 155 if stats.TotalReady != 1 { 156 return false, fmt.Errorf("bad: %#v", stats) 157 } 158 if stats.TotalUnacked != 0 { 159 return false, fmt.Errorf("bad: %#v", stats) 160 } 161 if stats.TotalWaiting != 0 { 162 return false, fmt.Errorf("bad: %#v", stats) 163 } 164 if stats.ByScheduler[eval.Type].Ready != 1 { 165 return false, fmt.Errorf("bad: %#v", stats) 166 } 167 if stats.ByScheduler[eval.Type].Unacked != 0 { 168 return false, fmt.Errorf("bad: %#v", stats) 169 } 170 171 return true, nil 172 }, func(e error) { 173 t.Fatal(e) 174 }) 175 176 // Dequeue should work again 177 out2, token2, err := b.Dequeue(defaultSched, time.Second) 178 if err != nil { 179 t.Fatalf("err: %v", err) 180 } 181 if out2 != eval { 182 t.Fatalf("bad : %#v", out2) 183 } 184 if token2 == token { 185 t.Fatalf("should get a new token") 186 } 187 188 tokenOut2, ok := b.Outstanding(out.ID) 189 if !ok { 190 t.Fatalf("should be outstanding") 191 } 192 if tokenOut2 != token2 { 193 t.Fatalf("Bad: %#v %#v", token2, tokenOut2) 194 } 195 196 // Ack with wrong token 197 err = b.Ack(eval.ID, "zip") 198 if err == nil { 199 t.Fatalf("should fail to ack") 200 } 201 202 // Ack finally 203 err = b.Ack(eval.ID, token2) 204 if err != nil { 205 t.Fatalf("err: %v", err) 206 } 207 208 if _, ok := b.Outstanding(out.ID); ok { 209 t.Fatalf("should not be outstanding") 210 } 211 212 // Check the stats 213 stats = b.Stats() 214 if stats.TotalReady != 0 { 215 t.Fatalf("bad: %#v", stats) 216 } 217 if stats.TotalUnacked != 0 { 218 t.Fatalf("bad: %#v", stats) 219 } 220 if stats.ByScheduler[eval.Type].Ready != 0 { 221 t.Fatalf("bad: %#v", stats) 222 } 223 if stats.ByScheduler[eval.Type].Unacked != 0 { 224 t.Fatalf("bad: %#v", stats) 225 } 226 } 227 228 func TestEvalBroker_Nack_Delay(t *testing.T) { 229 t.Parallel() 230 b := testBroker(t, 0) 231 232 // Enqueue, but broker is disabled! 233 b.SetEnabled(true) 234 eval := mock.Eval() 235 b.Enqueue(eval) 236 237 // Dequeue should work 238 out, token, err := b.Dequeue(defaultSched, time.Second) 239 if err != nil { 240 t.Fatalf("err: %v", err) 241 } 242 if out != eval { 243 t.Fatalf("bad : %#v", out) 244 } 245 246 // Nack back into the queue 247 err = b.Nack(eval.ID, token) 248 if err != nil { 249 t.Fatalf("err: %v", err) 250 } 251 252 if _, ok := b.Outstanding(out.ID); ok { 253 t.Fatalf("should not be outstanding") 254 } 255 256 // Check the stats to ensure that it is waiting 257 stats := b.Stats() 258 if stats.TotalReady != 0 { 259 t.Fatalf("bad: %#v", stats) 260 } 261 if stats.TotalUnacked != 0 { 262 t.Fatalf("bad: %#v", stats) 263 } 264 if stats.TotalWaiting != 1 { 265 t.Fatalf("bad: %#v", stats) 266 } 267 if stats.ByScheduler[eval.Type].Ready != 0 { 268 t.Fatalf("bad: %#v", stats) 269 } 270 if stats.ByScheduler[eval.Type].Unacked != 0 { 271 t.Fatalf("bad: %#v", stats) 272 } 273 274 // Now wait for it to be re-enqueued 275 testutil.WaitForResult(func() (bool, error) { 276 stats = b.Stats() 277 if stats.TotalReady != 1 { 278 return false, fmt.Errorf("bad: %#v", stats) 279 } 280 if stats.TotalUnacked != 0 { 281 return false, fmt.Errorf("bad: %#v", stats) 282 } 283 if stats.TotalWaiting != 0 { 284 return false, fmt.Errorf("bad: %#v", stats) 285 } 286 if stats.ByScheduler[eval.Type].Ready != 1 { 287 return false, fmt.Errorf("bad: %#v", stats) 288 } 289 if stats.ByScheduler[eval.Type].Unacked != 0 { 290 return false, fmt.Errorf("bad: %#v", stats) 291 } 292 293 return true, nil 294 }, func(e error) { 295 t.Fatal(e) 296 }) 297 298 // Dequeue should work again 299 out2, token2, err := b.Dequeue(defaultSched, time.Second) 300 if err != nil { 301 t.Fatalf("err: %v", err) 302 } 303 if out2 != eval { 304 t.Fatalf("bad : %#v", out2) 305 } 306 if token2 == token { 307 t.Fatalf("should get a new token") 308 } 309 310 // Capture the time 311 start := time.Now() 312 313 // Nack back into the queue 314 err = b.Nack(eval.ID, token2) 315 if err != nil { 316 t.Fatalf("err: %v", err) 317 } 318 319 // Now wait for it to be re-enqueued 320 testutil.WaitForResult(func() (bool, error) { 321 stats = b.Stats() 322 if stats.TotalReady != 1 { 323 return false, fmt.Errorf("bad: %#v", stats) 324 } 325 if stats.TotalUnacked != 0 { 326 return false, fmt.Errorf("bad: %#v", stats) 327 } 328 if stats.TotalWaiting != 0 { 329 return false, fmt.Errorf("bad: %#v", stats) 330 } 331 if stats.ByScheduler[eval.Type].Ready != 1 { 332 return false, fmt.Errorf("bad: %#v", stats) 333 } 334 if stats.ByScheduler[eval.Type].Unacked != 0 { 335 return false, fmt.Errorf("bad: %#v", stats) 336 } 337 338 return true, nil 339 }, func(e error) { 340 t.Fatal(e) 341 }) 342 343 delay := time.Now().Sub(start) 344 if delay < b.subsequentNackDelay { 345 t.Fatalf("bad: delay was %v; want at least %v", delay, b.subsequentNackDelay) 346 } 347 348 // Dequeue should work again 349 out3, token3, err := b.Dequeue(defaultSched, time.Second) 350 if err != nil { 351 t.Fatalf("err: %v", err) 352 } 353 if out3 != eval { 354 t.Fatalf("bad : %#v", out3) 355 } 356 if token3 == token || token3 == token2 { 357 t.Fatalf("should get a new token") 358 } 359 360 // Ack finally 361 err = b.Ack(eval.ID, token3) 362 if err != nil { 363 t.Fatalf("err: %v", err) 364 } 365 366 if _, ok := b.Outstanding(out.ID); ok { 367 t.Fatalf("should not be outstanding") 368 } 369 370 // Check the stats 371 stats = b.Stats() 372 if stats.TotalReady != 0 { 373 t.Fatalf("bad: %#v", stats) 374 } 375 if stats.TotalUnacked != 0 { 376 t.Fatalf("bad: %#v", stats) 377 } 378 if stats.ByScheduler[eval.Type].Ready != 0 { 379 t.Fatalf("bad: %#v", stats) 380 } 381 if stats.ByScheduler[eval.Type].Unacked != 0 { 382 t.Fatalf("bad: %#v", stats) 383 } 384 } 385 386 func TestEvalBroker_Serialize_DuplicateJobID(t *testing.T) { 387 t.Parallel() 388 b := testBroker(t, 0) 389 b.SetEnabled(true) 390 391 ns1 := "namespace-one" 392 ns2 := "namespace-two" 393 eval := mock.Eval() 394 eval.Namespace = ns1 395 b.Enqueue(eval) 396 397 eval2 := mock.Eval() 398 eval2.JobID = eval.JobID 399 eval2.Namespace = ns1 400 eval2.CreateIndex = eval.CreateIndex + 1 401 b.Enqueue(eval2) 402 403 eval3 := mock.Eval() 404 eval3.JobID = eval.JobID 405 eval3.Namespace = ns1 406 eval3.CreateIndex = eval.CreateIndex + 2 407 b.Enqueue(eval3) 408 409 eval4 := mock.Eval() 410 eval4.JobID = eval.JobID 411 eval4.Namespace = ns2 412 eval4.CreateIndex = eval.CreateIndex + 3 413 b.Enqueue(eval4) 414 415 eval5 := mock.Eval() 416 eval5.JobID = eval.JobID 417 eval5.Namespace = ns2 418 eval5.CreateIndex = eval.CreateIndex + 4 419 b.Enqueue(eval5) 420 421 stats := b.Stats() 422 if stats.TotalReady != 2 { 423 t.Fatalf("bad: %#v", stats) 424 } 425 if stats.TotalBlocked != 3 { 426 t.Fatalf("bad: %#v", stats) 427 } 428 429 // Dequeue should work 430 out, token, err := b.Dequeue(defaultSched, time.Second) 431 if err != nil { 432 t.Fatalf("err: %v", err) 433 } 434 if out != eval { 435 t.Fatalf("bad : %#v", out) 436 } 437 438 // Check the stats 439 stats = b.Stats() 440 if stats.TotalReady != 1 { 441 t.Fatalf("bad: %#v", stats) 442 } 443 if stats.TotalUnacked != 1 { 444 t.Fatalf("bad: %#v", stats) 445 } 446 if stats.TotalBlocked != 3 { 447 t.Fatalf("bad: %#v", stats) 448 } 449 450 // Ack out 451 err = b.Ack(eval.ID, token) 452 if err != nil { 453 t.Fatalf("err: %v", err) 454 } 455 456 // Check the stats 457 stats = b.Stats() 458 if stats.TotalReady != 2 { 459 t.Fatalf("bad: %#v", stats) 460 } 461 if stats.TotalUnacked != 0 { 462 t.Fatalf("bad: %#v", stats) 463 } 464 if stats.TotalBlocked != 2 { 465 t.Fatalf("bad: %#v", stats) 466 } 467 468 // Dequeue should work 469 out, token, err = b.Dequeue(defaultSched, time.Second) 470 if err != nil { 471 t.Fatalf("err: %v", err) 472 } 473 if out != eval2 { 474 t.Fatalf("bad : %#v", out) 475 } 476 477 // Check the stats 478 stats = b.Stats() 479 if stats.TotalReady != 1 { 480 t.Fatalf("bad: %#v", stats) 481 } 482 if stats.TotalUnacked != 1 { 483 t.Fatalf("bad: %#v", stats) 484 } 485 if stats.TotalBlocked != 2 { 486 t.Fatalf("bad: %#v", stats) 487 } 488 489 // Ack out 490 err = b.Ack(eval2.ID, token) 491 if err != nil { 492 t.Fatalf("err: %v", err) 493 } 494 495 // Check the stats 496 stats = b.Stats() 497 if stats.TotalReady != 2 { 498 t.Fatalf("bad: %#v", stats) 499 } 500 if stats.TotalUnacked != 0 { 501 t.Fatalf("bad: %#v", stats) 502 } 503 if stats.TotalBlocked != 1 { 504 t.Fatalf("bad: %#v", stats) 505 } 506 507 // Dequeue should work 508 out, token, err = b.Dequeue(defaultSched, time.Second) 509 if err != nil { 510 t.Fatalf("err: %v", err) 511 } 512 if out != eval3 { 513 t.Fatalf("bad : %#v", out) 514 } 515 516 // Check the stats 517 stats = b.Stats() 518 if stats.TotalReady != 1 { 519 t.Fatalf("bad: %#v", stats) 520 } 521 if stats.TotalUnacked != 1 { 522 t.Fatalf("bad: %#v", stats) 523 } 524 if stats.TotalBlocked != 1 { 525 t.Fatalf("bad: %#v", stats) 526 } 527 528 // Ack out 529 err = b.Ack(eval3.ID, token) 530 if err != nil { 531 t.Fatalf("err: %v", err) 532 } 533 534 // Check the stats 535 stats = b.Stats() 536 if stats.TotalReady != 1 { 537 t.Fatalf("bad: %#v", stats) 538 } 539 if stats.TotalUnacked != 0 { 540 t.Fatalf("bad: %#v", stats) 541 } 542 if stats.TotalBlocked != 1 { 543 t.Fatalf("bad: %#v", stats) 544 } 545 546 // Dequeue should work 547 out, token, err = b.Dequeue(defaultSched, time.Second) 548 if err != nil { 549 t.Fatalf("err: %v", err) 550 } 551 if out != eval4 { 552 t.Fatalf("bad : %#v", out) 553 } 554 555 // Check the stats 556 stats = b.Stats() 557 if stats.TotalReady != 0 { 558 t.Fatalf("bad: %#v", stats) 559 } 560 if stats.TotalUnacked != 1 { 561 t.Fatalf("bad: %#v", stats) 562 } 563 if stats.TotalBlocked != 1 { 564 t.Fatalf("bad: %#v", stats) 565 } 566 567 // Ack out 568 err = b.Ack(eval4.ID, token) 569 if err != nil { 570 t.Fatalf("err: %v", err) 571 } 572 573 // Check the stats 574 stats = b.Stats() 575 if stats.TotalReady != 1 { 576 t.Fatalf("bad: %#v", stats) 577 } 578 if stats.TotalUnacked != 0 { 579 t.Fatalf("bad: %#v", stats) 580 } 581 if stats.TotalBlocked != 0 { 582 t.Fatalf("bad: %#v", stats) 583 } 584 585 // Dequeue should work 586 out, token, err = b.Dequeue(defaultSched, time.Second) 587 if err != nil { 588 t.Fatalf("err: %v", err) 589 } 590 if out != eval5 { 591 t.Fatalf("bad : %#v", out) 592 } 593 594 // Check the stats 595 stats = b.Stats() 596 if stats.TotalReady != 0 { 597 t.Fatalf("bad: %#v", stats) 598 } 599 if stats.TotalUnacked != 1 { 600 t.Fatalf("bad: %#v", stats) 601 } 602 if stats.TotalBlocked != 0 { 603 t.Fatalf("bad: %#v", stats) 604 } 605 606 // Ack out 607 err = b.Ack(eval5.ID, token) 608 if err != nil { 609 t.Fatalf("err: %v", err) 610 } 611 612 // Check the stats 613 stats = b.Stats() 614 if stats.TotalReady != 0 { 615 t.Fatalf("bad: %#v", stats) 616 } 617 if stats.TotalUnacked != 0 { 618 t.Fatalf("bad: %#v", stats) 619 } 620 if stats.TotalBlocked != 0 { 621 t.Fatalf("bad: %#v", stats) 622 } 623 } 624 625 func TestEvalBroker_Enqueue_Disable(t *testing.T) { 626 t.Parallel() 627 b := testBroker(t, 0) 628 629 // Enqueue 630 eval := mock.Eval() 631 b.SetEnabled(true) 632 b.Enqueue(eval) 633 634 // Flush via SetEnabled 635 b.SetEnabled(false) 636 637 // Check the stats 638 stats := b.Stats() 639 if stats.TotalReady != 0 { 640 t.Fatalf("bad: %#v", stats) 641 } 642 if stats.TotalUnacked != 0 { 643 t.Fatalf("bad: %#v", stats) 644 } 645 if _, ok := stats.ByScheduler[eval.Type]; ok { 646 t.Fatalf("bad: %#v", stats) 647 } 648 } 649 650 func TestEvalBroker_Dequeue_Timeout(t *testing.T) { 651 t.Parallel() 652 b := testBroker(t, 0) 653 b.SetEnabled(true) 654 655 start := time.Now() 656 out, _, err := b.Dequeue(defaultSched, 5*time.Millisecond) 657 end := time.Now() 658 659 if err != nil { 660 t.Fatalf("err: %v", err) 661 } 662 if out != nil { 663 t.Fatalf("unexpected: %#v", out) 664 } 665 666 if diff := end.Sub(start); diff < 5*time.Millisecond { 667 t.Fatalf("bad: %#v", diff) 668 } 669 } 670 671 func TestEvalBroker_Dequeue_Empty_Timeout(t *testing.T) { 672 t.Parallel() 673 b := testBroker(t, 0) 674 b.SetEnabled(true) 675 doneCh := make(chan struct{}, 1) 676 677 go func() { 678 out, _, err := b.Dequeue(defaultSched, 0) 679 if err != nil { 680 t.Fatalf("err: %v", err) 681 } 682 if out == nil { 683 t.Fatal("Expect an eval") 684 } 685 doneCh <- struct{}{} 686 }() 687 688 // Sleep for a little bit 689 select { 690 case <-time.After(5 * time.Millisecond): 691 case <-doneCh: 692 t.Fatalf("Dequeue(0) should block") 693 } 694 695 // Enqueue to unblock the dequeue. 696 eval := mock.Eval() 697 b.Enqueue(eval) 698 699 select { 700 case <-doneCh: 701 return 702 case <-time.After(5 * time.Millisecond): 703 t.Fatal("timeout: Dequeue(0) should return after enqueue") 704 } 705 } 706 707 // Ensure higher priority dequeued first 708 func TestEvalBroker_Dequeue_Priority(t *testing.T) { 709 t.Parallel() 710 b := testBroker(t, 0) 711 b.SetEnabled(true) 712 713 eval1 := mock.Eval() 714 eval1.Priority = 10 715 b.Enqueue(eval1) 716 717 eval2 := mock.Eval() 718 eval2.Priority = 30 719 b.Enqueue(eval2) 720 721 eval3 := mock.Eval() 722 eval3.Priority = 20 723 b.Enqueue(eval3) 724 725 out1, _, _ := b.Dequeue(defaultSched, time.Second) 726 if out1 != eval2 { 727 t.Fatalf("bad: %#v", out1) 728 } 729 730 out2, _, _ := b.Dequeue(defaultSched, time.Second) 731 if out2 != eval3 { 732 t.Fatalf("bad: %#v", out2) 733 } 734 735 out3, _, _ := b.Dequeue(defaultSched, time.Second) 736 if out3 != eval1 { 737 t.Fatalf("bad: %#v", out3) 738 } 739 } 740 741 // Ensure FIFO at fixed priority 742 func TestEvalBroker_Dequeue_FIFO(t *testing.T) { 743 t.Parallel() 744 b := testBroker(t, 0) 745 b.SetEnabled(true) 746 NUM := 100 747 748 for i := 0; i < NUM; i++ { 749 eval1 := mock.Eval() 750 eval1.CreateIndex = uint64(i) 751 eval1.ModifyIndex = uint64(i) 752 b.Enqueue(eval1) 753 } 754 755 for i := 0; i < NUM; i++ { 756 out1, _, _ := b.Dequeue(defaultSched, time.Second) 757 if out1.CreateIndex != uint64(i) { 758 t.Fatalf("bad: %d %#v", i, out1) 759 } 760 } 761 } 762 763 // Ensure fairness between schedulers 764 func TestEvalBroker_Dequeue_Fairness(t *testing.T) { 765 t.Parallel() 766 b := testBroker(t, 0) 767 b.SetEnabled(true) 768 NUM := 1000 769 770 for i := 0; i < NUM; i++ { 771 eval1 := mock.Eval() 772 if i < (NUM / 2) { 773 eval1.Type = structs.JobTypeService 774 } else { 775 eval1.Type = structs.JobTypeBatch 776 } 777 b.Enqueue(eval1) 778 } 779 780 counter := 0 781 for i := 0; i < NUM; i++ { 782 out1, _, _ := b.Dequeue(defaultSched, time.Second) 783 784 switch out1.Type { 785 case structs.JobTypeService: 786 if counter < 0 { 787 counter = 0 788 } 789 counter += 1 790 case structs.JobTypeBatch: 791 if counter > 0 { 792 counter = 0 793 } 794 counter -= 1 795 } 796 797 // This will fail randomly at times. It is very hard to 798 // test deterministically that its acting randomly. 799 if counter >= 250 || counter <= -250 { 800 t.Fatalf("unlikely sequence: %d", counter) 801 } 802 } 803 } 804 805 // Ensure we get unblocked 806 func TestEvalBroker_Dequeue_Blocked(t *testing.T) { 807 t.Parallel() 808 b := testBroker(t, 0) 809 b.SetEnabled(true) 810 811 // Start with a blocked dequeue 812 outCh := make(chan *structs.Evaluation, 1) 813 go func() { 814 start := time.Now() 815 out, _, err := b.Dequeue(defaultSched, time.Second) 816 end := time.Now() 817 outCh <- out 818 if err != nil { 819 t.Fatalf("err: %v", err) 820 } 821 if d := end.Sub(start); d < 5*time.Millisecond { 822 t.Fatalf("bad: %v", d) 823 } 824 }() 825 826 // Wait for a bit 827 time.Sleep(5 * time.Millisecond) 828 829 // Enqueue 830 eval := mock.Eval() 831 b.Enqueue(eval) 832 833 // Ensure dequeue 834 select { 835 case out := <-outCh: 836 if out != eval { 837 t.Fatalf("bad: %v", out) 838 } 839 case <-time.After(time.Second): 840 t.Fatalf("timeout") 841 } 842 } 843 844 // Ensure we nack in a timely manner 845 func TestEvalBroker_Nack_Timeout(t *testing.T) { 846 t.Parallel() 847 b := testBroker(t, 5*time.Millisecond) 848 b.SetEnabled(true) 849 850 // Enqueue 851 eval := mock.Eval() 852 b.Enqueue(eval) 853 854 // Dequeue 855 out, _, err := b.Dequeue(defaultSched, time.Second) 856 start := time.Now() 857 if err != nil { 858 t.Fatalf("err: %v", err) 859 } 860 if out != eval { 861 t.Fatalf("bad: %v", out) 862 } 863 864 // Dequeue, should block on Nack timer 865 out, _, err = b.Dequeue(defaultSched, time.Second) 866 end := time.Now() 867 if err != nil { 868 t.Fatalf("err: %v", err) 869 } 870 if out != eval { 871 t.Fatalf("bad: %v", out) 872 } 873 874 // Check the nack timer 875 if diff := end.Sub(start); diff < 5*time.Millisecond { 876 t.Fatalf("bad: %#v", diff) 877 } 878 } 879 880 // Ensure we nack in a timely manner 881 func TestEvalBroker_Nack_TimeoutReset(t *testing.T) { 882 t.Parallel() 883 b := testBroker(t, 50*time.Millisecond) 884 b.SetEnabled(true) 885 886 // Enqueue 887 eval := mock.Eval() 888 b.Enqueue(eval) 889 890 // Dequeue 891 out, token, err := b.Dequeue(defaultSched, time.Second) 892 start := time.Now() 893 if err != nil { 894 t.Fatalf("err: %v", err) 895 } 896 if out != eval { 897 t.Fatalf("bad: %v", out) 898 } 899 900 // Reset in 20 milliseconds 901 time.Sleep(20 * time.Millisecond) 902 if err := b.OutstandingReset(out.ID, token); err != nil { 903 t.Fatalf("err: %v", err) 904 } 905 906 // Dequeue, should block on Nack timer 907 out, _, err = b.Dequeue(defaultSched, time.Second) 908 end := time.Now() 909 if err != nil { 910 t.Fatalf("err: %v", err) 911 } 912 if out != eval { 913 t.Fatalf("bad: %v", out) 914 } 915 916 // Check the nack timer 917 if diff := end.Sub(start); diff < 75*time.Millisecond { 918 t.Fatalf("bad: %#v", diff) 919 } 920 } 921 922 func TestEvalBroker_PauseResumeNackTimeout(t *testing.T) { 923 t.Parallel() 924 b := testBroker(t, 50*time.Millisecond) 925 b.SetEnabled(true) 926 927 // Enqueue 928 eval := mock.Eval() 929 b.Enqueue(eval) 930 931 // Dequeue 932 out, token, err := b.Dequeue(defaultSched, time.Second) 933 start := time.Now() 934 if err != nil { 935 t.Fatalf("err: %v", err) 936 } 937 if out != eval { 938 t.Fatalf("bad: %v", out) 939 } 940 941 // Pause in 20 milliseconds 942 time.Sleep(20 * time.Millisecond) 943 if err := b.PauseNackTimeout(out.ID, token); err != nil { 944 t.Fatalf("err: %v", err) 945 } 946 947 go func() { 948 time.Sleep(20 * time.Millisecond) 949 if err := b.ResumeNackTimeout(out.ID, token); err != nil { 950 t.Fatalf("err: %v", err) 951 } 952 }() 953 954 // Dequeue, should block until the timer is resumed 955 out, _, err = b.Dequeue(defaultSched, time.Second) 956 end := time.Now() 957 if err != nil { 958 t.Fatalf("err: %v", err) 959 } 960 if out != eval { 961 t.Fatalf("bad: %v", out) 962 } 963 964 // Check the nack timer 965 if diff := end.Sub(start); diff < 95*time.Millisecond { 966 t.Fatalf("bad: %#v", diff) 967 } 968 } 969 970 func TestEvalBroker_DeliveryLimit(t *testing.T) { 971 t.Parallel() 972 b := testBroker(t, 0) 973 b.SetEnabled(true) 974 975 eval := mock.Eval() 976 b.Enqueue(eval) 977 978 for i := 0; i < 3; i++ { 979 // Dequeue should work 980 out, token, err := b.Dequeue(defaultSched, time.Second) 981 if err != nil { 982 t.Fatalf("err: %v", err) 983 } 984 if out != eval { 985 t.Fatalf("bad : %#v", out) 986 } 987 988 // Nack with wrong token should fail 989 err = b.Nack(eval.ID, token) 990 if err != nil { 991 t.Fatalf("err: %v", err) 992 } 993 } 994 995 // Check the stats 996 stats := b.Stats() 997 if stats.TotalReady != 1 { 998 t.Fatalf("bad: %#v", stats) 999 } 1000 if stats.TotalUnacked != 0 { 1001 t.Fatalf("bad: %#v", stats) 1002 } 1003 if stats.ByScheduler[failedQueue].Ready != 1 { 1004 t.Fatalf("bad: %#v", stats) 1005 } 1006 if stats.ByScheduler[failedQueue].Unacked != 0 { 1007 t.Fatalf("bad: %#v", stats) 1008 } 1009 1010 // Dequeue from failed queue 1011 out, token, err := b.Dequeue([]string{failedQueue}, time.Second) 1012 if err != nil { 1013 t.Fatalf("err: %v", err) 1014 } 1015 if out != eval { 1016 t.Fatalf("bad : %#v", out) 1017 } 1018 1019 // Check the stats 1020 stats = b.Stats() 1021 if stats.TotalReady != 0 { 1022 t.Fatalf("bad: %#v", stats) 1023 } 1024 if stats.TotalUnacked != 1 { 1025 t.Fatalf("bad: %#v", stats) 1026 } 1027 if stats.ByScheduler[failedQueue].Ready != 0 { 1028 t.Fatalf("bad: %#v", stats) 1029 } 1030 if stats.ByScheduler[failedQueue].Unacked != 1 { 1031 t.Fatalf("bad: %#v", stats) 1032 } 1033 1034 // Ack finally 1035 err = b.Ack(out.ID, token) 1036 if err != nil { 1037 t.Fatalf("err: %v", err) 1038 } 1039 1040 if _, ok := b.Outstanding(out.ID); ok { 1041 t.Fatalf("should not be outstanding") 1042 } 1043 1044 // Check the stats 1045 stats = b.Stats() 1046 if stats.TotalReady != 0 { 1047 t.Fatalf("bad: %#v", stats) 1048 } 1049 if stats.TotalUnacked != 0 { 1050 t.Fatalf("bad: %#v", stats) 1051 } 1052 if stats.ByScheduler[failedQueue].Ready != 0 { 1053 t.Fatalf("bad: %#v", stats.ByScheduler[failedQueue]) 1054 } 1055 if stats.ByScheduler[failedQueue].Unacked != 0 { 1056 t.Fatalf("bad: %#v", stats.ByScheduler[failedQueue]) 1057 } 1058 } 1059 1060 func TestEvalBroker_AckAtDeliveryLimit(t *testing.T) { 1061 t.Parallel() 1062 b := testBroker(t, 0) 1063 b.SetEnabled(true) 1064 1065 eval := mock.Eval() 1066 b.Enqueue(eval) 1067 1068 for i := 0; i < 3; i++ { 1069 // Dequeue should work 1070 out, token, err := b.Dequeue(defaultSched, time.Second) 1071 if err != nil { 1072 t.Fatalf("err: %v", err) 1073 } 1074 if out != eval { 1075 t.Fatalf("bad : %#v", out) 1076 } 1077 1078 if i == 2 { 1079 b.Ack(eval.ID, token) 1080 } else { 1081 // Nack with wrong token should fail 1082 err = b.Nack(eval.ID, token) 1083 if err != nil { 1084 t.Fatalf("err: %v", err) 1085 } 1086 } 1087 } 1088 1089 // Check the stats 1090 stats := b.Stats() 1091 if stats.TotalReady != 0 { 1092 t.Fatalf("bad: %#v", stats) 1093 } 1094 if stats.TotalUnacked != 0 { 1095 t.Fatalf("bad: %#v", stats) 1096 } 1097 if _, ok := stats.ByScheduler[failedQueue]; ok { 1098 t.Fatalf("bad: %#v", stats) 1099 } 1100 } 1101 1102 // Ensure fairness between schedulers 1103 func TestEvalBroker_Wait(t *testing.T) { 1104 t.Parallel() 1105 b := testBroker(t, 0) 1106 b.SetEnabled(true) 1107 1108 // Create an eval that should wait 1109 eval := mock.Eval() 1110 eval.Wait = 10 * time.Millisecond 1111 b.Enqueue(eval) 1112 1113 // Verify waiting 1114 stats := b.Stats() 1115 if stats.TotalReady != 0 { 1116 t.Fatalf("bad: %#v", stats) 1117 } 1118 if stats.TotalWaiting != 1 { 1119 t.Fatalf("bad: %#v", stats) 1120 } 1121 1122 // Let the wait elapse 1123 time.Sleep(20 * time.Millisecond) 1124 1125 // Verify ready 1126 stats = b.Stats() 1127 if stats.TotalReady != 1 { 1128 t.Fatalf("bad: %#v", stats) 1129 } 1130 if stats.TotalWaiting != 0 { 1131 t.Fatalf("bad: %#v", stats) 1132 } 1133 1134 // Dequeue should work 1135 out, _, err := b.Dequeue(defaultSched, time.Second) 1136 if err != nil { 1137 t.Fatalf("err: %v", err) 1138 } 1139 if out != eval { 1140 t.Fatalf("bad : %#v", out) 1141 } 1142 } 1143 1144 // Ensure that delayed evaluations work as expected 1145 func TestEvalBroker_WaitUntil(t *testing.T) { 1146 t.Parallel() 1147 require := require.New(t) 1148 b := testBroker(t, 0) 1149 b.SetEnabled(true) 1150 1151 now := time.Now() 1152 // Create a few of evals with WaitUntil set 1153 eval1 := mock.Eval() 1154 eval1.WaitUntil = now.Add(1 * time.Second) 1155 eval1.CreateIndex = 1 1156 b.Enqueue(eval1) 1157 1158 eval2 := mock.Eval() 1159 eval2.WaitUntil = now.Add(100 * time.Millisecond) 1160 // set CreateIndex to use as a tie breaker when eval2 1161 // and eval3 are both in the pending evals heap 1162 eval2.CreateIndex = 2 1163 b.Enqueue(eval2) 1164 1165 eval3 := mock.Eval() 1166 eval3.WaitUntil = now.Add(20 * time.Millisecond) 1167 eval3.CreateIndex = 1 1168 b.Enqueue(eval3) 1169 require.Equal(3, b.stats.TotalWaiting) 1170 // sleep enough for two evals to be ready 1171 time.Sleep(200 * time.Millisecond) 1172 1173 // first dequeue should return eval3 1174 out, _, err := b.Dequeue(defaultSched, time.Second) 1175 require.Nil(err) 1176 require.Equal(eval3, out) 1177 1178 // second dequeue should return eval2 1179 out, _, err = b.Dequeue(defaultSched, time.Second) 1180 require.Nil(err) 1181 require.Equal(eval2, out) 1182 1183 // third dequeue should return eval1 1184 out, _, err = b.Dequeue(defaultSched, 2*time.Second) 1185 require.Nil(err) 1186 require.Equal(eval1, out) 1187 require.Equal(0, b.stats.TotalWaiting) 1188 } 1189 1190 // Ensure that priority is taken into account when enqueueing many evaluations. 1191 func TestEvalBroker_EnqueueAll_Dequeue_Fair(t *testing.T) { 1192 t.Parallel() 1193 b := testBroker(t, 0) 1194 b.SetEnabled(true) 1195 1196 // Start with a blocked dequeue 1197 outCh := make(chan *structs.Evaluation, 1) 1198 go func() { 1199 start := time.Now() 1200 out, _, err := b.Dequeue(defaultSched, time.Second) 1201 end := time.Now() 1202 outCh <- out 1203 if err != nil { 1204 t.Fatalf("err: %v", err) 1205 } 1206 if d := end.Sub(start); d < 5*time.Millisecond { 1207 t.Fatalf("bad: %v", d) 1208 } 1209 }() 1210 1211 // Wait for a bit 1212 time.Sleep(5 * time.Millisecond) 1213 1214 // Enqueue 1215 evals := make(map[*structs.Evaluation]string, 8) 1216 expectedPriority := 90 1217 for i := 10; i <= expectedPriority; i += 10 { 1218 eval := mock.Eval() 1219 eval.Priority = i 1220 evals[eval] = "" 1221 1222 } 1223 b.EnqueueAll(evals) 1224 1225 // Ensure dequeue 1226 select { 1227 case out := <-outCh: 1228 if out.Priority != expectedPriority { 1229 t.Fatalf("bad: %v", out) 1230 } 1231 case <-time.After(time.Second): 1232 t.Fatalf("timeout") 1233 } 1234 } 1235 1236 func TestEvalBroker_EnqueueAll_Requeue_Ack(t *testing.T) { 1237 t.Parallel() 1238 b := testBroker(t, 0) 1239 b.SetEnabled(true) 1240 1241 // Create the evaluation, enqueue and dequeue 1242 eval := mock.Eval() 1243 b.Enqueue(eval) 1244 1245 out, token, err := b.Dequeue(defaultSched, time.Second) 1246 if err != nil { 1247 t.Fatalf("err: %v", err) 1248 } 1249 if out != eval { 1250 t.Fatalf("bad : %#v", out) 1251 } 1252 1253 // Requeue the same evaluation. 1254 b.EnqueueAll(map[*structs.Evaluation]string{eval: token}) 1255 1256 // The stats should show one unacked 1257 stats := b.Stats() 1258 if stats.TotalReady != 0 { 1259 t.Fatalf("bad: %#v", stats) 1260 } 1261 if stats.TotalUnacked != 1 { 1262 t.Fatalf("bad: %#v", stats) 1263 } 1264 1265 // Ack the evaluation. 1266 if err := b.Ack(eval.ID, token); err != nil { 1267 t.Fatalf("err: %v", err) 1268 } 1269 1270 // Check stats again as this should cause the re-enqueued one to transition 1271 // into the ready state 1272 stats = b.Stats() 1273 if stats.TotalReady != 1 { 1274 t.Fatalf("bad: %#v", stats) 1275 } 1276 if stats.TotalUnacked != 0 { 1277 t.Fatalf("bad: %#v", stats) 1278 } 1279 1280 // Another dequeue should be successful 1281 out2, token2, err := b.Dequeue(defaultSched, time.Second) 1282 if err != nil { 1283 t.Fatalf("err: %v", err) 1284 } 1285 if out2 != eval { 1286 t.Fatalf("bad : %#v", out) 1287 } 1288 if token == token2 { 1289 t.Fatalf("bad : %s and %s", token, token2) 1290 } 1291 } 1292 1293 func TestEvalBroker_EnqueueAll_Requeue_Nack(t *testing.T) { 1294 t.Parallel() 1295 b := testBroker(t, 0) 1296 b.SetEnabled(true) 1297 1298 // Create the evaluation, enqueue and dequeue 1299 eval := mock.Eval() 1300 b.Enqueue(eval) 1301 1302 out, token, err := b.Dequeue(defaultSched, time.Second) 1303 if err != nil { 1304 t.Fatalf("err: %v", err) 1305 } 1306 if out != eval { 1307 t.Fatalf("bad : %#v", out) 1308 } 1309 1310 // Requeue the same evaluation. 1311 b.EnqueueAll(map[*structs.Evaluation]string{eval: token}) 1312 1313 // The stats should show one unacked 1314 stats := b.Stats() 1315 if stats.TotalReady != 0 { 1316 t.Fatalf("bad: %#v", stats) 1317 } 1318 if stats.TotalUnacked != 1 { 1319 t.Fatalf("bad: %#v", stats) 1320 } 1321 1322 // Nack the evaluation. 1323 if err := b.Nack(eval.ID, token); err != nil { 1324 t.Fatalf("err: %v", err) 1325 } 1326 1327 // Check stats again as this should cause the re-enqueued one to be dropped 1328 testutil.WaitForResult(func() (bool, error) { 1329 stats = b.Stats() 1330 if stats.TotalReady != 1 { 1331 return false, fmt.Errorf("bad: %#v", stats) 1332 } 1333 if stats.TotalUnacked != 0 { 1334 return false, fmt.Errorf("bad: %#v", stats) 1335 } 1336 if len(b.requeue) != 0 { 1337 return false, fmt.Errorf("bad: %#v", b.requeue) 1338 } 1339 1340 return true, nil 1341 }, func(e error) { 1342 t.Fatal(e) 1343 }) 1344 } 1345 1346 func TestEvalBroker_NamespacedJobs(t *testing.T) { 1347 t.Parallel() 1348 b := testBroker(t, 0) 1349 b.SetEnabled(true) 1350 1351 // Create evals with the same jobid and different namespace 1352 jobId := "test-jobID" 1353 1354 eval1 := mock.Eval() 1355 eval1.JobID = jobId 1356 eval1.Namespace = "n1" 1357 b.Enqueue(eval1) 1358 1359 // This eval should not block 1360 eval2 := mock.Eval() 1361 eval2.JobID = jobId 1362 eval2.Namespace = "default" 1363 b.Enqueue(eval2) 1364 1365 // This eval should block 1366 eval3 := mock.Eval() 1367 eval3.JobID = jobId 1368 eval3.Namespace = "default" 1369 b.Enqueue(eval3) 1370 1371 require := require.New(t) 1372 out1, _, err := b.Dequeue(defaultSched, 5*time.Millisecond) 1373 require.Nil(err) 1374 require.Equal(eval1.ID, out1.ID) 1375 1376 out2, _, err := b.Dequeue(defaultSched, 5*time.Millisecond) 1377 require.Nil(err) 1378 require.Equal(eval2.ID, out2.ID) 1379 1380 out3, _, err := b.Dequeue(defaultSched, 5*time.Millisecond) 1381 require.Nil(err) 1382 require.Nil(out3) 1383 1384 require.Equal(1, len(b.blocked)) 1385 1386 }