github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/nomad/fsm_test.go (about) 1 package nomad 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "testing" 9 "time" 10 11 memdb "github.com/hashicorp/go-memdb" 12 "github.com/hashicorp/nomad/nomad/mock" 13 "github.com/hashicorp/nomad/nomad/state" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/testutil" 16 "github.com/hashicorp/raft" 17 ) 18 19 type MockSink struct { 20 *bytes.Buffer 21 cancel bool 22 } 23 24 func (m *MockSink) ID() string { 25 return "Mock" 26 } 27 28 func (m *MockSink) Cancel() error { 29 m.cancel = true 30 return nil 31 } 32 33 func (m *MockSink) Close() error { 34 return nil 35 } 36 37 func testStateStore(t *testing.T) *state.StateStore { 38 state, err := state.NewStateStore(os.Stderr) 39 if err != nil { 40 t.Fatalf("err: %v", err) 41 } 42 if state == nil { 43 t.Fatalf("missing state") 44 } 45 return state 46 } 47 48 func testFSM(t *testing.T) *nomadFSM { 49 p, _ := testPeriodicDispatcher() 50 broker := testBroker(t, 0) 51 blocked := NewBlockedEvals(broker) 52 fsm, err := NewFSM(broker, p, blocked, os.Stderr) 53 if err != nil { 54 t.Fatalf("err: %v", err) 55 } 56 if fsm == nil { 57 t.Fatalf("missing fsm") 58 } 59 return fsm 60 } 61 62 func makeLog(buf []byte) *raft.Log { 63 return &raft.Log{ 64 Index: 1, 65 Term: 1, 66 Type: raft.LogCommand, 67 Data: buf, 68 } 69 } 70 71 func TestFSM_UpsertNode(t *testing.T) { 72 fsm := testFSM(t) 73 fsm.blockedEvals.SetEnabled(true) 74 75 node := mock.Node() 76 77 // Mark an eval as blocked. 78 eval := mock.Eval() 79 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 80 fsm.blockedEvals.Block(eval) 81 82 req := structs.NodeRegisterRequest{ 83 Node: node, 84 } 85 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 86 if err != nil { 87 t.Fatalf("err: %v", err) 88 } 89 90 resp := fsm.Apply(makeLog(buf)) 91 if resp != nil { 92 t.Fatalf("resp: %v", resp) 93 } 94 95 // Verify we are registered 96 ws := memdb.NewWatchSet() 97 n, err := fsm.State().NodeByID(ws, req.Node.ID) 98 if err != nil { 99 t.Fatalf("err: %v", err) 100 } 101 if n == nil { 102 t.Fatalf("not found!") 103 } 104 if n.CreateIndex != 1 { 105 t.Fatalf("bad index: %d", node.CreateIndex) 106 } 107 108 tt := fsm.TimeTable() 109 index := tt.NearestIndex(time.Now().UTC()) 110 if index != 1 { 111 t.Fatalf("bad: %d", index) 112 } 113 114 // Verify the eval was unblocked. 115 testutil.WaitForResult(func() (bool, error) { 116 bStats := fsm.blockedEvals.Stats() 117 if bStats.TotalBlocked != 0 { 118 return false, fmt.Errorf("bad: %#v", bStats) 119 } 120 return true, nil 121 }, func(err error) { 122 t.Fatalf("err: %s", err) 123 }) 124 125 } 126 127 func TestFSM_DeregisterNode(t *testing.T) { 128 fsm := testFSM(t) 129 130 node := mock.Node() 131 req := structs.NodeRegisterRequest{ 132 Node: node, 133 } 134 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 135 if err != nil { 136 t.Fatalf("err: %v", err) 137 } 138 139 resp := fsm.Apply(makeLog(buf)) 140 if resp != nil { 141 t.Fatalf("resp: %v", resp) 142 } 143 144 req2 := structs.NodeDeregisterRequest{ 145 NodeID: node.ID, 146 } 147 buf, err = structs.Encode(structs.NodeDeregisterRequestType, req2) 148 if err != nil { 149 t.Fatalf("err: %v", err) 150 } 151 152 resp = fsm.Apply(makeLog(buf)) 153 if resp != nil { 154 t.Fatalf("resp: %v", resp) 155 } 156 157 // Verify we are NOT registered 158 ws := memdb.NewWatchSet() 159 node, err = fsm.State().NodeByID(ws, req.Node.ID) 160 if err != nil { 161 t.Fatalf("err: %v", err) 162 } 163 if node != nil { 164 t.Fatalf("node found!") 165 } 166 } 167 168 func TestFSM_UpdateNodeStatus(t *testing.T) { 169 fsm := testFSM(t) 170 fsm.blockedEvals.SetEnabled(true) 171 172 node := mock.Node() 173 req := structs.NodeRegisterRequest{ 174 Node: node, 175 } 176 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 177 if err != nil { 178 t.Fatalf("err: %v", err) 179 } 180 181 resp := fsm.Apply(makeLog(buf)) 182 if resp != nil { 183 t.Fatalf("resp: %v", resp) 184 } 185 186 // Mark an eval as blocked. 187 eval := mock.Eval() 188 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 189 fsm.blockedEvals.Block(eval) 190 191 req2 := structs.NodeUpdateStatusRequest{ 192 NodeID: node.ID, 193 Status: structs.NodeStatusReady, 194 } 195 buf, err = structs.Encode(structs.NodeUpdateStatusRequestType, req2) 196 if err != nil { 197 t.Fatalf("err: %v", err) 198 } 199 200 resp = fsm.Apply(makeLog(buf)) 201 if resp != nil { 202 t.Fatalf("resp: %v", resp) 203 } 204 205 // Verify the status is ready. 206 ws := memdb.NewWatchSet() 207 node, err = fsm.State().NodeByID(ws, req.Node.ID) 208 if err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 if node.Status != structs.NodeStatusReady { 212 t.Fatalf("bad node: %#v", node) 213 } 214 215 // Verify the eval was unblocked. 216 testutil.WaitForResult(func() (bool, error) { 217 bStats := fsm.blockedEvals.Stats() 218 if bStats.TotalBlocked != 0 { 219 return false, fmt.Errorf("bad: %#v", bStats) 220 } 221 return true, nil 222 }, func(err error) { 223 t.Fatalf("err: %s", err) 224 }) 225 } 226 227 func TestFSM_UpdateNodeDrain(t *testing.T) { 228 fsm := testFSM(t) 229 230 node := mock.Node() 231 req := structs.NodeRegisterRequest{ 232 Node: node, 233 } 234 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 235 if err != nil { 236 t.Fatalf("err: %v", err) 237 } 238 239 resp := fsm.Apply(makeLog(buf)) 240 if resp != nil { 241 t.Fatalf("resp: %v", resp) 242 } 243 244 req2 := structs.NodeUpdateDrainRequest{ 245 NodeID: node.ID, 246 Drain: true, 247 } 248 buf, err = structs.Encode(structs.NodeUpdateDrainRequestType, req2) 249 if err != nil { 250 t.Fatalf("err: %v", err) 251 } 252 253 resp = fsm.Apply(makeLog(buf)) 254 if resp != nil { 255 t.Fatalf("resp: %v", resp) 256 } 257 258 // Verify we are NOT registered 259 ws := memdb.NewWatchSet() 260 node, err = fsm.State().NodeByID(ws, req.Node.ID) 261 if err != nil { 262 t.Fatalf("err: %v", err) 263 } 264 if !node.Drain { 265 t.Fatalf("bad node: %#v", node) 266 } 267 } 268 269 func TestFSM_RegisterJob(t *testing.T) { 270 fsm := testFSM(t) 271 272 job := mock.PeriodicJob() 273 req := structs.JobRegisterRequest{ 274 Job: job, 275 } 276 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 277 if err != nil { 278 t.Fatalf("err: %v", err) 279 } 280 281 resp := fsm.Apply(makeLog(buf)) 282 if resp != nil { 283 t.Fatalf("resp: %v", resp) 284 } 285 286 // Verify we are registered 287 ws := memdb.NewWatchSet() 288 jobOut, err := fsm.State().JobByID(ws, req.Job.ID) 289 if err != nil { 290 t.Fatalf("err: %v", err) 291 } 292 if jobOut == nil { 293 t.Fatalf("not found!") 294 } 295 if jobOut.CreateIndex != 1 { 296 t.Fatalf("bad index: %d", jobOut.CreateIndex) 297 } 298 299 // Verify it was added to the periodic runner. 300 if _, ok := fsm.periodicDispatcher.tracked[job.ID]; !ok { 301 t.Fatal("job not added to periodic runner") 302 } 303 304 // Verify the launch time was tracked. 305 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Job.ID) 306 if err != nil { 307 t.Fatalf("err: %v", err) 308 } 309 if launchOut == nil { 310 t.Fatalf("not found!") 311 } 312 if launchOut.Launch.IsZero() { 313 t.Fatalf("bad launch time: %v", launchOut.Launch) 314 } 315 } 316 317 func TestFSM_DeregisterJob(t *testing.T) { 318 fsm := testFSM(t) 319 320 job := mock.PeriodicJob() 321 req := structs.JobRegisterRequest{ 322 Job: job, 323 } 324 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 325 if err != nil { 326 t.Fatalf("err: %v", err) 327 } 328 329 resp := fsm.Apply(makeLog(buf)) 330 if resp != nil { 331 t.Fatalf("resp: %v", resp) 332 } 333 334 req2 := structs.JobDeregisterRequest{ 335 JobID: job.ID, 336 } 337 buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) 338 if err != nil { 339 t.Fatalf("err: %v", err) 340 } 341 342 resp = fsm.Apply(makeLog(buf)) 343 if resp != nil { 344 t.Fatalf("resp: %v", resp) 345 } 346 347 // Verify we are NOT registered 348 ws := memdb.NewWatchSet() 349 jobOut, err := fsm.State().JobByID(ws, req.Job.ID) 350 if err != nil { 351 t.Fatalf("err: %v", err) 352 } 353 if jobOut != nil { 354 t.Fatalf("job found!") 355 } 356 357 // Verify it was removed from the periodic runner. 358 if _, ok := fsm.periodicDispatcher.tracked[job.ID]; ok { 359 t.Fatal("job not removed from periodic runner") 360 } 361 362 // Verify it was removed from the periodic launch table. 363 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Job.ID) 364 if err != nil { 365 t.Fatalf("err: %v", err) 366 } 367 if launchOut != nil { 368 t.Fatalf("launch found!") 369 } 370 } 371 372 func TestFSM_UpdateEval(t *testing.T) { 373 fsm := testFSM(t) 374 fsm.evalBroker.SetEnabled(true) 375 376 req := structs.EvalUpdateRequest{ 377 Evals: []*structs.Evaluation{mock.Eval()}, 378 } 379 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 380 if err != nil { 381 t.Fatalf("err: %v", err) 382 } 383 384 resp := fsm.Apply(makeLog(buf)) 385 if resp != nil { 386 t.Fatalf("resp: %v", resp) 387 } 388 389 // Verify we are registered 390 ws := memdb.NewWatchSet() 391 eval, err := fsm.State().EvalByID(ws, req.Evals[0].ID) 392 if err != nil { 393 t.Fatalf("err: %v", err) 394 } 395 if eval == nil { 396 t.Fatalf("not found!") 397 } 398 if eval.CreateIndex != 1 { 399 t.Fatalf("bad index: %d", eval.CreateIndex) 400 } 401 402 // Verify enqueued 403 stats := fsm.evalBroker.Stats() 404 if stats.TotalReady != 1 { 405 t.Fatalf("bad: %#v %#v", stats, eval) 406 } 407 } 408 409 func TestFSM_UpdateEval_Blocked(t *testing.T) { 410 fsm := testFSM(t) 411 fsm.evalBroker.SetEnabled(true) 412 fsm.blockedEvals.SetEnabled(true) 413 414 // Create a blocked eval. 415 eval := mock.Eval() 416 eval.Status = structs.EvalStatusBlocked 417 418 req := structs.EvalUpdateRequest{ 419 Evals: []*structs.Evaluation{eval}, 420 } 421 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 422 if err != nil { 423 t.Fatalf("err: %v", err) 424 } 425 426 resp := fsm.Apply(makeLog(buf)) 427 if resp != nil { 428 t.Fatalf("resp: %v", resp) 429 } 430 431 // Verify we are registered 432 ws := memdb.NewWatchSet() 433 out, err := fsm.State().EvalByID(ws, eval.ID) 434 if err != nil { 435 t.Fatalf("err: %v", err) 436 } 437 if out == nil { 438 t.Fatalf("not found!") 439 } 440 if out.CreateIndex != 1 { 441 t.Fatalf("bad index: %d", out.CreateIndex) 442 } 443 444 // Verify the eval wasn't enqueued 445 stats := fsm.evalBroker.Stats() 446 if stats.TotalReady != 0 { 447 t.Fatalf("bad: %#v %#v", stats, out) 448 } 449 450 // Verify the eval was added to the blocked tracker. 451 bStats := fsm.blockedEvals.Stats() 452 if bStats.TotalBlocked != 1 { 453 t.Fatalf("bad: %#v %#v", bStats, out) 454 } 455 } 456 457 func TestFSM_UpdateEval_Untrack(t *testing.T) { 458 fsm := testFSM(t) 459 fsm.evalBroker.SetEnabled(true) 460 fsm.blockedEvals.SetEnabled(true) 461 462 // Mark an eval as blocked. 463 bEval := mock.Eval() 464 bEval.ClassEligibility = map[string]bool{"v1:123": true} 465 fsm.blockedEvals.Block(bEval) 466 467 // Create a successful eval for the same job 468 eval := mock.Eval() 469 eval.JobID = bEval.JobID 470 eval.Status = structs.EvalStatusComplete 471 472 req := structs.EvalUpdateRequest{ 473 Evals: []*structs.Evaluation{eval}, 474 } 475 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 476 if err != nil { 477 t.Fatalf("err: %v", err) 478 } 479 480 resp := fsm.Apply(makeLog(buf)) 481 if resp != nil { 482 t.Fatalf("resp: %v", resp) 483 } 484 485 // Verify we are registered 486 ws := memdb.NewWatchSet() 487 out, err := fsm.State().EvalByID(ws, eval.ID) 488 if err != nil { 489 t.Fatalf("err: %v", err) 490 } 491 if out == nil { 492 t.Fatalf("not found!") 493 } 494 if out.CreateIndex != 1 { 495 t.Fatalf("bad index: %d", out.CreateIndex) 496 } 497 498 // Verify the eval wasn't enqueued 499 stats := fsm.evalBroker.Stats() 500 if stats.TotalReady != 0 { 501 t.Fatalf("bad: %#v %#v", stats, out) 502 } 503 504 // Verify the eval was untracked in the blocked tracker. 505 bStats := fsm.blockedEvals.Stats() 506 if bStats.TotalBlocked != 0 { 507 t.Fatalf("bad: %#v %#v", bStats, out) 508 } 509 } 510 511 func TestFSM_UpdateEval_NoUntrack(t *testing.T) { 512 fsm := testFSM(t) 513 fsm.evalBroker.SetEnabled(true) 514 fsm.blockedEvals.SetEnabled(true) 515 516 // Mark an eval as blocked. 517 bEval := mock.Eval() 518 bEval.ClassEligibility = map[string]bool{"v1:123": true} 519 fsm.blockedEvals.Block(bEval) 520 521 // Create a successful eval for the same job but with placement failures 522 eval := mock.Eval() 523 eval.JobID = bEval.JobID 524 eval.Status = structs.EvalStatusComplete 525 eval.FailedTGAllocs = make(map[string]*structs.AllocMetric) 526 eval.FailedTGAllocs["test"] = new(structs.AllocMetric) 527 528 req := structs.EvalUpdateRequest{ 529 Evals: []*structs.Evaluation{eval}, 530 } 531 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 532 if err != nil { 533 t.Fatalf("err: %v", err) 534 } 535 536 resp := fsm.Apply(makeLog(buf)) 537 if resp != nil { 538 t.Fatalf("resp: %v", resp) 539 } 540 541 // Verify we are registered 542 ws := memdb.NewWatchSet() 543 out, err := fsm.State().EvalByID(ws, eval.ID) 544 if err != nil { 545 t.Fatalf("err: %v", err) 546 } 547 if out == nil { 548 t.Fatalf("not found!") 549 } 550 if out.CreateIndex != 1 { 551 t.Fatalf("bad index: %d", out.CreateIndex) 552 } 553 554 // Verify the eval wasn't enqueued 555 stats := fsm.evalBroker.Stats() 556 if stats.TotalReady != 0 { 557 t.Fatalf("bad: %#v %#v", stats, out) 558 } 559 560 // Verify the eval was not untracked in the blocked tracker. 561 bStats := fsm.blockedEvals.Stats() 562 if bStats.TotalBlocked != 1 { 563 t.Fatalf("bad: %#v %#v", bStats, out) 564 } 565 } 566 567 func TestFSM_DeleteEval(t *testing.T) { 568 fsm := testFSM(t) 569 570 eval := mock.Eval() 571 req := structs.EvalUpdateRequest{ 572 Evals: []*structs.Evaluation{eval}, 573 } 574 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 575 if err != nil { 576 t.Fatalf("err: %v", err) 577 } 578 579 resp := fsm.Apply(makeLog(buf)) 580 if resp != nil { 581 t.Fatalf("resp: %v", resp) 582 } 583 584 req2 := structs.EvalDeleteRequest{ 585 Evals: []string{eval.ID}, 586 } 587 buf, err = structs.Encode(structs.EvalDeleteRequestType, req2) 588 if err != nil { 589 t.Fatalf("err: %v", err) 590 } 591 592 resp = fsm.Apply(makeLog(buf)) 593 if resp != nil { 594 t.Fatalf("resp: %v", resp) 595 } 596 597 // Verify we are NOT registered 598 ws := memdb.NewWatchSet() 599 eval, err = fsm.State().EvalByID(ws, req.Evals[0].ID) 600 if err != nil { 601 t.Fatalf("err: %v", err) 602 } 603 if eval != nil { 604 t.Fatalf("eval found!") 605 } 606 } 607 608 func TestFSM_UpsertAllocs(t *testing.T) { 609 fsm := testFSM(t) 610 611 alloc := mock.Alloc() 612 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 613 req := structs.AllocUpdateRequest{ 614 Alloc: []*structs.Allocation{alloc}, 615 } 616 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 617 if err != nil { 618 t.Fatalf("err: %v", err) 619 } 620 621 resp := fsm.Apply(makeLog(buf)) 622 if resp != nil { 623 t.Fatalf("resp: %v", resp) 624 } 625 626 // Verify we are registered 627 ws := memdb.NewWatchSet() 628 out, err := fsm.State().AllocByID(ws, alloc.ID) 629 if err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 alloc.CreateIndex = out.CreateIndex 633 alloc.ModifyIndex = out.ModifyIndex 634 alloc.AllocModifyIndex = out.AllocModifyIndex 635 if !reflect.DeepEqual(alloc, out) { 636 t.Fatalf("bad: %#v %#v", alloc, out) 637 } 638 639 evictAlloc := new(structs.Allocation) 640 *evictAlloc = *alloc 641 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 642 req2 := structs.AllocUpdateRequest{ 643 Alloc: []*structs.Allocation{evictAlloc}, 644 } 645 buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) 646 if err != nil { 647 t.Fatalf("err: %v", err) 648 } 649 650 resp = fsm.Apply(makeLog(buf)) 651 if resp != nil { 652 t.Fatalf("resp: %v", resp) 653 } 654 655 // Verify we are evicted 656 out, err = fsm.State().AllocByID(ws, alloc.ID) 657 if err != nil { 658 t.Fatalf("err: %v", err) 659 } 660 if out.DesiredStatus != structs.AllocDesiredStatusEvict { 661 t.Fatalf("alloc found!") 662 } 663 } 664 665 func TestFSM_UpsertAllocs_SharedJob(t *testing.T) { 666 fsm := testFSM(t) 667 668 alloc := mock.Alloc() 669 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 670 job := alloc.Job 671 alloc.Job = nil 672 req := structs.AllocUpdateRequest{ 673 Job: job, 674 Alloc: []*structs.Allocation{alloc}, 675 } 676 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 677 if err != nil { 678 t.Fatalf("err: %v", err) 679 } 680 681 resp := fsm.Apply(makeLog(buf)) 682 if resp != nil { 683 t.Fatalf("resp: %v", resp) 684 } 685 686 // Verify we are registered 687 ws := memdb.NewWatchSet() 688 out, err := fsm.State().AllocByID(ws, alloc.ID) 689 if err != nil { 690 t.Fatalf("err: %v", err) 691 } 692 alloc.CreateIndex = out.CreateIndex 693 alloc.ModifyIndex = out.ModifyIndex 694 alloc.AllocModifyIndex = out.AllocModifyIndex 695 696 // Job should be re-attached 697 alloc.Job = job 698 if !reflect.DeepEqual(alloc, out) { 699 t.Fatalf("bad: %#v %#v", alloc, out) 700 } 701 702 // Ensure that the original job is used 703 evictAlloc := new(structs.Allocation) 704 *evictAlloc = *alloc 705 job = mock.Job() 706 job.Priority = 123 707 708 evictAlloc.Job = nil 709 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 710 req2 := structs.AllocUpdateRequest{ 711 Job: job, 712 Alloc: []*structs.Allocation{evictAlloc}, 713 } 714 buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) 715 if err != nil { 716 t.Fatalf("err: %v", err) 717 } 718 719 resp = fsm.Apply(makeLog(buf)) 720 if resp != nil { 721 t.Fatalf("resp: %v", resp) 722 } 723 724 // Verify we are evicted 725 out, err = fsm.State().AllocByID(ws, alloc.ID) 726 if err != nil { 727 t.Fatalf("err: %v", err) 728 } 729 if out.DesiredStatus != structs.AllocDesiredStatusEvict { 730 t.Fatalf("alloc found!") 731 } 732 if out.Job == nil || out.Job.Priority == 123 { 733 t.Fatalf("bad job") 734 } 735 } 736 737 func TestFSM_UpsertAllocs_StrippedResources(t *testing.T) { 738 fsm := testFSM(t) 739 740 alloc := mock.Alloc() 741 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 742 job := alloc.Job 743 resources := alloc.Resources 744 alloc.Resources = nil 745 req := structs.AllocUpdateRequest{ 746 Job: job, 747 Alloc: []*structs.Allocation{alloc}, 748 } 749 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 750 if err != nil { 751 t.Fatalf("err: %v", err) 752 } 753 754 resp := fsm.Apply(makeLog(buf)) 755 if resp != nil { 756 t.Fatalf("resp: %v", resp) 757 } 758 759 // Verify we are registered 760 ws := memdb.NewWatchSet() 761 out, err := fsm.State().AllocByID(ws, alloc.ID) 762 if err != nil { 763 t.Fatalf("err: %v", err) 764 } 765 alloc.CreateIndex = out.CreateIndex 766 alloc.ModifyIndex = out.ModifyIndex 767 alloc.AllocModifyIndex = out.AllocModifyIndex 768 769 // Resources should be recomputed 770 resources.DiskMB = alloc.Job.TaskGroups[0].EphemeralDisk.SizeMB 771 alloc.Resources = resources 772 if !reflect.DeepEqual(alloc, out) { 773 t.Fatalf("bad: %#v %#v", alloc, out) 774 } 775 } 776 777 func TestFSM_UpdateAllocFromClient_Unblock(t *testing.T) { 778 fsm := testFSM(t) 779 fsm.blockedEvals.SetEnabled(true) 780 state := fsm.State() 781 782 node := mock.Node() 783 state.UpsertNode(1, node) 784 785 // Mark an eval as blocked. 786 eval := mock.Eval() 787 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 788 fsm.blockedEvals.Block(eval) 789 790 bStats := fsm.blockedEvals.Stats() 791 if bStats.TotalBlocked != 1 { 792 t.Fatalf("bad: %#v", bStats) 793 } 794 795 // Create a completed eval 796 alloc := mock.Alloc() 797 alloc.NodeID = node.ID 798 alloc2 := mock.Alloc() 799 alloc2.NodeID = node.ID 800 state.UpsertJobSummary(8, mock.JobSummary(alloc.JobID)) 801 state.UpsertJobSummary(9, mock.JobSummary(alloc2.JobID)) 802 state.UpsertAllocs(10, []*structs.Allocation{alloc, alloc2}) 803 804 clientAlloc := new(structs.Allocation) 805 *clientAlloc = *alloc 806 clientAlloc.ClientStatus = structs.AllocClientStatusComplete 807 update2 := &structs.Allocation{ 808 ID: alloc2.ID, 809 ClientStatus: structs.AllocClientStatusRunning, 810 } 811 812 req := structs.AllocUpdateRequest{ 813 Alloc: []*structs.Allocation{clientAlloc, update2}, 814 } 815 buf, err := structs.Encode(structs.AllocClientUpdateRequestType, req) 816 if err != nil { 817 t.Fatalf("err: %v", err) 818 } 819 820 resp := fsm.Apply(makeLog(buf)) 821 if resp != nil { 822 t.Fatalf("resp: %v", resp) 823 } 824 825 // Verify we are updated 826 ws := memdb.NewWatchSet() 827 out, err := fsm.State().AllocByID(ws, alloc.ID) 828 if err != nil { 829 t.Fatalf("err: %v", err) 830 } 831 clientAlloc.CreateIndex = out.CreateIndex 832 clientAlloc.ModifyIndex = out.ModifyIndex 833 if !reflect.DeepEqual(clientAlloc, out) { 834 t.Fatalf("bad: %#v %#v", clientAlloc, out) 835 } 836 837 out, err = fsm.State().AllocByID(ws, alloc2.ID) 838 if err != nil { 839 t.Fatalf("err: %v", err) 840 } 841 alloc2.CreateIndex = out.CreateIndex 842 alloc2.ModifyIndex = out.ModifyIndex 843 alloc2.ClientStatus = structs.AllocClientStatusRunning 844 alloc2.TaskStates = nil 845 if !reflect.DeepEqual(alloc2, out) { 846 t.Fatalf("bad: %#v %#v", alloc2, out) 847 } 848 849 // Verify the eval was unblocked. 850 testutil.WaitForResult(func() (bool, error) { 851 bStats = fsm.blockedEvals.Stats() 852 if bStats.TotalBlocked != 0 { 853 return false, fmt.Errorf("bad: %#v %#v", bStats, out) 854 } 855 return true, nil 856 }, func(err error) { 857 t.Fatalf("err: %s", err) 858 }) 859 } 860 861 func TestFSM_UpdateAllocFromClient(t *testing.T) { 862 fsm := testFSM(t) 863 state := fsm.State() 864 865 alloc := mock.Alloc() 866 state.UpsertJobSummary(9, mock.JobSummary(alloc.JobID)) 867 state.UpsertAllocs(10, []*structs.Allocation{alloc}) 868 869 clientAlloc := new(structs.Allocation) 870 *clientAlloc = *alloc 871 clientAlloc.ClientStatus = structs.AllocClientStatusFailed 872 873 req := structs.AllocUpdateRequest{ 874 Alloc: []*structs.Allocation{clientAlloc}, 875 } 876 buf, err := structs.Encode(structs.AllocClientUpdateRequestType, req) 877 if err != nil { 878 t.Fatalf("err: %v", err) 879 } 880 881 resp := fsm.Apply(makeLog(buf)) 882 if resp != nil { 883 t.Fatalf("resp: %v", resp) 884 } 885 886 // Verify we are registered 887 ws := memdb.NewWatchSet() 888 out, err := fsm.State().AllocByID(ws, alloc.ID) 889 if err != nil { 890 t.Fatalf("err: %v", err) 891 } 892 clientAlloc.CreateIndex = out.CreateIndex 893 clientAlloc.ModifyIndex = out.ModifyIndex 894 if !reflect.DeepEqual(clientAlloc, out) { 895 t.Fatalf("err: %#v,%#v", clientAlloc, out) 896 } 897 } 898 899 func TestFSM_UpsertVaultAccessor(t *testing.T) { 900 fsm := testFSM(t) 901 fsm.blockedEvals.SetEnabled(true) 902 903 va := mock.VaultAccessor() 904 va2 := mock.VaultAccessor() 905 req := structs.VaultAccessorsRequest{ 906 Accessors: []*structs.VaultAccessor{va, va2}, 907 } 908 buf, err := structs.Encode(structs.VaultAccessorRegisterRequestType, req) 909 if err != nil { 910 t.Fatalf("err: %v", err) 911 } 912 913 resp := fsm.Apply(makeLog(buf)) 914 if resp != nil { 915 t.Fatalf("resp: %v", resp) 916 } 917 918 // Verify we are registered 919 ws := memdb.NewWatchSet() 920 out1, err := fsm.State().VaultAccessor(ws, va.Accessor) 921 if err != nil { 922 t.Fatalf("err: %v", err) 923 } 924 if out1 == nil { 925 t.Fatalf("not found!") 926 } 927 if out1.CreateIndex != 1 { 928 t.Fatalf("bad index: %d", out1.CreateIndex) 929 } 930 out2, err := fsm.State().VaultAccessor(ws, va2.Accessor) 931 if err != nil { 932 t.Fatalf("err: %v", err) 933 } 934 if out2 == nil { 935 t.Fatalf("not found!") 936 } 937 if out1.CreateIndex != 1 { 938 t.Fatalf("bad index: %d", out2.CreateIndex) 939 } 940 941 tt := fsm.TimeTable() 942 index := tt.NearestIndex(time.Now().UTC()) 943 if index != 1 { 944 t.Fatalf("bad: %d", index) 945 } 946 } 947 948 func TestFSM_DeregisterVaultAccessor(t *testing.T) { 949 fsm := testFSM(t) 950 fsm.blockedEvals.SetEnabled(true) 951 952 va := mock.VaultAccessor() 953 va2 := mock.VaultAccessor() 954 accessors := []*structs.VaultAccessor{va, va2} 955 956 // Insert the accessors 957 if err := fsm.State().UpsertVaultAccessor(1000, accessors); err != nil { 958 t.Fatalf("bad: %v", err) 959 } 960 961 req := structs.VaultAccessorsRequest{ 962 Accessors: accessors, 963 } 964 buf, err := structs.Encode(structs.VaultAccessorDegisterRequestType, req) 965 if err != nil { 966 t.Fatalf("err: %v", err) 967 } 968 969 resp := fsm.Apply(makeLog(buf)) 970 if resp != nil { 971 t.Fatalf("resp: %v", resp) 972 } 973 974 ws := memdb.NewWatchSet() 975 out1, err := fsm.State().VaultAccessor(ws, va.Accessor) 976 if err != nil { 977 t.Fatalf("err: %v", err) 978 } 979 if out1 != nil { 980 t.Fatalf("not deleted!") 981 } 982 983 tt := fsm.TimeTable() 984 index := tt.NearestIndex(time.Now().UTC()) 985 if index != 1 { 986 t.Fatalf("bad: %d", index) 987 } 988 } 989 990 func testSnapshotRestore(t *testing.T, fsm *nomadFSM) *nomadFSM { 991 // Snapshot 992 snap, err := fsm.Snapshot() 993 if err != nil { 994 t.Fatalf("err: %v", err) 995 } 996 defer snap.Release() 997 998 // Persist 999 buf := bytes.NewBuffer(nil) 1000 sink := &MockSink{buf, false} 1001 if err := snap.Persist(sink); err != nil { 1002 t.Fatalf("err: %v", err) 1003 } 1004 1005 // Try to restore on a new FSM 1006 fsm2 := testFSM(t) 1007 snap, err = fsm2.Snapshot() 1008 if err != nil { 1009 t.Fatalf("err: %v", err) 1010 } 1011 defer snap.Release() 1012 1013 abandonCh := fsm2.State().AbandonCh() 1014 1015 // Do a restore 1016 if err := fsm2.Restore(sink); err != nil { 1017 t.Fatalf("err: %v", err) 1018 } 1019 1020 select { 1021 case <-abandonCh: 1022 default: 1023 t.Fatalf("bad") 1024 } 1025 1026 return fsm2 1027 } 1028 1029 func TestFSM_SnapshotRestore_Nodes(t *testing.T) { 1030 // Add some state 1031 fsm := testFSM(t) 1032 state := fsm.State() 1033 node1 := mock.Node() 1034 state.UpsertNode(1000, node1) 1035 node2 := mock.Node() 1036 state.UpsertNode(1001, node2) 1037 1038 // Verify the contents 1039 fsm2 := testSnapshotRestore(t, fsm) 1040 state2 := fsm2.State() 1041 ws := memdb.NewWatchSet() 1042 out1, _ := state2.NodeByID(ws, node1.ID) 1043 out2, _ := state2.NodeByID(ws, node2.ID) 1044 if !reflect.DeepEqual(node1, out1) { 1045 t.Fatalf("bad: \n%#v\n%#v", out1, node1) 1046 } 1047 if !reflect.DeepEqual(node2, out2) { 1048 t.Fatalf("bad: \n%#v\n%#v", out2, node2) 1049 } 1050 } 1051 1052 func TestFSM_SnapshotRestore_Jobs(t *testing.T) { 1053 // Add some state 1054 fsm := testFSM(t) 1055 state := fsm.State() 1056 job1 := mock.Job() 1057 state.UpsertJob(1000, job1) 1058 job2 := mock.Job() 1059 state.UpsertJob(1001, job2) 1060 1061 // Verify the contents 1062 ws := memdb.NewWatchSet() 1063 fsm2 := testSnapshotRestore(t, fsm) 1064 state2 := fsm2.State() 1065 out1, _ := state2.JobByID(ws, job1.ID) 1066 out2, _ := state2.JobByID(ws, job2.ID) 1067 if !reflect.DeepEqual(job1, out1) { 1068 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 1069 } 1070 if !reflect.DeepEqual(job2, out2) { 1071 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 1072 } 1073 } 1074 1075 func TestFSM_SnapshotRestore_Evals(t *testing.T) { 1076 // Add some state 1077 fsm := testFSM(t) 1078 state := fsm.State() 1079 eval1 := mock.Eval() 1080 state.UpsertEvals(1000, []*structs.Evaluation{eval1}) 1081 eval2 := mock.Eval() 1082 state.UpsertEvals(1001, []*structs.Evaluation{eval2}) 1083 1084 // Verify the contents 1085 fsm2 := testSnapshotRestore(t, fsm) 1086 state2 := fsm2.State() 1087 ws := memdb.NewWatchSet() 1088 out1, _ := state2.EvalByID(ws, eval1.ID) 1089 out2, _ := state2.EvalByID(ws, eval2.ID) 1090 if !reflect.DeepEqual(eval1, out1) { 1091 t.Fatalf("bad: \n%#v\n%#v", out1, eval1) 1092 } 1093 if !reflect.DeepEqual(eval2, out2) { 1094 t.Fatalf("bad: \n%#v\n%#v", out2, eval2) 1095 } 1096 } 1097 1098 func TestFSM_SnapshotRestore_Allocs(t *testing.T) { 1099 // Add some state 1100 fsm := testFSM(t) 1101 state := fsm.State() 1102 alloc1 := mock.Alloc() 1103 alloc2 := mock.Alloc() 1104 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 1105 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 1106 state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 1107 state.UpsertAllocs(1001, []*structs.Allocation{alloc2}) 1108 1109 // Verify the contents 1110 fsm2 := testSnapshotRestore(t, fsm) 1111 state2 := fsm2.State() 1112 ws := memdb.NewWatchSet() 1113 out1, _ := state2.AllocByID(ws, alloc1.ID) 1114 out2, _ := state2.AllocByID(ws, alloc2.ID) 1115 if !reflect.DeepEqual(alloc1, out1) { 1116 t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) 1117 } 1118 if !reflect.DeepEqual(alloc2, out2) { 1119 t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) 1120 } 1121 } 1122 1123 func TestFSM_SnapshotRestore_Allocs_NoSharedResources(t *testing.T) { 1124 // Add some state 1125 fsm := testFSM(t) 1126 state := fsm.State() 1127 alloc1 := mock.Alloc() 1128 alloc2 := mock.Alloc() 1129 alloc1.SharedResources = nil 1130 alloc2.SharedResources = nil 1131 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 1132 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 1133 state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 1134 state.UpsertAllocs(1001, []*structs.Allocation{alloc2}) 1135 1136 // Verify the contents 1137 fsm2 := testSnapshotRestore(t, fsm) 1138 state2 := fsm2.State() 1139 ws := memdb.NewWatchSet() 1140 out1, _ := state2.AllocByID(ws, alloc1.ID) 1141 out2, _ := state2.AllocByID(ws, alloc2.ID) 1142 alloc1.SharedResources = &structs.Resources{DiskMB: 150} 1143 alloc2.SharedResources = &structs.Resources{DiskMB: 150} 1144 1145 if !reflect.DeepEqual(alloc1, out1) { 1146 t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) 1147 } 1148 if !reflect.DeepEqual(alloc2, out2) { 1149 t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) 1150 } 1151 } 1152 1153 func TestFSM_SnapshotRestore_Indexes(t *testing.T) { 1154 // Add some state 1155 fsm := testFSM(t) 1156 state := fsm.State() 1157 node1 := mock.Node() 1158 state.UpsertNode(1000, node1) 1159 1160 // Verify the contents 1161 fsm2 := testSnapshotRestore(t, fsm) 1162 state2 := fsm2.State() 1163 1164 index, err := state2.Index("nodes") 1165 if err != nil { 1166 t.Fatalf("err: %v", err) 1167 } 1168 if index != 1000 { 1169 t.Fatalf("bad: %d", index) 1170 } 1171 } 1172 1173 func TestFSM_SnapshotRestore_TimeTable(t *testing.T) { 1174 // Add some state 1175 fsm := testFSM(t) 1176 1177 tt := fsm.TimeTable() 1178 start := time.Now().UTC() 1179 tt.Witness(1000, start) 1180 tt.Witness(2000, start.Add(10*time.Minute)) 1181 1182 // Verify the contents 1183 fsm2 := testSnapshotRestore(t, fsm) 1184 1185 tt2 := fsm2.TimeTable() 1186 if tt2.NearestTime(1500) != start { 1187 t.Fatalf("bad") 1188 } 1189 if tt2.NearestIndex(start.Add(15*time.Minute)) != 2000 { 1190 t.Fatalf("bad") 1191 } 1192 } 1193 1194 func TestFSM_SnapshotRestore_PeriodicLaunches(t *testing.T) { 1195 // Add some state 1196 fsm := testFSM(t) 1197 state := fsm.State() 1198 job1 := mock.Job() 1199 launch1 := &structs.PeriodicLaunch{ID: job1.ID, Launch: time.Now()} 1200 state.UpsertPeriodicLaunch(1000, launch1) 1201 job2 := mock.Job() 1202 launch2 := &structs.PeriodicLaunch{ID: job2.ID, Launch: time.Now()} 1203 state.UpsertPeriodicLaunch(1001, launch2) 1204 1205 // Verify the contents 1206 fsm2 := testSnapshotRestore(t, fsm) 1207 state2 := fsm2.State() 1208 ws := memdb.NewWatchSet() 1209 out1, _ := state2.PeriodicLaunchByID(ws, launch1.ID) 1210 out2, _ := state2.PeriodicLaunchByID(ws, launch2.ID) 1211 if !reflect.DeepEqual(launch1, out1) { 1212 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 1213 } 1214 if !reflect.DeepEqual(launch2, out2) { 1215 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 1216 } 1217 } 1218 1219 func TestFSM_SnapshotRestore_JobSummary(t *testing.T) { 1220 // Add some state 1221 fsm := testFSM(t) 1222 state := fsm.State() 1223 1224 job1 := mock.Job() 1225 state.UpsertJob(1000, job1) 1226 ws := memdb.NewWatchSet() 1227 js1, _ := state.JobSummaryByID(ws, job1.ID) 1228 1229 job2 := mock.Job() 1230 state.UpsertJob(1001, job2) 1231 js2, _ := state.JobSummaryByID(ws, job2.ID) 1232 1233 // Verify the contents 1234 fsm2 := testSnapshotRestore(t, fsm) 1235 state2 := fsm2.State() 1236 out1, _ := state2.JobSummaryByID(ws, job1.ID) 1237 out2, _ := state2.JobSummaryByID(ws, job2.ID) 1238 if !reflect.DeepEqual(js1, out1) { 1239 t.Fatalf("bad: \n%#v\n%#v", js1, out1) 1240 } 1241 if !reflect.DeepEqual(js2, out2) { 1242 t.Fatalf("bad: \n%#v\n%#v", js2, out2) 1243 } 1244 } 1245 1246 func TestFSM_SnapshotRestore_VaultAccessors(t *testing.T) { 1247 // Add some state 1248 fsm := testFSM(t) 1249 state := fsm.State() 1250 a1 := mock.VaultAccessor() 1251 a2 := mock.VaultAccessor() 1252 state.UpsertVaultAccessor(1000, []*structs.VaultAccessor{a1, a2}) 1253 1254 // Verify the contents 1255 fsm2 := testSnapshotRestore(t, fsm) 1256 state2 := fsm2.State() 1257 ws := memdb.NewWatchSet() 1258 out1, _ := state2.VaultAccessor(ws, a1.Accessor) 1259 out2, _ := state2.VaultAccessor(ws, a2.Accessor) 1260 if !reflect.DeepEqual(a1, out1) { 1261 t.Fatalf("bad: \n%#v\n%#v", out1, a1) 1262 } 1263 if !reflect.DeepEqual(a2, out2) { 1264 t.Fatalf("bad: \n%#v\n%#v", out2, a2) 1265 } 1266 } 1267 1268 func TestFSM_SnapshotRestore_AddMissingSummary(t *testing.T) { 1269 // Add some state 1270 fsm := testFSM(t) 1271 state := fsm.State() 1272 1273 // make an allocation 1274 alloc := mock.Alloc() 1275 state.UpsertJob(1010, alloc.Job) 1276 state.UpsertAllocs(1011, []*structs.Allocation{alloc}) 1277 1278 // Delete the summary 1279 state.DeleteJobSummary(1040, alloc.Job.ID) 1280 1281 // Delete the index 1282 if err := state.RemoveIndex("job_summary"); err != nil { 1283 t.Fatalf("err: %v", err) 1284 } 1285 1286 fsm2 := testSnapshotRestore(t, fsm) 1287 state2 := fsm2.State() 1288 latestIndex, _ := state.LatestIndex() 1289 1290 ws := memdb.NewWatchSet() 1291 out, _ := state2.JobSummaryByID(ws, alloc.Job.ID) 1292 expected := structs.JobSummary{ 1293 JobID: alloc.Job.ID, 1294 Summary: map[string]structs.TaskGroupSummary{ 1295 "web": structs.TaskGroupSummary{ 1296 Starting: 1, 1297 }, 1298 }, 1299 CreateIndex: 1010, 1300 ModifyIndex: latestIndex, 1301 } 1302 if !reflect.DeepEqual(&expected, out) { 1303 t.Fatalf("expected: %#v, actual: %#v", &expected, out) 1304 } 1305 } 1306 1307 func TestFSM_ReconcileSummaries(t *testing.T) { 1308 // Add some state 1309 fsm := testFSM(t) 1310 state := fsm.State() 1311 1312 // Add a node 1313 node := mock.Node() 1314 state.UpsertNode(800, node) 1315 1316 // Make a job so that none of the tasks can be placed 1317 job1 := mock.Job() 1318 job1.TaskGroups[0].Tasks[0].Resources.CPU = 5000 1319 state.UpsertJob(1000, job1) 1320 1321 // make a job which can make partial progress 1322 alloc := mock.Alloc() 1323 alloc.NodeID = node.ID 1324 state.UpsertJob(1010, alloc.Job) 1325 state.UpsertAllocs(1011, []*structs.Allocation{alloc}) 1326 1327 // Delete the summaries 1328 state.DeleteJobSummary(1030, job1.ID) 1329 state.DeleteJobSummary(1040, alloc.Job.ID) 1330 1331 req := structs.GenericRequest{} 1332 buf, err := structs.Encode(structs.ReconcileJobSummariesRequestType, req) 1333 if err != nil { 1334 t.Fatalf("err: %v", err) 1335 } 1336 1337 resp := fsm.Apply(makeLog(buf)) 1338 if resp != nil { 1339 t.Fatalf("resp: %v", resp) 1340 } 1341 1342 ws := memdb.NewWatchSet() 1343 out1, _ := state.JobSummaryByID(ws, job1.ID) 1344 expected := structs.JobSummary{ 1345 JobID: job1.ID, 1346 Summary: map[string]structs.TaskGroupSummary{ 1347 "web": structs.TaskGroupSummary{ 1348 Queued: 10, 1349 }, 1350 }, 1351 CreateIndex: 1000, 1352 ModifyIndex: out1.ModifyIndex, 1353 } 1354 if !reflect.DeepEqual(&expected, out1) { 1355 t.Fatalf("expected: %#v, actual: %#v", &expected, out1) 1356 } 1357 1358 // This exercises the code path which adds the allocations made by the 1359 // planner and the number of unplaced allocations in the reconcile summaries 1360 // codepath 1361 out2, _ := state.JobSummaryByID(ws, alloc.Job.ID) 1362 expected = structs.JobSummary{ 1363 JobID: alloc.Job.ID, 1364 Summary: map[string]structs.TaskGroupSummary{ 1365 "web": structs.TaskGroupSummary{ 1366 Queued: 10, 1367 Starting: 1, 1368 }, 1369 }, 1370 CreateIndex: 1010, 1371 ModifyIndex: out2.ModifyIndex, 1372 } 1373 if !reflect.DeepEqual(&expected, out2) { 1374 t.Fatalf("expected: %#v, actual: %#v", &expected, out2) 1375 } 1376 }