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 }