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