github.com/djenriquez/nomad-1@v0.8.1/scheduler/stack_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"runtime"
     7  	"testing"
     8  
     9  	"github.com/hashicorp/nomad/nomad/mock"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func BenchmarkServiceStack_With_ComputedClass(b *testing.B) {
    15  	// Key doesn't escape computed node class.
    16  	benchmarkServiceStack_MetaKeyConstraint(b, "key", 5000, 64)
    17  }
    18  
    19  func BenchmarkServiceStack_WithOut_ComputedClass(b *testing.B) {
    20  	// Key escapes computed node class.
    21  	benchmarkServiceStack_MetaKeyConstraint(b, "unique.key", 5000, 64)
    22  }
    23  
    24  // benchmarkServiceStack_MetaKeyConstraint creates the passed number of nodes
    25  // and sets the meta data key to have nodePartitions number of values. It then
    26  // benchmarks the stack by selecting a job that constrains against one of the
    27  // partitions.
    28  func benchmarkServiceStack_MetaKeyConstraint(b *testing.B, key string, numNodes, nodePartitions int) {
    29  	_, ctx := testContext(b)
    30  	stack := NewGenericStack(false, ctx)
    31  
    32  	// Create 4 classes of nodes.
    33  	nodes := make([]*structs.Node, numNodes)
    34  	for i := 0; i < numNodes; i++ {
    35  		n := mock.Node()
    36  		n.Meta[key] = fmt.Sprintf("%d", i%nodePartitions)
    37  		nodes[i] = n
    38  	}
    39  	stack.SetNodes(nodes)
    40  
    41  	// Create a job whose constraint meets two node classes.
    42  	job := mock.Job()
    43  	job.Constraints[0] = &structs.Constraint{
    44  		LTarget: fmt.Sprintf("${meta.%v}", key),
    45  		RTarget: "1",
    46  		Operand: "<",
    47  	}
    48  	stack.SetJob(job)
    49  
    50  	b.ResetTimer()
    51  	selectOptions := &SelectOptions{}
    52  	for i := 0; i < b.N; i++ {
    53  		stack.Select(job.TaskGroups[0], selectOptions)
    54  	}
    55  }
    56  
    57  func TestServiceStack_SetNodes(t *testing.T) {
    58  	_, ctx := testContext(t)
    59  	stack := NewGenericStack(false, ctx)
    60  
    61  	nodes := []*structs.Node{
    62  		mock.Node(),
    63  		mock.Node(),
    64  		mock.Node(),
    65  		mock.Node(),
    66  		mock.Node(),
    67  		mock.Node(),
    68  		mock.Node(),
    69  		mock.Node(),
    70  	}
    71  	stack.SetNodes(nodes)
    72  
    73  	// Check that our scan limit is updated
    74  	if stack.limit.limit != 3 {
    75  		t.Fatalf("bad limit %d", stack.limit.limit)
    76  	}
    77  
    78  	out := collectFeasible(stack.source)
    79  	if !reflect.DeepEqual(out, nodes) {
    80  		t.Fatalf("bad: %#v", out)
    81  	}
    82  }
    83  
    84  func TestServiceStack_SetJob(t *testing.T) {
    85  	_, ctx := testContext(t)
    86  	stack := NewGenericStack(false, ctx)
    87  
    88  	job := mock.Job()
    89  	stack.SetJob(job)
    90  
    91  	if stack.binPack.priority != job.Priority {
    92  		t.Fatalf("bad")
    93  	}
    94  	if !reflect.DeepEqual(stack.jobConstraint.constraints, job.Constraints) {
    95  		t.Fatalf("bad")
    96  	}
    97  }
    98  
    99  func TestServiceStack_Select_Size(t *testing.T) {
   100  	_, ctx := testContext(t)
   101  	nodes := []*structs.Node{
   102  		mock.Node(),
   103  	}
   104  	stack := NewGenericStack(false, ctx)
   105  	stack.SetNodes(nodes)
   106  
   107  	job := mock.Job()
   108  	stack.SetJob(job)
   109  	selectOptions := &SelectOptions{}
   110  	node, size := stack.Select(job.TaskGroups[0], selectOptions)
   111  	if node == nil {
   112  		t.Fatalf("missing node %#v", ctx.Metrics())
   113  	}
   114  	if size == nil {
   115  		t.Fatalf("missing size")
   116  	}
   117  
   118  	if size.CPU != 500 || size.MemoryMB != 256 {
   119  		t.Fatalf("bad: %#v", size)
   120  	}
   121  
   122  	// Note: On Windows time.Now currently has a best case granularity of 1ms.
   123  	// We skip the following assertion on Windows because this test usually
   124  	// runs too fast to measure an allocation time on Windows.
   125  	met := ctx.Metrics()
   126  	if runtime.GOOS != "windows" && met.AllocationTime == 0 {
   127  		t.Fatalf("missing time")
   128  	}
   129  }
   130  
   131  func TestServiceStack_Select_PreferringNodes(t *testing.T) {
   132  	_, ctx := testContext(t)
   133  	nodes := []*structs.Node{
   134  		mock.Node(),
   135  	}
   136  	stack := NewGenericStack(false, ctx)
   137  	stack.SetNodes(nodes)
   138  
   139  	job := mock.Job()
   140  	stack.SetJob(job)
   141  
   142  	// Create a preferred node
   143  	preferredNode := mock.Node()
   144  	prefNodes := []*structs.Node{preferredNode}
   145  	selectOptions := &SelectOptions{PreferredNodes: prefNodes}
   146  	option, _ := stack.Select(job.TaskGroups[0], selectOptions)
   147  	if option == nil {
   148  		t.Fatalf("missing node %#v", ctx.Metrics())
   149  	}
   150  	if option.Node.ID != preferredNode.ID {
   151  		t.Fatalf("expected: %v, actual: %v", option.Node.ID, preferredNode.ID)
   152  	}
   153  
   154  	// Make sure select doesn't have a side effect on preferred nodes
   155  	require.Equal(t, prefNodes, selectOptions.PreferredNodes)
   156  
   157  	// Change the preferred node's kernel to windows and ensure the allocations
   158  	// are placed elsewhere
   159  	preferredNode1 := preferredNode.Copy()
   160  	preferredNode1.Attributes["kernel.name"] = "windows"
   161  	preferredNode1.ComputeClass()
   162  	prefNodes1 := []*structs.Node{preferredNode1}
   163  	selectOptions = &SelectOptions{PreferredNodes: prefNodes1}
   164  	option, _ = stack.Select(job.TaskGroups[0], selectOptions)
   165  	if option == nil {
   166  		t.Fatalf("missing node %#v", ctx.Metrics())
   167  	}
   168  
   169  	if option.Node.ID != nodes[0].ID {
   170  		t.Fatalf("expected: %#v, actual: %#v", nodes[0], option.Node)
   171  	}
   172  	require.Equal(t, prefNodes1, selectOptions.PreferredNodes)
   173  }
   174  
   175  func TestServiceStack_Select_MetricsReset(t *testing.T) {
   176  	_, ctx := testContext(t)
   177  	nodes := []*structs.Node{
   178  		mock.Node(),
   179  		mock.Node(),
   180  		mock.Node(),
   181  		mock.Node(),
   182  	}
   183  	stack := NewGenericStack(false, ctx)
   184  	stack.SetNodes(nodes)
   185  
   186  	job := mock.Job()
   187  	stack.SetJob(job)
   188  	selectOptions := &SelectOptions{}
   189  	n1, _ := stack.Select(job.TaskGroups[0], selectOptions)
   190  	m1 := ctx.Metrics()
   191  	if n1 == nil {
   192  		t.Fatalf("missing node %#v", m1)
   193  	}
   194  
   195  	if m1.NodesEvaluated != 2 {
   196  		t.Fatalf("should only be 2")
   197  	}
   198  
   199  	n2, _ := stack.Select(job.TaskGroups[0], selectOptions)
   200  	m2 := ctx.Metrics()
   201  	if n2 == nil {
   202  		t.Fatalf("missing node %#v", m2)
   203  	}
   204  
   205  	// If we don't reset, this would be 4
   206  	if m2.NodesEvaluated != 2 {
   207  		t.Fatalf("should only be 2")
   208  	}
   209  }
   210  
   211  func TestServiceStack_Select_DriverFilter(t *testing.T) {
   212  	_, ctx := testContext(t)
   213  	nodes := []*structs.Node{
   214  		mock.Node(),
   215  		mock.Node(),
   216  	}
   217  	zero := nodes[0]
   218  	zero.Attributes["driver.foo"] = "1"
   219  	if err := zero.ComputeClass(); err != nil {
   220  		t.Fatalf("ComputedClass() failed: %v", err)
   221  	}
   222  
   223  	stack := NewGenericStack(false, ctx)
   224  	stack.SetNodes(nodes)
   225  
   226  	job := mock.Job()
   227  	job.TaskGroups[0].Tasks[0].Driver = "foo"
   228  	stack.SetJob(job)
   229  
   230  	selectOptions := &SelectOptions{}
   231  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   232  	if node == nil {
   233  		t.Fatalf("missing node %#v", ctx.Metrics())
   234  	}
   235  
   236  	if node.Node != zero {
   237  		t.Fatalf("bad")
   238  	}
   239  }
   240  
   241  func TestServiceStack_Select_ConstraintFilter(t *testing.T) {
   242  	_, ctx := testContext(t)
   243  	nodes := []*structs.Node{
   244  		mock.Node(),
   245  		mock.Node(),
   246  	}
   247  	zero := nodes[0]
   248  	zero.Attributes["kernel.name"] = "freebsd"
   249  	if err := zero.ComputeClass(); err != nil {
   250  		t.Fatalf("ComputedClass() failed: %v", err)
   251  	}
   252  
   253  	stack := NewGenericStack(false, ctx)
   254  	stack.SetNodes(nodes)
   255  
   256  	job := mock.Job()
   257  	job.Constraints[0].RTarget = "freebsd"
   258  	stack.SetJob(job)
   259  	selectOptions := &SelectOptions{}
   260  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   261  	if node == nil {
   262  		t.Fatalf("missing node %#v", ctx.Metrics())
   263  	}
   264  
   265  	if node.Node != zero {
   266  		t.Fatalf("bad")
   267  	}
   268  
   269  	met := ctx.Metrics()
   270  	if met.NodesFiltered != 1 {
   271  		t.Fatalf("bad: %#v", met)
   272  	}
   273  	if met.ClassFiltered["linux-medium-pci"] != 1 {
   274  		t.Fatalf("bad: %#v", met)
   275  	}
   276  	if met.ConstraintFiltered["${attr.kernel.name} = freebsd"] != 1 {
   277  		t.Fatalf("bad: %#v", met)
   278  	}
   279  }
   280  
   281  func TestServiceStack_Select_BinPack_Overflow(t *testing.T) {
   282  	_, ctx := testContext(t)
   283  	nodes := []*structs.Node{
   284  		mock.Node(),
   285  		mock.Node(),
   286  	}
   287  	zero := nodes[0]
   288  	one := nodes[1]
   289  	one.Reserved = one.Resources
   290  
   291  	stack := NewGenericStack(false, ctx)
   292  	stack.SetNodes(nodes)
   293  
   294  	job := mock.Job()
   295  	stack.SetJob(job)
   296  	selectOptions := &SelectOptions{}
   297  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   298  	if node == nil {
   299  		t.Fatalf("missing node %#v", ctx.Metrics())
   300  	}
   301  
   302  	if node.Node != zero {
   303  		t.Fatalf("bad")
   304  	}
   305  
   306  	met := ctx.Metrics()
   307  	if met.NodesExhausted != 1 {
   308  		t.Fatalf("bad: %#v", met)
   309  	}
   310  	if met.ClassExhausted["linux-medium-pci"] != 1 {
   311  		t.Fatalf("bad: %#v", met)
   312  	}
   313  	if len(met.Scores) != 1 {
   314  		t.Fatalf("bad: %#v", met)
   315  	}
   316  }
   317  
   318  func TestSystemStack_SetNodes(t *testing.T) {
   319  	_, ctx := testContext(t)
   320  	stack := NewSystemStack(ctx)
   321  
   322  	nodes := []*structs.Node{
   323  		mock.Node(),
   324  		mock.Node(),
   325  		mock.Node(),
   326  		mock.Node(),
   327  		mock.Node(),
   328  		mock.Node(),
   329  		mock.Node(),
   330  		mock.Node(),
   331  	}
   332  	stack.SetNodes(nodes)
   333  
   334  	out := collectFeasible(stack.source)
   335  	if !reflect.DeepEqual(out, nodes) {
   336  		t.Fatalf("bad: %#v", out)
   337  	}
   338  }
   339  
   340  func TestSystemStack_SetJob(t *testing.T) {
   341  	_, ctx := testContext(t)
   342  	stack := NewSystemStack(ctx)
   343  
   344  	job := mock.Job()
   345  	stack.SetJob(job)
   346  
   347  	if stack.binPack.priority != job.Priority {
   348  		t.Fatalf("bad")
   349  	}
   350  	if !reflect.DeepEqual(stack.jobConstraint.constraints, job.Constraints) {
   351  		t.Fatalf("bad")
   352  	}
   353  }
   354  
   355  func TestSystemStack_Select_Size(t *testing.T) {
   356  	_, ctx := testContext(t)
   357  	nodes := []*structs.Node{mock.Node()}
   358  	stack := NewSystemStack(ctx)
   359  	stack.SetNodes(nodes)
   360  
   361  	job := mock.Job()
   362  	stack.SetJob(job)
   363  	selectOptions := &SelectOptions{}
   364  	node, size := stack.Select(job.TaskGroups[0], selectOptions)
   365  	if node == nil {
   366  		t.Fatalf("missing node %#v", ctx.Metrics())
   367  	}
   368  	if size == nil {
   369  		t.Fatalf("missing size")
   370  	}
   371  
   372  	if size.CPU != 500 || size.MemoryMB != 256 {
   373  		t.Fatalf("bad: %#v", size)
   374  	}
   375  
   376  	// Note: On Windows time.Now currently has a best case granularity of 1ms.
   377  	// We skip the following assertion on Windows because this test usually
   378  	// runs too fast to measure an allocation time on Windows.
   379  	met := ctx.Metrics()
   380  	if runtime.GOOS != "windows" && met.AllocationTime == 0 {
   381  		t.Fatalf("missing time")
   382  	}
   383  }
   384  
   385  func TestSystemStack_Select_MetricsReset(t *testing.T) {
   386  	_, ctx := testContext(t)
   387  	nodes := []*structs.Node{
   388  		mock.Node(),
   389  		mock.Node(),
   390  		mock.Node(),
   391  		mock.Node(),
   392  	}
   393  	stack := NewSystemStack(ctx)
   394  	stack.SetNodes(nodes)
   395  
   396  	job := mock.Job()
   397  	stack.SetJob(job)
   398  	selectOptions := &SelectOptions{}
   399  	n1, _ := stack.Select(job.TaskGroups[0], selectOptions)
   400  	m1 := ctx.Metrics()
   401  	if n1 == nil {
   402  		t.Fatalf("missing node %#v", m1)
   403  	}
   404  
   405  	if m1.NodesEvaluated != 1 {
   406  		t.Fatalf("should only be 1")
   407  	}
   408  
   409  	n2, _ := stack.Select(job.TaskGroups[0], selectOptions)
   410  	m2 := ctx.Metrics()
   411  	if n2 == nil {
   412  		t.Fatalf("missing node %#v", m2)
   413  	}
   414  
   415  	// If we don't reset, this would be 2
   416  	if m2.NodesEvaluated != 1 {
   417  		t.Fatalf("should only be 2")
   418  	}
   419  }
   420  
   421  func TestSystemStack_Select_DriverFilter(t *testing.T) {
   422  	_, ctx := testContext(t)
   423  	nodes := []*structs.Node{
   424  		mock.Node(),
   425  	}
   426  	zero := nodes[0]
   427  	zero.Attributes["driver.foo"] = "1"
   428  
   429  	stack := NewSystemStack(ctx)
   430  	stack.SetNodes(nodes)
   431  
   432  	job := mock.Job()
   433  	job.TaskGroups[0].Tasks[0].Driver = "foo"
   434  	stack.SetJob(job)
   435  
   436  	selectOptions := &SelectOptions{}
   437  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   438  	if node == nil {
   439  		t.Fatalf("missing node %#v", ctx.Metrics())
   440  	}
   441  
   442  	if node.Node != zero {
   443  		t.Fatalf("bad")
   444  	}
   445  
   446  	zero.Attributes["driver.foo"] = "0"
   447  	if err := zero.ComputeClass(); err != nil {
   448  		t.Fatalf("ComputedClass() failed: %v", err)
   449  	}
   450  
   451  	stack = NewSystemStack(ctx)
   452  	stack.SetNodes(nodes)
   453  	stack.SetJob(job)
   454  	node, _ = stack.Select(job.TaskGroups[0], selectOptions)
   455  	if node != nil {
   456  		t.Fatalf("node not filtered %#v", node)
   457  	}
   458  }
   459  
   460  func TestSystemStack_Select_ConstraintFilter(t *testing.T) {
   461  	_, ctx := testContext(t)
   462  	nodes := []*structs.Node{
   463  		mock.Node(),
   464  		mock.Node(),
   465  	}
   466  	zero := nodes[1]
   467  	zero.Attributes["kernel.name"] = "freebsd"
   468  	if err := zero.ComputeClass(); err != nil {
   469  		t.Fatalf("ComputedClass() failed: %v", err)
   470  	}
   471  
   472  	stack := NewSystemStack(ctx)
   473  	stack.SetNodes(nodes)
   474  
   475  	job := mock.Job()
   476  	job.Constraints[0].RTarget = "freebsd"
   477  	stack.SetJob(job)
   478  
   479  	selectOptions := &SelectOptions{}
   480  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   481  	if node == nil {
   482  		t.Fatalf("missing node %#v", ctx.Metrics())
   483  	}
   484  
   485  	if node.Node != zero {
   486  		t.Fatalf("bad")
   487  	}
   488  
   489  	met := ctx.Metrics()
   490  	if met.NodesFiltered != 1 {
   491  		t.Fatalf("bad: %#v", met)
   492  	}
   493  	if met.ClassFiltered["linux-medium-pci"] != 1 {
   494  		t.Fatalf("bad: %#v", met)
   495  	}
   496  	if met.ConstraintFiltered["${attr.kernel.name} = freebsd"] != 1 {
   497  		t.Fatalf("bad: %#v", met)
   498  	}
   499  }
   500  
   501  func TestSystemStack_Select_BinPack_Overflow(t *testing.T) {
   502  	_, ctx := testContext(t)
   503  	nodes := []*structs.Node{
   504  		mock.Node(),
   505  		mock.Node(),
   506  	}
   507  	zero := nodes[0]
   508  	zero.Reserved = zero.Resources
   509  	one := nodes[1]
   510  
   511  	stack := NewSystemStack(ctx)
   512  	stack.SetNodes(nodes)
   513  
   514  	job := mock.Job()
   515  	stack.SetJob(job)
   516  
   517  	selectOptions := &SelectOptions{}
   518  	node, _ := stack.Select(job.TaskGroups[0], selectOptions)
   519  	if node == nil {
   520  		t.Fatalf("missing node %#v", ctx.Metrics())
   521  	}
   522  
   523  	if node.Node != one {
   524  		t.Fatalf("bad")
   525  	}
   526  
   527  	met := ctx.Metrics()
   528  	if met.NodesExhausted != 1 {
   529  		t.Fatalf("bad: %#v", met)
   530  	}
   531  	if met.ClassExhausted["linux-medium-pci"] != 1 {
   532  		t.Fatalf("bad: %#v", met)
   533  	}
   534  	if len(met.Scores) != 1 {
   535  		t.Fatalf("bad: %#v", met)
   536  	}
   537  }