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