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