github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/scheduler/generic_sched_test.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "testing" 8 "time" 9 10 memdb "github.com/hashicorp/go-memdb" 11 "github.com/ncodes/nomad/nomad/mock" 12 "github.com/ncodes/nomad/nomad/structs" 13 ) 14 15 func TestServiceSched_JobRegister(t *testing.T) { 16 h := NewHarness(t) 17 18 // Create some nodes 19 for i := 0; i < 10; i++ { 20 node := mock.Node() 21 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 22 } 23 24 // Create a job 25 job := mock.Job() 26 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 27 28 // Create a mock evaluation to register the job 29 eval := &structs.Evaluation{ 30 ID: structs.GenerateUUID(), 31 Priority: job.Priority, 32 TriggeredBy: structs.EvalTriggerJobRegister, 33 JobID: job.ID, 34 } 35 36 // Process the evaluation 37 err := h.Process(NewServiceScheduler, eval) 38 if err != nil { 39 t.Fatalf("err: %v", err) 40 } 41 42 // Ensure a single plan 43 if len(h.Plans) != 1 { 44 t.Fatalf("bad: %#v", h.Plans) 45 } 46 plan := h.Plans[0] 47 48 // Ensure the plan doesn't have annotations. 49 if plan.Annotations != nil { 50 t.Fatalf("expected no annotations") 51 } 52 53 // Ensure the eval has no spawned blocked eval 54 if len(h.CreateEvals) != 0 { 55 t.Fatalf("bad: %#v", h.CreateEvals) 56 if h.Evals[0].BlockedEval != "" { 57 t.Fatalf("bad: %#v", h.Evals[0]) 58 } 59 } 60 61 // Ensure the plan allocated 62 var planned []*structs.Allocation 63 for _, allocList := range plan.NodeAllocation { 64 planned = append(planned, allocList...) 65 } 66 if len(planned) != 10 { 67 t.Fatalf("bad: %#v", plan) 68 } 69 70 // Lookup the allocations by JobID 71 ws := memdb.NewWatchSet() 72 out, err := h.State.AllocsByJob(ws, job.ID, false) 73 noErr(t, err) 74 75 // Ensure all allocations placed 76 if len(out) != 10 { 77 t.Fatalf("bad: %#v", out) 78 } 79 80 // Ensure different ports were used. 81 used := make(map[int]struct{}) 82 for _, alloc := range out { 83 for _, resource := range alloc.TaskResources { 84 for _, port := range resource.Networks[0].DynamicPorts { 85 if _, ok := used[port.Value]; ok { 86 t.Fatalf("Port collision %v", port.Value) 87 } 88 used[port.Value] = struct{}{} 89 } 90 } 91 } 92 93 h.AssertEvalStatus(t, structs.EvalStatusComplete) 94 } 95 96 func TestServiceSched_JobRegister_StickyAllocs(t *testing.T) { 97 h := NewHarness(t) 98 99 // Create some nodes 100 for i := 0; i < 10; i++ { 101 node := mock.Node() 102 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 103 } 104 105 // Create a job 106 job := mock.Job() 107 job.TaskGroups[0].EphemeralDisk.Sticky = true 108 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 109 110 // Create a mock evaluation to register the job 111 eval := &structs.Evaluation{ 112 ID: structs.GenerateUUID(), 113 Priority: job.Priority, 114 TriggeredBy: structs.EvalTriggerJobRegister, 115 JobID: job.ID, 116 } 117 118 // Process the evaluation 119 if err := h.Process(NewServiceScheduler, eval); err != nil { 120 t.Fatalf("err: %v", err) 121 } 122 123 // Ensure the plan allocated 124 plan := h.Plans[0] 125 var planned []*structs.Allocation 126 for _, allocList := range plan.NodeAllocation { 127 planned = append(planned, allocList...) 128 } 129 if len(planned) != 10 { 130 t.Fatalf("bad: %#v", plan) 131 } 132 133 // Get an allocation and mark it as failed 134 alloc := planned[4].Copy() 135 alloc.ClientStatus = structs.AllocClientStatusFailed 136 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), []*structs.Allocation{alloc})) 137 138 // Create a mock evaluation to handle the update 139 eval = &structs.Evaluation{ 140 ID: structs.GenerateUUID(), 141 Priority: job.Priority, 142 TriggeredBy: structs.EvalTriggerNodeUpdate, 143 JobID: job.ID, 144 } 145 h1 := NewHarnessWithState(t, h.State) 146 if err := h1.Process(NewServiceScheduler, eval); err != nil { 147 t.Fatalf("err: %v", err) 148 } 149 150 // Ensure we have created only one new allocation 151 plan = h1.Plans[0] 152 var newPlanned []*structs.Allocation 153 for _, allocList := range plan.NodeAllocation { 154 newPlanned = append(newPlanned, allocList...) 155 } 156 if len(newPlanned) != 1 { 157 t.Fatalf("bad plan: %#v", plan) 158 } 159 // Ensure that the new allocation was placed on the same node as the older 160 // one 161 if newPlanned[0].NodeID != alloc.NodeID || newPlanned[0].PreviousAllocation != alloc.ID { 162 t.Fatalf("expected: %#v, actual: %#v", alloc, newPlanned[0]) 163 } 164 } 165 166 func TestServiceSched_JobRegister_DiskConstraints(t *testing.T) { 167 h := NewHarness(t) 168 169 // Create a node 170 node := mock.Node() 171 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 172 173 // Create a job with count 2 and disk as 60GB so that only one allocation 174 // can fit 175 job := mock.Job() 176 job.TaskGroups[0].Count = 2 177 job.TaskGroups[0].EphemeralDisk.SizeMB = 88 * 1024 178 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 179 180 // Create a mock evaluation to register the job 181 eval := &structs.Evaluation{ 182 ID: structs.GenerateUUID(), 183 Priority: job.Priority, 184 TriggeredBy: structs.EvalTriggerJobRegister, 185 JobID: job.ID, 186 } 187 188 // Process the evaluation 189 err := h.Process(NewServiceScheduler, eval) 190 if err != nil { 191 t.Fatalf("err: %v", err) 192 } 193 194 // Ensure a single plan 195 if len(h.Plans) != 1 { 196 t.Fatalf("bad: %#v", h.Plans) 197 } 198 plan := h.Plans[0] 199 200 // Ensure the plan doesn't have annotations. 201 if plan.Annotations != nil { 202 t.Fatalf("expected no annotations") 203 } 204 205 // Ensure the eval has a blocked eval 206 if len(h.CreateEvals) != 1 { 207 t.Fatalf("bad: %#v", h.CreateEvals) 208 } 209 210 // Ensure the plan allocated only one allocation 211 var planned []*structs.Allocation 212 for _, allocList := range plan.NodeAllocation { 213 planned = append(planned, allocList...) 214 } 215 if len(planned) != 1 { 216 t.Fatalf("bad: %#v", plan) 217 } 218 219 // Lookup the allocations by JobID 220 ws := memdb.NewWatchSet() 221 out, err := h.State.AllocsByJob(ws, job.ID, false) 222 noErr(t, err) 223 224 // Ensure only one allocation was placed 225 if len(out) != 1 { 226 t.Fatalf("bad: %#v", out) 227 } 228 229 h.AssertEvalStatus(t, structs.EvalStatusComplete) 230 } 231 232 func TestServiceSched_JobRegister_DistinctHosts(t *testing.T) { 233 h := NewHarness(t) 234 235 // Create some nodes 236 for i := 0; i < 10; i++ { 237 node := mock.Node() 238 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 239 } 240 241 // Create a job that uses distinct host and has count 1 higher than what is 242 // possible. 243 job := mock.Job() 244 job.TaskGroups[0].Count = 11 245 job.Constraints = append(job.Constraints, &structs.Constraint{Operand: structs.ConstraintDistinctHosts}) 246 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 247 248 // Create a mock evaluation to register the job 249 eval := &structs.Evaluation{ 250 ID: structs.GenerateUUID(), 251 Priority: job.Priority, 252 TriggeredBy: structs.EvalTriggerJobRegister, 253 JobID: job.ID, 254 } 255 256 // Process the evaluation 257 err := h.Process(NewServiceScheduler, eval) 258 if err != nil { 259 t.Fatalf("err: %v", err) 260 } 261 262 // Ensure a single plan 263 if len(h.Plans) != 1 { 264 t.Fatalf("bad: %#v", h.Plans) 265 } 266 plan := h.Plans[0] 267 268 // Ensure the eval has spawned blocked eval 269 if len(h.CreateEvals) != 1 { 270 t.Fatalf("bad: %#v", h.CreateEvals) 271 } 272 273 // Ensure the plan failed to alloc 274 outEval := h.Evals[0] 275 if len(outEval.FailedTGAllocs) != 1 { 276 t.Fatalf("bad: %+v", outEval) 277 } 278 279 // Ensure the plan allocated 280 var planned []*structs.Allocation 281 for _, allocList := range plan.NodeAllocation { 282 planned = append(planned, allocList...) 283 } 284 if len(planned) != 10 { 285 t.Fatalf("bad: %#v", plan) 286 } 287 288 // Lookup the allocations by JobID 289 ws := memdb.NewWatchSet() 290 out, err := h.State.AllocsByJob(ws, job.ID, false) 291 noErr(t, err) 292 293 // Ensure all allocations placed 294 if len(out) != 10 { 295 t.Fatalf("bad: %#v", out) 296 } 297 298 // Ensure different node was used per. 299 used := make(map[string]struct{}) 300 for _, alloc := range out { 301 if _, ok := used[alloc.NodeID]; ok { 302 t.Fatalf("Node collision %v", alloc.NodeID) 303 } 304 used[alloc.NodeID] = struct{}{} 305 } 306 307 h.AssertEvalStatus(t, structs.EvalStatusComplete) 308 } 309 310 func TestServiceSched_JobRegister_DistinctProperty(t *testing.T) { 311 h := NewHarness(t) 312 313 // Create some nodes 314 for i := 0; i < 10; i++ { 315 node := mock.Node() 316 rack := "rack2" 317 if i < 5 { 318 rack = "rack1" 319 } 320 node.Meta["rack"] = rack 321 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 322 } 323 324 // Create a job that uses distinct property and has count higher than what is 325 // possible. 326 job := mock.Job() 327 job.TaskGroups[0].Count = 4 328 job.Constraints = append(job.Constraints, 329 &structs.Constraint{ 330 Operand: structs.ConstraintDistinctProperty, 331 LTarget: "${meta.rack}", 332 }) 333 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 334 335 // Create a mock evaluation to register the job 336 eval := &structs.Evaluation{ 337 ID: structs.GenerateUUID(), 338 Priority: job.Priority, 339 TriggeredBy: structs.EvalTriggerJobRegister, 340 JobID: job.ID, 341 } 342 343 // Process the evaluation 344 err := h.Process(NewServiceScheduler, eval) 345 if err != nil { 346 t.Fatalf("err: %v", err) 347 } 348 349 // Ensure a single plan 350 if len(h.Plans) != 1 { 351 t.Fatalf("bad: %#v", h.Plans) 352 } 353 plan := h.Plans[0] 354 355 // Ensure the plan doesn't have annotations. 356 if plan.Annotations != nil { 357 t.Fatalf("expected no annotations") 358 } 359 360 // Ensure the eval has spawned blocked eval 361 if len(h.CreateEvals) != 1 { 362 t.Fatalf("bad: %#v", h.CreateEvals) 363 } 364 365 // Ensure the plan failed to alloc 366 outEval := h.Evals[0] 367 if len(outEval.FailedTGAllocs) != 1 { 368 t.Fatalf("bad: %+v", outEval) 369 } 370 371 // Ensure the plan allocated 372 var planned []*structs.Allocation 373 for _, allocList := range plan.NodeAllocation { 374 planned = append(planned, allocList...) 375 } 376 if len(planned) != 2 { 377 t.Fatalf("bad: %#v", plan) 378 } 379 380 // Lookup the allocations by JobID 381 ws := memdb.NewWatchSet() 382 out, err := h.State.AllocsByJob(ws, job.ID, false) 383 noErr(t, err) 384 385 // Ensure all allocations placed 386 if len(out) != 2 { 387 t.Fatalf("bad: %#v", out) 388 } 389 390 // Ensure different node was used per. 391 used := make(map[string]struct{}) 392 for _, alloc := range out { 393 if _, ok := used[alloc.NodeID]; ok { 394 t.Fatalf("Node collision %v", alloc.NodeID) 395 } 396 used[alloc.NodeID] = struct{}{} 397 } 398 399 h.AssertEvalStatus(t, structs.EvalStatusComplete) 400 } 401 402 func TestServiceSched_JobRegister_DistinctProperty_TaskGroup(t *testing.T) { 403 h := NewHarness(t) 404 405 // Create some nodes 406 for i := 0; i < 2; i++ { 407 node := mock.Node() 408 node.Meta["ssd"] = "true" 409 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 410 } 411 412 // Create a job that uses distinct property and has count higher than what is 413 // possible. 414 job := mock.Job() 415 job.TaskGroups = append(job.TaskGroups, job.TaskGroups[0].Copy()) 416 job.TaskGroups[0].Count = 1 417 job.TaskGroups[0].Constraints = append(job.TaskGroups[0].Constraints, 418 &structs.Constraint{ 419 Operand: structs.ConstraintDistinctProperty, 420 LTarget: "${meta.ssd}", 421 }) 422 423 job.TaskGroups[1].Name = "tg2" 424 job.TaskGroups[1].Count = 1 425 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 426 427 // Create a mock evaluation to register the job 428 eval := &structs.Evaluation{ 429 ID: structs.GenerateUUID(), 430 Priority: job.Priority, 431 TriggeredBy: structs.EvalTriggerJobRegister, 432 JobID: job.ID, 433 } 434 435 // Process the evaluation 436 err := h.Process(NewServiceScheduler, eval) 437 if err != nil { 438 t.Fatalf("err: %v", err) 439 } 440 441 // Ensure a single plan 442 if len(h.Plans) != 1 { 443 t.Fatalf("bad: %#v", h.Plans) 444 } 445 plan := h.Plans[0] 446 447 // Ensure the plan doesn't have annotations. 448 if plan.Annotations != nil { 449 t.Fatalf("expected no annotations") 450 } 451 452 // Ensure the eval hasn't spawned blocked eval 453 if len(h.CreateEvals) != 0 { 454 t.Fatalf("bad: %#v", h.CreateEvals[0]) 455 } 456 457 // Ensure the plan allocated 458 var planned []*structs.Allocation 459 for _, allocList := range plan.NodeAllocation { 460 planned = append(planned, allocList...) 461 } 462 if len(planned) != 2 { 463 t.Fatalf("bad: %#v", plan) 464 } 465 466 // Lookup the allocations by JobID 467 ws := memdb.NewWatchSet() 468 out, err := h.State.AllocsByJob(ws, job.ID, false) 469 noErr(t, err) 470 471 // Ensure all allocations placed 472 if len(out) != 2 { 473 t.Fatalf("bad: %#v", out) 474 } 475 476 h.AssertEvalStatus(t, structs.EvalStatusComplete) 477 } 478 479 func TestServiceSched_JobRegister_Annotate(t *testing.T) { 480 h := NewHarness(t) 481 482 // Create some nodes 483 for i := 0; i < 10; i++ { 484 node := mock.Node() 485 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 486 } 487 488 // Create a job 489 job := mock.Job() 490 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 491 492 // Create a mock evaluation to register the job 493 eval := &structs.Evaluation{ 494 ID: structs.GenerateUUID(), 495 Priority: job.Priority, 496 TriggeredBy: structs.EvalTriggerJobRegister, 497 JobID: job.ID, 498 AnnotatePlan: true, 499 } 500 501 // Process the evaluation 502 err := h.Process(NewServiceScheduler, eval) 503 if err != nil { 504 t.Fatalf("err: %v", err) 505 } 506 507 // Ensure a single plan 508 if len(h.Plans) != 1 { 509 t.Fatalf("bad: %#v", h.Plans) 510 } 511 plan := h.Plans[0] 512 513 // Ensure the plan allocated 514 var planned []*structs.Allocation 515 for _, allocList := range plan.NodeAllocation { 516 planned = append(planned, allocList...) 517 } 518 if len(planned) != 10 { 519 t.Fatalf("bad: %#v", plan) 520 } 521 522 // Lookup the allocations by JobID 523 ws := memdb.NewWatchSet() 524 out, err := h.State.AllocsByJob(ws, job.ID, false) 525 noErr(t, err) 526 527 // Ensure all allocations placed 528 if len(out) != 10 { 529 t.Fatalf("bad: %#v", out) 530 } 531 532 h.AssertEvalStatus(t, structs.EvalStatusComplete) 533 534 // Ensure the plan had annotations. 535 if plan.Annotations == nil { 536 t.Fatalf("expected annotations") 537 } 538 539 desiredTGs := plan.Annotations.DesiredTGUpdates 540 if l := len(desiredTGs); l != 1 { 541 t.Fatalf("incorrect number of task groups; got %v; want %v", l, 1) 542 } 543 544 desiredChanges, ok := desiredTGs["web"] 545 if !ok { 546 t.Fatalf("expected task group web to have desired changes") 547 } 548 549 expected := &structs.DesiredUpdates{Place: 10} 550 if !reflect.DeepEqual(desiredChanges, expected) { 551 t.Fatalf("Unexpected desired updates; got %#v; want %#v", desiredChanges, expected) 552 } 553 } 554 555 func TestServiceSched_JobRegister_CountZero(t *testing.T) { 556 h := NewHarness(t) 557 558 // Create some nodes 559 for i := 0; i < 10; i++ { 560 node := mock.Node() 561 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 562 } 563 564 // Create a job and set the task group count to zero. 565 job := mock.Job() 566 job.TaskGroups[0].Count = 0 567 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 568 569 // Create a mock evaluation to register the job 570 eval := &structs.Evaluation{ 571 ID: structs.GenerateUUID(), 572 Priority: job.Priority, 573 TriggeredBy: structs.EvalTriggerJobRegister, 574 JobID: job.ID, 575 } 576 577 // Process the evaluation 578 err := h.Process(NewServiceScheduler, eval) 579 if err != nil { 580 t.Fatalf("err: %v", err) 581 } 582 583 // Ensure there was no plan 584 if len(h.Plans) != 0 { 585 t.Fatalf("bad: %#v", h.Plans) 586 } 587 588 // Lookup the allocations by JobID 589 ws := memdb.NewWatchSet() 590 out, err := h.State.AllocsByJob(ws, job.ID, false) 591 noErr(t, err) 592 593 // Ensure no allocations placed 594 if len(out) != 0 { 595 t.Fatalf("bad: %#v", out) 596 } 597 598 h.AssertEvalStatus(t, structs.EvalStatusComplete) 599 } 600 601 func TestServiceSched_JobRegister_AllocFail(t *testing.T) { 602 h := NewHarness(t) 603 604 // Create NO nodes 605 // Create a job 606 job := mock.Job() 607 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 608 609 // Create a mock evaluation to register the job 610 eval := &structs.Evaluation{ 611 ID: structs.GenerateUUID(), 612 Priority: job.Priority, 613 TriggeredBy: structs.EvalTriggerJobRegister, 614 JobID: job.ID, 615 } 616 617 // Process the evaluation 618 err := h.Process(NewServiceScheduler, eval) 619 if err != nil { 620 t.Fatalf("err: %v", err) 621 } 622 623 // Ensure no plan 624 if len(h.Plans) != 0 { 625 t.Fatalf("bad: %#v", h.Plans) 626 } 627 628 // Ensure there is a follow up eval. 629 if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked { 630 t.Fatalf("bad: %#v", h.CreateEvals) 631 } 632 633 if len(h.Evals) != 1 { 634 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 635 } 636 outEval := h.Evals[0] 637 638 // Ensure the eval has its spawned blocked eval 639 if outEval.BlockedEval != h.CreateEvals[0].ID { 640 t.Fatalf("bad: %#v", outEval) 641 } 642 643 // Ensure the plan failed to alloc 644 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 645 t.Fatalf("bad: %#v", outEval) 646 } 647 648 metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name] 649 if !ok { 650 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 651 } 652 653 // Check the coalesced failures 654 if metrics.CoalescedFailures != 9 { 655 t.Fatalf("bad: %#v", metrics) 656 } 657 658 // Check the available nodes 659 if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 0 { 660 t.Fatalf("bad: %#v", metrics) 661 } 662 663 // Check queued allocations 664 queued := outEval.QueuedAllocations["web"] 665 if queued != 10 { 666 t.Fatalf("expected queued: %v, actual: %v", 10, queued) 667 } 668 h.AssertEvalStatus(t, structs.EvalStatusComplete) 669 } 670 671 func TestServiceSched_JobRegister_CreateBlockedEval(t *testing.T) { 672 h := NewHarness(t) 673 674 // Create a full node 675 node := mock.Node() 676 node.Reserved = node.Resources 677 node.ComputeClass() 678 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 679 680 // Create an ineligible node 681 node2 := mock.Node() 682 node2.Attributes["kernel.name"] = "windows" 683 node2.ComputeClass() 684 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 685 686 // Create a jobs 687 job := mock.Job() 688 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 689 690 // Create a mock evaluation to register the job 691 eval := &structs.Evaluation{ 692 ID: structs.GenerateUUID(), 693 Priority: job.Priority, 694 TriggeredBy: structs.EvalTriggerJobRegister, 695 JobID: job.ID, 696 } 697 698 // Process the evaluation 699 err := h.Process(NewServiceScheduler, eval) 700 if err != nil { 701 t.Fatalf("err: %v", err) 702 } 703 704 // Ensure no plan 705 if len(h.Plans) != 0 { 706 t.Fatalf("bad: %#v", h.Plans) 707 } 708 709 // Ensure the plan has created a follow up eval. 710 if len(h.CreateEvals) != 1 { 711 t.Fatalf("bad: %#v", h.CreateEvals) 712 } 713 714 created := h.CreateEvals[0] 715 if created.Status != structs.EvalStatusBlocked { 716 t.Fatalf("bad: %#v", created) 717 } 718 719 classes := created.ClassEligibility 720 if len(classes) != 2 || !classes[node.ComputedClass] || classes[node2.ComputedClass] { 721 t.Fatalf("bad: %#v", classes) 722 } 723 724 if created.EscapedComputedClass { 725 t.Fatalf("bad: %#v", created) 726 } 727 728 // Ensure there is a follow up eval. 729 if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked { 730 t.Fatalf("bad: %#v", h.CreateEvals) 731 } 732 733 if len(h.Evals) != 1 { 734 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 735 } 736 outEval := h.Evals[0] 737 738 // Ensure the plan failed to alloc 739 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 740 t.Fatalf("bad: %#v", outEval) 741 } 742 743 metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name] 744 if !ok { 745 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 746 } 747 748 // Check the coalesced failures 749 if metrics.CoalescedFailures != 9 { 750 t.Fatalf("bad: %#v", metrics) 751 } 752 753 // Check the available nodes 754 if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 2 { 755 t.Fatalf("bad: %#v", metrics) 756 } 757 758 h.AssertEvalStatus(t, structs.EvalStatusComplete) 759 } 760 761 func TestServiceSched_JobRegister_FeasibleAndInfeasibleTG(t *testing.T) { 762 h := NewHarness(t) 763 764 // Create one node 765 node := mock.Node() 766 node.NodeClass = "class_0" 767 noErr(t, node.ComputeClass()) 768 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 769 770 // Create a job that constrains on a node class 771 job := mock.Job() 772 job.TaskGroups[0].Count = 2 773 job.TaskGroups[0].Constraints = append(job.Constraints, 774 &structs.Constraint{ 775 LTarget: "${node.class}", 776 RTarget: "class_0", 777 Operand: "=", 778 }, 779 ) 780 tg2 := job.TaskGroups[0].Copy() 781 tg2.Name = "web2" 782 tg2.Constraints[1].RTarget = "class_1" 783 job.TaskGroups = append(job.TaskGroups, tg2) 784 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 785 786 // Create a mock evaluation to register the job 787 eval := &structs.Evaluation{ 788 ID: structs.GenerateUUID(), 789 Priority: job.Priority, 790 TriggeredBy: structs.EvalTriggerJobRegister, 791 JobID: job.ID, 792 } 793 794 // Process the evaluation 795 err := h.Process(NewServiceScheduler, eval) 796 if err != nil { 797 t.Fatalf("err: %v", err) 798 } 799 800 // Ensure a single plan 801 if len(h.Plans) != 1 { 802 t.Fatalf("bad: %#v", h.Plans) 803 } 804 plan := h.Plans[0] 805 806 // Ensure the plan allocated 807 var planned []*structs.Allocation 808 for _, allocList := range plan.NodeAllocation { 809 planned = append(planned, allocList...) 810 } 811 if len(planned) != 2 { 812 t.Fatalf("bad: %#v", plan) 813 } 814 815 // Ensure two allocations placed 816 ws := memdb.NewWatchSet() 817 out, err := h.State.AllocsByJob(ws, job.ID, false) 818 noErr(t, err) 819 if len(out) != 2 { 820 t.Fatalf("bad: %#v", out) 821 } 822 823 if len(h.Evals) != 1 { 824 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 825 } 826 outEval := h.Evals[0] 827 828 // Ensure the eval has its spawned blocked eval 829 if outEval.BlockedEval != h.CreateEvals[0].ID { 830 t.Fatalf("bad: %#v", outEval) 831 } 832 833 // Ensure the plan failed to alloc one tg 834 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 835 t.Fatalf("bad: %#v", outEval) 836 } 837 838 metrics, ok := outEval.FailedTGAllocs[tg2.Name] 839 if !ok { 840 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 841 } 842 843 // Check the coalesced failures 844 if metrics.CoalescedFailures != tg2.Count-1 { 845 t.Fatalf("bad: %#v", metrics) 846 } 847 848 h.AssertEvalStatus(t, structs.EvalStatusComplete) 849 } 850 851 // This test just ensures the scheduler handles the eval type to avoid 852 // regressions. 853 func TestServiceSched_EvaluateMaxPlanEval(t *testing.T) { 854 h := NewHarness(t) 855 856 // Create a job and set the task group count to zero. 857 job := mock.Job() 858 job.TaskGroups[0].Count = 0 859 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 860 861 // Create a mock blocked evaluation 862 eval := &structs.Evaluation{ 863 ID: structs.GenerateUUID(), 864 Status: structs.EvalStatusBlocked, 865 Priority: job.Priority, 866 TriggeredBy: structs.EvalTriggerMaxPlans, 867 JobID: job.ID, 868 } 869 870 // Insert it into the state store 871 noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval})) 872 873 // Process the evaluation 874 err := h.Process(NewServiceScheduler, eval) 875 if err != nil { 876 t.Fatalf("err: %v", err) 877 } 878 879 // Ensure there was no plan 880 if len(h.Plans) != 0 { 881 t.Fatalf("bad: %#v", h.Plans) 882 } 883 884 h.AssertEvalStatus(t, structs.EvalStatusComplete) 885 } 886 887 func TestServiceSched_Plan_Partial_Progress(t *testing.T) { 888 h := NewHarness(t) 889 890 // Create a node 891 node := mock.Node() 892 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 893 894 // Create a job with a high resource ask so that all the allocations can't 895 // be placed on a single node. 896 job := mock.Job() 897 job.TaskGroups[0].Count = 3 898 job.TaskGroups[0].Tasks[0].Resources.CPU = 3600 899 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 900 901 // Create a mock evaluation to register the job 902 eval := &structs.Evaluation{ 903 ID: structs.GenerateUUID(), 904 Priority: job.Priority, 905 TriggeredBy: structs.EvalTriggerJobRegister, 906 JobID: job.ID, 907 } 908 909 // Process the evaluation 910 err := h.Process(NewServiceScheduler, eval) 911 if err != nil { 912 t.Fatalf("err: %v", err) 913 } 914 915 // Ensure a single plan 916 if len(h.Plans) != 1 { 917 t.Fatalf("bad: %#v", h.Plans) 918 } 919 plan := h.Plans[0] 920 921 // Ensure the plan doesn't have annotations. 922 if plan.Annotations != nil { 923 t.Fatalf("expected no annotations") 924 } 925 926 // Ensure the plan allocated 927 var planned []*structs.Allocation 928 for _, allocList := range plan.NodeAllocation { 929 planned = append(planned, allocList...) 930 } 931 if len(planned) != 1 { 932 t.Fatalf("bad: %#v", plan) 933 } 934 935 // Lookup the allocations by JobID 936 ws := memdb.NewWatchSet() 937 out, err := h.State.AllocsByJob(ws, job.ID, false) 938 noErr(t, err) 939 940 // Ensure only one allocations placed 941 if len(out) != 1 { 942 t.Fatalf("bad: %#v", out) 943 } 944 945 queued := h.Evals[0].QueuedAllocations["web"] 946 if queued != 2 { 947 t.Fatalf("expected: %v, actual: %v", 2, queued) 948 } 949 950 h.AssertEvalStatus(t, structs.EvalStatusComplete) 951 } 952 953 func TestServiceSched_EvaluateBlockedEval(t *testing.T) { 954 h := NewHarness(t) 955 956 // Create a job 957 job := mock.Job() 958 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 959 960 // Create a mock blocked evaluation 961 eval := &structs.Evaluation{ 962 ID: structs.GenerateUUID(), 963 Status: structs.EvalStatusBlocked, 964 Priority: job.Priority, 965 TriggeredBy: structs.EvalTriggerJobRegister, 966 JobID: job.ID, 967 } 968 969 // Insert it into the state store 970 noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval})) 971 972 // Process the evaluation 973 err := h.Process(NewServiceScheduler, eval) 974 if err != nil { 975 t.Fatalf("err: %v", err) 976 } 977 978 // Ensure there was no plan 979 if len(h.Plans) != 0 { 980 t.Fatalf("bad: %#v", h.Plans) 981 } 982 983 // Ensure that the eval was reblocked 984 if len(h.ReblockEvals) != 1 { 985 t.Fatalf("bad: %#v", h.ReblockEvals) 986 } 987 if h.ReblockEvals[0].ID != eval.ID { 988 t.Fatalf("expect same eval to be reblocked; got %q; want %q", h.ReblockEvals[0].ID, eval.ID) 989 } 990 991 // Ensure the eval status was not updated 992 if len(h.Evals) != 0 { 993 t.Fatalf("Existing eval should not have status set") 994 } 995 } 996 997 func TestServiceSched_EvaluateBlockedEval_Finished(t *testing.T) { 998 h := NewHarness(t) 999 1000 // Create some nodes 1001 for i := 0; i < 10; i++ { 1002 node := mock.Node() 1003 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1004 } 1005 1006 // Create a job and set the task group count to zero. 1007 job := mock.Job() 1008 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1009 1010 // Create a mock blocked evaluation 1011 eval := &structs.Evaluation{ 1012 ID: structs.GenerateUUID(), 1013 Status: structs.EvalStatusBlocked, 1014 Priority: job.Priority, 1015 TriggeredBy: structs.EvalTriggerJobRegister, 1016 JobID: job.ID, 1017 } 1018 1019 // Insert it into the state store 1020 noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval})) 1021 1022 // Process the evaluation 1023 err := h.Process(NewServiceScheduler, eval) 1024 if err != nil { 1025 t.Fatalf("err: %v", err) 1026 } 1027 1028 // Ensure a single plan 1029 if len(h.Plans) != 1 { 1030 t.Fatalf("bad: %#v", h.Plans) 1031 } 1032 plan := h.Plans[0] 1033 1034 // Ensure the plan doesn't have annotations. 1035 if plan.Annotations != nil { 1036 t.Fatalf("expected no annotations") 1037 } 1038 1039 // Ensure the eval has no spawned blocked eval 1040 if len(h.Evals) != 1 { 1041 t.Fatalf("bad: %#v", h.Evals) 1042 if h.Evals[0].BlockedEval != "" { 1043 t.Fatalf("bad: %#v", h.Evals[0]) 1044 } 1045 } 1046 1047 // Ensure the plan allocated 1048 var planned []*structs.Allocation 1049 for _, allocList := range plan.NodeAllocation { 1050 planned = append(planned, allocList...) 1051 } 1052 if len(planned) != 10 { 1053 t.Fatalf("bad: %#v", plan) 1054 } 1055 1056 // Lookup the allocations by JobID 1057 ws := memdb.NewWatchSet() 1058 out, err := h.State.AllocsByJob(ws, job.ID, false) 1059 noErr(t, err) 1060 1061 // Ensure all allocations placed 1062 if len(out) != 10 { 1063 t.Fatalf("bad: %#v", out) 1064 } 1065 1066 // Ensure the eval was not reblocked 1067 if len(h.ReblockEvals) != 0 { 1068 t.Fatalf("Existing eval should not have been reblocked as it placed all allocations") 1069 } 1070 1071 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1072 1073 // Ensure queued allocations is zero 1074 queued := h.Evals[0].QueuedAllocations["web"] 1075 if queued != 0 { 1076 t.Fatalf("expected queued: %v, actual: %v", 0, queued) 1077 } 1078 } 1079 1080 func TestServiceSched_JobModify(t *testing.T) { 1081 h := NewHarness(t) 1082 1083 // Create some nodes 1084 var nodes []*structs.Node 1085 for i := 0; i < 10; i++ { 1086 node := mock.Node() 1087 nodes = append(nodes, node) 1088 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1089 } 1090 1091 // Generate a fake job with allocations 1092 job := mock.Job() 1093 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1094 1095 var allocs []*structs.Allocation 1096 for i := 0; i < 10; i++ { 1097 alloc := mock.Alloc() 1098 alloc.Job = job 1099 alloc.JobID = job.ID 1100 alloc.NodeID = nodes[i].ID 1101 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1102 allocs = append(allocs, alloc) 1103 } 1104 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1105 1106 // Add a few terminal status allocations, these should be ignored 1107 var terminal []*structs.Allocation 1108 for i := 0; i < 5; i++ { 1109 alloc := mock.Alloc() 1110 alloc.Job = job 1111 alloc.JobID = job.ID 1112 alloc.NodeID = nodes[i].ID 1113 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1114 alloc.DesiredStatus = structs.AllocDesiredStatusStop 1115 terminal = append(terminal, alloc) 1116 } 1117 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 1118 1119 // Update the job 1120 job2 := mock.Job() 1121 job2.ID = job.ID 1122 1123 // Update the task, such that it cannot be done in-place 1124 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 1125 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 1126 1127 // Create a mock evaluation to deal with drain 1128 eval := &structs.Evaluation{ 1129 ID: structs.GenerateUUID(), 1130 Priority: 50, 1131 TriggeredBy: structs.EvalTriggerJobRegister, 1132 JobID: job.ID, 1133 } 1134 1135 // Process the evaluation 1136 err := h.Process(NewServiceScheduler, eval) 1137 if err != nil { 1138 t.Fatalf("err: %v", err) 1139 } 1140 1141 // Ensure a single plan 1142 if len(h.Plans) != 1 { 1143 t.Fatalf("bad: %#v", h.Plans) 1144 } 1145 plan := h.Plans[0] 1146 1147 // Ensure the plan evicted all allocs 1148 var update []*structs.Allocation 1149 for _, updateList := range plan.NodeUpdate { 1150 update = append(update, updateList...) 1151 } 1152 if len(update) != len(allocs) { 1153 t.Fatalf("bad: %#v", plan) 1154 } 1155 1156 // Ensure the plan allocated 1157 var planned []*structs.Allocation 1158 for _, allocList := range plan.NodeAllocation { 1159 planned = append(planned, allocList...) 1160 } 1161 if len(planned) != 10 { 1162 t.Fatalf("bad: %#v", plan) 1163 } 1164 1165 // Lookup the allocations by JobID 1166 ws := memdb.NewWatchSet() 1167 out, err := h.State.AllocsByJob(ws, job.ID, false) 1168 noErr(t, err) 1169 1170 // Ensure all allocations placed 1171 out, _ = structs.FilterTerminalAllocs(out) 1172 if len(out) != 10 { 1173 t.Fatalf("bad: %#v", out) 1174 } 1175 1176 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1177 } 1178 1179 // Have a single node and submit a job. Increment the count such that all fit 1180 // on the node but the node doesn't have enough resources to fit the new count + 1181 // 1. This tests that we properly discount the resources of existing allocs. 1182 func TestServiceSched_JobModify_IncrCount_NodeLimit(t *testing.T) { 1183 h := NewHarness(t) 1184 1185 // Create one node 1186 node := mock.Node() 1187 node.Resources.CPU = 1000 1188 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1189 1190 // Generate a fake job with one allocation 1191 job := mock.Job() 1192 job.TaskGroups[0].Tasks[0].Resources.CPU = 256 1193 job2 := job.Copy() 1194 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1195 1196 var allocs []*structs.Allocation 1197 alloc := mock.Alloc() 1198 alloc.Job = job 1199 alloc.JobID = job.ID 1200 alloc.NodeID = node.ID 1201 alloc.Name = "my-job.web[0]" 1202 alloc.Resources.CPU = 256 1203 allocs = append(allocs, alloc) 1204 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1205 1206 // Update the job to count 3 1207 job2.TaskGroups[0].Count = 3 1208 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 1209 1210 // Create a mock evaluation to deal with drain 1211 eval := &structs.Evaluation{ 1212 ID: structs.GenerateUUID(), 1213 Priority: 50, 1214 TriggeredBy: structs.EvalTriggerJobRegister, 1215 JobID: job.ID, 1216 } 1217 1218 // Process the evaluation 1219 err := h.Process(NewServiceScheduler, eval) 1220 if err != nil { 1221 t.Fatalf("err: %v", err) 1222 } 1223 1224 // Ensure a single plan 1225 if len(h.Plans) != 1 { 1226 t.Fatalf("bad: %#v", h.Plans) 1227 } 1228 plan := h.Plans[0] 1229 1230 // Ensure the plan didn't evicted the alloc 1231 var update []*structs.Allocation 1232 for _, updateList := range plan.NodeUpdate { 1233 update = append(update, updateList...) 1234 } 1235 if len(update) != 0 { 1236 t.Fatalf("bad: %#v", plan) 1237 } 1238 1239 // Ensure the plan allocated 1240 var planned []*structs.Allocation 1241 for _, allocList := range plan.NodeAllocation { 1242 planned = append(planned, allocList...) 1243 } 1244 if len(planned) != 3 { 1245 t.Fatalf("bad: %#v", plan) 1246 } 1247 1248 // Ensure the plan had no failures 1249 if len(h.Evals) != 1 { 1250 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 1251 } 1252 outEval := h.Evals[0] 1253 if outEval == nil || len(outEval.FailedTGAllocs) != 0 { 1254 t.Fatalf("bad: %#v", outEval) 1255 } 1256 1257 // Lookup the allocations by JobID 1258 ws := memdb.NewWatchSet() 1259 out, err := h.State.AllocsByJob(ws, job.ID, false) 1260 noErr(t, err) 1261 1262 // Ensure all allocations placed 1263 out, _ = structs.FilterTerminalAllocs(out) 1264 if len(out) != 3 { 1265 t.Fatalf("bad: %#v", out) 1266 } 1267 1268 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1269 } 1270 1271 func TestServiceSched_JobModify_CountZero(t *testing.T) { 1272 h := NewHarness(t) 1273 1274 // Create some nodes 1275 var nodes []*structs.Node 1276 for i := 0; i < 10; i++ { 1277 node := mock.Node() 1278 nodes = append(nodes, node) 1279 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1280 } 1281 1282 // Generate a fake job with allocations 1283 job := mock.Job() 1284 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1285 1286 var allocs []*structs.Allocation 1287 for i := 0; i < 10; i++ { 1288 alloc := mock.Alloc() 1289 alloc.Job = job 1290 alloc.JobID = job.ID 1291 alloc.NodeID = nodes[i].ID 1292 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1293 allocs = append(allocs, alloc) 1294 } 1295 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1296 1297 // Add a few terminal status allocations, these should be ignored 1298 var terminal []*structs.Allocation 1299 for i := 0; i < 5; i++ { 1300 alloc := mock.Alloc() 1301 alloc.Job = job 1302 alloc.JobID = job.ID 1303 alloc.NodeID = nodes[i].ID 1304 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1305 alloc.DesiredStatus = structs.AllocDesiredStatusStop 1306 terminal = append(terminal, alloc) 1307 } 1308 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 1309 1310 // Update the job to be count zero 1311 job2 := mock.Job() 1312 job2.ID = job.ID 1313 job2.TaskGroups[0].Count = 0 1314 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 1315 1316 // Create a mock evaluation to deal with drain 1317 eval := &structs.Evaluation{ 1318 ID: structs.GenerateUUID(), 1319 Priority: 50, 1320 TriggeredBy: structs.EvalTriggerJobRegister, 1321 JobID: job.ID, 1322 } 1323 1324 // Process the evaluation 1325 err := h.Process(NewServiceScheduler, eval) 1326 if err != nil { 1327 t.Fatalf("err: %v", err) 1328 } 1329 1330 // Ensure a single plan 1331 if len(h.Plans) != 1 { 1332 t.Fatalf("bad: %#v", h.Plans) 1333 } 1334 plan := h.Plans[0] 1335 1336 // Ensure the plan evicted all allocs 1337 var update []*structs.Allocation 1338 for _, updateList := range plan.NodeUpdate { 1339 update = append(update, updateList...) 1340 } 1341 if len(update) != len(allocs) { 1342 t.Fatalf("bad: %#v", plan) 1343 } 1344 1345 // Ensure the plan didn't allocated 1346 var planned []*structs.Allocation 1347 for _, allocList := range plan.NodeAllocation { 1348 planned = append(planned, allocList...) 1349 } 1350 if len(planned) != 0 { 1351 t.Fatalf("bad: %#v", plan) 1352 } 1353 1354 // Lookup the allocations by JobID 1355 ws := memdb.NewWatchSet() 1356 out, err := h.State.AllocsByJob(ws, job.ID, false) 1357 noErr(t, err) 1358 1359 // Ensure all allocations placed 1360 out, _ = structs.FilterTerminalAllocs(out) 1361 if len(out) != 0 { 1362 t.Fatalf("bad: %#v", out) 1363 } 1364 1365 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1366 } 1367 1368 func TestServiceSched_JobModify_Rolling(t *testing.T) { 1369 h := NewHarness(t) 1370 1371 // Create some nodes 1372 var nodes []*structs.Node 1373 for i := 0; i < 10; i++ { 1374 node := mock.Node() 1375 nodes = append(nodes, node) 1376 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1377 } 1378 1379 // Generate a fake job with allocations 1380 job := mock.Job() 1381 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1382 1383 var allocs []*structs.Allocation 1384 for i := 0; i < 10; i++ { 1385 alloc := mock.Alloc() 1386 alloc.Job = job 1387 alloc.JobID = job.ID 1388 alloc.NodeID = nodes[i].ID 1389 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1390 allocs = append(allocs, alloc) 1391 } 1392 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1393 1394 // Update the job 1395 job2 := mock.Job() 1396 job2.ID = job.ID 1397 job2.Update = structs.UpdateStrategy{ 1398 Stagger: 30 * time.Second, 1399 MaxParallel: 5, 1400 } 1401 1402 // Update the task, such that it cannot be done in-place 1403 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 1404 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 1405 1406 // Create a mock evaluation to deal with drain 1407 eval := &structs.Evaluation{ 1408 ID: structs.GenerateUUID(), 1409 Priority: 50, 1410 TriggeredBy: structs.EvalTriggerJobRegister, 1411 JobID: job.ID, 1412 } 1413 1414 // Process the evaluation 1415 err := h.Process(NewServiceScheduler, eval) 1416 if err != nil { 1417 t.Fatalf("err: %v", err) 1418 } 1419 1420 // Ensure a single plan 1421 if len(h.Plans) != 1 { 1422 t.Fatalf("bad: %#v", h.Plans) 1423 } 1424 plan := h.Plans[0] 1425 1426 // Ensure the plan evicted only MaxParallel 1427 var update []*structs.Allocation 1428 for _, updateList := range plan.NodeUpdate { 1429 update = append(update, updateList...) 1430 } 1431 if len(update) != job2.Update.MaxParallel { 1432 t.Fatalf("bad: %#v", plan) 1433 } 1434 1435 // Ensure the plan allocated 1436 var planned []*structs.Allocation 1437 for _, allocList := range plan.NodeAllocation { 1438 planned = append(planned, allocList...) 1439 } 1440 if len(planned) != job2.Update.MaxParallel { 1441 t.Fatalf("bad: %#v", plan) 1442 } 1443 1444 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1445 1446 // Ensure a follow up eval was created 1447 eval = h.Evals[0] 1448 if eval.NextEval == "" { 1449 t.Fatalf("missing next eval") 1450 } 1451 1452 // Check for create 1453 if len(h.CreateEvals) == 0 { 1454 t.Fatalf("missing created eval") 1455 } 1456 create := h.CreateEvals[0] 1457 if eval.NextEval != create.ID { 1458 t.Fatalf("ID mismatch") 1459 } 1460 if create.PreviousEval != eval.ID { 1461 t.Fatalf("missing previous eval") 1462 } 1463 1464 if create.TriggeredBy != structs.EvalTriggerRollingUpdate { 1465 t.Fatalf("bad: %#v", create) 1466 } 1467 } 1468 1469 func TestServiceSched_JobModify_InPlace(t *testing.T) { 1470 h := NewHarness(t) 1471 1472 // Create some nodes 1473 var nodes []*structs.Node 1474 for i := 0; i < 10; i++ { 1475 node := mock.Node() 1476 nodes = append(nodes, node) 1477 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1478 } 1479 1480 // Generate a fake job with allocations 1481 job := mock.Job() 1482 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1483 1484 var allocs []*structs.Allocation 1485 for i := 0; i < 10; i++ { 1486 alloc := mock.Alloc() 1487 alloc.Job = job 1488 alloc.JobID = job.ID 1489 alloc.NodeID = nodes[i].ID 1490 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1491 allocs = append(allocs, alloc) 1492 } 1493 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1494 1495 // Update the job 1496 job2 := mock.Job() 1497 job2.ID = job.ID 1498 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 1499 1500 // Create a mock evaluation to deal with drain 1501 eval := &structs.Evaluation{ 1502 ID: structs.GenerateUUID(), 1503 Priority: 50, 1504 TriggeredBy: structs.EvalTriggerJobRegister, 1505 JobID: job.ID, 1506 } 1507 1508 // Process the evaluation 1509 err := h.Process(NewServiceScheduler, eval) 1510 if err != nil { 1511 t.Fatalf("err: %v", err) 1512 } 1513 1514 // Ensure a single plan 1515 if len(h.Plans) != 1 { 1516 t.Fatalf("bad: %#v", h.Plans) 1517 } 1518 plan := h.Plans[0] 1519 1520 // Ensure the plan did not evict any allocs 1521 var update []*structs.Allocation 1522 for _, updateList := range plan.NodeUpdate { 1523 update = append(update, updateList...) 1524 } 1525 if len(update) != 0 { 1526 t.Fatalf("bad: %#v", plan) 1527 } 1528 1529 // Ensure the plan updated the existing allocs 1530 var planned []*structs.Allocation 1531 for _, allocList := range plan.NodeAllocation { 1532 planned = append(planned, allocList...) 1533 } 1534 if len(planned) != 10 { 1535 t.Fatalf("bad: %#v", plan) 1536 } 1537 for _, p := range planned { 1538 if p.Job != job2 { 1539 t.Fatalf("should update job") 1540 } 1541 } 1542 1543 // Lookup the allocations by JobID 1544 ws := memdb.NewWatchSet() 1545 out, err := h.State.AllocsByJob(ws, job.ID, false) 1546 noErr(t, err) 1547 1548 // Ensure all allocations placed 1549 if len(out) != 10 { 1550 for _, alloc := range out { 1551 t.Logf("%#v", alloc) 1552 } 1553 t.Fatalf("bad: %#v", out) 1554 } 1555 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1556 1557 // Verify the network did not change 1558 rp := structs.Port{Label: "main", Value: 5000} 1559 for _, alloc := range out { 1560 for _, resources := range alloc.TaskResources { 1561 if resources.Networks[0].ReservedPorts[0] != rp { 1562 t.Fatalf("bad: %#v", alloc) 1563 } 1564 } 1565 } 1566 } 1567 1568 func TestServiceSched_JobModify_DistinctProperty(t *testing.T) { 1569 h := NewHarness(t) 1570 1571 // Create some nodes 1572 var nodes []*structs.Node 1573 for i := 0; i < 10; i++ { 1574 node := mock.Node() 1575 node.Meta["rack"] = fmt.Sprintf("rack%d", i) 1576 nodes = append(nodes, node) 1577 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1578 } 1579 1580 // Create a job that uses distinct property and has count higher than what is 1581 // possible. 1582 job := mock.Job() 1583 job.TaskGroups[0].Count = 11 1584 job.Constraints = append(job.Constraints, 1585 &structs.Constraint{ 1586 Operand: structs.ConstraintDistinctProperty, 1587 LTarget: "${meta.rack}", 1588 }) 1589 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1590 1591 oldJob := job.Copy() 1592 oldJob.JobModifyIndex -= 1 1593 oldJob.TaskGroups[0].Count = 4 1594 1595 // Place 4 of 10 1596 var allocs []*structs.Allocation 1597 for i := 0; i < 4; i++ { 1598 alloc := mock.Alloc() 1599 alloc.Job = oldJob 1600 alloc.JobID = job.ID 1601 alloc.NodeID = nodes[i].ID 1602 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1603 allocs = append(allocs, alloc) 1604 } 1605 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1606 1607 // Create a mock evaluation to register the job 1608 eval := &structs.Evaluation{ 1609 ID: structs.GenerateUUID(), 1610 Priority: job.Priority, 1611 TriggeredBy: structs.EvalTriggerJobRegister, 1612 JobID: job.ID, 1613 } 1614 1615 // Process the evaluation 1616 err := h.Process(NewServiceScheduler, eval) 1617 if err != nil { 1618 t.Fatalf("err: %v", err) 1619 } 1620 1621 // Ensure a single plan 1622 if len(h.Plans) != 1 { 1623 t.Fatalf("bad: %#v", h.Plans) 1624 } 1625 plan := h.Plans[0] 1626 1627 // Ensure the plan doesn't have annotations. 1628 if plan.Annotations != nil { 1629 t.Fatalf("expected no annotations") 1630 } 1631 1632 // Ensure the eval hasn't spawned blocked eval 1633 if len(h.CreateEvals) != 1 { 1634 t.Fatalf("bad: %#v", h.CreateEvals) 1635 } 1636 1637 // Ensure the plan failed to alloc 1638 outEval := h.Evals[0] 1639 if len(outEval.FailedTGAllocs) != 1 { 1640 t.Fatalf("bad: %+v", outEval) 1641 } 1642 1643 // Ensure the plan allocated 1644 var planned []*structs.Allocation 1645 for _, allocList := range plan.NodeAllocation { 1646 planned = append(planned, allocList...) 1647 } 1648 if len(planned) != 10 { 1649 t.Fatalf("bad: %#v", planned) 1650 } 1651 1652 // Lookup the allocations by JobID 1653 ws := memdb.NewWatchSet() 1654 out, err := h.State.AllocsByJob(ws, job.ID, false) 1655 noErr(t, err) 1656 1657 // Ensure all allocations placed 1658 if len(out) != 10 { 1659 t.Fatalf("bad: %#v", out) 1660 } 1661 1662 // Ensure different node was used per. 1663 used := make(map[string]struct{}) 1664 for _, alloc := range out { 1665 if _, ok := used[alloc.NodeID]; ok { 1666 t.Fatalf("Node collision %v", alloc.NodeID) 1667 } 1668 used[alloc.NodeID] = struct{}{} 1669 } 1670 1671 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1672 } 1673 1674 func TestServiceSched_JobDeregister(t *testing.T) { 1675 h := NewHarness(t) 1676 1677 // Generate a fake job with allocations 1678 job := mock.Job() 1679 1680 var allocs []*structs.Allocation 1681 for i := 0; i < 10; i++ { 1682 alloc := mock.Alloc() 1683 alloc.Job = job 1684 alloc.JobID = job.ID 1685 allocs = append(allocs, alloc) 1686 } 1687 for _, alloc := range allocs { 1688 h.State.UpsertJobSummary(h.NextIndex(), mock.JobSummary(alloc.JobID)) 1689 } 1690 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1691 1692 // Create a mock evaluation to deregister the job 1693 eval := &structs.Evaluation{ 1694 ID: structs.GenerateUUID(), 1695 Priority: 50, 1696 TriggeredBy: structs.EvalTriggerJobDeregister, 1697 JobID: job.ID, 1698 } 1699 1700 // Process the evaluation 1701 err := h.Process(NewServiceScheduler, eval) 1702 if err != nil { 1703 t.Fatalf("err: %v", err) 1704 } 1705 1706 // Ensure a single plan 1707 if len(h.Plans) != 1 { 1708 t.Fatalf("bad: %#v", h.Plans) 1709 } 1710 plan := h.Plans[0] 1711 1712 // Ensure the plan evicted all nodes 1713 if len(plan.NodeUpdate["12345678-abcd-efab-cdef-123456789abc"]) != len(allocs) { 1714 t.Fatalf("bad: %#v", plan) 1715 } 1716 1717 // Lookup the allocations by JobID 1718 ws := memdb.NewWatchSet() 1719 out, err := h.State.AllocsByJob(ws, job.ID, false) 1720 noErr(t, err) 1721 1722 // Ensure that the job field on the allocation is still populated 1723 for _, alloc := range out { 1724 if alloc.Job == nil { 1725 t.Fatalf("bad: %#v", alloc) 1726 } 1727 } 1728 1729 // Ensure no remaining allocations 1730 out, _ = structs.FilterTerminalAllocs(out) 1731 if len(out) != 0 { 1732 t.Fatalf("bad: %#v", out) 1733 } 1734 1735 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1736 } 1737 1738 func TestServiceSched_NodeDown(t *testing.T) { 1739 h := NewHarness(t) 1740 1741 // Register a node 1742 node := mock.Node() 1743 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1744 1745 // Generate a fake job with allocations and an update policy. 1746 job := mock.Job() 1747 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1748 1749 var allocs []*structs.Allocation 1750 for i := 0; i < 10; i++ { 1751 alloc := mock.Alloc() 1752 alloc.Job = job 1753 alloc.JobID = job.ID 1754 alloc.NodeID = node.ID 1755 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1756 allocs = append(allocs, alloc) 1757 } 1758 1759 // Cover each terminal case and ensure it doesn't change to lost 1760 allocs[7].DesiredStatus = structs.AllocDesiredStatusRun 1761 allocs[7].ClientStatus = structs.AllocClientStatusLost 1762 allocs[8].DesiredStatus = structs.AllocDesiredStatusRun 1763 allocs[8].ClientStatus = structs.AllocClientStatusFailed 1764 allocs[9].DesiredStatus = structs.AllocDesiredStatusRun 1765 allocs[9].ClientStatus = structs.AllocClientStatusComplete 1766 1767 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1768 1769 // Mark some allocs as running 1770 ws := memdb.NewWatchSet() 1771 for i := 0; i < 4; i++ { 1772 out, _ := h.State.AllocByID(ws, allocs[i].ID) 1773 out.ClientStatus = structs.AllocClientStatusRunning 1774 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), []*structs.Allocation{out})) 1775 } 1776 1777 // Mark the node as down 1778 noErr(t, h.State.UpdateNodeStatus(h.NextIndex(), node.ID, structs.NodeStatusDown)) 1779 1780 // Create a mock evaluation to deal with drain 1781 eval := &structs.Evaluation{ 1782 ID: structs.GenerateUUID(), 1783 Priority: 50, 1784 TriggeredBy: structs.EvalTriggerNodeUpdate, 1785 JobID: job.ID, 1786 NodeID: node.ID, 1787 } 1788 1789 // Process the evaluation 1790 err := h.Process(NewServiceScheduler, eval) 1791 if err != nil { 1792 t.Fatalf("err: %v", err) 1793 } 1794 1795 // Ensure a single plan 1796 if len(h.Plans) != 1 { 1797 t.Fatalf("bad: %#v", h.Plans) 1798 } 1799 plan := h.Plans[0] 1800 1801 // Test the scheduler marked all non-terminal allocations as lost 1802 if len(plan.NodeUpdate[node.ID]) != 7 { 1803 t.Fatalf("bad: %#v", plan) 1804 } 1805 1806 for _, out := range plan.NodeUpdate[node.ID] { 1807 if out.ClientStatus != structs.AllocClientStatusLost && out.DesiredStatus != structs.AllocDesiredStatusStop { 1808 t.Fatalf("bad alloc: %#v", out) 1809 } 1810 } 1811 1812 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1813 } 1814 1815 func TestServiceSched_NodeUpdate(t *testing.T) { 1816 h := NewHarness(t) 1817 1818 // Register a node 1819 node := mock.Node() 1820 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1821 1822 // Generate a fake job with allocations and an update policy. 1823 job := mock.Job() 1824 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1825 1826 var allocs []*structs.Allocation 1827 for i := 0; i < 10; i++ { 1828 alloc := mock.Alloc() 1829 alloc.Job = job 1830 alloc.JobID = job.ID 1831 alloc.NodeID = node.ID 1832 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1833 allocs = append(allocs, alloc) 1834 } 1835 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1836 1837 // Mark some allocs as running 1838 ws := memdb.NewWatchSet() 1839 for i := 0; i < 4; i++ { 1840 out, _ := h.State.AllocByID(ws, allocs[i].ID) 1841 out.ClientStatus = structs.AllocClientStatusRunning 1842 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), []*structs.Allocation{out})) 1843 } 1844 1845 // Create a mock evaluation which won't trigger any new placements 1846 eval := &structs.Evaluation{ 1847 ID: structs.GenerateUUID(), 1848 Priority: 50, 1849 TriggeredBy: structs.EvalTriggerNodeUpdate, 1850 JobID: job.ID, 1851 NodeID: node.ID, 1852 } 1853 1854 // Process the evaluation 1855 err := h.Process(NewServiceScheduler, eval) 1856 if err != nil { 1857 t.Fatalf("err: %v", err) 1858 } 1859 if val, ok := h.Evals[0].QueuedAllocations["web"]; !ok || val != 0 { 1860 t.Fatalf("bad queued allocations: %v", h.Evals[0].QueuedAllocations) 1861 } 1862 1863 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1864 } 1865 1866 func TestServiceSched_NodeDrain(t *testing.T) { 1867 h := NewHarness(t) 1868 1869 // Register a draining node 1870 node := mock.Node() 1871 node.Drain = true 1872 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1873 1874 // Create some nodes 1875 for i := 0; i < 10; i++ { 1876 node := mock.Node() 1877 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1878 } 1879 1880 // Generate a fake job with allocations and an update policy. 1881 job := mock.Job() 1882 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1883 1884 var allocs []*structs.Allocation 1885 for i := 0; i < 10; i++ { 1886 alloc := mock.Alloc() 1887 alloc.Job = job 1888 alloc.JobID = job.ID 1889 alloc.NodeID = node.ID 1890 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1891 allocs = append(allocs, alloc) 1892 } 1893 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1894 1895 // Create a mock evaluation to deal with drain 1896 eval := &structs.Evaluation{ 1897 ID: structs.GenerateUUID(), 1898 Priority: 50, 1899 TriggeredBy: structs.EvalTriggerNodeUpdate, 1900 JobID: job.ID, 1901 NodeID: node.ID, 1902 } 1903 1904 // Process the evaluation 1905 err := h.Process(NewServiceScheduler, eval) 1906 if err != nil { 1907 t.Fatalf("err: %v", err) 1908 } 1909 1910 // Ensure a single plan 1911 if len(h.Plans) != 1 { 1912 t.Fatalf("bad: %#v", h.Plans) 1913 } 1914 plan := h.Plans[0] 1915 1916 // Ensure the plan evicted all allocs 1917 if len(plan.NodeUpdate[node.ID]) != len(allocs) { 1918 t.Fatalf("bad: %#v", plan) 1919 } 1920 1921 // Ensure the plan allocated 1922 var planned []*structs.Allocation 1923 for _, allocList := range plan.NodeAllocation { 1924 planned = append(planned, allocList...) 1925 } 1926 if len(planned) != 10 { 1927 t.Fatalf("bad: %#v", plan) 1928 } 1929 1930 // Lookup the allocations by JobID 1931 ws := memdb.NewWatchSet() 1932 out, err := h.State.AllocsByJob(ws, job.ID, false) 1933 noErr(t, err) 1934 1935 // Ensure all allocations placed 1936 out, _ = structs.FilterTerminalAllocs(out) 1937 if len(out) != 10 { 1938 t.Fatalf("bad: %#v", out) 1939 } 1940 1941 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1942 } 1943 1944 func TestServiceSched_NodeDrain_Down(t *testing.T) { 1945 h := NewHarness(t) 1946 1947 // Register a draining node 1948 node := mock.Node() 1949 node.Drain = true 1950 node.Status = structs.NodeStatusDown 1951 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1952 1953 // Generate a fake job with allocations 1954 job := mock.Job() 1955 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1956 1957 var allocs []*structs.Allocation 1958 for i := 0; i < 10; i++ { 1959 alloc := mock.Alloc() 1960 alloc.Job = job 1961 alloc.JobID = job.ID 1962 alloc.NodeID = node.ID 1963 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1964 allocs = append(allocs, alloc) 1965 } 1966 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1967 1968 // Set the desired state of the allocs to stop 1969 var stop []*structs.Allocation 1970 for i := 0; i < 10; i++ { 1971 newAlloc := allocs[i].Copy() 1972 newAlloc.ClientStatus = structs.AllocDesiredStatusStop 1973 stop = append(stop, newAlloc) 1974 } 1975 noErr(t, h.State.UpsertAllocs(h.NextIndex(), stop)) 1976 1977 // Mark some of the allocations as running 1978 var running []*structs.Allocation 1979 for i := 4; i < 6; i++ { 1980 newAlloc := stop[i].Copy() 1981 newAlloc.ClientStatus = structs.AllocClientStatusRunning 1982 running = append(running, newAlloc) 1983 } 1984 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), running)) 1985 1986 // Mark some of the allocations as complete 1987 var complete []*structs.Allocation 1988 for i := 6; i < 10; i++ { 1989 newAlloc := stop[i].Copy() 1990 newAlloc.ClientStatus = structs.AllocClientStatusComplete 1991 complete = append(complete, newAlloc) 1992 } 1993 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), complete)) 1994 1995 // Create a mock evaluation to deal with the node update 1996 eval := &structs.Evaluation{ 1997 ID: structs.GenerateUUID(), 1998 Priority: 50, 1999 TriggeredBy: structs.EvalTriggerNodeUpdate, 2000 JobID: job.ID, 2001 NodeID: node.ID, 2002 } 2003 2004 // Process the evaluation 2005 err := h.Process(NewServiceScheduler, eval) 2006 if err != nil { 2007 t.Fatalf("err: %v", err) 2008 } 2009 2010 // Ensure a single plan 2011 if len(h.Plans) != 1 { 2012 t.Fatalf("bad: %#v", h.Plans) 2013 } 2014 plan := h.Plans[0] 2015 2016 // Ensure the plan evicted non terminal allocs 2017 if len(plan.NodeUpdate[node.ID]) != 6 { 2018 t.Fatalf("bad: %#v", plan) 2019 } 2020 2021 // Ensure that all the allocations which were in running or pending state 2022 // has been marked as lost 2023 var lostAllocs []string 2024 for _, alloc := range plan.NodeUpdate[node.ID] { 2025 lostAllocs = append(lostAllocs, alloc.ID) 2026 } 2027 sort.Strings(lostAllocs) 2028 2029 var expectedLostAllocs []string 2030 for i := 0; i < 6; i++ { 2031 expectedLostAllocs = append(expectedLostAllocs, allocs[i].ID) 2032 } 2033 sort.Strings(expectedLostAllocs) 2034 2035 if !reflect.DeepEqual(expectedLostAllocs, lostAllocs) { 2036 t.Fatalf("expected: %v, actual: %v", expectedLostAllocs, lostAllocs) 2037 } 2038 2039 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2040 } 2041 2042 func TestServiceSched_NodeDrain_Queued_Allocations(t *testing.T) { 2043 h := NewHarness(t) 2044 2045 // Register a draining node 2046 node := mock.Node() 2047 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2048 2049 // Generate a fake job with allocations and an update policy. 2050 job := mock.Job() 2051 job.TaskGroups[0].Count = 2 2052 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2053 2054 var allocs []*structs.Allocation 2055 for i := 0; i < 2; i++ { 2056 alloc := mock.Alloc() 2057 alloc.Job = job 2058 alloc.JobID = job.ID 2059 alloc.NodeID = node.ID 2060 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 2061 allocs = append(allocs, alloc) 2062 } 2063 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 2064 2065 node.Drain = true 2066 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2067 2068 // Create a mock evaluation to deal with drain 2069 eval := &structs.Evaluation{ 2070 ID: structs.GenerateUUID(), 2071 Priority: 50, 2072 TriggeredBy: structs.EvalTriggerNodeUpdate, 2073 JobID: job.ID, 2074 NodeID: node.ID, 2075 } 2076 2077 // Process the evaluation 2078 err := h.Process(NewServiceScheduler, eval) 2079 if err != nil { 2080 t.Fatalf("err: %v", err) 2081 } 2082 2083 queued := h.Evals[0].QueuedAllocations["web"] 2084 if queued != 2 { 2085 t.Fatalf("expected: %v, actual: %v", 2, queued) 2086 } 2087 } 2088 2089 func TestServiceSched_NodeDrain_UpdateStrategy(t *testing.T) { 2090 h := NewHarness(t) 2091 2092 // Register a draining node 2093 node := mock.Node() 2094 node.Drain = true 2095 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2096 2097 // Create some nodes 2098 for i := 0; i < 10; i++ { 2099 node := mock.Node() 2100 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2101 } 2102 2103 // Generate a fake job with allocations and an update policy. 2104 job := mock.Job() 2105 mp := 5 2106 job.Update = structs.UpdateStrategy{ 2107 Stagger: time.Second, 2108 MaxParallel: mp, 2109 } 2110 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2111 2112 var allocs []*structs.Allocation 2113 for i := 0; i < 10; i++ { 2114 alloc := mock.Alloc() 2115 alloc.Job = job 2116 alloc.JobID = job.ID 2117 alloc.NodeID = node.ID 2118 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 2119 allocs = append(allocs, alloc) 2120 } 2121 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 2122 2123 // Create a mock evaluation to deal with drain 2124 eval := &structs.Evaluation{ 2125 ID: structs.GenerateUUID(), 2126 Priority: 50, 2127 TriggeredBy: structs.EvalTriggerNodeUpdate, 2128 JobID: job.ID, 2129 NodeID: node.ID, 2130 } 2131 2132 // Process the evaluation 2133 err := h.Process(NewServiceScheduler, eval) 2134 if err != nil { 2135 t.Fatalf("err: %v", err) 2136 } 2137 2138 // Ensure a single plan 2139 if len(h.Plans) != 1 { 2140 t.Fatalf("bad: %#v", h.Plans) 2141 } 2142 plan := h.Plans[0] 2143 2144 // Ensure the plan evicted all allocs 2145 if len(plan.NodeUpdate[node.ID]) != mp { 2146 t.Fatalf("bad: %#v", plan) 2147 } 2148 2149 // Ensure the plan allocated 2150 var planned []*structs.Allocation 2151 for _, allocList := range plan.NodeAllocation { 2152 planned = append(planned, allocList...) 2153 } 2154 if len(planned) != mp { 2155 t.Fatalf("bad: %#v", plan) 2156 } 2157 2158 // Ensure there is a followup eval. 2159 if len(h.CreateEvals) != 1 || 2160 h.CreateEvals[0].TriggeredBy != structs.EvalTriggerRollingUpdate { 2161 t.Fatalf("bad: %#v", h.CreateEvals) 2162 } 2163 2164 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2165 } 2166 2167 func TestServiceSched_RetryLimit(t *testing.T) { 2168 h := NewHarness(t) 2169 h.Planner = &RejectPlan{h} 2170 2171 // Create some nodes 2172 for i := 0; i < 10; i++ { 2173 node := mock.Node() 2174 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2175 } 2176 2177 // Create a job 2178 job := mock.Job() 2179 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2180 2181 // Create a mock evaluation to register the job 2182 eval := &structs.Evaluation{ 2183 ID: structs.GenerateUUID(), 2184 Priority: job.Priority, 2185 TriggeredBy: structs.EvalTriggerJobRegister, 2186 JobID: job.ID, 2187 } 2188 2189 // Process the evaluation 2190 err := h.Process(NewServiceScheduler, eval) 2191 if err != nil { 2192 t.Fatalf("err: %v", err) 2193 } 2194 2195 // Ensure multiple plans 2196 if len(h.Plans) == 0 { 2197 t.Fatalf("bad: %#v", h.Plans) 2198 } 2199 2200 // Lookup the allocations by JobID 2201 ws := memdb.NewWatchSet() 2202 out, err := h.State.AllocsByJob(ws, job.ID, false) 2203 noErr(t, err) 2204 2205 // Ensure no allocations placed 2206 if len(out) != 0 { 2207 t.Fatalf("bad: %#v", out) 2208 } 2209 2210 // Should hit the retry limit 2211 h.AssertEvalStatus(t, structs.EvalStatusFailed) 2212 } 2213 2214 func TestBatchSched_Run_CompleteAlloc(t *testing.T) { 2215 h := NewHarness(t) 2216 2217 // Create a node 2218 node := mock.Node() 2219 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2220 2221 // Create a job 2222 job := mock.Job() 2223 job.TaskGroups[0].Count = 1 2224 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2225 2226 // Create a complete alloc 2227 alloc := mock.Alloc() 2228 alloc.Job = job 2229 alloc.JobID = job.ID 2230 alloc.NodeID = node.ID 2231 alloc.Name = "my-job.web[0]" 2232 alloc.ClientStatus = structs.AllocClientStatusComplete 2233 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2234 2235 // Create a mock evaluation to register the job 2236 eval := &structs.Evaluation{ 2237 ID: structs.GenerateUUID(), 2238 Priority: job.Priority, 2239 TriggeredBy: structs.EvalTriggerJobRegister, 2240 JobID: job.ID, 2241 } 2242 2243 // Process the evaluation 2244 err := h.Process(NewBatchScheduler, eval) 2245 if err != nil { 2246 t.Fatalf("err: %v", err) 2247 } 2248 2249 // Ensure no plan as it should be a no-op 2250 if len(h.Plans) != 0 { 2251 t.Fatalf("bad: %#v", h.Plans) 2252 } 2253 2254 // Lookup the allocations by JobID 2255 ws := memdb.NewWatchSet() 2256 out, err := h.State.AllocsByJob(ws, job.ID, false) 2257 noErr(t, err) 2258 2259 // Ensure no allocations placed 2260 if len(out) != 1 { 2261 t.Fatalf("bad: %#v", out) 2262 } 2263 2264 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2265 } 2266 2267 func TestBatchSched_Run_DrainedAlloc(t *testing.T) { 2268 h := NewHarness(t) 2269 2270 // Create a node 2271 node := mock.Node() 2272 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2273 2274 // Create a job 2275 job := mock.Job() 2276 job.TaskGroups[0].Count = 1 2277 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2278 2279 // Create a complete alloc 2280 alloc := mock.Alloc() 2281 alloc.Job = job 2282 alloc.JobID = job.ID 2283 alloc.NodeID = node.ID 2284 alloc.Name = "my-job.web[0]" 2285 alloc.DesiredStatus = structs.AllocDesiredStatusStop 2286 alloc.ClientStatus = structs.AllocClientStatusComplete 2287 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2288 2289 // Create a mock evaluation to register the job 2290 eval := &structs.Evaluation{ 2291 ID: structs.GenerateUUID(), 2292 Priority: job.Priority, 2293 TriggeredBy: structs.EvalTriggerJobRegister, 2294 JobID: job.ID, 2295 } 2296 2297 // Process the evaluation 2298 err := h.Process(NewBatchScheduler, eval) 2299 if err != nil { 2300 t.Fatalf("err: %v", err) 2301 } 2302 2303 // Ensure a plan 2304 if len(h.Plans) != 1 { 2305 t.Fatalf("bad: %#v", h.Plans) 2306 } 2307 2308 // Lookup the allocations by JobID 2309 ws := memdb.NewWatchSet() 2310 out, err := h.State.AllocsByJob(ws, job.ID, false) 2311 noErr(t, err) 2312 2313 // Ensure a replacement alloc was placed. 2314 if len(out) != 2 { 2315 t.Fatalf("bad: %#v", out) 2316 } 2317 2318 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2319 } 2320 2321 func TestBatchSched_Run_FailedAlloc(t *testing.T) { 2322 h := NewHarness(t) 2323 2324 // Create a node 2325 node := mock.Node() 2326 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2327 2328 // Create a job 2329 job := mock.Job() 2330 job.TaskGroups[0].Count = 1 2331 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2332 2333 // Create a failed alloc 2334 alloc := mock.Alloc() 2335 alloc.Job = job 2336 alloc.JobID = job.ID 2337 alloc.NodeID = node.ID 2338 alloc.Name = "my-job.web[0]" 2339 alloc.ClientStatus = structs.AllocClientStatusFailed 2340 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2341 2342 // Create a mock evaluation to register the job 2343 eval := &structs.Evaluation{ 2344 ID: structs.GenerateUUID(), 2345 Priority: job.Priority, 2346 TriggeredBy: structs.EvalTriggerJobRegister, 2347 JobID: job.ID, 2348 } 2349 2350 // Process the evaluation 2351 err := h.Process(NewBatchScheduler, eval) 2352 if err != nil { 2353 t.Fatalf("err: %v", err) 2354 } 2355 2356 // Ensure a plan 2357 if len(h.Plans) != 1 { 2358 t.Fatalf("bad: %#v", h.Plans) 2359 } 2360 2361 // Lookup the allocations by JobID 2362 ws := memdb.NewWatchSet() 2363 out, err := h.State.AllocsByJob(ws, job.ID, false) 2364 noErr(t, err) 2365 2366 // Ensure a replacement alloc was placed. 2367 if len(out) != 2 { 2368 t.Fatalf("bad: %#v", out) 2369 } 2370 2371 // Ensure that the scheduler is recording the correct number of queued 2372 // allocations 2373 queued := h.Evals[0].QueuedAllocations["web"] 2374 if queued != 0 { 2375 t.Fatalf("expected: %v, actual: %v", 1, queued) 2376 } 2377 2378 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2379 } 2380 2381 func TestBatchSched_Run_FailedAllocQueuedAllocations(t *testing.T) { 2382 h := NewHarness(t) 2383 2384 node := mock.Node() 2385 node.Drain = true 2386 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2387 2388 // Create a job 2389 job := mock.Job() 2390 job.TaskGroups[0].Count = 1 2391 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2392 2393 // Create a failed alloc 2394 alloc := mock.Alloc() 2395 alloc.Job = job 2396 alloc.JobID = job.ID 2397 alloc.NodeID = node.ID 2398 alloc.Name = "my-job.web[0]" 2399 alloc.ClientStatus = structs.AllocClientStatusFailed 2400 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2401 2402 // Create a mock evaluation to register the job 2403 eval := &structs.Evaluation{ 2404 ID: structs.GenerateUUID(), 2405 Priority: job.Priority, 2406 TriggeredBy: structs.EvalTriggerJobRegister, 2407 JobID: job.ID, 2408 } 2409 2410 // Process the evaluation 2411 err := h.Process(NewBatchScheduler, eval) 2412 if err != nil { 2413 t.Fatalf("err: %v", err) 2414 } 2415 2416 // Ensure that the scheduler is recording the correct number of queued 2417 // allocations 2418 queued := h.Evals[0].QueuedAllocations["web"] 2419 if queued != 1 { 2420 t.Fatalf("expected: %v, actual: %v", 1, queued) 2421 } 2422 } 2423 2424 func TestBatchSched_ReRun_SuccessfullyFinishedAlloc(t *testing.T) { 2425 h := NewHarness(t) 2426 2427 // Create two nodes, one that is drained and has a successfully finished 2428 // alloc and a fresh undrained one 2429 node := mock.Node() 2430 node.Drain = true 2431 node2 := mock.Node() 2432 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2433 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 2434 2435 // Create a job 2436 job := mock.Job() 2437 job.Type = structs.JobTypeBatch 2438 job.TaskGroups[0].Count = 1 2439 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2440 2441 // Create a successful alloc 2442 alloc := mock.Alloc() 2443 alloc.Job = job 2444 alloc.JobID = job.ID 2445 alloc.NodeID = node.ID 2446 alloc.Name = "my-job.web[0]" 2447 alloc.ClientStatus = structs.AllocClientStatusComplete 2448 alloc.TaskStates = map[string]*structs.TaskState{ 2449 "web": &structs.TaskState{ 2450 State: structs.TaskStateDead, 2451 Events: []*structs.TaskEvent{ 2452 { 2453 Type: structs.TaskTerminated, 2454 ExitCode: 0, 2455 }, 2456 }, 2457 }, 2458 } 2459 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2460 2461 // Create a mock evaluation to rerun the job 2462 eval := &structs.Evaluation{ 2463 ID: structs.GenerateUUID(), 2464 Priority: job.Priority, 2465 TriggeredBy: structs.EvalTriggerJobRegister, 2466 JobID: job.ID, 2467 } 2468 2469 // Process the evaluation 2470 err := h.Process(NewBatchScheduler, eval) 2471 if err != nil { 2472 t.Fatalf("err: %v", err) 2473 } 2474 2475 // Ensure no plan 2476 if len(h.Plans) != 0 { 2477 t.Fatalf("bad: %#v", h.Plans) 2478 } 2479 2480 // Lookup the allocations by JobID 2481 ws := memdb.NewWatchSet() 2482 out, err := h.State.AllocsByJob(ws, job.ID, false) 2483 noErr(t, err) 2484 2485 // Ensure no replacement alloc was placed. 2486 if len(out) != 1 { 2487 t.Fatalf("bad: %#v", out) 2488 } 2489 2490 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2491 } 2492 2493 // This test checks that terminal allocations that receive an in-place updated 2494 // are not added to the plan 2495 func TestBatchSched_JobModify_InPlace_Terminal(t *testing.T) { 2496 h := NewHarness(t) 2497 2498 // Create some nodes 2499 var nodes []*structs.Node 2500 for i := 0; i < 10; i++ { 2501 node := mock.Node() 2502 nodes = append(nodes, node) 2503 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2504 } 2505 2506 // Generate a fake job with allocations 2507 job := mock.Job() 2508 job.Type = structs.JobTypeBatch 2509 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2510 2511 var allocs []*structs.Allocation 2512 for i := 0; i < 10; i++ { 2513 alloc := mock.Alloc() 2514 alloc.Job = job 2515 alloc.JobID = job.ID 2516 alloc.NodeID = nodes[i].ID 2517 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 2518 alloc.ClientStatus = structs.AllocClientStatusComplete 2519 allocs = append(allocs, alloc) 2520 } 2521 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 2522 2523 // Update the job 2524 job2 := mock.Job() 2525 job2.ID = job.ID 2526 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 2527 2528 // Create a mock evaluation to deal with drain 2529 eval := &structs.Evaluation{ 2530 ID: structs.GenerateUUID(), 2531 Priority: 50, 2532 TriggeredBy: structs.EvalTriggerJobRegister, 2533 JobID: job.ID, 2534 } 2535 2536 // Process the evaluation 2537 err := h.Process(NewBatchScheduler, eval) 2538 if err != nil { 2539 t.Fatalf("err: %v", err) 2540 } 2541 2542 // Ensure no plan 2543 if len(h.Plans) != 0 { 2544 t.Fatalf("bad: %#v", h.Plans) 2545 } 2546 } 2547 2548 func TestGenericSched_FilterCompleteAllocs(t *testing.T) { 2549 running := mock.Alloc() 2550 desiredStop := mock.Alloc() 2551 desiredStop.DesiredStatus = structs.AllocDesiredStatusStop 2552 2553 new := mock.Alloc() 2554 new.CreateIndex = 10000 2555 2556 oldSuccessful := mock.Alloc() 2557 oldSuccessful.CreateIndex = 30 2558 oldSuccessful.DesiredStatus = structs.AllocDesiredStatusStop 2559 oldSuccessful.ClientStatus = structs.AllocClientStatusComplete 2560 oldSuccessful.TaskStates = make(map[string]*structs.TaskState, 1) 2561 oldSuccessful.TaskStates["foo"] = &structs.TaskState{ 2562 State: structs.TaskStateDead, 2563 Events: []*structs.TaskEvent{{Type: structs.TaskTerminated, ExitCode: 0}}, 2564 } 2565 2566 unsuccessful := mock.Alloc() 2567 unsuccessful.DesiredStatus = structs.AllocDesiredStatusRun 2568 unsuccessful.ClientStatus = structs.AllocClientStatusFailed 2569 unsuccessful.TaskStates = make(map[string]*structs.TaskState, 1) 2570 unsuccessful.TaskStates["foo"] = &structs.TaskState{ 2571 State: structs.TaskStateDead, 2572 Events: []*structs.TaskEvent{{Type: structs.TaskTerminated, ExitCode: 1}}, 2573 } 2574 2575 cases := []struct { 2576 Batch bool 2577 Input, Output []*structs.Allocation 2578 TerminalAllocs map[string]*structs.Allocation 2579 }{ 2580 { 2581 Input: []*structs.Allocation{running}, 2582 Output: []*structs.Allocation{running}, 2583 TerminalAllocs: map[string]*structs.Allocation{}, 2584 }, 2585 { 2586 Input: []*structs.Allocation{running, desiredStop}, 2587 Output: []*structs.Allocation{running}, 2588 TerminalAllocs: map[string]*structs.Allocation{ 2589 desiredStop.Name: desiredStop, 2590 }, 2591 }, 2592 { 2593 Batch: true, 2594 Input: []*structs.Allocation{running}, 2595 Output: []*structs.Allocation{running}, 2596 TerminalAllocs: map[string]*structs.Allocation{}, 2597 }, 2598 { 2599 Batch: true, 2600 Input: []*structs.Allocation{new, oldSuccessful}, 2601 Output: []*structs.Allocation{new}, 2602 TerminalAllocs: map[string]*structs.Allocation{}, 2603 }, 2604 { 2605 Batch: true, 2606 Input: []*structs.Allocation{unsuccessful}, 2607 Output: []*structs.Allocation{}, 2608 TerminalAllocs: map[string]*structs.Allocation{ 2609 unsuccessful.Name: unsuccessful, 2610 }, 2611 }, 2612 } 2613 2614 for i, c := range cases { 2615 g := &GenericScheduler{batch: c.Batch} 2616 out, terminalAllocs := g.filterCompleteAllocs(c.Input) 2617 2618 if !reflect.DeepEqual(out, c.Output) { 2619 t.Log("Got:") 2620 for i, a := range out { 2621 t.Logf("%d: %#v", i, a) 2622 } 2623 t.Log("Want:") 2624 for i, a := range c.Output { 2625 t.Logf("%d: %#v", i, a) 2626 } 2627 t.Fatalf("Case %d failed", i+1) 2628 } 2629 2630 if !reflect.DeepEqual(terminalAllocs, c.TerminalAllocs) { 2631 t.Log("Got:") 2632 for n, a := range terminalAllocs { 2633 t.Logf("%v: %#v", n, a) 2634 } 2635 t.Log("Want:") 2636 for n, a := range c.TerminalAllocs { 2637 t.Logf("%v: %#v", n, a) 2638 } 2639 t.Fatalf("Case %d failed", i+1) 2640 } 2641 2642 } 2643 } 2644 2645 func TestGenericSched_ChainedAlloc(t *testing.T) { 2646 h := NewHarness(t) 2647 2648 // Create some nodes 2649 for i := 0; i < 10; i++ { 2650 node := mock.Node() 2651 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2652 } 2653 2654 // Create a job 2655 job := mock.Job() 2656 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 2657 2658 // Create a mock evaluation to register the job 2659 eval := &structs.Evaluation{ 2660 ID: structs.GenerateUUID(), 2661 Priority: job.Priority, 2662 TriggeredBy: structs.EvalTriggerJobRegister, 2663 JobID: job.ID, 2664 } 2665 // Process the evaluation 2666 if err := h.Process(NewServiceScheduler, eval); err != nil { 2667 t.Fatalf("err: %v", err) 2668 } 2669 2670 var allocIDs []string 2671 for _, allocList := range h.Plans[0].NodeAllocation { 2672 for _, alloc := range allocList { 2673 allocIDs = append(allocIDs, alloc.ID) 2674 } 2675 } 2676 sort.Strings(allocIDs) 2677 2678 // Create a new harness to invoke the scheduler again 2679 h1 := NewHarnessWithState(t, h.State) 2680 job1 := mock.Job() 2681 job1.ID = job.ID 2682 job1.TaskGroups[0].Tasks[0].Env["foo"] = "bar" 2683 job1.TaskGroups[0].Count = 12 2684 noErr(t, h1.State.UpsertJob(h1.NextIndex(), job1)) 2685 2686 // Create a mock evaluation to update the job 2687 eval1 := &structs.Evaluation{ 2688 ID: structs.GenerateUUID(), 2689 Priority: job1.Priority, 2690 TriggeredBy: structs.EvalTriggerJobRegister, 2691 JobID: job1.ID, 2692 } 2693 // Process the evaluation 2694 if err := h1.Process(NewServiceScheduler, eval1); err != nil { 2695 t.Fatalf("err: %v", err) 2696 } 2697 2698 plan := h1.Plans[0] 2699 2700 // Collect all the chained allocation ids and the new allocations which 2701 // don't have any chained allocations 2702 var prevAllocs []string 2703 var newAllocs []string 2704 for _, allocList := range plan.NodeAllocation { 2705 for _, alloc := range allocList { 2706 if alloc.PreviousAllocation == "" { 2707 newAllocs = append(newAllocs, alloc.ID) 2708 continue 2709 } 2710 prevAllocs = append(prevAllocs, alloc.PreviousAllocation) 2711 } 2712 } 2713 sort.Strings(prevAllocs) 2714 2715 // Ensure that the new allocations has their corresponging original 2716 // allocation ids 2717 if !reflect.DeepEqual(prevAllocs, allocIDs) { 2718 t.Fatalf("expected: %v, actual: %v", len(allocIDs), len(prevAllocs)) 2719 } 2720 2721 // Ensuring two new allocations don't have any chained allocations 2722 if len(newAllocs) != 2 { 2723 t.Fatalf("expected: %v, actual: %v", 2, len(newAllocs)) 2724 } 2725 } 2726 2727 func TestServiceSched_NodeDrain_Sticky(t *testing.T) { 2728 h := NewHarness(t) 2729 2730 // Register a draining node 2731 node := mock.Node() 2732 node.Drain = true 2733 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 2734 2735 // Create an alloc on the draining node 2736 alloc := mock.Alloc() 2737 alloc.Name = "my-job.web[0]" 2738 alloc.DesiredStatus = structs.AllocDesiredStatusStop 2739 alloc.NodeID = node.ID 2740 alloc.Job.TaskGroups[0].Count = 1 2741 alloc.Job.TaskGroups[0].EphemeralDisk.Sticky = true 2742 noErr(t, h.State.UpsertJob(h.NextIndex(), alloc.Job)) 2743 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 2744 2745 // Create a mock evaluation to deal with drain 2746 eval := &structs.Evaluation{ 2747 ID: structs.GenerateUUID(), 2748 Priority: 50, 2749 TriggeredBy: structs.EvalTriggerNodeUpdate, 2750 JobID: alloc.Job.ID, 2751 NodeID: node.ID, 2752 } 2753 2754 // Process the evaluation 2755 err := h.Process(NewServiceScheduler, eval) 2756 if err != nil { 2757 t.Fatalf("err: %v", err) 2758 } 2759 2760 // Ensure a single plan 2761 if len(h.Plans) != 1 { 2762 t.Fatalf("bad: %#v", h.Plans) 2763 } 2764 plan := h.Plans[0] 2765 2766 // Ensure the plan evicted all allocs 2767 if len(plan.NodeUpdate[node.ID]) != 1 { 2768 t.Fatalf("bad: %#v", plan) 2769 } 2770 2771 // Ensure the plan didn't create any new allocations 2772 var planned []*structs.Allocation 2773 for _, allocList := range plan.NodeAllocation { 2774 planned = append(planned, allocList...) 2775 } 2776 if len(planned) != 0 { 2777 t.Fatalf("bad: %#v", plan) 2778 } 2779 2780 h.AssertEvalStatus(t, structs.EvalStatusComplete) 2781 }