package nomad import ( "bytes" "os" "reflect" "testing" "time" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/raft" ) type MockSink struct { *bytes.Buffer cancel bool } func (m *MockSink) ID() string { return "Mock" } func (m *MockSink) Cancel() error { m.cancel = true return nil } func (m *MockSink) Close() error { return nil } func testStateStore(t *testing.T) *state.StateStore { state, err := state.NewStateStore(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } if state == nil { t.Fatalf("missing state") } return state } func testFSM(t *testing.T) *nomadFSM { fsm, err := NewFSM(testBroker(t, 0), os.Stderr) if err != nil { t.Fatalf("err: %v", err) } if fsm == nil { t.Fatalf("missing fsm") } return fsm } func makeLog(buf []byte) *raft.Log { return &raft.Log{ Index: 1, Term: 1, Type: raft.LogCommand, Data: buf, } } func TestFSM_RegisterNode(t *testing.T) { fsm := testFSM(t) req := structs.NodeRegisterRequest{ Node: mock.Node(), } buf, err := structs.Encode(structs.NodeRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered node, err := fsm.State().GetNodeByID(req.Node.ID) if err != nil { t.Fatalf("err: %v", err) } if node == nil { t.Fatalf("not found!") } if node.CreateIndex != 1 { t.Fatalf("bad index: %d", node.CreateIndex) } tt := fsm.TimeTable() index := tt.NearestIndex(time.Now().UTC()) if index != 1 { t.Fatalf("bad: %d", index) } } func TestFSM_DeregisterNode(t *testing.T) { fsm := testFSM(t) node := mock.Node() req := structs.NodeRegisterRequest{ Node: node, } buf, err := structs.Encode(structs.NodeRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } req2 := structs.NodeDeregisterRequest{ NodeID: node.ID, } buf, err = structs.Encode(structs.NodeDeregisterRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are NOT registered node, err = fsm.State().GetNodeByID(req.Node.ID) if err != nil { t.Fatalf("err: %v", err) } if node != nil { t.Fatalf("node found!") } } func TestFSM_UpdateNodeStatus(t *testing.T) { fsm := testFSM(t) node := mock.Node() req := structs.NodeRegisterRequest{ Node: node, } buf, err := structs.Encode(structs.NodeRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } req2 := structs.NodeUpdateStatusRequest{ NodeID: node.ID, Status: structs.NodeStatusReady, } buf, err = structs.Encode(structs.NodeUpdateStatusRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are NOT registered node, err = fsm.State().GetNodeByID(req.Node.ID) if err != nil { t.Fatalf("err: %v", err) } if node.Status != structs.NodeStatusReady { t.Fatalf("bad node: %#v", node) } } func TestFSM_RegisterJob(t *testing.T) { fsm := testFSM(t) req := structs.JobRegisterRequest{ Job: mock.Job(), } buf, err := structs.Encode(structs.JobRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered job, err := fsm.State().GetJobByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if job == nil { t.Fatalf("not found!") } if job.CreateIndex != 1 { t.Fatalf("bad index: %d", job.CreateIndex) } } func TestFSM_DeregisterJob(t *testing.T) { fsm := testFSM(t) job := mock.Job() req := structs.JobRegisterRequest{ Job: job, } buf, err := structs.Encode(structs.JobRegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } req2 := structs.JobDeregisterRequest{ JobID: job.ID, } buf, err = structs.Encode(structs.JobDeregisterRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are NOT registered job, err = fsm.State().GetJobByID(req.Job.ID) if err != nil { t.Fatalf("err: %v", err) } if job != nil { t.Fatalf("job found!") } } func TestFSM_UpdateEval(t *testing.T) { fsm := testFSM(t) fsm.evalBroker.SetEnabled(true) req := structs.EvalUpdateRequest{ Evals: []*structs.Evaluation{mock.Eval()}, } buf, err := structs.Encode(structs.EvalUpdateRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered eval, err := fsm.State().GetEvalByID(req.Evals[0].ID) if err != nil { t.Fatalf("err: %v", err) } if eval == nil { t.Fatalf("not found!") } if eval.CreateIndex != 1 { t.Fatalf("bad index: %d", eval.CreateIndex) } // Verify enqueued stats := fsm.evalBroker.Stats() if stats.TotalReady != 1 { t.Fatalf("bad: %#v %#v", stats, eval) } } func TestFSM_DeleteEval(t *testing.T) { fsm := testFSM(t) eval := mock.Eval() req := structs.EvalUpdateRequest{ Evals: []*structs.Evaluation{eval}, } buf, err := structs.Encode(structs.EvalUpdateRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } req2 := structs.EvalDeleteRequest{ Evals: []string{eval.ID}, } buf, err = structs.Encode(structs.EvalDeleteRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are NOT registered eval, err = fsm.State().GetEvalByID(req.Evals[0].ID) if err != nil { t.Fatalf("err: %v", err) } if eval != nil { t.Fatalf("eval found!") } } func TestFSM_UpdateAllocations(t *testing.T) { fsm := testFSM(t) alloc := mock.Alloc() req := structs.AllocUpdateRequest{ Evict: nil, Alloc: []*structs.Allocation{alloc}, } buf, err := structs.Encode(structs.AllocUpdateRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered out, err := fsm.State().GetAllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } alloc.CreateIndex = out.CreateIndex alloc.ModifyIndex = out.ModifyIndex if !reflect.DeepEqual(alloc, out) { t.Fatalf("bad: %#v %#v", alloc, out) } req2 := structs.AllocUpdateRequest{ Evict: []string{alloc.ID}, } buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are evicted out, err = fsm.State().GetAllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } if out.Status != structs.AllocStatusEvict { t.Fatalf("alloc found!") } } func testSnapshotRestore(t *testing.T, fsm *nomadFSM) *nomadFSM { // Snapshot snap, err := fsm.Snapshot() if err != nil { t.Fatalf("err: %v", err) } defer snap.Release() // Persist buf := bytes.NewBuffer(nil) sink := &MockSink{buf, false} if err := snap.Persist(sink); err != nil { t.Fatalf("err: %v", err) } // Try to restore on a new FSM fsm2 := testFSM(t) // Do a restore if err := fsm2.Restore(sink); err != nil { t.Fatalf("err: %v", err) } return fsm2 } func TestFSM_SnapshotRestore_Nodes(t *testing.T) { // Add some state fsm := testFSM(t) state := fsm.State() node1 := mock.Node() state.RegisterNode(1000, node1) node2 := mock.Node() state.RegisterNode(1001, node2) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) state2 := fsm2.State() out1, _ := state2.GetNodeByID(node1.ID) out2, _ := state2.GetNodeByID(node2.ID) if !reflect.DeepEqual(node1, out1) { t.Fatalf("bad: \n%#v\n%#v", out1, node1) } if !reflect.DeepEqual(node2, out2) { t.Fatalf("bad: \n%#v\n%#v", out2, node2) } } func TestFSM_SnapshotRestore_Jobs(t *testing.T) { // Add some state fsm := testFSM(t) state := fsm.State() job1 := mock.Job() state.RegisterJob(1000, job1) job2 := mock.Job() state.RegisterJob(1001, job2) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) state2 := fsm2.State() out1, _ := state2.GetJobByID(job1.ID) out2, _ := state2.GetJobByID(job2.ID) if !reflect.DeepEqual(job1, out1) { t.Fatalf("bad: \n%#v\n%#v", out1, job1) } if !reflect.DeepEqual(job2, out2) { t.Fatalf("bad: \n%#v\n%#v", out2, job2) } } func TestFSM_SnapshotRestore_Evals(t *testing.T) { // Add some state fsm := testFSM(t) state := fsm.State() eval1 := mock.Eval() state.UpsertEvals(1000, []*structs.Evaluation{eval1}) eval2 := mock.Eval() state.UpsertEvals(1001, []*structs.Evaluation{eval2}) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) state2 := fsm2.State() out1, _ := state2.GetEvalByID(eval1.ID) out2, _ := state2.GetEvalByID(eval2.ID) if !reflect.DeepEqual(eval1, out1) { t.Fatalf("bad: \n%#v\n%#v", out1, eval1) } if !reflect.DeepEqual(eval2, out2) { t.Fatalf("bad: \n%#v\n%#v", out2, eval2) } } func TestFSM_SnapshotRestore_Allocs(t *testing.T) { // Add some state fsm := testFSM(t) state := fsm.State() alloc1 := mock.Alloc() state.UpdateAllocations(1000, nil, []*structs.Allocation{alloc1}) alloc2 := mock.Alloc() state.UpdateAllocations(1001, nil, []*structs.Allocation{alloc2}) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) state2 := fsm2.State() out1, _ := state2.GetAllocByID(alloc1.ID) out2, _ := state2.GetAllocByID(alloc2.ID) if !reflect.DeepEqual(alloc1, out1) { t.Fatalf("bad: \n%#v\n%#v", out1, alloc1) } if !reflect.DeepEqual(alloc2, out2) { t.Fatalf("bad: \n%#v\n%#v", out2, alloc2) } } func TestFSM_SnapshotRestore_Indexes(t *testing.T) { // Add some state fsm := testFSM(t) state := fsm.State() node1 := mock.Node() state.RegisterNode(1000, node1) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) state2 := fsm2.State() index, err := state2.GetIndex("nodes") if err != nil { t.Fatalf("err: %v", err) } if index != 1000 { t.Fatalf("bad: %d", index) } } func TestFSM_SnapshotRestore_TimeTable(t *testing.T) { // Add some state fsm := testFSM(t) tt := fsm.TimeTable() start := time.Now().UTC() tt.Witness(1000, start) tt.Witness(2000, start.Add(10*time.Minute)) // Verify the contents fsm2 := testSnapshotRestore(t, fsm) tt2 := fsm2.TimeTable() if tt2.NearestTime(1500) != start { t.Fatalf("bad") } if tt2.NearestIndex(start.Add(15*time.Minute)) != 2000 { t.Fatalf("bad") } }