github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/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: "$meta." + 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_MetricsReset(t *testing.T) {
   129  	_, ctx := testContext(t)
   130  	nodes := []*structs.Node{
   131  		mock.Node(),
   132  		mock.Node(),
   133  		mock.Node(),
   134  		mock.Node(),
   135  	}
   136  	stack := NewGenericStack(false, ctx)
   137  	stack.SetNodes(nodes)
   138  
   139  	job := mock.Job()
   140  	stack.SetJob(job)
   141  	n1, _ := stack.Select(job.TaskGroups[0])
   142  	m1 := ctx.Metrics()
   143  	if n1 == nil {
   144  		t.Fatalf("missing node %#v", m1)
   145  	}
   146  
   147  	if m1.NodesEvaluated != 2 {
   148  		t.Fatalf("should only be 2")
   149  	}
   150  
   151  	n2, _ := stack.Select(job.TaskGroups[0])
   152  	m2 := ctx.Metrics()
   153  	if n2 == nil {
   154  		t.Fatalf("missing node %#v", m2)
   155  	}
   156  
   157  	// If we don't reset, this would be 4
   158  	if m2.NodesEvaluated != 2 {
   159  		t.Fatalf("should only be 2")
   160  	}
   161  }
   162  
   163  func TestServiceStack_Select_DriverFilter(t *testing.T) {
   164  	_, ctx := testContext(t)
   165  	nodes := []*structs.Node{
   166  		mock.Node(),
   167  		mock.Node(),
   168  	}
   169  	zero := nodes[0]
   170  	zero.Attributes["driver.foo"] = "1"
   171  	if err := zero.ComputeClass(); err != nil {
   172  		t.Fatalf("ComputedClass() failed: %v", err)
   173  	}
   174  
   175  	stack := NewGenericStack(false, ctx)
   176  	stack.SetNodes(nodes)
   177  
   178  	job := mock.Job()
   179  	job.TaskGroups[0].Tasks[0].Driver = "foo"
   180  	stack.SetJob(job)
   181  
   182  	node, _ := stack.Select(job.TaskGroups[0])
   183  	if node == nil {
   184  		t.Fatalf("missing node %#v", ctx.Metrics())
   185  	}
   186  
   187  	if node.Node != zero {
   188  		t.Fatalf("bad")
   189  	}
   190  }
   191  
   192  func TestServiceStack_Select_ConstraintFilter(t *testing.T) {
   193  	_, ctx := testContext(t)
   194  	nodes := []*structs.Node{
   195  		mock.Node(),
   196  		mock.Node(),
   197  	}
   198  	zero := nodes[0]
   199  	zero.Attributes["kernel.name"] = "freebsd"
   200  	if err := zero.ComputeClass(); err != nil {
   201  		t.Fatalf("ComputedClass() failed: %v", err)
   202  	}
   203  
   204  	stack := NewGenericStack(false, ctx)
   205  	stack.SetNodes(nodes)
   206  
   207  	job := mock.Job()
   208  	job.Constraints[0].RTarget = "freebsd"
   209  	stack.SetJob(job)
   210  
   211  	node, _ := stack.Select(job.TaskGroups[0])
   212  	if node == nil {
   213  		t.Fatalf("missing node %#v", ctx.Metrics())
   214  	}
   215  
   216  	if node.Node != zero {
   217  		t.Fatalf("bad")
   218  	}
   219  
   220  	met := ctx.Metrics()
   221  	if met.NodesFiltered != 1 {
   222  		t.Fatalf("bad: %#v", met)
   223  	}
   224  	if met.ClassFiltered["linux-medium-pci"] != 1 {
   225  		t.Fatalf("bad: %#v", met)
   226  	}
   227  	if met.ConstraintFiltered["$attr.kernel.name = freebsd"] != 1 {
   228  		t.Fatalf("bad: %#v", met)
   229  	}
   230  }
   231  
   232  func TestServiceStack_Select_BinPack_Overflow(t *testing.T) {
   233  	_, ctx := testContext(t)
   234  	nodes := []*structs.Node{
   235  		mock.Node(),
   236  		mock.Node(),
   237  	}
   238  	zero := nodes[0]
   239  	one := nodes[1]
   240  	one.Reserved = one.Resources
   241  
   242  	stack := NewGenericStack(false, ctx)
   243  	stack.SetNodes(nodes)
   244  
   245  	job := mock.Job()
   246  	stack.SetJob(job)
   247  
   248  	node, _ := stack.Select(job.TaskGroups[0])
   249  	if node == nil {
   250  		t.Fatalf("missing node %#v", ctx.Metrics())
   251  	}
   252  
   253  	if node.Node != zero {
   254  		t.Fatalf("bad")
   255  	}
   256  
   257  	met := ctx.Metrics()
   258  	if met.NodesExhausted != 1 {
   259  		t.Fatalf("bad: %#v", met)
   260  	}
   261  	if met.ClassExhausted["linux-medium-pci"] != 1 {
   262  		t.Fatalf("bad: %#v", met)
   263  	}
   264  	if len(met.Scores) != 1 {
   265  		t.Fatalf("bad: %#v", met)
   266  	}
   267  }
   268  
   269  func TestSystemStack_SetNodes(t *testing.T) {
   270  	_, ctx := testContext(t)
   271  	stack := NewSystemStack(ctx)
   272  
   273  	nodes := []*structs.Node{
   274  		mock.Node(),
   275  		mock.Node(),
   276  		mock.Node(),
   277  		mock.Node(),
   278  		mock.Node(),
   279  		mock.Node(),
   280  		mock.Node(),
   281  		mock.Node(),
   282  	}
   283  	stack.SetNodes(nodes)
   284  
   285  	out := collectFeasible(stack.source)
   286  	if !reflect.DeepEqual(out, nodes) {
   287  		t.Fatalf("bad: %#v", out)
   288  	}
   289  }
   290  
   291  func TestSystemStack_SetJob(t *testing.T) {
   292  	_, ctx := testContext(t)
   293  	stack := NewSystemStack(ctx)
   294  
   295  	job := mock.Job()
   296  	stack.SetJob(job)
   297  
   298  	if stack.binPack.priority != job.Priority {
   299  		t.Fatalf("bad")
   300  	}
   301  	if !reflect.DeepEqual(stack.jobConstraint.constraints, job.Constraints) {
   302  		t.Fatalf("bad")
   303  	}
   304  }
   305  
   306  func TestSystemStack_Select_Size(t *testing.T) {
   307  	_, ctx := testContext(t)
   308  	nodes := []*structs.Node{mock.Node()}
   309  	stack := NewSystemStack(ctx)
   310  	stack.SetNodes(nodes)
   311  
   312  	job := mock.Job()
   313  	stack.SetJob(job)
   314  	node, size := stack.Select(job.TaskGroups[0])
   315  	if node == nil {
   316  		t.Fatalf("missing node %#v", ctx.Metrics())
   317  	}
   318  	if size == nil {
   319  		t.Fatalf("missing size")
   320  	}
   321  
   322  	if size.CPU != 500 || size.MemoryMB != 256 {
   323  		t.Fatalf("bad: %#v", size)
   324  	}
   325  
   326  	// Note: On Windows time.Now currently has a best case granularity of 1ms.
   327  	// We skip the following assertion on Windows because this test usually
   328  	// runs too fast to measure an allocation time on Windows.
   329  	met := ctx.Metrics()
   330  	if runtime.GOOS != "windows" && met.AllocationTime == 0 {
   331  		t.Fatalf("missing time")
   332  	}
   333  }
   334  
   335  func TestSystemStack_Select_MetricsReset(t *testing.T) {
   336  	_, ctx := testContext(t)
   337  	nodes := []*structs.Node{
   338  		mock.Node(),
   339  		mock.Node(),
   340  		mock.Node(),
   341  		mock.Node(),
   342  	}
   343  	stack := NewSystemStack(ctx)
   344  	stack.SetNodes(nodes)
   345  
   346  	job := mock.Job()
   347  	stack.SetJob(job)
   348  	n1, _ := stack.Select(job.TaskGroups[0])
   349  	m1 := ctx.Metrics()
   350  	if n1 == nil {
   351  		t.Fatalf("missing node %#v", m1)
   352  	}
   353  
   354  	if m1.NodesEvaluated != 1 {
   355  		t.Fatalf("should only be 1")
   356  	}
   357  
   358  	n2, _ := stack.Select(job.TaskGroups[0])
   359  	m2 := ctx.Metrics()
   360  	if n2 == nil {
   361  		t.Fatalf("missing node %#v", m2)
   362  	}
   363  
   364  	// If we don't reset, this would be 2
   365  	if m2.NodesEvaluated != 1 {
   366  		t.Fatalf("should only be 2")
   367  	}
   368  }
   369  
   370  func TestSystemStack_Select_DriverFilter(t *testing.T) {
   371  	_, ctx := testContext(t)
   372  	nodes := []*structs.Node{
   373  		mock.Node(),
   374  	}
   375  	zero := nodes[0]
   376  	zero.Attributes["driver.foo"] = "1"
   377  
   378  	stack := NewSystemStack(ctx)
   379  	stack.SetNodes(nodes)
   380  
   381  	job := mock.Job()
   382  	job.TaskGroups[0].Tasks[0].Driver = "foo"
   383  	stack.SetJob(job)
   384  
   385  	node, _ := stack.Select(job.TaskGroups[0])
   386  	if node == nil {
   387  		t.Fatalf("missing node %#v", ctx.Metrics())
   388  	}
   389  
   390  	if node.Node != zero {
   391  		t.Fatalf("bad")
   392  	}
   393  
   394  	zero.Attributes["driver.foo"] = "0"
   395  	if err := zero.ComputeClass(); err != nil {
   396  		t.Fatalf("ComputedClass() failed: %v", err)
   397  	}
   398  
   399  	stack = NewSystemStack(ctx)
   400  	stack.SetNodes(nodes)
   401  	stack.SetJob(job)
   402  	node, _ = stack.Select(job.TaskGroups[0])
   403  	if node != nil {
   404  		t.Fatalf("node not filtered %#v", node)
   405  	}
   406  }
   407  
   408  func TestSystemStack_Select_ConstraintFilter(t *testing.T) {
   409  	_, ctx := testContext(t)
   410  	nodes := []*structs.Node{
   411  		mock.Node(),
   412  		mock.Node(),
   413  	}
   414  	zero := nodes[1]
   415  	zero.Attributes["kernel.name"] = "freebsd"
   416  	if err := zero.ComputeClass(); err != nil {
   417  		t.Fatalf("ComputedClass() failed: %v", err)
   418  	}
   419  
   420  	stack := NewSystemStack(ctx)
   421  	stack.SetNodes(nodes)
   422  
   423  	job := mock.Job()
   424  	job.Constraints[0].RTarget = "freebsd"
   425  	stack.SetJob(job)
   426  
   427  	node, _ := stack.Select(job.TaskGroups[0])
   428  	if node == nil {
   429  		t.Fatalf("missing node %#v", ctx.Metrics())
   430  	}
   431  
   432  	if node.Node != zero {
   433  		t.Fatalf("bad")
   434  	}
   435  
   436  	met := ctx.Metrics()
   437  	if met.NodesFiltered != 1 {
   438  		t.Fatalf("bad: %#v", met)
   439  	}
   440  	if met.ClassFiltered["linux-medium-pci"] != 1 {
   441  		t.Fatalf("bad: %#v", met)
   442  	}
   443  	if met.ConstraintFiltered["$attr.kernel.name = freebsd"] != 1 {
   444  		t.Fatalf("bad: %#v", met)
   445  	}
   446  }
   447  
   448  func TestSystemStack_Select_BinPack_Overflow(t *testing.T) {
   449  	_, ctx := testContext(t)
   450  	nodes := []*structs.Node{
   451  		mock.Node(),
   452  		mock.Node(),
   453  	}
   454  	zero := nodes[0]
   455  	zero.Reserved = zero.Resources
   456  	one := nodes[1]
   457  
   458  	stack := NewSystemStack(ctx)
   459  	stack.SetNodes(nodes)
   460  
   461  	job := mock.Job()
   462  	stack.SetJob(job)
   463  
   464  	node, _ := stack.Select(job.TaskGroups[0])
   465  	if node == nil {
   466  		t.Fatalf("missing node %#v", ctx.Metrics())
   467  	}
   468  
   469  	if node.Node != one {
   470  		t.Fatalf("bad")
   471  	}
   472  
   473  	met := ctx.Metrics()
   474  	if met.NodesExhausted != 1 {
   475  		t.Fatalf("bad: %#v", met)
   476  	}
   477  	if met.ClassExhausted["linux-medium-pci"] != 1 {
   478  		t.Fatalf("bad: %#v", met)
   479  	}
   480  	if len(met.Scores) != 1 {
   481  		t.Fatalf("bad: %#v", met)
   482  	}
   483  }