github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/scheduler/system_sched_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/hashicorp/nomad/nomad/mock"
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  func TestSystemSched_JobRegister(t *testing.T) {
    12  	h := NewHarness(t)
    13  
    14  	// Create some nodes
    15  	for i := 0; i < 10; i++ {
    16  		node := mock.Node()
    17  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
    18  	}
    19  
    20  	// Create a job
    21  	job := mock.SystemJob()
    22  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
    23  
    24  	// Create a mock evaluation to deregister the job
    25  	eval := &structs.Evaluation{
    26  		ID:          structs.GenerateUUID(),
    27  		Priority:    job.Priority,
    28  		TriggeredBy: structs.EvalTriggerJobRegister,
    29  		JobID:       job.ID,
    30  	}
    31  
    32  	// Process the evaluation
    33  	err := h.Process(NewSystemScheduler, eval)
    34  	if err != nil {
    35  		t.Fatalf("err: %v", err)
    36  	}
    37  
    38  	// Ensure a single plan
    39  	if len(h.Plans) != 1 {
    40  		t.Fatalf("bad: %#v", h.Plans)
    41  	}
    42  	plan := h.Plans[0]
    43  
    44  	// Ensure the plan allocated
    45  	var planned []*structs.Allocation
    46  	for _, allocList := range plan.NodeAllocation {
    47  		planned = append(planned, allocList...)
    48  	}
    49  	if len(planned) != 10 {
    50  		t.Fatalf("bad: %#v", plan)
    51  	}
    52  
    53  	// Lookup the allocations by JobID
    54  	out, err := h.State.AllocsByJob(job.ID)
    55  	noErr(t, err)
    56  
    57  	// Ensure all allocations placed
    58  	if len(out) != 10 {
    59  		t.Fatalf("bad: %#v", out)
    60  	}
    61  
    62  	// Check the available nodes
    63  	if count, ok := out[0].Metrics.NodesAvailable["dc1"]; !ok || count != 10 {
    64  		t.Fatalf("bad: %#v", out[0].Metrics)
    65  	}
    66  
    67  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
    68  }
    69  
    70  func TestSystemSched_JobRegister_AddNode(t *testing.T) {
    71  	h := NewHarness(t)
    72  
    73  	// Create some nodes
    74  	var nodes []*structs.Node
    75  	for i := 0; i < 10; i++ {
    76  		node := mock.Node()
    77  		nodes = append(nodes, node)
    78  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
    79  	}
    80  
    81  	// Generate a fake job with allocations
    82  	job := mock.SystemJob()
    83  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
    84  
    85  	var allocs []*structs.Allocation
    86  	for _, node := range nodes {
    87  		alloc := mock.Alloc()
    88  		alloc.Job = job
    89  		alloc.JobID = job.ID
    90  		alloc.NodeID = node.ID
    91  		alloc.Name = "my-job.web[0]"
    92  		allocs = append(allocs, alloc)
    93  	}
    94  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
    95  
    96  	// Add a new node.
    97  	node := mock.Node()
    98  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
    99  
   100  	// Create a mock evaluation to deal with the node update
   101  	eval := &structs.Evaluation{
   102  		ID:          structs.GenerateUUID(),
   103  		Priority:    50,
   104  		TriggeredBy: structs.EvalTriggerNodeUpdate,
   105  		JobID:       job.ID,
   106  	}
   107  
   108  	// Process the evaluation
   109  	err := h.Process(NewSystemScheduler, eval)
   110  	if err != nil {
   111  		t.Fatalf("err: %v", err)
   112  	}
   113  
   114  	// Ensure a single plan
   115  	if len(h.Plans) != 1 {
   116  		t.Fatalf("bad: %#v", h.Plans)
   117  	}
   118  	plan := h.Plans[0]
   119  
   120  	// Ensure the plan had no node updates
   121  	var update []*structs.Allocation
   122  	for _, updateList := range plan.NodeUpdate {
   123  		update = append(update, updateList...)
   124  	}
   125  	if len(update) != 0 {
   126  		t.Log(len(update))
   127  		t.Fatalf("bad: %#v", plan)
   128  	}
   129  
   130  	// Ensure the plan allocated on the new node
   131  	var planned []*structs.Allocation
   132  	for _, allocList := range plan.NodeAllocation {
   133  		planned = append(planned, allocList...)
   134  	}
   135  	if len(planned) != 1 {
   136  		t.Fatalf("bad: %#v", plan)
   137  	}
   138  
   139  	// Ensure it allocated on the right node
   140  	if _, ok := plan.NodeAllocation[node.ID]; !ok {
   141  		t.Fatalf("allocated on wrong node: %#v", plan)
   142  	}
   143  
   144  	// Lookup the allocations by JobID
   145  	out, err := h.State.AllocsByJob(job.ID)
   146  	noErr(t, err)
   147  
   148  	// Ensure all allocations placed
   149  	out = structs.FilterTerminalAllocs(out)
   150  	if len(out) != 11 {
   151  		t.Fatalf("bad: %#v", out)
   152  	}
   153  
   154  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   155  }
   156  
   157  func TestSystemSched_JobRegister_AllocFail(t *testing.T) {
   158  	h := NewHarness(t)
   159  
   160  	// Create NO nodes
   161  	// Create a job
   162  	job := mock.SystemJob()
   163  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   164  
   165  	// Create a mock evaluation to register the job
   166  	eval := &structs.Evaluation{
   167  		ID:          structs.GenerateUUID(),
   168  		Priority:    job.Priority,
   169  		TriggeredBy: structs.EvalTriggerJobRegister,
   170  		JobID:       job.ID,
   171  	}
   172  
   173  	// Process the evaluation
   174  	err := h.Process(NewSystemScheduler, eval)
   175  	if err != nil {
   176  		t.Fatalf("err: %v", err)
   177  	}
   178  
   179  	// Ensure no plan as this should be a no-op.
   180  	if len(h.Plans) != 0 {
   181  		t.Fatalf("bad: %#v", h.Plans)
   182  	}
   183  
   184  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   185  }
   186  
   187  func TestSystemSched_JobModify(t *testing.T) {
   188  	h := NewHarness(t)
   189  
   190  	// Create some nodes
   191  	var nodes []*structs.Node
   192  	for i := 0; i < 10; i++ {
   193  		node := mock.Node()
   194  		nodes = append(nodes, node)
   195  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   196  	}
   197  
   198  	// Generate a fake job with allocations
   199  	job := mock.SystemJob()
   200  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   201  
   202  	var allocs []*structs.Allocation
   203  	for _, node := range nodes {
   204  		alloc := mock.Alloc()
   205  		alloc.Job = job
   206  		alloc.JobID = job.ID
   207  		alloc.NodeID = node.ID
   208  		alloc.Name = "my-job.web[0]"
   209  		allocs = append(allocs, alloc)
   210  	}
   211  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   212  
   213  	// Add a few terminal status allocations, these should be ignored
   214  	var terminal []*structs.Allocation
   215  	for i := 0; i < 5; i++ {
   216  		alloc := mock.Alloc()
   217  		alloc.Job = job
   218  		alloc.JobID = job.ID
   219  		alloc.NodeID = nodes[i].ID
   220  		alloc.Name = "my-job.web[0]"
   221  		alloc.DesiredStatus = structs.AllocDesiredStatusFailed
   222  		terminal = append(terminal, alloc)
   223  	}
   224  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal))
   225  
   226  	// Update the job
   227  	job2 := mock.SystemJob()
   228  	job2.ID = job.ID
   229  
   230  	// Update the task, such that it cannot be done in-place
   231  	job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   232  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   233  
   234  	// Create a mock evaluation to deal with drain
   235  	eval := &structs.Evaluation{
   236  		ID:          structs.GenerateUUID(),
   237  		Priority:    50,
   238  		TriggeredBy: structs.EvalTriggerJobRegister,
   239  		JobID:       job.ID,
   240  	}
   241  
   242  	// Process the evaluation
   243  	err := h.Process(NewSystemScheduler, eval)
   244  	if err != nil {
   245  		t.Fatalf("err: %v", err)
   246  	}
   247  
   248  	// Ensure a single plan
   249  	if len(h.Plans) != 1 {
   250  		t.Fatalf("bad: %#v", h.Plans)
   251  	}
   252  	plan := h.Plans[0]
   253  
   254  	// Ensure the plan evicted all allocs
   255  	var update []*structs.Allocation
   256  	for _, updateList := range plan.NodeUpdate {
   257  		update = append(update, updateList...)
   258  	}
   259  	if len(update) != len(allocs) {
   260  		t.Fatalf("bad: %#v", plan)
   261  	}
   262  
   263  	// Ensure the plan allocated
   264  	var planned []*structs.Allocation
   265  	for _, allocList := range plan.NodeAllocation {
   266  		planned = append(planned, allocList...)
   267  	}
   268  	if len(planned) != 10 {
   269  		t.Fatalf("bad: %#v", plan)
   270  	}
   271  
   272  	// Lookup the allocations by JobID
   273  	out, err := h.State.AllocsByJob(job.ID)
   274  	noErr(t, err)
   275  
   276  	// Ensure all allocations placed
   277  	out = structs.FilterTerminalAllocs(out)
   278  	if len(out) != 10 {
   279  		t.Fatalf("bad: %#v", out)
   280  	}
   281  
   282  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   283  }
   284  
   285  func TestSystemSched_JobModify_Rolling(t *testing.T) {
   286  	h := NewHarness(t)
   287  
   288  	// Create some nodes
   289  	var nodes []*structs.Node
   290  	for i := 0; i < 10; i++ {
   291  		node := mock.Node()
   292  		nodes = append(nodes, node)
   293  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   294  	}
   295  
   296  	// Generate a fake job with allocations
   297  	job := mock.SystemJob()
   298  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   299  
   300  	var allocs []*structs.Allocation
   301  	for _, node := range nodes {
   302  		alloc := mock.Alloc()
   303  		alloc.Job = job
   304  		alloc.JobID = job.ID
   305  		alloc.NodeID = node.ID
   306  		alloc.Name = "my-job.web[0]"
   307  		allocs = append(allocs, alloc)
   308  	}
   309  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   310  
   311  	// Update the job
   312  	job2 := mock.SystemJob()
   313  	job2.ID = job.ID
   314  	job2.Update = structs.UpdateStrategy{
   315  		Stagger:     30 * time.Second,
   316  		MaxParallel: 5,
   317  	}
   318  
   319  	// Update the task, such that it cannot be done in-place
   320  	job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   321  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   322  
   323  	// Create a mock evaluation to deal with drain
   324  	eval := &structs.Evaluation{
   325  		ID:          structs.GenerateUUID(),
   326  		Priority:    50,
   327  		TriggeredBy: structs.EvalTriggerJobRegister,
   328  		JobID:       job.ID,
   329  	}
   330  
   331  	// Process the evaluation
   332  	err := h.Process(NewSystemScheduler, eval)
   333  	if err != nil {
   334  		t.Fatalf("err: %v", err)
   335  	}
   336  
   337  	// Ensure a single plan
   338  	if len(h.Plans) != 1 {
   339  		t.Fatalf("bad: %#v", h.Plans)
   340  	}
   341  	plan := h.Plans[0]
   342  
   343  	// Ensure the plan evicted only MaxParallel
   344  	var update []*structs.Allocation
   345  	for _, updateList := range plan.NodeUpdate {
   346  		update = append(update, updateList...)
   347  	}
   348  	if len(update) != job2.Update.MaxParallel {
   349  		t.Fatalf("bad: %#v", plan)
   350  	}
   351  
   352  	// Ensure the plan allocated
   353  	var planned []*structs.Allocation
   354  	for _, allocList := range plan.NodeAllocation {
   355  		planned = append(planned, allocList...)
   356  	}
   357  	if len(planned) != job2.Update.MaxParallel {
   358  		t.Fatalf("bad: %#v", plan)
   359  	}
   360  
   361  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   362  
   363  	// Ensure a follow up eval was created
   364  	eval = h.Evals[0]
   365  	if eval.NextEval == "" {
   366  		t.Fatalf("missing next eval")
   367  	}
   368  
   369  	// Check for create
   370  	if len(h.CreateEvals) == 0 {
   371  		t.Fatalf("missing created eval")
   372  	}
   373  	create := h.CreateEvals[0]
   374  	if eval.NextEval != create.ID {
   375  		t.Fatalf("ID mismatch")
   376  	}
   377  	if create.PreviousEval != eval.ID {
   378  		t.Fatalf("missing previous eval")
   379  	}
   380  
   381  	if create.TriggeredBy != structs.EvalTriggerRollingUpdate {
   382  		t.Fatalf("bad: %#v", create)
   383  	}
   384  }
   385  
   386  func TestSystemSched_JobModify_InPlace(t *testing.T) {
   387  	h := NewHarness(t)
   388  
   389  	// Create some nodes
   390  	var nodes []*structs.Node
   391  	for i := 0; i < 10; i++ {
   392  		node := mock.Node()
   393  		nodes = append(nodes, node)
   394  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   395  	}
   396  
   397  	// Generate a fake job with allocations
   398  	job := mock.SystemJob()
   399  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   400  
   401  	var allocs []*structs.Allocation
   402  	for _, node := range nodes {
   403  		alloc := mock.Alloc()
   404  		alloc.Job = job
   405  		alloc.JobID = job.ID
   406  		alloc.NodeID = node.ID
   407  		alloc.Name = "my-job.web[0]"
   408  		allocs = append(allocs, alloc)
   409  	}
   410  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   411  
   412  	// Update the job
   413  	job2 := mock.SystemJob()
   414  	job2.ID = job.ID
   415  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   416  
   417  	// Create a mock evaluation to deal with drain
   418  	eval := &structs.Evaluation{
   419  		ID:          structs.GenerateUUID(),
   420  		Priority:    50,
   421  		TriggeredBy: structs.EvalTriggerJobRegister,
   422  		JobID:       job.ID,
   423  	}
   424  
   425  	// Process the evaluation
   426  	err := h.Process(NewSystemScheduler, eval)
   427  	if err != nil {
   428  		t.Fatalf("err: %v", err)
   429  	}
   430  
   431  	// Ensure a single plan
   432  	if len(h.Plans) != 1 {
   433  		t.Fatalf("bad: %#v", h.Plans)
   434  	}
   435  	plan := h.Plans[0]
   436  
   437  	// Ensure the plan did not evict any allocs
   438  	var update []*structs.Allocation
   439  	for _, updateList := range plan.NodeUpdate {
   440  		update = append(update, updateList...)
   441  	}
   442  	if len(update) != 0 {
   443  		t.Fatalf("bad: %#v", plan)
   444  	}
   445  
   446  	// Ensure the plan updated the existing allocs
   447  	var planned []*structs.Allocation
   448  	for _, allocList := range plan.NodeAllocation {
   449  		planned = append(planned, allocList...)
   450  	}
   451  	if len(planned) != 10 {
   452  		t.Fatalf("bad: %#v", plan)
   453  	}
   454  	for _, p := range planned {
   455  		if p.Job != job2 {
   456  			t.Fatalf("should update job")
   457  		}
   458  	}
   459  
   460  	// Lookup the allocations by JobID
   461  	out, err := h.State.AllocsByJob(job.ID)
   462  	noErr(t, err)
   463  
   464  	// Ensure all allocations placed
   465  	if len(out) != 10 {
   466  		t.Fatalf("bad: %#v", out)
   467  	}
   468  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   469  
   470  	// Verify the network did not change
   471  	rp := structs.Port{"main", 5000}
   472  	for _, alloc := range out {
   473  		for _, resources := range alloc.TaskResources {
   474  			if resources.Networks[0].ReservedPorts[0] != rp {
   475  				t.Fatalf("bad: %#v", alloc)
   476  			}
   477  		}
   478  	}
   479  }
   480  
   481  func TestSystemSched_JobDeregister(t *testing.T) {
   482  	h := NewHarness(t)
   483  
   484  	// Create some nodes
   485  	var nodes []*structs.Node
   486  	for i := 0; i < 10; i++ {
   487  		node := mock.Node()
   488  		nodes = append(nodes, node)
   489  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   490  	}
   491  
   492  	// Generate a fake job with allocations
   493  	job := mock.SystemJob()
   494  
   495  	var allocs []*structs.Allocation
   496  	for _, node := range nodes {
   497  		alloc := mock.Alloc()
   498  		alloc.Job = job
   499  		alloc.JobID = job.ID
   500  		alloc.NodeID = node.ID
   501  		alloc.Name = "my-job.web[0]"
   502  		allocs = append(allocs, alloc)
   503  	}
   504  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   505  
   506  	// Create a mock evaluation to deregister the job
   507  	eval := &structs.Evaluation{
   508  		ID:          structs.GenerateUUID(),
   509  		Priority:    50,
   510  		TriggeredBy: structs.EvalTriggerJobDeregister,
   511  		JobID:       job.ID,
   512  	}
   513  
   514  	// Process the evaluation
   515  	err := h.Process(NewSystemScheduler, eval)
   516  	if err != nil {
   517  		t.Fatalf("err: %v", err)
   518  	}
   519  
   520  	// Ensure a single plan
   521  	if len(h.Plans) != 1 {
   522  		t.Fatalf("bad: %#v", h.Plans)
   523  	}
   524  	plan := h.Plans[0]
   525  
   526  	// Ensure the plan evicted the job from all nodes.
   527  	for _, node := range nodes {
   528  		if len(plan.NodeUpdate[node.ID]) != 1 {
   529  			t.Fatalf("bad: %#v", plan)
   530  		}
   531  	}
   532  
   533  	// Lookup the allocations by JobID
   534  	out, err := h.State.AllocsByJob(job.ID)
   535  	noErr(t, err)
   536  
   537  	// Ensure no remaining allocations
   538  	out = structs.FilterTerminalAllocs(out)
   539  	if len(out) != 0 {
   540  		t.Fatalf("bad: %#v", out)
   541  	}
   542  
   543  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   544  }
   545  
   546  func TestSystemSched_NodeDrain(t *testing.T) {
   547  	h := NewHarness(t)
   548  
   549  	// Register a draining node
   550  	node := mock.Node()
   551  	node.Drain = true
   552  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   553  
   554  	// Generate a fake job allocated on that node.
   555  	job := mock.SystemJob()
   556  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   557  
   558  	alloc := mock.Alloc()
   559  	alloc.Job = job
   560  	alloc.JobID = job.ID
   561  	alloc.NodeID = node.ID
   562  	alloc.Name = "my-job.web[0]"
   563  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc}))
   564  
   565  	// Create a mock evaluation to deal with drain
   566  	eval := &structs.Evaluation{
   567  		ID:          structs.GenerateUUID(),
   568  		Priority:    50,
   569  		TriggeredBy: structs.EvalTriggerNodeUpdate,
   570  		JobID:       job.ID,
   571  		NodeID:      node.ID,
   572  	}
   573  
   574  	// Process the evaluation
   575  	err := h.Process(NewSystemScheduler, eval)
   576  	if err != nil {
   577  		t.Fatalf("err: %v", err)
   578  	}
   579  
   580  	// Ensure a single plan
   581  	if len(h.Plans) != 1 {
   582  		t.Fatalf("bad: %#v", h.Plans)
   583  	}
   584  	plan := h.Plans[0]
   585  
   586  	// Ensure the plan evicted all allocs
   587  	if len(plan.NodeUpdate[node.ID]) != 1 {
   588  		t.Fatalf("bad: %#v", plan)
   589  	}
   590  
   591  	// Ensure the plan updated the allocation.
   592  	var planned []*structs.Allocation
   593  	for _, allocList := range plan.NodeUpdate {
   594  		planned = append(planned, allocList...)
   595  	}
   596  	if len(planned) != 1 {
   597  		t.Log(len(planned))
   598  		t.Fatalf("bad: %#v", plan)
   599  	}
   600  
   601  	// Lookup the allocations by JobID
   602  	out, err := h.State.AllocsByJob(job.ID)
   603  	noErr(t, err)
   604  
   605  	// Ensure the allocations is stopped
   606  	if planned[0].DesiredStatus != structs.AllocDesiredStatusStop {
   607  		t.Fatalf("bad: %#v", out)
   608  	}
   609  
   610  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   611  }
   612  
   613  func TestSystemSched_RetryLimit(t *testing.T) {
   614  	h := NewHarness(t)
   615  	h.Planner = &RejectPlan{h}
   616  
   617  	// Create some nodes
   618  	for i := 0; i < 10; i++ {
   619  		node := mock.Node()
   620  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   621  	}
   622  
   623  	// Create a job
   624  	job := mock.SystemJob()
   625  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   626  
   627  	// Create a mock evaluation to deregister the job
   628  	eval := &structs.Evaluation{
   629  		ID:          structs.GenerateUUID(),
   630  		Priority:    job.Priority,
   631  		TriggeredBy: structs.EvalTriggerJobRegister,
   632  		JobID:       job.ID,
   633  	}
   634  
   635  	// Process the evaluation
   636  	err := h.Process(NewSystemScheduler, eval)
   637  	if err != nil {
   638  		t.Fatalf("err: %v", err)
   639  	}
   640  
   641  	// Ensure multiple plans
   642  	if len(h.Plans) == 0 {
   643  		t.Fatalf("bad: %#v", h.Plans)
   644  	}
   645  
   646  	// Lookup the allocations by JobID
   647  	out, err := h.State.AllocsByJob(job.ID)
   648  	noErr(t, err)
   649  
   650  	// Ensure no allocations placed
   651  	if len(out) != 0 {
   652  		t.Fatalf("bad: %#v", out)
   653  	}
   654  
   655  	// Should hit the retry limit
   656  	h.AssertEvalStatus(t, structs.EvalStatusFailed)
   657  }