github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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 // noErr is used to assert there are no errors 16 func noErr(t *testing.T, err error) { 17 if err != nil { 18 t.Fatalf("err: %v", err) 19 } 20 } 21 22 func TestMaterializeTaskGroups(t *testing.T) { 23 job := mock.Job() 24 index := materializeTaskGroups(job) 25 if len(index) != 10 { 26 t.Fatalf("Bad: %#v", index) 27 } 28 29 for i := 0; i < 10; i++ { 30 name := fmt.Sprintf("my-job.web[%d]", i) 31 tg, ok := index[name] 32 if !ok { 33 t.Fatalf("bad") 34 } 35 if tg != job.TaskGroups[0] { 36 t.Fatalf("bad") 37 } 38 } 39 } 40 41 func TestDiffAllocs(t *testing.T) { 42 job := mock.Job() 43 required := materializeTaskGroups(job) 44 45 // The "old" job has a previous modify index 46 oldJob := new(structs.Job) 47 *oldJob = *job 48 oldJob.JobModifyIndex -= 1 49 50 drainNode := mock.Node() 51 drainNode.Drain = true 52 53 deadNode := mock.Node() 54 deadNode.Status = structs.NodeStatusDown 55 56 tainted := map[string]*structs.Node{ 57 "dead": deadNode, 58 "drainNode": drainNode, 59 } 60 61 allocs := []*structs.Allocation{ 62 // Update the 1st 63 &structs.Allocation{ 64 ID: structs.GenerateUUID(), 65 NodeID: "zip", 66 Name: "my-job.web[0]", 67 Job: oldJob, 68 }, 69 70 // Ignore the 2rd 71 &structs.Allocation{ 72 ID: structs.GenerateUUID(), 73 NodeID: "zip", 74 Name: "my-job.web[1]", 75 Job: job, 76 }, 77 78 // Evict 11th 79 &structs.Allocation{ 80 ID: structs.GenerateUUID(), 81 NodeID: "zip", 82 Name: "my-job.web[10]", 83 Job: oldJob, 84 }, 85 86 // Migrate the 3rd 87 &structs.Allocation{ 88 ID: structs.GenerateUUID(), 89 NodeID: "drainNode", 90 Name: "my-job.web[2]", 91 Job: oldJob, 92 }, 93 // Mark the 4th lost 94 &structs.Allocation{ 95 ID: structs.GenerateUUID(), 96 NodeID: "dead", 97 Name: "my-job.web[3]", 98 Job: oldJob, 99 }, 100 } 101 102 // Have three terminal allocs 103 terminalAllocs := map[string]*structs.Allocation{ 104 "my-job.web[4]": &structs.Allocation{ 105 ID: structs.GenerateUUID(), 106 NodeID: "zip", 107 Name: "my-job.web[4]", 108 Job: job, 109 }, 110 "my-job.web[5]": &structs.Allocation{ 111 ID: structs.GenerateUUID(), 112 NodeID: "zip", 113 Name: "my-job.web[5]", 114 Job: job, 115 }, 116 "my-job.web[6]": &structs.Allocation{ 117 ID: structs.GenerateUUID(), 118 NodeID: "zip", 119 Name: "my-job.web[6]", 120 Job: job, 121 }, 122 } 123 124 diff := diffAllocs(job, tainted, required, allocs, terminalAllocs) 125 place := diff.place 126 update := diff.update 127 migrate := diff.migrate 128 stop := diff.stop 129 ignore := diff.ignore 130 lost := diff.lost 131 132 // We should update the first alloc 133 if len(update) != 1 || update[0].Alloc != allocs[0] { 134 t.Fatalf("bad: %#v", update) 135 } 136 137 // We should ignore the second alloc 138 if len(ignore) != 1 || ignore[0].Alloc != allocs[1] { 139 t.Fatalf("bad: %#v", ignore) 140 } 141 142 // We should stop the 3rd alloc 143 if len(stop) != 1 || stop[0].Alloc != allocs[2] { 144 t.Fatalf("bad: %#v", stop) 145 } 146 147 // We should migrate the 4rd alloc 148 if len(migrate) != 1 || migrate[0].Alloc != allocs[3] { 149 t.Fatalf("bad: %#v", migrate) 150 } 151 152 // We should mark the 5th alloc as lost 153 if len(lost) != 1 || lost[0].Alloc != allocs[4] { 154 t.Fatalf("bad: %#v", migrate) 155 } 156 157 // We should place 6 158 if len(place) != 6 { 159 t.Fatalf("bad: %#v", place) 160 } 161 162 // Ensure that the allocations which are replacements of terminal allocs are 163 // annotated 164 for name, alloc := range terminalAllocs { 165 for _, allocTuple := range diff.place { 166 if name == allocTuple.Name { 167 if !reflect.DeepEqual(alloc, allocTuple.Alloc) { 168 t.Fatalf("expected: %#v, actual: %#v", alloc, allocTuple.Alloc) 169 } 170 } 171 } 172 } 173 } 174 175 func TestDiffSystemAllocs(t *testing.T) { 176 job := mock.SystemJob() 177 178 drainNode := mock.Node() 179 drainNode.Drain = true 180 181 deadNode := mock.Node() 182 deadNode.Status = structs.NodeStatusDown 183 184 tainted := map[string]*structs.Node{ 185 deadNode.ID: deadNode, 186 drainNode.ID: drainNode, 187 } 188 189 // Create three alive nodes. 190 nodes := []*structs.Node{{ID: "foo"}, {ID: "bar"}, {ID: "baz"}, 191 {ID: "pipe"}, {ID: drainNode.ID}, {ID: deadNode.ID}} 192 193 // The "old" job has a previous modify index 194 oldJob := new(structs.Job) 195 *oldJob = *job 196 oldJob.JobModifyIndex -= 1 197 198 allocs := []*structs.Allocation{ 199 // Update allocation on baz 200 &structs.Allocation{ 201 ID: structs.GenerateUUID(), 202 NodeID: "baz", 203 Name: "my-job.web[0]", 204 Job: oldJob, 205 }, 206 207 // Ignore allocation on bar 208 &structs.Allocation{ 209 ID: structs.GenerateUUID(), 210 NodeID: "bar", 211 Name: "my-job.web[0]", 212 Job: job, 213 }, 214 215 // Stop allocation on draining node. 216 &structs.Allocation{ 217 ID: structs.GenerateUUID(), 218 NodeID: drainNode.ID, 219 Name: "my-job.web[0]", 220 Job: oldJob, 221 }, 222 // Mark as lost on a dead node 223 &structs.Allocation{ 224 ID: structs.GenerateUUID(), 225 NodeID: deadNode.ID, 226 Name: "my-job.web[0]", 227 Job: oldJob, 228 }, 229 } 230 231 // Have three terminal allocs 232 terminalAllocs := map[string]*structs.Allocation{ 233 "my-job.web[0]": &structs.Allocation{ 234 ID: structs.GenerateUUID(), 235 NodeID: "pipe", 236 Name: "my-job.web[0]", 237 Job: job, 238 }, 239 } 240 241 diff := diffSystemAllocs(job, nodes, tainted, allocs, terminalAllocs) 242 place := diff.place 243 update := diff.update 244 migrate := diff.migrate 245 stop := diff.stop 246 ignore := diff.ignore 247 lost := diff.lost 248 249 // We should update the first alloc 250 if len(update) != 1 || update[0].Alloc != allocs[0] { 251 t.Fatalf("bad: %#v", update) 252 } 253 254 // We should ignore the second alloc 255 if len(ignore) != 1 || ignore[0].Alloc != allocs[1] { 256 t.Fatalf("bad: %#v", ignore) 257 } 258 259 // We should stop the third alloc 260 if len(stop) != 1 || stop[0].Alloc != allocs[2] { 261 t.Fatalf("bad: %#v", stop) 262 } 263 264 // There should be no migrates. 265 if len(migrate) != 0 { 266 t.Fatalf("bad: %#v", migrate) 267 } 268 269 // We should mark the 5th alloc as lost 270 if len(lost) != 1 || lost[0].Alloc != allocs[3] { 271 t.Fatalf("bad: %#v", migrate) 272 } 273 274 // We should place 1 275 if l := len(place); l != 2 { 276 t.Fatalf("bad: %#v", l) 277 } 278 279 // Ensure that the allocations which are replacements of terminal allocs are 280 // annotated 281 for _, alloc := range terminalAllocs { 282 for _, allocTuple := range diff.place { 283 if alloc.NodeID == allocTuple.Alloc.NodeID { 284 if !reflect.DeepEqual(alloc, allocTuple.Alloc) { 285 t.Fatalf("expected: %#v, actual: %#v", alloc, allocTuple.Alloc) 286 } 287 } 288 } 289 } 290 } 291 292 func TestReadyNodesInDCs(t *testing.T) { 293 state, err := state.NewStateStore(os.Stderr) 294 if err != nil { 295 t.Fatalf("err: %v", err) 296 } 297 298 node1 := mock.Node() 299 node2 := mock.Node() 300 node2.Datacenter = "dc2" 301 node3 := mock.Node() 302 node3.Datacenter = "dc2" 303 node3.Status = structs.NodeStatusDown 304 node4 := mock.Node() 305 node4.Drain = true 306 307 noErr(t, state.UpsertNode(1000, node1)) 308 noErr(t, state.UpsertNode(1001, node2)) 309 noErr(t, state.UpsertNode(1002, node3)) 310 noErr(t, state.UpsertNode(1003, node4)) 311 312 nodes, dc, err := readyNodesInDCs(state, []string{"dc1", "dc2"}) 313 if err != nil { 314 t.Fatalf("err: %v", err) 315 } 316 317 if len(nodes) != 2 { 318 t.Fatalf("bad: %v", nodes) 319 } 320 if nodes[0].ID == node3.ID || nodes[1].ID == node3.ID { 321 t.Fatalf("Bad: %#v", nodes) 322 } 323 if count, ok := dc["dc1"]; !ok || count != 1 { 324 t.Fatalf("Bad: dc1 count %v", count) 325 } 326 if count, ok := dc["dc2"]; !ok || count != 1 { 327 t.Fatalf("Bad: dc2 count %v", count) 328 } 329 } 330 331 func TestRetryMax(t *testing.T) { 332 calls := 0 333 bad := func() (bool, error) { 334 calls += 1 335 return false, nil 336 } 337 err := retryMax(3, bad, nil) 338 if err == nil { 339 t.Fatalf("should fail") 340 } 341 if calls != 3 { 342 t.Fatalf("mis match") 343 } 344 345 calls = 0 346 first := true 347 reset := func() bool { 348 if calls == 3 && first { 349 first = false 350 return true 351 } 352 return false 353 } 354 err = retryMax(3, bad, reset) 355 if err == nil { 356 t.Fatalf("should fail") 357 } 358 if calls != 6 { 359 t.Fatalf("mis match") 360 } 361 362 calls = 0 363 good := func() (bool, error) { 364 calls += 1 365 return true, nil 366 } 367 err = retryMax(3, good, nil) 368 if err != nil { 369 t.Fatalf("err: %v", err) 370 } 371 if calls != 1 { 372 t.Fatalf("mis match") 373 } 374 } 375 376 func TestTaintedNodes(t *testing.T) { 377 state, err := state.NewStateStore(os.Stderr) 378 if err != nil { 379 t.Fatalf("err: %v", err) 380 } 381 382 node1 := mock.Node() 383 node2 := mock.Node() 384 node2.Datacenter = "dc2" 385 node3 := mock.Node() 386 node3.Datacenter = "dc2" 387 node3.Status = structs.NodeStatusDown 388 node4 := mock.Node() 389 node4.Drain = true 390 noErr(t, state.UpsertNode(1000, node1)) 391 noErr(t, state.UpsertNode(1001, node2)) 392 noErr(t, state.UpsertNode(1002, node3)) 393 noErr(t, state.UpsertNode(1003, node4)) 394 395 allocs := []*structs.Allocation{ 396 &structs.Allocation{NodeID: node1.ID}, 397 &structs.Allocation{NodeID: node2.ID}, 398 &structs.Allocation{NodeID: node3.ID}, 399 &structs.Allocation{NodeID: node4.ID}, 400 &structs.Allocation{NodeID: "12345678-abcd-efab-cdef-123456789abc"}, 401 } 402 tainted, err := taintedNodes(state, allocs) 403 if err != nil { 404 t.Fatalf("err: %v", err) 405 } 406 407 if len(tainted) != 3 { 408 t.Fatalf("bad: %v", tainted) 409 } 410 411 if _, ok := tainted[node1.ID]; ok { 412 t.Fatalf("Bad: %v", tainted) 413 } 414 if _, ok := tainted[node2.ID]; ok { 415 t.Fatalf("Bad: %v", tainted) 416 } 417 418 if node, ok := tainted[node3.ID]; !ok || node == nil { 419 t.Fatalf("Bad: %v", tainted) 420 } 421 422 if node, ok := tainted[node4.ID]; !ok || node == nil { 423 t.Fatalf("Bad: %v", tainted) 424 } 425 426 if node, ok := tainted["12345678-abcd-efab-cdef-123456789abc"]; !ok || node != nil { 427 t.Fatalf("Bad: %v", tainted) 428 } 429 } 430 431 func TestShuffleNodes(t *testing.T) { 432 // Use a large number of nodes to make the probability of shuffling to the 433 // original order very low. 434 nodes := []*structs.Node{ 435 mock.Node(), 436 mock.Node(), 437 mock.Node(), 438 mock.Node(), 439 mock.Node(), 440 mock.Node(), 441 mock.Node(), 442 mock.Node(), 443 mock.Node(), 444 mock.Node(), 445 } 446 orig := make([]*structs.Node, len(nodes)) 447 copy(orig, nodes) 448 shuffleNodes(nodes) 449 if reflect.DeepEqual(nodes, orig) { 450 t.Fatalf("should not match") 451 } 452 } 453 454 func TestTasksUpdated(t *testing.T) { 455 j1 := mock.Job() 456 j2 := mock.Job() 457 458 if tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) { 459 t.Fatalf("bad") 460 } 461 462 j2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 463 if !tasksUpdated(j1.TaskGroups[0], j2.TaskGroups[0]) { 464 t.Fatalf("bad") 465 } 466 467 j3 := mock.Job() 468 j3.TaskGroups[0].Tasks[0].Name = "foo" 469 if !tasksUpdated(j1.TaskGroups[0], j3.TaskGroups[0]) { 470 t.Fatalf("bad") 471 } 472 473 j4 := mock.Job() 474 j4.TaskGroups[0].Tasks[0].Driver = "foo" 475 if !tasksUpdated(j1.TaskGroups[0], j4.TaskGroups[0]) { 476 t.Fatalf("bad") 477 } 478 479 j5 := mock.Job() 480 j5.TaskGroups[0].Tasks = append(j5.TaskGroups[0].Tasks, 481 j5.TaskGroups[0].Tasks[0]) 482 if !tasksUpdated(j1.TaskGroups[0], j5.TaskGroups[0]) { 483 t.Fatalf("bad") 484 } 485 486 j6 := mock.Job() 487 j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []structs.Port{{"http", 0}, {"https", 0}, {"admin", 0}} 488 if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) { 489 t.Fatalf("bad") 490 } 491 492 j7 := mock.Job() 493 j7.TaskGroups[0].Tasks[0].Env["NEW_ENV"] = "NEW_VALUE" 494 if !tasksUpdated(j1.TaskGroups[0], j7.TaskGroups[0]) { 495 t.Fatalf("bad") 496 } 497 498 j8 := mock.Job() 499 j8.TaskGroups[0].Tasks[0].User = "foo" 500 if !tasksUpdated(j1.TaskGroups[0], j8.TaskGroups[0]) { 501 t.Fatalf("bad") 502 } 503 504 j9 := mock.Job() 505 j9.TaskGroups[0].Tasks[0].Artifacts = []*structs.TaskArtifact{ 506 { 507 GetterSource: "http://foo.com/bar", 508 }, 509 } 510 if !tasksUpdated(j1.TaskGroups[0], j9.TaskGroups[0]) { 511 t.Fatalf("bad") 512 } 513 514 j10 := mock.Job() 515 j10.TaskGroups[0].Tasks[0].Meta["baz"] = "boom" 516 if !tasksUpdated(j1.TaskGroups[0], j10.TaskGroups[0]) { 517 t.Fatalf("bad") 518 } 519 520 j11 := mock.Job() 521 j11.TaskGroups[0].Tasks[0].Resources.CPU = 1337 522 if !tasksUpdated(j1.TaskGroups[0], j11.TaskGroups[0]) { 523 t.Fatalf("bad") 524 } 525 526 j12 := mock.Job() 527 j12.TaskGroups[0].Tasks[0].Resources.Networks[0].MBits = 100 528 if !tasksUpdated(j1.TaskGroups[0], j12.TaskGroups[0]) { 529 t.Fatalf("bad") 530 } 531 532 j13 := mock.Job() 533 j13.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts[0].Label = "foobar" 534 if !tasksUpdated(j1.TaskGroups[0], j13.TaskGroups[0]) { 535 t.Fatalf("bad") 536 } 537 538 j14 := mock.Job() 539 j14.TaskGroups[0].Tasks[0].Resources.Networks[0].ReservedPorts = []structs.Port{{Label: "foo", Value: 1312}} 540 if !tasksUpdated(j1.TaskGroups[0], j14.TaskGroups[0]) { 541 t.Fatalf("bad") 542 } 543 544 j15 := mock.Job() 545 j15.TaskGroups[0].Tasks[0].Vault = &structs.Vault{Policies: []string{"foo"}} 546 if !tasksUpdated(j1.TaskGroups[0], j15.TaskGroups[0]) { 547 t.Fatalf("bad") 548 } 549 550 j16 := mock.Job() 551 j16.TaskGroups[0].EphemeralDisk.Sticky = true 552 if !tasksUpdated(j1.TaskGroups[0], j16.TaskGroups[0]) { 553 t.Fatal("bad") 554 } 555 } 556 557 func TestEvictAndPlace_LimitLessThanAllocs(t *testing.T) { 558 _, ctx := testContext(t) 559 allocs := []allocTuple{ 560 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 561 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 562 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 563 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 564 } 565 diff := &diffResult{} 566 567 limit := 2 568 if !evictAndPlace(ctx, diff, allocs, "", &limit) { 569 t.Fatal("evictAndReplace() should have returned true") 570 } 571 572 if limit != 0 { 573 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit) 574 } 575 576 if len(diff.place) != 2 { 577 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 578 } 579 } 580 581 func TestEvictAndPlace_LimitEqualToAllocs(t *testing.T) { 582 _, ctx := testContext(t) 583 allocs := []allocTuple{ 584 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 585 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 586 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 587 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 588 } 589 diff := &diffResult{} 590 591 limit := 4 592 if evictAndPlace(ctx, diff, allocs, "", &limit) { 593 t.Fatal("evictAndReplace() should have returned false") 594 } 595 596 if limit != 0 { 597 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 0", limit) 598 } 599 600 if len(diff.place) != 4 { 601 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 602 } 603 } 604 605 func TestSetStatus(t *testing.T) { 606 h := NewHarness(t) 607 logger := log.New(os.Stderr, "", log.LstdFlags) 608 eval := mock.Eval() 609 status := "a" 610 desc := "b" 611 if err := setStatus(logger, h, eval, nil, nil, nil, status, desc, nil); err != nil { 612 t.Fatalf("setStatus() failed: %v", err) 613 } 614 615 if len(h.Evals) != 1 { 616 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 617 } 618 619 newEval := h.Evals[0] 620 if newEval.ID != eval.ID || newEval.Status != status || newEval.StatusDescription != desc { 621 t.Fatalf("setStatus() submited invalid eval: %v", newEval) 622 } 623 624 // Test next evals 625 h = NewHarness(t) 626 next := mock.Eval() 627 if err := setStatus(logger, h, eval, next, nil, nil, status, desc, nil); err != nil { 628 t.Fatalf("setStatus() failed: %v", err) 629 } 630 631 if len(h.Evals) != 1 { 632 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 633 } 634 635 newEval = h.Evals[0] 636 if newEval.NextEval != next.ID { 637 t.Fatalf("setStatus() didn't set nextEval correctly: %v", newEval) 638 } 639 640 // Test blocked evals 641 h = NewHarness(t) 642 blocked := mock.Eval() 643 if err := setStatus(logger, h, eval, nil, blocked, nil, status, desc, nil); err != nil { 644 t.Fatalf("setStatus() failed: %v", err) 645 } 646 647 if len(h.Evals) != 1 { 648 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 649 } 650 651 newEval = h.Evals[0] 652 if newEval.BlockedEval != blocked.ID { 653 t.Fatalf("setStatus() didn't set BlockedEval correctly: %v", newEval) 654 } 655 656 // Test metrics 657 h = NewHarness(t) 658 metrics := map[string]*structs.AllocMetric{"foo": nil} 659 if err := setStatus(logger, h, eval, nil, nil, metrics, status, desc, nil); err != nil { 660 t.Fatalf("setStatus() failed: %v", err) 661 } 662 663 if len(h.Evals) != 1 { 664 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 665 } 666 667 newEval = h.Evals[0] 668 if !reflect.DeepEqual(newEval.FailedTGAllocs, metrics) { 669 t.Fatalf("setStatus() didn't set failed task group metrics correctly: %v", newEval) 670 } 671 672 // Test queued allocations 673 h = NewHarness(t) 674 queuedAllocs := map[string]int{"web": 1} 675 676 if err := setStatus(logger, h, eval, nil, nil, metrics, status, desc, queuedAllocs); err != nil { 677 t.Fatalf("setStatus() failed: %v", err) 678 } 679 680 if len(h.Evals) != 1 { 681 t.Fatalf("setStatus() didn't update plan: %v", h.Evals) 682 } 683 684 newEval = h.Evals[0] 685 if !reflect.DeepEqual(newEval.QueuedAllocations, queuedAllocs) { 686 t.Fatalf("setStatus() didn't set failed task group metrics correctly: %v", newEval) 687 } 688 } 689 690 func TestInplaceUpdate_ChangedTaskGroup(t *testing.T) { 691 state, ctx := testContext(t) 692 eval := mock.Eval() 693 job := mock.Job() 694 695 node := mock.Node() 696 noErr(t, state.UpsertNode(900, node)) 697 698 // Register an alloc 699 alloc := &structs.Allocation{ 700 ID: structs.GenerateUUID(), 701 EvalID: eval.ID, 702 NodeID: node.ID, 703 JobID: job.ID, 704 Job: job, 705 Resources: &structs.Resources{ 706 CPU: 2048, 707 MemoryMB: 2048, 708 }, 709 DesiredStatus: structs.AllocDesiredStatusRun, 710 TaskGroup: "web", 711 } 712 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 713 noErr(t, state.UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))) 714 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 715 716 // Create a new task group that prevents in-place updates. 717 tg := &structs.TaskGroup{} 718 *tg = *job.TaskGroups[0] 719 task := &structs.Task{Name: "FOO"} 720 tg.Tasks = nil 721 tg.Tasks = append(tg.Tasks, task) 722 723 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 724 stack := NewGenericStack(false, ctx) 725 726 // Do the inplace update. 727 unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates) 728 729 if len(unplaced) != 1 || len(inplace) != 0 { 730 t.Fatal("inplaceUpdate incorrectly did an inplace update") 731 } 732 733 if len(ctx.plan.NodeAllocation) != 0 { 734 t.Fatal("inplaceUpdate incorrectly did an inplace update") 735 } 736 } 737 738 func TestInplaceUpdate_NoMatch(t *testing.T) { 739 state, ctx := testContext(t) 740 eval := mock.Eval() 741 job := mock.Job() 742 743 node := mock.Node() 744 noErr(t, state.UpsertNode(900, node)) 745 746 // Register an alloc 747 alloc := &structs.Allocation{ 748 ID: structs.GenerateUUID(), 749 EvalID: eval.ID, 750 NodeID: node.ID, 751 JobID: job.ID, 752 Job: job, 753 Resources: &structs.Resources{ 754 CPU: 2048, 755 MemoryMB: 2048, 756 }, 757 DesiredStatus: structs.AllocDesiredStatusRun, 758 TaskGroup: "web", 759 } 760 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 761 noErr(t, state.UpsertJobSummary(1000, mock.JobSummary(alloc.JobID))) 762 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 763 764 // Create a new task group that requires too much resources. 765 tg := &structs.TaskGroup{} 766 *tg = *job.TaskGroups[0] 767 resource := &structs.Resources{CPU: 9999} 768 tg.Tasks[0].Resources = resource 769 770 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 771 stack := NewGenericStack(false, ctx) 772 773 // Do the inplace update. 774 unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates) 775 776 if len(unplaced) != 1 || len(inplace) != 0 { 777 t.Fatal("inplaceUpdate incorrectly did an inplace update") 778 } 779 780 if len(ctx.plan.NodeAllocation) != 0 { 781 t.Fatal("inplaceUpdate incorrectly did an inplace update") 782 } 783 } 784 785 func TestInplaceUpdate_Success(t *testing.T) { 786 state, ctx := testContext(t) 787 eval := mock.Eval() 788 job := mock.Job() 789 790 node := mock.Node() 791 noErr(t, state.UpsertNode(900, node)) 792 793 // Register an alloc 794 alloc := &structs.Allocation{ 795 ID: structs.GenerateUUID(), 796 EvalID: eval.ID, 797 NodeID: node.ID, 798 JobID: job.ID, 799 Job: job, 800 TaskGroup: job.TaskGroups[0].Name, 801 Resources: &structs.Resources{ 802 CPU: 2048, 803 MemoryMB: 2048, 804 }, 805 DesiredStatus: structs.AllocDesiredStatusRun, 806 } 807 alloc.TaskResources = map[string]*structs.Resources{"web": alloc.Resources} 808 noErr(t, state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID))) 809 noErr(t, state.UpsertAllocs(1001, []*structs.Allocation{alloc})) 810 811 // Create a new task group that updates the resources. 812 tg := &structs.TaskGroup{} 813 *tg = *job.TaskGroups[0] 814 resource := &structs.Resources{CPU: 737} 815 tg.Tasks[0].Resources = resource 816 newServices := []*structs.Service{ 817 { 818 Name: "dummy-service", 819 PortLabel: "http", 820 }, 821 { 822 Name: "dummy-service2", 823 PortLabel: "http", 824 }, 825 } 826 827 // Delete service 2 828 tg.Tasks[0].Services = tg.Tasks[0].Services[:1] 829 830 // Add the new services 831 tg.Tasks[0].Services = append(tg.Tasks[0].Services, newServices...) 832 833 updates := []allocTuple{{Alloc: alloc, TaskGroup: tg}} 834 stack := NewGenericStack(false, ctx) 835 stack.SetJob(job) 836 837 // Do the inplace update. 838 unplaced, inplace := inplaceUpdate(ctx, eval, job, stack, updates) 839 840 if len(unplaced) != 0 || len(inplace) != 1 { 841 t.Fatal("inplaceUpdate did not do an inplace update") 842 } 843 844 if len(ctx.plan.NodeAllocation) != 1 { 845 t.Fatal("inplaceUpdate did not do an inplace update") 846 } 847 848 if inplace[0].Alloc.ID != alloc.ID { 849 t.Fatalf("inplaceUpdate returned the wrong, inplace updated alloc: %#v", inplace) 850 } 851 852 // Get the alloc we inserted. 853 a := inplace[0].Alloc // TODO(sean@): Verify this is correct vs: ctx.plan.NodeAllocation[alloc.NodeID][0] 854 if a.Job == nil { 855 t.Fatalf("bad") 856 } 857 858 if len(a.Job.TaskGroups) != 1 { 859 t.Fatalf("bad") 860 } 861 862 if len(a.Job.TaskGroups[0].Tasks) != 1 { 863 t.Fatalf("bad") 864 } 865 866 if len(a.Job.TaskGroups[0].Tasks[0].Services) != 3 { 867 t.Fatalf("Expected number of services: %v, Actual: %v", 3, len(a.Job.TaskGroups[0].Tasks[0].Services)) 868 } 869 870 serviceNames := make(map[string]struct{}, 3) 871 for _, consulService := range a.Job.TaskGroups[0].Tasks[0].Services { 872 serviceNames[consulService.Name] = struct{}{} 873 } 874 if len(serviceNames) != 3 { 875 t.Fatalf("bad") 876 } 877 878 for _, name := range []string{"dummy-service", "dummy-service2", "web-frontend"} { 879 if _, found := serviceNames[name]; !found { 880 t.Errorf("Expected consul service name missing: %v", name) 881 } 882 } 883 } 884 885 func TestEvictAndPlace_LimitGreaterThanAllocs(t *testing.T) { 886 _, ctx := testContext(t) 887 allocs := []allocTuple{ 888 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 889 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 890 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 891 allocTuple{Alloc: &structs.Allocation{ID: structs.GenerateUUID()}}, 892 } 893 diff := &diffResult{} 894 895 limit := 6 896 if evictAndPlace(ctx, diff, allocs, "", &limit) { 897 t.Fatal("evictAndReplace() should have returned false") 898 } 899 900 if limit != 2 { 901 t.Fatalf("evictAndReplace() should decremented limit; got %v; want 2", limit) 902 } 903 904 if len(diff.place) != 4 { 905 t.Fatalf("evictAndReplace() didn't insert into diffResult properly: %v", diff.place) 906 } 907 } 908 909 func TestTaskGroupConstraints(t *testing.T) { 910 constr := &structs.Constraint{RTarget: "bar"} 911 constr2 := &structs.Constraint{LTarget: "foo"} 912 constr3 := &structs.Constraint{Operand: "<"} 913 914 tg := &structs.TaskGroup{ 915 Name: "web", 916 Count: 10, 917 Constraints: []*structs.Constraint{constr}, 918 EphemeralDisk: &structs.EphemeralDisk{}, 919 Tasks: []*structs.Task{ 920 &structs.Task{ 921 Driver: "exec", 922 Resources: &structs.Resources{ 923 CPU: 500, 924 MemoryMB: 256, 925 }, 926 Constraints: []*structs.Constraint{constr2}, 927 }, 928 &structs.Task{ 929 Driver: "docker", 930 Resources: &structs.Resources{ 931 CPU: 500, 932 MemoryMB: 256, 933 }, 934 Constraints: []*structs.Constraint{constr3}, 935 }, 936 }, 937 } 938 939 // Build the expected values. 940 expConstr := []*structs.Constraint{constr, constr2, constr3} 941 expDrivers := map[string]struct{}{"exec": struct{}{}, "docker": struct{}{}} 942 expSize := &structs.Resources{ 943 CPU: 1000, 944 MemoryMB: 512, 945 } 946 947 actConstrains := taskGroupConstraints(tg) 948 if !reflect.DeepEqual(actConstrains.constraints, expConstr) { 949 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.constraints, expConstr) 950 } 951 if !reflect.DeepEqual(actConstrains.drivers, expDrivers) { 952 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.drivers, expDrivers) 953 } 954 if !reflect.DeepEqual(actConstrains.size, expSize) { 955 t.Fatalf("taskGroupConstraints(%v) returned %v; want %v", tg, actConstrains.size, expSize) 956 } 957 958 } 959 960 func TestProgressMade(t *testing.T) { 961 noopPlan := &structs.PlanResult{} 962 if progressMade(nil) || progressMade(noopPlan) { 963 t.Fatal("no progress plan marked as making progress") 964 } 965 966 m := map[string][]*structs.Allocation{ 967 "foo": []*structs.Allocation{mock.Alloc()}, 968 } 969 both := &structs.PlanResult{ 970 NodeAllocation: m, 971 NodeUpdate: m, 972 } 973 update := &structs.PlanResult{NodeUpdate: m} 974 alloc := &structs.PlanResult{NodeAllocation: m} 975 if !(progressMade(both) && progressMade(update) && progressMade(alloc)) { 976 t.Fatal("bad") 977 } 978 } 979 980 func TestDesiredUpdates(t *testing.T) { 981 tg1 := &structs.TaskGroup{Name: "foo"} 982 tg2 := &structs.TaskGroup{Name: "bar"} 983 a2 := &structs.Allocation{TaskGroup: "bar"} 984 985 place := []allocTuple{ 986 allocTuple{TaskGroup: tg1}, 987 allocTuple{TaskGroup: tg1}, 988 allocTuple{TaskGroup: tg1}, 989 allocTuple{TaskGroup: tg2}, 990 } 991 stop := []allocTuple{ 992 allocTuple{TaskGroup: tg2, Alloc: a2}, 993 allocTuple{TaskGroup: tg2, Alloc: a2}, 994 } 995 ignore := []allocTuple{ 996 allocTuple{TaskGroup: tg1}, 997 } 998 migrate := []allocTuple{ 999 allocTuple{TaskGroup: tg2}, 1000 } 1001 inplace := []allocTuple{ 1002 allocTuple{TaskGroup: tg1}, 1003 allocTuple{TaskGroup: tg1}, 1004 } 1005 destructive := []allocTuple{ 1006 allocTuple{TaskGroup: tg1}, 1007 allocTuple{TaskGroup: tg2}, 1008 allocTuple{TaskGroup: tg2}, 1009 } 1010 diff := &diffResult{ 1011 place: place, 1012 stop: stop, 1013 ignore: ignore, 1014 migrate: migrate, 1015 } 1016 1017 expected := map[string]*structs.DesiredUpdates{ 1018 "foo": { 1019 Place: 3, 1020 Ignore: 1, 1021 InPlaceUpdate: 2, 1022 DestructiveUpdate: 1, 1023 }, 1024 "bar": { 1025 Place: 1, 1026 Stop: 2, 1027 Migrate: 1, 1028 DestructiveUpdate: 2, 1029 }, 1030 } 1031 1032 desired := desiredUpdates(diff, inplace, destructive) 1033 if !reflect.DeepEqual(desired, expected) { 1034 t.Fatalf("desiredUpdates() returned %#v; want %#v", desired, expected) 1035 } 1036 } 1037 1038 func TestUtil_AdjustQueuedAllocations(t *testing.T) { 1039 logger := log.New(os.Stderr, "", log.LstdFlags) 1040 alloc1 := mock.Alloc() 1041 alloc2 := mock.Alloc() 1042 alloc2.CreateIndex = 4 1043 alloc3 := mock.Alloc() 1044 alloc3.CreateIndex = 3 1045 alloc4 := mock.Alloc() 1046 alloc4.CreateIndex = 6 1047 1048 planResult := structs.PlanResult{ 1049 NodeUpdate: map[string][]*structs.Allocation{ 1050 "node-1": []*structs.Allocation{alloc1}, 1051 }, 1052 NodeAllocation: map[string][]*structs.Allocation{ 1053 "node-1": []*structs.Allocation{ 1054 alloc2, 1055 }, 1056 "node-2": []*structs.Allocation{ 1057 alloc3, alloc4, 1058 }, 1059 }, 1060 RefreshIndex: 3, 1061 AllocIndex: 4, 1062 } 1063 1064 queuedAllocs := map[string]int{"web": 2} 1065 adjustQueuedAllocations(logger, &planResult, queuedAllocs) 1066 1067 if queuedAllocs["web"] != 1 { 1068 t.Fatalf("expected: %v, actual: %v", 1, queuedAllocs["web"]) 1069 } 1070 } 1071 1072 func TestUtil_UpdateNonTerminalAllocsToLost(t *testing.T) { 1073 node := mock.Node() 1074 alloc1 := mock.Alloc() 1075 alloc1.NodeID = node.ID 1076 alloc1.DesiredStatus = structs.AllocDesiredStatusStop 1077 1078 alloc2 := mock.Alloc() 1079 alloc2.NodeID = node.ID 1080 alloc2.DesiredStatus = structs.AllocDesiredStatusStop 1081 alloc2.ClientStatus = structs.AllocClientStatusRunning 1082 1083 alloc3 := mock.Alloc() 1084 alloc3.NodeID = node.ID 1085 alloc3.DesiredStatus = structs.AllocDesiredStatusStop 1086 alloc3.ClientStatus = structs.AllocClientStatusComplete 1087 1088 alloc4 := mock.Alloc() 1089 alloc4.NodeID = node.ID 1090 alloc4.DesiredStatus = structs.AllocDesiredStatusStop 1091 alloc4.ClientStatus = structs.AllocClientStatusFailed 1092 1093 allocs := []*structs.Allocation{alloc1, alloc2, alloc3, alloc4} 1094 plan := structs.Plan{ 1095 NodeUpdate: make(map[string][]*structs.Allocation), 1096 } 1097 tainted := map[string]*structs.Node{node.ID: node} 1098 1099 updateNonTerminalAllocsToLost(&plan, tainted, allocs) 1100 1101 allocsLost := make([]string, 0, 2) 1102 for _, alloc := range plan.NodeUpdate[node.ID] { 1103 allocsLost = append(allocsLost, alloc.ID) 1104 } 1105 expected := []string{alloc1.ID, alloc2.ID} 1106 if !reflect.DeepEqual(allocsLost, expected) { 1107 t.Fatalf("actual: %v, expected: %v", allocsLost, expected) 1108 } 1109 }