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