github.com/hooklift/nomad@v0.5.7-0.20170407200202-db11e7dd7b55/nomad/worker_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"log"
     5  	"reflect"
     6  	"strings"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	memdb "github.com/hashicorp/go-memdb"
    12  	"github.com/hashicorp/nomad/nomad/mock"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/nomad/scheduler"
    15  	"github.com/hashicorp/nomad/testutil"
    16  )
    17  
    18  type NoopScheduler struct {
    19  	state   scheduler.State
    20  	planner scheduler.Planner
    21  	eval    *structs.Evaluation
    22  	err     error
    23  }
    24  
    25  func (n *NoopScheduler) Process(eval *structs.Evaluation) error {
    26  	if n.state == nil {
    27  		panic("missing state")
    28  	}
    29  	if n.planner == nil {
    30  		panic("missing planner")
    31  	}
    32  	n.eval = eval
    33  	return n.err
    34  }
    35  
    36  func init() {
    37  	scheduler.BuiltinSchedulers["noop"] = func(logger *log.Logger, s scheduler.State, p scheduler.Planner) scheduler.Scheduler {
    38  		n := &NoopScheduler{
    39  			state:   s,
    40  			planner: p,
    41  		}
    42  		return n
    43  	}
    44  }
    45  
    46  func TestWorker_dequeueEvaluation(t *testing.T) {
    47  	s1 := testServer(t, func(c *Config) {
    48  		c.NumSchedulers = 0
    49  		c.EnabledSchedulers = []string{structs.JobTypeService}
    50  	})
    51  	defer s1.Shutdown()
    52  	testutil.WaitForLeader(t, s1.RPC)
    53  
    54  	// Create the evaluation
    55  	eval1 := mock.Eval()
    56  	s1.evalBroker.Enqueue(eval1)
    57  
    58  	// Create a worker
    59  	w := &Worker{srv: s1, logger: s1.logger}
    60  
    61  	// Attempt dequeue
    62  	eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
    63  	if shutdown {
    64  		t.Fatalf("should not shutdown")
    65  	}
    66  	if token == "" {
    67  		t.Fatalf("should get token")
    68  	}
    69  
    70  	// Ensure we get a sane eval
    71  	if !reflect.DeepEqual(eval, eval1) {
    72  		t.Fatalf("bad: %#v %#v", eval, eval1)
    73  	}
    74  }
    75  
    76  func TestWorker_dequeueEvaluation_paused(t *testing.T) {
    77  	s1 := testServer(t, func(c *Config) {
    78  		c.NumSchedulers = 0
    79  		c.EnabledSchedulers = []string{structs.JobTypeService}
    80  	})
    81  	defer s1.Shutdown()
    82  	testutil.WaitForLeader(t, s1.RPC)
    83  
    84  	// Create the evaluation
    85  	eval1 := mock.Eval()
    86  	s1.evalBroker.Enqueue(eval1)
    87  
    88  	// Create a worker
    89  	w := &Worker{srv: s1, logger: s1.logger}
    90  	w.pauseCond = sync.NewCond(&w.pauseLock)
    91  
    92  	// PAUSE the worker
    93  	w.SetPause(true)
    94  
    95  	go func() {
    96  		time.Sleep(100 * time.Millisecond)
    97  		w.SetPause(false)
    98  	}()
    99  
   100  	// Attempt dequeue
   101  	start := time.Now()
   102  	eval, token, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
   103  	if diff := time.Since(start); diff < 100*time.Millisecond {
   104  		t.Fatalf("should have paused: %v", diff)
   105  	}
   106  	if shutdown {
   107  		t.Fatalf("should not shutdown")
   108  	}
   109  	if token == "" {
   110  		t.Fatalf("should get token")
   111  	}
   112  
   113  	// Ensure we get a sane eval
   114  	if !reflect.DeepEqual(eval, eval1) {
   115  		t.Fatalf("bad: %#v %#v", eval, eval1)
   116  	}
   117  }
   118  
   119  func TestWorker_dequeueEvaluation_shutdown(t *testing.T) {
   120  	s1 := testServer(t, func(c *Config) {
   121  		c.NumSchedulers = 0
   122  		c.EnabledSchedulers = []string{structs.JobTypeService}
   123  	})
   124  	defer s1.Shutdown()
   125  	testutil.WaitForLeader(t, s1.RPC)
   126  
   127  	// Create a worker
   128  	w := &Worker{srv: s1, logger: s1.logger}
   129  
   130  	go func() {
   131  		time.Sleep(10 * time.Millisecond)
   132  		s1.Shutdown()
   133  	}()
   134  
   135  	// Attempt dequeue
   136  	eval, _, shutdown := w.dequeueEvaluation(10 * time.Millisecond)
   137  	if !shutdown {
   138  		t.Fatalf("should not shutdown")
   139  	}
   140  
   141  	// Ensure we get a sane eval
   142  	if eval != nil {
   143  		t.Fatalf("bad: %#v", eval)
   144  	}
   145  }
   146  
   147  func TestWorker_sendAck(t *testing.T) {
   148  	s1 := testServer(t, func(c *Config) {
   149  		c.NumSchedulers = 0
   150  		c.EnabledSchedulers = []string{structs.JobTypeService}
   151  	})
   152  	defer s1.Shutdown()
   153  	testutil.WaitForLeader(t, s1.RPC)
   154  
   155  	// Create the evaluation
   156  	eval1 := mock.Eval()
   157  	s1.evalBroker.Enqueue(eval1)
   158  
   159  	// Create a worker
   160  	w := &Worker{srv: s1, logger: s1.logger}
   161  
   162  	// Attempt dequeue
   163  	eval, token, _ := w.dequeueEvaluation(10 * time.Millisecond)
   164  
   165  	// Check the depth is 0, 1 unacked
   166  	stats := s1.evalBroker.Stats()
   167  	if stats.TotalReady != 0 && stats.TotalUnacked != 1 {
   168  		t.Fatalf("bad: %#v", stats)
   169  	}
   170  
   171  	// Send the Nack
   172  	w.sendAck(eval.ID, token, false)
   173  
   174  	// Check the depth is 1, nothing unacked
   175  	stats = s1.evalBroker.Stats()
   176  	if stats.TotalReady != 1 && stats.TotalUnacked != 0 {
   177  		t.Fatalf("bad: %#v", stats)
   178  	}
   179  
   180  	// Attempt dequeue
   181  	eval, token, _ = w.dequeueEvaluation(10 * time.Millisecond)
   182  
   183  	// Send the Ack
   184  	w.sendAck(eval.ID, token, true)
   185  
   186  	// Check the depth is 0
   187  	stats = s1.evalBroker.Stats()
   188  	if stats.TotalReady != 0 && stats.TotalUnacked != 0 {
   189  		t.Fatalf("bad: %#v", stats)
   190  	}
   191  }
   192  
   193  func TestWorker_waitForIndex(t *testing.T) {
   194  	s1 := testServer(t, func(c *Config) {
   195  		c.NumSchedulers = 0
   196  		c.EnabledSchedulers = []string{structs.JobTypeService}
   197  	})
   198  	defer s1.Shutdown()
   199  	testutil.WaitForLeader(t, s1.RPC)
   200  
   201  	// Get the current index
   202  	index := s1.raft.AppliedIndex()
   203  
   204  	// Cause an increment
   205  	go func() {
   206  		time.Sleep(10 * time.Millisecond)
   207  		n := mock.Node()
   208  		if err := s1.fsm.state.UpsertNode(index+1, n); err != nil {
   209  			t.Fatalf("failed to upsert node: %v", err)
   210  		}
   211  	}()
   212  
   213  	// Wait for a future index
   214  	w := &Worker{srv: s1, logger: s1.logger}
   215  	err := w.waitForIndex(index+1, time.Second)
   216  	if err != nil {
   217  		t.Fatalf("err: %v", err)
   218  	}
   219  
   220  	// Cause a timeout
   221  	err = w.waitForIndex(index+100, 10*time.Millisecond)
   222  	if err == nil || !strings.Contains(err.Error(), "timeout") {
   223  		t.Fatalf("err: %v", err)
   224  	}
   225  }
   226  
   227  func TestWorker_invokeScheduler(t *testing.T) {
   228  	s1 := testServer(t, func(c *Config) {
   229  		c.NumSchedulers = 0
   230  		c.EnabledSchedulers = []string{structs.JobTypeService}
   231  	})
   232  	defer s1.Shutdown()
   233  
   234  	w := &Worker{srv: s1, logger: s1.logger}
   235  	eval := mock.Eval()
   236  	eval.Type = "noop"
   237  
   238  	err := w.invokeScheduler(eval, structs.GenerateUUID())
   239  	if err != nil {
   240  		t.Fatalf("err: %v", err)
   241  	}
   242  }
   243  
   244  func TestWorker_SubmitPlan(t *testing.T) {
   245  	s1 := testServer(t, func(c *Config) {
   246  		c.NumSchedulers = 0
   247  		c.EnabledSchedulers = []string{structs.JobTypeService}
   248  	})
   249  	defer s1.Shutdown()
   250  	testutil.WaitForLeader(t, s1.RPC)
   251  
   252  	// Register node
   253  	node := mock.Node()
   254  	testRegisterNode(t, s1, node)
   255  
   256  	eval1 := mock.Eval()
   257  	s1.fsm.State().UpsertJobSummary(1000, mock.JobSummary(eval1.JobID))
   258  
   259  	// Create the register request
   260  	s1.evalBroker.Enqueue(eval1)
   261  
   262  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   263  	if err != nil {
   264  		t.Fatalf("err: %v", err)
   265  	}
   266  	if evalOut != eval1 {
   267  		t.Fatalf("Bad eval")
   268  	}
   269  
   270  	// Create an allocation plan
   271  	alloc := mock.Alloc()
   272  	s1.fsm.State().UpsertJobSummary(1200, mock.JobSummary(alloc.JobID))
   273  	plan := &structs.Plan{
   274  		EvalID: eval1.ID,
   275  		NodeAllocation: map[string][]*structs.Allocation{
   276  			node.ID: []*structs.Allocation{alloc},
   277  		},
   278  	}
   279  
   280  	// Attempt to submit a plan
   281  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   282  	result, state, err := w.SubmitPlan(plan)
   283  	if err != nil {
   284  		t.Fatalf("err: %v", err)
   285  	}
   286  
   287  	// Should have no update
   288  	if state != nil {
   289  		t.Fatalf("unexpected state update")
   290  	}
   291  
   292  	// Result should have allocated
   293  	if result == nil {
   294  		t.Fatalf("missing result")
   295  	}
   296  
   297  	if result.AllocIndex == 0 {
   298  		t.Fatalf("Bad: %#v", result)
   299  	}
   300  	if len(result.NodeAllocation) != 1 {
   301  		t.Fatalf("Bad: %#v", result)
   302  	}
   303  }
   304  
   305  func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) {
   306  	s1 := testServer(t, func(c *Config) {
   307  		c.NumSchedulers = 0
   308  		c.EnabledSchedulers = []string{structs.JobTypeService}
   309  	})
   310  	defer s1.Shutdown()
   311  	testutil.WaitForLeader(t, s1.RPC)
   312  
   313  	// Register node
   314  	node := mock.Node()
   315  	testRegisterNode(t, s1, node)
   316  
   317  	// Create the register request
   318  	eval1 := mock.Eval()
   319  	s1.evalBroker.Enqueue(eval1)
   320  
   321  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   322  	if err != nil {
   323  		t.Fatalf("err: %v", err)
   324  	}
   325  	if evalOut != eval1 {
   326  		t.Fatalf("Bad eval")
   327  	}
   328  
   329  	// Create an allocation plan, with unregistered node
   330  	node2 := mock.Node()
   331  	alloc := mock.Alloc()
   332  	plan := &structs.Plan{
   333  		EvalID: eval1.ID,
   334  		NodeAllocation: map[string][]*structs.Allocation{
   335  			node2.ID: []*structs.Allocation{alloc},
   336  		},
   337  	}
   338  
   339  	// Attempt to submit a plan
   340  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   341  	result, state, err := w.SubmitPlan(plan)
   342  	if err != nil {
   343  		t.Fatalf("err: %v", err)
   344  	}
   345  
   346  	// Result should have allocated
   347  	if result == nil {
   348  		t.Fatalf("missing result")
   349  	}
   350  
   351  	// Expect no allocation and forced refresh
   352  	if result.AllocIndex != 0 {
   353  		t.Fatalf("Bad: %#v", result)
   354  	}
   355  	if result.RefreshIndex == 0 {
   356  		t.Fatalf("Bad: %#v", result)
   357  	}
   358  	if len(result.NodeAllocation) != 0 {
   359  		t.Fatalf("Bad: %#v", result)
   360  	}
   361  
   362  	// Should have an update
   363  	if state == nil {
   364  		t.Fatalf("expected state update")
   365  	}
   366  }
   367  
   368  func TestWorker_UpdateEval(t *testing.T) {
   369  	s1 := testServer(t, func(c *Config) {
   370  		c.NumSchedulers = 0
   371  		c.EnabledSchedulers = []string{structs.JobTypeService}
   372  	})
   373  	defer s1.Shutdown()
   374  	testutil.WaitForLeader(t, s1.RPC)
   375  
   376  	// Register node
   377  	node := mock.Node()
   378  	testRegisterNode(t, s1, node)
   379  
   380  	// Create the register request
   381  	eval1 := mock.Eval()
   382  	s1.evalBroker.Enqueue(eval1)
   383  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   384  	if err != nil {
   385  		t.Fatalf("err: %v", err)
   386  	}
   387  	if evalOut != eval1 {
   388  		t.Fatalf("Bad eval")
   389  	}
   390  
   391  	eval2 := evalOut.Copy()
   392  	eval2.Status = structs.EvalStatusComplete
   393  
   394  	// Attempt to update eval
   395  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   396  	err = w.UpdateEval(eval2)
   397  	if err != nil {
   398  		t.Fatalf("err: %v", err)
   399  	}
   400  
   401  	ws := memdb.NewWatchSet()
   402  	out, err := s1.fsm.State().EvalByID(ws, eval2.ID)
   403  	if err != nil {
   404  		t.Fatalf("err: %v", err)
   405  	}
   406  	if out.Status != structs.EvalStatusComplete {
   407  		t.Fatalf("bad: %v", out)
   408  	}
   409  	if out.SnapshotIndex != w.snapshotIndex {
   410  		t.Fatalf("bad: %v", out)
   411  	}
   412  }
   413  
   414  func TestWorker_CreateEval(t *testing.T) {
   415  	s1 := testServer(t, func(c *Config) {
   416  		c.NumSchedulers = 0
   417  		c.EnabledSchedulers = []string{structs.JobTypeService}
   418  	})
   419  	defer s1.Shutdown()
   420  	testutil.WaitForLeader(t, s1.RPC)
   421  
   422  	// Register node
   423  	node := mock.Node()
   424  	testRegisterNode(t, s1, node)
   425  
   426  	// Create the register request
   427  	eval1 := mock.Eval()
   428  	s1.evalBroker.Enqueue(eval1)
   429  
   430  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   431  	if err != nil {
   432  		t.Fatalf("err: %v", err)
   433  	}
   434  	if evalOut != eval1 {
   435  		t.Fatalf("Bad eval")
   436  	}
   437  
   438  	eval2 := mock.Eval()
   439  	eval2.PreviousEval = eval1.ID
   440  
   441  	// Attempt to create eval
   442  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   443  	err = w.CreateEval(eval2)
   444  	if err != nil {
   445  		t.Fatalf("err: %v", err)
   446  	}
   447  
   448  	ws := memdb.NewWatchSet()
   449  	out, err := s1.fsm.State().EvalByID(ws, eval2.ID)
   450  	if err != nil {
   451  		t.Fatalf("err: %v", err)
   452  	}
   453  	if out.PreviousEval != eval1.ID {
   454  		t.Fatalf("bad: %v", out)
   455  	}
   456  	if out.SnapshotIndex != w.snapshotIndex {
   457  		t.Fatalf("bad: %v", out)
   458  	}
   459  }
   460  
   461  func TestWorker_ReblockEval(t *testing.T) {
   462  	s1 := testServer(t, func(c *Config) {
   463  		c.NumSchedulers = 0
   464  		c.EnabledSchedulers = []string{structs.JobTypeService}
   465  	})
   466  	defer s1.Shutdown()
   467  	testutil.WaitForLeader(t, s1.RPC)
   468  
   469  	// Create the blocked eval
   470  	eval1 := mock.Eval()
   471  	eval1.Status = structs.EvalStatusBlocked
   472  	eval1.QueuedAllocations = map[string]int{"cache": 100}
   473  
   474  	// Insert it into the state store
   475  	if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil {
   476  		t.Fatal(err)
   477  	}
   478  
   479  	// Create the job summary
   480  	js := mock.JobSummary(eval1.JobID)
   481  	tg := js.Summary["web"]
   482  	tg.Queued = 100
   483  	js.Summary["web"] = tg
   484  	if err := s1.fsm.State().UpsertJobSummary(1001, js); err != nil {
   485  		t.Fatal(err)
   486  	}
   487  
   488  	// Enqueue the eval and then dequeue
   489  	s1.evalBroker.Enqueue(eval1)
   490  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   491  	if err != nil {
   492  		t.Fatalf("err: %v", err)
   493  	}
   494  	if evalOut != eval1 {
   495  		t.Fatalf("Bad eval")
   496  	}
   497  
   498  	eval2 := evalOut.Copy()
   499  	eval2.QueuedAllocations = map[string]int{"web": 50}
   500  
   501  	// Attempt to reblock eval
   502  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   503  	err = w.ReblockEval(eval2)
   504  	if err != nil {
   505  		t.Fatalf("err: %v", err)
   506  	}
   507  
   508  	// Ack the eval
   509  	w.sendAck(evalOut.ID, token, true)
   510  
   511  	// Check that it is blocked
   512  	bStats := s1.blockedEvals.Stats()
   513  	if bStats.TotalBlocked+bStats.TotalEscaped != 1 {
   514  		t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats)
   515  	}
   516  
   517  	// Check that the eval was updated
   518  	ws := memdb.NewWatchSet()
   519  	eval, err := s1.fsm.State().EvalByID(ws, eval2.ID)
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	if !reflect.DeepEqual(eval.QueuedAllocations, eval2.QueuedAllocations) {
   524  		t.Fatalf("expected: %#v, actual: %#v", eval2.QueuedAllocations, eval.QueuedAllocations)
   525  	}
   526  
   527  	// Check that the snapshot index was set properly by unblocking the eval and
   528  	// then dequeuing.
   529  	s1.blockedEvals.Unblock("foobar", 1000)
   530  
   531  	reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second)
   532  	if err != nil {
   533  		t.Fatalf("err: %v", err)
   534  	}
   535  	if reblockedEval == nil {
   536  		t.Fatalf("Nil eval")
   537  	}
   538  	if reblockedEval.ID != eval1.ID {
   539  		t.Fatalf("Bad eval")
   540  	}
   541  
   542  	// Check that the SnapshotIndex is set
   543  	if reblockedEval.SnapshotIndex != w.snapshotIndex {
   544  		t.Fatalf("incorrect snapshot index; got %d; want %d",
   545  			reblockedEval.SnapshotIndex, w.snapshotIndex)
   546  	}
   547  }