github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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  		s1.raft.Barrier(0)
   207  	}()
   208  
   209  	// Wait for a future index
   210  	w := &Worker{srv: s1, logger: s1.logger}
   211  	err := w.waitForIndex(index+1, time.Second)
   212  	if err != nil {
   213  		t.Fatalf("err: %v", err)
   214  	}
   215  
   216  	// Cause a timeout
   217  	err = w.waitForIndex(index+100, 10*time.Millisecond)
   218  	if err == nil || !strings.Contains(err.Error(), "timeout") {
   219  		t.Fatalf("err: %v", err)
   220  	}
   221  }
   222  
   223  func TestWorker_invokeScheduler(t *testing.T) {
   224  	s1 := testServer(t, func(c *Config) {
   225  		c.NumSchedulers = 0
   226  		c.EnabledSchedulers = []string{structs.JobTypeService}
   227  	})
   228  	defer s1.Shutdown()
   229  
   230  	w := &Worker{srv: s1, logger: s1.logger}
   231  	eval := mock.Eval()
   232  	eval.Type = "noop"
   233  
   234  	err := w.invokeScheduler(eval, structs.GenerateUUID())
   235  	if err != nil {
   236  		t.Fatalf("err: %v", err)
   237  	}
   238  }
   239  
   240  func TestWorker_SubmitPlan(t *testing.T) {
   241  	s1 := testServer(t, func(c *Config) {
   242  		c.NumSchedulers = 0
   243  		c.EnabledSchedulers = []string{structs.JobTypeService}
   244  	})
   245  	defer s1.Shutdown()
   246  	testutil.WaitForLeader(t, s1.RPC)
   247  
   248  	// Register node
   249  	node := mock.Node()
   250  	testRegisterNode(t, s1, node)
   251  
   252  	// Create the register request
   253  	eval1 := mock.Eval()
   254  	s1.evalBroker.Enqueue(eval1)
   255  
   256  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   257  	if err != nil {
   258  		t.Fatalf("err: %v", err)
   259  	}
   260  	if evalOut != eval1 {
   261  		t.Fatalf("Bad eval")
   262  	}
   263  
   264  	// Create an allocation plan
   265  	alloc := mock.Alloc()
   266  	plan := &structs.Plan{
   267  		EvalID: eval1.ID,
   268  		NodeAllocation: map[string][]*structs.Allocation{
   269  			node.ID: []*structs.Allocation{alloc},
   270  		},
   271  	}
   272  
   273  	// Attempt to submit a plan
   274  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   275  	result, state, err := w.SubmitPlan(plan)
   276  	if err != nil {
   277  		t.Fatalf("err: %v", err)
   278  	}
   279  
   280  	// Should have no update
   281  	if state != nil {
   282  		t.Fatalf("unexpected state update")
   283  	}
   284  
   285  	// Result should have allocated
   286  	if result == nil {
   287  		t.Fatalf("missing result")
   288  	}
   289  
   290  	if result.AllocIndex == 0 {
   291  		t.Fatalf("Bad: %#v", result)
   292  	}
   293  	if len(result.NodeAllocation) != 1 {
   294  		t.Fatalf("Bad: %#v", result)
   295  	}
   296  }
   297  
   298  func TestWorker_SubmitPlan_MissingNodeRefresh(t *testing.T) {
   299  	s1 := testServer(t, func(c *Config) {
   300  		c.NumSchedulers = 0
   301  		c.EnabledSchedulers = []string{structs.JobTypeService}
   302  	})
   303  	defer s1.Shutdown()
   304  	testutil.WaitForLeader(t, s1.RPC)
   305  
   306  	// Register node
   307  	node := mock.Node()
   308  	testRegisterNode(t, s1, node)
   309  
   310  	// Create the register request
   311  	eval1 := mock.Eval()
   312  	s1.evalBroker.Enqueue(eval1)
   313  
   314  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   315  	if err != nil {
   316  		t.Fatalf("err: %v", err)
   317  	}
   318  	if evalOut != eval1 {
   319  		t.Fatalf("Bad eval")
   320  	}
   321  
   322  	// Create an allocation plan, with unregistered node
   323  	node2 := mock.Node()
   324  	alloc := mock.Alloc()
   325  	plan := &structs.Plan{
   326  		EvalID: eval1.ID,
   327  		NodeAllocation: map[string][]*structs.Allocation{
   328  			node2.ID: []*structs.Allocation{alloc},
   329  		},
   330  	}
   331  
   332  	// Attempt to submit a plan
   333  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   334  	result, state, err := w.SubmitPlan(plan)
   335  	if err != nil {
   336  		t.Fatalf("err: %v", err)
   337  	}
   338  
   339  	// Result should have allocated
   340  	if result == nil {
   341  		t.Fatalf("missing result")
   342  	}
   343  
   344  	// Expect no allocation and forced refresh
   345  	if result.AllocIndex != 0 {
   346  		t.Fatalf("Bad: %#v", result)
   347  	}
   348  	if result.RefreshIndex == 0 {
   349  		t.Fatalf("Bad: %#v", result)
   350  	}
   351  	if len(result.NodeAllocation) != 0 {
   352  		t.Fatalf("Bad: %#v", result)
   353  	}
   354  
   355  	// Should have an update
   356  	if state == nil {
   357  		t.Fatalf("expected state update")
   358  	}
   359  }
   360  
   361  func TestWorker_UpdateEval(t *testing.T) {
   362  	s1 := testServer(t, func(c *Config) {
   363  		c.NumSchedulers = 0
   364  		c.EnabledSchedulers = []string{structs.JobTypeService}
   365  	})
   366  	defer s1.Shutdown()
   367  	testutil.WaitForLeader(t, s1.RPC)
   368  
   369  	// Register node
   370  	node := mock.Node()
   371  	testRegisterNode(t, s1, node)
   372  
   373  	// Create the register request
   374  	eval1 := mock.Eval()
   375  	s1.evalBroker.Enqueue(eval1)
   376  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   377  	if err != nil {
   378  		t.Fatalf("err: %v", err)
   379  	}
   380  	if evalOut != eval1 {
   381  		t.Fatalf("Bad eval")
   382  	}
   383  
   384  	eval2 := evalOut.Copy()
   385  	eval2.Status = structs.EvalStatusComplete
   386  
   387  	// Attempt to update eval
   388  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   389  	err = w.UpdateEval(eval2)
   390  	if err != nil {
   391  		t.Fatalf("err: %v", err)
   392  	}
   393  
   394  	out, err := s1.fsm.State().EvalByID(eval2.ID)
   395  	if err != nil {
   396  		t.Fatalf("err: %v", err)
   397  	}
   398  	if out.Status != structs.EvalStatusComplete {
   399  		t.Fatalf("bad: %v", out)
   400  	}
   401  	if out.SnapshotIndex != w.snapshotIndex {
   402  		t.Fatalf("bad: %v", out)
   403  	}
   404  }
   405  
   406  func TestWorker_CreateEval(t *testing.T) {
   407  	s1 := testServer(t, func(c *Config) {
   408  		c.NumSchedulers = 0
   409  		c.EnabledSchedulers = []string{structs.JobTypeService}
   410  	})
   411  	defer s1.Shutdown()
   412  	testutil.WaitForLeader(t, s1.RPC)
   413  
   414  	// Register node
   415  	node := mock.Node()
   416  	testRegisterNode(t, s1, node)
   417  
   418  	// Create the register request
   419  	eval1 := mock.Eval()
   420  	s1.evalBroker.Enqueue(eval1)
   421  
   422  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   423  	if err != nil {
   424  		t.Fatalf("err: %v", err)
   425  	}
   426  	if evalOut != eval1 {
   427  		t.Fatalf("Bad eval")
   428  	}
   429  
   430  	eval2 := mock.Eval()
   431  	eval2.PreviousEval = eval1.ID
   432  
   433  	// Attempt to create eval
   434  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   435  	err = w.CreateEval(eval2)
   436  	if err != nil {
   437  		t.Fatalf("err: %v", err)
   438  	}
   439  
   440  	out, err := s1.fsm.State().EvalByID(eval2.ID)
   441  	if err != nil {
   442  		t.Fatalf("err: %v", err)
   443  	}
   444  	if out.PreviousEval != eval1.ID {
   445  		t.Fatalf("bad: %v", out)
   446  	}
   447  	if out.SnapshotIndex != w.snapshotIndex {
   448  		t.Fatalf("bad: %v", out)
   449  	}
   450  }
   451  
   452  func TestWorker_ReblockEval(t *testing.T) {
   453  	s1 := testServer(t, func(c *Config) {
   454  		c.NumSchedulers = 0
   455  		c.EnabledSchedulers = []string{structs.JobTypeService}
   456  	})
   457  	defer s1.Shutdown()
   458  	testutil.WaitForLeader(t, s1.RPC)
   459  
   460  	// Create the blocked eval
   461  	eval1 := mock.Eval()
   462  	eval1.Status = structs.EvalStatusBlocked
   463  
   464  	// Insert it into the state store
   465  	if err := s1.fsm.State().UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	// Enqueue the eval and then dequeue
   470  	s1.evalBroker.Enqueue(eval1)
   471  	evalOut, token, err := s1.evalBroker.Dequeue([]string{eval1.Type}, time.Second)
   472  	if err != nil {
   473  		t.Fatalf("err: %v", err)
   474  	}
   475  	if evalOut != eval1 {
   476  		t.Fatalf("Bad eval")
   477  	}
   478  
   479  	eval2 := evalOut.Copy()
   480  
   481  	// Attempt to reblock eval
   482  	w := &Worker{srv: s1, logger: s1.logger, evalToken: token}
   483  	err = w.ReblockEval(eval2)
   484  	if err != nil {
   485  		t.Fatalf("err: %v", err)
   486  	}
   487  
   488  	// Ack the eval
   489  	w.sendAck(evalOut.ID, token, true)
   490  
   491  	// Check that it is blocked
   492  	bStats := s1.blockedEvals.Stats()
   493  	if bStats.TotalBlocked+bStats.TotalEscaped != 1 {
   494  		t.Fatalf("ReblockEval didn't insert eval into the blocked eval tracker: %#v", bStats)
   495  	}
   496  
   497  	// Check that the snapshot index was set properly by unblocking the eval and
   498  	// then dequeuing.
   499  	s1.blockedEvals.Unblock("foobar", 1000)
   500  
   501  	reblockedEval, _, err := s1.evalBroker.Dequeue([]string{eval1.Type}, 1*time.Second)
   502  	if err != nil {
   503  		t.Fatalf("err: %v", err)
   504  	}
   505  	if reblockedEval == nil {
   506  		t.Fatalf("Nil eval")
   507  	}
   508  	if reblockedEval.ID != eval1.ID {
   509  		t.Fatalf("Bad eval")
   510  	}
   511  
   512  	// Check that the SnapshotIndex is set
   513  	if reblockedEval.SnapshotIndex != w.snapshotIndex {
   514  		t.Fatalf("incorrect snapshot index; got %d; want %d",
   515  			reblockedEval.SnapshotIndex, w.snapshotIndex)
   516  	}
   517  }