github.com/banmanh482/nomad@v0.11.8/scheduler/context_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hashicorp/nomad/helper/testlog"
     7  	"github.com/hashicorp/nomad/helper/uuid"
     8  	"github.com/hashicorp/nomad/nomad/mock"
     9  	"github.com/hashicorp/nomad/nomad/state"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func testContext(t testing.TB) (*state.StateStore, *EvalContext) {
    15  	state := state.TestStateStore(t)
    16  	plan := &structs.Plan{
    17  		NodeUpdate:      make(map[string][]*structs.Allocation),
    18  		NodeAllocation:  make(map[string][]*structs.Allocation),
    19  		NodePreemptions: make(map[string][]*structs.Allocation),
    20  	}
    21  
    22  	logger := testlog.HCLogger(t)
    23  
    24  	ctx := NewEvalContext(state, plan, logger)
    25  	return state, ctx
    26  }
    27  
    28  func TestEvalContext_ProposedAlloc(t *testing.T) {
    29  	state, ctx := testContext(t)
    30  	nodes := []*RankedNode{
    31  		{
    32  			Node: &structs.Node{
    33  				// Perfect fit
    34  				ID: uuid.Generate(),
    35  				NodeResources: &structs.NodeResources{
    36  					Cpu: structs.NodeCpuResources{
    37  						CpuShares: 2048,
    38  					},
    39  					Memory: structs.NodeMemoryResources{
    40  						MemoryMB: 2048,
    41  					},
    42  				},
    43  			},
    44  		},
    45  		{
    46  			Node: &structs.Node{
    47  				// Perfect fit
    48  				ID: uuid.Generate(),
    49  				NodeResources: &structs.NodeResources{
    50  					Cpu: structs.NodeCpuResources{
    51  						CpuShares: 2048,
    52  					},
    53  					Memory: structs.NodeMemoryResources{
    54  						MemoryMB: 2048,
    55  					},
    56  				},
    57  			},
    58  		},
    59  	}
    60  
    61  	// Add existing allocations
    62  	j1, j2 := mock.Job(), mock.Job()
    63  	alloc1 := &structs.Allocation{
    64  		ID:        uuid.Generate(),
    65  		Namespace: structs.DefaultNamespace,
    66  		EvalID:    uuid.Generate(),
    67  		NodeID:    nodes[0].Node.ID,
    68  		JobID:     j1.ID,
    69  		Job:       j1,
    70  		AllocatedResources: &structs.AllocatedResources{
    71  			Tasks: map[string]*structs.AllocatedTaskResources{
    72  				"web": {
    73  					Cpu: structs.AllocatedCpuResources{
    74  						CpuShares: 2048,
    75  					},
    76  					Memory: structs.AllocatedMemoryResources{
    77  						MemoryMB: 2048,
    78  					},
    79  				},
    80  			},
    81  		},
    82  		DesiredStatus: structs.AllocDesiredStatusRun,
    83  		ClientStatus:  structs.AllocClientStatusPending,
    84  		TaskGroup:     "web",
    85  	}
    86  	alloc2 := &structs.Allocation{
    87  		ID:        uuid.Generate(),
    88  		Namespace: structs.DefaultNamespace,
    89  		EvalID:    uuid.Generate(),
    90  		NodeID:    nodes[1].Node.ID,
    91  		JobID:     j2.ID,
    92  		Job:       j2,
    93  		AllocatedResources: &structs.AllocatedResources{
    94  			Tasks: map[string]*structs.AllocatedTaskResources{
    95  				"web": {
    96  					Cpu: structs.AllocatedCpuResources{
    97  						CpuShares: 1024,
    98  					},
    99  					Memory: structs.AllocatedMemoryResources{
   100  						MemoryMB: 1024,
   101  					},
   102  				},
   103  			},
   104  		},
   105  		DesiredStatus: structs.AllocDesiredStatusRun,
   106  		ClientStatus:  structs.AllocClientStatusPending,
   107  		TaskGroup:     "web",
   108  	}
   109  	require.NoError(t, state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)))
   110  	require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)))
   111  	require.NoError(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2}))
   112  
   113  	// Add a planned eviction to alloc1
   114  	plan := ctx.Plan()
   115  	plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{alloc1}
   116  
   117  	// Add a planned placement to node1
   118  	plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{
   119  		{
   120  			AllocatedResources: &structs.AllocatedResources{
   121  				Tasks: map[string]*structs.AllocatedTaskResources{
   122  					"web": {
   123  						Cpu: structs.AllocatedCpuResources{
   124  							CpuShares: 1024,
   125  						},
   126  						Memory: structs.AllocatedMemoryResources{
   127  							MemoryMB: 1024,
   128  						},
   129  					},
   130  				},
   131  			},
   132  		},
   133  	}
   134  
   135  	proposed, err := ctx.ProposedAllocs(nodes[0].Node.ID)
   136  	if err != nil {
   137  		t.Fatalf("err: %v", err)
   138  	}
   139  	if len(proposed) != 0 {
   140  		t.Fatalf("bad: %#v", proposed)
   141  	}
   142  
   143  	proposed, err = ctx.ProposedAllocs(nodes[1].Node.ID)
   144  	if err != nil {
   145  		t.Fatalf("err: %v", err)
   146  	}
   147  	if len(proposed) != 2 {
   148  		t.Fatalf("bad: %#v", proposed)
   149  	}
   150  }
   151  
   152  // TestEvalContext_ProposedAlloc_EvictPreempt asserts both Evicted and
   153  // Preempted allocs are removed from the allocs propsed for a node.
   154  //
   155  // See https://github.com/hashicorp/nomad/issues/6787
   156  //
   157  func TestEvalContext_ProposedAlloc_EvictPreempt(t *testing.T) {
   158  	t.Parallel()
   159  	state, ctx := testContext(t)
   160  	nodes := []*RankedNode{
   161  		{
   162  			Node: &structs.Node{
   163  				ID: uuid.Generate(),
   164  				NodeResources: &structs.NodeResources{
   165  					Cpu: structs.NodeCpuResources{
   166  						CpuShares: 1024 * 3,
   167  					},
   168  					Memory: structs.NodeMemoryResources{
   169  						MemoryMB: 1024 * 3,
   170  					},
   171  				},
   172  			},
   173  		},
   174  	}
   175  
   176  	// Add existing allocations
   177  	j1, j2, j3 := mock.Job(), mock.Job(), mock.Job()
   178  	allocEvict := &structs.Allocation{
   179  		ID:        uuid.Generate(),
   180  		Namespace: structs.DefaultNamespace,
   181  		EvalID:    uuid.Generate(),
   182  		NodeID:    nodes[0].Node.ID,
   183  		JobID:     j1.ID,
   184  		Job:       j1,
   185  		AllocatedResources: &structs.AllocatedResources{
   186  			Tasks: map[string]*structs.AllocatedTaskResources{
   187  				"web": {
   188  					Cpu: structs.AllocatedCpuResources{
   189  						CpuShares: 1024,
   190  					},
   191  					Memory: structs.AllocatedMemoryResources{
   192  						MemoryMB: 1024,
   193  					},
   194  				},
   195  			},
   196  		},
   197  		DesiredStatus: structs.AllocDesiredStatusRun,
   198  		ClientStatus:  structs.AllocClientStatusPending,
   199  		TaskGroup:     "web",
   200  	}
   201  	allocPreempt := &structs.Allocation{
   202  		ID:        uuid.Generate(),
   203  		Namespace: structs.DefaultNamespace,
   204  		EvalID:    uuid.Generate(),
   205  		NodeID:    nodes[0].Node.ID,
   206  		JobID:     j2.ID,
   207  		Job:       j2,
   208  		AllocatedResources: &structs.AllocatedResources{
   209  			Tasks: map[string]*structs.AllocatedTaskResources{
   210  				"web": {
   211  					Cpu: structs.AllocatedCpuResources{
   212  						CpuShares: 1024,
   213  					},
   214  					Memory: structs.AllocatedMemoryResources{
   215  						MemoryMB: 1024,
   216  					},
   217  				},
   218  			},
   219  		},
   220  		DesiredStatus: structs.AllocDesiredStatusRun,
   221  		ClientStatus:  structs.AllocClientStatusPending,
   222  		TaskGroup:     "web",
   223  	}
   224  	allocPropose := &structs.Allocation{
   225  		ID:        uuid.Generate(),
   226  		Namespace: structs.DefaultNamespace,
   227  		EvalID:    uuid.Generate(),
   228  		NodeID:    nodes[0].Node.ID,
   229  		JobID:     j3.ID,
   230  		Job:       j3,
   231  		AllocatedResources: &structs.AllocatedResources{
   232  			Tasks: map[string]*structs.AllocatedTaskResources{
   233  				"web": {
   234  					Cpu: structs.AllocatedCpuResources{
   235  						CpuShares: 1024,
   236  					},
   237  					Memory: structs.AllocatedMemoryResources{
   238  						MemoryMB: 1024,
   239  					},
   240  				},
   241  			},
   242  		},
   243  		DesiredStatus: structs.AllocDesiredStatusRun,
   244  		ClientStatus:  structs.AllocClientStatusPending,
   245  		TaskGroup:     "web",
   246  	}
   247  	require.NoError(t, state.UpsertJobSummary(998, mock.JobSummary(allocEvict.JobID)))
   248  	require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(allocPreempt.JobID)))
   249  	require.NoError(t, state.UpsertJobSummary(999, mock.JobSummary(allocPropose.JobID)))
   250  	require.NoError(t, state.UpsertAllocs(1000, []*structs.Allocation{allocEvict, allocPreempt, allocPropose}))
   251  
   252  	// Plan to evict one alloc and preempt another
   253  	plan := ctx.Plan()
   254  	plan.NodePreemptions[nodes[0].Node.ID] = []*structs.Allocation{allocEvict}
   255  	plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{allocPreempt}
   256  
   257  	proposed, err := ctx.ProposedAllocs(nodes[0].Node.ID)
   258  	require.NoError(t, err)
   259  	require.Len(t, proposed, 1)
   260  }
   261  
   262  func TestEvalEligibility_JobStatus(t *testing.T) {
   263  	e := NewEvalEligibility()
   264  	cc := "v1:100"
   265  
   266  	// Get the job before its been set.
   267  	if status := e.JobStatus(cc); status != EvalComputedClassUnknown {
   268  		t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassUnknown)
   269  	}
   270  
   271  	// Set the job and get its status.
   272  	e.SetJobEligibility(false, cc)
   273  	if status := e.JobStatus(cc); status != EvalComputedClassIneligible {
   274  		t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassIneligible)
   275  	}
   276  
   277  	e.SetJobEligibility(true, cc)
   278  	if status := e.JobStatus(cc); status != EvalComputedClassEligible {
   279  		t.Fatalf("JobStatus() returned %v; want %v", status, EvalComputedClassEligible)
   280  	}
   281  }
   282  
   283  func TestEvalEligibility_TaskGroupStatus(t *testing.T) {
   284  	e := NewEvalEligibility()
   285  	cc := "v1:100"
   286  	tg := "foo"
   287  
   288  	// Get the tg before its been set.
   289  	if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassUnknown {
   290  		t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassUnknown)
   291  	}
   292  
   293  	// Set the tg and get its status.
   294  	e.SetTaskGroupEligibility(false, tg, cc)
   295  	if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassIneligible {
   296  		t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassIneligible)
   297  	}
   298  
   299  	e.SetTaskGroupEligibility(true, tg, cc)
   300  	if status := e.TaskGroupStatus(tg, cc); status != EvalComputedClassEligible {
   301  		t.Fatalf("TaskGroupStatus() returned %v; want %v", status, EvalComputedClassEligible)
   302  	}
   303  }
   304  
   305  func TestEvalEligibility_SetJob(t *testing.T) {
   306  	e := NewEvalEligibility()
   307  	ne1 := &structs.Constraint{
   308  		LTarget: "${attr.kernel.name}",
   309  		RTarget: "linux",
   310  		Operand: "=",
   311  	}
   312  	e1 := &structs.Constraint{
   313  		LTarget: "${attr.unique.kernel.name}",
   314  		RTarget: "linux",
   315  		Operand: "=",
   316  	}
   317  	e2 := &structs.Constraint{
   318  		LTarget: "${meta.unique.key_foo}",
   319  		RTarget: "linux",
   320  		Operand: "<",
   321  	}
   322  	e3 := &structs.Constraint{
   323  		LTarget: "${meta.unique.key_foo}",
   324  		RTarget: "Windows",
   325  		Operand: "<",
   326  	}
   327  
   328  	job := mock.Job()
   329  	jobCon := []*structs.Constraint{ne1, e1, e2}
   330  	job.Constraints = jobCon
   331  
   332  	// Set the task constraints
   333  	tg := job.TaskGroups[0]
   334  	tg.Constraints = []*structs.Constraint{e1}
   335  	tg.Tasks[0].Constraints = []*structs.Constraint{e3}
   336  
   337  	e.SetJob(job)
   338  	if !e.HasEscaped() {
   339  		t.Fatalf("HasEscaped() should be true")
   340  	}
   341  
   342  	if !e.jobEscaped {
   343  		t.Fatalf("SetJob() should mark job as escaped")
   344  	}
   345  	if escaped, ok := e.tgEscapedConstraints[tg.Name]; !ok || !escaped {
   346  		t.Fatalf("SetJob() should mark task group as escaped")
   347  	}
   348  }
   349  
   350  func TestEvalEligibility_GetClasses(t *testing.T) {
   351  	e := NewEvalEligibility()
   352  	e.SetJobEligibility(true, "v1:1")
   353  	e.SetJobEligibility(false, "v1:2")
   354  	e.SetTaskGroupEligibility(true, "foo", "v1:3")
   355  	e.SetTaskGroupEligibility(false, "bar", "v1:4")
   356  	e.SetTaskGroupEligibility(true, "bar", "v1:5")
   357  
   358  	// Mark an existing eligible class as ineligible in the TG.
   359  	e.SetTaskGroupEligibility(false, "fizz", "v1:1")
   360  	e.SetTaskGroupEligibility(false, "fizz", "v1:3")
   361  
   362  	expClasses := map[string]bool{
   363  		"v1:1": false,
   364  		"v1:2": false,
   365  		"v1:3": true,
   366  		"v1:4": false,
   367  		"v1:5": true,
   368  	}
   369  
   370  	actClasses := e.GetClasses()
   371  	require.Equal(t, expClasses, actClasses)
   372  }
   373  func TestEvalEligibility_GetClasses_JobEligible_TaskGroupIneligible(t *testing.T) {
   374  	e := NewEvalEligibility()
   375  	e.SetJobEligibility(true, "v1:1")
   376  	e.SetTaskGroupEligibility(false, "foo", "v1:1")
   377  
   378  	e.SetJobEligibility(true, "v1:2")
   379  	e.SetTaskGroupEligibility(false, "foo", "v1:2")
   380  	e.SetTaskGroupEligibility(true, "bar", "v1:2")
   381  
   382  	e.SetJobEligibility(true, "v1:3")
   383  	e.SetTaskGroupEligibility(false, "foo", "v1:3")
   384  	e.SetTaskGroupEligibility(false, "bar", "v1:3")
   385  
   386  	expClasses := map[string]bool{
   387  		"v1:1": false,
   388  		"v1:2": true,
   389  		"v1:3": false,
   390  	}
   391  
   392  	actClasses := e.GetClasses()
   393  	require.Equal(t, expClasses, actClasses)
   394  }