github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/scheduler/util_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"reflect"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/state"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  // noErr is used to assert there are no errors
    16  func noErr(t *testing.T, err error) {
    17  	if err != nil {
    18  		t.Fatalf("err: %v", err)
    19  	}
    20  }
    21  
    22  func TestMaterializeTaskGroups(t *testing.T) {
    23  	job := mock.Job()
    24  	index := materializeTaskGroups(job)
    25  	if len(index) != 10 {
    26  		t.Fatalf("Bad: %#v", index)
    27  	}
    28  
    29  	for i := 0; i < 10; i++ {
    30  		name := fmt.Sprintf("my-job.web[%d]", i)
    31  		tg, ok := index[name]
    32  		if !ok {
    33  			t.Fatalf("bad")
    34  		}
    35  		if tg != job.TaskGroups[0] {
    36  			t.Fatalf("bad")
    37  		}
    38  	}
    39  }
    40  
    41  func TestDiffAllocs(t *testing.T) {
    42  	job := mock.Job()
    43  	required := materializeTaskGroups(job)
    44  
    45  	// The "old" job has a previous modify index
    46  	oldJob := new(structs.Job)
    47  	*oldJob = *job
    48  	oldJob.JobModifyIndex -= 1
    49  
    50  	drainNode := mock.Node()
    51  	drainNode.Drain = true
    52  
    53  	deadNode := mock.Node()
    54  	deadNode.Status = structs.NodeStatusDown
    55  
    56  	tainted := map[string]*structs.Node{
    57  		"dead":      deadNode,
    58  		"drainNode": drainNode,
    59  	}
    60  
    61  	allocs := []*structs.Allocation{
    62  		// Update the 1st
    63  		&structs.Allocation{
    64  			ID:     structs.GenerateUUID(),
    65  			NodeID: "zip",
    66  			Name:   "my-job.web[0]",
    67  			Job:    oldJob,
    68  		},
    69  
    70  		// Ignore the 2rd
    71  		&structs.Allocation{
    72  			ID:     structs.GenerateUUID(),
    73  			NodeID: "zip",
    74  			Name:   "my-job.web[1]",
    75  			Job:    job,
    76  		},
    77  
    78  		// Evict 11th
    79  		&structs.Allocation{
    80  			ID:     structs.GenerateUUID(),
    81  			NodeID: "zip",
    82  			Name:   "my-job.web[10]",
    83  			Job:    oldJob,
    84  		},
    85  
    86  		// Migrate the 3rd
    87  		&structs.Allocation{
    88  			ID:     structs.GenerateUUID(),
    89  			NodeID: "drainNode",
    90  			Name:   "my-job.web[2]",
    91  			Job:    oldJob,
    92  		},
    93  		// Mark the 4th lost
    94  		&structs.Allocation{
    95  			ID:     structs.GenerateUUID(),
    96  			NodeID: "dead",
    97  			Name:   "my-job.web[3]",
    98  			Job:    oldJob,
    99  		},
   100  	}
   101  
   102  	// Have three terminal allocs
   103  	terminalAllocs := map[string]*structs.Allocation{
   104  		"my-job.web[4]": &structs.Allocation{
   105  			ID:     structs.GenerateUUID(),
   106  			NodeID: "zip",
   107  			Name:   "my-job.web[4]",
   108  			Job:    job,
   109  		},
   110  		"my-job.web[5]": &structs.Allocation{
   111  			ID:     structs.GenerateUUID(),
   112  			NodeID: "zip",
   113  			Name:   "my-job.web[5]",
   114  			Job:    job,
   115  		},
   116  		"my-job.web[6]": &structs.Allocation{
   117  			ID:     structs.GenerateUUID(),
   118  			NodeID: "zip",
   119  			Name:   "my-job.web[6]",
   120  			Job:    job,
   121  		},
   122  	}
   123  
   124  	diff := diffAllocs(job, tainted, required, allocs, terminalAllocs)
   125  	place := diff.place
   126  	update := diff.update
   127  	migrate := diff.migrate
   128  	stop := diff.stop
   129  	ignore := diff.ignore
   130  	lost := diff.lost
   131  
   132  	// We should update the first alloc
   133  	if len(update) != 1 || update[0].Alloc != allocs[0] {
   134  		t.Fatalf("bad: %#v", update)
   135  	}
   136  
   137  	// We should ignore the second alloc
   138  	if len(ignore) != 1 || ignore[0].Alloc != allocs[1] {
   139  		t.Fatalf("bad: %#v", ignore)
   140  	}
   141  
   142  	// We should stop the 3rd alloc
   143  	if len(stop) != 1 || stop[0].Alloc != allocs[2] {
   144  		t.Fatalf("bad: %#v", stop)
   145  	}
   146  
   147  	// We should migrate the 4rd alloc
   148  	if len(migrate) != 1 || migrate[0].Alloc != allocs[3] {
   149  		t.Fatalf("bad: %#v", migrate)
   150  	}
   151  
   152  	// We should mark the 5th alloc as lost
   153  	if len(lost) != 1 || lost[0].Alloc != allocs[4] {
   154  		t.Fatalf("bad: %#v", migrate)
   155  	}
   156  
   157  	// We should place 6
   158  	if len(place) != 6 {
   159  		t.Fatalf("bad: %#v", place)
   160  	}
   161  
   162  	// Ensure that the allocations which are replacements of terminal allocs are
   163  	// annotated
   164  	for name, alloc := range terminalAllocs {
   165  		for _, allocTuple := range diff.place {
   166  			if name == allocTuple.Name {
   167  				if !reflect.DeepEqual(alloc, allocTuple.Alloc) {
   168  					t.Fatalf("expected: %#v, actual: %#v", alloc, allocTuple.Alloc)
   169  				}
   170  			}
   171  		}
   172  	}
   173  }
   174  
   175  func TestDiffSystemAllocs(t *testing.T) {
   176  	job := mock.SystemJob()
   177  
   178  	drainNode := mock.Node()
   179  	drainNode.Drain = true
   180  
   181  	deadNode := mock.Node()
   182  	deadNode.Status = structs.NodeStatusDown
   183  
   184  	tainted := map[string]*structs.Node{
   185  		deadNode.ID:  deadNode,
   186  		drainNode.ID: drainNode,
   187  	}
   188  
   189  	// Create three alive nodes.
   190  	nodes := []*structs.Node{{ID: "foo"}, {ID: "bar"}, {ID: "baz"},
   191  		{ID: "pipe"}, {ID: drainNode.ID}, {ID: deadNode.ID}}
   192  
   193  	// The "old" job has a previous modify index
   194  	oldJob := new(structs.Job)
   195  	*oldJob = *job
   196  	oldJob.JobModifyIndex -= 1
   197  
   198  	allocs := []*structs.Allocation{
   199  		// Update allocation on baz
   200  		&structs.Allocation{
   201  			ID:     structs.GenerateUUID(),
   202  			NodeID: "baz",
   203  			Name:   "my-job.web[0]",
   204  			Job:    oldJob,
   205  		},
   206  
   207  		// Ignore allocation on bar
   208  		&structs.Allocation{
   209  			ID:     structs.GenerateUUID(),
   210  			NodeID: "bar",
   211  			Name:   "my-job.web[0]",
   212  			Job:    job,
   213  		},
   214  
   215  		// Stop allocation on draining node.
   216  		&structs.Allocation{
   217  			ID:     structs.GenerateUUID(),
   218  			NodeID: drainNode.ID,
   219  			Name:   "my-job.web[0]",
   220  			Job:    oldJob,
   221  		},
   222  		// Mark as lost on a dead node
   223  		&structs.Allocation{
   224  			ID:     structs.GenerateUUID(),
   225  			NodeID: deadNode.ID,
   226  			Name:   "my-job.web[0]",
   227  			Job:    oldJob,
   228  		},
   229  	}
   230  
   231  	// Have three terminal allocs
   232  	terminalAllocs := map[string]*structs.Allocation{
   233  		"my-job.web[0]": &structs.Allocation{
   234  			ID:     structs.GenerateUUID(),
   235  			NodeID: "pipe",
   236  			Name:   "my-job.web[0]",
   237  			Job:    job,
   238  		},
   239  	}
   240  
   241  	diff := diffSystemAllocs(job, nodes, tainted, allocs, terminalAllocs)
   242  	place := diff.place
   243  	update := diff.update
   244  	migrate := diff.migrate
   245  	stop := diff.stop
   246  	ignore := diff.ignore
   247  	lost := diff.lost
   248  
   249  	// We should update the first alloc
   250  	if len(update) != 1 || update[0].Alloc != allocs[0] {
   251  		t.Fatalf("bad: %#v", update)
   252  	}
   253  
   254  	// We should ignore the second alloc
   255  	if len(ignore) != 1 || ignore[0].Alloc != allocs[1] {
   256  		t.Fatalf("bad: %#v", ignore)
   257  	}
   258  
   259  	// We should stop the third alloc
   260  	if len(stop) != 1 || stop[0].Alloc != allocs[2] {
   261  		t.Fatalf("bad: %#v", stop)
   262  	}
   263  
   264  	// There should be no migrates.
   265  	if len(migrate) != 0 {
   266  		t.Fatalf("bad: %#v", migrate)
   267  	}
   268  
   269  	// We should mark the 5th alloc as lost
   270  	if len(lost) != 1 || lost[0].Alloc != allocs[3] {
   271  		t.Fatalf("bad: %#v", migrate)
   272  	}
   273  
   274  	// We should place 1
   275  	if l := len(place); l != 2 {
   276  		t.Fatalf("bad: %#v", l)
   277  	}
   278  
   279  	// Ensure that the allocations which are replacements of terminal allocs are
   280  	// annotated
   281  	for _, alloc := range terminalAllocs {
   282  		for _, allocTuple := range diff.place {
   283  			if alloc.NodeID == allocTuple.Alloc.NodeID {
   284  				if !reflect.DeepEqual(alloc, allocTuple.Alloc) {
   285  					t.Fatalf("expected: %#v, actual: %#v", alloc, allocTuple.Alloc)
   286  				}
   287  			}
   288  		}
   289  	}
   290  }
   291  
   292  func TestReadyNodesInDCs(t *testing.T) {
   293  	state, err := state.NewStateStore(os.Stderr)
   294  	if err != nil {
   295  		t.Fatalf("err: %v", err)
   296  	}
   297  
   298  	node1 := mock.Node()
   299  	node2 := mock.Node()
   300  	node2.Datacenter = "dc2"
   301  	node3 := mock.Node()
   302  	node3.Datacenter = "dc2"
   303  	node3.Status = structs.NodeStatusDown
   304  	node4 := mock.Node()
   305  	node4.Drain = true
   306  
   307  	noErr(t, state.UpsertNode(1000, node1))
   308  	noErr(t, state.UpsertNode(1001, node2))
   309  	noErr(t, state.UpsertNode(1002, node3))
   310  	noErr(t, state.UpsertNode(1003, node4))
   311  
   312  	nodes, dc, err := readyNodesInDCs(state, []string{"dc1", "dc2"})
   313  	if err != nil {
   314  		t.Fatalf("err: %v", err)
   315  	}
   316  
   317  	if len(nodes) != 2 {
   318  		t.Fatalf("bad: %v", nodes)
   319  	}
   320  	if nodes[0].ID == node3.ID || nodes[1].ID == node3.ID {
   321  		t.Fatalf("Bad: %#v", nodes)
   322  	}
   323  	if count, ok := dc["dc1"]; !ok || count != 1 {
   324  		t.Fatalf("Bad: dc1 count %v", count)
   325  	}
   326  	if count, ok := dc["dc2"]; !ok || count != 1 {
   327  		t.Fatalf("Bad: dc2 count %v", count)
   328  	}
   329  }
   330  
   331  func TestRetryMax(t *testing.T) {
   332  	calls := 0
   333  	bad := func() (bool, error) {
   334  		calls += 1
   335  		return false, nil
   336  	}
   337  	err := retryMax(3, bad, nil)
   338  	if err == nil {
   339  		t.Fatalf("should fail")
   340  	}
   341  	if calls != 3 {
   342  		t.Fatalf("mis match")
   343  	}
   344  
   345  	calls = 0
   346  	first := true
   347  	reset := func() bool {
   348  		if calls == 3 && first {
   349  			first = false
   350  			return true
   351  		}
   352  		return false
   353  	}
   354  	err = retryMax(3, bad, reset)
   355  	if err == nil {
   356  		t.Fatalf("should fail")
   357  	}
   358  	if calls != 6 {
   359  		t.Fatalf("mis match")
   360  	}
   361  
   362  	calls = 0
   363  	good := func() (bool, error) {
   364  		calls += 1
   365  		return true, nil
   366  	}
   367  	err = retryMax(3, good, nil)
   368  	if err != nil {
   369  		t.Fatalf("err: %v", err)
   370  	}
   371  	if calls != 1 {
   372  		t.Fatalf("mis match")
   373  	}
   374  }
   375  
   376  func TestTaintedNodes(t *testing.T) {
   377  	state, err := state.NewStateStore(os.Stderr)
   378  	if err != nil {
   379  		t.Fatalf("err: %v", err)
   380  	}
   381  
   382  	node1 := mock.Node()
   383  	node2 := mock.Node()
   384  	node2.Datacenter = "dc2"
   385  	node3 := mock.Node()
   386  	node3.Datacenter = "dc2"
   387  	node3.Status = structs.NodeStatusDown
   388  	node4 := mock.Node()
   389  	node4.Drain = true
   390  	noErr(t, state.UpsertNode(1000, node1))
   391  	noErr(t, state.UpsertNode(1001, node2))
   392  	noErr(t, state.UpsertNode(1002, node3))
   393  	noErr(t, state.UpsertNode(1003, node4))
   394  
   395  	allocs := []*structs.Allocation{
   396  		&structs.Allocation{NodeID: node1.ID},
   397  		&structs.Allocation{NodeID: node2.ID},
   398  		&structs.Allocation{NodeID: node3.ID},
   399  		&structs.Allocation{NodeID: node4.ID},
   400  		&structs.Allocation{NodeID: "12345678-abcd-efab-cdef-123456789abc"},
   401  	}
   402  	tainted, err := taintedNodes(state, allocs)
   403  	if err != nil {
   404  		t.Fatalf("err: %v", err)
   405  	}
   406  
   407  	if len(tainted) != 3 {
   408  		t.Fatalf("bad: %v", tainted)
   409  	}
   410  
   411  	if _, ok := tainted[node1.ID]; ok {
   412  		t.Fatalf("Bad: %v", tainted)
   413  	}
   414  	if _, ok := tainted[node2.ID]; ok {
   415  		t.Fatalf("Bad: %v", tainted)
   416  	}
   417  
   418  	if node, ok := tainted[node3.ID]; !ok || node == nil {
   419  		t.Fatalf("Bad: %v", tainted)
   420  	}
   421  
   422  	if node, ok := tainted[node4.ID]; !ok || node == nil {
   423  		t.Fatalf("Bad: %v", tainted)
   424  	}
   425  
   426  	if node, ok := tainted["12345678-abcd-efab-cdef-123456789abc"]; !ok || node != nil {
   427  		t.Fatalf("Bad: %v", tainted)
   428  	}
   429  }
   430  
   431  func TestShuffleNodes(t *testing.T) {
   432  	// Use a large number of nodes to make the probability of shuffling to the
   433  	// original order very low.
   434  	nodes := []*structs.Node{
   435  		mock.Node(),
   436  		mock.Node(),
   437  		mock.Node(),
   438  		mock.Node(),
   439  		mock.Node(),
   440  		mock.Node(),
   441  		mock.Node(),
   442  		mock.Node(),
   443  		mock.Node(),
   444  		mock.Node(),
   445  	}
   446  	orig := make([]*structs.Node, len(nodes))
   447  	copy(orig, nodes)
   448  	shuffleNodes(nodes)
   449  	if reflect.DeepEqual(nodes, orig) {
   450  		t.Fatalf("should not match")
   451  	}
   452  }
   453  
   454  func TestTasksUpdated(t *testing.T) {
   455  	j1 := mock.Job()
   456  	j2 := mock.Job()
   457  
   458  	if tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) {
   459  		t.Fatalf("bad")
   460  	}
   461  
   462  	j2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other"
   463  	if !tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) {
   464  		t.Fatalf("bad")
   465  	}
   466  
   467  	j3 := mock.Job()
   468  	j3.TaskGroups[0].Tasks[0].Name = "foo"
   469  	if !tasksUpdated(j1.TaskGroups[0], j3.TaskGroups[0]) {
   470  		t.Fatalf("bad")
   471  	}
   472  
   473  	j4 := mock.Job()
   474  	j4.TaskGroups[0].Tasks[0].Driver = "foo"
   475  	if !tasksUpdated(j1.TaskGroups[0], j4.TaskGroups[0]) {
   476  		t.Fatalf("bad")
   477  	}
   478  
   479  	j5 := mock.Job()
   480  	j5.TaskGroups[0].Tasks = append(j5.TaskGroups[0].Tasks,
   481  		j5.TaskGroups[0].Tasks[0])
   482  	if !tasksUpdated(j1.TaskGroups[0], j5.TaskGroups[0]) {
   483  		t.Fatalf("bad")
   484  	}
   485  
   486  	j6 := mock.Job()
   487  	j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []structs.Port{{"http", 0}, {"https", 0}, {"admin", 0}}
   488  	if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) {
   489  		t.Fatalf("bad")
   490  	}
   491  
   492  	j7 := mock.Job()
   493  	j7.TaskGroups[0].Tasks[0].Env["NEW_ENV"] = "NEW_VALUE"
   494  	if !tasksUpdated(j1.TaskGroups[0], j7.TaskGroups[0]) {
   495  		t.Fatalf("bad")
   496  	}
   497  
   498  	j8 := mock.Job()
   499  	j8.TaskGroups[0].Tasks[0].User = "foo"
   500  	if !tasksUpdated(j1.TaskGroups[0], j8.TaskGroups[0]) {
   501  		t.Fatalf("bad")
   502  	}
   503  
   504  	j9 := mock.Job()
   505  	j9.TaskGroups[0].Tasks[0].Artifacts = []*structs.TaskArtifact{
   506  		{
   507  			GetterSource: "http://foo.com/bar",
   508  		},
   509  	}
   510  	if !tasksUpdated(j1.TaskGroups[0], j9.TaskGroups[0]) {
   511  		t.Fatalf("bad")
   512  	}
   513  
   514  	j10 := mock.Job()
   515  	j10.TaskGroups[0].Tasks[0].Meta["baz"] = "boom"
   516  	if !tasksUpdated(j1.TaskGroups[0], j10.TaskGroups[0]) {
   517  		t.Fatalf("bad")
   518  	}
   519  
   520  	j11 := mock.Job()
   521  	j11.TaskGroups[0].Tasks[0].Resources.CPU = 1337
   522  	if !tasksUpdated(j1.TaskGroups[0], j11.TaskGroups[0]) {
   523  		t.Fatalf("bad")
   524  	}
   525  
   526  	j12 := mock.Job()
   527  	j12.TaskGroups[0].Tasks[0].Resources.Networks[0].MBits = 100
   528  	if !tasksUpdated(j1.TaskGroups[0], j12.TaskGroups[0]) {
   529  		t.Fatalf("bad")
   530  	}
   531  
   532  	j13 := mock.Job()
   533  	j13.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts[0].Label = "foobar"
   534  	if !tasksUpdated(j1.TaskGroups[0], j13.TaskGroups[0]) {
   535  		t.Fatalf("bad")
   536  	}
   537  
   538  	j14 := mock.Job()
   539  	j14.TaskGroups[0].Tasks[0].Resources.Networks[0].ReservedPorts = []structs.Port{{Label: "foo", Value: 1312}}
   540  	if !tasksUpdated(j1.TaskGroups[0], j14.TaskGroups[0]) {
   541  		t.Fatalf("bad")
   542  	}
   543  
   544  	j15 := mock.Job()
   545  	j15.TaskGroups[0].Tasks[0].Vault = &structs.Vault{Policies: []string{"foo"}}
   546  	if !tasksUpdated(j1.TaskGroups[0], j15.TaskGroups[0]) {
   547  		t.Fatalf("bad")
   548  	}
   549  
   550  	j16 := mock.Job()
   551  	j16.TaskGroups[0].EphemeralDisk.Sticky = true
   552  	if !tasksUpdated(j1.TaskGroups[0], j16.TaskGroups[0]) {
   553  		t.Fatal("bad")
   554  	}
   555  }
   556  
   557  func TestEvictAndPlace_LimitLessThanAllocs(t *testing.T) {
   558  	_, ctx := testContext(t)
   559  	allocs := []allocTuple{
   560  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   561  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   562  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   563  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   564  	}
   565  	diff := &diffResult{}
   566  
   567  	limit := 2
   568  	if !evictAndPlace(ctx, diff, allocs, "", &limit) {
   569  		t.Fatal("evictAndReplace() should have returned true")
   570  	}
   571  
   572  	if limit != 0 {
   573  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit)
   574  	}
   575  
   576  	if len(diff.place) != 2 {
   577  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   578  	}
   579  }
   580  
   581  func TestEvictAndPlace_LimitEqualToAllocs(t *testing.T) {
   582  	_, ctx := testContext(t)
   583  	allocs := []allocTuple{
   584  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   585  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   586  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   587  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   588  	}
   589  	diff := &diffResult{}
   590  
   591  	limit := 4
   592  	if evictAndPlace(ctx, diff, allocs, "", &limit) {
   593  		t.Fatal("evictAndReplace() should have returned false")
   594  	}
   595  
   596  	if limit != 0 {
   597  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit)
   598  	}
   599  
   600  	if len(diff.place) != 4 {
   601  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   602  	}
   603  }
   604  
   605  func TestSetStatus(t *testing.T) {
   606  	h := NewHarness(t)
   607  	logger := log.New(os.Stderr, "", log.LstdFlags)
   608  	eval := mock.Eval()
   609  	status := "a"
   610  	desc := "b"
   611  	if err := setStatus(logger, h, eval, nil, nil, nil, status, desc, nil); err != nil {
   612  		t.Fatalf("setStatus() failed: %v", err)
   613  	}
   614  
   615  	if len(h.Evals) != 1 {
   616  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   617  	}
   618  
   619  	newEval := h.Evals[0]
   620  	if newEval.ID != eval.ID || newEval.Status != status || newEval.StatusDescription != desc {
   621  		t.Fatalf("setStatus() submited invalid eval: %v", newEval)
   622  	}
   623  
   624  	// Test next evals
   625  	h = NewHarness(t)
   626  	next := mock.Eval()
   627  	if err := setStatus(logger, h, eval, next, nil, nil, status, desc, nil); err != nil {
   628  		t.Fatalf("setStatus() failed: %v", err)
   629  	}
   630  
   631  	if len(h.Evals) != 1 {
   632  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   633  	}
   634  
   635  	newEval = h.Evals[0]
   636  	if newEval.NextEval != next.ID {
   637  		t.Fatalf("setStatus() didn't set nextEval correctly: %v", newEval)
   638  	}
   639  
   640  	// Test blocked evals
   641  	h = NewHarness(t)
   642  	blocked := mock.Eval()
   643  	if err := setStatus(logger, h, eval, nil, blocked, nil, status, desc, nil); err != nil {
   644  		t.Fatalf("setStatus() failed: %v", err)
   645  	}
   646  
   647  	if len(h.Evals) != 1 {
   648  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   649  	}
   650  
   651  	newEval = h.Evals[0]
   652  	if newEval.BlockedEval != blocked.ID {
   653  		t.Fatalf("setStatus() didn't set BlockedEval correctly: %v", newEval)
   654  	}
   655  
   656  	// Test metrics
   657  	h = NewHarness(t)
   658  	metrics := map[string]*structs.AllocMetric{"foo": nil}
   659  	if err := setStatus(logger, h, eval, nil, nil, metrics, status, desc, nil); err != nil {
   660  		t.Fatalf("setStatus() failed: %v", err)
   661  	}
   662  
   663  	if len(h.Evals) != 1 {
   664  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   665  	}
   666  
   667  	newEval = h.Evals[0]
   668  	if !reflect.DeepEqual(newEval.FailedTGAllocs, metrics) {
   669  		t.Fatalf("setStatus() didn't set failed task group metrics correctly: %v", newEval)
   670  	}
   671  
   672  	// Test queued allocations
   673  	h = NewHarness(t)
   674  	queuedAllocs := map[string]int{"web": 1}
   675  
   676  	if err := setStatus(logger, h, eval, nil, nil, metrics, status, desc, queuedAllocs); err != nil {
   677  		t.Fatalf("setStatus() failed: %v", err)
   678  	}
   679  
   680  	if len(h.Evals) != 1 {
   681  		t.Fatalf("setStatus() didn't update plan: %v", h.Evals)
   682  	}
   683  
   684  	newEval = h.Evals[0]
   685  	if !reflect.DeepEqual(newEval.QueuedAllocations, queuedAllocs) {
   686  		t.Fatalf("setStatus() didn't set failed task group metrics correctly: %v", newEval)
   687  	}
   688  }
   689  
   690  func TestInplaceUpdate_ChangedTaskGroup(t *testing.T) {
   691  	state, ctx := testContext(t)
   692  	eval := mock.Eval()
   693  	job := mock.Job()
   694  
   695  	node := mock.Node()
   696  	noErr(t, state.UpsertNode(900, node))
   697  
   698  	// Register an alloc
   699  	alloc := &structs.Allocation{
   700  		ID:     structs.GenerateUUID(),
   701  		EvalID: eval.ID,
   702  		NodeID: node.ID,
   703  		JobID:  job.ID,
   704  		Job:    job,
   705  		Resources: &structs.Resources{
   706  			CPU:      2048,
   707  			MemoryMB: 2048,
   708  		},
   709  		DesiredStatus: structs.AllocDesiredStatusRun,
   710  		TaskGroup:     "web",
   711  	}
   712  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   713  	noErr(t, state.UpsertJobSummary(1000, mock.JobSummary(alloc.JobID)))
   714  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   715  
   716  	// Create a new task group that prevents in-place updates.
   717  	tg := &structs.TaskGroup{}
   718  	*tg = *job.TaskGroups[0]
   719  	task := &structs.Task{Name: "FOO"}
   720  	tg.Tasks = nil
   721  	tg.Tasks = append(tg.Tasks, task)
   722  
   723  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   724  	stack := NewGenericStack(false, ctx)
   725  
   726  	// Do the inplace update.
   727  	unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates)
   728  
   729  	if len(unplaced) != 1 || len(inplace) != 0 {
   730  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   731  	}
   732  
   733  	if len(ctx.plan.NodeAllocation) != 0 {
   734  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   735  	}
   736  }
   737  
   738  func TestInplaceUpdate_NoMatch(t *testing.T) {
   739  	state, ctx := testContext(t)
   740  	eval := mock.Eval()
   741  	job := mock.Job()
   742  
   743  	node := mock.Node()
   744  	noErr(t, state.UpsertNode(900, node))
   745  
   746  	// Register an alloc
   747  	alloc := &structs.Allocation{
   748  		ID:     structs.GenerateUUID(),
   749  		EvalID: eval.ID,
   750  		NodeID: node.ID,
   751  		JobID:  job.ID,
   752  		Job:    job,
   753  		Resources: &structs.Resources{
   754  			CPU:      2048,
   755  			MemoryMB: 2048,
   756  		},
   757  		DesiredStatus: structs.AllocDesiredStatusRun,
   758  		TaskGroup:     "web",
   759  	}
   760  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   761  	noErr(t, state.UpsertJobSummary(1000, mock.JobSummary(alloc.JobID)))
   762  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   763  
   764  	// Create a new task group that requires too much resources.
   765  	tg := &structs.TaskGroup{}
   766  	*tg = *job.TaskGroups[0]
   767  	resource := &structs.Resources{CPU: 9999}
   768  	tg.Tasks[0].Resources = resource
   769  
   770  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   771  	stack := NewGenericStack(false, ctx)
   772  
   773  	// Do the inplace update.
   774  	unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates)
   775  
   776  	if len(unplaced) != 1 || len(inplace) != 0 {
   777  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   778  	}
   779  
   780  	if len(ctx.plan.NodeAllocation) != 0 {
   781  		t.Fatal("inplaceUpdate incorrectly did an inplace update")
   782  	}
   783  }
   784  
   785  func TestInplaceUpdate_Success(t *testing.T) {
   786  	state, ctx := testContext(t)
   787  	eval := mock.Eval()
   788  	job := mock.Job()
   789  
   790  	node := mock.Node()
   791  	noErr(t, state.UpsertNode(900, node))
   792  
   793  	// Register an alloc
   794  	alloc := &structs.Allocation{
   795  		ID:        structs.GenerateUUID(),
   796  		EvalID:    eval.ID,
   797  		NodeID:    node.ID,
   798  		JobID:     job.ID,
   799  		Job:       job,
   800  		TaskGroup: job.TaskGroups[0].Name,
   801  		Resources: &structs.Resources{
   802  			CPU:      2048,
   803  			MemoryMB: 2048,
   804  		},
   805  		DesiredStatus: structs.AllocDesiredStatusRun,
   806  	}
   807  	alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources}
   808  	noErr(t, state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)))
   809  	noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc}))
   810  
   811  	// Create a new task group that updates the resources.
   812  	tg := &structs.TaskGroup{}
   813  	*tg = *job.TaskGroups[0]
   814  	resource := &structs.Resources{CPU: 737}
   815  	tg.Tasks[0].Resources = resource
   816  	newServices := []*structs.Service{
   817  		{
   818  			Name:      "dummy-service",
   819  			PortLabel: "http",
   820  		},
   821  		{
   822  			Name:      "dummy-service2",
   823  			PortLabel: "http",
   824  		},
   825  	}
   826  
   827  	// Delete service 2
   828  	tg.Tasks[0].Services = tg.Tasks[0].Services[:1]
   829  
   830  	// Add the new services
   831  	tg.Tasks[0].Services = append(tg.Tasks[0].Services, newServices...)
   832  
   833  	updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}}
   834  	stack := NewGenericStack(false, ctx)
   835  	stack.SetJob(job)
   836  
   837  	// Do the inplace update.
   838  	unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates)
   839  
   840  	if len(unplaced) != 0 || len(inplace) != 1 {
   841  		t.Fatal("inplaceUpdate did not do an inplace update")
   842  	}
   843  
   844  	if len(ctx.plan.NodeAllocation) != 1 {
   845  		t.Fatal("inplaceUpdate did not do an inplace update")
   846  	}
   847  
   848  	if inplace[0].Alloc.ID != alloc.ID {
   849  		t.Fatalf("inplaceUpdate returned the wrong, inplace updated alloc: %#v", inplace)
   850  	}
   851  
   852  	// Get the alloc we inserted.
   853  	a := inplace[0].Alloc // TODO(sean@): Verify this is correct vs: ctx.plan.NodeAllocation[alloc.NodeID][0]
   854  	if a.Job == nil {
   855  		t.Fatalf("bad")
   856  	}
   857  
   858  	if len(a.Job.TaskGroups) != 1 {
   859  		t.Fatalf("bad")
   860  	}
   861  
   862  	if len(a.Job.TaskGroups[0].Tasks) != 1 {
   863  		t.Fatalf("bad")
   864  	}
   865  
   866  	if len(a.Job.TaskGroups[0].Tasks[0].Services) != 3 {
   867  		t.Fatalf("Expected number of services: %v, Actual: %v", 3, len(a.Job.TaskGroups[0].Tasks[0].Services))
   868  	}
   869  
   870  	serviceNames := make(map[string]struct{}, 3)
   871  	for _, consulService := range a.Job.TaskGroups[0].Tasks[0].Services {
   872  		serviceNames[consulService.Name] = struct{}{}
   873  	}
   874  	if len(serviceNames) != 3 {
   875  		t.Fatalf("bad")
   876  	}
   877  
   878  	for _, name := range []string{"dummy-service", "dummy-service2", "web-frontend"} {
   879  		if _, found := serviceNames[name]; !found {
   880  			t.Errorf("Expected consul service name missing: %v", name)
   881  		}
   882  	}
   883  }
   884  
   885  func TestEvictAndPlace_LimitGreaterThanAllocs(t *testing.T) {
   886  	_, ctx := testContext(t)
   887  	allocs := []allocTuple{
   888  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   889  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   890  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   891  		allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}},
   892  	}
   893  	diff := &diffResult{}
   894  
   895  	limit := 6
   896  	if evictAndPlace(ctx, diff, allocs, "", &limit) {
   897  		t.Fatal("evictAndReplace() should have returned false")
   898  	}
   899  
   900  	if limit != 2 {
   901  		t.Fatalf("evictAndReplace() should decremented limit; got %v; want 2", limit)
   902  	}
   903  
   904  	if len(diff.place) != 4 {
   905  		t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place)
   906  	}
   907  }
   908  
   909  func TestTaskGroupConstraints(t *testing.T) {
   910  	constr := &structs.Constraint{RTarget: "bar"}
   911  	constr2 := &structs.Constraint{LTarget: "foo"}
   912  	constr3 := &structs.Constraint{Operand: "<"}
   913  
   914  	tg := &structs.TaskGroup{
   915  		Name:          "web",
   916  		Count:         10,
   917  		Constraints:   []*structs.Constraint{constr},
   918  		EphemeralDisk: &structs.EphemeralDisk{},
   919  		Tasks: []*structs.Task{
   920  			&structs.Task{
   921  				Driver: "exec",
   922  				Resources: &structs.Resources{
   923  					CPU:      500,
   924  					MemoryMB: 256,
   925  				},
   926  				Constraints: []*structs.Constraint{constr2},
   927  			},
   928  			&structs.Task{
   929  				Driver: "docker",
   930  				Resources: &structs.Resources{
   931  					CPU:      500,
   932  					MemoryMB: 256,
   933  				},
   934  				Constraints: []*structs.Constraint{constr3},
   935  			},
   936  		},
   937  	}
   938  
   939  	// Build the expected values.
   940  	expConstr := []*structs.Constraint{constr, constr2, constr3}
   941  	expDrivers := map[string]struct{}{"exec": struct{}{}, "docker": struct{}{}}
   942  	expSize := &structs.Resources{
   943  		CPU:      1000,
   944  		MemoryMB: 512,
   945  	}
   946  
   947  	actConstrains := taskGroupConstraints(tg)
   948  	if !reflect.DeepEqual(actConstrains.constraints, expConstr) {
   949  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.constraints, expConstr)
   950  	}
   951  	if !reflect.DeepEqual(actConstrains.drivers, expDrivers) {
   952  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.drivers, expDrivers)
   953  	}
   954  	if !reflect.DeepEqual(actConstrains.size, expSize) {
   955  		t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.size, expSize)
   956  	}
   957  
   958  }
   959  
   960  func TestProgressMade(t *testing.T) {
   961  	noopPlan := &structs.PlanResult{}
   962  	if progressMade(nil) || progressMade(noopPlan) {
   963  		t.Fatal("no progress plan marked as making progress")
   964  	}
   965  
   966  	m := map[string][]*structs.Allocation{
   967  		"foo": []*structs.Allocation{mock.Alloc()},
   968  	}
   969  	both := &structs.PlanResult{
   970  		NodeAllocation: m,
   971  		NodeUpdate:     m,
   972  	}
   973  	update := &structs.PlanResult{NodeUpdate: m}
   974  	alloc := &structs.PlanResult{NodeAllocation: m}
   975  	if !(progressMade(both) && progressMade(update) && progressMade(alloc)) {
   976  		t.Fatal("bad")
   977  	}
   978  }
   979  
   980  func TestDesiredUpdates(t *testing.T) {
   981  	tg1 := &structs.TaskGroup{Name: "foo"}
   982  	tg2 := &structs.TaskGroup{Name: "bar"}
   983  	a2 := &structs.Allocation{TaskGroup: "bar"}
   984  
   985  	place := []allocTuple{
   986  		allocTuple{TaskGroup: tg1},
   987  		allocTuple{TaskGroup: tg1},
   988  		allocTuple{TaskGroup: tg1},
   989  		allocTuple{TaskGroup: tg2},
   990  	}
   991  	stop := []allocTuple{
   992  		allocTuple{TaskGroup: tg2, Alloc: a2},
   993  		allocTuple{TaskGroup: tg2, Alloc: a2},
   994  	}
   995  	ignore := []allocTuple{
   996  		allocTuple{TaskGroup: tg1},
   997  	}
   998  	migrate := []allocTuple{
   999  		allocTuple{TaskGroup: tg2},
  1000  	}
  1001  	inplace := []allocTuple{
  1002  		allocTuple{TaskGroup: tg1},
  1003  		allocTuple{TaskGroup: tg1},
  1004  	}
  1005  	destructive := []allocTuple{
  1006  		allocTuple{TaskGroup: tg1},
  1007  		allocTuple{TaskGroup: tg2},
  1008  		allocTuple{TaskGroup: tg2},
  1009  	}
  1010  	diff := &diffResult{
  1011  		place:   place,
  1012  		stop:    stop,
  1013  		ignore:  ignore,
  1014  		migrate: migrate,
  1015  	}
  1016  
  1017  	expected := map[string]*structs.DesiredUpdates{
  1018  		"foo": {
  1019  			Place:             3,
  1020  			Ignore:            1,
  1021  			InPlaceUpdate:     2,
  1022  			DestructiveUpdate: 1,
  1023  		},
  1024  		"bar": {
  1025  			Place:             1,
  1026  			Stop:              2,
  1027  			Migrate:           1,
  1028  			DestructiveUpdate: 2,
  1029  		},
  1030  	}
  1031  
  1032  	desired := desiredUpdates(diff, inplace, destructive)
  1033  	if !reflect.DeepEqual(desired, expected) {
  1034  		t.Fatalf("desiredUpdates() returned %#v; want %#v", desired, expected)
  1035  	}
  1036  }
  1037  
  1038  func TestUtil_AdjustQueuedAllocations(t *testing.T) {
  1039  	logger := log.New(os.Stderr, "", log.LstdFlags)
  1040  	alloc1 := mock.Alloc()
  1041  	alloc2 := mock.Alloc()
  1042  	alloc2.CreateIndex = 4
  1043  	alloc3 := mock.Alloc()
  1044  	alloc3.CreateIndex = 3
  1045  	alloc4 := mock.Alloc()
  1046  	alloc4.CreateIndex = 6
  1047  
  1048  	planResult := structs.PlanResult{
  1049  		NodeUpdate: map[string][]*structs.Allocation{
  1050  			"node-1": []*structs.Allocation{alloc1},
  1051  		},
  1052  		NodeAllocation: map[string][]*structs.Allocation{
  1053  			"node-1": []*structs.Allocation{
  1054  				alloc2,
  1055  			},
  1056  			"node-2": []*structs.Allocation{
  1057  				alloc3, alloc4,
  1058  			},
  1059  		},
  1060  		RefreshIndex: 3,
  1061  		AllocIndex:   4,
  1062  	}
  1063  
  1064  	queuedAllocs := map[string]int{"web": 2}
  1065  	adjustQueuedAllocations(logger, &planResult, queuedAllocs)
  1066  
  1067  	if queuedAllocs["web"] != 1 {
  1068  		t.Fatalf("expected: %v, actual: %v", 1, queuedAllocs["web"])
  1069  	}
  1070  }
  1071  
  1072  func TestUtil_UpdateNonTerminalAllocsToLost(t *testing.T) {
  1073  	node := mock.Node()
  1074  	alloc1 := mock.Alloc()
  1075  	alloc1.NodeID = node.ID
  1076  	alloc1.DesiredStatus = structs.AllocDesiredStatusStop
  1077  
  1078  	alloc2 := mock.Alloc()
  1079  	alloc2.NodeID = node.ID
  1080  	alloc2.DesiredStatus = structs.AllocDesiredStatusStop
  1081  	alloc2.ClientStatus = structs.AllocClientStatusRunning
  1082  
  1083  	alloc3 := mock.Alloc()
  1084  	alloc3.NodeID = node.ID
  1085  	alloc3.DesiredStatus = structs.AllocDesiredStatusStop
  1086  	alloc3.ClientStatus = structs.AllocClientStatusComplete
  1087  
  1088  	alloc4 := mock.Alloc()
  1089  	alloc4.NodeID = node.ID
  1090  	alloc4.DesiredStatus = structs.AllocDesiredStatusStop
  1091  	alloc4.ClientStatus = structs.AllocClientStatusFailed
  1092  
  1093  	allocs := []*structs.Allocation{alloc1, alloc2, alloc3, alloc4}
  1094  	plan := structs.Plan{
  1095  		NodeUpdate: make(map[string][]*structs.Allocation),
  1096  	}
  1097  	tainted := map[string]*structs.Node{node.ID: node}
  1098  
  1099  	updateNonTerminalAllocsToLost(&plan, tainted, allocs)
  1100  
  1101  	allocsLost := make([]string, 0, 2)
  1102  	for _, alloc := range plan.NodeUpdate[node.ID] {
  1103  		allocsLost = append(allocsLost, alloc.ID)
  1104  	}
  1105  	expected := []string{alloc1.ID, alloc2.ID}
  1106  	if !reflect.DeepEqual(allocsLost, expected) {
  1107  		t.Fatalf("actual: %v, expected: %v", allocsLost, expected)
  1108  	}
  1109  }