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