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 }