github.com/djenriquez/nomad-1@v0.8.1/nomad/fsm_test.go (about) 1 package nomad 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 "testing" 10 "time" 11 12 "github.com/google/go-cmp/cmp" 13 memdb "github.com/hashicorp/go-memdb" 14 "github.com/hashicorp/nomad/helper" 15 "github.com/hashicorp/nomad/helper/uuid" 16 "github.com/hashicorp/nomad/nomad/mock" 17 "github.com/hashicorp/nomad/nomad/state" 18 "github.com/hashicorp/nomad/nomad/structs" 19 "github.com/hashicorp/nomad/testutil" 20 "github.com/hashicorp/raft" 21 "github.com/kr/pretty" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 ) 25 26 type MockSink struct { 27 *bytes.Buffer 28 cancel bool 29 } 30 31 func (m *MockSink) ID() string { 32 return "Mock" 33 } 34 35 func (m *MockSink) Cancel() error { 36 m.cancel = true 37 return nil 38 } 39 40 func (m *MockSink) Close() error { 41 return nil 42 } 43 44 func testStateStore(t *testing.T) *state.StateStore { 45 return state.TestStateStore(t) 46 } 47 48 func testFSM(t *testing.T) *nomadFSM { 49 broker := testBroker(t, 0) 50 dispatcher, _ := testPeriodicDispatcher() 51 fsmConfig := &FSMConfig{ 52 EvalBroker: broker, 53 Periodic: dispatcher, 54 Blocked: NewBlockedEvals(broker), 55 LogOutput: os.Stderr, 56 Region: "global", 57 } 58 fsm, err := NewFSM(fsmConfig) 59 if err != nil { 60 t.Fatalf("err: %v", err) 61 } 62 if fsm == nil { 63 t.Fatalf("missing fsm") 64 } 65 state.TestInitState(t, fsm.state) 66 return fsm 67 } 68 69 func makeLog(buf []byte) *raft.Log { 70 return &raft.Log{ 71 Index: 1, 72 Term: 1, 73 Type: raft.LogCommand, 74 Data: buf, 75 } 76 } 77 78 func TestFSM_UpsertNodeEvents(t *testing.T) { 79 t.Parallel() 80 require := require.New(t) 81 fsm := testFSM(t) 82 state := fsm.State() 83 84 node := mock.Node() 85 86 err := state.UpsertNode(1000, node) 87 if err != nil { 88 t.Fatalf("err: %v", err) 89 } 90 91 nodeEvent := &structs.NodeEvent{ 92 Message: "Heartbeating failed", 93 Subsystem: "Heartbeat", 94 Timestamp: time.Now(), 95 } 96 97 nodeEvents := []*structs.NodeEvent{nodeEvent} 98 allEvents := map[string][]*structs.NodeEvent{node.ID: nodeEvents} 99 100 req := structs.EmitNodeEventsRequest{ 101 NodeEvents: allEvents, 102 WriteRequest: structs.WriteRequest{Region: "global"}, 103 } 104 buf, err := structs.Encode(structs.UpsertNodeEventsType, req) 105 require.Nil(err) 106 107 // the response in this case will be an error 108 resp := fsm.Apply(makeLog(buf)) 109 require.Nil(resp) 110 111 ws := memdb.NewWatchSet() 112 out, err := state.NodeByID(ws, node.ID) 113 require.Nil(err) 114 115 require.Equal(2, len(out.Events)) 116 117 first := out.Events[1] 118 require.Equal(uint64(1), first.CreateIndex) 119 require.Equal("Heartbeating failed", first.Message) 120 } 121 122 func TestFSM_UpsertNode(t *testing.T) { 123 t.Parallel() 124 fsm := testFSM(t) 125 fsm.blockedEvals.SetEnabled(true) 126 127 node := mock.Node() 128 129 // Mark an eval as blocked. 130 eval := mock.Eval() 131 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 132 fsm.blockedEvals.Block(eval) 133 134 req := structs.NodeRegisterRequest{ 135 Node: node, 136 } 137 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 138 if err != nil { 139 t.Fatalf("err: %v", err) 140 } 141 142 resp := fsm.Apply(makeLog(buf)) 143 if resp != nil { 144 t.Fatalf("resp: %v", resp) 145 } 146 147 // Verify we are registered 148 ws := memdb.NewWatchSet() 149 n, err := fsm.State().NodeByID(ws, req.Node.ID) 150 if err != nil { 151 t.Fatalf("err: %v", err) 152 } 153 if n == nil { 154 t.Fatalf("not found!") 155 } 156 if n.CreateIndex != 1 { 157 t.Fatalf("bad index: %d", node.CreateIndex) 158 } 159 160 tt := fsm.TimeTable() 161 index := tt.NearestIndex(time.Now().UTC()) 162 if index != 1 { 163 t.Fatalf("bad: %d", index) 164 } 165 166 // Verify the eval was unblocked. 167 testutil.WaitForResult(func() (bool, error) { 168 bStats := fsm.blockedEvals.Stats() 169 if bStats.TotalBlocked != 0 { 170 return false, fmt.Errorf("bad: %#v", bStats) 171 } 172 return true, nil 173 }, func(err error) { 174 t.Fatalf("err: %s", err) 175 }) 176 177 } 178 179 func TestFSM_UpsertNode_Canonicalize(t *testing.T) { 180 t.Parallel() 181 require := require.New(t) 182 183 fsm := testFSM(t) 184 fsm.blockedEvals.SetEnabled(true) 185 186 // Setup a node without eligiblity 187 node := mock.Node() 188 node.SchedulingEligibility = "" 189 190 req := structs.NodeRegisterRequest{ 191 Node: node, 192 } 193 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 194 require.Nil(err) 195 196 resp := fsm.Apply(makeLog(buf)) 197 require.Nil(resp) 198 199 // Verify we are registered 200 ws := memdb.NewWatchSet() 201 n, err := fsm.State().NodeByID(ws, req.Node.ID) 202 require.Nil(err) 203 require.NotNil(n) 204 require.EqualValues(1, n.CreateIndex) 205 require.Equal(structs.NodeSchedulingEligible, n.SchedulingEligibility) 206 } 207 208 func TestFSM_DeregisterNode(t *testing.T) { 209 t.Parallel() 210 fsm := testFSM(t) 211 212 node := mock.Node() 213 req := structs.NodeRegisterRequest{ 214 Node: node, 215 } 216 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 217 if err != nil { 218 t.Fatalf("err: %v", err) 219 } 220 221 resp := fsm.Apply(makeLog(buf)) 222 if resp != nil { 223 t.Fatalf("resp: %v", resp) 224 } 225 226 req2 := structs.NodeDeregisterRequest{ 227 NodeID: node.ID, 228 } 229 buf, err = structs.Encode(structs.NodeDeregisterRequestType, req2) 230 if err != nil { 231 t.Fatalf("err: %v", err) 232 } 233 234 resp = fsm.Apply(makeLog(buf)) 235 if resp != nil { 236 t.Fatalf("resp: %v", resp) 237 } 238 239 // Verify we are NOT registered 240 ws := memdb.NewWatchSet() 241 node, err = fsm.State().NodeByID(ws, req.Node.ID) 242 if err != nil { 243 t.Fatalf("err: %v", err) 244 } 245 if node != nil { 246 t.Fatalf("node found!") 247 } 248 } 249 250 func TestFSM_UpdateNodeStatus(t *testing.T) { 251 t.Parallel() 252 fsm := testFSM(t) 253 fsm.blockedEvals.SetEnabled(true) 254 255 node := mock.Node() 256 req := structs.NodeRegisterRequest{ 257 Node: node, 258 } 259 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 260 if err != nil { 261 t.Fatalf("err: %v", err) 262 } 263 264 resp := fsm.Apply(makeLog(buf)) 265 if resp != nil { 266 t.Fatalf("resp: %v", resp) 267 } 268 269 // Mark an eval as blocked. 270 eval := mock.Eval() 271 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 272 fsm.blockedEvals.Block(eval) 273 274 req2 := structs.NodeUpdateStatusRequest{ 275 NodeID: node.ID, 276 Status: structs.NodeStatusReady, 277 } 278 buf, err = structs.Encode(structs.NodeUpdateStatusRequestType, req2) 279 if err != nil { 280 t.Fatalf("err: %v", err) 281 } 282 283 resp = fsm.Apply(makeLog(buf)) 284 if resp != nil { 285 t.Fatalf("resp: %v", resp) 286 } 287 288 // Verify the status is ready. 289 ws := memdb.NewWatchSet() 290 node, err = fsm.State().NodeByID(ws, req.Node.ID) 291 if err != nil { 292 t.Fatalf("err: %v", err) 293 } 294 if node.Status != structs.NodeStatusReady { 295 t.Fatalf("bad node: %#v", node) 296 } 297 298 // Verify the eval was unblocked. 299 testutil.WaitForResult(func() (bool, error) { 300 bStats := fsm.blockedEvals.Stats() 301 if bStats.TotalBlocked != 0 { 302 return false, fmt.Errorf("bad: %#v", bStats) 303 } 304 return true, nil 305 }, func(err error) { 306 t.Fatalf("err: %s", err) 307 }) 308 } 309 310 func TestFSM_BatchUpdateNodeDrain(t *testing.T) { 311 t.Parallel() 312 require := require.New(t) 313 fsm := testFSM(t) 314 315 node := mock.Node() 316 req := structs.NodeRegisterRequest{ 317 Node: node, 318 } 319 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 320 require.Nil(err) 321 322 resp := fsm.Apply(makeLog(buf)) 323 require.Nil(resp) 324 325 strategy := &structs.DrainStrategy{ 326 DrainSpec: structs.DrainSpec{ 327 Deadline: 10 * time.Second, 328 }, 329 } 330 req2 := structs.BatchNodeUpdateDrainRequest{ 331 Updates: map[string]*structs.DrainUpdate{ 332 node.ID: { 333 DrainStrategy: strategy, 334 }, 335 }, 336 } 337 buf, err = structs.Encode(structs.BatchNodeUpdateDrainRequestType, req2) 338 require.Nil(err) 339 340 resp = fsm.Apply(makeLog(buf)) 341 require.Nil(resp) 342 343 // Verify drain is set 344 ws := memdb.NewWatchSet() 345 node, err = fsm.State().NodeByID(ws, req.Node.ID) 346 require.Nil(err) 347 require.True(node.Drain) 348 require.Equal(node.DrainStrategy, strategy) 349 } 350 351 func TestFSM_UpdateNodeDrain(t *testing.T) { 352 t.Parallel() 353 require := require.New(t) 354 fsm := testFSM(t) 355 356 node := mock.Node() 357 req := structs.NodeRegisterRequest{ 358 Node: node, 359 } 360 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 361 require.Nil(err) 362 363 resp := fsm.Apply(makeLog(buf)) 364 require.Nil(resp) 365 366 strategy := &structs.DrainStrategy{ 367 DrainSpec: structs.DrainSpec{ 368 Deadline: 10 * time.Second, 369 }, 370 } 371 req2 := structs.NodeUpdateDrainRequest{ 372 NodeID: node.ID, 373 DrainStrategy: strategy, 374 } 375 buf, err = structs.Encode(structs.NodeUpdateDrainRequestType, req2) 376 require.Nil(err) 377 378 resp = fsm.Apply(makeLog(buf)) 379 require.Nil(resp) 380 381 // Verify we are NOT registered 382 ws := memdb.NewWatchSet() 383 node, err = fsm.State().NodeByID(ws, req.Node.ID) 384 require.Nil(err) 385 require.True(node.Drain) 386 require.Equal(node.DrainStrategy, strategy) 387 } 388 389 func TestFSM_UpdateNodeDrain_Pre08_Compatibility(t *testing.T) { 390 t.Parallel() 391 require := require.New(t) 392 fsm := testFSM(t) 393 394 // Force a node into the state store without eligiblity 395 node := mock.Node() 396 node.SchedulingEligibility = "" 397 require.Nil(fsm.State().UpsertNode(1, node)) 398 399 // Do an old style drain 400 req := structs.NodeUpdateDrainRequest{ 401 NodeID: node.ID, 402 Drain: true, 403 } 404 buf, err := structs.Encode(structs.NodeUpdateDrainRequestType, req) 405 require.Nil(err) 406 407 resp := fsm.Apply(makeLog(buf)) 408 require.Nil(resp) 409 410 // Verify we have upgraded to a force drain 411 ws := memdb.NewWatchSet() 412 node, err = fsm.State().NodeByID(ws, req.NodeID) 413 require.Nil(err) 414 require.True(node.Drain) 415 416 expected := &structs.DrainStrategy{ 417 DrainSpec: structs.DrainSpec{ 418 Deadline: -1 * time.Second, 419 }, 420 } 421 require.Equal(expected, node.DrainStrategy) 422 } 423 424 func TestFSM_UpdateNodeEligibility(t *testing.T) { 425 t.Parallel() 426 require := require.New(t) 427 fsm := testFSM(t) 428 429 node := mock.Node() 430 req := structs.NodeRegisterRequest{ 431 Node: node, 432 } 433 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 434 require.Nil(err) 435 436 resp := fsm.Apply(makeLog(buf)) 437 require.Nil(resp) 438 439 // Set the eligibility 440 req2 := structs.NodeUpdateEligibilityRequest{ 441 NodeID: node.ID, 442 Eligibility: structs.NodeSchedulingIneligible, 443 } 444 buf, err = structs.Encode(structs.NodeUpdateEligibilityRequestType, req2) 445 require.Nil(err) 446 447 resp = fsm.Apply(makeLog(buf)) 448 require.Nil(resp) 449 450 // Lookup the node and check 451 node, err = fsm.State().NodeByID(nil, req.Node.ID) 452 require.Nil(err) 453 require.Equal(node.SchedulingEligibility, structs.NodeSchedulingIneligible) 454 455 // Update the drain 456 strategy := &structs.DrainStrategy{ 457 DrainSpec: structs.DrainSpec{ 458 Deadline: 10 * time.Second, 459 }, 460 } 461 req3 := structs.NodeUpdateDrainRequest{ 462 NodeID: node.ID, 463 DrainStrategy: strategy, 464 } 465 buf, err = structs.Encode(structs.NodeUpdateDrainRequestType, req3) 466 require.Nil(err) 467 resp = fsm.Apply(makeLog(buf)) 468 require.Nil(resp) 469 470 // Try forcing eligibility 471 req4 := structs.NodeUpdateEligibilityRequest{ 472 NodeID: node.ID, 473 Eligibility: structs.NodeSchedulingEligible, 474 } 475 buf, err = structs.Encode(structs.NodeUpdateEligibilityRequestType, req4) 476 require.Nil(err) 477 478 resp = fsm.Apply(makeLog(buf)) 479 require.NotNil(resp) 480 err, ok := resp.(error) 481 require.True(ok) 482 require.Contains(err.Error(), "draining") 483 } 484 485 func TestFSM_UpdateNodeEligibility_Unblock(t *testing.T) { 486 t.Parallel() 487 require := require.New(t) 488 fsm := testFSM(t) 489 490 node := mock.Node() 491 req := structs.NodeRegisterRequest{ 492 Node: node, 493 } 494 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 495 require.Nil(err) 496 497 resp := fsm.Apply(makeLog(buf)) 498 require.Nil(resp) 499 500 // Set the eligibility 501 req2 := structs.NodeUpdateEligibilityRequest{ 502 NodeID: node.ID, 503 Eligibility: structs.NodeSchedulingIneligible, 504 } 505 buf, err = structs.Encode(structs.NodeUpdateEligibilityRequestType, req2) 506 require.Nil(err) 507 508 resp = fsm.Apply(makeLog(buf)) 509 require.Nil(resp) 510 511 // Mark an eval as blocked. 512 eval := mock.Eval() 513 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 514 fsm.blockedEvals.Block(eval) 515 516 // Set eligible 517 req4 := structs.NodeUpdateEligibilityRequest{ 518 NodeID: node.ID, 519 Eligibility: structs.NodeSchedulingEligible, 520 } 521 buf, err = structs.Encode(structs.NodeUpdateEligibilityRequestType, req4) 522 require.Nil(err) 523 524 resp = fsm.Apply(makeLog(buf)) 525 require.Nil(resp) 526 527 // Verify the eval was unblocked. 528 testutil.WaitForResult(func() (bool, error) { 529 bStats := fsm.blockedEvals.Stats() 530 if bStats.TotalBlocked != 0 { 531 return false, fmt.Errorf("bad: %#v", bStats) 532 } 533 return true, nil 534 }, func(err error) { 535 t.Fatalf("err: %s", err) 536 }) 537 } 538 539 func TestFSM_RegisterJob(t *testing.T) { 540 t.Parallel() 541 fsm := testFSM(t) 542 543 job := mock.PeriodicJob() 544 req := structs.JobRegisterRequest{ 545 Job: job, 546 WriteRequest: structs.WriteRequest{ 547 Namespace: job.Namespace, 548 }, 549 } 550 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 551 if err != nil { 552 t.Fatalf("err: %v", err) 553 } 554 555 resp := fsm.Apply(makeLog(buf)) 556 if resp != nil { 557 t.Fatalf("resp: %v", resp) 558 } 559 560 // Verify we are registered 561 ws := memdb.NewWatchSet() 562 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 563 if err != nil { 564 t.Fatalf("err: %v", err) 565 } 566 if jobOut == nil { 567 t.Fatalf("not found!") 568 } 569 if jobOut.CreateIndex != 1 { 570 t.Fatalf("bad index: %d", jobOut.CreateIndex) 571 } 572 573 // Verify it was added to the periodic runner. 574 tuple := structs.NamespacedID{ 575 ID: job.ID, 576 Namespace: job.Namespace, 577 } 578 if _, ok := fsm.periodicDispatcher.tracked[tuple]; !ok { 579 t.Fatal("job not added to periodic runner") 580 } 581 582 // Verify the launch time was tracked. 583 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID) 584 if err != nil { 585 t.Fatalf("err: %v", err) 586 } 587 if launchOut == nil { 588 t.Fatalf("not found!") 589 } 590 if launchOut.Launch.IsZero() { 591 t.Fatalf("bad launch time: %v", launchOut.Launch) 592 } 593 } 594 595 func TestFSM_RegisterPeriodicJob_NonLeader(t *testing.T) { 596 t.Parallel() 597 fsm := testFSM(t) 598 599 // Disable the dispatcher 600 fsm.periodicDispatcher.SetEnabled(false) 601 602 job := mock.PeriodicJob() 603 req := structs.JobRegisterRequest{ 604 Job: job, 605 WriteRequest: structs.WriteRequest{ 606 Namespace: job.Namespace, 607 }, 608 } 609 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 610 if err != nil { 611 t.Fatalf("err: %v", err) 612 } 613 614 resp := fsm.Apply(makeLog(buf)) 615 if resp != nil { 616 t.Fatalf("resp: %v", resp) 617 } 618 619 // Verify we are registered 620 ws := memdb.NewWatchSet() 621 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 622 if err != nil { 623 t.Fatalf("err: %v", err) 624 } 625 if jobOut == nil { 626 t.Fatalf("not found!") 627 } 628 if jobOut.CreateIndex != 1 { 629 t.Fatalf("bad index: %d", jobOut.CreateIndex) 630 } 631 632 // Verify it wasn't added to the periodic runner. 633 tuple := structs.NamespacedID{ 634 ID: job.ID, 635 Namespace: job.Namespace, 636 } 637 if _, ok := fsm.periodicDispatcher.tracked[tuple]; ok { 638 t.Fatal("job added to periodic runner") 639 } 640 641 // Verify the launch time was tracked. 642 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID) 643 if err != nil { 644 t.Fatalf("err: %v", err) 645 } 646 if launchOut == nil { 647 t.Fatalf("not found!") 648 } 649 if launchOut.Launch.IsZero() { 650 t.Fatalf("bad launch time: %v", launchOut.Launch) 651 } 652 } 653 654 func TestFSM_RegisterJob_BadNamespace(t *testing.T) { 655 t.Parallel() 656 fsm := testFSM(t) 657 658 job := mock.Job() 659 job.Namespace = "foo" 660 req := structs.JobRegisterRequest{ 661 Job: job, 662 WriteRequest: structs.WriteRequest{ 663 Namespace: job.Namespace, 664 }, 665 } 666 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 667 if err != nil { 668 t.Fatalf("err: %v", err) 669 } 670 671 resp := fsm.Apply(makeLog(buf)) 672 if resp == nil { 673 t.Fatalf("no resp: %v", resp) 674 } 675 err, ok := resp.(error) 676 if !ok { 677 t.Fatalf("resp not of error type: %T %v", resp, resp) 678 } 679 if !strings.Contains(err.Error(), "nonexistent namespace") { 680 t.Fatalf("bad error: %v", err) 681 } 682 683 // Verify we are not registered 684 ws := memdb.NewWatchSet() 685 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 686 if err != nil { 687 t.Fatalf("err: %v", err) 688 } 689 if jobOut != nil { 690 t.Fatalf("job found!") 691 } 692 } 693 694 func TestFSM_DeregisterJob_Purge(t *testing.T) { 695 t.Parallel() 696 fsm := testFSM(t) 697 698 job := mock.PeriodicJob() 699 req := structs.JobRegisterRequest{ 700 Job: job, 701 WriteRequest: structs.WriteRequest{ 702 Namespace: job.Namespace, 703 }, 704 } 705 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 706 if err != nil { 707 t.Fatalf("err: %v", err) 708 } 709 710 resp := fsm.Apply(makeLog(buf)) 711 if resp != nil { 712 t.Fatalf("resp: %v", resp) 713 } 714 715 req2 := structs.JobDeregisterRequest{ 716 JobID: job.ID, 717 Purge: true, 718 WriteRequest: structs.WriteRequest{ 719 Namespace: job.Namespace, 720 }, 721 } 722 buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) 723 if err != nil { 724 t.Fatalf("err: %v", err) 725 } 726 727 resp = fsm.Apply(makeLog(buf)) 728 if resp != nil { 729 t.Fatalf("resp: %v", resp) 730 } 731 732 // Verify we are NOT registered 733 ws := memdb.NewWatchSet() 734 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 735 if err != nil { 736 t.Fatalf("err: %v", err) 737 } 738 if jobOut != nil { 739 t.Fatalf("job found!") 740 } 741 742 // Verify it was removed from the periodic runner. 743 tuple := structs.NamespacedID{ 744 ID: job.ID, 745 Namespace: job.Namespace, 746 } 747 if _, ok := fsm.periodicDispatcher.tracked[tuple]; ok { 748 t.Fatal("job not removed from periodic runner") 749 } 750 751 // Verify it was removed from the periodic launch table. 752 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID) 753 if err != nil { 754 t.Fatalf("err: %v", err) 755 } 756 if launchOut != nil { 757 t.Fatalf("launch found!") 758 } 759 } 760 761 func TestFSM_DeregisterJob_NoPurge(t *testing.T) { 762 t.Parallel() 763 fsm := testFSM(t) 764 765 job := mock.PeriodicJob() 766 req := structs.JobRegisterRequest{ 767 Job: job, 768 WriteRequest: structs.WriteRequest{ 769 Namespace: job.Namespace, 770 }, 771 } 772 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 773 if err != nil { 774 t.Fatalf("err: %v", err) 775 } 776 777 resp := fsm.Apply(makeLog(buf)) 778 if resp != nil { 779 t.Fatalf("resp: %v", resp) 780 } 781 782 req2 := structs.JobDeregisterRequest{ 783 JobID: job.ID, 784 Purge: false, 785 WriteRequest: structs.WriteRequest{ 786 Namespace: job.Namespace, 787 }, 788 } 789 buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) 790 if err != nil { 791 t.Fatalf("err: %v", err) 792 } 793 794 resp = fsm.Apply(makeLog(buf)) 795 if resp != nil { 796 t.Fatalf("resp: %v", resp) 797 } 798 799 // Verify we are NOT registered 800 ws := memdb.NewWatchSet() 801 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 802 if err != nil { 803 t.Fatalf("err: %v", err) 804 } 805 if jobOut == nil { 806 t.Fatalf("job not found!") 807 } 808 if !jobOut.Stop { 809 t.Fatalf("job not stopped found!") 810 } 811 812 // Verify it was removed from the periodic runner. 813 tuple := structs.NamespacedID{ 814 ID: job.ID, 815 Namespace: job.Namespace, 816 } 817 if _, ok := fsm.periodicDispatcher.tracked[tuple]; ok { 818 t.Fatal("job not removed from periodic runner") 819 } 820 821 // Verify it was removed from the periodic launch table. 822 launchOut, err := fsm.State().PeriodicLaunchByID(ws, req.Namespace, req.Job.ID) 823 if err != nil { 824 t.Fatalf("err: %v", err) 825 } 826 if launchOut == nil { 827 t.Fatalf("launch not found!") 828 } 829 } 830 831 func TestFSM_BatchDeregisterJob(t *testing.T) { 832 t.Parallel() 833 require := require.New(t) 834 fsm := testFSM(t) 835 836 job := mock.PeriodicJob() 837 req := structs.JobRegisterRequest{ 838 Job: job, 839 WriteRequest: structs.WriteRequest{ 840 Namespace: job.Namespace, 841 }, 842 } 843 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 844 require.Nil(err) 845 resp := fsm.Apply(makeLog(buf)) 846 require.Nil(resp) 847 848 job2 := mock.Job() 849 req2 := structs.JobRegisterRequest{ 850 Job: job2, 851 WriteRequest: structs.WriteRequest{ 852 Namespace: job2.Namespace, 853 }, 854 } 855 856 buf, err = structs.Encode(structs.JobRegisterRequestType, req2) 857 require.Nil(err) 858 resp = fsm.Apply(makeLog(buf)) 859 require.Nil(resp) 860 861 req3 := structs.JobBatchDeregisterRequest{ 862 Jobs: map[structs.NamespacedID]*structs.JobDeregisterOptions{ 863 { 864 ID: job.ID, 865 Namespace: job.Namespace, 866 }: {}, 867 { 868 ID: job2.ID, 869 Namespace: job2.Namespace, 870 }: { 871 Purge: true, 872 }, 873 }, 874 WriteRequest: structs.WriteRequest{ 875 Namespace: job.Namespace, 876 }, 877 } 878 buf, err = structs.Encode(structs.JobBatchDeregisterRequestType, req3) 879 require.Nil(err) 880 881 resp = fsm.Apply(makeLog(buf)) 882 require.Nil(resp) 883 884 // Verify we are NOT registered 885 ws := memdb.NewWatchSet() 886 jobOut, err := fsm.State().JobByID(ws, req.Namespace, req.Job.ID) 887 require.Nil(err) 888 require.NotNil(jobOut) 889 require.True(jobOut.Stop) 890 891 // Verify it was removed from the periodic runner. 892 tuple := structs.NamespacedID{ 893 ID: job.ID, 894 Namespace: job.Namespace, 895 } 896 require.NotContains(fsm.periodicDispatcher.tracked, tuple) 897 898 // Verify it was not removed from the periodic launch table. 899 launchOut, err := fsm.State().PeriodicLaunchByID(ws, job.Namespace, job.ID) 900 require.Nil(err) 901 require.NotNil(launchOut) 902 903 // Verify the other jbo was purged 904 jobOut2, err := fsm.State().JobByID(ws, job2.Namespace, job2.ID) 905 require.Nil(err) 906 require.Nil(jobOut2) 907 } 908 909 func TestFSM_UpdateEval(t *testing.T) { 910 t.Parallel() 911 fsm := testFSM(t) 912 fsm.evalBroker.SetEnabled(true) 913 914 req := structs.EvalUpdateRequest{ 915 Evals: []*structs.Evaluation{mock.Eval()}, 916 } 917 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 918 if err != nil { 919 t.Fatalf("err: %v", err) 920 } 921 922 resp := fsm.Apply(makeLog(buf)) 923 if resp != nil { 924 t.Fatalf("resp: %v", resp) 925 } 926 927 // Verify we are registered 928 ws := memdb.NewWatchSet() 929 eval, err := fsm.State().EvalByID(ws, req.Evals[0].ID) 930 if err != nil { 931 t.Fatalf("err: %v", err) 932 } 933 if eval == nil { 934 t.Fatalf("not found!") 935 } 936 if eval.CreateIndex != 1 { 937 t.Fatalf("bad index: %d", eval.CreateIndex) 938 } 939 940 // Verify enqueued 941 stats := fsm.evalBroker.Stats() 942 if stats.TotalReady != 1 { 943 t.Fatalf("bad: %#v %#v", stats, eval) 944 } 945 } 946 947 func TestFSM_UpdateEval_Blocked(t *testing.T) { 948 t.Parallel() 949 fsm := testFSM(t) 950 fsm.evalBroker.SetEnabled(true) 951 fsm.blockedEvals.SetEnabled(true) 952 953 // Create a blocked eval. 954 eval := mock.Eval() 955 eval.Status = structs.EvalStatusBlocked 956 957 req := structs.EvalUpdateRequest{ 958 Evals: []*structs.Evaluation{eval}, 959 } 960 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 961 if err != nil { 962 t.Fatalf("err: %v", err) 963 } 964 965 resp := fsm.Apply(makeLog(buf)) 966 if resp != nil { 967 t.Fatalf("resp: %v", resp) 968 } 969 970 // Verify we are registered 971 ws := memdb.NewWatchSet() 972 out, err := fsm.State().EvalByID(ws, eval.ID) 973 if err != nil { 974 t.Fatalf("err: %v", err) 975 } 976 if out == nil { 977 t.Fatalf("not found!") 978 } 979 if out.CreateIndex != 1 { 980 t.Fatalf("bad index: %d", out.CreateIndex) 981 } 982 983 // Verify the eval wasn't enqueued 984 stats := fsm.evalBroker.Stats() 985 if stats.TotalReady != 0 { 986 t.Fatalf("bad: %#v %#v", stats, out) 987 } 988 989 // Verify the eval was added to the blocked tracker. 990 bStats := fsm.blockedEvals.Stats() 991 if bStats.TotalBlocked != 1 { 992 t.Fatalf("bad: %#v %#v", bStats, out) 993 } 994 } 995 996 func TestFSM_UpdateEval_Untrack(t *testing.T) { 997 t.Parallel() 998 fsm := testFSM(t) 999 fsm.evalBroker.SetEnabled(true) 1000 fsm.blockedEvals.SetEnabled(true) 1001 1002 // Mark an eval as blocked. 1003 bEval := mock.Eval() 1004 bEval.ClassEligibility = map[string]bool{"v1:123": true} 1005 fsm.blockedEvals.Block(bEval) 1006 1007 // Create a successful eval for the same job 1008 eval := mock.Eval() 1009 eval.JobID = bEval.JobID 1010 eval.Status = structs.EvalStatusComplete 1011 1012 req := structs.EvalUpdateRequest{ 1013 Evals: []*structs.Evaluation{eval}, 1014 } 1015 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 1016 if err != nil { 1017 t.Fatalf("err: %v", err) 1018 } 1019 1020 resp := fsm.Apply(makeLog(buf)) 1021 if resp != nil { 1022 t.Fatalf("resp: %v", resp) 1023 } 1024 1025 // Verify we are registered 1026 ws := memdb.NewWatchSet() 1027 out, err := fsm.State().EvalByID(ws, eval.ID) 1028 if err != nil { 1029 t.Fatalf("err: %v", err) 1030 } 1031 if out == nil { 1032 t.Fatalf("not found!") 1033 } 1034 if out.CreateIndex != 1 { 1035 t.Fatalf("bad index: %d", out.CreateIndex) 1036 } 1037 1038 // Verify the eval wasn't enqueued 1039 stats := fsm.evalBroker.Stats() 1040 if stats.TotalReady != 0 { 1041 t.Fatalf("bad: %#v %#v", stats, out) 1042 } 1043 1044 // Verify the eval was untracked in the blocked tracker. 1045 bStats := fsm.blockedEvals.Stats() 1046 if bStats.TotalBlocked != 0 { 1047 t.Fatalf("bad: %#v %#v", bStats, out) 1048 } 1049 } 1050 1051 func TestFSM_UpdateEval_NoUntrack(t *testing.T) { 1052 t.Parallel() 1053 fsm := testFSM(t) 1054 fsm.evalBroker.SetEnabled(true) 1055 fsm.blockedEvals.SetEnabled(true) 1056 1057 // Mark an eval as blocked. 1058 bEval := mock.Eval() 1059 bEval.ClassEligibility = map[string]bool{"v1:123": true} 1060 fsm.blockedEvals.Block(bEval) 1061 1062 // Create a successful eval for the same job but with placement failures 1063 eval := mock.Eval() 1064 eval.JobID = bEval.JobID 1065 eval.Status = structs.EvalStatusComplete 1066 eval.FailedTGAllocs = make(map[string]*structs.AllocMetric) 1067 eval.FailedTGAllocs["test"] = new(structs.AllocMetric) 1068 1069 req := structs.EvalUpdateRequest{ 1070 Evals: []*structs.Evaluation{eval}, 1071 } 1072 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 1073 if err != nil { 1074 t.Fatalf("err: %v", err) 1075 } 1076 1077 resp := fsm.Apply(makeLog(buf)) 1078 if resp != nil { 1079 t.Fatalf("resp: %v", resp) 1080 } 1081 1082 // Verify we are registered 1083 ws := memdb.NewWatchSet() 1084 out, err := fsm.State().EvalByID(ws, eval.ID) 1085 if err != nil { 1086 t.Fatalf("err: %v", err) 1087 } 1088 if out == nil { 1089 t.Fatalf("not found!") 1090 } 1091 if out.CreateIndex != 1 { 1092 t.Fatalf("bad index: %d", out.CreateIndex) 1093 } 1094 1095 // Verify the eval wasn't enqueued 1096 stats := fsm.evalBroker.Stats() 1097 if stats.TotalReady != 0 { 1098 t.Fatalf("bad: %#v %#v", stats, out) 1099 } 1100 1101 // Verify the eval was not untracked in the blocked tracker. 1102 bStats := fsm.blockedEvals.Stats() 1103 if bStats.TotalBlocked != 1 { 1104 t.Fatalf("bad: %#v %#v", bStats, out) 1105 } 1106 } 1107 1108 func TestFSM_DeleteEval(t *testing.T) { 1109 t.Parallel() 1110 fsm := testFSM(t) 1111 1112 eval := mock.Eval() 1113 req := structs.EvalUpdateRequest{ 1114 Evals: []*structs.Evaluation{eval}, 1115 } 1116 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 1117 if err != nil { 1118 t.Fatalf("err: %v", err) 1119 } 1120 1121 resp := fsm.Apply(makeLog(buf)) 1122 if resp != nil { 1123 t.Fatalf("resp: %v", resp) 1124 } 1125 1126 req2 := structs.EvalDeleteRequest{ 1127 Evals: []string{eval.ID}, 1128 } 1129 buf, err = structs.Encode(structs.EvalDeleteRequestType, req2) 1130 if err != nil { 1131 t.Fatalf("err: %v", err) 1132 } 1133 1134 resp = fsm.Apply(makeLog(buf)) 1135 if resp != nil { 1136 t.Fatalf("resp: %v", resp) 1137 } 1138 1139 // Verify we are NOT registered 1140 ws := memdb.NewWatchSet() 1141 eval, err = fsm.State().EvalByID(ws, req.Evals[0].ID) 1142 if err != nil { 1143 t.Fatalf("err: %v", err) 1144 } 1145 if eval != nil { 1146 t.Fatalf("eval found!") 1147 } 1148 } 1149 1150 func TestFSM_UpsertAllocs(t *testing.T) { 1151 t.Parallel() 1152 fsm := testFSM(t) 1153 1154 alloc := mock.Alloc() 1155 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 1156 req := structs.AllocUpdateRequest{ 1157 Alloc: []*structs.Allocation{alloc}, 1158 } 1159 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 1160 if err != nil { 1161 t.Fatalf("err: %v", err) 1162 } 1163 1164 resp := fsm.Apply(makeLog(buf)) 1165 if resp != nil { 1166 t.Fatalf("resp: %v", resp) 1167 } 1168 1169 // Verify we are registered 1170 ws := memdb.NewWatchSet() 1171 out, err := fsm.State().AllocByID(ws, alloc.ID) 1172 if err != nil { 1173 t.Fatalf("err: %v", err) 1174 } 1175 alloc.CreateIndex = out.CreateIndex 1176 alloc.ModifyIndex = out.ModifyIndex 1177 alloc.AllocModifyIndex = out.AllocModifyIndex 1178 if !reflect.DeepEqual(alloc, out) { 1179 t.Fatalf("bad: %#v %#v", alloc, out) 1180 } 1181 1182 evictAlloc := new(structs.Allocation) 1183 *evictAlloc = *alloc 1184 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 1185 req2 := structs.AllocUpdateRequest{ 1186 Alloc: []*structs.Allocation{evictAlloc}, 1187 } 1188 buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) 1189 if err != nil { 1190 t.Fatalf("err: %v", err) 1191 } 1192 1193 resp = fsm.Apply(makeLog(buf)) 1194 if resp != nil { 1195 t.Fatalf("resp: %v", resp) 1196 } 1197 1198 // Verify we are evicted 1199 out, err = fsm.State().AllocByID(ws, alloc.ID) 1200 if err != nil { 1201 t.Fatalf("err: %v", err) 1202 } 1203 if out.DesiredStatus != structs.AllocDesiredStatusEvict { 1204 t.Fatalf("alloc found!") 1205 } 1206 } 1207 1208 func TestFSM_UpsertAllocs_SharedJob(t *testing.T) { 1209 t.Parallel() 1210 fsm := testFSM(t) 1211 1212 alloc := mock.Alloc() 1213 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 1214 job := alloc.Job 1215 alloc.Job = nil 1216 req := structs.AllocUpdateRequest{ 1217 Job: job, 1218 Alloc: []*structs.Allocation{alloc}, 1219 } 1220 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 1221 if err != nil { 1222 t.Fatalf("err: %v", err) 1223 } 1224 1225 resp := fsm.Apply(makeLog(buf)) 1226 if resp != nil { 1227 t.Fatalf("resp: %v", resp) 1228 } 1229 1230 // Verify we are registered 1231 ws := memdb.NewWatchSet() 1232 out, err := fsm.State().AllocByID(ws, alloc.ID) 1233 if err != nil { 1234 t.Fatalf("err: %v", err) 1235 } 1236 alloc.CreateIndex = out.CreateIndex 1237 alloc.ModifyIndex = out.ModifyIndex 1238 alloc.AllocModifyIndex = out.AllocModifyIndex 1239 1240 // Job should be re-attached 1241 alloc.Job = job 1242 if !reflect.DeepEqual(alloc, out) { 1243 t.Fatalf("bad: %#v %#v", alloc, out) 1244 } 1245 1246 // Ensure that the original job is used 1247 evictAlloc := new(structs.Allocation) 1248 *evictAlloc = *alloc 1249 job = mock.Job() 1250 job.Priority = 123 1251 1252 evictAlloc.Job = nil 1253 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 1254 req2 := structs.AllocUpdateRequest{ 1255 Job: job, 1256 Alloc: []*structs.Allocation{evictAlloc}, 1257 } 1258 buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) 1259 if err != nil { 1260 t.Fatalf("err: %v", err) 1261 } 1262 1263 resp = fsm.Apply(makeLog(buf)) 1264 if resp != nil { 1265 t.Fatalf("resp: %v", resp) 1266 } 1267 1268 // Verify we are evicted 1269 out, err = fsm.State().AllocByID(ws, alloc.ID) 1270 if err != nil { 1271 t.Fatalf("err: %v", err) 1272 } 1273 if out.DesiredStatus != structs.AllocDesiredStatusEvict { 1274 t.Fatalf("alloc found!") 1275 } 1276 if out.Job == nil || out.Job.Priority == 123 { 1277 t.Fatalf("bad job") 1278 } 1279 } 1280 1281 func TestFSM_UpsertAllocs_StrippedResources(t *testing.T) { 1282 t.Parallel() 1283 fsm := testFSM(t) 1284 1285 alloc := mock.Alloc() 1286 1287 // Need to remove mock dynamic port from alloc as it won't be computed 1288 // in this test 1289 alloc.TaskResources["web"].Networks[0].DynamicPorts[0].Value = 0 1290 1291 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 1292 job := alloc.Job 1293 origResources := alloc.Resources 1294 alloc.Resources = nil 1295 req := structs.AllocUpdateRequest{ 1296 Job: job, 1297 Alloc: []*structs.Allocation{alloc}, 1298 } 1299 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 1300 if err != nil { 1301 t.Fatalf("err: %v", err) 1302 } 1303 1304 resp := fsm.Apply(makeLog(buf)) 1305 if resp != nil { 1306 t.Fatalf("resp: %v", resp) 1307 } 1308 1309 // Verify we are registered 1310 ws := memdb.NewWatchSet() 1311 out, err := fsm.State().AllocByID(ws, alloc.ID) 1312 if err != nil { 1313 t.Fatalf("err: %v", err) 1314 } 1315 alloc.CreateIndex = out.CreateIndex 1316 alloc.ModifyIndex = out.ModifyIndex 1317 alloc.AllocModifyIndex = out.AllocModifyIndex 1318 1319 // Resources should be recomputed 1320 origResources.DiskMB = alloc.Job.TaskGroups[0].EphemeralDisk.SizeMB 1321 alloc.Resources = origResources 1322 if !reflect.DeepEqual(alloc, out) { 1323 t.Fatalf("not equal: % #v", pretty.Diff(alloc, out)) 1324 } 1325 } 1326 1327 func TestFSM_UpdateAllocFromClient_Unblock(t *testing.T) { 1328 t.Parallel() 1329 fsm := testFSM(t) 1330 fsm.blockedEvals.SetEnabled(true) 1331 state := fsm.State() 1332 1333 node := mock.Node() 1334 state.UpsertNode(1, node) 1335 1336 // Mark an eval as blocked. 1337 eval := mock.Eval() 1338 eval.ClassEligibility = map[string]bool{node.ComputedClass: true} 1339 fsm.blockedEvals.Block(eval) 1340 1341 bStats := fsm.blockedEvals.Stats() 1342 if bStats.TotalBlocked != 1 { 1343 t.Fatalf("bad: %#v", bStats) 1344 } 1345 1346 // Create a completed eval 1347 alloc := mock.Alloc() 1348 alloc.NodeID = node.ID 1349 alloc2 := mock.Alloc() 1350 alloc2.NodeID = node.ID 1351 state.UpsertJobSummary(8, mock.JobSummary(alloc.JobID)) 1352 state.UpsertJobSummary(9, mock.JobSummary(alloc2.JobID)) 1353 state.UpsertAllocs(10, []*structs.Allocation{alloc, alloc2}) 1354 1355 clientAlloc := new(structs.Allocation) 1356 *clientAlloc = *alloc 1357 clientAlloc.ClientStatus = structs.AllocClientStatusComplete 1358 update2 := &structs.Allocation{ 1359 ID: alloc2.ID, 1360 ClientStatus: structs.AllocClientStatusRunning, 1361 } 1362 1363 req := structs.AllocUpdateRequest{ 1364 Alloc: []*structs.Allocation{clientAlloc, update2}, 1365 } 1366 buf, err := structs.Encode(structs.AllocClientUpdateRequestType, req) 1367 if err != nil { 1368 t.Fatalf("err: %v", err) 1369 } 1370 1371 resp := fsm.Apply(makeLog(buf)) 1372 if resp != nil { 1373 t.Fatalf("resp: %v", resp) 1374 } 1375 1376 // Verify we are updated 1377 ws := memdb.NewWatchSet() 1378 out, err := fsm.State().AllocByID(ws, alloc.ID) 1379 if err != nil { 1380 t.Fatalf("err: %v", err) 1381 } 1382 clientAlloc.CreateIndex = out.CreateIndex 1383 clientAlloc.ModifyIndex = out.ModifyIndex 1384 if !reflect.DeepEqual(clientAlloc, out) { 1385 t.Fatalf("bad: %#v %#v", clientAlloc, out) 1386 } 1387 1388 out, err = fsm.State().AllocByID(ws, alloc2.ID) 1389 if err != nil { 1390 t.Fatalf("err: %v", err) 1391 } 1392 alloc2.CreateIndex = out.CreateIndex 1393 alloc2.ModifyIndex = out.ModifyIndex 1394 alloc2.ClientStatus = structs.AllocClientStatusRunning 1395 alloc2.TaskStates = nil 1396 if !reflect.DeepEqual(alloc2, out) { 1397 t.Fatalf("bad: %#v %#v", alloc2, out) 1398 } 1399 1400 // Verify the eval was unblocked. 1401 testutil.WaitForResult(func() (bool, error) { 1402 bStats = fsm.blockedEvals.Stats() 1403 if bStats.TotalBlocked != 0 { 1404 return false, fmt.Errorf("bad: %#v %#v", bStats, out) 1405 } 1406 return true, nil 1407 }, func(err error) { 1408 t.Fatalf("err: %s", err) 1409 }) 1410 } 1411 1412 func TestFSM_UpdateAllocFromClient(t *testing.T) { 1413 t.Parallel() 1414 fsm := testFSM(t) 1415 state := fsm.State() 1416 require := require.New(t) 1417 1418 alloc := mock.Alloc() 1419 state.UpsertJobSummary(9, mock.JobSummary(alloc.JobID)) 1420 state.UpsertAllocs(10, []*structs.Allocation{alloc}) 1421 1422 clientAlloc := new(structs.Allocation) 1423 *clientAlloc = *alloc 1424 clientAlloc.ClientStatus = structs.AllocClientStatusFailed 1425 1426 eval := mock.Eval() 1427 eval.JobID = alloc.JobID 1428 eval.TriggeredBy = structs.EvalTriggerRetryFailedAlloc 1429 eval.Type = alloc.Job.Type 1430 1431 req := structs.AllocUpdateRequest{ 1432 Alloc: []*structs.Allocation{clientAlloc}, 1433 Evals: []*structs.Evaluation{eval}, 1434 } 1435 buf, err := structs.Encode(structs.AllocClientUpdateRequestType, req) 1436 require.Nil(err) 1437 1438 resp := fsm.Apply(makeLog(buf)) 1439 require.Nil(resp) 1440 1441 // Verify we are registered 1442 ws := memdb.NewWatchSet() 1443 out, err := fsm.State().AllocByID(ws, alloc.ID) 1444 require.Nil(err) 1445 clientAlloc.CreateIndex = out.CreateIndex 1446 clientAlloc.ModifyIndex = out.ModifyIndex 1447 require.Equal(clientAlloc, out) 1448 1449 // Verify eval was inserted 1450 ws = memdb.NewWatchSet() 1451 evals, err := fsm.State().EvalsByJob(ws, eval.Namespace, eval.JobID) 1452 require.Nil(err) 1453 require.Equal(1, len(evals)) 1454 res := evals[0] 1455 eval.CreateIndex = res.CreateIndex 1456 eval.ModifyIndex = res.ModifyIndex 1457 require.Equal(eval, res) 1458 } 1459 1460 func TestFSM_UpdateAllocDesiredTransition(t *testing.T) { 1461 t.Parallel() 1462 fsm := testFSM(t) 1463 state := fsm.State() 1464 require := require.New(t) 1465 1466 alloc := mock.Alloc() 1467 alloc2 := mock.Alloc() 1468 alloc2.Job = alloc.Job 1469 alloc2.JobID = alloc.JobID 1470 state.UpsertJobSummary(9, mock.JobSummary(alloc.JobID)) 1471 state.UpsertAllocs(10, []*structs.Allocation{alloc, alloc2}) 1472 1473 t1 := &structs.DesiredTransition{ 1474 Migrate: helper.BoolToPtr(true), 1475 } 1476 1477 eval := &structs.Evaluation{ 1478 ID: uuid.Generate(), 1479 Namespace: alloc.Namespace, 1480 Priority: alloc.Job.Priority, 1481 Type: alloc.Job.Type, 1482 TriggeredBy: structs.EvalTriggerNodeDrain, 1483 JobID: alloc.Job.ID, 1484 JobModifyIndex: alloc.Job.ModifyIndex, 1485 Status: structs.EvalStatusPending, 1486 } 1487 req := structs.AllocUpdateDesiredTransitionRequest{ 1488 Allocs: map[string]*structs.DesiredTransition{ 1489 alloc.ID: t1, 1490 alloc2.ID: t1, 1491 }, 1492 Evals: []*structs.Evaluation{eval}, 1493 } 1494 buf, err := structs.Encode(structs.AllocUpdateDesiredTransitionRequestType, req) 1495 require.Nil(err) 1496 1497 resp := fsm.Apply(makeLog(buf)) 1498 require.Nil(resp) 1499 1500 // Verify we are registered 1501 ws := memdb.NewWatchSet() 1502 out1, err := fsm.State().AllocByID(ws, alloc.ID) 1503 require.Nil(err) 1504 out2, err := fsm.State().AllocByID(ws, alloc2.ID) 1505 require.Nil(err) 1506 evalOut, err := fsm.State().EvalByID(ws, eval.ID) 1507 require.Nil(err) 1508 require.NotNil(evalOut) 1509 require.Equal(eval.ID, evalOut.ID) 1510 1511 require.NotNil(out1.DesiredTransition.Migrate) 1512 require.NotNil(out2.DesiredTransition.Migrate) 1513 require.True(*out1.DesiredTransition.Migrate) 1514 require.True(*out2.DesiredTransition.Migrate) 1515 } 1516 1517 func TestFSM_UpsertVaultAccessor(t *testing.T) { 1518 t.Parallel() 1519 fsm := testFSM(t) 1520 fsm.blockedEvals.SetEnabled(true) 1521 1522 va := mock.VaultAccessor() 1523 va2 := mock.VaultAccessor() 1524 req := structs.VaultAccessorsRequest{ 1525 Accessors: []*structs.VaultAccessor{va, va2}, 1526 } 1527 buf, err := structs.Encode(structs.VaultAccessorRegisterRequestType, req) 1528 if err != nil { 1529 t.Fatalf("err: %v", err) 1530 } 1531 1532 resp := fsm.Apply(makeLog(buf)) 1533 if resp != nil { 1534 t.Fatalf("resp: %v", resp) 1535 } 1536 1537 // Verify we are registered 1538 ws := memdb.NewWatchSet() 1539 out1, err := fsm.State().VaultAccessor(ws, va.Accessor) 1540 if err != nil { 1541 t.Fatalf("err: %v", err) 1542 } 1543 if out1 == nil { 1544 t.Fatalf("not found!") 1545 } 1546 if out1.CreateIndex != 1 { 1547 t.Fatalf("bad index: %d", out1.CreateIndex) 1548 } 1549 out2, err := fsm.State().VaultAccessor(ws, va2.Accessor) 1550 if err != nil { 1551 t.Fatalf("err: %v", err) 1552 } 1553 if out2 == nil { 1554 t.Fatalf("not found!") 1555 } 1556 if out1.CreateIndex != 1 { 1557 t.Fatalf("bad index: %d", out2.CreateIndex) 1558 } 1559 1560 tt := fsm.TimeTable() 1561 index := tt.NearestIndex(time.Now().UTC()) 1562 if index != 1 { 1563 t.Fatalf("bad: %d", index) 1564 } 1565 } 1566 1567 func TestFSM_DeregisterVaultAccessor(t *testing.T) { 1568 t.Parallel() 1569 fsm := testFSM(t) 1570 fsm.blockedEvals.SetEnabled(true) 1571 1572 va := mock.VaultAccessor() 1573 va2 := mock.VaultAccessor() 1574 accessors := []*structs.VaultAccessor{va, va2} 1575 1576 // Insert the accessors 1577 if err := fsm.State().UpsertVaultAccessor(1000, accessors); err != nil { 1578 t.Fatalf("bad: %v", err) 1579 } 1580 1581 req := structs.VaultAccessorsRequest{ 1582 Accessors: accessors, 1583 } 1584 buf, err := structs.Encode(structs.VaultAccessorDeregisterRequestType, req) 1585 if err != nil { 1586 t.Fatalf("err: %v", err) 1587 } 1588 1589 resp := fsm.Apply(makeLog(buf)) 1590 if resp != nil { 1591 t.Fatalf("resp: %v", resp) 1592 } 1593 1594 ws := memdb.NewWatchSet() 1595 out1, err := fsm.State().VaultAccessor(ws, va.Accessor) 1596 if err != nil { 1597 t.Fatalf("err: %v", err) 1598 } 1599 if out1 != nil { 1600 t.Fatalf("not deleted!") 1601 } 1602 1603 tt := fsm.TimeTable() 1604 index := tt.NearestIndex(time.Now().UTC()) 1605 if index != 1 { 1606 t.Fatalf("bad: %d", index) 1607 } 1608 } 1609 1610 func TestFSM_ApplyPlanResults(t *testing.T) { 1611 t.Parallel() 1612 fsm := testFSM(t) 1613 1614 // Create the request and create a deployment 1615 alloc := mock.Alloc() 1616 job := alloc.Job 1617 alloc.Job = nil 1618 1619 d := mock.Deployment() 1620 d.JobID = job.ID 1621 d.JobModifyIndex = job.ModifyIndex 1622 d.JobVersion = job.Version 1623 1624 alloc.DeploymentID = d.ID 1625 1626 eval := mock.Eval() 1627 eval.JobID = job.ID 1628 fsm.State().UpsertEvals(1, []*structs.Evaluation{eval}) 1629 1630 fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) 1631 req := structs.ApplyPlanResultsRequest{ 1632 AllocUpdateRequest: structs.AllocUpdateRequest{ 1633 Job: job, 1634 Alloc: []*structs.Allocation{alloc}, 1635 }, 1636 Deployment: d, 1637 EvalID: eval.ID, 1638 } 1639 buf, err := structs.Encode(structs.ApplyPlanResultsRequestType, req) 1640 if err != nil { 1641 t.Fatalf("err: %v", err) 1642 } 1643 1644 resp := fsm.Apply(makeLog(buf)) 1645 if resp != nil { 1646 t.Fatalf("resp: %v", resp) 1647 } 1648 1649 // Verify the allocation is registered 1650 ws := memdb.NewWatchSet() 1651 assert := assert.New(t) 1652 out, err := fsm.State().AllocByID(ws, alloc.ID) 1653 assert.Nil(err) 1654 alloc.CreateIndex = out.CreateIndex 1655 alloc.ModifyIndex = out.ModifyIndex 1656 alloc.AllocModifyIndex = out.AllocModifyIndex 1657 1658 // Job should be re-attached 1659 alloc.Job = job 1660 assert.Equal(alloc, out) 1661 1662 dout, err := fsm.State().DeploymentByID(ws, d.ID) 1663 assert.Nil(err) 1664 tg, ok := dout.TaskGroups[alloc.TaskGroup] 1665 assert.True(ok) 1666 assert.NotNil(tg) 1667 assert.Equal(1, tg.PlacedAllocs) 1668 1669 // Ensure that the original job is used 1670 evictAlloc := alloc.Copy() 1671 job = mock.Job() 1672 job.Priority = 123 1673 eval = mock.Eval() 1674 eval.JobID = job.ID 1675 1676 fsm.State().UpsertEvals(2, []*structs.Evaluation{eval}) 1677 1678 evictAlloc.Job = nil 1679 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 1680 req2 := structs.ApplyPlanResultsRequest{ 1681 AllocUpdateRequest: structs.AllocUpdateRequest{ 1682 Job: job, 1683 Alloc: []*structs.Allocation{evictAlloc}, 1684 }, 1685 EvalID: eval.ID, 1686 } 1687 buf, err = structs.Encode(structs.ApplyPlanResultsRequestType, req2) 1688 assert.Nil(err) 1689 1690 log := makeLog(buf) 1691 //set the index to something other than 1 1692 log.Index = 25 1693 resp = fsm.Apply(log) 1694 assert.Nil(resp) 1695 1696 // Verify we are evicted 1697 out, err = fsm.State().AllocByID(ws, alloc.ID) 1698 assert.Nil(err) 1699 assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus) 1700 assert.NotNil(out.Job) 1701 assert.NotEqual(123, out.Job.Priority) 1702 1703 evalOut, err := fsm.State().EvalByID(ws, eval.ID) 1704 assert.Nil(err) 1705 assert.Equal(log.Index, evalOut.ModifyIndex) 1706 1707 } 1708 1709 func TestFSM_DeploymentStatusUpdate(t *testing.T) { 1710 t.Parallel() 1711 fsm := testFSM(t) 1712 fsm.evalBroker.SetEnabled(true) 1713 state := fsm.State() 1714 1715 // Upsert a deployment 1716 d := mock.Deployment() 1717 if err := state.UpsertDeployment(1, d); err != nil { 1718 t.Fatalf("bad: %v", err) 1719 } 1720 1721 // Create a request to update the deployment, create an eval and job 1722 e := mock.Eval() 1723 j := mock.Job() 1724 status, desc := structs.DeploymentStatusFailed, "foo" 1725 req := &structs.DeploymentStatusUpdateRequest{ 1726 DeploymentUpdate: &structs.DeploymentStatusUpdate{ 1727 DeploymentID: d.ID, 1728 Status: status, 1729 StatusDescription: desc, 1730 }, 1731 Job: j, 1732 Eval: e, 1733 } 1734 buf, err := structs.Encode(structs.DeploymentStatusUpdateRequestType, req) 1735 if err != nil { 1736 t.Fatalf("err: %v", err) 1737 } 1738 resp := fsm.Apply(makeLog(buf)) 1739 if resp != nil { 1740 t.Fatalf("resp: %v", resp) 1741 } 1742 1743 // Check that the status was updated properly 1744 ws := memdb.NewWatchSet() 1745 dout, err := state.DeploymentByID(ws, d.ID) 1746 if err != nil { 1747 t.Fatalf("bad: %v", err) 1748 } 1749 if dout.Status != status || dout.StatusDescription != desc { 1750 t.Fatalf("bad: %#v", dout) 1751 } 1752 1753 // Check that the evaluation was created 1754 eout, _ := state.EvalByID(ws, e.ID) 1755 if err != nil { 1756 t.Fatalf("bad: %v", err) 1757 } 1758 if eout == nil { 1759 t.Fatalf("bad: %#v", eout) 1760 } 1761 1762 // Check that the job was created 1763 jout, _ := state.JobByID(ws, j.Namespace, j.ID) 1764 if err != nil { 1765 t.Fatalf("bad: %v", err) 1766 } 1767 if jout == nil { 1768 t.Fatalf("bad: %#v", jout) 1769 } 1770 1771 // Assert the eval was enqueued 1772 stats := fsm.evalBroker.Stats() 1773 if stats.TotalReady != 1 { 1774 t.Fatalf("bad: %#v %#v", stats, e) 1775 } 1776 } 1777 1778 func TestFSM_JobStabilityUpdate(t *testing.T) { 1779 t.Parallel() 1780 fsm := testFSM(t) 1781 fsm.evalBroker.SetEnabled(true) 1782 state := fsm.State() 1783 1784 // Upsert a deployment 1785 job := mock.Job() 1786 if err := state.UpsertJob(1, job); err != nil { 1787 t.Fatalf("bad: %v", err) 1788 } 1789 1790 // Create a request to update the job to stable 1791 req := &structs.JobStabilityRequest{ 1792 JobID: job.ID, 1793 JobVersion: job.Version, 1794 Stable: true, 1795 WriteRequest: structs.WriteRequest{ 1796 Namespace: job.Namespace, 1797 }, 1798 } 1799 buf, err := structs.Encode(structs.JobStabilityRequestType, req) 1800 if err != nil { 1801 t.Fatalf("err: %v", err) 1802 } 1803 resp := fsm.Apply(makeLog(buf)) 1804 if resp != nil { 1805 t.Fatalf("resp: %v", resp) 1806 } 1807 1808 // Check that the stability was updated properly 1809 ws := memdb.NewWatchSet() 1810 jout, _ := state.JobByIDAndVersion(ws, job.Namespace, job.ID, job.Version) 1811 if err != nil { 1812 t.Fatalf("bad: %v", err) 1813 } 1814 if jout == nil || !jout.Stable { 1815 t.Fatalf("bad: %#v", jout) 1816 } 1817 } 1818 1819 func TestFSM_DeploymentPromotion(t *testing.T) { 1820 t.Parallel() 1821 fsm := testFSM(t) 1822 fsm.evalBroker.SetEnabled(true) 1823 state := fsm.State() 1824 1825 // Create a job with two task groups 1826 j := mock.Job() 1827 tg1 := j.TaskGroups[0] 1828 tg2 := tg1.Copy() 1829 tg2.Name = "foo" 1830 j.TaskGroups = append(j.TaskGroups, tg2) 1831 if err := state.UpsertJob(1, j); err != nil { 1832 t.Fatalf("bad: %v", err) 1833 } 1834 1835 // Create a deployment 1836 d := mock.Deployment() 1837 d.JobID = j.ID 1838 d.TaskGroups = map[string]*structs.DeploymentState{ 1839 "web": { 1840 DesiredTotal: 10, 1841 DesiredCanaries: 1, 1842 }, 1843 "foo": { 1844 DesiredTotal: 10, 1845 DesiredCanaries: 1, 1846 }, 1847 } 1848 if err := state.UpsertDeployment(2, d); err != nil { 1849 t.Fatalf("bad: %v", err) 1850 } 1851 1852 // Create a set of allocations 1853 c1 := mock.Alloc() 1854 c1.JobID = j.ID 1855 c1.DeploymentID = d.ID 1856 d.TaskGroups[c1.TaskGroup].PlacedCanaries = append(d.TaskGroups[c1.TaskGroup].PlacedCanaries, c1.ID) 1857 c1.DeploymentStatus = &structs.AllocDeploymentStatus{ 1858 Healthy: helper.BoolToPtr(true), 1859 } 1860 c2 := mock.Alloc() 1861 c2.JobID = j.ID 1862 c2.DeploymentID = d.ID 1863 d.TaskGroups[c2.TaskGroup].PlacedCanaries = append(d.TaskGroups[c2.TaskGroup].PlacedCanaries, c2.ID) 1864 c2.TaskGroup = tg2.Name 1865 c2.DeploymentStatus = &structs.AllocDeploymentStatus{ 1866 Healthy: helper.BoolToPtr(true), 1867 } 1868 1869 if err := state.UpsertAllocs(3, []*structs.Allocation{c1, c2}); err != nil { 1870 t.Fatalf("err: %v", err) 1871 } 1872 1873 // Create an eval 1874 e := mock.Eval() 1875 1876 // Promote the canaries 1877 req := &structs.ApplyDeploymentPromoteRequest{ 1878 DeploymentPromoteRequest: structs.DeploymentPromoteRequest{ 1879 DeploymentID: d.ID, 1880 All: true, 1881 }, 1882 Eval: e, 1883 } 1884 buf, err := structs.Encode(structs.DeploymentPromoteRequestType, req) 1885 if err != nil { 1886 t.Fatalf("err: %v", err) 1887 } 1888 resp := fsm.Apply(makeLog(buf)) 1889 if resp != nil { 1890 t.Fatalf("resp: %v", resp) 1891 } 1892 1893 // Check that the status per task group was updated properly 1894 ws := memdb.NewWatchSet() 1895 dout, err := state.DeploymentByID(ws, d.ID) 1896 if err != nil { 1897 t.Fatalf("bad: %v", err) 1898 } 1899 if len(dout.TaskGroups) != 2 { 1900 t.Fatalf("bad: %#v", dout.TaskGroups) 1901 } 1902 for tg, state := range dout.TaskGroups { 1903 if !state.Promoted { 1904 t.Fatalf("bad: group %q not promoted %#v", tg, state) 1905 } 1906 } 1907 1908 // Check that the evaluation was created 1909 eout, _ := state.EvalByID(ws, e.ID) 1910 if err != nil { 1911 t.Fatalf("bad: %v", err) 1912 } 1913 if eout == nil { 1914 t.Fatalf("bad: %#v", eout) 1915 } 1916 1917 // Assert the eval was enqueued 1918 stats := fsm.evalBroker.Stats() 1919 if stats.TotalReady != 1 { 1920 t.Fatalf("bad: %#v %#v", stats, e) 1921 } 1922 } 1923 1924 func TestFSM_DeploymentAllocHealth(t *testing.T) { 1925 t.Parallel() 1926 fsm := testFSM(t) 1927 fsm.evalBroker.SetEnabled(true) 1928 state := fsm.State() 1929 1930 // Insert a deployment 1931 d := mock.Deployment() 1932 if err := state.UpsertDeployment(1, d); err != nil { 1933 t.Fatalf("bad: %v", err) 1934 } 1935 1936 // Insert two allocations 1937 a1 := mock.Alloc() 1938 a1.DeploymentID = d.ID 1939 a2 := mock.Alloc() 1940 a2.DeploymentID = d.ID 1941 if err := state.UpsertAllocs(2, []*structs.Allocation{a1, a2}); err != nil { 1942 t.Fatalf("bad: %v", err) 1943 } 1944 1945 // Create a job to roll back to 1946 j := mock.Job() 1947 1948 // Create an eval that should be upserted 1949 e := mock.Eval() 1950 1951 // Create a status update for the deployment 1952 status, desc := structs.DeploymentStatusFailed, "foo" 1953 u := &structs.DeploymentStatusUpdate{ 1954 DeploymentID: d.ID, 1955 Status: status, 1956 StatusDescription: desc, 1957 } 1958 1959 // Set health against the deployment 1960 req := &structs.ApplyDeploymentAllocHealthRequest{ 1961 DeploymentAllocHealthRequest: structs.DeploymentAllocHealthRequest{ 1962 DeploymentID: d.ID, 1963 HealthyAllocationIDs: []string{a1.ID}, 1964 UnhealthyAllocationIDs: []string{a2.ID}, 1965 }, 1966 Job: j, 1967 Eval: e, 1968 DeploymentUpdate: u, 1969 } 1970 buf, err := structs.Encode(structs.DeploymentAllocHealthRequestType, req) 1971 if err != nil { 1972 t.Fatalf("err: %v", err) 1973 } 1974 resp := fsm.Apply(makeLog(buf)) 1975 if resp != nil { 1976 t.Fatalf("resp: %v", resp) 1977 } 1978 1979 // Check that the status was updated properly 1980 ws := memdb.NewWatchSet() 1981 dout, err := state.DeploymentByID(ws, d.ID) 1982 if err != nil { 1983 t.Fatalf("bad: %v", err) 1984 } 1985 if dout.Status != status || dout.StatusDescription != desc { 1986 t.Fatalf("bad: %#v", dout) 1987 } 1988 1989 // Check that the evaluation was created 1990 eout, _ := state.EvalByID(ws, e.ID) 1991 if err != nil { 1992 t.Fatalf("bad: %v", err) 1993 } 1994 if eout == nil { 1995 t.Fatalf("bad: %#v", eout) 1996 } 1997 1998 // Check that the job was created 1999 jout, _ := state.JobByID(ws, j.Namespace, j.ID) 2000 if err != nil { 2001 t.Fatalf("bad: %v", err) 2002 } 2003 if jout == nil { 2004 t.Fatalf("bad: %#v", jout) 2005 } 2006 2007 // Check the status of the allocs 2008 out1, err := state.AllocByID(ws, a1.ID) 2009 if err != nil { 2010 t.Fatalf("err: %v", err) 2011 } 2012 out2, err := state.AllocByID(ws, a2.ID) 2013 if err != nil { 2014 t.Fatalf("err: %v", err) 2015 } 2016 2017 if !out1.DeploymentStatus.IsHealthy() { 2018 t.Fatalf("bad: alloc %q not healthy", out1.ID) 2019 } 2020 if !out2.DeploymentStatus.IsUnhealthy() { 2021 t.Fatalf("bad: alloc %q not unhealthy", out2.ID) 2022 } 2023 2024 // Assert the eval was enqueued 2025 stats := fsm.evalBroker.Stats() 2026 if stats.TotalReady != 1 { 2027 t.Fatalf("bad: %#v %#v", stats, e) 2028 } 2029 } 2030 2031 func TestFSM_DeleteDeployment(t *testing.T) { 2032 t.Parallel() 2033 fsm := testFSM(t) 2034 state := fsm.State() 2035 2036 // Upsert a deployments 2037 d := mock.Deployment() 2038 if err := state.UpsertDeployment(1, d); err != nil { 2039 t.Fatalf("bad: %v", err) 2040 } 2041 2042 req := structs.DeploymentDeleteRequest{ 2043 Deployments: []string{d.ID}, 2044 } 2045 buf, err := structs.Encode(structs.DeploymentDeleteRequestType, req) 2046 if err != nil { 2047 t.Fatalf("err: %v", err) 2048 } 2049 2050 resp := fsm.Apply(makeLog(buf)) 2051 if resp != nil { 2052 t.Fatalf("resp: %v", resp) 2053 } 2054 2055 // Verify we are NOT registered 2056 ws := memdb.NewWatchSet() 2057 deployment, err := state.DeploymentByID(ws, d.ID) 2058 if err != nil { 2059 t.Fatalf("err: %v", err) 2060 } 2061 if deployment != nil { 2062 t.Fatalf("deployment found!") 2063 } 2064 } 2065 2066 func TestFSM_UpsertACLPolicies(t *testing.T) { 2067 t.Parallel() 2068 fsm := testFSM(t) 2069 2070 policy := mock.ACLPolicy() 2071 req := structs.ACLPolicyUpsertRequest{ 2072 Policies: []*structs.ACLPolicy{policy}, 2073 } 2074 buf, err := structs.Encode(structs.ACLPolicyUpsertRequestType, req) 2075 if err != nil { 2076 t.Fatalf("err: %v", err) 2077 } 2078 2079 resp := fsm.Apply(makeLog(buf)) 2080 if resp != nil { 2081 t.Fatalf("resp: %v", resp) 2082 } 2083 2084 // Verify we are registered 2085 ws := memdb.NewWatchSet() 2086 out, err := fsm.State().ACLPolicyByName(ws, policy.Name) 2087 assert.Nil(t, err) 2088 assert.NotNil(t, out) 2089 } 2090 2091 func TestFSM_DeleteACLPolicies(t *testing.T) { 2092 t.Parallel() 2093 fsm := testFSM(t) 2094 2095 policy := mock.ACLPolicy() 2096 err := fsm.State().UpsertACLPolicies(1000, []*structs.ACLPolicy{policy}) 2097 assert.Nil(t, err) 2098 2099 req := structs.ACLPolicyDeleteRequest{ 2100 Names: []string{policy.Name}, 2101 } 2102 buf, err := structs.Encode(structs.ACLPolicyDeleteRequestType, req) 2103 if err != nil { 2104 t.Fatalf("err: %v", err) 2105 } 2106 2107 resp := fsm.Apply(makeLog(buf)) 2108 if resp != nil { 2109 t.Fatalf("resp: %v", resp) 2110 } 2111 2112 // Verify we are NOT registered 2113 ws := memdb.NewWatchSet() 2114 out, err := fsm.State().ACLPolicyByName(ws, policy.Name) 2115 assert.Nil(t, err) 2116 assert.Nil(t, out) 2117 } 2118 2119 func TestFSM_BootstrapACLTokens(t *testing.T) { 2120 t.Parallel() 2121 fsm := testFSM(t) 2122 2123 token := mock.ACLToken() 2124 req := structs.ACLTokenBootstrapRequest{ 2125 Token: token, 2126 } 2127 buf, err := structs.Encode(structs.ACLTokenBootstrapRequestType, req) 2128 if err != nil { 2129 t.Fatalf("err: %v", err) 2130 } 2131 2132 resp := fsm.Apply(makeLog(buf)) 2133 if resp != nil { 2134 t.Fatalf("resp: %v", resp) 2135 } 2136 2137 // Verify we are registered 2138 out, err := fsm.State().ACLTokenByAccessorID(nil, token.AccessorID) 2139 assert.Nil(t, err) 2140 assert.NotNil(t, out) 2141 2142 // Test with reset 2143 token2 := mock.ACLToken() 2144 req = structs.ACLTokenBootstrapRequest{ 2145 Token: token2, 2146 ResetIndex: out.CreateIndex, 2147 } 2148 buf, err = structs.Encode(structs.ACLTokenBootstrapRequestType, req) 2149 if err != nil { 2150 t.Fatalf("err: %v", err) 2151 } 2152 2153 resp = fsm.Apply(makeLog(buf)) 2154 if resp != nil { 2155 t.Fatalf("resp: %v", resp) 2156 } 2157 2158 // Verify we are registered 2159 out2, err := fsm.State().ACLTokenByAccessorID(nil, token2.AccessorID) 2160 assert.Nil(t, err) 2161 assert.NotNil(t, out2) 2162 } 2163 2164 func TestFSM_UpsertACLTokens(t *testing.T) { 2165 t.Parallel() 2166 fsm := testFSM(t) 2167 2168 token := mock.ACLToken() 2169 req := structs.ACLTokenUpsertRequest{ 2170 Tokens: []*structs.ACLToken{token}, 2171 } 2172 buf, err := structs.Encode(structs.ACLTokenUpsertRequestType, req) 2173 if err != nil { 2174 t.Fatalf("err: %v", err) 2175 } 2176 2177 resp := fsm.Apply(makeLog(buf)) 2178 if resp != nil { 2179 t.Fatalf("resp: %v", resp) 2180 } 2181 2182 // Verify we are registered 2183 ws := memdb.NewWatchSet() 2184 out, err := fsm.State().ACLTokenByAccessorID(ws, token.AccessorID) 2185 assert.Nil(t, err) 2186 assert.NotNil(t, out) 2187 } 2188 2189 func TestFSM_DeleteACLTokens(t *testing.T) { 2190 t.Parallel() 2191 fsm := testFSM(t) 2192 2193 token := mock.ACLToken() 2194 err := fsm.State().UpsertACLTokens(1000, []*structs.ACLToken{token}) 2195 assert.Nil(t, err) 2196 2197 req := structs.ACLTokenDeleteRequest{ 2198 AccessorIDs: []string{token.AccessorID}, 2199 } 2200 buf, err := structs.Encode(structs.ACLTokenDeleteRequestType, req) 2201 if err != nil { 2202 t.Fatalf("err: %v", err) 2203 } 2204 2205 resp := fsm.Apply(makeLog(buf)) 2206 if resp != nil { 2207 t.Fatalf("resp: %v", resp) 2208 } 2209 2210 // Verify we are NOT registered 2211 ws := memdb.NewWatchSet() 2212 out, err := fsm.State().ACLTokenByAccessorID(ws, token.AccessorID) 2213 assert.Nil(t, err) 2214 assert.Nil(t, out) 2215 } 2216 2217 func testSnapshotRestore(t *testing.T, fsm *nomadFSM) *nomadFSM { 2218 // Snapshot 2219 snap, err := fsm.Snapshot() 2220 if err != nil { 2221 t.Fatalf("err: %v", err) 2222 } 2223 defer snap.Release() 2224 2225 // Persist 2226 buf := bytes.NewBuffer(nil) 2227 sink := &MockSink{buf, false} 2228 if err := snap.Persist(sink); err != nil { 2229 t.Fatalf("err: %v", err) 2230 } 2231 2232 // Try to restore on a new FSM 2233 fsm2 := testFSM(t) 2234 snap, err = fsm2.Snapshot() 2235 if err != nil { 2236 t.Fatalf("err: %v", err) 2237 } 2238 defer snap.Release() 2239 2240 abandonCh := fsm2.State().AbandonCh() 2241 2242 // Do a restore 2243 if err := fsm2.Restore(sink); err != nil { 2244 t.Fatalf("err: %v", err) 2245 } 2246 2247 select { 2248 case <-abandonCh: 2249 default: 2250 t.Fatalf("bad") 2251 } 2252 2253 return fsm2 2254 } 2255 2256 func TestFSM_SnapshotRestore_Nodes(t *testing.T) { 2257 t.Parallel() 2258 // Add some state 2259 fsm := testFSM(t) 2260 state := fsm.State() 2261 node1 := mock.Node() 2262 state.UpsertNode(1000, node1) 2263 2264 // Upgrade this node 2265 node2 := mock.Node() 2266 node2.SchedulingEligibility = "" 2267 state.UpsertNode(1001, node2) 2268 2269 // Verify the contents 2270 fsm2 := testSnapshotRestore(t, fsm) 2271 state2 := fsm2.State() 2272 out1, _ := state2.NodeByID(nil, node1.ID) 2273 out2, _ := state2.NodeByID(nil, node2.ID) 2274 node2.SchedulingEligibility = structs.NodeSchedulingEligible 2275 if !reflect.DeepEqual(node1, out1) { 2276 t.Fatalf("bad: \n%#v\n%#v", out1, node1) 2277 } 2278 if !reflect.DeepEqual(node2, out2) { 2279 t.Fatalf("bad: \n%#v\n%#v", out2, node2) 2280 } 2281 } 2282 2283 func TestFSM_SnapshotRestore_Jobs(t *testing.T) { 2284 t.Parallel() 2285 // Add some state 2286 fsm := testFSM(t) 2287 state := fsm.State() 2288 job1 := mock.Job() 2289 state.UpsertJob(1000, job1) 2290 job2 := mock.Job() 2291 state.UpsertJob(1001, job2) 2292 2293 // Verify the contents 2294 ws := memdb.NewWatchSet() 2295 fsm2 := testSnapshotRestore(t, fsm) 2296 state2 := fsm2.State() 2297 out1, _ := state2.JobByID(ws, job1.Namespace, job1.ID) 2298 out2, _ := state2.JobByID(ws, job2.Namespace, job2.ID) 2299 if !reflect.DeepEqual(job1, out1) { 2300 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 2301 } 2302 if !reflect.DeepEqual(job2, out2) { 2303 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 2304 } 2305 } 2306 2307 func TestFSM_SnapshotRestore_Evals(t *testing.T) { 2308 t.Parallel() 2309 // Add some state 2310 fsm := testFSM(t) 2311 state := fsm.State() 2312 eval1 := mock.Eval() 2313 state.UpsertEvals(1000, []*structs.Evaluation{eval1}) 2314 eval2 := mock.Eval() 2315 state.UpsertEvals(1001, []*structs.Evaluation{eval2}) 2316 2317 // Verify the contents 2318 fsm2 := testSnapshotRestore(t, fsm) 2319 state2 := fsm2.State() 2320 ws := memdb.NewWatchSet() 2321 out1, _ := state2.EvalByID(ws, eval1.ID) 2322 out2, _ := state2.EvalByID(ws, eval2.ID) 2323 if !reflect.DeepEqual(eval1, out1) { 2324 t.Fatalf("bad: \n%#v\n%#v", out1, eval1) 2325 } 2326 if !reflect.DeepEqual(eval2, out2) { 2327 t.Fatalf("bad: \n%#v\n%#v", out2, eval2) 2328 } 2329 } 2330 2331 func TestFSM_SnapshotRestore_Allocs(t *testing.T) { 2332 t.Parallel() 2333 // Add some state 2334 fsm := testFSM(t) 2335 state := fsm.State() 2336 alloc1 := mock.Alloc() 2337 alloc2 := mock.Alloc() 2338 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 2339 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 2340 state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 2341 state.UpsertAllocs(1001, []*structs.Allocation{alloc2}) 2342 2343 // Verify the contents 2344 fsm2 := testSnapshotRestore(t, fsm) 2345 state2 := fsm2.State() 2346 ws := memdb.NewWatchSet() 2347 out1, _ := state2.AllocByID(ws, alloc1.ID) 2348 out2, _ := state2.AllocByID(ws, alloc2.ID) 2349 if !reflect.DeepEqual(alloc1, out1) { 2350 t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) 2351 } 2352 if !reflect.DeepEqual(alloc2, out2) { 2353 t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) 2354 } 2355 } 2356 2357 func TestFSM_SnapshotRestore_Allocs_NoSharedResources(t *testing.T) { 2358 t.Parallel() 2359 // Add some state 2360 fsm := testFSM(t) 2361 state := fsm.State() 2362 alloc1 := mock.Alloc() 2363 alloc2 := mock.Alloc() 2364 alloc1.SharedResources = nil 2365 alloc2.SharedResources = nil 2366 state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)) 2367 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 2368 state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 2369 state.UpsertAllocs(1001, []*structs.Allocation{alloc2}) 2370 2371 // Verify the contents 2372 fsm2 := testSnapshotRestore(t, fsm) 2373 state2 := fsm2.State() 2374 ws := memdb.NewWatchSet() 2375 out1, _ := state2.AllocByID(ws, alloc1.ID) 2376 out2, _ := state2.AllocByID(ws, alloc2.ID) 2377 alloc1.SharedResources = &structs.Resources{DiskMB: 150} 2378 alloc2.SharedResources = &structs.Resources{DiskMB: 150} 2379 2380 if !reflect.DeepEqual(alloc1, out1) { 2381 t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) 2382 } 2383 if !reflect.DeepEqual(alloc2, out2) { 2384 t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) 2385 } 2386 } 2387 2388 func TestFSM_SnapshotRestore_Indexes(t *testing.T) { 2389 t.Parallel() 2390 // Add some state 2391 fsm := testFSM(t) 2392 state := fsm.State() 2393 node1 := mock.Node() 2394 state.UpsertNode(1000, node1) 2395 2396 // Verify the contents 2397 fsm2 := testSnapshotRestore(t, fsm) 2398 state2 := fsm2.State() 2399 2400 index, err := state2.Index("nodes") 2401 if err != nil { 2402 t.Fatalf("err: %v", err) 2403 } 2404 if index != 1000 { 2405 t.Fatalf("bad: %d", index) 2406 } 2407 } 2408 2409 func TestFSM_SnapshotRestore_TimeTable(t *testing.T) { 2410 t.Parallel() 2411 // Add some state 2412 fsm := testFSM(t) 2413 2414 tt := fsm.TimeTable() 2415 start := time.Now().UTC() 2416 tt.Witness(1000, start) 2417 tt.Witness(2000, start.Add(10*time.Minute)) 2418 2419 // Verify the contents 2420 fsm2 := testSnapshotRestore(t, fsm) 2421 2422 tt2 := fsm2.TimeTable() 2423 if tt2.NearestTime(1500) != start { 2424 t.Fatalf("bad") 2425 } 2426 if tt2.NearestIndex(start.Add(15*time.Minute)) != 2000 { 2427 t.Fatalf("bad") 2428 } 2429 } 2430 2431 func TestFSM_SnapshotRestore_PeriodicLaunches(t *testing.T) { 2432 t.Parallel() 2433 // Add some state 2434 fsm := testFSM(t) 2435 state := fsm.State() 2436 job1 := mock.Job() 2437 launch1 := &structs.PeriodicLaunch{ 2438 ID: job1.ID, 2439 Namespace: job1.Namespace, 2440 Launch: time.Now(), 2441 } 2442 state.UpsertPeriodicLaunch(1000, launch1) 2443 job2 := mock.Job() 2444 launch2 := &structs.PeriodicLaunch{ 2445 ID: job2.ID, 2446 Namespace: job2.Namespace, 2447 Launch: time.Now(), 2448 } 2449 state.UpsertPeriodicLaunch(1001, launch2) 2450 2451 // Verify the contents 2452 fsm2 := testSnapshotRestore(t, fsm) 2453 state2 := fsm2.State() 2454 ws := memdb.NewWatchSet() 2455 out1, _ := state2.PeriodicLaunchByID(ws, launch1.Namespace, launch1.ID) 2456 out2, _ := state2.PeriodicLaunchByID(ws, launch2.Namespace, launch2.ID) 2457 2458 if !cmp.Equal(launch1, out1) { 2459 t.Fatalf("bad: %v", cmp.Diff(launch1, out1)) 2460 } 2461 if !cmp.Equal(launch2, out2) { 2462 t.Fatalf("bad: %v", cmp.Diff(launch2, out2)) 2463 } 2464 } 2465 2466 func TestFSM_SnapshotRestore_JobSummary(t *testing.T) { 2467 t.Parallel() 2468 // Add some state 2469 fsm := testFSM(t) 2470 state := fsm.State() 2471 2472 job1 := mock.Job() 2473 state.UpsertJob(1000, job1) 2474 ws := memdb.NewWatchSet() 2475 js1, _ := state.JobSummaryByID(ws, job1.Namespace, job1.ID) 2476 2477 job2 := mock.Job() 2478 state.UpsertJob(1001, job2) 2479 js2, _ := state.JobSummaryByID(ws, job2.Namespace, job2.ID) 2480 2481 // Verify the contents 2482 fsm2 := testSnapshotRestore(t, fsm) 2483 state2 := fsm2.State() 2484 out1, _ := state2.JobSummaryByID(ws, job1.Namespace, job1.ID) 2485 out2, _ := state2.JobSummaryByID(ws, job2.Namespace, job2.ID) 2486 if !reflect.DeepEqual(js1, out1) { 2487 t.Fatalf("bad: \n%#v\n%#v", js1, out1) 2488 } 2489 if !reflect.DeepEqual(js2, out2) { 2490 t.Fatalf("bad: \n%#v\n%#v", js2, out2) 2491 } 2492 } 2493 2494 func TestFSM_SnapshotRestore_VaultAccessors(t *testing.T) { 2495 t.Parallel() 2496 // Add some state 2497 fsm := testFSM(t) 2498 state := fsm.State() 2499 a1 := mock.VaultAccessor() 2500 a2 := mock.VaultAccessor() 2501 state.UpsertVaultAccessor(1000, []*structs.VaultAccessor{a1, a2}) 2502 2503 // Verify the contents 2504 fsm2 := testSnapshotRestore(t, fsm) 2505 state2 := fsm2.State() 2506 ws := memdb.NewWatchSet() 2507 out1, _ := state2.VaultAccessor(ws, a1.Accessor) 2508 out2, _ := state2.VaultAccessor(ws, a2.Accessor) 2509 if !reflect.DeepEqual(a1, out1) { 2510 t.Fatalf("bad: \n%#v\n%#v", out1, a1) 2511 } 2512 if !reflect.DeepEqual(a2, out2) { 2513 t.Fatalf("bad: \n%#v\n%#v", out2, a2) 2514 } 2515 } 2516 2517 func TestFSM_SnapshotRestore_JobVersions(t *testing.T) { 2518 t.Parallel() 2519 // Add some state 2520 fsm := testFSM(t) 2521 state := fsm.State() 2522 job1 := mock.Job() 2523 state.UpsertJob(1000, job1) 2524 job2 := mock.Job() 2525 job2.ID = job1.ID 2526 state.UpsertJob(1001, job2) 2527 2528 // Verify the contents 2529 ws := memdb.NewWatchSet() 2530 fsm2 := testSnapshotRestore(t, fsm) 2531 state2 := fsm2.State() 2532 out1, _ := state2.JobByIDAndVersion(ws, job1.Namespace, job1.ID, job1.Version) 2533 out2, _ := state2.JobByIDAndVersion(ws, job2.Namespace, job2.ID, job2.Version) 2534 if !reflect.DeepEqual(job1, out1) { 2535 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 2536 } 2537 if !reflect.DeepEqual(job2, out2) { 2538 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 2539 } 2540 if job2.Version != 1 { 2541 t.Fatalf("bad: \n%#v\n%#v", 1, job2) 2542 } 2543 } 2544 2545 func TestFSM_SnapshotRestore_Deployments(t *testing.T) { 2546 t.Parallel() 2547 // Add some state 2548 fsm := testFSM(t) 2549 state := fsm.State() 2550 d1 := mock.Deployment() 2551 d2 := mock.Deployment() 2552 state.UpsertDeployment(1000, d1) 2553 state.UpsertDeployment(1001, d2) 2554 2555 // Verify the contents 2556 fsm2 := testSnapshotRestore(t, fsm) 2557 state2 := fsm2.State() 2558 ws := memdb.NewWatchSet() 2559 out1, _ := state2.DeploymentByID(ws, d1.ID) 2560 out2, _ := state2.DeploymentByID(ws, d2.ID) 2561 if !reflect.DeepEqual(d1, out1) { 2562 t.Fatalf("bad: \n%#v\n%#v", out1, d1) 2563 } 2564 if !reflect.DeepEqual(d2, out2) { 2565 t.Fatalf("bad: \n%#v\n%#v", out2, d2) 2566 } 2567 } 2568 2569 func TestFSM_SnapshotRestore_ACLPolicy(t *testing.T) { 2570 t.Parallel() 2571 // Add some state 2572 fsm := testFSM(t) 2573 state := fsm.State() 2574 p1 := mock.ACLPolicy() 2575 p2 := mock.ACLPolicy() 2576 state.UpsertACLPolicies(1000, []*structs.ACLPolicy{p1, p2}) 2577 2578 // Verify the contents 2579 fsm2 := testSnapshotRestore(t, fsm) 2580 state2 := fsm2.State() 2581 ws := memdb.NewWatchSet() 2582 out1, _ := state2.ACLPolicyByName(ws, p1.Name) 2583 out2, _ := state2.ACLPolicyByName(ws, p2.Name) 2584 assert.Equal(t, p1, out1) 2585 assert.Equal(t, p2, out2) 2586 } 2587 2588 func TestFSM_SnapshotRestore_ACLTokens(t *testing.T) { 2589 t.Parallel() 2590 // Add some state 2591 fsm := testFSM(t) 2592 state := fsm.State() 2593 tk1 := mock.ACLToken() 2594 tk2 := mock.ACLToken() 2595 state.UpsertACLTokens(1000, []*structs.ACLToken{tk1, tk2}) 2596 2597 // Verify the contents 2598 fsm2 := testSnapshotRestore(t, fsm) 2599 state2 := fsm2.State() 2600 ws := memdb.NewWatchSet() 2601 out1, _ := state2.ACLTokenByAccessorID(ws, tk1.AccessorID) 2602 out2, _ := state2.ACLTokenByAccessorID(ws, tk2.AccessorID) 2603 assert.Equal(t, tk1, out1) 2604 assert.Equal(t, tk2, out2) 2605 } 2606 2607 func TestFSM_SnapshotRestore_AddMissingSummary(t *testing.T) { 2608 t.Parallel() 2609 // Add some state 2610 fsm := testFSM(t) 2611 state := fsm.State() 2612 2613 // make an allocation 2614 alloc := mock.Alloc() 2615 state.UpsertJob(1010, alloc.Job) 2616 state.UpsertAllocs(1011, []*structs.Allocation{alloc}) 2617 2618 // Delete the summary 2619 state.DeleteJobSummary(1040, alloc.Namespace, alloc.Job.ID) 2620 2621 // Delete the index 2622 if err := state.RemoveIndex("job_summary"); err != nil { 2623 t.Fatalf("err: %v", err) 2624 } 2625 2626 fsm2 := testSnapshotRestore(t, fsm) 2627 state2 := fsm2.State() 2628 latestIndex, _ := state.LatestIndex() 2629 2630 ws := memdb.NewWatchSet() 2631 out, _ := state2.JobSummaryByID(ws, alloc.Namespace, alloc.Job.ID) 2632 expected := structs.JobSummary{ 2633 JobID: alloc.Job.ID, 2634 Namespace: alloc.Job.Namespace, 2635 Summary: map[string]structs.TaskGroupSummary{ 2636 "web": { 2637 Starting: 1, 2638 }, 2639 }, 2640 CreateIndex: 1010, 2641 ModifyIndex: latestIndex, 2642 } 2643 if !reflect.DeepEqual(&expected, out) { 2644 t.Fatalf("expected: %#v, actual: %#v", &expected, out) 2645 } 2646 } 2647 2648 func TestFSM_ReconcileSummaries(t *testing.T) { 2649 t.Parallel() 2650 // Add some state 2651 fsm := testFSM(t) 2652 state := fsm.State() 2653 2654 // Add a node 2655 node := mock.Node() 2656 state.UpsertNode(800, node) 2657 2658 // Make a job so that none of the tasks can be placed 2659 job1 := mock.Job() 2660 job1.TaskGroups[0].Tasks[0].Resources.CPU = 5000 2661 state.UpsertJob(1000, job1) 2662 2663 // make a job which can make partial progress 2664 alloc := mock.Alloc() 2665 alloc.NodeID = node.ID 2666 state.UpsertJob(1010, alloc.Job) 2667 state.UpsertAllocs(1011, []*structs.Allocation{alloc}) 2668 2669 // Delete the summaries 2670 state.DeleteJobSummary(1030, job1.Namespace, job1.ID) 2671 state.DeleteJobSummary(1040, alloc.Namespace, alloc.Job.ID) 2672 2673 req := structs.GenericRequest{} 2674 buf, err := structs.Encode(structs.ReconcileJobSummariesRequestType, req) 2675 if err != nil { 2676 t.Fatalf("err: %v", err) 2677 } 2678 2679 resp := fsm.Apply(makeLog(buf)) 2680 if resp != nil { 2681 t.Fatalf("resp: %v", resp) 2682 } 2683 2684 ws := memdb.NewWatchSet() 2685 out1, _ := state.JobSummaryByID(ws, job1.Namespace, job1.ID) 2686 expected := structs.JobSummary{ 2687 JobID: job1.ID, 2688 Namespace: job1.Namespace, 2689 Summary: map[string]structs.TaskGroupSummary{ 2690 "web": { 2691 Queued: 10, 2692 }, 2693 }, 2694 CreateIndex: 1000, 2695 ModifyIndex: out1.ModifyIndex, 2696 } 2697 if !reflect.DeepEqual(&expected, out1) { 2698 t.Fatalf("expected: %#v, actual: %#v", &expected, out1) 2699 } 2700 2701 // This exercises the code path which adds the allocations made by the 2702 // planner and the number of unplaced allocations in the reconcile summaries 2703 // codepath 2704 out2, _ := state.JobSummaryByID(ws, alloc.Namespace, alloc.Job.ID) 2705 expected = structs.JobSummary{ 2706 JobID: alloc.Job.ID, 2707 Namespace: alloc.Job.Namespace, 2708 Summary: map[string]structs.TaskGroupSummary{ 2709 "web": { 2710 Queued: 9, 2711 Starting: 1, 2712 }, 2713 }, 2714 CreateIndex: 1010, 2715 ModifyIndex: out2.ModifyIndex, 2716 } 2717 if !reflect.DeepEqual(&expected, out2) { 2718 t.Fatalf("Diff % #v", pretty.Diff(&expected, out2)) 2719 } 2720 } 2721 2722 func TestFSM_Autopilot(t *testing.T) { 2723 t.Parallel() 2724 fsm := testFSM(t) 2725 2726 // Set the autopilot config using a request. 2727 req := structs.AutopilotSetConfigRequest{ 2728 Datacenter: "dc1", 2729 Config: structs.AutopilotConfig{ 2730 CleanupDeadServers: true, 2731 LastContactThreshold: 10 * time.Second, 2732 MaxTrailingLogs: 300, 2733 }, 2734 } 2735 buf, err := structs.Encode(structs.AutopilotRequestType, req) 2736 if err != nil { 2737 t.Fatalf("err: %v", err) 2738 } 2739 resp := fsm.Apply(makeLog(buf)) 2740 if _, ok := resp.(error); ok { 2741 t.Fatalf("bad: %v", resp) 2742 } 2743 2744 // Verify key is set directly in the state store. 2745 _, config, err := fsm.state.AutopilotConfig() 2746 if err != nil { 2747 t.Fatalf("err: %v", err) 2748 } 2749 if config.CleanupDeadServers != req.Config.CleanupDeadServers { 2750 t.Fatalf("bad: %v", config.CleanupDeadServers) 2751 } 2752 if config.LastContactThreshold != req.Config.LastContactThreshold { 2753 t.Fatalf("bad: %v", config.LastContactThreshold) 2754 } 2755 if config.MaxTrailingLogs != req.Config.MaxTrailingLogs { 2756 t.Fatalf("bad: %v", config.MaxTrailingLogs) 2757 } 2758 2759 // Now use CAS and provide an old index 2760 req.CAS = true 2761 req.Config.CleanupDeadServers = false 2762 req.Config.ModifyIndex = config.ModifyIndex - 1 2763 buf, err = structs.Encode(structs.AutopilotRequestType, req) 2764 if err != nil { 2765 t.Fatalf("err: %v", err) 2766 } 2767 resp = fsm.Apply(makeLog(buf)) 2768 if _, ok := resp.(error); ok { 2769 t.Fatalf("bad: %v", resp) 2770 } 2771 2772 _, config, err = fsm.state.AutopilotConfig() 2773 if err != nil { 2774 t.Fatalf("err: %v", err) 2775 } 2776 if !config.CleanupDeadServers { 2777 t.Fatalf("bad: %v", config.CleanupDeadServers) 2778 } 2779 }