github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/fsm_test.go (about) 1 package nomad 2 3 import ( 4 "bytes" 5 "os" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/state" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/raft" 14 ) 15 16 type MockSink struct { 17 *bytes.Buffer 18 cancel bool 19 } 20 21 func (m *MockSink) ID() string { 22 return "Mock" 23 } 24 25 func (m *MockSink) Cancel() error { 26 m.cancel = true 27 return nil 28 } 29 30 func (m *MockSink) Close() error { 31 return nil 32 } 33 34 func testStateStore(t *testing.T) *state.StateStore { 35 state, err := state.NewStateStore(os.Stderr) 36 if err != nil { 37 t.Fatalf("err: %v", err) 38 } 39 if state == nil { 40 t.Fatalf("missing state") 41 } 42 return state 43 } 44 45 func testFSM(t *testing.T) *nomadFSM { 46 p, _ := testPeriodicDispatcher() 47 fsm, err := NewFSM(testBroker(t, 0), p, os.Stderr) 48 if err != nil { 49 t.Fatalf("err: %v", err) 50 } 51 if fsm == nil { 52 t.Fatalf("missing fsm") 53 } 54 return fsm 55 } 56 57 func makeLog(buf []byte) *raft.Log { 58 return &raft.Log{ 59 Index: 1, 60 Term: 1, 61 Type: raft.LogCommand, 62 Data: buf, 63 } 64 } 65 66 func TestFSM_UpsertNode(t *testing.T) { 67 fsm := testFSM(t) 68 69 req := structs.NodeRegisterRequest{ 70 Node: mock.Node(), 71 } 72 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 73 if err != nil { 74 t.Fatalf("err: %v", err) 75 } 76 77 resp := fsm.Apply(makeLog(buf)) 78 if resp != nil { 79 t.Fatalf("resp: %v", resp) 80 } 81 82 // Verify we are registered 83 node, err := fsm.State().NodeByID(req.Node.ID) 84 if err != nil { 85 t.Fatalf("err: %v", err) 86 } 87 if node == nil { 88 t.Fatalf("not found!") 89 } 90 if node.CreateIndex != 1 { 91 t.Fatalf("bad index: %d", node.CreateIndex) 92 } 93 94 tt := fsm.TimeTable() 95 index := tt.NearestIndex(time.Now().UTC()) 96 if index != 1 { 97 t.Fatalf("bad: %d", index) 98 } 99 } 100 101 func TestFSM_DeregisterNode(t *testing.T) { 102 fsm := testFSM(t) 103 104 node := mock.Node() 105 req := structs.NodeRegisterRequest{ 106 Node: node, 107 } 108 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 109 if err != nil { 110 t.Fatalf("err: %v", err) 111 } 112 113 resp := fsm.Apply(makeLog(buf)) 114 if resp != nil { 115 t.Fatalf("resp: %v", resp) 116 } 117 118 req2 := structs.NodeDeregisterRequest{ 119 NodeID: node.ID, 120 } 121 buf, err = structs.Encode(structs.NodeDeregisterRequestType, req2) 122 if err != nil { 123 t.Fatalf("err: %v", err) 124 } 125 126 resp = fsm.Apply(makeLog(buf)) 127 if resp != nil { 128 t.Fatalf("resp: %v", resp) 129 } 130 131 // Verify we are NOT registered 132 node, err = fsm.State().NodeByID(req.Node.ID) 133 if err != nil { 134 t.Fatalf("err: %v", err) 135 } 136 if node != nil { 137 t.Fatalf("node found!") 138 } 139 } 140 141 func TestFSM_UpdateNodeStatus(t *testing.T) { 142 fsm := testFSM(t) 143 144 node := mock.Node() 145 req := structs.NodeRegisterRequest{ 146 Node: node, 147 } 148 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 153 resp := fsm.Apply(makeLog(buf)) 154 if resp != nil { 155 t.Fatalf("resp: %v", resp) 156 } 157 158 req2 := structs.NodeUpdateStatusRequest{ 159 NodeID: node.ID, 160 Status: structs.NodeStatusReady, 161 } 162 buf, err = structs.Encode(structs.NodeUpdateStatusRequestType, req2) 163 if err != nil { 164 t.Fatalf("err: %v", err) 165 } 166 167 resp = fsm.Apply(makeLog(buf)) 168 if resp != nil { 169 t.Fatalf("resp: %v", resp) 170 } 171 172 // Verify we are NOT registered 173 node, err = fsm.State().NodeByID(req.Node.ID) 174 if err != nil { 175 t.Fatalf("err: %v", err) 176 } 177 if node.Status != structs.NodeStatusReady { 178 t.Fatalf("bad node: %#v", node) 179 } 180 } 181 182 func TestFSM_UpdateNodeDrain(t *testing.T) { 183 fsm := testFSM(t) 184 185 node := mock.Node() 186 req := structs.NodeRegisterRequest{ 187 Node: node, 188 } 189 buf, err := structs.Encode(structs.NodeRegisterRequestType, req) 190 if err != nil { 191 t.Fatalf("err: %v", err) 192 } 193 194 resp := fsm.Apply(makeLog(buf)) 195 if resp != nil { 196 t.Fatalf("resp: %v", resp) 197 } 198 199 req2 := structs.NodeUpdateDrainRequest{ 200 NodeID: node.ID, 201 Drain: true, 202 } 203 buf, err = structs.Encode(structs.NodeUpdateDrainRequestType, req2) 204 if err != nil { 205 t.Fatalf("err: %v", err) 206 } 207 208 resp = fsm.Apply(makeLog(buf)) 209 if resp != nil { 210 t.Fatalf("resp: %v", resp) 211 } 212 213 // Verify we are NOT registered 214 node, err = fsm.State().NodeByID(req.Node.ID) 215 if err != nil { 216 t.Fatalf("err: %v", err) 217 } 218 if !node.Drain { 219 t.Fatalf("bad node: %#v", node) 220 } 221 } 222 223 func TestFSM_RegisterJob(t *testing.T) { 224 fsm := testFSM(t) 225 226 job := mock.PeriodicJob() 227 req := structs.JobRegisterRequest{ 228 Job: job, 229 } 230 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 231 if err != nil { 232 t.Fatalf("err: %v", err) 233 } 234 235 resp := fsm.Apply(makeLog(buf)) 236 if resp != nil { 237 t.Fatalf("resp: %v", resp) 238 } 239 240 // Verify we are registered 241 jobOut, err := fsm.State().JobByID(req.Job.ID) 242 if err != nil { 243 t.Fatalf("err: %v", err) 244 } 245 if jobOut == nil { 246 t.Fatalf("not found!") 247 } 248 if jobOut.CreateIndex != 1 { 249 t.Fatalf("bad index: %d", jobOut.CreateIndex) 250 } 251 252 // Verify it was added to the periodic runner. 253 if _, ok := fsm.periodicDispatcher.tracked[job.ID]; !ok { 254 t.Fatal("job not added to periodic runner") 255 } 256 257 // Verify the launch time was tracked. 258 launchOut, err := fsm.State().PeriodicLaunchByID(req.Job.ID) 259 if err != nil { 260 t.Fatalf("err: %v", err) 261 } 262 if launchOut == nil { 263 t.Fatalf("not found!") 264 } 265 if launchOut.Launch.IsZero() { 266 t.Fatalf("bad launch time: %v", launchOut.Launch) 267 } 268 } 269 270 func TestFSM_DeregisterJob(t *testing.T) { 271 fsm := testFSM(t) 272 273 job := mock.PeriodicJob() 274 req := structs.JobRegisterRequest{ 275 Job: job, 276 } 277 buf, err := structs.Encode(structs.JobRegisterRequestType, req) 278 if err != nil { 279 t.Fatalf("err: %v", err) 280 } 281 282 resp := fsm.Apply(makeLog(buf)) 283 if resp != nil { 284 t.Fatalf("resp: %v", resp) 285 } 286 287 req2 := structs.JobDeregisterRequest{ 288 JobID: job.ID, 289 } 290 buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) 291 if err != nil { 292 t.Fatalf("err: %v", err) 293 } 294 295 resp = fsm.Apply(makeLog(buf)) 296 if resp != nil { 297 t.Fatalf("resp: %v", resp) 298 } 299 300 // Verify we are NOT registered 301 jobOut, err := fsm.State().JobByID(req.Job.ID) 302 if err != nil { 303 t.Fatalf("err: %v", err) 304 } 305 if jobOut != nil { 306 t.Fatalf("job found!") 307 } 308 309 // Verify it was removed from the periodic runner. 310 if _, ok := fsm.periodicDispatcher.tracked[job.ID]; ok { 311 t.Fatal("job not removed from periodic runner") 312 } 313 314 // Verify it was removed from the periodic launch table. 315 launchOut, err := fsm.State().PeriodicLaunchByID(req.Job.ID) 316 if err != nil { 317 t.Fatalf("err: %v", err) 318 } 319 if launchOut != nil { 320 t.Fatalf("launch found!") 321 } 322 } 323 324 func TestFSM_UpdateEval(t *testing.T) { 325 fsm := testFSM(t) 326 fsm.evalBroker.SetEnabled(true) 327 328 req := structs.EvalUpdateRequest{ 329 Evals: []*structs.Evaluation{mock.Eval()}, 330 } 331 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 332 if err != nil { 333 t.Fatalf("err: %v", err) 334 } 335 336 resp := fsm.Apply(makeLog(buf)) 337 if resp != nil { 338 t.Fatalf("resp: %v", resp) 339 } 340 341 // Verify we are registered 342 eval, err := fsm.State().EvalByID(req.Evals[0].ID) 343 if err != nil { 344 t.Fatalf("err: %v", err) 345 } 346 if eval == nil { 347 t.Fatalf("not found!") 348 } 349 if eval.CreateIndex != 1 { 350 t.Fatalf("bad index: %d", eval.CreateIndex) 351 } 352 353 // Verify enqueued 354 stats := fsm.evalBroker.Stats() 355 if stats.TotalReady != 1 { 356 t.Fatalf("bad: %#v %#v", stats, eval) 357 } 358 } 359 360 func TestFSM_DeleteEval(t *testing.T) { 361 fsm := testFSM(t) 362 363 eval := mock.Eval() 364 req := structs.EvalUpdateRequest{ 365 Evals: []*structs.Evaluation{eval}, 366 } 367 buf, err := structs.Encode(structs.EvalUpdateRequestType, req) 368 if err != nil { 369 t.Fatalf("err: %v", err) 370 } 371 372 resp := fsm.Apply(makeLog(buf)) 373 if resp != nil { 374 t.Fatalf("resp: %v", resp) 375 } 376 377 req2 := structs.EvalDeleteRequest{ 378 Evals: []string{eval.ID}, 379 } 380 buf, err = structs.Encode(structs.EvalDeleteRequestType, req2) 381 if err != nil { 382 t.Fatalf("err: %v", err) 383 } 384 385 resp = fsm.Apply(makeLog(buf)) 386 if resp != nil { 387 t.Fatalf("resp: %v", resp) 388 } 389 390 // Verify we are NOT registered 391 eval, err = fsm.State().EvalByID(req.Evals[0].ID) 392 if err != nil { 393 t.Fatalf("err: %v", err) 394 } 395 if eval != nil { 396 t.Fatalf("eval found!") 397 } 398 } 399 400 func TestFSM_UpsertAllocs(t *testing.T) { 401 fsm := testFSM(t) 402 403 alloc := mock.Alloc() 404 req := structs.AllocUpdateRequest{ 405 Alloc: []*structs.Allocation{alloc}, 406 } 407 buf, err := structs.Encode(structs.AllocUpdateRequestType, req) 408 if err != nil { 409 t.Fatalf("err: %v", err) 410 } 411 412 resp := fsm.Apply(makeLog(buf)) 413 if resp != nil { 414 t.Fatalf("resp: %v", resp) 415 } 416 417 // Verify we are registered 418 out, err := fsm.State().AllocByID(alloc.ID) 419 if err != nil { 420 t.Fatalf("err: %v", err) 421 } 422 alloc.CreateIndex = out.CreateIndex 423 alloc.ModifyIndex = out.ModifyIndex 424 if !reflect.DeepEqual(alloc, out) { 425 t.Fatalf("bad: %#v %#v", alloc, out) 426 } 427 428 evictAlloc := new(structs.Allocation) 429 *evictAlloc = *alloc 430 evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict 431 req2 := structs.AllocUpdateRequest{ 432 Alloc: []*structs.Allocation{evictAlloc}, 433 } 434 buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) 435 if err != nil { 436 t.Fatalf("err: %v", err) 437 } 438 439 resp = fsm.Apply(makeLog(buf)) 440 if resp != nil { 441 t.Fatalf("resp: %v", resp) 442 } 443 444 // Verify we are evicted 445 out, err = fsm.State().AllocByID(alloc.ID) 446 if err != nil { 447 t.Fatalf("err: %v", err) 448 } 449 if out.DesiredStatus != structs.AllocDesiredStatusEvict { 450 t.Fatalf("alloc found!") 451 } 452 } 453 454 func TestFSM_UpdateAllocFromClient(t *testing.T) { 455 fsm := testFSM(t) 456 state := fsm.State() 457 458 alloc := mock.Alloc() 459 state.UpsertAllocs(1, []*structs.Allocation{alloc}) 460 461 clientAlloc := new(structs.Allocation) 462 *clientAlloc = *alloc 463 clientAlloc.ClientStatus = structs.AllocClientStatusFailed 464 465 req := structs.AllocUpdateRequest{ 466 Alloc: []*structs.Allocation{clientAlloc}, 467 } 468 buf, err := structs.Encode(structs.AllocClientUpdateRequestType, req) 469 if err != nil { 470 t.Fatalf("err: %v", err) 471 } 472 473 resp := fsm.Apply(makeLog(buf)) 474 if resp != nil { 475 t.Fatalf("resp: %v", resp) 476 } 477 478 // Verify we are registered 479 out, err := fsm.State().AllocByID(alloc.ID) 480 if err != nil { 481 t.Fatalf("err: %v", err) 482 } 483 clientAlloc.CreateIndex = out.CreateIndex 484 clientAlloc.ModifyIndex = out.ModifyIndex 485 if !reflect.DeepEqual(clientAlloc, out) { 486 t.Fatalf("bad: %#v %#v", clientAlloc, out) 487 } 488 } 489 490 func testSnapshotRestore(t *testing.T, fsm *nomadFSM) *nomadFSM { 491 // Snapshot 492 snap, err := fsm.Snapshot() 493 if err != nil { 494 t.Fatalf("err: %v", err) 495 } 496 defer snap.Release() 497 498 // Persist 499 buf := bytes.NewBuffer(nil) 500 sink := &MockSink{buf, false} 501 if err := snap.Persist(sink); err != nil { 502 t.Fatalf("err: %v", err) 503 } 504 505 // Try to restore on a new FSM 506 fsm2 := testFSM(t) 507 508 // Do a restore 509 if err := fsm2.Restore(sink); err != nil { 510 t.Fatalf("err: %v", err) 511 } 512 return fsm2 513 } 514 515 func TestFSM_SnapshotRestore_Nodes(t *testing.T) { 516 // Add some state 517 fsm := testFSM(t) 518 state := fsm.State() 519 node1 := mock.Node() 520 state.UpsertNode(1000, node1) 521 node2 := mock.Node() 522 state.UpsertNode(1001, node2) 523 524 // Verify the contents 525 fsm2 := testSnapshotRestore(t, fsm) 526 state2 := fsm2.State() 527 out1, _ := state2.NodeByID(node1.ID) 528 out2, _ := state2.NodeByID(node2.ID) 529 if !reflect.DeepEqual(node1, out1) { 530 t.Fatalf("bad: \n%#v\n%#v", out1, node1) 531 } 532 if !reflect.DeepEqual(node2, out2) { 533 t.Fatalf("bad: \n%#v\n%#v", out2, node2) 534 } 535 } 536 537 func TestFSM_SnapshotRestore_Jobs(t *testing.T) { 538 // Add some state 539 fsm := testFSM(t) 540 state := fsm.State() 541 job1 := mock.Job() 542 state.UpsertJob(1000, job1) 543 job2 := mock.Job() 544 state.UpsertJob(1001, job2) 545 546 // Verify the contents 547 fsm2 := testSnapshotRestore(t, fsm) 548 state2 := fsm2.State() 549 out1, _ := state2.JobByID(job1.ID) 550 out2, _ := state2.JobByID(job2.ID) 551 if !reflect.DeepEqual(job1, out1) { 552 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 553 } 554 if !reflect.DeepEqual(job2, out2) { 555 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 556 } 557 } 558 559 func TestFSM_SnapshotRestore_Evals(t *testing.T) { 560 // Add some state 561 fsm := testFSM(t) 562 state := fsm.State() 563 eval1 := mock.Eval() 564 state.UpsertEvals(1000, []*structs.Evaluation{eval1}) 565 eval2 := mock.Eval() 566 state.UpsertEvals(1001, []*structs.Evaluation{eval2}) 567 568 // Verify the contents 569 fsm2 := testSnapshotRestore(t, fsm) 570 state2 := fsm2.State() 571 out1, _ := state2.EvalByID(eval1.ID) 572 out2, _ := state2.EvalByID(eval2.ID) 573 if !reflect.DeepEqual(eval1, out1) { 574 t.Fatalf("bad: \n%#v\n%#v", out1, eval1) 575 } 576 if !reflect.DeepEqual(eval2, out2) { 577 t.Fatalf("bad: \n%#v\n%#v", out2, eval2) 578 } 579 } 580 581 func TestFSM_SnapshotRestore_Allocs(t *testing.T) { 582 // Add some state 583 fsm := testFSM(t) 584 state := fsm.State() 585 alloc1 := mock.Alloc() 586 state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 587 alloc2 := mock.Alloc() 588 state.UpsertAllocs(1001, []*structs.Allocation{alloc2}) 589 590 // Verify the contents 591 fsm2 := testSnapshotRestore(t, fsm) 592 state2 := fsm2.State() 593 out1, _ := state2.AllocByID(alloc1.ID) 594 out2, _ := state2.AllocByID(alloc2.ID) 595 if !reflect.DeepEqual(alloc1, out1) { 596 t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) 597 } 598 if !reflect.DeepEqual(alloc2, out2) { 599 t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) 600 } 601 } 602 603 func TestFSM_SnapshotRestore_Indexes(t *testing.T) { 604 // Add some state 605 fsm := testFSM(t) 606 state := fsm.State() 607 node1 := mock.Node() 608 state.UpsertNode(1000, node1) 609 610 // Verify the contents 611 fsm2 := testSnapshotRestore(t, fsm) 612 state2 := fsm2.State() 613 614 index, err := state2.Index("nodes") 615 if err != nil { 616 t.Fatalf("err: %v", err) 617 } 618 if index != 1000 { 619 t.Fatalf("bad: %d", index) 620 } 621 } 622 623 func TestFSM_SnapshotRestore_TimeTable(t *testing.T) { 624 // Add some state 625 fsm := testFSM(t) 626 627 tt := fsm.TimeTable() 628 start := time.Now().UTC() 629 tt.Witness(1000, start) 630 tt.Witness(2000, start.Add(10*time.Minute)) 631 632 // Verify the contents 633 fsm2 := testSnapshotRestore(t, fsm) 634 635 tt2 := fsm2.TimeTable() 636 if tt2.NearestTime(1500) != start { 637 t.Fatalf("bad") 638 } 639 if tt2.NearestIndex(start.Add(15*time.Minute)) != 2000 { 640 t.Fatalf("bad") 641 } 642 } 643 644 func TestFSM_SnapshotRestore_PeriodicLaunches(t *testing.T) { 645 // Add some state 646 fsm := testFSM(t) 647 state := fsm.State() 648 job1 := mock.Job() 649 launch1 := &structs.PeriodicLaunch{ID: job1.ID, Launch: time.Now()} 650 state.UpsertPeriodicLaunch(1000, launch1) 651 job2 := mock.Job() 652 launch2 := &structs.PeriodicLaunch{ID: job2.ID, Launch: time.Now()} 653 state.UpsertPeriodicLaunch(1001, launch2) 654 655 // Verify the contents 656 fsm2 := testSnapshotRestore(t, fsm) 657 state2 := fsm2.State() 658 out1, _ := state2.PeriodicLaunchByID(launch1.ID) 659 out2, _ := state2.PeriodicLaunchByID(launch2.ID) 660 if !reflect.DeepEqual(launch1, out1) { 661 t.Fatalf("bad: \n%#v\n%#v", out1, job1) 662 } 663 if !reflect.DeepEqual(launch2, out2) { 664 t.Fatalf("bad: \n%#v\n%#v", out2, job2) 665 } 666 }