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 }