github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/eval_broker_test.go (about) 1 package nomad 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/nomad/nomad/mock" 8 "github.com/hashicorp/nomad/nomad/structs" 9 ) 10 11 var ( 12 defaultSched = []string{ 13 structs.JobTypeService, 14 structs.JobTypeBatch, 15 } 16 ) 17 18 func testBroker(t *testing.T, timeout time.Duration) *EvalBroker { 19 if timeout == 0 { 20 timeout = 5 * time.Second 21 } 22 b, err := NewEvalBroker(timeout, 3) 23 if err != nil { 24 t.Fatalf("err: %v", err) 25 } 26 return b 27 } 28 29 func TestEvalBroker_Enqueue_Dequeue_Nack_Ack(t *testing.T) { 30 b := testBroker(t, 0) 31 32 // Enqueue, but broker is disabled! 33 eval := mock.Eval() 34 err := b.Enqueue(eval) 35 if err != nil { 36 t.Fatalf("err: %v", err) 37 } 38 39 // Verify nothing was done 40 stats := b.Stats() 41 if stats.TotalReady != 0 { 42 t.Fatalf("bad: %#v", stats) 43 } 44 45 if b.Enabled() { 46 t.Fatalf("should not be enabled") 47 } 48 49 // Enable the broker, and enqueue 50 b.SetEnabled(true) 51 err = b.Enqueue(eval) 52 if err != nil { 53 t.Fatalf("err: %v", err) 54 } 55 56 // Double enqueue is a no-op 57 err = b.Enqueue(eval) 58 if err != nil { 59 t.Fatalf("err: %v", err) 60 } 61 62 if !b.Enabled() { 63 t.Fatalf("should be enabled") 64 } 65 66 // Verify enqueue is done 67 stats = b.Stats() 68 if stats.TotalReady != 1 { 69 t.Fatalf("bad: %#v", stats) 70 } 71 if stats.ByScheduler[eval.Type].Ready != 1 { 72 t.Fatalf("bad: %#v", stats) 73 } 74 75 // Dequeue should work 76 out, token, err := b.Dequeue(defaultSched, time.Second) 77 if err != nil { 78 t.Fatalf("err: %v", err) 79 } 80 if out != eval { 81 t.Fatalf("bad : %#v", out) 82 } 83 84 tokenOut, ok := b.Outstanding(out.ID) 85 if !ok { 86 t.Fatalf("should be outstanding") 87 } 88 if tokenOut != token { 89 t.Fatalf("Bad: %#v %#v", token, tokenOut) 90 } 91 92 // OutstandingReset should verify the token 93 err = b.OutstandingReset("nope", "foo") 94 if err != ErrNotOutstanding { 95 t.Fatalf("err: %v", err) 96 } 97 err = b.OutstandingReset(out.ID, "foo") 98 if err != ErrTokenMismatch { 99 t.Fatalf("err: %v", err) 100 } 101 err = b.OutstandingReset(out.ID, tokenOut) 102 if err != nil { 103 t.Fatalf("err: %v", err) 104 } 105 106 // Check the stats 107 stats = b.Stats() 108 if stats.TotalReady != 0 { 109 t.Fatalf("bad: %#v", stats) 110 } 111 if stats.TotalUnacked != 1 { 112 t.Fatalf("bad: %#v", stats) 113 } 114 if stats.ByScheduler[eval.Type].Ready != 0 { 115 t.Fatalf("bad: %#v", stats) 116 } 117 if stats.ByScheduler[eval.Type].Unacked != 1 { 118 t.Fatalf("bad: %#v", stats) 119 } 120 121 // Nack with wrong token should fail 122 err = b.Nack(eval.ID, "foobarbaz") 123 if err == nil { 124 t.Fatalf("should fail to nack") 125 } 126 127 // Nack back into the queue 128 err = b.Nack(eval.ID, token) 129 if err != nil { 130 t.Fatalf("err: %v", err) 131 } 132 133 if _, ok := b.Outstanding(out.ID); ok { 134 t.Fatalf("should not be outstanding") 135 } 136 137 // Check the stats 138 stats = b.Stats() 139 if stats.TotalReady != 1 { 140 t.Fatalf("bad: %#v", stats) 141 } 142 if stats.TotalUnacked != 0 { 143 t.Fatalf("bad: %#v", stats) 144 } 145 if stats.ByScheduler[eval.Type].Ready != 1 { 146 t.Fatalf("bad: %#v", stats) 147 } 148 if stats.ByScheduler[eval.Type].Unacked != 0 { 149 t.Fatalf("bad: %#v", stats) 150 } 151 152 // Dequeue should work again 153 out2, token2, err := b.Dequeue(defaultSched, time.Second) 154 if err != nil { 155 t.Fatalf("err: %v", err) 156 } 157 if out2 != eval { 158 t.Fatalf("bad : %#v", out2) 159 } 160 if token2 == token { 161 t.Fatalf("should get a new token") 162 } 163 164 tokenOut2, ok := b.Outstanding(out.ID) 165 if !ok { 166 t.Fatalf("should be outstanding") 167 } 168 if tokenOut2 != token2 { 169 t.Fatalf("Bad: %#v %#v", token2, tokenOut2) 170 } 171 172 // Ack with wrong token 173 err = b.Ack(eval.ID, "zip") 174 if err == nil { 175 t.Fatalf("should fail to ack") 176 } 177 178 // Ack finally 179 err = b.Ack(eval.ID, token2) 180 if err != nil { 181 t.Fatalf("err: %v", err) 182 } 183 184 if _, ok := b.Outstanding(out.ID); ok { 185 t.Fatalf("should not be outstanding") 186 } 187 188 // Check the stats 189 stats = b.Stats() 190 if stats.TotalReady != 0 { 191 t.Fatalf("bad: %#v", stats) 192 } 193 if stats.TotalUnacked != 0 { 194 t.Fatalf("bad: %#v", stats) 195 } 196 if stats.ByScheduler[eval.Type].Ready != 0 { 197 t.Fatalf("bad: %#v", stats) 198 } 199 if stats.ByScheduler[eval.Type].Unacked != 0 { 200 t.Fatalf("bad: %#v", stats) 201 } 202 } 203 204 func TestEvalBroker_Serialize_DuplicateJobID(t *testing.T) { 205 b := testBroker(t, 0) 206 b.SetEnabled(true) 207 208 eval := mock.Eval() 209 err := b.Enqueue(eval) 210 if err != nil { 211 t.Fatalf("err: %v", err) 212 } 213 214 eval2 := mock.Eval() 215 eval2.JobID = eval.JobID 216 eval2.CreateIndex = eval.CreateIndex + 1 217 err = b.Enqueue(eval2) 218 if err != nil { 219 t.Fatalf("err: %v", err) 220 } 221 222 eval3 := mock.Eval() 223 eval3.JobID = eval.JobID 224 eval3.CreateIndex = eval.CreateIndex + 2 225 err = b.Enqueue(eval3) 226 if err != nil { 227 t.Fatalf("err: %v", err) 228 } 229 230 stats := b.Stats() 231 if stats.TotalReady != 1 { 232 t.Fatalf("bad: %#v", stats) 233 } 234 if stats.TotalBlocked != 2 { 235 t.Fatalf("bad: %#v", stats) 236 } 237 238 // Dequeue should work 239 out, token, err := b.Dequeue(defaultSched, time.Second) 240 if err != nil { 241 t.Fatalf("err: %v", err) 242 } 243 if out != eval { 244 t.Fatalf("bad : %#v", out) 245 } 246 247 // Check the stats 248 stats = b.Stats() 249 if stats.TotalReady != 0 { 250 t.Fatalf("bad: %#v", stats) 251 } 252 if stats.TotalUnacked != 1 { 253 t.Fatalf("bad: %#v", stats) 254 } 255 if stats.TotalBlocked != 2 { 256 t.Fatalf("bad: %#v", stats) 257 } 258 259 // Ack out 260 err = b.Ack(eval.ID, token) 261 if err != nil { 262 t.Fatalf("err: %v", err) 263 } 264 265 // Check the stats 266 stats = b.Stats() 267 if stats.TotalReady != 1 { 268 t.Fatalf("bad: %#v", stats) 269 } 270 if stats.TotalUnacked != 0 { 271 t.Fatalf("bad: %#v", stats) 272 } 273 if stats.TotalBlocked != 1 { 274 t.Fatalf("bad: %#v", stats) 275 } 276 277 // Dequeue should work 278 out, token, err = b.Dequeue(defaultSched, time.Second) 279 if err != nil { 280 t.Fatalf("err: %v", err) 281 } 282 if out != eval2 { 283 t.Fatalf("bad : %#v", out) 284 } 285 286 // Check the stats 287 stats = b.Stats() 288 if stats.TotalReady != 0 { 289 t.Fatalf("bad: %#v", stats) 290 } 291 if stats.TotalUnacked != 1 { 292 t.Fatalf("bad: %#v", stats) 293 } 294 if stats.TotalBlocked != 1 { 295 t.Fatalf("bad: %#v", stats) 296 } 297 298 // Ack out 299 err = b.Ack(eval2.ID, token) 300 if err != nil { 301 t.Fatalf("err: %v", err) 302 } 303 304 // Check the stats 305 stats = b.Stats() 306 if stats.TotalReady != 1 { 307 t.Fatalf("bad: %#v", stats) 308 } 309 if stats.TotalUnacked != 0 { 310 t.Fatalf("bad: %#v", stats) 311 } 312 if stats.TotalBlocked != 0 { 313 t.Fatalf("bad: %#v", stats) 314 } 315 316 // Dequeue should work 317 out, token, err = b.Dequeue(defaultSched, time.Second) 318 if err != nil { 319 t.Fatalf("err: %v", err) 320 } 321 if out != eval3 { 322 t.Fatalf("bad : %#v", out) 323 } 324 325 // Check the stats 326 stats = b.Stats() 327 if stats.TotalReady != 0 { 328 t.Fatalf("bad: %#v", stats) 329 } 330 if stats.TotalUnacked != 1 { 331 t.Fatalf("bad: %#v", stats) 332 } 333 if stats.TotalBlocked != 0 { 334 t.Fatalf("bad: %#v", stats) 335 } 336 337 // Ack out 338 err = b.Ack(eval3.ID, token) 339 if err != nil { 340 t.Fatalf("err: %v", err) 341 } 342 343 // Check the stats 344 stats = b.Stats() 345 if stats.TotalReady != 0 { 346 t.Fatalf("bad: %#v", stats) 347 } 348 if stats.TotalUnacked != 0 { 349 t.Fatalf("bad: %#v", stats) 350 } 351 if stats.TotalBlocked != 0 { 352 t.Fatalf("bad: %#v", stats) 353 } 354 } 355 356 func TestEvalBroker_Enqueue_Disable(t *testing.T) { 357 b := testBroker(t, 0) 358 359 // Enqueue 360 eval := mock.Eval() 361 b.SetEnabled(true) 362 err := b.Enqueue(eval) 363 if err != nil { 364 t.Fatalf("err: %v", err) 365 } 366 367 // Flush via SetEnabled 368 b.SetEnabled(false) 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 _, ok := stats.ByScheduler[eval.Type]; ok { 379 t.Fatalf("bad: %#v", stats) 380 } 381 } 382 383 func TestEvalBroker_Dequeue_Timeout(t *testing.T) { 384 b := testBroker(t, 0) 385 b.SetEnabled(true) 386 387 start := time.Now() 388 out, _, err := b.Dequeue(defaultSched, 5*time.Millisecond) 389 end := time.Now() 390 391 if err != nil { 392 t.Fatalf("err: %v", err) 393 } 394 if out != nil { 395 t.Fatalf("unexpected: %#v", out) 396 } 397 398 if diff := end.Sub(start); diff < 5*time.Millisecond { 399 t.Fatalf("bad: %#v", diff) 400 } 401 } 402 403 func TestEvalBroker_Dequeue_Empty_Timeout(t *testing.T) { 404 b := testBroker(t, 0) 405 b.SetEnabled(true) 406 doneCh := make(chan struct{}, 1) 407 408 go func() { 409 out, _, err := b.Dequeue(defaultSched, 0) 410 if err != nil { 411 t.Fatalf("err: %v", err) 412 } 413 if out == nil { 414 t.Fatal("Expect an eval") 415 } 416 doneCh <- struct{}{} 417 }() 418 419 // Sleep for a little bit 420 select { 421 case <-time.After(5 * time.Millisecond): 422 case <-doneCh: 423 t.Fatalf("Dequeue(0) should block") 424 } 425 426 // Enqueue to unblock the dequeue. 427 eval := mock.Eval() 428 err := b.Enqueue(eval) 429 if err != nil { 430 t.Fatalf("err: %v", err) 431 } 432 433 select { 434 case <-doneCh: 435 return 436 case <-time.After(5 * time.Millisecond): 437 t.Fatal("timeout: Dequeue(0) should return after enqueue") 438 } 439 } 440 441 // Ensure higher priority dequeued first 442 func TestEvalBroker_Dequeue_Priority(t *testing.T) { 443 b := testBroker(t, 0) 444 b.SetEnabled(true) 445 446 eval1 := mock.Eval() 447 eval1.Priority = 10 448 b.Enqueue(eval1) 449 450 eval2 := mock.Eval() 451 eval2.Priority = 30 452 b.Enqueue(eval2) 453 454 eval3 := mock.Eval() 455 eval3.Priority = 20 456 b.Enqueue(eval3) 457 458 out1, _, _ := b.Dequeue(defaultSched, time.Second) 459 if out1 != eval2 { 460 t.Fatalf("bad: %#v", out1) 461 } 462 463 out2, _, _ := b.Dequeue(defaultSched, time.Second) 464 if out2 != eval3 { 465 t.Fatalf("bad: %#v", out2) 466 } 467 468 out3, _, _ := b.Dequeue(defaultSched, time.Second) 469 if out3 != eval1 { 470 t.Fatalf("bad: %#v", out3) 471 } 472 } 473 474 // Ensure FIFO at fixed priority 475 func TestEvalBroker_Dequeue_FIFO(t *testing.T) { 476 b := testBroker(t, 0) 477 b.SetEnabled(true) 478 NUM := 100 479 480 for i := 0; i < NUM; i++ { 481 eval1 := mock.Eval() 482 eval1.CreateIndex = uint64(i) 483 eval1.ModifyIndex = uint64(i) 484 b.Enqueue(eval1) 485 } 486 487 for i := 0; i < NUM; i++ { 488 out1, _, _ := b.Dequeue(defaultSched, time.Second) 489 if out1.CreateIndex != uint64(i) { 490 t.Fatalf("bad: %d %#v", i, out1) 491 } 492 } 493 } 494 495 // Ensure fairness between schedulers 496 func TestEvalBroker_Dequeue_Fairness(t *testing.T) { 497 b := testBroker(t, 0) 498 b.SetEnabled(true) 499 NUM := 100 500 501 for i := 0; i < NUM; i++ { 502 eval1 := mock.Eval() 503 if i < (NUM / 2) { 504 eval1.Type = structs.JobTypeService 505 } else { 506 eval1.Type = structs.JobTypeBatch 507 } 508 b.Enqueue(eval1) 509 } 510 511 counter := 0 512 for i := 0; i < NUM; i++ { 513 out1, _, _ := b.Dequeue(defaultSched, time.Second) 514 515 switch out1.Type { 516 case structs.JobTypeService: 517 if counter < 0 { 518 counter = 0 519 } 520 counter += 1 521 case structs.JobTypeBatch: 522 if counter > 0 { 523 counter = 0 524 } 525 counter -= 1 526 } 527 528 // This will fail randomly at times. It is very hard to 529 // test deterministically that its acting randomly. 530 if counter >= 25 || counter <= -25 { 531 t.Fatalf("unlikely sequence: %d", counter) 532 } 533 } 534 } 535 536 // Ensure we get unblocked 537 func TestEvalBroker_Dequeue_Blocked(t *testing.T) { 538 b := testBroker(t, 0) 539 b.SetEnabled(true) 540 541 // Start with a blocked dequeue 542 outCh := make(chan *structs.Evaluation, 1) 543 go func() { 544 start := time.Now() 545 out, _, err := b.Dequeue(defaultSched, time.Second) 546 end := time.Now() 547 outCh <- out 548 if err != nil { 549 t.Fatalf("err: %v", err) 550 } 551 if d := end.Sub(start); d < 5*time.Millisecond { 552 t.Fatalf("bad: %v", d) 553 } 554 }() 555 556 // Wait for a bit 557 time.Sleep(5 * time.Millisecond) 558 559 // Enqueue 560 eval := mock.Eval() 561 err := b.Enqueue(eval) 562 if err != nil { 563 t.Fatalf("err: %v", err) 564 } 565 566 // Ensure dequeue 567 select { 568 case out := <-outCh: 569 if out != eval { 570 t.Fatalf("bad: %v", out) 571 } 572 case <-time.After(time.Second): 573 t.Fatalf("timeout") 574 } 575 } 576 577 // Ensure we nack in a timely manner 578 func TestEvalBroker_Nack_Timeout(t *testing.T) { 579 b := testBroker(t, 5*time.Millisecond) 580 b.SetEnabled(true) 581 582 // Enqueue 583 eval := mock.Eval() 584 err := b.Enqueue(eval) 585 if err != nil { 586 t.Fatalf("err: %v", err) 587 } 588 589 // Dequeue 590 out, _, err := b.Dequeue(defaultSched, time.Second) 591 start := time.Now() 592 if err != nil { 593 t.Fatalf("err: %v", err) 594 } 595 if out != eval { 596 t.Fatalf("bad: %v", out) 597 } 598 599 // Dequeue, should block on Nack timer 600 out, _, err = b.Dequeue(defaultSched, time.Second) 601 end := time.Now() 602 if err != nil { 603 t.Fatalf("err: %v", err) 604 } 605 if out != eval { 606 t.Fatalf("bad: %v", out) 607 } 608 609 // Check the nack timer 610 if diff := end.Sub(start); diff < 5*time.Millisecond { 611 t.Fatalf("bad: %#v", diff) 612 } 613 } 614 615 // Ensure we nack in a timely manner 616 func TestEvalBroker_Nack_TimeoutReset(t *testing.T) { 617 b := testBroker(t, 5*time.Millisecond) 618 b.SetEnabled(true) 619 620 // Enqueue 621 eval := mock.Eval() 622 err := b.Enqueue(eval) 623 if err != nil { 624 t.Fatalf("err: %v", err) 625 } 626 627 // Dequeue 628 out, token, err := b.Dequeue(defaultSched, time.Second) 629 start := time.Now() 630 if err != nil { 631 t.Fatalf("err: %v", err) 632 } 633 if out != eval { 634 t.Fatalf("bad: %v", out) 635 } 636 637 // Reset in 2 milliseconds 638 time.Sleep(2 * time.Millisecond) 639 if err := b.OutstandingReset(out.ID, token); err != nil { 640 t.Fatalf("err: %v", err) 641 } 642 643 // Dequeue, should block on Nack timer 644 out, _, err = b.Dequeue(defaultSched, time.Second) 645 end := time.Now() 646 if err != nil { 647 t.Fatalf("err: %v", err) 648 } 649 if out != eval { 650 t.Fatalf("bad: %v", out) 651 } 652 653 // Check the nack timer 654 if diff := end.Sub(start); diff < 7*time.Millisecond { 655 t.Fatalf("bad: %#v", diff) 656 } 657 } 658 659 func TestEvalBroker_DeliveryLimit(t *testing.T) { 660 b := testBroker(t, 0) 661 b.SetEnabled(true) 662 663 eval := mock.Eval() 664 err := b.Enqueue(eval) 665 if err != nil { 666 t.Fatalf("err: %v", err) 667 } 668 669 for i := 0; i < 3; i++ { 670 // Dequeue should work 671 out, token, err := b.Dequeue(defaultSched, time.Second) 672 if err != nil { 673 t.Fatalf("err: %v", err) 674 } 675 if out != eval { 676 t.Fatalf("bad : %#v", out) 677 } 678 679 // Nack with wrong token should fail 680 err = b.Nack(eval.ID, token) 681 if err != nil { 682 t.Fatalf("err: %v", err) 683 } 684 } 685 686 // Check the stats 687 stats := b.Stats() 688 if stats.TotalReady != 1 { 689 t.Fatalf("bad: %#v", stats) 690 } 691 if stats.TotalUnacked != 0 { 692 t.Fatalf("bad: %#v", stats) 693 } 694 if stats.ByScheduler[failedQueue].Ready != 1 { 695 t.Fatalf("bad: %#v", stats) 696 } 697 if stats.ByScheduler[failedQueue].Unacked != 0 { 698 t.Fatalf("bad: %#v", stats) 699 } 700 701 // Dequeue from failed queue 702 out, token, err := b.Dequeue([]string{failedQueue}, time.Second) 703 if err != nil { 704 t.Fatalf("err: %v", err) 705 } 706 if out != eval { 707 t.Fatalf("bad : %#v", out) 708 } 709 710 // Check the stats 711 stats = b.Stats() 712 if stats.TotalReady != 0 { 713 t.Fatalf("bad: %#v", stats) 714 } 715 if stats.TotalUnacked != 1 { 716 t.Fatalf("bad: %#v", stats) 717 } 718 if stats.ByScheduler[failedQueue].Ready != 0 { 719 t.Fatalf("bad: %#v", stats) 720 } 721 if stats.ByScheduler[failedQueue].Unacked != 1 { 722 t.Fatalf("bad: %#v", stats) 723 } 724 725 // Ack finally 726 err = b.Ack(out.ID, token) 727 if err != nil { 728 t.Fatalf("err: %v", err) 729 } 730 731 if _, ok := b.Outstanding(out.ID); ok { 732 t.Fatalf("should not be outstanding") 733 } 734 735 // Check the stats 736 stats = b.Stats() 737 if stats.TotalReady != 0 { 738 t.Fatalf("bad: %#v", stats) 739 } 740 if stats.TotalUnacked != 0 { 741 t.Fatalf("bad: %#v", stats) 742 } 743 if stats.ByScheduler[failedQueue].Ready != 0 { 744 t.Fatalf("bad: %#v", stats.ByScheduler[failedQueue]) 745 } 746 if stats.ByScheduler[failedQueue].Unacked != 0 { 747 t.Fatalf("bad: %#v", stats.ByScheduler[failedQueue]) 748 } 749 } 750 751 // Ensure fairness between schedulers 752 func TestEvalBroker_Wait(t *testing.T) { 753 b := testBroker(t, 0) 754 b.SetEnabled(true) 755 756 // Create an eval that should wait 757 eval := mock.Eval() 758 eval.Wait = 10 * time.Millisecond 759 err := b.Enqueue(eval) 760 if err != nil { 761 t.Fatalf("err: %v", err) 762 } 763 764 // Verify waiting 765 stats := b.Stats() 766 if stats.TotalReady != 0 { 767 t.Fatalf("bad: %#v", stats) 768 } 769 if stats.TotalWaiting != 1 { 770 t.Fatalf("bad: %#v", stats) 771 } 772 773 // Let the wait elapse 774 time.Sleep(15 * time.Millisecond) 775 776 // Verify ready 777 stats = b.Stats() 778 if stats.TotalReady != 1 { 779 t.Fatalf("bad: %#v", stats) 780 } 781 if stats.TotalWaiting != 0 { 782 t.Fatalf("bad: %#v", stats) 783 } 784 785 // Dequeue should work 786 out, _, err := b.Dequeue(defaultSched, time.Second) 787 if err != nil { 788 t.Fatalf("err: %v", err) 789 } 790 if out != eval { 791 t.Fatalf("bad : %#v", out) 792 } 793 }