github.com/bigcommerce/nomad@v0.9.3-bc/nomad/plan_apply_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/go-memdb"
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/helper/uuid"
    11  	"github.com/hashicorp/nomad/nomad/mock"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/hashicorp/nomad/testutil"
    14  	"github.com/hashicorp/raft"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  const (
    20  	// workerPoolSize is the size of the worker pool
    21  	workerPoolSize = 2
    22  )
    23  
    24  // planWaitFuture is used to wait for the Raft future to complete
    25  func planWaitFuture(future raft.ApplyFuture) (uint64, error) {
    26  	if err := future.Error(); err != nil {
    27  		return 0, err
    28  	}
    29  	return future.Index(), nil
    30  }
    31  
    32  func testRegisterNode(t *testing.T, s *Server, n *structs.Node) {
    33  	// Create the register request
    34  	req := &structs.NodeRegisterRequest{
    35  		Node:         n,
    36  		WriteRequest: structs.WriteRequest{Region: "global"},
    37  	}
    38  
    39  	// Fetch the response
    40  	var resp structs.NodeUpdateResponse
    41  	if err := s.RPC("Node.Register", req, &resp); err != nil {
    42  		t.Fatalf("err: %v", err)
    43  	}
    44  	if resp.Index == 0 {
    45  		t.Fatalf("bad index: %d", resp.Index)
    46  	}
    47  }
    48  
    49  func testRegisterJob(t *testing.T, s *Server, j *structs.Job) {
    50  	// Create the register request
    51  	req := &structs.JobRegisterRequest{
    52  		Job:          j,
    53  		WriteRequest: structs.WriteRequest{Region: "global"},
    54  	}
    55  
    56  	// Fetch the response
    57  	var resp structs.JobRegisterResponse
    58  	if err := s.RPC("Job.Register", req, &resp); err != nil {
    59  		t.Fatalf("err: %v", err)
    60  	}
    61  	if resp.Index == 0 {
    62  		t.Fatalf("bad index: %d", resp.Index)
    63  	}
    64  }
    65  
    66  // COMPAT 0.11: Tests the older unoptimized code path for applyPlan
    67  func TestPlanApply_applyPlan(t *testing.T) {
    68  	t.Parallel()
    69  	s1 := TestServer(t, nil)
    70  	defer s1.Shutdown()
    71  	testutil.WaitForLeader(t, s1.RPC)
    72  
    73  	// Register node
    74  	node := mock.Node()
    75  	testRegisterNode(t, s1, node)
    76  
    77  	// Register a fake deployment
    78  	oldDeployment := mock.Deployment()
    79  	if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil {
    80  		t.Fatalf("UpsertDeployment failed: %v", err)
    81  	}
    82  
    83  	// Create a deployment
    84  	dnew := mock.Deployment()
    85  
    86  	// Create a deployment update for the old deployment id
    87  	desiredStatus, desiredStatusDescription := "foo", "bar"
    88  	updates := []*structs.DeploymentStatusUpdate{
    89  		{
    90  			DeploymentID:      oldDeployment.ID,
    91  			Status:            desiredStatus,
    92  			StatusDescription: desiredStatusDescription,
    93  		},
    94  	}
    95  
    96  	// Register alloc, deployment and deployment update
    97  	alloc := mock.Alloc()
    98  	s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))
    99  	// Create an eval
   100  	eval := mock.Eval()
   101  	eval.JobID = alloc.JobID
   102  	if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil {
   103  		t.Fatalf("err: %v", err)
   104  	}
   105  
   106  	planRes := &structs.PlanResult{
   107  		NodeAllocation: map[string][]*structs.Allocation{
   108  			node.ID: {alloc},
   109  		},
   110  		Deployment:        dnew,
   111  		DeploymentUpdates: updates,
   112  	}
   113  
   114  	// Snapshot the state
   115  	snap, err := s1.State().Snapshot()
   116  	if err != nil {
   117  		t.Fatalf("err: %v", err)
   118  	}
   119  
   120  	// Create the plan with a deployment
   121  	plan := &structs.Plan{
   122  		Job:               alloc.Job,
   123  		Deployment:        dnew,
   124  		DeploymentUpdates: updates,
   125  		EvalID:            eval.ID,
   126  	}
   127  
   128  	// Apply the plan
   129  	future, err := s1.applyPlan(plan, planRes, snap)
   130  	assert := assert.New(t)
   131  	assert.Nil(err)
   132  
   133  	// Verify our optimistic snapshot is updated
   134  	ws := memdb.NewWatchSet()
   135  	allocOut, err := snap.AllocByID(ws, alloc.ID)
   136  	assert.Nil(err)
   137  	assert.NotNil(allocOut)
   138  
   139  	deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID)
   140  	assert.Nil(err)
   141  	assert.NotNil(deploymentOut)
   142  
   143  	// Check plan does apply cleanly
   144  	index, err := planWaitFuture(future)
   145  	assert.Nil(err)
   146  	assert.NotEqual(0, index)
   147  
   148  	// Lookup the allocation
   149  	fsmState := s1.fsm.State()
   150  	allocOut, err = fsmState.AllocByID(ws, alloc.ID)
   151  	assert.Nil(err)
   152  	assert.NotNil(allocOut)
   153  	assert.True(allocOut.CreateTime > 0)
   154  	assert.True(allocOut.ModifyTime > 0)
   155  	assert.Equal(allocOut.CreateTime, allocOut.ModifyTime)
   156  
   157  	// Lookup the new deployment
   158  	dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID)
   159  	assert.Nil(err)
   160  	assert.NotNil(dout)
   161  
   162  	// Lookup the updated deployment
   163  	dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID)
   164  	assert.Nil(err)
   165  	assert.NotNil(dout2)
   166  	assert.Equal(desiredStatus, dout2.Status)
   167  	assert.Equal(desiredStatusDescription, dout2.StatusDescription)
   168  
   169  	// Lookup updated eval
   170  	evalOut, err := fsmState.EvalByID(ws, eval.ID)
   171  	assert.Nil(err)
   172  	assert.NotNil(evalOut)
   173  	assert.Equal(index, evalOut.ModifyIndex)
   174  
   175  	// Evict alloc, Register alloc2
   176  	allocEvict := new(structs.Allocation)
   177  	*allocEvict = *alloc
   178  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   179  	job := allocEvict.Job
   180  	allocEvict.Job = nil
   181  	alloc2 := mock.Alloc()
   182  	s1.State().UpsertJobSummary(1500, mock.JobSummary(alloc2.JobID))
   183  	planRes = &structs.PlanResult{
   184  		NodeUpdate: map[string][]*structs.Allocation{
   185  			node.ID: {allocEvict},
   186  		},
   187  		NodeAllocation: map[string][]*structs.Allocation{
   188  			node.ID: {alloc2},
   189  		},
   190  	}
   191  
   192  	// Snapshot the state
   193  	snap, err = s1.State().Snapshot()
   194  	assert.Nil(err)
   195  
   196  	// Apply the plan
   197  	plan = &structs.Plan{
   198  		Job:    job,
   199  		EvalID: eval.ID,
   200  	}
   201  	future, err = s1.applyPlan(plan, planRes, snap)
   202  	assert.Nil(err)
   203  
   204  	// Check that our optimistic view is updated
   205  	out, _ := snap.AllocByID(ws, allocEvict.ID)
   206  	assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus)
   207  
   208  	// Verify plan applies cleanly
   209  	index, err = planWaitFuture(future)
   210  	assert.Nil(err)
   211  	assert.NotEqual(0, index)
   212  
   213  	// Lookup the allocation
   214  	allocOut, err = s1.fsm.State().AllocByID(ws, alloc.ID)
   215  	assert.Nil(err)
   216  	assert.Equal(structs.AllocDesiredStatusEvict, allocOut.DesiredStatus)
   217  	assert.NotNil(allocOut.Job)
   218  	assert.True(allocOut.ModifyTime > 0)
   219  
   220  	// Lookup the allocation
   221  	allocOut, err = s1.fsm.State().AllocByID(ws, alloc2.ID)
   222  	assert.Nil(err)
   223  	assert.NotNil(allocOut)
   224  	assert.NotNil(allocOut.Job)
   225  
   226  	// Lookup updated eval
   227  	evalOut, err = fsmState.EvalByID(ws, eval.ID)
   228  	assert.Nil(err)
   229  	assert.NotNil(evalOut)
   230  	assert.Equal(index, evalOut.ModifyIndex)
   231  }
   232  
   233  // Verifies that applyPlan properly updates the constituent objects in MemDB,
   234  // when the plan contains normalized allocs.
   235  func TestPlanApply_applyPlanWithNormalizedAllocs(t *testing.T) {
   236  	t.Parallel()
   237  	s1 := TestServer(t, func(c *Config) {
   238  		c.Build = "0.9.2"
   239  	})
   240  	defer s1.Shutdown()
   241  	testutil.WaitForLeader(t, s1.RPC)
   242  
   243  	// Register node
   244  	node := mock.Node()
   245  	testRegisterNode(t, s1, node)
   246  
   247  	// Register a fake deployment
   248  	oldDeployment := mock.Deployment()
   249  	if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil {
   250  		t.Fatalf("UpsertDeployment failed: %v", err)
   251  	}
   252  
   253  	// Create a deployment
   254  	dnew := mock.Deployment()
   255  
   256  	// Create a deployment update for the old deployment id
   257  	desiredStatus, desiredStatusDescription := "foo", "bar"
   258  	updates := []*structs.DeploymentStatusUpdate{
   259  		{
   260  			DeploymentID:      oldDeployment.ID,
   261  			Status:            desiredStatus,
   262  			StatusDescription: desiredStatusDescription,
   263  		},
   264  	}
   265  
   266  	// Register allocs, deployment and deployment update
   267  	alloc := mock.Alloc()
   268  	stoppedAlloc := mock.Alloc()
   269  	stoppedAllocDiff := &structs.Allocation{
   270  		ID:                 stoppedAlloc.ID,
   271  		DesiredDescription: "Desired Description",
   272  		ClientStatus:       structs.AllocClientStatusLost,
   273  	}
   274  	preemptedAlloc := mock.Alloc()
   275  	preemptedAllocDiff := &structs.Allocation{
   276  		ID:                    preemptedAlloc.ID,
   277  		PreemptedByAllocation: alloc.ID,
   278  	}
   279  	s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))
   280  	s1.State().UpsertAllocs(1100, []*structs.Allocation{stoppedAlloc, preemptedAlloc})
   281  	// Create an eval
   282  	eval := mock.Eval()
   283  	eval.JobID = alloc.JobID
   284  	if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil {
   285  		t.Fatalf("err: %v", err)
   286  	}
   287  
   288  	timestampBeforeCommit := time.Now().UTC().UnixNano()
   289  	planRes := &structs.PlanResult{
   290  		NodeAllocation: map[string][]*structs.Allocation{
   291  			node.ID: {alloc},
   292  		},
   293  		NodeUpdate: map[string][]*structs.Allocation{
   294  			stoppedAlloc.NodeID: {stoppedAllocDiff},
   295  		},
   296  		NodePreemptions: map[string][]*structs.Allocation{
   297  			preemptedAlloc.NodeID: {preemptedAllocDiff},
   298  		},
   299  		Deployment:        dnew,
   300  		DeploymentUpdates: updates,
   301  	}
   302  
   303  	// Snapshot the state
   304  	snap, err := s1.State().Snapshot()
   305  	if err != nil {
   306  		t.Fatalf("err: %v", err)
   307  	}
   308  
   309  	// Create the plan with a deployment
   310  	plan := &structs.Plan{
   311  		Job:               alloc.Job,
   312  		Deployment:        dnew,
   313  		DeploymentUpdates: updates,
   314  		EvalID:            eval.ID,
   315  	}
   316  
   317  	require := require.New(t)
   318  	assert := assert.New(t)
   319  
   320  	// Apply the plan
   321  	future, err := s1.applyPlan(plan, planRes, snap)
   322  	require.NoError(err)
   323  
   324  	// Verify our optimistic snapshot is updated
   325  	ws := memdb.NewWatchSet()
   326  	allocOut, err := snap.AllocByID(ws, alloc.ID)
   327  	require.NoError(err)
   328  	require.NotNil(allocOut)
   329  
   330  	deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID)
   331  	require.NoError(err)
   332  	require.NotNil(deploymentOut)
   333  
   334  	// Check plan does apply cleanly
   335  	index, err := planWaitFuture(future)
   336  	require.NoError(err)
   337  	assert.NotEqual(0, index)
   338  
   339  	// Lookup the allocation
   340  	fsmState := s1.fsm.State()
   341  	allocOut, err = fsmState.AllocByID(ws, alloc.ID)
   342  	require.NoError(err)
   343  	require.NotNil(allocOut)
   344  	assert.True(allocOut.CreateTime > 0)
   345  	assert.True(allocOut.ModifyTime > 0)
   346  	assert.Equal(allocOut.CreateTime, allocOut.ModifyTime)
   347  
   348  	// Verify stopped alloc diff applied cleanly
   349  	updatedStoppedAlloc, err := fsmState.AllocByID(ws, stoppedAlloc.ID)
   350  	require.NoError(err)
   351  	require.NotNil(updatedStoppedAlloc)
   352  	assert.True(updatedStoppedAlloc.ModifyTime > timestampBeforeCommit)
   353  	assert.Equal(updatedStoppedAlloc.DesiredDescription, stoppedAllocDiff.DesiredDescription)
   354  	assert.Equal(updatedStoppedAlloc.ClientStatus, stoppedAllocDiff.ClientStatus)
   355  	assert.Equal(updatedStoppedAlloc.DesiredStatus, structs.AllocDesiredStatusStop)
   356  
   357  	// Verify preempted alloc diff applied cleanly
   358  	updatedPreemptedAlloc, err := fsmState.AllocByID(ws, preemptedAlloc.ID)
   359  	require.NoError(err)
   360  	require.NotNil(updatedPreemptedAlloc)
   361  	assert.True(updatedPreemptedAlloc.ModifyTime > timestampBeforeCommit)
   362  	assert.Equal(updatedPreemptedAlloc.DesiredDescription,
   363  		"Preempted by alloc ID "+preemptedAllocDiff.PreemptedByAllocation)
   364  	assert.Equal(updatedPreemptedAlloc.DesiredStatus, structs.AllocDesiredStatusEvict)
   365  
   366  	// Lookup the new deployment
   367  	dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID)
   368  	require.NoError(err)
   369  	require.NotNil(dout)
   370  
   371  	// Lookup the updated deployment
   372  	dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID)
   373  	require.NoError(err)
   374  	require.NotNil(dout2)
   375  	assert.Equal(desiredStatus, dout2.Status)
   376  	assert.Equal(desiredStatusDescription, dout2.StatusDescription)
   377  
   378  	// Lookup updated eval
   379  	evalOut, err := fsmState.EvalByID(ws, eval.ID)
   380  	require.NoError(err)
   381  	require.NotNil(evalOut)
   382  	assert.Equal(index, evalOut.ModifyIndex)
   383  }
   384  
   385  func TestPlanApply_EvalPlan_Simple(t *testing.T) {
   386  	t.Parallel()
   387  	state := testStateStore(t)
   388  	node := mock.Node()
   389  	state.UpsertNode(1000, node)
   390  	snap, _ := state.Snapshot()
   391  
   392  	alloc := mock.Alloc()
   393  	plan := &structs.Plan{
   394  		Job: alloc.Job,
   395  		NodeAllocation: map[string][]*structs.Allocation{
   396  			node.ID: {alloc},
   397  		},
   398  		Deployment: mock.Deployment(),
   399  		DeploymentUpdates: []*structs.DeploymentStatusUpdate{
   400  			{
   401  				DeploymentID:      uuid.Generate(),
   402  				Status:            "foo",
   403  				StatusDescription: "bar",
   404  			},
   405  		},
   406  	}
   407  
   408  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   409  	defer pool.Shutdown()
   410  
   411  	result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
   412  	if err != nil {
   413  		t.Fatalf("err: %v", err)
   414  	}
   415  	if result == nil {
   416  		t.Fatalf("missing result")
   417  	}
   418  	if !reflect.DeepEqual(result.NodeAllocation, plan.NodeAllocation) {
   419  		t.Fatalf("incorrect node allocations")
   420  	}
   421  	if !reflect.DeepEqual(result.Deployment, plan.Deployment) {
   422  		t.Fatalf("incorrect deployment")
   423  	}
   424  	if !reflect.DeepEqual(result.DeploymentUpdates, plan.DeploymentUpdates) {
   425  		t.Fatalf("incorrect deployment updates")
   426  	}
   427  }
   428  
   429  func TestPlanApply_EvalPlan_Preemption(t *testing.T) {
   430  	t.Parallel()
   431  	state := testStateStore(t)
   432  	node := mock.Node()
   433  	node.NodeResources = &structs.NodeResources{
   434  		Cpu: structs.NodeCpuResources{
   435  			CpuShares: 2000,
   436  		},
   437  		Memory: structs.NodeMemoryResources{
   438  			MemoryMB: 4192,
   439  		},
   440  		Disk: structs.NodeDiskResources{
   441  			DiskMB: 30 * 1024,
   442  		},
   443  		Networks: []*structs.NetworkResource{
   444  			{
   445  				Device: "eth0",
   446  				CIDR:   "192.168.0.100/32",
   447  				MBits:  1000,
   448  			},
   449  		},
   450  	}
   451  	state.UpsertNode(1000, node)
   452  
   453  	preemptedAlloc := mock.Alloc()
   454  	preemptedAlloc.NodeID = node.ID
   455  	preemptedAlloc.AllocatedResources = &structs.AllocatedResources{
   456  		Shared: structs.AllocatedSharedResources{
   457  			DiskMB: 25 * 1024,
   458  		},
   459  		Tasks: map[string]*structs.AllocatedTaskResources{
   460  			"web": {
   461  				Cpu: structs.AllocatedCpuResources{
   462  					CpuShares: 1500,
   463  				},
   464  				Memory: structs.AllocatedMemoryResources{
   465  					MemoryMB: 4000,
   466  				},
   467  				Networks: []*structs.NetworkResource{
   468  					{
   469  						Device:        "eth0",
   470  						IP:            "192.168.0.100",
   471  						ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
   472  						MBits:         800,
   473  						DynamicPorts:  []structs.Port{{Label: "http", Value: 9876}},
   474  					},
   475  				},
   476  			},
   477  		},
   478  	}
   479  
   480  	// Insert a preempted alloc such that the alloc will fit only after preemption
   481  	state.UpsertAllocs(1001, []*structs.Allocation{preemptedAlloc})
   482  
   483  	alloc := mock.Alloc()
   484  	alloc.AllocatedResources = &structs.AllocatedResources{
   485  		Shared: structs.AllocatedSharedResources{
   486  			DiskMB: 24 * 1024,
   487  		},
   488  		Tasks: map[string]*structs.AllocatedTaskResources{
   489  			"web": {
   490  				Cpu: structs.AllocatedCpuResources{
   491  					CpuShares: 1500,
   492  				},
   493  				Memory: structs.AllocatedMemoryResources{
   494  					MemoryMB: 3200,
   495  				},
   496  				Networks: []*structs.NetworkResource{
   497  					{
   498  						Device:        "eth0",
   499  						IP:            "192.168.0.100",
   500  						ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
   501  						MBits:         800,
   502  						DynamicPorts:  []structs.Port{{Label: "http", Value: 9876}},
   503  					},
   504  				},
   505  			},
   506  		},
   507  	}
   508  	plan := &structs.Plan{
   509  		Job: alloc.Job,
   510  		NodeAllocation: map[string][]*structs.Allocation{
   511  			node.ID: {alloc},
   512  		},
   513  		NodePreemptions: map[string][]*structs.Allocation{
   514  			node.ID: {preemptedAlloc},
   515  		},
   516  		Deployment: mock.Deployment(),
   517  		DeploymentUpdates: []*structs.DeploymentStatusUpdate{
   518  			{
   519  				DeploymentID:      uuid.Generate(),
   520  				Status:            "foo",
   521  				StatusDescription: "bar",
   522  			},
   523  		},
   524  	}
   525  	snap, _ := state.Snapshot()
   526  
   527  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   528  	defer pool.Shutdown()
   529  
   530  	result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
   531  
   532  	require := require.New(t)
   533  	require.NoError(err)
   534  	require.NotNil(result)
   535  
   536  	require.Equal(result.NodeAllocation, plan.NodeAllocation)
   537  	require.Equal(result.Deployment, plan.Deployment)
   538  	require.Equal(result.DeploymentUpdates, plan.DeploymentUpdates)
   539  	require.Equal(result.NodePreemptions, plan.NodePreemptions)
   540  
   541  }
   542  
   543  func TestPlanApply_EvalPlan_Partial(t *testing.T) {
   544  	t.Parallel()
   545  	state := testStateStore(t)
   546  	node := mock.Node()
   547  	state.UpsertNode(1000, node)
   548  	node2 := mock.Node()
   549  	state.UpsertNode(1001, node2)
   550  	snap, _ := state.Snapshot()
   551  
   552  	alloc := mock.Alloc()
   553  	alloc2 := mock.Alloc() // Ensure alloc2 does not fit
   554  	alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources)
   555  
   556  	// Create a deployment where the allocs are markeda as canaries
   557  	d := mock.Deployment()
   558  	d.TaskGroups["web"].PlacedCanaries = []string{alloc.ID, alloc2.ID}
   559  
   560  	plan := &structs.Plan{
   561  		Job: alloc.Job,
   562  		NodeAllocation: map[string][]*structs.Allocation{
   563  			node.ID:  {alloc},
   564  			node2.ID: {alloc2},
   565  		},
   566  		Deployment: d,
   567  	}
   568  
   569  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   570  	defer pool.Shutdown()
   571  
   572  	result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
   573  	if err != nil {
   574  		t.Fatalf("err: %v", err)
   575  	}
   576  	if result == nil {
   577  		t.Fatalf("missing result")
   578  	}
   579  
   580  	if _, ok := result.NodeAllocation[node.ID]; !ok {
   581  		t.Fatalf("should allow alloc")
   582  	}
   583  	if _, ok := result.NodeAllocation[node2.ID]; ok {
   584  		t.Fatalf("should not allow alloc2")
   585  	}
   586  
   587  	// Check the deployment was updated
   588  	if result.Deployment == nil || len(result.Deployment.TaskGroups) == 0 {
   589  		t.Fatalf("bad: %v", result.Deployment)
   590  	}
   591  	placedCanaries := result.Deployment.TaskGroups["web"].PlacedCanaries
   592  	if len(placedCanaries) != 1 || placedCanaries[0] != alloc.ID {
   593  		t.Fatalf("bad: %v", placedCanaries)
   594  	}
   595  
   596  	if result.RefreshIndex != 1001 {
   597  		t.Fatalf("bad: %d", result.RefreshIndex)
   598  	}
   599  }
   600  
   601  func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) {
   602  	t.Parallel()
   603  	state := testStateStore(t)
   604  	node := mock.Node()
   605  	state.UpsertNode(1000, node)
   606  	node2 := mock.Node()
   607  	state.UpsertNode(1001, node2)
   608  	snap, _ := state.Snapshot()
   609  
   610  	alloc := mock.Alloc()
   611  	alloc2 := mock.Alloc() // Ensure alloc2 does not fit
   612  	alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources)
   613  	plan := &structs.Plan{
   614  		Job:       alloc.Job,
   615  		AllAtOnce: true, // Require all to make progress
   616  		NodeAllocation: map[string][]*structs.Allocation{
   617  			node.ID:  {alloc},
   618  			node2.ID: {alloc2},
   619  		},
   620  		Deployment: mock.Deployment(),
   621  		DeploymentUpdates: []*structs.DeploymentStatusUpdate{
   622  			{
   623  				DeploymentID:      uuid.Generate(),
   624  				Status:            "foo",
   625  				StatusDescription: "bar",
   626  			},
   627  		},
   628  	}
   629  
   630  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   631  	defer pool.Shutdown()
   632  
   633  	result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t))
   634  	if err != nil {
   635  		t.Fatalf("err: %v", err)
   636  	}
   637  	if result == nil {
   638  		t.Fatalf("missing result")
   639  	}
   640  
   641  	if len(result.NodeAllocation) != 0 {
   642  		t.Fatalf("should not alloc: %v", result.NodeAllocation)
   643  	}
   644  	if result.RefreshIndex != 1001 {
   645  		t.Fatalf("bad: %d", result.RefreshIndex)
   646  	}
   647  	if result.Deployment != nil || len(result.DeploymentUpdates) != 0 {
   648  		t.Fatalf("bad: %v", result)
   649  	}
   650  }
   651  
   652  func TestPlanApply_EvalNodePlan_Simple(t *testing.T) {
   653  	t.Parallel()
   654  	state := testStateStore(t)
   655  	node := mock.Node()
   656  	state.UpsertNode(1000, node)
   657  	snap, _ := state.Snapshot()
   658  
   659  	alloc := mock.Alloc()
   660  	plan := &structs.Plan{
   661  		Job: alloc.Job,
   662  		NodeAllocation: map[string][]*structs.Allocation{
   663  			node.ID: {alloc},
   664  		},
   665  	}
   666  
   667  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   668  	if err != nil {
   669  		t.Fatalf("err: %v", err)
   670  	}
   671  	if !fit {
   672  		t.Fatalf("bad")
   673  	}
   674  	if reason != "" {
   675  		t.Fatalf("bad")
   676  	}
   677  }
   678  
   679  func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) {
   680  	t.Parallel()
   681  	state := testStateStore(t)
   682  	node := mock.Node()
   683  	node.Status = structs.NodeStatusInit
   684  	state.UpsertNode(1000, node)
   685  	snap, _ := state.Snapshot()
   686  
   687  	alloc := mock.Alloc()
   688  	plan := &structs.Plan{
   689  		Job: alloc.Job,
   690  		NodeAllocation: map[string][]*structs.Allocation{
   691  			node.ID: {alloc},
   692  		},
   693  	}
   694  
   695  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   696  	if err != nil {
   697  		t.Fatalf("err: %v", err)
   698  	}
   699  	if fit {
   700  		t.Fatalf("bad")
   701  	}
   702  	if reason == "" {
   703  		t.Fatalf("bad")
   704  	}
   705  }
   706  
   707  func TestPlanApply_EvalNodePlan_NodeDrain(t *testing.T) {
   708  	t.Parallel()
   709  	state := testStateStore(t)
   710  	node := mock.Node()
   711  	node.Drain = true
   712  	state.UpsertNode(1000, node)
   713  	snap, _ := state.Snapshot()
   714  
   715  	alloc := mock.Alloc()
   716  	plan := &structs.Plan{
   717  		Job: alloc.Job,
   718  		NodeAllocation: map[string][]*structs.Allocation{
   719  			node.ID: {alloc},
   720  		},
   721  	}
   722  
   723  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   724  	if err != nil {
   725  		t.Fatalf("err: %v", err)
   726  	}
   727  	if fit {
   728  		t.Fatalf("bad")
   729  	}
   730  	if reason == "" {
   731  		t.Fatalf("bad")
   732  	}
   733  }
   734  
   735  func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) {
   736  	t.Parallel()
   737  	state := testStateStore(t)
   738  	snap, _ := state.Snapshot()
   739  
   740  	nodeID := "12345678-abcd-efab-cdef-123456789abc"
   741  	alloc := mock.Alloc()
   742  	plan := &structs.Plan{
   743  		Job: alloc.Job,
   744  		NodeAllocation: map[string][]*structs.Allocation{
   745  			nodeID: {alloc},
   746  		},
   747  	}
   748  
   749  	fit, reason, err := evaluateNodePlan(snap, plan, nodeID)
   750  	if err != nil {
   751  		t.Fatalf("err: %v", err)
   752  	}
   753  	if fit {
   754  		t.Fatalf("bad")
   755  	}
   756  	if reason == "" {
   757  		t.Fatalf("bad")
   758  	}
   759  }
   760  
   761  func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) {
   762  	t.Parallel()
   763  	alloc := mock.Alloc()
   764  	state := testStateStore(t)
   765  	node := mock.Node()
   766  	node.ReservedResources = nil
   767  	alloc.NodeID = node.ID
   768  	alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
   769  	state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))
   770  	state.UpsertNode(1000, node)
   771  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   772  
   773  	alloc2 := mock.Alloc()
   774  	alloc2.NodeID = node.ID
   775  	state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID))
   776  
   777  	snap, _ := state.Snapshot()
   778  	plan := &structs.Plan{
   779  		Job: alloc.Job,
   780  		NodeAllocation: map[string][]*structs.Allocation{
   781  			node.ID: {alloc2},
   782  		},
   783  	}
   784  
   785  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   786  	if err != nil {
   787  		t.Fatalf("err: %v", err)
   788  	}
   789  	if fit {
   790  		t.Fatalf("bad")
   791  	}
   792  	if reason == "" {
   793  		t.Fatalf("bad")
   794  	}
   795  }
   796  
   797  // Test that we detect device oversubscription
   798  func TestPlanApply_EvalNodePlan_NodeFull_Device(t *testing.T) {
   799  	t.Parallel()
   800  	require := require.New(t)
   801  	alloc := mock.Alloc()
   802  	state := testStateStore(t)
   803  	node := mock.NvidiaNode()
   804  	node.ReservedResources = nil
   805  
   806  	nvidia0 := node.NodeResources.Devices[0].Instances[0].ID
   807  
   808  	// Have the allocation use a Nvidia device
   809  	alloc.NodeID = node.ID
   810  	alloc.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{
   811  		{
   812  			Type:      "gpu",
   813  			Vendor:    "nvidia",
   814  			Name:      "1080ti",
   815  			DeviceIDs: []string{nvidia0},
   816  		},
   817  	}
   818  
   819  	state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))
   820  	state.UpsertNode(1000, node)
   821  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   822  
   823  	// Alloc2 tries to use the same device
   824  	alloc2 := mock.Alloc()
   825  	alloc2.AllocatedResources.Tasks["web"].Networks = nil
   826  	alloc2.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{
   827  		{
   828  			Type:      "gpu",
   829  			Vendor:    "nvidia",
   830  			Name:      "1080ti",
   831  			DeviceIDs: []string{nvidia0},
   832  		},
   833  	}
   834  	alloc2.NodeID = node.ID
   835  	state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID))
   836  
   837  	snap, _ := state.Snapshot()
   838  	plan := &structs.Plan{
   839  		Job: alloc.Job,
   840  		NodeAllocation: map[string][]*structs.Allocation{
   841  			node.ID: {alloc2},
   842  		},
   843  	}
   844  
   845  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   846  	require.NoError(err)
   847  	require.False(fit)
   848  	require.Equal("device oversubscribed", reason)
   849  }
   850  
   851  func TestPlanApply_EvalNodePlan_UpdateExisting(t *testing.T) {
   852  	t.Parallel()
   853  	alloc := mock.Alloc()
   854  	state := testStateStore(t)
   855  	node := mock.Node()
   856  	node.ReservedResources = nil
   857  	node.Reserved = nil
   858  	alloc.NodeID = node.ID
   859  	alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
   860  	state.UpsertNode(1000, node)
   861  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   862  	snap, _ := state.Snapshot()
   863  
   864  	plan := &structs.Plan{
   865  		Job: alloc.Job,
   866  		NodeAllocation: map[string][]*structs.Allocation{
   867  			node.ID: {alloc},
   868  		},
   869  	}
   870  
   871  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   872  	if err != nil {
   873  		t.Fatalf("err: %v", err)
   874  	}
   875  	if !fit {
   876  		t.Fatalf("bad")
   877  	}
   878  	if reason != "" {
   879  		t.Fatalf("bad")
   880  	}
   881  }
   882  
   883  func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) {
   884  	t.Parallel()
   885  	alloc := mock.Alloc()
   886  	state := testStateStore(t)
   887  	node := mock.Node()
   888  	node.ReservedResources = nil
   889  	alloc.NodeID = node.ID
   890  	alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
   891  	state.UpsertNode(1000, node)
   892  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   893  	snap, _ := state.Snapshot()
   894  
   895  	allocEvict := new(structs.Allocation)
   896  	*allocEvict = *alloc
   897  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   898  	alloc2 := mock.Alloc()
   899  	plan := &structs.Plan{
   900  		Job: alloc.Job,
   901  		NodeUpdate: map[string][]*structs.Allocation{
   902  			node.ID: {allocEvict},
   903  		},
   904  		NodeAllocation: map[string][]*structs.Allocation{
   905  			node.ID: {alloc2},
   906  		},
   907  	}
   908  
   909  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   910  	if err != nil {
   911  		t.Fatalf("err: %v", err)
   912  	}
   913  	if !fit {
   914  		t.Fatalf("bad")
   915  	}
   916  	if reason != "" {
   917  		t.Fatalf("bad")
   918  	}
   919  }
   920  
   921  func TestPlanApply_EvalNodePlan_NodeFull_AllocEvict(t *testing.T) {
   922  	t.Parallel()
   923  	alloc := mock.Alloc()
   924  	state := testStateStore(t)
   925  	node := mock.Node()
   926  	node.ReservedResources = nil
   927  	alloc.NodeID = node.ID
   928  	alloc.DesiredStatus = structs.AllocDesiredStatusEvict
   929  	alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
   930  	state.UpsertNode(1000, node)
   931  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   932  	snap, _ := state.Snapshot()
   933  
   934  	alloc2 := mock.Alloc()
   935  	plan := &structs.Plan{
   936  		Job: alloc.Job,
   937  		NodeAllocation: map[string][]*structs.Allocation{
   938  			node.ID: {alloc2},
   939  		},
   940  	}
   941  
   942  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   943  	if err != nil {
   944  		t.Fatalf("err: %v", err)
   945  	}
   946  	if !fit {
   947  		t.Fatalf("bad")
   948  	}
   949  	if reason != "" {
   950  		t.Fatalf("bad")
   951  	}
   952  }
   953  
   954  func TestPlanApply_EvalNodePlan_NodeDown_EvictOnly(t *testing.T) {
   955  	t.Parallel()
   956  	alloc := mock.Alloc()
   957  	state := testStateStore(t)
   958  	node := mock.Node()
   959  	alloc.NodeID = node.ID
   960  	alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources)
   961  	node.ReservedResources = nil
   962  	node.Status = structs.NodeStatusDown
   963  	state.UpsertNode(1000, node)
   964  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   965  	snap, _ := state.Snapshot()
   966  
   967  	allocEvict := new(structs.Allocation)
   968  	*allocEvict = *alloc
   969  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   970  	plan := &structs.Plan{
   971  		Job: alloc.Job,
   972  		NodeUpdate: map[string][]*structs.Allocation{
   973  			node.ID: {allocEvict},
   974  		},
   975  	}
   976  
   977  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   978  	if err != nil {
   979  		t.Fatalf("err: %v", err)
   980  	}
   981  	if !fit {
   982  		t.Fatalf("bad")
   983  	}
   984  	if reason != "" {
   985  		t.Fatalf("bad")
   986  	}
   987  }