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