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