github.com/anuvu/nomad@v0.8.7-atom1/scheduler/rank_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/hashicorp/nomad/helper/uuid"
     7  	"github.com/hashicorp/nomad/nomad/mock"
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  	require "github.com/stretchr/testify/require"
    10  )
    11  
    12  func TestFeasibleRankIterator(t *testing.T) {
    13  	_, ctx := testContext(t)
    14  	var nodes []*structs.Node
    15  	for i := 0; i < 10; i++ {
    16  		nodes = append(nodes, mock.Node())
    17  	}
    18  	static := NewStaticIterator(ctx, nodes)
    19  
    20  	feasible := NewFeasibleRankIterator(ctx, static)
    21  
    22  	out := collectRanked(feasible)
    23  	if len(out) != len(nodes) {
    24  		t.Fatalf("bad: %v", out)
    25  	}
    26  }
    27  
    28  func TestBinPackIterator_NoExistingAlloc(t *testing.T) {
    29  	_, ctx := testContext(t)
    30  	nodes := []*RankedNode{
    31  		{
    32  			Node: &structs.Node{
    33  				// Perfect fit
    34  				Resources: &structs.Resources{
    35  					CPU:      2048,
    36  					MemoryMB: 2048,
    37  				},
    38  				Reserved: &structs.Resources{
    39  					CPU:      1024,
    40  					MemoryMB: 1024,
    41  				},
    42  			},
    43  		},
    44  		{
    45  			Node: &structs.Node{
    46  				// Overloaded
    47  				Resources: &structs.Resources{
    48  					CPU:      1024,
    49  					MemoryMB: 1024,
    50  				},
    51  				Reserved: &structs.Resources{
    52  					CPU:      512,
    53  					MemoryMB: 512,
    54  				},
    55  			},
    56  		},
    57  		{
    58  			Node: &structs.Node{
    59  				// 50% fit
    60  				Resources: &structs.Resources{
    61  					CPU:      4096,
    62  					MemoryMB: 4096,
    63  				},
    64  				Reserved: &structs.Resources{
    65  					CPU:      1024,
    66  					MemoryMB: 1024,
    67  				},
    68  			},
    69  		},
    70  	}
    71  	static := NewStaticRankIterator(ctx, nodes)
    72  
    73  	taskGroup := &structs.TaskGroup{
    74  		EphemeralDisk: &structs.EphemeralDisk{},
    75  		Tasks: []*structs.Task{
    76  			{
    77  				Name: "web",
    78  				Resources: &structs.Resources{
    79  					CPU:      1024,
    80  					MemoryMB: 1024,
    81  				},
    82  			},
    83  		},
    84  	}
    85  	binp := NewBinPackIterator(ctx, static, false, 0)
    86  	binp.SetTaskGroup(taskGroup)
    87  
    88  	out := collectRanked(binp)
    89  	if len(out) != 2 {
    90  		t.Fatalf("Bad: %v", out)
    91  	}
    92  	if out[0] != nodes[0] || out[1] != nodes[2] {
    93  		t.Fatalf("Bad: %v", out)
    94  	}
    95  
    96  	if out[0].Score != 18 {
    97  		t.Fatalf("Bad: %v", out[0])
    98  	}
    99  	if out[1].Score < 10 || out[1].Score > 16 {
   100  		t.Fatalf("Bad: %v", out[1])
   101  	}
   102  }
   103  
   104  func TestBinPackIterator_PlannedAlloc(t *testing.T) {
   105  	_, ctx := testContext(t)
   106  	nodes := []*RankedNode{
   107  		{
   108  			Node: &structs.Node{
   109  				// Perfect fit
   110  				ID: uuid.Generate(),
   111  				Resources: &structs.Resources{
   112  					CPU:      2048,
   113  					MemoryMB: 2048,
   114  				},
   115  			},
   116  		},
   117  		{
   118  			Node: &structs.Node{
   119  				// Perfect fit
   120  				ID: uuid.Generate(),
   121  				Resources: &structs.Resources{
   122  					CPU:      2048,
   123  					MemoryMB: 2048,
   124  				},
   125  			},
   126  		},
   127  	}
   128  	static := NewStaticRankIterator(ctx, nodes)
   129  
   130  	// Add a planned alloc to node1 that fills it
   131  	plan := ctx.Plan()
   132  	plan.NodeAllocation[nodes[0].Node.ID] = []*structs.Allocation{
   133  		{
   134  			Resources: &structs.Resources{
   135  				CPU:      2048,
   136  				MemoryMB: 2048,
   137  			},
   138  		},
   139  	}
   140  
   141  	// Add a planned alloc to node2 that half fills it
   142  	plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{
   143  		{
   144  			Resources: &structs.Resources{
   145  				CPU:      1024,
   146  				MemoryMB: 1024,
   147  			},
   148  		},
   149  	}
   150  
   151  	taskGroup := &structs.TaskGroup{
   152  		EphemeralDisk: &structs.EphemeralDisk{},
   153  		Tasks: []*structs.Task{
   154  			{
   155  				Name: "web",
   156  				Resources: &structs.Resources{
   157  					CPU:      1024,
   158  					MemoryMB: 1024,
   159  				},
   160  			},
   161  		},
   162  	}
   163  
   164  	binp := NewBinPackIterator(ctx, static, false, 0)
   165  	binp.SetTaskGroup(taskGroup)
   166  
   167  	out := collectRanked(binp)
   168  	if len(out) != 1 {
   169  		t.Fatalf("Bad: %#v", out)
   170  	}
   171  	if out[0] != nodes[1] {
   172  		t.Fatalf("Bad: %v", out)
   173  	}
   174  
   175  	if out[0].Score != 18 {
   176  		t.Fatalf("Bad: %v", out[0])
   177  	}
   178  }
   179  
   180  func TestBinPackIterator_ExistingAlloc(t *testing.T) {
   181  	state, ctx := testContext(t)
   182  	nodes := []*RankedNode{
   183  		{
   184  			Node: &structs.Node{
   185  				// Perfect fit
   186  				ID: uuid.Generate(),
   187  				Resources: &structs.Resources{
   188  					CPU:      2048,
   189  					MemoryMB: 2048,
   190  				},
   191  			},
   192  		},
   193  		{
   194  			Node: &structs.Node{
   195  				// Perfect fit
   196  				ID: uuid.Generate(),
   197  				Resources: &structs.Resources{
   198  					CPU:      2048,
   199  					MemoryMB: 2048,
   200  				},
   201  			},
   202  		},
   203  	}
   204  	static := NewStaticRankIterator(ctx, nodes)
   205  
   206  	// Add existing allocations
   207  	j1, j2 := mock.Job(), mock.Job()
   208  	alloc1 := &structs.Allocation{
   209  		Namespace: structs.DefaultNamespace,
   210  		ID:        uuid.Generate(),
   211  		EvalID:    uuid.Generate(),
   212  		NodeID:    nodes[0].Node.ID,
   213  		JobID:     j1.ID,
   214  		Job:       j1,
   215  		Resources: &structs.Resources{
   216  			CPU:      2048,
   217  			MemoryMB: 2048,
   218  		},
   219  		DesiredStatus: structs.AllocDesiredStatusRun,
   220  		ClientStatus:  structs.AllocClientStatusPending,
   221  		TaskGroup:     "web",
   222  	}
   223  	alloc2 := &structs.Allocation{
   224  		Namespace: structs.DefaultNamespace,
   225  		ID:        uuid.Generate(),
   226  		EvalID:    uuid.Generate(),
   227  		NodeID:    nodes[1].Node.ID,
   228  		JobID:     j2.ID,
   229  		Job:       j2,
   230  		Resources: &structs.Resources{
   231  			CPU:      1024,
   232  			MemoryMB: 1024,
   233  		},
   234  		DesiredStatus: structs.AllocDesiredStatusRun,
   235  		ClientStatus:  structs.AllocClientStatusPending,
   236  		TaskGroup:     "web",
   237  	}
   238  	noErr(t, state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)))
   239  	noErr(t, state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)))
   240  	noErr(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2}))
   241  
   242  	taskGroup := &structs.TaskGroup{
   243  		EphemeralDisk: &structs.EphemeralDisk{},
   244  		Tasks: []*structs.Task{
   245  			{
   246  				Name: "web",
   247  				Resources: &structs.Resources{
   248  					CPU:      1024,
   249  					MemoryMB: 1024,
   250  				},
   251  			},
   252  		},
   253  	}
   254  	binp := NewBinPackIterator(ctx, static, false, 0)
   255  	binp.SetTaskGroup(taskGroup)
   256  
   257  	out := collectRanked(binp)
   258  	if len(out) != 1 {
   259  		t.Fatalf("Bad: %#v", out)
   260  	}
   261  	if out[0] != nodes[1] {
   262  		t.Fatalf("Bad: %v", out)
   263  	}
   264  	if out[0].Score != 18 {
   265  		t.Fatalf("Bad: %v", out[0])
   266  	}
   267  }
   268  
   269  func TestBinPackIterator_ExistingAlloc_PlannedEvict(t *testing.T) {
   270  	state, ctx := testContext(t)
   271  	nodes := []*RankedNode{
   272  		{
   273  			Node: &structs.Node{
   274  				// Perfect fit
   275  				ID: uuid.Generate(),
   276  				Resources: &structs.Resources{
   277  					CPU:      2048,
   278  					MemoryMB: 2048,
   279  				},
   280  			},
   281  		},
   282  		{
   283  			Node: &structs.Node{
   284  				// Perfect fit
   285  				ID: uuid.Generate(),
   286  				Resources: &structs.Resources{
   287  					CPU:      2048,
   288  					MemoryMB: 2048,
   289  				},
   290  			},
   291  		},
   292  	}
   293  	static := NewStaticRankIterator(ctx, nodes)
   294  
   295  	// Add existing allocations
   296  	j1, j2 := mock.Job(), mock.Job()
   297  	alloc1 := &structs.Allocation{
   298  		Namespace: structs.DefaultNamespace,
   299  		ID:        uuid.Generate(),
   300  		EvalID:    uuid.Generate(),
   301  		NodeID:    nodes[0].Node.ID,
   302  		JobID:     j1.ID,
   303  		Job:       j1,
   304  		Resources: &structs.Resources{
   305  			CPU:      2048,
   306  			MemoryMB: 2048,
   307  		},
   308  		DesiredStatus: structs.AllocDesiredStatusRun,
   309  		ClientStatus:  structs.AllocClientStatusPending,
   310  		TaskGroup:     "web",
   311  	}
   312  	alloc2 := &structs.Allocation{
   313  		Namespace: structs.DefaultNamespace,
   314  		ID:        uuid.Generate(),
   315  		EvalID:    uuid.Generate(),
   316  		NodeID:    nodes[1].Node.ID,
   317  		JobID:     j2.ID,
   318  		Job:       j2,
   319  		Resources: &structs.Resources{
   320  			CPU:      1024,
   321  			MemoryMB: 1024,
   322  		},
   323  		DesiredStatus: structs.AllocDesiredStatusRun,
   324  		ClientStatus:  structs.AllocClientStatusPending,
   325  		TaskGroup:     "web",
   326  	}
   327  	noErr(t, state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID)))
   328  	noErr(t, state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)))
   329  	noErr(t, state.UpsertAllocs(1000, []*structs.Allocation{alloc1, alloc2}))
   330  
   331  	// Add a planned eviction to alloc1
   332  	plan := ctx.Plan()
   333  	plan.NodeUpdate[nodes[0].Node.ID] = []*structs.Allocation{alloc1}
   334  
   335  	taskGroup := &structs.TaskGroup{
   336  		EphemeralDisk: &structs.EphemeralDisk{},
   337  		Tasks: []*structs.Task{
   338  			{
   339  				Name: "web",
   340  				Resources: &structs.Resources{
   341  					CPU:      1024,
   342  					MemoryMB: 1024,
   343  				},
   344  			},
   345  		},
   346  	}
   347  
   348  	binp := NewBinPackIterator(ctx, static, false, 0)
   349  	binp.SetTaskGroup(taskGroup)
   350  
   351  	out := collectRanked(binp)
   352  	if len(out) != 2 {
   353  		t.Fatalf("Bad: %#v", out)
   354  	}
   355  	if out[0] != nodes[0] || out[1] != nodes[1] {
   356  		t.Fatalf("Bad: %v", out)
   357  	}
   358  	if out[0].Score < 10 || out[0].Score > 16 {
   359  		t.Fatalf("Bad: %v", out[0])
   360  	}
   361  	if out[1].Score != 18 {
   362  		t.Fatalf("Bad: %v", out[1])
   363  	}
   364  }
   365  
   366  func TestJobAntiAffinity_PlannedAlloc(t *testing.T) {
   367  	_, ctx := testContext(t)
   368  	nodes := []*RankedNode{
   369  		{
   370  			Node: &structs.Node{
   371  				ID: uuid.Generate(),
   372  			},
   373  		},
   374  		{
   375  			Node: &structs.Node{
   376  				ID: uuid.Generate(),
   377  			},
   378  		},
   379  	}
   380  	static := NewStaticRankIterator(ctx, nodes)
   381  
   382  	// Add a planned alloc to node1 that fills it
   383  	plan := ctx.Plan()
   384  	plan.NodeAllocation[nodes[0].Node.ID] = []*structs.Allocation{
   385  		{
   386  			ID:    uuid.Generate(),
   387  			JobID: "foo",
   388  		},
   389  		{
   390  			ID:    uuid.Generate(),
   391  			JobID: "foo",
   392  		},
   393  	}
   394  
   395  	// Add a planned alloc to node2 that half fills it
   396  	plan.NodeAllocation[nodes[1].Node.ID] = []*structs.Allocation{
   397  		{
   398  			JobID: "bar",
   399  		},
   400  	}
   401  
   402  	binp := NewJobAntiAffinityIterator(ctx, static, 5.0, "foo")
   403  
   404  	out := collectRanked(binp)
   405  	if len(out) != 2 {
   406  		t.Fatalf("Bad: %#v", out)
   407  	}
   408  	if out[0] != nodes[0] {
   409  		t.Fatalf("Bad: %v", out)
   410  	}
   411  	if out[0].Score != -10.0 {
   412  		t.Fatalf("Bad: %#v", out[0])
   413  	}
   414  
   415  	if out[1] != nodes[1] {
   416  		t.Fatalf("Bad: %v", out)
   417  	}
   418  	if out[1].Score != 0.0 {
   419  		t.Fatalf("Bad: %v", out[1])
   420  	}
   421  }
   422  
   423  func collectRanked(iter RankIterator) (out []*RankedNode) {
   424  	for {
   425  		next := iter.Next()
   426  		if next == nil {
   427  			break
   428  		}
   429  		out = append(out, next)
   430  	}
   431  	return
   432  }
   433  
   434  func TestNodeAntiAffinity_PenaltyNodes(t *testing.T) {
   435  	_, ctx := testContext(t)
   436  	node1 := &structs.Node{
   437  		ID: uuid.Generate(),
   438  	}
   439  	node2 := &structs.Node{
   440  		ID: uuid.Generate(),
   441  	}
   442  
   443  	nodes := []*RankedNode{
   444  		{
   445  			Node: node1,
   446  		},
   447  		{
   448  			Node: node2,
   449  		},
   450  	}
   451  	static := NewStaticRankIterator(ctx, nodes)
   452  
   453  	nodeAntiAffIter := NewNodeAntiAffinityIterator(ctx, static, 50.0)
   454  	nodeAntiAffIter.SetPenaltyNodes(map[string]struct{}{node1.ID: {}})
   455  
   456  	out := collectRanked(nodeAntiAffIter)
   457  
   458  	require := require.New(t)
   459  	require.Equal(2, len(out))
   460  	require.Equal(node1.ID, out[0].Node.ID)
   461  	require.Equal(-50.0, out[0].Score)
   462  
   463  	require.Equal(node2.ID, out[1].Node.ID)
   464  	require.Equal(0.0, out[1].Score)
   465  
   466  }