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  }