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