github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/plan_apply_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	memdb "github.com/hashicorp/go-memdb"
     8  	"github.com/hashicorp/nomad/helper/testlog"
     9  	"github.com/hashicorp/nomad/helper/uuid"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/hashicorp/nomad/testutil"
    13  	"github.com/hashicorp/raft"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  const (
    18  	// workerPoolSize is the size of the worker pool
    19  	workerPoolSize = 2
    20  )
    21  
    22  // planWaitFuture is used to wait for the Raft future to complete
    23  func planWaitFuture(future raft.ApplyFuture) (uint64, error) {
    24  	if err := future.Error(); err != nil {
    25  		return 0, err
    26  	}
    27  	return future.Index(), nil
    28  }
    29  
    30  func testRegisterNode(t *testing.T, s *Server, n *structs.Node) {
    31  	// Create the register request
    32  	req := &structs.NodeRegisterRequest{
    33  		Node:         n,
    34  		WriteRequest: structs.WriteRequest{Region: "global"},
    35  	}
    36  
    37  	// Fetch the response
    38  	var resp structs.NodeUpdateResponse
    39  	if err := s.RPC("Node.Register", req, &resp); err != nil {
    40  		t.Fatalf("err: %v", err)
    41  	}
    42  	if resp.Index == 0 {
    43  		t.Fatalf("bad index: %d", resp.Index)
    44  	}
    45  }
    46  
    47  func testRegisterJob(t *testing.T, s *Server, j *structs.Job) {
    48  	// Create the register request
    49  	req := &structs.JobRegisterRequest{
    50  		Job:          j,
    51  		WriteRequest: structs.WriteRequest{Region: "global"},
    52  	}
    53  
    54  	// Fetch the response
    55  	var resp structs.JobRegisterResponse
    56  	if err := s.RPC("Job.Register", req, &resp); err != nil {
    57  		t.Fatalf("err: %v", err)
    58  	}
    59  	if resp.Index == 0 {
    60  		t.Fatalf("bad index: %d", resp.Index)
    61  	}
    62  }
    63  
    64  func TestPlanApply_applyPlan(t *testing.T) {
    65  	t.Parallel()
    66  	s1 := TestServer(t, nil)
    67  	defer s1.Shutdown()
    68  	testutil.WaitForLeader(t, s1.RPC)
    69  
    70  	// Register node
    71  	node := mock.Node()
    72  	testRegisterNode(t, s1, node)
    73  
    74  	// Register a fake deployment
    75  	oldDeployment := mock.Deployment()
    76  	if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil {
    77  		t.Fatalf("UpsertDeployment failed: %v", err)
    78  	}
    79  
    80  	// Create a deployment
    81  	dnew := mock.Deployment()
    82  
    83  	// Create a deployment update for the old deployment id
    84  	desiredStatus, desiredStatusDescription := "foo", "bar"
    85  	updates := []*structs.DeploymentStatusUpdate{
    86  		{
    87  			DeploymentID:      oldDeployment.ID,
    88  			Status:            desiredStatus,
    89  			StatusDescription: desiredStatusDescription,
    90  		},
    91  	}
    92  
    93  	// Register alloc, deployment and deployment update
    94  	alloc := mock.Alloc()
    95  	s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))
    96  	// Create an eval
    97  	eval := mock.Eval()
    98  	eval.JobID = alloc.JobID
    99  	if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil {
   100  		t.Fatalf("err: %v", err)
   101  	}
   102  
   103  	planRes := &structs.PlanResult{
   104  		NodeAllocation: map[string][]*structs.Allocation{
   105  			node.ID: {alloc},
   106  		},
   107  		Deployment:        dnew,
   108  		DeploymentUpdates: updates,
   109  	}
   110  
   111  	// Snapshot the state
   112  	snap, err := s1.State().Snapshot()
   113  	if err != nil {
   114  		t.Fatalf("err: %v", err)
   115  	}
   116  
   117  	// Create the plan with a deployment
   118  	plan := &structs.Plan{
   119  		Job:               alloc.Job,
   120  		Deployment:        dnew,
   121  		DeploymentUpdates: updates,
   122  		EvalID:            eval.ID,
   123  	}
   124  
   125  	// Apply the plan
   126  	future, err := s1.applyPlan(plan, planRes, snap)
   127  	assert := assert.New(t)
   128  	assert.Nil(err)
   129  
   130  	// Verify our optimistic snapshot is updated
   131  	ws := memdb.NewWatchSet()
   132  	allocOut, err := snap.AllocByID(ws, alloc.ID)
   133  	assert.Nil(err)
   134  	assert.NotNil(allocOut)
   135  
   136  	deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID)
   137  	assert.Nil(err)
   138  	assert.NotNil(deploymentOut)
   139  
   140  	// Check plan does apply cleanly
   141  	index, err := planWaitFuture(future)
   142  	assert.Nil(err)
   143  	assert.NotEqual(0, index)
   144  
   145  	// Lookup the allocation
   146  	fsmState := s1.fsm.State()
   147  	allocOut, err = fsmState.AllocByID(ws, alloc.ID)
   148  	assert.Nil(err)
   149  	assert.NotNil(allocOut)
   150  	assert.True(allocOut.CreateTime > 0)
   151  	assert.True(allocOut.ModifyTime > 0)
   152  	assert.Equal(allocOut.CreateTime, allocOut.ModifyTime)
   153  
   154  	// Lookup the new deployment
   155  	dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID)
   156  	assert.Nil(err)
   157  	assert.NotNil(dout)
   158  
   159  	// Lookup the updated deployment
   160  	dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID)
   161  	assert.Nil(err)
   162  	assert.NotNil(dout2)
   163  	assert.Equal(desiredStatus, dout2.Status)
   164  	assert.Equal(desiredStatusDescription, dout2.StatusDescription)
   165  
   166  	// Lookup updated eval
   167  	evalOut, err := fsmState.EvalByID(ws, eval.ID)
   168  	assert.Nil(err)
   169  	assert.NotNil(evalOut)
   170  	assert.Equal(index, evalOut.ModifyIndex)
   171  
   172  	// Evict alloc, Register alloc2
   173  	allocEvict := new(structs.Allocation)
   174  	*allocEvict = *alloc
   175  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   176  	job := allocEvict.Job
   177  	allocEvict.Job = nil
   178  	alloc2 := mock.Alloc()
   179  	s1.State().UpsertJobSummary(1500, mock.JobSummary(alloc2.JobID))
   180  	planRes = &structs.PlanResult{
   181  		NodeUpdate: map[string][]*structs.Allocation{
   182  			node.ID: {allocEvict},
   183  		},
   184  		NodeAllocation: map[string][]*structs.Allocation{
   185  			node.ID: {alloc2},
   186  		},
   187  	}
   188  
   189  	// Snapshot the state
   190  	snap, err = s1.State().Snapshot()
   191  	assert.Nil(err)
   192  
   193  	// Apply the plan
   194  	plan = &structs.Plan{
   195  		Job:    job,
   196  		EvalID: eval.ID,
   197  	}
   198  	future, err = s1.applyPlan(plan, planRes, snap)
   199  	assert.Nil(err)
   200  
   201  	// Check that our optimistic view is updated
   202  	out, _ := snap.AllocByID(ws, allocEvict.ID)
   203  	assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus)
   204  
   205  	// Verify plan applies cleanly
   206  	index, err = planWaitFuture(future)
   207  	assert.Nil(err)
   208  	assert.NotEqual(0, index)
   209  
   210  	// Lookup the allocation
   211  	allocOut, err = s1.fsm.State().AllocByID(ws, alloc.ID)
   212  	assert.Nil(err)
   213  	assert.Equal(structs.AllocDesiredStatusEvict, allocOut.DesiredStatus)
   214  	assert.NotNil(allocOut.Job)
   215  	assert.True(allocOut.ModifyTime > 0)
   216  
   217  	// Lookup the allocation
   218  	allocOut, err = s1.fsm.State().AllocByID(ws, alloc2.ID)
   219  	assert.Nil(err)
   220  	assert.NotNil(allocOut)
   221  	assert.NotNil(allocOut.Job)
   222  
   223  	// Lookup updated eval
   224  	evalOut, err = fsmState.EvalByID(ws, eval.ID)
   225  	assert.Nil(err)
   226  	assert.NotNil(evalOut)
   227  	assert.Equal(index, evalOut.ModifyIndex)
   228  }
   229  
   230  func TestPlanApply_EvalPlan_Simple(t *testing.T) {
   231  	t.Parallel()
   232  	state := testStateStore(t)
   233  	node := mock.Node()
   234  	state.UpsertNode(1000, node)
   235  	snap, _ := state.Snapshot()
   236  
   237  	alloc := mock.Alloc()
   238  	plan := &structs.Plan{
   239  		Job: alloc.Job,
   240  		NodeAllocation: map[string][]*structs.Allocation{
   241  			node.ID: {alloc},
   242  		},
   243  		Deployment: mock.Deployment(),
   244  		DeploymentUpdates: []*structs.DeploymentStatusUpdate{
   245  			{
   246  				DeploymentID:      uuid.Generate(),
   247  				Status:            "foo",
   248  				StatusDescription: "bar",
   249  			},
   250  		},
   251  	}
   252  
   253  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   254  	defer pool.Shutdown()
   255  
   256  	result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t))
   257  	if err != nil {
   258  		t.Fatalf("err: %v", err)
   259  	}
   260  	if result == nil {
   261  		t.Fatalf("missing result")
   262  	}
   263  	if !reflect.DeepEqual(result.NodeAllocation, plan.NodeAllocation) {
   264  		t.Fatalf("incorrect node allocations")
   265  	}
   266  	if !reflect.DeepEqual(result.Deployment, plan.Deployment) {
   267  		t.Fatalf("incorrect deployment")
   268  	}
   269  	if !reflect.DeepEqual(result.DeploymentUpdates, plan.DeploymentUpdates) {
   270  		t.Fatalf("incorrect deployment updates")
   271  	}
   272  }
   273  
   274  func TestPlanApply_EvalPlan_Partial(t *testing.T) {
   275  	t.Parallel()
   276  	state := testStateStore(t)
   277  	node := mock.Node()
   278  	state.UpsertNode(1000, node)
   279  	node2 := mock.Node()
   280  	state.UpsertNode(1001, node2)
   281  	snap, _ := state.Snapshot()
   282  
   283  	alloc := mock.Alloc()
   284  	alloc2 := mock.Alloc() // Ensure alloc2 does not fit
   285  	alloc2.Resources = node2.Resources
   286  
   287  	// Create a deployment where the allocs are markeda as canaries
   288  	d := mock.Deployment()
   289  	d.TaskGroups["web"].PlacedCanaries = []string{alloc.ID, alloc2.ID}
   290  
   291  	plan := &structs.Plan{
   292  		Job: alloc.Job,
   293  		NodeAllocation: map[string][]*structs.Allocation{
   294  			node.ID:  {alloc},
   295  			node2.ID: {alloc2},
   296  		},
   297  		Deployment: d,
   298  	}
   299  
   300  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   301  	defer pool.Shutdown()
   302  
   303  	result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t))
   304  	if err != nil {
   305  		t.Fatalf("err: %v", err)
   306  	}
   307  	if result == nil {
   308  		t.Fatalf("missing result")
   309  	}
   310  
   311  	if _, ok := result.NodeAllocation[node.ID]; !ok {
   312  		t.Fatalf("should allow alloc")
   313  	}
   314  	if _, ok := result.NodeAllocation[node2.ID]; ok {
   315  		t.Fatalf("should not allow alloc2")
   316  	}
   317  
   318  	// Check the deployment was updated
   319  	if result.Deployment == nil || len(result.Deployment.TaskGroups) == 0 {
   320  		t.Fatalf("bad: %v", result.Deployment)
   321  	}
   322  	placedCanaries := result.Deployment.TaskGroups["web"].PlacedCanaries
   323  	if len(placedCanaries) != 1 || placedCanaries[0] != alloc.ID {
   324  		t.Fatalf("bad: %v", placedCanaries)
   325  	}
   326  
   327  	if result.RefreshIndex != 1001 {
   328  		t.Fatalf("bad: %d", result.RefreshIndex)
   329  	}
   330  }
   331  
   332  func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) {
   333  	t.Parallel()
   334  	state := testStateStore(t)
   335  	node := mock.Node()
   336  	state.UpsertNode(1000, node)
   337  	node2 := mock.Node()
   338  	state.UpsertNode(1001, node2)
   339  	snap, _ := state.Snapshot()
   340  
   341  	alloc := mock.Alloc()
   342  	alloc2 := mock.Alloc() // Ensure alloc2 does not fit
   343  	alloc2.Resources = node2.Resources
   344  	plan := &structs.Plan{
   345  		Job:       alloc.Job,
   346  		AllAtOnce: true, // Require all to make progress
   347  		NodeAllocation: map[string][]*structs.Allocation{
   348  			node.ID:  {alloc},
   349  			node2.ID: {alloc2},
   350  		},
   351  		Deployment: mock.Deployment(),
   352  		DeploymentUpdates: []*structs.DeploymentStatusUpdate{
   353  			{
   354  				DeploymentID:      uuid.Generate(),
   355  				Status:            "foo",
   356  				StatusDescription: "bar",
   357  			},
   358  		},
   359  	}
   360  
   361  	pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize)
   362  	defer pool.Shutdown()
   363  
   364  	result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t))
   365  	if err != nil {
   366  		t.Fatalf("err: %v", err)
   367  	}
   368  	if result == nil {
   369  		t.Fatalf("missing result")
   370  	}
   371  
   372  	if len(result.NodeAllocation) != 0 {
   373  		t.Fatalf("should not alloc: %v", result.NodeAllocation)
   374  	}
   375  	if result.RefreshIndex != 1001 {
   376  		t.Fatalf("bad: %d", result.RefreshIndex)
   377  	}
   378  	if result.Deployment != nil || len(result.DeploymentUpdates) != 0 {
   379  		t.Fatalf("bad: %v", result)
   380  	}
   381  }
   382  
   383  func TestPlanApply_EvalNodePlan_Simple(t *testing.T) {
   384  	t.Parallel()
   385  	state := testStateStore(t)
   386  	node := mock.Node()
   387  	state.UpsertNode(1000, node)
   388  	snap, _ := state.Snapshot()
   389  
   390  	alloc := mock.Alloc()
   391  	plan := &structs.Plan{
   392  		Job: alloc.Job,
   393  		NodeAllocation: map[string][]*structs.Allocation{
   394  			node.ID: {alloc},
   395  		},
   396  	}
   397  
   398  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   399  	if err != nil {
   400  		t.Fatalf("err: %v", err)
   401  	}
   402  	if !fit {
   403  		t.Fatalf("bad")
   404  	}
   405  	if reason != "" {
   406  		t.Fatalf("bad")
   407  	}
   408  }
   409  
   410  func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) {
   411  	t.Parallel()
   412  	state := testStateStore(t)
   413  	node := mock.Node()
   414  	node.Status = structs.NodeStatusInit
   415  	state.UpsertNode(1000, node)
   416  	snap, _ := state.Snapshot()
   417  
   418  	alloc := mock.Alloc()
   419  	plan := &structs.Plan{
   420  		Job: alloc.Job,
   421  		NodeAllocation: map[string][]*structs.Allocation{
   422  			node.ID: {alloc},
   423  		},
   424  	}
   425  
   426  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   427  	if err != nil {
   428  		t.Fatalf("err: %v", err)
   429  	}
   430  	if fit {
   431  		t.Fatalf("bad")
   432  	}
   433  	if reason == "" {
   434  		t.Fatalf("bad")
   435  	}
   436  }
   437  
   438  func TestPlanApply_EvalNodePlan_NodeDrain(t *testing.T) {
   439  	t.Parallel()
   440  	state := testStateStore(t)
   441  	node := mock.Node()
   442  	node.Drain = true
   443  	state.UpsertNode(1000, node)
   444  	snap, _ := state.Snapshot()
   445  
   446  	alloc := mock.Alloc()
   447  	plan := &structs.Plan{
   448  		Job: alloc.Job,
   449  		NodeAllocation: map[string][]*structs.Allocation{
   450  			node.ID: {alloc},
   451  		},
   452  	}
   453  
   454  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   455  	if err != nil {
   456  		t.Fatalf("err: %v", err)
   457  	}
   458  	if fit {
   459  		t.Fatalf("bad")
   460  	}
   461  	if reason == "" {
   462  		t.Fatalf("bad")
   463  	}
   464  }
   465  
   466  func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) {
   467  	t.Parallel()
   468  	state := testStateStore(t)
   469  	snap, _ := state.Snapshot()
   470  
   471  	nodeID := "12345678-abcd-efab-cdef-123456789abc"
   472  	alloc := mock.Alloc()
   473  	plan := &structs.Plan{
   474  		Job: alloc.Job,
   475  		NodeAllocation: map[string][]*structs.Allocation{
   476  			nodeID: {alloc},
   477  		},
   478  	}
   479  
   480  	fit, reason, err := evaluateNodePlan(snap, plan, nodeID)
   481  	if err != nil {
   482  		t.Fatalf("err: %v", err)
   483  	}
   484  	if fit {
   485  		t.Fatalf("bad")
   486  	}
   487  	if reason == "" {
   488  		t.Fatalf("bad")
   489  	}
   490  }
   491  
   492  func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) {
   493  	t.Parallel()
   494  	alloc := mock.Alloc()
   495  	state := testStateStore(t)
   496  	node := mock.Node()
   497  	alloc.NodeID = node.ID
   498  	node.Resources = alloc.Resources
   499  	node.Reserved = nil
   500  	state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))
   501  	state.UpsertNode(1000, node)
   502  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   503  
   504  	alloc2 := mock.Alloc()
   505  	alloc2.NodeID = node.ID
   506  	state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID))
   507  
   508  	snap, _ := state.Snapshot()
   509  	plan := &structs.Plan{
   510  		Job: alloc.Job,
   511  		NodeAllocation: map[string][]*structs.Allocation{
   512  			node.ID: {alloc2},
   513  		},
   514  	}
   515  
   516  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   517  	if err != nil {
   518  		t.Fatalf("err: %v", err)
   519  	}
   520  	if fit {
   521  		t.Fatalf("bad")
   522  	}
   523  	if reason == "" {
   524  		t.Fatalf("bad")
   525  	}
   526  }
   527  
   528  func TestPlanApply_EvalNodePlan_UpdateExisting(t *testing.T) {
   529  	t.Parallel()
   530  	alloc := mock.Alloc()
   531  	state := testStateStore(t)
   532  	node := mock.Node()
   533  	alloc.NodeID = node.ID
   534  	node.Resources = alloc.Resources
   535  	node.Reserved = nil
   536  	state.UpsertNode(1000, node)
   537  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   538  	snap, _ := state.Snapshot()
   539  
   540  	plan := &structs.Plan{
   541  		Job: alloc.Job,
   542  		NodeAllocation: map[string][]*structs.Allocation{
   543  			node.ID: {alloc},
   544  		},
   545  	}
   546  
   547  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   548  	if err != nil {
   549  		t.Fatalf("err: %v", err)
   550  	}
   551  	if !fit {
   552  		t.Fatalf("bad")
   553  	}
   554  	if reason != "" {
   555  		t.Fatalf("bad")
   556  	}
   557  }
   558  
   559  func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) {
   560  	t.Parallel()
   561  	alloc := mock.Alloc()
   562  	state := testStateStore(t)
   563  	node := mock.Node()
   564  	alloc.NodeID = node.ID
   565  	node.Resources = alloc.Resources
   566  	node.Reserved = nil
   567  	state.UpsertNode(1000, node)
   568  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   569  	snap, _ := state.Snapshot()
   570  
   571  	allocEvict := new(structs.Allocation)
   572  	*allocEvict = *alloc
   573  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   574  	alloc2 := mock.Alloc()
   575  	plan := &structs.Plan{
   576  		Job: alloc.Job,
   577  		NodeUpdate: map[string][]*structs.Allocation{
   578  			node.ID: {allocEvict},
   579  		},
   580  		NodeAllocation: map[string][]*structs.Allocation{
   581  			node.ID: {alloc2},
   582  		},
   583  	}
   584  
   585  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   586  	if err != nil {
   587  		t.Fatalf("err: %v", err)
   588  	}
   589  	if !fit {
   590  		t.Fatalf("bad")
   591  	}
   592  	if reason != "" {
   593  		t.Fatalf("bad")
   594  	}
   595  }
   596  
   597  func TestPlanApply_EvalNodePlan_NodeFull_AllocEvict(t *testing.T) {
   598  	t.Parallel()
   599  	alloc := mock.Alloc()
   600  	state := testStateStore(t)
   601  	node := mock.Node()
   602  	alloc.NodeID = node.ID
   603  	alloc.DesiredStatus = structs.AllocDesiredStatusEvict
   604  	node.Resources = alloc.Resources
   605  	node.Reserved = nil
   606  	state.UpsertNode(1000, node)
   607  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   608  	snap, _ := state.Snapshot()
   609  
   610  	alloc2 := mock.Alloc()
   611  	plan := &structs.Plan{
   612  		Job: alloc.Job,
   613  		NodeAllocation: map[string][]*structs.Allocation{
   614  			node.ID: {alloc2},
   615  		},
   616  	}
   617  
   618  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   619  	if err != nil {
   620  		t.Fatalf("err: %v", err)
   621  	}
   622  	if !fit {
   623  		t.Fatalf("bad")
   624  	}
   625  	if reason != "" {
   626  		t.Fatalf("bad")
   627  	}
   628  }
   629  
   630  func TestPlanApply_EvalNodePlan_NodeDown_EvictOnly(t *testing.T) {
   631  	t.Parallel()
   632  	alloc := mock.Alloc()
   633  	state := testStateStore(t)
   634  	node := mock.Node()
   635  	alloc.NodeID = node.ID
   636  	node.Resources = alloc.Resources
   637  	node.Reserved = nil
   638  	node.Status = structs.NodeStatusDown
   639  	state.UpsertNode(1000, node)
   640  	state.UpsertAllocs(1001, []*structs.Allocation{alloc})
   641  	snap, _ := state.Snapshot()
   642  
   643  	allocEvict := new(structs.Allocation)
   644  	*allocEvict = *alloc
   645  	allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict
   646  	plan := &structs.Plan{
   647  		Job: alloc.Job,
   648  		NodeUpdate: map[string][]*structs.Allocation{
   649  			node.ID: {allocEvict},
   650  		},
   651  	}
   652  
   653  	fit, reason, err := evaluateNodePlan(snap, plan, node.ID)
   654  	if err != nil {
   655  		t.Fatalf("err: %v", err)
   656  	}
   657  	if !fit {
   658  		t.Fatalf("bad")
   659  	}
   660  	if reason != "" {
   661  		t.Fatalf("bad")
   662  	}
   663  }