github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/scheduler/util_test.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "reflect" 8 "testing" 9 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/state" 12 "github.com/hashicorp/nomad/nomad/structs" 13 ) 14 15 func TestMaterializeTaskGroups(t *testing.T) { 16 job := mock.Job() 17 index := materializeTaskGroups(job) 18 if len(index) != 10 { 19 t.Fatalf("Bad: %#v", index) 20 } 21 22 for i := 0; i < 10; i++ { 23 name := fmt.Sprintf("my-job.web[%d]", i) 24 tg, ok := index[name] 25 if !ok { 26 t.Fatalf("bad") 27 } 28 if tg != job.TaskGroups[0] { 29 t.Fatalf("bad") 30 } 31 } 32 } 33 34 func TestDiffAllocs(t *testing.T) { 35 job := mock.Job() 36 required := materializeTaskGroups(job) 37 38 // The "old" job has a previous modify index 39 oldJob := new(structs.Job) 40 *oldJob = *job 41 oldJob.ModifyIndex -= 1 42 43 tainted := map[string]bool{ 44 "dead": true, 45 "zip": false, 46 } 47 48 allocs := []*structs.Allocation{ 49 // Update the 1st 50 &structs.Allocation{ 51 ID: structs.GenerateUUID(), 52 NodeID: "zip", 53 Name: "my-job.web[0]", 54 Job: oldJob, 55 }, 56 57 // Ignore the 2rd 58 &structs.Allocation{ 59 ID: structs.GenerateUUID(), 60 NodeID: "zip", 61 Name: "my-job.web[1]", 62 Job: job, 63 }, 64 65 // Evict 11th 66 &structs.Allocation{ 67 ID: structs.GenerateUUID(), 68 NodeID: "zip", 69 Name: "my-job.web[10]", 70 }, 71 72 // Migrate the 3rd 73 &structs.Allocation{ 74 ID: structs.GenerateUUID(), 75 NodeID: "dead", 76 Name: "my-job.web[2]", 77 }, 78 } 79 80 diff := diffAllocs(job, tainted, required, allocs) 81 place := diff.place 82 update := diff.update 83 migrate := diff.migrate 84 stop := diff.stop 85 ignore := diff.ignore 86 87 // We should update the first alloc 88 if len(update) != 1 || update[0].Alloc != allocs[0] { 89 t.Fatalf("bad: %#v", update) 90 } 91 92 // We should ignore the second alloc 93 if len(ignore) != 1 || ignore[0].Alloc != allocs[1] { 94 t.Fatalf("bad: %#v", ignore) 95 } 96 97 // We should stop the 3rd alloc 98 if len(stop) != 1 || stop[0].Alloc != allocs[2] { 99 t.Fatalf("bad: %#v", stop) 100 } 101 102 // We should migrate the 4rd alloc 103 if len(migrate) != 1 || migrate[0].Alloc != allocs[3] { 104 t.Fatalf("bad: %#v", migrate) 105 } 106 107 // We should place 7 108 if len(place) != 7 { 109 t.Fatalf("bad: %#v", place) 110 } 111 } 112 113 func TestDiffSystemAllocs(t *testing.T) { 114 job := mock.SystemJob() 115 116 // Create three alive nodes. 117 nodes := []*structs.Node{{ID: "foo"}, {ID: "bar"}, {ID: "baz"}} 118 119 // The "old" job has a previous modify index 120 oldJob := new(structs.Job) 121 *oldJob = *job 122 oldJob.ModifyIndex -= 1 123 124 tainted := map[string]bool{ 125 "dead": true, 126 "baz": false, 127 } 128 129 allocs := []*structs.Allocation{ 130 // Update allocation on baz 131 &structs.Allocation{ 132 ID: structs.GenerateUUID(), 133 NodeID: "baz", 134 Name: "my-job.web[0]", 135 Job: oldJob, 136 }, 137 138 // Ignore allocation on bar 139 &structs.Allocation{ 140 ID: structs.GenerateUUID(), 141 NodeID: "bar", 142 Name: "my-job.web[0]", 143 Job: job, 144 }, 145 146 // Stop allocation on dead. 147 &structs.Allocation{ 148 ID: structs.GenerateUUID(), 149 NodeID: "dead", 150 Name: "my-job.web[0]", 151 }, 152 } 153 154 diff := diffSystemAllocs(job, nodes, tainted, allocs) 155 place := diff.place 156 update := diff.update 157 migrate := diff.migrate 158 stop := diff.stop 159 ignore := diff.ignore 160 161 // We should update the first alloc 162 if len(update) != 1 || update[0].Alloc != allocs[0] { 163 t.Fatalf("bad: %#v", update) 164 } 165 166 // We should ignore the second alloc 167 if len(ignore) != 1 || ignore[0].Alloc != allocs[1] { 168 t.Fatalf("bad: %#v", ignore) 169 } 170 171 // We should stop the third alloc 172 if len(stop) != 1 || stop[0].Alloc != allocs[2] { 173 t.Fatalf("bad: %#v", stop) 174 } 175 176 // There should be no migrates. 177 if len(migrate) != 0 { 178 t.Fatalf("bad: %#v", migrate) 179 } 180 181 // We should place 1 182 if len(place) != 1 { 183 t.Fatalf("bad: %#v", place) 184 } 185 } 186 187 func TestReadyNodesInDCs(t *testing.T) { 188 state, err := state.NewStateStore(os.Stderr) 189 if err != nil { 190 t.Fatalf("err: %v", err) 191 } 192 193 node1 := mock.Node() 194 node2 := mock.Node() 195 node2.Datacenter = "dc2" 196 node3 := mock.Node() 197 node3.Datacenter = "dc2" 198 node3.Status = structs.NodeStatusDown 199 node4 := mock.Node() 200 node4.Drain = true 201 202 noErr(t, state.UpsertNode(1000, node1)) 203 noErr(t, state.UpsertNode(1001, node2)) 204 noErr(t, state.UpsertNode(1002, node3)) 205 noErr(t, state.UpsertNode(1003, node4)) 206 207 nodes, err := readyNodesInDCs(state, []string{"dc1", "dc2"}) 208 if err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 212 if len(nodes) != 2 { 213 t.Fatalf("bad: %v", nodes) 214 } 215 if nodes[0].ID == node3.ID || nodes[1].ID == node3.ID { 216 t.Fatalf("Bad: %#v", nodes) 217 } 218 } 219 220 func TestRetryMax(t *testing.T) { 221 calls := 0 222 bad := func() (bool, error) { 223 calls += 1 224 return false, nil 225 } 226 err := retryMax(3, bad) 227 if err == nil { 228 t.Fatalf("should fail") 229 } 230 if calls != 3 { 231 t.Fatalf("mis match") 232 } 233 234 calls = 0 235 good := func() (bool, error) { 236 calls += 1 237 return true, nil 238 } 239 err = retryMax(3, good) 240 if err != nil { 241 t.Fatalf("err: %v", err) 242 } 243 if calls != 1 { 244 t.Fatalf("mis match") 245 } 246 } 247 248 func TestTaintedNodes(t *testing.T) { 249 state, err := state.NewStateStore(os.Stderr) 250 if err != nil { 251 t.Fatalf("err: %v", err) 252 } 253 254 node1 := mock.Node() 255 node2 := mock.Node() 256 node2.Datacenter = "dc2" 257 node3 := mock.Node() 258 node3.Datacenter = "dc2" 259 node3.Status = structs.NodeStatusDown 260 node4 := mock.Node() 261 node4.Drain = true 262 noErr(t, state.UpsertNode(1000, node1)) 263 noErr(t, state.UpsertNode(1001, node2)) 264 noErr(t, state.UpsertNode(1002, node3)) 265 noErr(t, state.UpsertNode(1003, node4)) 266 267 allocs := []*structs.Allocation{ 268 &structs.Allocation{NodeID: node1.ID}, 269 &structs.Allocation{NodeID: node2.ID}, 270 &structs.Allocation{NodeID: node3.ID}, 271 &structs.Allocation{NodeID: node4.ID}, 272 &structs.Allocation{NodeID: "blah"}, 273 } 274 tainted, err := taintedNodes(state, allocs) 275 if err != nil { 276 t.Fatalf("err: %v", err) 277 } 278 279 if len(tainted) != 5 { 280 t.Fatalf("bad: %v", tainted) 281 } 282 if tainted[node1.ID] || tainted[node2.ID] { 283 t.Fatalf("Bad: %v", tainted) 284 } 285 if !tainted[node3.ID] || !tainted[node4.ID] || !tainted["blah"] { 286 t.Fatalf("Bad: %v", tainted) 287 } 288 } 289 290 func TestShuffleNodes(t *testing.T) { 291 // Use a large number of nodes to make the probability of shuffling to the 292 // original order very low. 293 nodes := []*structs.Node{ 294 mock.Node(), 295 mock.Node(), 296 mock.Node(), 297 mock.Node(), 298 mock.Node(), 299 mock.Node(), 300 mock.Node(), 301 mock.Node(), 302 mock.Node(), 303 mock.Node(), 304 } 305 orig := make([]*structs.Node, len(nodes)) 306 copy(orig, nodes) 307 shuffleNodes(nodes) 308 if reflect.DeepEqual(nodes, orig) { 309 t.Fatalf("should not match") 310 } 311 } 312 313 func TestTasksUpdated(t *testing.T) { 314 j1 := mock.Job() 315 j2 := mock.Job() 316 317 if tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) { 318 t.Fatalf("bad") 319 } 320 321 j2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 322 if !tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) { 323 t.Fatalf("bad") 324 } 325 326 j3 := mock.Job() 327 j3.TaskGroups[0].Tasks[0].Name = "foo" 328 if !tasksUpdated(j1.TaskGroups[0], j3.TaskGroups[0]) { 329 t.Fatalf("bad") 330 } 331 332 j4 := mock.Job() 333 j4.TaskGroups[0].Tasks[0].Driver = "foo" 334 if !tasksUpdated(j1.TaskGroups[0], j4.TaskGroups[0]) { 335 t.Fatalf("bad") 336 } 337 338 j5 := mock.Job() 339 j5.TaskGroups[0].Tasks = append(j5.TaskGroups[0].Tasks, 340 j5.TaskGroups[0].Tasks[0]) 341 if !tasksUpdated(j1.TaskGroups[0], j5.TaskGroups[0]) { 342 t.Fatalf("bad") 343 } 344 345 j6 := mock.Job() 346 j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []string{"http", "https", "admin"} 347 if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) { 348 t.Fatalf("bad") 349 } 350 } 351 352 func TestEvictAndPlace_LimitLessThanAllocs(t *testing.T) { 353 _, ctx := testContext(t) 354 allocs := []allocTuple{ 355 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 356 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 357 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 358 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 359 } 360 diff := &diffResult{} 361 362 limit := 2 363 if !evictAndPlace(ctx, diff, allocs, "", &limit) { 364 t.Fatal("evictAndReplace() should have returned true") 365 } 366 367 if limit != 0 { 368 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit) 369 } 370 371 if len(diff.place) != 2 { 372 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 373 } 374 } 375 376 func TestEvictAndPlace_LimitEqualToAllocs(t *testing.T) { 377 _, ctx := testContext(t) 378 allocs := []allocTuple{ 379 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 380 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 381 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 382 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 383 } 384 diff := &diffResult{} 385 386 limit := 4 387 if evictAndPlace(ctx, diff, allocs, "", &limit) { 388 t.Fatal("evictAndReplace() should have returned false") 389 } 390 391 if limit != 0 { 392 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit) 393 } 394 395 if len(diff.place) != 4 { 396 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 397 } 398 } 399 400 func TestSetStatus(t *testing.T) { 401 h := NewHarness(t) 402 logger := log.New(os.Stderr, "", log.LstdFlags) 403 eval := mock.Eval() 404 status := "a" 405 desc := "b" 406 if err := setStatus(logger, h, eval, nil, status, desc); err != nil { 407 t.Fatalf("setStatus() failed: %v", err) 408 } 409 410 if len(h.Evals) != 1 { 411 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 412 } 413 414 newEval := h.Evals[0] 415 if newEval.ID != eval.ID || newEval.Status != status || newEval.StatusDescription != desc { 416 t.Fatalf("setStatus() submited invalid eval: %v", newEval) 417 } 418 419 h = NewHarness(t) 420 next := mock.Eval() 421 if err := setStatus(logger, h, eval, next, status, desc); err != nil { 422 t.Fatalf("setStatus() failed: %v", err) 423 } 424 425 if len(h.Evals) != 1 { 426 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 427 } 428 429 newEval = h.Evals[0] 430 if newEval.NextEval != next.ID { 431 t.Fatalf("setStatus() didn't set nextEval correctly: %v", newEval) 432 } 433 } 434 435 func TestInplaceUpdate_ChangedTaskGroup(t *testing.T) { 436 state, ctx := testContext(t) 437 eval := mock.Eval() 438 job := mock.Job() 439 440 node := mock.Node() 441 noErr(t, state.UpsertNode(1000, node)) 442 443 // Register an alloc 444 alloc := &structs.Allocation{ 445 ID: structs.GenerateUUID(), 446 EvalID: eval.ID, 447 NodeID: node.ID, 448 JobID: job.ID, 449 Job: job, 450 Resources: &structs.Resources{ 451 CPU: 2048, 452 MemoryMB: 2048, 453 }, 454 DesiredStatus: structs.AllocDesiredStatusRun, 455 } 456 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 457 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 458 459 // Create a new task group that prevents in-place updates. 460 tg := &structs.TaskGroup{} 461 *tg = *job.TaskGroups[0] 462 task := &structs.Task{Name: "FOO"} 463 tg.Tasks = nil 464 tg.Tasks = append(tg.Tasks, task) 465 466 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 467 stack := NewGenericStack(false, ctx) 468 469 // Do the inplace update. 470 unplaced := inplaceUpdate(ctx, eval, job, stack, updates) 471 472 if len(unplaced) != 1 { 473 t.Fatal("inplaceUpdate incorrectly did an inplace update") 474 } 475 476 if len(ctx.plan.NodeAllocation) != 0 { 477 t.Fatal("inplaceUpdate incorrectly did an inplace update") 478 } 479 } 480 481 func TestInplaceUpdate_NoMatch(t *testing.T) { 482 state, ctx := testContext(t) 483 eval := mock.Eval() 484 job := mock.Job() 485 486 node := mock.Node() 487 noErr(t, state.UpsertNode(1000, node)) 488 489 // Register an alloc 490 alloc := &structs.Allocation{ 491 ID: structs.GenerateUUID(), 492 EvalID: eval.ID, 493 NodeID: node.ID, 494 JobID: job.ID, 495 Job: job, 496 Resources: &structs.Resources{ 497 CPU: 2048, 498 MemoryMB: 2048, 499 }, 500 DesiredStatus: structs.AllocDesiredStatusRun, 501 } 502 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 503 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 504 505 // Create a new task group that requires too much resources. 506 tg := &structs.TaskGroup{} 507 *tg = *job.TaskGroups[0] 508 resource := &structs.Resources{CPU: 9999} 509 tg.Tasks[0].Resources = resource 510 511 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 512 stack := NewGenericStack(false, ctx) 513 514 // Do the inplace update. 515 unplaced := inplaceUpdate(ctx, eval, job, stack, updates) 516 517 if len(unplaced) != 1 { 518 t.Fatal("inplaceUpdate incorrectly did an inplace update") 519 } 520 521 if len(ctx.plan.NodeAllocation) != 0 { 522 t.Fatal("inplaceUpdate incorrectly did an inplace update") 523 } 524 } 525 526 func TestInplaceUpdate_Success(t *testing.T) { 527 state, ctx := testContext(t) 528 eval := mock.Eval() 529 job := mock.Job() 530 531 node := mock.Node() 532 noErr(t, state.UpsertNode(1000, node)) 533 534 // Register an alloc 535 alloc := &structs.Allocation{ 536 ID: structs.GenerateUUID(), 537 EvalID: eval.ID, 538 NodeID: node.ID, 539 JobID: job.ID, 540 Job: job, 541 Resources: &structs.Resources{ 542 CPU: 2048, 543 MemoryMB: 2048, 544 }, 545 DesiredStatus: structs.AllocDesiredStatusRun, 546 } 547 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 548 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 549 550 // Create a new task group that updates the resources. 551 tg := &structs.TaskGroup{} 552 *tg = *job.TaskGroups[0] 553 resource := &structs.Resources{CPU: 737} 554 tg.Tasks[0].Resources = resource 555 556 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 557 stack := NewGenericStack(false, ctx) 558 559 // Do the inplace update. 560 unplaced := inplaceUpdate(ctx, eval, job, stack, updates) 561 562 if len(unplaced) != 0 { 563 t.Fatal("inplaceUpdate did not do an inplace update") 564 } 565 566 if len(ctx.plan.NodeAllocation) != 1 { 567 t.Fatal("inplaceUpdate did not do an inplace update") 568 } 569 } 570 571 func TestEvictAndPlace_LimitGreaterThanAllocs(t *testing.T) { 572 _, ctx := testContext(t) 573 allocs := []allocTuple{ 574 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 575 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 576 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 577 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 578 } 579 diff := &diffResult{} 580 581 limit := 6 582 if evictAndPlace(ctx, diff, allocs, "", &limit) { 583 t.Fatal("evictAndReplace() should have returned false") 584 } 585 586 if limit != 2 { 587 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 2", limit) 588 } 589 590 if len(diff.place) != 4 { 591 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 592 } 593 } 594 595 func TestTaskGroupConstraints(t *testing.T) { 596 constr := &structs.Constraint{Hard: true} 597 constr2 := &structs.Constraint{LTarget: "foo"} 598 constr3 := &structs.Constraint{Weight: 10} 599 600 tg := &structs.TaskGroup{ 601 Name: "web", 602 Count: 10, 603 Constraints: []*structs.Constraint{constr}, 604 Tasks: []*structs.Task{ 605 &structs.Task{ 606 Driver: "exec", 607 Resources: &structs.Resources{ 608 CPU: 500, 609 MemoryMB: 256, 610 }, 611 Constraints: []*structs.Constraint{constr2}, 612 }, 613 &structs.Task{ 614 Driver: "docker", 615 Resources: &structs.Resources{ 616 CPU: 500, 617 MemoryMB: 256, 618 }, 619 Constraints: []*structs.Constraint{constr3}, 620 }, 621 }, 622 } 623 624 // Build the expected values. 625 expConstr := []*structs.Constraint{constr, constr2, constr3} 626 expDrivers := map[string]struct{}{"exec": struct{}{}, "docker": struct{}{}} 627 expSize := &structs.Resources{ 628 CPU: 1000, 629 MemoryMB: 512, 630 } 631 632 actConstrains := taskGroupConstraints(tg) 633 if !reflect.DeepEqual(actConstrains.constraints, expConstr) { 634 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.constraints, expConstr) 635 } 636 if !reflect.DeepEqual(actConstrains.drivers, expDrivers) { 637 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.drivers, expDrivers) 638 } 639 if !reflect.DeepEqual(actConstrains.size, expSize) { 640 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.size, expSize) 641 } 642 643 }