github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/scheduler/system_sched_test.go (about)

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