github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/scheduler/generic_sched_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/nomad/mock"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  )
    12  
    13  func TestServiceSched_JobRegister(t *testing.T) {
    14  	h := NewHarness(t)
    15  
    16  	// Create some nodes
    17  	for i := 0; i < 10; i++ {
    18  		node := mock.Node()
    19  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
    20  	}
    21  
    22  	// Create a job
    23  	job := mock.Job()
    24  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
    25  
    26  	// Create a mock evaluation to register the job
    27  	eval := &structs.Evaluation{
    28  		ID:          structs.GenerateUUID(),
    29  		Priority:    job.Priority,
    30  		TriggeredBy: structs.EvalTriggerJobRegister,
    31  		JobID:       job.ID,
    32  	}
    33  
    34  	// Process the evaluation
    35  	err := h.Process(NewServiceScheduler, eval)
    36  	if err != nil {
    37  		t.Fatalf("err: %v", err)
    38  	}
    39  
    40  	// Ensure a single plan
    41  	if len(h.Plans) != 1 {
    42  		t.Fatalf("bad: %#v", h.Plans)
    43  	}
    44  	plan := h.Plans[0]
    45  
    46  	// Ensure the plan doesn't have annotations.
    47  	if plan.Annotations != nil {
    48  		t.Fatalf("expected no annotations")
    49  	}
    50  
    51  	// Ensure the eval has no spawned blocked eval
    52  	if len(h.Evals) != 1 {
    53  		t.Fatalf("bad: %#v", h.Evals)
    54  		if h.Evals[0].BlockedEval != "" {
    55  			t.Fatalf("bad: %#v", h.Evals[0])
    56  		}
    57  	}
    58  
    59  	// Ensure the plan allocated
    60  	var planned []*structs.Allocation
    61  	for _, allocList := range plan.NodeAllocation {
    62  		planned = append(planned, allocList...)
    63  	}
    64  	if len(planned) != 10 {
    65  		t.Fatalf("bad: %#v", plan)
    66  	}
    67  
    68  	// Lookup the allocations by JobID
    69  	out, err := h.State.AllocsByJob(job.ID)
    70  	noErr(t, err)
    71  
    72  	// Ensure all allocations placed
    73  	if len(out) != 10 {
    74  		t.Fatalf("bad: %#v", out)
    75  	}
    76  
    77  	// Ensure different ports were used.
    78  	used := make(map[int]struct{})
    79  	for _, alloc := range out {
    80  		for _, resource := range alloc.TaskResources {
    81  			for _, port := range resource.Networks[0].DynamicPorts {
    82  				if _, ok := used[port.Value]; ok {
    83  					t.Fatalf("Port collision %v", port.Value)
    84  				}
    85  				used[port.Value] = struct{}{}
    86  			}
    87  		}
    88  	}
    89  
    90  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
    91  }
    92  
    93  func TestServiceSched_JobRegister_Annotate(t *testing.T) {
    94  	h := NewHarness(t)
    95  
    96  	// Create some nodes
    97  	for i := 0; i < 10; i++ {
    98  		node := mock.Node()
    99  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   100  	}
   101  
   102  	// Create a job
   103  	job := mock.Job()
   104  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   105  
   106  	// Create a mock evaluation to register the job
   107  	eval := &structs.Evaluation{
   108  		ID:           structs.GenerateUUID(),
   109  		Priority:     job.Priority,
   110  		TriggeredBy:  structs.EvalTriggerJobRegister,
   111  		JobID:        job.ID,
   112  		AnnotatePlan: true,
   113  	}
   114  
   115  	// Process the evaluation
   116  	err := h.Process(NewServiceScheduler, eval)
   117  	if err != nil {
   118  		t.Fatalf("err: %v", err)
   119  	}
   120  
   121  	// Ensure a single plan
   122  	if len(h.Plans) != 1 {
   123  		t.Fatalf("bad: %#v", h.Plans)
   124  	}
   125  	plan := h.Plans[0]
   126  
   127  	// Ensure the plan allocated
   128  	var planned []*structs.Allocation
   129  	for _, allocList := range plan.NodeAllocation {
   130  		planned = append(planned, allocList...)
   131  	}
   132  	if len(planned) != 10 {
   133  		t.Fatalf("bad: %#v", plan)
   134  	}
   135  
   136  	// Lookup the allocations by JobID
   137  	out, err := h.State.AllocsByJob(job.ID)
   138  	noErr(t, err)
   139  
   140  	// Ensure all allocations placed
   141  	if len(out) != 10 {
   142  		t.Fatalf("bad: %#v", out)
   143  	}
   144  
   145  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   146  
   147  	// Ensure the plan had annotations.
   148  	if plan.Annotations == nil {
   149  		t.Fatalf("expected annotations")
   150  	}
   151  
   152  	desiredTGs := plan.Annotations.DesiredTGUpdates
   153  	if l := len(desiredTGs); l != 1 {
   154  		t.Fatalf("incorrect number of task groups; got %v; want %v", l, 1)
   155  	}
   156  
   157  	desiredChanges, ok := desiredTGs["web"]
   158  	if !ok {
   159  		t.Fatalf("expected task group web to have desired changes")
   160  	}
   161  
   162  	expected := &structs.DesiredUpdates{Place: 10}
   163  	if !reflect.DeepEqual(desiredChanges, expected) {
   164  		t.Fatalf("Unexpected desired updates; got %#v; want %#v", desiredChanges, expected)
   165  	}
   166  }
   167  
   168  func TestServiceSched_JobRegister_CountZero(t *testing.T) {
   169  	h := NewHarness(t)
   170  
   171  	// Create some nodes
   172  	for i := 0; i < 10; i++ {
   173  		node := mock.Node()
   174  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   175  	}
   176  
   177  	// Create a job and set the task group count to zero.
   178  	job := mock.Job()
   179  	job.TaskGroups[0].Count = 0
   180  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   181  
   182  	// Create a mock evaluation to register the job
   183  	eval := &structs.Evaluation{
   184  		ID:          structs.GenerateUUID(),
   185  		Priority:    job.Priority,
   186  		TriggeredBy: structs.EvalTriggerJobRegister,
   187  		JobID:       job.ID,
   188  	}
   189  
   190  	// Process the evaluation
   191  	err := h.Process(NewServiceScheduler, eval)
   192  	if err != nil {
   193  		t.Fatalf("err: %v", err)
   194  	}
   195  
   196  	// Ensure there was no plan
   197  	if len(h.Plans) != 0 {
   198  		t.Fatalf("bad: %#v", h.Plans)
   199  	}
   200  
   201  	// Lookup the allocations by JobID
   202  	out, err := h.State.AllocsByJob(job.ID)
   203  	noErr(t, err)
   204  
   205  	// Ensure no allocations placed
   206  	if len(out) != 0 {
   207  		t.Fatalf("bad: %#v", out)
   208  	}
   209  
   210  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   211  }
   212  
   213  func TestServiceSched_JobRegister_AllocFail(t *testing.T) {
   214  	h := NewHarness(t)
   215  
   216  	// Create NO nodes
   217  	// Create a job
   218  	job := mock.Job()
   219  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   220  
   221  	// Create a mock evaluation to register the job
   222  	eval := &structs.Evaluation{
   223  		ID:          structs.GenerateUUID(),
   224  		Priority:    job.Priority,
   225  		TriggeredBy: structs.EvalTriggerJobRegister,
   226  		JobID:       job.ID,
   227  	}
   228  
   229  	// Process the evaluation
   230  	err := h.Process(NewServiceScheduler, eval)
   231  	if err != nil {
   232  		t.Fatalf("err: %v", err)
   233  	}
   234  
   235  	// Ensure no plan
   236  	if len(h.Plans) != 0 {
   237  		t.Fatalf("bad: %#v", h.Plans)
   238  	}
   239  
   240  	// Ensure there is a follow up eval.
   241  	if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked {
   242  		t.Fatalf("bad: %#v", h.CreateEvals)
   243  	}
   244  
   245  	if len(h.Evals) != 1 {
   246  		t.Fatalf("incorrect number of updated eval: %#v", h.Evals)
   247  	}
   248  	outEval := h.Evals[0]
   249  
   250  	// Ensure the eval has its spawned blocked eval
   251  	if outEval.BlockedEval != h.CreateEvals[0].ID {
   252  		t.Fatalf("bad: %#v", outEval)
   253  	}
   254  
   255  	// Ensure the plan failed to alloc
   256  	if outEval == nil || len(outEval.FailedTGAllocs) != 1 {
   257  		t.Fatalf("bad: %#v", outEval)
   258  	}
   259  
   260  	metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name]
   261  	if !ok {
   262  		t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs)
   263  	}
   264  
   265  	// Check the coalesced failures
   266  	if metrics.CoalescedFailures != 9 {
   267  		t.Fatalf("bad: %#v", metrics)
   268  	}
   269  
   270  	// Check the available nodes
   271  	if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 0 {
   272  		t.Fatalf("bad: %#v", metrics)
   273  	}
   274  
   275  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   276  }
   277  
   278  func TestServiceSched_JobRegister_CreateBlockedEval(t *testing.T) {
   279  	h := NewHarness(t)
   280  
   281  	// Create a full node
   282  	node := mock.Node()
   283  	node.Reserved = node.Resources
   284  	node.ComputeClass()
   285  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   286  
   287  	// Create an ineligible node
   288  	node2 := mock.Node()
   289  	node2.Attributes["kernel.name"] = "windows"
   290  	node2.ComputeClass()
   291  	noErr(t, h.State.UpsertNode(h.NextIndex(), node2))
   292  
   293  	// Create a jobs
   294  	job := mock.Job()
   295  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   296  
   297  	// Create a mock evaluation to register the job
   298  	eval := &structs.Evaluation{
   299  		ID:          structs.GenerateUUID(),
   300  		Priority:    job.Priority,
   301  		TriggeredBy: structs.EvalTriggerJobRegister,
   302  		JobID:       job.ID,
   303  	}
   304  
   305  	// Process the evaluation
   306  	err := h.Process(NewServiceScheduler, eval)
   307  	if err != nil {
   308  		t.Fatalf("err: %v", err)
   309  	}
   310  
   311  	// Ensure no plan
   312  	if len(h.Plans) != 0 {
   313  		t.Fatalf("bad: %#v", h.Plans)
   314  	}
   315  
   316  	// Ensure the plan has created a follow up eval.
   317  	if len(h.CreateEvals) != 1 {
   318  		t.Fatalf("bad: %#v", h.CreateEvals)
   319  	}
   320  
   321  	created := h.CreateEvals[0]
   322  	if created.Status != structs.EvalStatusBlocked {
   323  		t.Fatalf("bad: %#v", created)
   324  	}
   325  
   326  	classes := created.ClassEligibility
   327  	if len(classes) != 2 || !classes[node.ComputedClass] || classes[node2.ComputedClass] {
   328  		t.Fatalf("bad: %#v", classes)
   329  	}
   330  
   331  	if created.EscapedComputedClass {
   332  		t.Fatalf("bad: %#v", created)
   333  	}
   334  
   335  	// Ensure there is a follow up eval.
   336  	if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked {
   337  		t.Fatalf("bad: %#v", h.CreateEvals)
   338  	}
   339  
   340  	if len(h.Evals) != 1 {
   341  		t.Fatalf("incorrect number of updated eval: %#v", h.Evals)
   342  	}
   343  	outEval := h.Evals[0]
   344  
   345  	// Ensure the plan failed to alloc
   346  	if outEval == nil || len(outEval.FailedTGAllocs) != 1 {
   347  		t.Fatalf("bad: %#v", outEval)
   348  	}
   349  
   350  	metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name]
   351  	if !ok {
   352  		t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs)
   353  	}
   354  
   355  	// Check the coalesced failures
   356  	if metrics.CoalescedFailures != 9 {
   357  		t.Fatalf("bad: %#v", metrics)
   358  	}
   359  
   360  	// Check the available nodes
   361  	if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 2 {
   362  		t.Fatalf("bad: %#v", metrics)
   363  	}
   364  
   365  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   366  }
   367  
   368  func TestServiceSched_JobRegister_FeasibleAndInfeasibleTG(t *testing.T) {
   369  	h := NewHarness(t)
   370  
   371  	// Create one node
   372  	node := mock.Node()
   373  	node.NodeClass = "class_0"
   374  	noErr(t, node.ComputeClass())
   375  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   376  
   377  	// Create a job that constrains on a node class
   378  	job := mock.Job()
   379  	job.TaskGroups[0].Count = 2
   380  	job.TaskGroups[0].Constraints = append(job.Constraints,
   381  		&structs.Constraint{
   382  			LTarget: "${node.class}",
   383  			RTarget: "class_0",
   384  			Operand: "=",
   385  		},
   386  	)
   387  	tg2 := job.TaskGroups[0].Copy()
   388  	tg2.Name = "web2"
   389  	tg2.Constraints[1].RTarget = "class_1"
   390  	job.TaskGroups = append(job.TaskGroups, tg2)
   391  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   392  
   393  	// Create a mock evaluation to register the job
   394  	eval := &structs.Evaluation{
   395  		ID:          structs.GenerateUUID(),
   396  		Priority:    job.Priority,
   397  		TriggeredBy: structs.EvalTriggerJobRegister,
   398  		JobID:       job.ID,
   399  	}
   400  
   401  	// Process the evaluation
   402  	err := h.Process(NewServiceScheduler, eval)
   403  	if err != nil {
   404  		t.Fatalf("err: %v", err)
   405  	}
   406  
   407  	// Ensure a single plan
   408  	if len(h.Plans) != 1 {
   409  		t.Fatalf("bad: %#v", h.Plans)
   410  	}
   411  	plan := h.Plans[0]
   412  
   413  	// Ensure the plan allocated
   414  	var planned []*structs.Allocation
   415  	for _, allocList := range plan.NodeAllocation {
   416  		planned = append(planned, allocList...)
   417  	}
   418  	if len(planned) != 2 {
   419  		t.Fatalf("bad: %#v", plan)
   420  	}
   421  
   422  	// Ensure two allocations placed
   423  	out, err := h.State.AllocsByJob(job.ID)
   424  	noErr(t, err)
   425  	if len(out) != 2 {
   426  		t.Fatalf("bad: %#v", out)
   427  	}
   428  
   429  	if len(h.Evals) != 1 {
   430  		t.Fatalf("incorrect number of updated eval: %#v", h.Evals)
   431  	}
   432  	outEval := h.Evals[0]
   433  
   434  	// Ensure the eval has its spawned blocked eval
   435  	if outEval.BlockedEval != h.CreateEvals[0].ID {
   436  		t.Fatalf("bad: %#v", outEval)
   437  	}
   438  
   439  	// Ensure the plan failed to alloc one tg
   440  	if outEval == nil || len(outEval.FailedTGAllocs) != 1 {
   441  		t.Fatalf("bad: %#v", outEval)
   442  	}
   443  
   444  	metrics, ok := outEval.FailedTGAllocs[tg2.Name]
   445  	if !ok {
   446  		t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs)
   447  	}
   448  
   449  	// Check the coalesced failures
   450  	if metrics.CoalescedFailures != tg2.Count-1 {
   451  		t.Fatalf("bad: %#v", metrics)
   452  	}
   453  
   454  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   455  }
   456  
   457  func TestServiceSched_EvaluateBlockedEval(t *testing.T) {
   458  	h := NewHarness(t)
   459  
   460  	// Create a job and set the task group count to zero.
   461  	job := mock.Job()
   462  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   463  
   464  	// Create a mock blocked evaluation
   465  	eval := &structs.Evaluation{
   466  		ID:          structs.GenerateUUID(),
   467  		Status:      structs.EvalStatusBlocked,
   468  		Priority:    job.Priority,
   469  		TriggeredBy: structs.EvalTriggerJobRegister,
   470  		JobID:       job.ID,
   471  	}
   472  
   473  	// Insert it into the state store
   474  	noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval}))
   475  
   476  	// Process the evaluation
   477  	err := h.Process(NewServiceScheduler, eval)
   478  	if err != nil {
   479  		t.Fatalf("err: %v", err)
   480  	}
   481  
   482  	// Ensure there was no plan
   483  	if len(h.Plans) != 0 {
   484  		t.Fatalf("bad: %#v", h.Plans)
   485  	}
   486  
   487  	// Ensure that the eval was reblocked
   488  	if len(h.ReblockEvals) != 1 {
   489  		t.Fatalf("bad: %#v", h.ReblockEvals)
   490  	}
   491  	if h.ReblockEvals[0].ID != eval.ID {
   492  		t.Fatalf("expect same eval to be reblocked; got %q; want %q", h.ReblockEvals[0].ID, eval.ID)
   493  	}
   494  
   495  	// Ensure the eval status was not updated
   496  	if len(h.Evals) != 0 {
   497  		t.Fatalf("Existing eval should not have status set")
   498  	}
   499  }
   500  
   501  func TestServiceSched_EvaluateBlockedEval_Finished(t *testing.T) {
   502  	h := NewHarness(t)
   503  
   504  	// Create some nodes
   505  	for i := 0; i < 10; i++ {
   506  		node := mock.Node()
   507  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   508  	}
   509  
   510  	// Create a job and set the task group count to zero.
   511  	job := mock.Job()
   512  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   513  
   514  	// Create a mock blocked evaluation
   515  	eval := &structs.Evaluation{
   516  		ID:          structs.GenerateUUID(),
   517  		Status:      structs.EvalStatusBlocked,
   518  		Priority:    job.Priority,
   519  		TriggeredBy: structs.EvalTriggerJobRegister,
   520  		JobID:       job.ID,
   521  	}
   522  
   523  	// Insert it into the state store
   524  	noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval}))
   525  
   526  	// Process the evaluation
   527  	err := h.Process(NewServiceScheduler, eval)
   528  	if err != nil {
   529  		t.Fatalf("err: %v", err)
   530  	}
   531  
   532  	// Ensure a single plan
   533  	if len(h.Plans) != 1 {
   534  		t.Fatalf("bad: %#v", h.Plans)
   535  	}
   536  	plan := h.Plans[0]
   537  
   538  	// Ensure the plan doesn't have annotations.
   539  	if plan.Annotations != nil {
   540  		t.Fatalf("expected no annotations")
   541  	}
   542  
   543  	// Ensure the eval has no spawned blocked eval
   544  	if len(h.Evals) != 1 {
   545  		t.Fatalf("bad: %#v", h.Evals)
   546  		if h.Evals[0].BlockedEval != "" {
   547  			t.Fatalf("bad: %#v", h.Evals[0])
   548  		}
   549  	}
   550  
   551  	// Ensure the plan allocated
   552  	var planned []*structs.Allocation
   553  	for _, allocList := range plan.NodeAllocation {
   554  		planned = append(planned, allocList...)
   555  	}
   556  	if len(planned) != 10 {
   557  		t.Fatalf("bad: %#v", plan)
   558  	}
   559  
   560  	// Lookup the allocations by JobID
   561  	out, err := h.State.AllocsByJob(job.ID)
   562  	noErr(t, err)
   563  
   564  	// Ensure all allocations placed
   565  	if len(out) != 10 {
   566  		t.Fatalf("bad: %#v", out)
   567  	}
   568  
   569  	// Ensure the eval was not reblocked
   570  	if len(h.ReblockEvals) != 0 {
   571  		t.Fatalf("Existing eval should not have been reblocked as it placed all allocations")
   572  	}
   573  
   574  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   575  }
   576  
   577  func TestServiceSched_JobModify(t *testing.T) {
   578  	h := NewHarness(t)
   579  
   580  	// Create some nodes
   581  	var nodes []*structs.Node
   582  	for i := 0; i < 10; i++ {
   583  		node := mock.Node()
   584  		nodes = append(nodes, node)
   585  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   586  	}
   587  
   588  	// Generate a fake job with allocations
   589  	job := mock.Job()
   590  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   591  
   592  	var allocs []*structs.Allocation
   593  	for i := 0; i < 10; i++ {
   594  		alloc := mock.Alloc()
   595  		alloc.Job = job
   596  		alloc.JobID = job.ID
   597  		alloc.NodeID = nodes[i].ID
   598  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   599  		allocs = append(allocs, alloc)
   600  	}
   601  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   602  
   603  	// Add a few terminal status allocations, these should be ignored
   604  	var terminal []*structs.Allocation
   605  	for i := 0; i < 5; i++ {
   606  		alloc := mock.Alloc()
   607  		alloc.Job = job
   608  		alloc.JobID = job.ID
   609  		alloc.NodeID = nodes[i].ID
   610  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   611  		alloc.DesiredStatus = structs.AllocDesiredStatusFailed
   612  		terminal = append(terminal, alloc)
   613  	}
   614  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal))
   615  
   616  	// Update the job
   617  	job2 := mock.Job()
   618  	job2.ID = job.ID
   619  
   620  	// Update the task, such that it cannot be done in-place
   621  	job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   622  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   623  
   624  	// Create a mock evaluation to deal with drain
   625  	eval := &structs.Evaluation{
   626  		ID:          structs.GenerateUUID(),
   627  		Priority:    50,
   628  		TriggeredBy: structs.EvalTriggerJobRegister,
   629  		JobID:       job.ID,
   630  	}
   631  
   632  	// Process the evaluation
   633  	err := h.Process(NewServiceScheduler, eval)
   634  	if err != nil {
   635  		t.Fatalf("err: %v", err)
   636  	}
   637  
   638  	// Ensure a single plan
   639  	if len(h.Plans) != 1 {
   640  		t.Fatalf("bad: %#v", h.Plans)
   641  	}
   642  	plan := h.Plans[0]
   643  
   644  	// Ensure the plan evicted all allocs
   645  	var update []*structs.Allocation
   646  	for _, updateList := range plan.NodeUpdate {
   647  		update = append(update, updateList...)
   648  	}
   649  	if len(update) != len(allocs) {
   650  		t.Fatalf("bad: %#v", plan)
   651  	}
   652  
   653  	// Ensure the plan allocated
   654  	var planned []*structs.Allocation
   655  	for _, allocList := range plan.NodeAllocation {
   656  		planned = append(planned, allocList...)
   657  	}
   658  	if len(planned) != 10 {
   659  		t.Fatalf("bad: %#v", plan)
   660  	}
   661  
   662  	// Lookup the allocations by JobID
   663  	out, err := h.State.AllocsByJob(job.ID)
   664  	noErr(t, err)
   665  
   666  	// Ensure all allocations placed
   667  	out = structs.FilterTerminalAllocs(out)
   668  	if len(out) != 10 {
   669  		t.Fatalf("bad: %#v", out)
   670  	}
   671  
   672  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   673  }
   674  
   675  // Have a single node and submit a job. Increment the count such that all fit
   676  // on the node but the node doesn't have enough resources to fit the new count +
   677  // 1. This tests that we properly discount the resources of existing allocs.
   678  func TestServiceSched_JobModify_IncrCount_NodeLimit(t *testing.T) {
   679  	h := NewHarness(t)
   680  
   681  	// Create one node
   682  	node := mock.Node()
   683  	node.Resources.CPU = 1000
   684  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   685  
   686  	// Generate a fake job with one allocation
   687  	job := mock.Job()
   688  	job.TaskGroups[0].Tasks[0].Resources.CPU = 256
   689  	job2 := job.Copy()
   690  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   691  
   692  	var allocs []*structs.Allocation
   693  	alloc := mock.Alloc()
   694  	alloc.Job = job
   695  	alloc.JobID = job.ID
   696  	alloc.NodeID = node.ID
   697  	alloc.Name = "my-job.web[0]"
   698  	alloc.Resources.CPU = 256
   699  	allocs = append(allocs, alloc)
   700  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   701  
   702  	// Update the job to count 3
   703  	job2.TaskGroups[0].Count = 3
   704  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   705  
   706  	// Create a mock evaluation to deal with drain
   707  	eval := &structs.Evaluation{
   708  		ID:          structs.GenerateUUID(),
   709  		Priority:    50,
   710  		TriggeredBy: structs.EvalTriggerJobRegister,
   711  		JobID:       job.ID,
   712  	}
   713  
   714  	// Process the evaluation
   715  	err := h.Process(NewServiceScheduler, eval)
   716  	if err != nil {
   717  		t.Fatalf("err: %v", err)
   718  	}
   719  
   720  	// Ensure a single plan
   721  	if len(h.Plans) != 1 {
   722  		t.Fatalf("bad: %#v", h.Plans)
   723  	}
   724  	plan := h.Plans[0]
   725  
   726  	// Ensure the plan didn't evicted the alloc
   727  	var update []*structs.Allocation
   728  	for _, updateList := range plan.NodeUpdate {
   729  		update = append(update, updateList...)
   730  	}
   731  	if len(update) != 0 {
   732  		t.Fatalf("bad: %#v", plan)
   733  	}
   734  
   735  	// Ensure the plan allocated
   736  	var planned []*structs.Allocation
   737  	for _, allocList := range plan.NodeAllocation {
   738  		planned = append(planned, allocList...)
   739  	}
   740  	if len(planned) != 3 {
   741  		t.Fatalf("bad: %#v", plan)
   742  	}
   743  
   744  	// Ensure the plan had no failures
   745  	if len(h.Evals) != 1 {
   746  		t.Fatalf("incorrect number of updated eval: %#v", h.Evals)
   747  	}
   748  	outEval := h.Evals[0]
   749  	if outEval == nil || len(outEval.FailedTGAllocs) != 0 {
   750  		t.Fatalf("bad: %#v", outEval)
   751  	}
   752  
   753  	// Lookup the allocations by JobID
   754  	out, err := h.State.AllocsByJob(job.ID)
   755  	noErr(t, err)
   756  
   757  	// Ensure all allocations placed
   758  	out = structs.FilterTerminalAllocs(out)
   759  	if len(out) != 3 {
   760  		t.Fatalf("bad: %#v", out)
   761  	}
   762  
   763  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   764  }
   765  
   766  func TestServiceSched_JobModify_CountZero(t *testing.T) {
   767  	h := NewHarness(t)
   768  
   769  	// Create some nodes
   770  	var nodes []*structs.Node
   771  	for i := 0; i < 10; i++ {
   772  		node := mock.Node()
   773  		nodes = append(nodes, node)
   774  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   775  	}
   776  
   777  	// Generate a fake job with allocations
   778  	job := mock.Job()
   779  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   780  
   781  	var allocs []*structs.Allocation
   782  	for i := 0; i < 10; i++ {
   783  		alloc := mock.Alloc()
   784  		alloc.Job = job
   785  		alloc.JobID = job.ID
   786  		alloc.NodeID = nodes[i].ID
   787  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   788  		allocs = append(allocs, alloc)
   789  	}
   790  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   791  
   792  	// Add a few terminal status allocations, these should be ignored
   793  	var terminal []*structs.Allocation
   794  	for i := 0; i < 5; i++ {
   795  		alloc := mock.Alloc()
   796  		alloc.Job = job
   797  		alloc.JobID = job.ID
   798  		alloc.NodeID = nodes[i].ID
   799  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   800  		alloc.DesiredStatus = structs.AllocDesiredStatusFailed
   801  		terminal = append(terminal, alloc)
   802  	}
   803  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal))
   804  
   805  	// Update the job to be count zero
   806  	job2 := mock.Job()
   807  	job2.ID = job.ID
   808  	job2.TaskGroups[0].Count = 0
   809  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   810  
   811  	// Create a mock evaluation to deal with drain
   812  	eval := &structs.Evaluation{
   813  		ID:          structs.GenerateUUID(),
   814  		Priority:    50,
   815  		TriggeredBy: structs.EvalTriggerJobRegister,
   816  		JobID:       job.ID,
   817  	}
   818  
   819  	// Process the evaluation
   820  	err := h.Process(NewServiceScheduler, eval)
   821  	if err != nil {
   822  		t.Fatalf("err: %v", err)
   823  	}
   824  
   825  	// Ensure a single plan
   826  	if len(h.Plans) != 1 {
   827  		t.Fatalf("bad: %#v", h.Plans)
   828  	}
   829  	plan := h.Plans[0]
   830  
   831  	// Ensure the plan evicted all allocs
   832  	var update []*structs.Allocation
   833  	for _, updateList := range plan.NodeUpdate {
   834  		update = append(update, updateList...)
   835  	}
   836  	if len(update) != len(allocs) {
   837  		t.Fatalf("bad: %#v", plan)
   838  	}
   839  
   840  	// Ensure the plan didn't allocated
   841  	var planned []*structs.Allocation
   842  	for _, allocList := range plan.NodeAllocation {
   843  		planned = append(planned, allocList...)
   844  	}
   845  	if len(planned) != 0 {
   846  		t.Fatalf("bad: %#v", plan)
   847  	}
   848  
   849  	// Lookup the allocations by JobID
   850  	out, err := h.State.AllocsByJob(job.ID)
   851  	noErr(t, err)
   852  
   853  	// Ensure all allocations placed
   854  	out = structs.FilterTerminalAllocs(out)
   855  	if len(out) != 0 {
   856  		t.Fatalf("bad: %#v", out)
   857  	}
   858  
   859  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   860  }
   861  
   862  func TestServiceSched_JobModify_Rolling(t *testing.T) {
   863  	h := NewHarness(t)
   864  
   865  	// Create some nodes
   866  	var nodes []*structs.Node
   867  	for i := 0; i < 10; i++ {
   868  		node := mock.Node()
   869  		nodes = append(nodes, node)
   870  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   871  	}
   872  
   873  	// Generate a fake job with allocations
   874  	job := mock.Job()
   875  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   876  
   877  	var allocs []*structs.Allocation
   878  	for i := 0; i < 10; i++ {
   879  		alloc := mock.Alloc()
   880  		alloc.Job = job
   881  		alloc.JobID = job.ID
   882  		alloc.NodeID = nodes[i].ID
   883  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   884  		allocs = append(allocs, alloc)
   885  	}
   886  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   887  
   888  	// Update the job
   889  	job2 := mock.Job()
   890  	job2.ID = job.ID
   891  	job2.Update = structs.UpdateStrategy{
   892  		Stagger:     30 * time.Second,
   893  		MaxParallel: 5,
   894  	}
   895  
   896  	// Update the task, such that it cannot be done in-place
   897  	job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   898  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   899  
   900  	// Create a mock evaluation to deal with drain
   901  	eval := &structs.Evaluation{
   902  		ID:          structs.GenerateUUID(),
   903  		Priority:    50,
   904  		TriggeredBy: structs.EvalTriggerJobRegister,
   905  		JobID:       job.ID,
   906  	}
   907  
   908  	// Process the evaluation
   909  	err := h.Process(NewServiceScheduler, eval)
   910  	if err != nil {
   911  		t.Fatalf("err: %v", err)
   912  	}
   913  
   914  	// Ensure a single plan
   915  	if len(h.Plans) != 1 {
   916  		t.Fatalf("bad: %#v", h.Plans)
   917  	}
   918  	plan := h.Plans[0]
   919  
   920  	// Ensure the plan evicted only MaxParallel
   921  	var update []*structs.Allocation
   922  	for _, updateList := range plan.NodeUpdate {
   923  		update = append(update, updateList...)
   924  	}
   925  	if len(update) != job2.Update.MaxParallel {
   926  		t.Fatalf("bad: %#v", plan)
   927  	}
   928  
   929  	// Ensure the plan allocated
   930  	var planned []*structs.Allocation
   931  	for _, allocList := range plan.NodeAllocation {
   932  		planned = append(planned, allocList...)
   933  	}
   934  	if len(planned) != job2.Update.MaxParallel {
   935  		t.Fatalf("bad: %#v", plan)
   936  	}
   937  
   938  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
   939  
   940  	// Ensure a follow up eval was created
   941  	eval = h.Evals[0]
   942  	if eval.NextEval == "" {
   943  		t.Fatalf("missing next eval")
   944  	}
   945  
   946  	// Check for create
   947  	if len(h.CreateEvals) == 0 {
   948  		t.Fatalf("missing created eval")
   949  	}
   950  	create := h.CreateEvals[0]
   951  	if eval.NextEval != create.ID {
   952  		t.Fatalf("ID mismatch")
   953  	}
   954  	if create.PreviousEval != eval.ID {
   955  		t.Fatalf("missing previous eval")
   956  	}
   957  
   958  	if create.TriggeredBy != structs.EvalTriggerRollingUpdate {
   959  		t.Fatalf("bad: %#v", create)
   960  	}
   961  }
   962  
   963  func TestServiceSched_JobModify_InPlace(t *testing.T) {
   964  	h := NewHarness(t)
   965  
   966  	// Create some nodes
   967  	var nodes []*structs.Node
   968  	for i := 0; i < 10; i++ {
   969  		node := mock.Node()
   970  		nodes = append(nodes, node)
   971  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
   972  	}
   973  
   974  	// Generate a fake job with allocations
   975  	job := mock.Job()
   976  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
   977  
   978  	var allocs []*structs.Allocation
   979  	for i := 0; i < 10; i++ {
   980  		alloc := mock.Alloc()
   981  		alloc.Job = job
   982  		alloc.JobID = job.ID
   983  		alloc.NodeID = nodes[i].ID
   984  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
   985  		allocs = append(allocs, alloc)
   986  	}
   987  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
   988  
   989  	// Update the job
   990  	job2 := mock.Job()
   991  	job2.ID = job.ID
   992  	noErr(t, h.State.UpsertJob(h.NextIndex(), job2))
   993  
   994  	// Create a mock evaluation to deal with drain
   995  	eval := &structs.Evaluation{
   996  		ID:          structs.GenerateUUID(),
   997  		Priority:    50,
   998  		TriggeredBy: structs.EvalTriggerJobRegister,
   999  		JobID:       job.ID,
  1000  	}
  1001  
  1002  	// Process the evaluation
  1003  	err := h.Process(NewServiceScheduler, eval)
  1004  	if err != nil {
  1005  		t.Fatalf("err: %v", err)
  1006  	}
  1007  
  1008  	// Ensure a single plan
  1009  	if len(h.Plans) != 1 {
  1010  		t.Fatalf("bad: %#v", h.Plans)
  1011  	}
  1012  	plan := h.Plans[0]
  1013  
  1014  	// Ensure the plan did not evict any allocs
  1015  	var update []*structs.Allocation
  1016  	for _, updateList := range plan.NodeUpdate {
  1017  		update = append(update, updateList...)
  1018  	}
  1019  	if len(update) != 0 {
  1020  		t.Fatalf("bad: %#v", plan)
  1021  	}
  1022  
  1023  	// Ensure the plan updated the existing allocs
  1024  	var planned []*structs.Allocation
  1025  	for _, allocList := range plan.NodeAllocation {
  1026  		planned = append(planned, allocList...)
  1027  	}
  1028  	if len(planned) != 10 {
  1029  		t.Fatalf("bad: %#v", plan)
  1030  	}
  1031  	for _, p := range planned {
  1032  		if p.Job != job2 {
  1033  			t.Fatalf("should update job")
  1034  		}
  1035  	}
  1036  
  1037  	// Lookup the allocations by JobID
  1038  	out, err := h.State.AllocsByJob(job.ID)
  1039  	noErr(t, err)
  1040  
  1041  	// Ensure all allocations placed
  1042  	if len(out) != 10 {
  1043  		for _, alloc := range out {
  1044  			t.Logf("%#v", alloc)
  1045  		}
  1046  		t.Fatalf("bad: %#v", out)
  1047  	}
  1048  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1049  
  1050  	// Verify the network did not change
  1051  	rp := structs.Port{Label: "main", Value: 5000}
  1052  	for _, alloc := range out {
  1053  		for _, resources := range alloc.TaskResources {
  1054  			if resources.Networks[0].ReservedPorts[0] != rp {
  1055  				t.Fatalf("bad: %#v", alloc)
  1056  			}
  1057  		}
  1058  	}
  1059  }
  1060  
  1061  func TestServiceSched_JobDeregister(t *testing.T) {
  1062  	h := NewHarness(t)
  1063  
  1064  	// Generate a fake job with allocations
  1065  	job := mock.Job()
  1066  
  1067  	var allocs []*structs.Allocation
  1068  	for i := 0; i < 10; i++ {
  1069  		alloc := mock.Alloc()
  1070  		alloc.Job = job
  1071  		alloc.JobID = job.ID
  1072  		allocs = append(allocs, alloc)
  1073  	}
  1074  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
  1075  
  1076  	// Create a mock evaluation to deregister the job
  1077  	eval := &structs.Evaluation{
  1078  		ID:          structs.GenerateUUID(),
  1079  		Priority:    50,
  1080  		TriggeredBy: structs.EvalTriggerJobDeregister,
  1081  		JobID:       job.ID,
  1082  	}
  1083  
  1084  	// Process the evaluation
  1085  	err := h.Process(NewServiceScheduler, eval)
  1086  	if err != nil {
  1087  		t.Fatalf("err: %v", err)
  1088  	}
  1089  
  1090  	// Ensure a single plan
  1091  	if len(h.Plans) != 1 {
  1092  		t.Fatalf("bad: %#v", h.Plans)
  1093  	}
  1094  	plan := h.Plans[0]
  1095  
  1096  	// Ensure the plan evicted all nodes
  1097  	if len(plan.NodeUpdate["12345678-abcd-efab-cdef-123456789abc"]) != len(allocs) {
  1098  		t.Fatalf("bad: %#v", plan)
  1099  	}
  1100  
  1101  	// Lookup the allocations by JobID
  1102  	out, err := h.State.AllocsByJob(job.ID)
  1103  	noErr(t, err)
  1104  
  1105  	// Ensure that the job field on the allocation is still populated
  1106  	for _, alloc := range out {
  1107  		if alloc.Job == nil {
  1108  			t.Fatalf("bad: %#v", alloc)
  1109  		}
  1110  	}
  1111  
  1112  	// Ensure no remaining allocations
  1113  	out = structs.FilterTerminalAllocs(out)
  1114  	if len(out) != 0 {
  1115  		t.Fatalf("bad: %#v", out)
  1116  	}
  1117  
  1118  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1119  }
  1120  
  1121  func TestServiceSched_NodeDrain(t *testing.T) {
  1122  	h := NewHarness(t)
  1123  
  1124  	// Register a draining node
  1125  	node := mock.Node()
  1126  	node.Drain = true
  1127  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1128  
  1129  	// Create some nodes
  1130  	for i := 0; i < 10; i++ {
  1131  		node := mock.Node()
  1132  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1133  	}
  1134  
  1135  	// Generate a fake job with allocations and an update policy.
  1136  	job := mock.Job()
  1137  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1138  
  1139  	var allocs []*structs.Allocation
  1140  	for i := 0; i < 10; i++ {
  1141  		alloc := mock.Alloc()
  1142  		alloc.Job = job
  1143  		alloc.JobID = job.ID
  1144  		alloc.NodeID = node.ID
  1145  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
  1146  		allocs = append(allocs, alloc)
  1147  	}
  1148  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
  1149  
  1150  	// Create a mock evaluation to deal with drain
  1151  	eval := &structs.Evaluation{
  1152  		ID:          structs.GenerateUUID(),
  1153  		Priority:    50,
  1154  		TriggeredBy: structs.EvalTriggerNodeUpdate,
  1155  		JobID:       job.ID,
  1156  		NodeID:      node.ID,
  1157  	}
  1158  
  1159  	// Process the evaluation
  1160  	err := h.Process(NewServiceScheduler, eval)
  1161  	if err != nil {
  1162  		t.Fatalf("err: %v", err)
  1163  	}
  1164  
  1165  	// Ensure a single plan
  1166  	if len(h.Plans) != 1 {
  1167  		t.Fatalf("bad: %#v", h.Plans)
  1168  	}
  1169  	plan := h.Plans[0]
  1170  
  1171  	// Ensure the plan evicted all allocs
  1172  	if len(plan.NodeUpdate[node.ID]) != len(allocs) {
  1173  		t.Fatalf("bad: %#v", plan)
  1174  	}
  1175  
  1176  	// Ensure the plan allocated
  1177  	var planned []*structs.Allocation
  1178  	for _, allocList := range plan.NodeAllocation {
  1179  		planned = append(planned, allocList...)
  1180  	}
  1181  	if len(planned) != 10 {
  1182  		t.Fatalf("bad: %#v", plan)
  1183  	}
  1184  
  1185  	// Lookup the allocations by JobID
  1186  	out, err := h.State.AllocsByJob(job.ID)
  1187  	noErr(t, err)
  1188  
  1189  	// Ensure all allocations placed
  1190  	out = structs.FilterTerminalAllocs(out)
  1191  	if len(out) != 10 {
  1192  		t.Fatalf("bad: %#v", out)
  1193  	}
  1194  
  1195  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1196  }
  1197  
  1198  func TestServiceSched_NodeDrain_UpdateStrategy(t *testing.T) {
  1199  	h := NewHarness(t)
  1200  
  1201  	// Register a draining node
  1202  	node := mock.Node()
  1203  	node.Drain = true
  1204  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1205  
  1206  	// Create some nodes
  1207  	for i := 0; i < 10; i++ {
  1208  		node := mock.Node()
  1209  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1210  	}
  1211  
  1212  	// Generate a fake job with allocations and an update policy.
  1213  	job := mock.Job()
  1214  	mp := 5
  1215  	job.Update = structs.UpdateStrategy{
  1216  		Stagger:     time.Second,
  1217  		MaxParallel: mp,
  1218  	}
  1219  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1220  
  1221  	var allocs []*structs.Allocation
  1222  	for i := 0; i < 10; i++ {
  1223  		alloc := mock.Alloc()
  1224  		alloc.Job = job
  1225  		alloc.JobID = job.ID
  1226  		alloc.NodeID = node.ID
  1227  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
  1228  		allocs = append(allocs, alloc)
  1229  	}
  1230  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs))
  1231  
  1232  	// Create a mock evaluation to deal with drain
  1233  	eval := &structs.Evaluation{
  1234  		ID:          structs.GenerateUUID(),
  1235  		Priority:    50,
  1236  		TriggeredBy: structs.EvalTriggerNodeUpdate,
  1237  		JobID:       job.ID,
  1238  		NodeID:      node.ID,
  1239  	}
  1240  
  1241  	// Process the evaluation
  1242  	err := h.Process(NewServiceScheduler, eval)
  1243  	if err != nil {
  1244  		t.Fatalf("err: %v", err)
  1245  	}
  1246  
  1247  	// Ensure a single plan
  1248  	if len(h.Plans) != 1 {
  1249  		t.Fatalf("bad: %#v", h.Plans)
  1250  	}
  1251  	plan := h.Plans[0]
  1252  
  1253  	// Ensure the plan evicted all allocs
  1254  	if len(plan.NodeUpdate[node.ID]) != mp {
  1255  		t.Fatalf("bad: %#v", plan)
  1256  	}
  1257  
  1258  	// Ensure the plan allocated
  1259  	var planned []*structs.Allocation
  1260  	for _, allocList := range plan.NodeAllocation {
  1261  		planned = append(planned, allocList...)
  1262  	}
  1263  	if len(planned) != mp {
  1264  		t.Fatalf("bad: %#v", plan)
  1265  	}
  1266  
  1267  	// Ensure there is a followup eval.
  1268  	if len(h.CreateEvals) != 1 ||
  1269  		h.CreateEvals[0].TriggeredBy != structs.EvalTriggerRollingUpdate {
  1270  		t.Fatalf("bad: %#v", h.CreateEvals)
  1271  	}
  1272  
  1273  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1274  }
  1275  
  1276  func TestServiceSched_RetryLimit(t *testing.T) {
  1277  	h := NewHarness(t)
  1278  	h.Planner = &RejectPlan{h}
  1279  
  1280  	// Create some nodes
  1281  	for i := 0; i < 10; i++ {
  1282  		node := mock.Node()
  1283  		noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1284  	}
  1285  
  1286  	// Create a job
  1287  	job := mock.Job()
  1288  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1289  
  1290  	// Create a mock evaluation to register the job
  1291  	eval := &structs.Evaluation{
  1292  		ID:          structs.GenerateUUID(),
  1293  		Priority:    job.Priority,
  1294  		TriggeredBy: structs.EvalTriggerJobRegister,
  1295  		JobID:       job.ID,
  1296  	}
  1297  
  1298  	// Process the evaluation
  1299  	err := h.Process(NewServiceScheduler, eval)
  1300  	if err != nil {
  1301  		t.Fatalf("err: %v", err)
  1302  	}
  1303  
  1304  	// Ensure multiple plans
  1305  	if len(h.Plans) == 0 {
  1306  		t.Fatalf("bad: %#v", h.Plans)
  1307  	}
  1308  
  1309  	// Lookup the allocations by JobID
  1310  	out, err := h.State.AllocsByJob(job.ID)
  1311  	noErr(t, err)
  1312  
  1313  	// Ensure no allocations placed
  1314  	if len(out) != 0 {
  1315  		t.Fatalf("bad: %#v", out)
  1316  	}
  1317  
  1318  	// Should hit the retry limit
  1319  	h.AssertEvalStatus(t, structs.EvalStatusFailed)
  1320  }
  1321  
  1322  func TestBatchSched_Run_CompleteAlloc(t *testing.T) {
  1323  	h := NewHarness(t)
  1324  
  1325  	// Create a node
  1326  	node := mock.Node()
  1327  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1328  
  1329  	// Create a job
  1330  	job := mock.Job()
  1331  	job.TaskGroups[0].Count = 1
  1332  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1333  
  1334  	// Create a complete alloc
  1335  	alloc := mock.Alloc()
  1336  	alloc.Job = job
  1337  	alloc.JobID = job.ID
  1338  	alloc.NodeID = node.ID
  1339  	alloc.Name = "my-job.web[0]"
  1340  	alloc.ClientStatus = structs.AllocClientStatusComplete
  1341  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc}))
  1342  
  1343  	// Create a mock evaluation to register the job
  1344  	eval := &structs.Evaluation{
  1345  		ID:          structs.GenerateUUID(),
  1346  		Priority:    job.Priority,
  1347  		TriggeredBy: structs.EvalTriggerJobRegister,
  1348  		JobID:       job.ID,
  1349  	}
  1350  
  1351  	// Process the evaluation
  1352  	err := h.Process(NewBatchScheduler, eval)
  1353  	if err != nil {
  1354  		t.Fatalf("err: %v", err)
  1355  	}
  1356  
  1357  	// Ensure no plan as it should be a no-op
  1358  	if len(h.Plans) != 0 {
  1359  		t.Fatalf("bad: %#v", h.Plans)
  1360  	}
  1361  
  1362  	// Lookup the allocations by JobID
  1363  	out, err := h.State.AllocsByJob(job.ID)
  1364  	noErr(t, err)
  1365  
  1366  	// Ensure no allocations placed
  1367  	if len(out) != 1 {
  1368  		t.Fatalf("bad: %#v", out)
  1369  	}
  1370  
  1371  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1372  }
  1373  
  1374  func TestBatchSched_Run_DrainedAlloc(t *testing.T) {
  1375  	h := NewHarness(t)
  1376  
  1377  	// Create a node
  1378  	node := mock.Node()
  1379  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1380  
  1381  	// Create a job
  1382  	job := mock.Job()
  1383  	job.TaskGroups[0].Count = 1
  1384  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1385  
  1386  	// Create a complete alloc
  1387  	alloc := mock.Alloc()
  1388  	alloc.Job = job
  1389  	alloc.JobID = job.ID
  1390  	alloc.NodeID = node.ID
  1391  	alloc.Name = "my-job.web[0]"
  1392  	alloc.DesiredStatus = structs.AllocDesiredStatusStop
  1393  	alloc.ClientStatus = structs.AllocClientStatusComplete
  1394  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc}))
  1395  
  1396  	// Create a mock evaluation to register the job
  1397  	eval := &structs.Evaluation{
  1398  		ID:          structs.GenerateUUID(),
  1399  		Priority:    job.Priority,
  1400  		TriggeredBy: structs.EvalTriggerJobRegister,
  1401  		JobID:       job.ID,
  1402  	}
  1403  
  1404  	// Process the evaluation
  1405  	err := h.Process(NewBatchScheduler, eval)
  1406  	if err != nil {
  1407  		t.Fatalf("err: %v", err)
  1408  	}
  1409  
  1410  	// Ensure a plan
  1411  	if len(h.Plans) != 1 {
  1412  		t.Fatalf("bad: %#v", h.Plans)
  1413  	}
  1414  
  1415  	// Lookup the allocations by JobID
  1416  	out, err := h.State.AllocsByJob(job.ID)
  1417  	noErr(t, err)
  1418  
  1419  	// Ensure a replacement alloc was placed.
  1420  	if len(out) != 2 {
  1421  		t.Fatalf("bad: %#v", out)
  1422  	}
  1423  
  1424  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1425  }
  1426  
  1427  func TestBatchSched_Run_FailedAlloc(t *testing.T) {
  1428  	h := NewHarness(t)
  1429  
  1430  	// Create a node
  1431  	node := mock.Node()
  1432  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1433  
  1434  	// Create a job
  1435  	job := mock.Job()
  1436  	job.TaskGroups[0].Count = 1
  1437  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1438  
  1439  	// Create a failed alloc
  1440  	alloc := mock.Alloc()
  1441  	alloc.Job = job
  1442  	alloc.JobID = job.ID
  1443  	alloc.NodeID = node.ID
  1444  	alloc.Name = "my-job.web[0]"
  1445  	alloc.ClientStatus = structs.AllocClientStatusFailed
  1446  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc}))
  1447  
  1448  	// Create a mock evaluation to register the job
  1449  	eval := &structs.Evaluation{
  1450  		ID:          structs.GenerateUUID(),
  1451  		Priority:    job.Priority,
  1452  		TriggeredBy: structs.EvalTriggerJobRegister,
  1453  		JobID:       job.ID,
  1454  	}
  1455  
  1456  	// Process the evaluation
  1457  	err := h.Process(NewBatchScheduler, eval)
  1458  	if err != nil {
  1459  		t.Fatalf("err: %v", err)
  1460  	}
  1461  
  1462  	// Ensure a plan
  1463  	if len(h.Plans) != 1 {
  1464  		t.Fatalf("bad: %#v", h.Plans)
  1465  	}
  1466  
  1467  	// Lookup the allocations by JobID
  1468  	out, err := h.State.AllocsByJob(job.ID)
  1469  	noErr(t, err)
  1470  
  1471  	// Ensure a replacement alloc was placed.
  1472  	if len(out) != 2 {
  1473  		t.Fatalf("bad: %#v", out)
  1474  	}
  1475  
  1476  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1477  }
  1478  
  1479  func TestBatchSched_ReRun_SuccessfullyFinishedAlloc(t *testing.T) {
  1480  	h := NewHarness(t)
  1481  
  1482  	// Create two nodes, one that is drained and has a successfully finished
  1483  	// alloc and a fresh undrained one
  1484  	node := mock.Node()
  1485  	node.Drain = true
  1486  	node2 := mock.Node()
  1487  	noErr(t, h.State.UpsertNode(h.NextIndex(), node))
  1488  	noErr(t, h.State.UpsertNode(h.NextIndex(), node2))
  1489  
  1490  	// Create a job
  1491  	job := mock.Job()
  1492  	job.Type = structs.JobTypeBatch
  1493  	job.TaskGroups[0].Count = 1
  1494  	noErr(t, h.State.UpsertJob(h.NextIndex(), job))
  1495  
  1496  	// Create a successful alloc
  1497  	alloc := mock.Alloc()
  1498  	alloc.Job = job
  1499  	alloc.JobID = job.ID
  1500  	alloc.NodeID = node.ID
  1501  	alloc.Name = "my-job.web[0]"
  1502  	alloc.ClientStatus = structs.AllocClientStatusComplete
  1503  	alloc.TaskStates = map[string]*structs.TaskState{
  1504  		"web": &structs.TaskState{
  1505  			State: structs.TaskStateDead,
  1506  			Events: []*structs.TaskEvent{
  1507  				{
  1508  					Type:     structs.TaskTerminated,
  1509  					ExitCode: 0,
  1510  				},
  1511  			},
  1512  		},
  1513  	}
  1514  	noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc}))
  1515  
  1516  	// Create a mock evaluation to rerun the job
  1517  	eval := &structs.Evaluation{
  1518  		ID:          structs.GenerateUUID(),
  1519  		Priority:    job.Priority,
  1520  		TriggeredBy: structs.EvalTriggerJobRegister,
  1521  		JobID:       job.ID,
  1522  	}
  1523  
  1524  	// Process the evaluation
  1525  	err := h.Process(NewBatchScheduler, eval)
  1526  	if err != nil {
  1527  		t.Fatalf("err: %v", err)
  1528  	}
  1529  
  1530  	// Ensure no plan
  1531  	if len(h.Plans) != 0 {
  1532  		t.Fatalf("bad: %#v", h.Plans)
  1533  	}
  1534  
  1535  	// Lookup the allocations by JobID
  1536  	out, err := h.State.AllocsByJob(job.ID)
  1537  	noErr(t, err)
  1538  
  1539  	// Ensure no replacement alloc was placed.
  1540  	if len(out) != 1 {
  1541  		t.Fatalf("bad: %#v", out)
  1542  	}
  1543  
  1544  	h.AssertEvalStatus(t, structs.EvalStatusComplete)
  1545  }