github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/scheduler/generic_sched_test.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "reflect" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/nomad/mock" 10 "github.com/hashicorp/nomad/nomad/structs" 11 ) 12 13 func TestServiceSched_JobRegister(t *testing.T) { 14 h := NewHarness(t) 15 16 // Create some nodes 17 for i := 0; i < 10; i++ { 18 node := mock.Node() 19 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 20 } 21 22 // Create a job 23 job := mock.Job() 24 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 25 26 // Create a mock evaluation to register the job 27 eval := &structs.Evaluation{ 28 ID: structs.GenerateUUID(), 29 Priority: job.Priority, 30 TriggeredBy: structs.EvalTriggerJobRegister, 31 JobID: job.ID, 32 } 33 34 // Process the evaluation 35 err := h.Process(NewServiceScheduler, eval) 36 if err != nil { 37 t.Fatalf("err: %v", err) 38 } 39 40 // Ensure a single plan 41 if len(h.Plans) != 1 { 42 t.Fatalf("bad: %#v", h.Plans) 43 } 44 plan := h.Plans[0] 45 46 // Ensure the plan doesn't have annotations. 47 if plan.Annotations != nil { 48 t.Fatalf("expected no annotations") 49 } 50 51 // Ensure the eval has no spawned blocked eval 52 if len(h.Evals) != 1 { 53 t.Fatalf("bad: %#v", h.Evals) 54 if h.Evals[0].BlockedEval != "" { 55 t.Fatalf("bad: %#v", h.Evals[0]) 56 } 57 } 58 59 // Ensure the plan allocated 60 var planned []*structs.Allocation 61 for _, allocList := range plan.NodeAllocation { 62 planned = append(planned, allocList...) 63 } 64 if len(planned) != 10 { 65 t.Fatalf("bad: %#v", plan) 66 } 67 68 // Lookup the allocations by JobID 69 out, err := h.State.AllocsByJob(job.ID) 70 noErr(t, err) 71 72 // Ensure all allocations placed 73 if len(out) != 10 { 74 t.Fatalf("bad: %#v", out) 75 } 76 77 // Ensure different ports were used. 78 used := make(map[int]struct{}) 79 for _, alloc := range out { 80 for _, resource := range alloc.TaskResources { 81 for _, port := range resource.Networks[0].DynamicPorts { 82 if _, ok := used[port.Value]; ok { 83 t.Fatalf("Port collision %v", port.Value) 84 } 85 used[port.Value] = struct{}{} 86 } 87 } 88 } 89 90 h.AssertEvalStatus(t, structs.EvalStatusComplete) 91 } 92 93 func TestServiceSched_JobRegister_Annotate(t *testing.T) { 94 h := NewHarness(t) 95 96 // Create some nodes 97 for i := 0; i < 10; i++ { 98 node := mock.Node() 99 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 100 } 101 102 // Create a job 103 job := mock.Job() 104 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 105 106 // Create a mock evaluation to register the job 107 eval := &structs.Evaluation{ 108 ID: structs.GenerateUUID(), 109 Priority: job.Priority, 110 TriggeredBy: structs.EvalTriggerJobRegister, 111 JobID: job.ID, 112 AnnotatePlan: true, 113 } 114 115 // Process the evaluation 116 err := h.Process(NewServiceScheduler, eval) 117 if err != nil { 118 t.Fatalf("err: %v", err) 119 } 120 121 // Ensure a single plan 122 if len(h.Plans) != 1 { 123 t.Fatalf("bad: %#v", h.Plans) 124 } 125 plan := h.Plans[0] 126 127 // Ensure the plan allocated 128 var planned []*structs.Allocation 129 for _, allocList := range plan.NodeAllocation { 130 planned = append(planned, allocList...) 131 } 132 if len(planned) != 10 { 133 t.Fatalf("bad: %#v", plan) 134 } 135 136 // Lookup the allocations by JobID 137 out, err := h.State.AllocsByJob(job.ID) 138 noErr(t, err) 139 140 // Ensure all allocations placed 141 if len(out) != 10 { 142 t.Fatalf("bad: %#v", out) 143 } 144 145 h.AssertEvalStatus(t, structs.EvalStatusComplete) 146 147 // Ensure the plan had annotations. 148 if plan.Annotations == nil { 149 t.Fatalf("expected annotations") 150 } 151 152 desiredTGs := plan.Annotations.DesiredTGUpdates 153 if l := len(desiredTGs); l != 1 { 154 t.Fatalf("incorrect number of task groups; got %v; want %v", l, 1) 155 } 156 157 desiredChanges, ok := desiredTGs["web"] 158 if !ok { 159 t.Fatalf("expected task group web to have desired changes") 160 } 161 162 expected := &structs.DesiredUpdates{Place: 10} 163 if !reflect.DeepEqual(desiredChanges, expected) { 164 t.Fatalf("Unexpected desired updates; got %#v; want %#v", desiredChanges, expected) 165 } 166 } 167 168 func TestServiceSched_JobRegister_CountZero(t *testing.T) { 169 h := NewHarness(t) 170 171 // Create some nodes 172 for i := 0; i < 10; i++ { 173 node := mock.Node() 174 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 175 } 176 177 // Create a job and set the task group count to zero. 178 job := mock.Job() 179 job.TaskGroups[0].Count = 0 180 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 181 182 // Create a mock evaluation to register the job 183 eval := &structs.Evaluation{ 184 ID: structs.GenerateUUID(), 185 Priority: job.Priority, 186 TriggeredBy: structs.EvalTriggerJobRegister, 187 JobID: job.ID, 188 } 189 190 // Process the evaluation 191 err := h.Process(NewServiceScheduler, eval) 192 if err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 196 // Ensure there was no plan 197 if len(h.Plans) != 0 { 198 t.Fatalf("bad: %#v", h.Plans) 199 } 200 201 // Lookup the allocations by JobID 202 out, err := h.State.AllocsByJob(job.ID) 203 noErr(t, err) 204 205 // Ensure no allocations placed 206 if len(out) != 0 { 207 t.Fatalf("bad: %#v", out) 208 } 209 210 h.AssertEvalStatus(t, structs.EvalStatusComplete) 211 } 212 213 func TestServiceSched_JobRegister_AllocFail(t *testing.T) { 214 h := NewHarness(t) 215 216 // Create NO nodes 217 // Create a job 218 job := mock.Job() 219 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 220 221 // Create a mock evaluation to register the job 222 eval := &structs.Evaluation{ 223 ID: structs.GenerateUUID(), 224 Priority: job.Priority, 225 TriggeredBy: structs.EvalTriggerJobRegister, 226 JobID: job.ID, 227 } 228 229 // Process the evaluation 230 err := h.Process(NewServiceScheduler, eval) 231 if err != nil { 232 t.Fatalf("err: %v", err) 233 } 234 235 // Ensure no plan 236 if len(h.Plans) != 0 { 237 t.Fatalf("bad: %#v", h.Plans) 238 } 239 240 // Ensure there is a follow up eval. 241 if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked { 242 t.Fatalf("bad: %#v", h.CreateEvals) 243 } 244 245 if len(h.Evals) != 1 { 246 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 247 } 248 outEval := h.Evals[0] 249 250 // Ensure the eval has its spawned blocked eval 251 if outEval.BlockedEval != h.CreateEvals[0].ID { 252 t.Fatalf("bad: %#v", outEval) 253 } 254 255 // Ensure the plan failed to alloc 256 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 257 t.Fatalf("bad: %#v", outEval) 258 } 259 260 metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name] 261 if !ok { 262 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 263 } 264 265 // Check the coalesced failures 266 if metrics.CoalescedFailures != 9 { 267 t.Fatalf("bad: %#v", metrics) 268 } 269 270 // Check the available nodes 271 if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 0 { 272 t.Fatalf("bad: %#v", metrics) 273 } 274 275 h.AssertEvalStatus(t, structs.EvalStatusComplete) 276 } 277 278 func TestServiceSched_JobRegister_CreateBlockedEval(t *testing.T) { 279 h := NewHarness(t) 280 281 // Create a full node 282 node := mock.Node() 283 node.Reserved = node.Resources 284 node.ComputeClass() 285 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 286 287 // Create an ineligible node 288 node2 := mock.Node() 289 node2.Attributes["kernel.name"] = "windows" 290 node2.ComputeClass() 291 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 292 293 // Create a jobs 294 job := mock.Job() 295 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 296 297 // Create a mock evaluation to register the job 298 eval := &structs.Evaluation{ 299 ID: structs.GenerateUUID(), 300 Priority: job.Priority, 301 TriggeredBy: structs.EvalTriggerJobRegister, 302 JobID: job.ID, 303 } 304 305 // Process the evaluation 306 err := h.Process(NewServiceScheduler, eval) 307 if err != nil { 308 t.Fatalf("err: %v", err) 309 } 310 311 // Ensure no plan 312 if len(h.Plans) != 0 { 313 t.Fatalf("bad: %#v", h.Plans) 314 } 315 316 // Ensure the plan has created a follow up eval. 317 if len(h.CreateEvals) != 1 { 318 t.Fatalf("bad: %#v", h.CreateEvals) 319 } 320 321 created := h.CreateEvals[0] 322 if created.Status != structs.EvalStatusBlocked { 323 t.Fatalf("bad: %#v", created) 324 } 325 326 classes := created.ClassEligibility 327 if len(classes) != 2 || !classes[node.ComputedClass] || classes[node2.ComputedClass] { 328 t.Fatalf("bad: %#v", classes) 329 } 330 331 if created.EscapedComputedClass { 332 t.Fatalf("bad: %#v", created) 333 } 334 335 // Ensure there is a follow up eval. 336 if len(h.CreateEvals) != 1 || h.CreateEvals[0].Status != structs.EvalStatusBlocked { 337 t.Fatalf("bad: %#v", h.CreateEvals) 338 } 339 340 if len(h.Evals) != 1 { 341 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 342 } 343 outEval := h.Evals[0] 344 345 // Ensure the plan failed to alloc 346 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 347 t.Fatalf("bad: %#v", outEval) 348 } 349 350 metrics, ok := outEval.FailedTGAllocs[job.TaskGroups[0].Name] 351 if !ok { 352 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 353 } 354 355 // Check the coalesced failures 356 if metrics.CoalescedFailures != 9 { 357 t.Fatalf("bad: %#v", metrics) 358 } 359 360 // Check the available nodes 361 if count, ok := metrics.NodesAvailable["dc1"]; !ok || count != 2 { 362 t.Fatalf("bad: %#v", metrics) 363 } 364 365 h.AssertEvalStatus(t, structs.EvalStatusComplete) 366 } 367 368 func TestServiceSched_JobRegister_FeasibleAndInfeasibleTG(t *testing.T) { 369 h := NewHarness(t) 370 371 // Create one node 372 node := mock.Node() 373 node.NodeClass = "class_0" 374 noErr(t, node.ComputeClass()) 375 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 376 377 // Create a job that constrains on a node class 378 job := mock.Job() 379 job.TaskGroups[0].Count = 2 380 job.TaskGroups[0].Constraints = append(job.Constraints, 381 &structs.Constraint{ 382 LTarget: "${node.class}", 383 RTarget: "class_0", 384 Operand: "=", 385 }, 386 ) 387 tg2 := job.TaskGroups[0].Copy() 388 tg2.Name = "web2" 389 tg2.Constraints[1].RTarget = "class_1" 390 job.TaskGroups = append(job.TaskGroups, tg2) 391 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 392 393 // Create a mock evaluation to register the job 394 eval := &structs.Evaluation{ 395 ID: structs.GenerateUUID(), 396 Priority: job.Priority, 397 TriggeredBy: structs.EvalTriggerJobRegister, 398 JobID: job.ID, 399 } 400 401 // Process the evaluation 402 err := h.Process(NewServiceScheduler, eval) 403 if err != nil { 404 t.Fatalf("err: %v", err) 405 } 406 407 // Ensure a single plan 408 if len(h.Plans) != 1 { 409 t.Fatalf("bad: %#v", h.Plans) 410 } 411 plan := h.Plans[0] 412 413 // Ensure the plan allocated 414 var planned []*structs.Allocation 415 for _, allocList := range plan.NodeAllocation { 416 planned = append(planned, allocList...) 417 } 418 if len(planned) != 2 { 419 t.Fatalf("bad: %#v", plan) 420 } 421 422 // Ensure two allocations placed 423 out, err := h.State.AllocsByJob(job.ID) 424 noErr(t, err) 425 if len(out) != 2 { 426 t.Fatalf("bad: %#v", out) 427 } 428 429 if len(h.Evals) != 1 { 430 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 431 } 432 outEval := h.Evals[0] 433 434 // Ensure the eval has its spawned blocked eval 435 if outEval.BlockedEval != h.CreateEvals[0].ID { 436 t.Fatalf("bad: %#v", outEval) 437 } 438 439 // Ensure the plan failed to alloc one tg 440 if outEval == nil || len(outEval.FailedTGAllocs) != 1 { 441 t.Fatalf("bad: %#v", outEval) 442 } 443 444 metrics, ok := outEval.FailedTGAllocs[tg2.Name] 445 if !ok { 446 t.Fatalf("no failed metrics: %#v", outEval.FailedTGAllocs) 447 } 448 449 // Check the coalesced failures 450 if metrics.CoalescedFailures != tg2.Count-1 { 451 t.Fatalf("bad: %#v", metrics) 452 } 453 454 h.AssertEvalStatus(t, structs.EvalStatusComplete) 455 } 456 457 func TestServiceSched_EvaluateBlockedEval(t *testing.T) { 458 h := NewHarness(t) 459 460 // Create a job and set the task group count to zero. 461 job := mock.Job() 462 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 463 464 // Create a mock blocked evaluation 465 eval := &structs.Evaluation{ 466 ID: structs.GenerateUUID(), 467 Status: structs.EvalStatusBlocked, 468 Priority: job.Priority, 469 TriggeredBy: structs.EvalTriggerJobRegister, 470 JobID: job.ID, 471 } 472 473 // Insert it into the state store 474 noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval})) 475 476 // Process the evaluation 477 err := h.Process(NewServiceScheduler, eval) 478 if err != nil { 479 t.Fatalf("err: %v", err) 480 } 481 482 // Ensure there was no plan 483 if len(h.Plans) != 0 { 484 t.Fatalf("bad: %#v", h.Plans) 485 } 486 487 // Ensure that the eval was reblocked 488 if len(h.ReblockEvals) != 1 { 489 t.Fatalf("bad: %#v", h.ReblockEvals) 490 } 491 if h.ReblockEvals[0].ID != eval.ID { 492 t.Fatalf("expect same eval to be reblocked; got %q; want %q", h.ReblockEvals[0].ID, eval.ID) 493 } 494 495 // Ensure the eval status was not updated 496 if len(h.Evals) != 0 { 497 t.Fatalf("Existing eval should not have status set") 498 } 499 } 500 501 func TestServiceSched_EvaluateBlockedEval_Finished(t *testing.T) { 502 h := NewHarness(t) 503 504 // Create some nodes 505 for i := 0; i < 10; i++ { 506 node := mock.Node() 507 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 508 } 509 510 // Create a job and set the task group count to zero. 511 job := mock.Job() 512 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 513 514 // Create a mock blocked evaluation 515 eval := &structs.Evaluation{ 516 ID: structs.GenerateUUID(), 517 Status: structs.EvalStatusBlocked, 518 Priority: job.Priority, 519 TriggeredBy: structs.EvalTriggerJobRegister, 520 JobID: job.ID, 521 } 522 523 // Insert it into the state store 524 noErr(t, h.State.UpsertEvals(h.NextIndex(), []*structs.Evaluation{eval})) 525 526 // Process the evaluation 527 err := h.Process(NewServiceScheduler, eval) 528 if err != nil { 529 t.Fatalf("err: %v", err) 530 } 531 532 // Ensure a single plan 533 if len(h.Plans) != 1 { 534 t.Fatalf("bad: %#v", h.Plans) 535 } 536 plan := h.Plans[0] 537 538 // Ensure the plan doesn't have annotations. 539 if plan.Annotations != nil { 540 t.Fatalf("expected no annotations") 541 } 542 543 // Ensure the eval has no spawned blocked eval 544 if len(h.Evals) != 1 { 545 t.Fatalf("bad: %#v", h.Evals) 546 if h.Evals[0].BlockedEval != "" { 547 t.Fatalf("bad: %#v", h.Evals[0]) 548 } 549 } 550 551 // Ensure the plan allocated 552 var planned []*structs.Allocation 553 for _, allocList := range plan.NodeAllocation { 554 planned = append(planned, allocList...) 555 } 556 if len(planned) != 10 { 557 t.Fatalf("bad: %#v", plan) 558 } 559 560 // Lookup the allocations by JobID 561 out, err := h.State.AllocsByJob(job.ID) 562 noErr(t, err) 563 564 // Ensure all allocations placed 565 if len(out) != 10 { 566 t.Fatalf("bad: %#v", out) 567 } 568 569 // Ensure the eval was not reblocked 570 if len(h.ReblockEvals) != 0 { 571 t.Fatalf("Existing eval should not have been reblocked as it placed all allocations") 572 } 573 574 h.AssertEvalStatus(t, structs.EvalStatusComplete) 575 } 576 577 func TestServiceSched_JobModify(t *testing.T) { 578 h := NewHarness(t) 579 580 // Create some nodes 581 var nodes []*structs.Node 582 for i := 0; i < 10; i++ { 583 node := mock.Node() 584 nodes = append(nodes, node) 585 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 586 } 587 588 // Generate a fake job with allocations 589 job := mock.Job() 590 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 591 592 var allocs []*structs.Allocation 593 for i := 0; i < 10; i++ { 594 alloc := mock.Alloc() 595 alloc.Job = job 596 alloc.JobID = job.ID 597 alloc.NodeID = nodes[i].ID 598 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 599 allocs = append(allocs, alloc) 600 } 601 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 602 603 // Add a few terminal status allocations, these should be ignored 604 var terminal []*structs.Allocation 605 for i := 0; i < 5; i++ { 606 alloc := mock.Alloc() 607 alloc.Job = job 608 alloc.JobID = job.ID 609 alloc.NodeID = nodes[i].ID 610 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 611 alloc.DesiredStatus = structs.AllocDesiredStatusFailed 612 terminal = append(terminal, alloc) 613 } 614 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 615 616 // Update the job 617 job2 := mock.Job() 618 job2.ID = job.ID 619 620 // Update the task, such that it cannot be done in-place 621 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 622 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 623 624 // Create a mock evaluation to deal with drain 625 eval := &structs.Evaluation{ 626 ID: structs.GenerateUUID(), 627 Priority: 50, 628 TriggeredBy: structs.EvalTriggerJobRegister, 629 JobID: job.ID, 630 } 631 632 // Process the evaluation 633 err := h.Process(NewServiceScheduler, eval) 634 if err != nil { 635 t.Fatalf("err: %v", err) 636 } 637 638 // Ensure a single plan 639 if len(h.Plans) != 1 { 640 t.Fatalf("bad: %#v", h.Plans) 641 } 642 plan := h.Plans[0] 643 644 // Ensure the plan evicted all allocs 645 var update []*structs.Allocation 646 for _, updateList := range plan.NodeUpdate { 647 update = append(update, updateList...) 648 } 649 if len(update) != len(allocs) { 650 t.Fatalf("bad: %#v", plan) 651 } 652 653 // Ensure the plan allocated 654 var planned []*structs.Allocation 655 for _, allocList := range plan.NodeAllocation { 656 planned = append(planned, allocList...) 657 } 658 if len(planned) != 10 { 659 t.Fatalf("bad: %#v", plan) 660 } 661 662 // Lookup the allocations by JobID 663 out, err := h.State.AllocsByJob(job.ID) 664 noErr(t, err) 665 666 // Ensure all allocations placed 667 out = structs.FilterTerminalAllocs(out) 668 if len(out) != 10 { 669 t.Fatalf("bad: %#v", out) 670 } 671 672 h.AssertEvalStatus(t, structs.EvalStatusComplete) 673 } 674 675 // Have a single node and submit a job. Increment the count such that all fit 676 // on the node but the node doesn't have enough resources to fit the new count + 677 // 1. This tests that we properly discount the resources of existing allocs. 678 func TestServiceSched_JobModify_IncrCount_NodeLimit(t *testing.T) { 679 h := NewHarness(t) 680 681 // Create one node 682 node := mock.Node() 683 node.Resources.CPU = 1000 684 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 685 686 // Generate a fake job with one allocation 687 job := mock.Job() 688 job.TaskGroups[0].Tasks[0].Resources.CPU = 256 689 job2 := job.Copy() 690 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 691 692 var allocs []*structs.Allocation 693 alloc := mock.Alloc() 694 alloc.Job = job 695 alloc.JobID = job.ID 696 alloc.NodeID = node.ID 697 alloc.Name = "my-job.web[0]" 698 alloc.Resources.CPU = 256 699 allocs = append(allocs, alloc) 700 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 701 702 // Update the job to count 3 703 job2.TaskGroups[0].Count = 3 704 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 705 706 // Create a mock evaluation to deal with drain 707 eval := &structs.Evaluation{ 708 ID: structs.GenerateUUID(), 709 Priority: 50, 710 TriggeredBy: structs.EvalTriggerJobRegister, 711 JobID: job.ID, 712 } 713 714 // Process the evaluation 715 err := h.Process(NewServiceScheduler, eval) 716 if err != nil { 717 t.Fatalf("err: %v", err) 718 } 719 720 // Ensure a single plan 721 if len(h.Plans) != 1 { 722 t.Fatalf("bad: %#v", h.Plans) 723 } 724 plan := h.Plans[0] 725 726 // Ensure the plan didn't evicted the alloc 727 var update []*structs.Allocation 728 for _, updateList := range plan.NodeUpdate { 729 update = append(update, updateList...) 730 } 731 if len(update) != 0 { 732 t.Fatalf("bad: %#v", plan) 733 } 734 735 // Ensure the plan allocated 736 var planned []*structs.Allocation 737 for _, allocList := range plan.NodeAllocation { 738 planned = append(planned, allocList...) 739 } 740 if len(planned) != 3 { 741 t.Fatalf("bad: %#v", plan) 742 } 743 744 // Ensure the plan had no failures 745 if len(h.Evals) != 1 { 746 t.Fatalf("incorrect number of updated eval: %#v", h.Evals) 747 } 748 outEval := h.Evals[0] 749 if outEval == nil || len(outEval.FailedTGAllocs) != 0 { 750 t.Fatalf("bad: %#v", outEval) 751 } 752 753 // Lookup the allocations by JobID 754 out, err := h.State.AllocsByJob(job.ID) 755 noErr(t, err) 756 757 // Ensure all allocations placed 758 out = structs.FilterTerminalAllocs(out) 759 if len(out) != 3 { 760 t.Fatalf("bad: %#v", out) 761 } 762 763 h.AssertEvalStatus(t, structs.EvalStatusComplete) 764 } 765 766 func TestServiceSched_JobModify_CountZero(t *testing.T) { 767 h := NewHarness(t) 768 769 // Create some nodes 770 var nodes []*structs.Node 771 for i := 0; i < 10; i++ { 772 node := mock.Node() 773 nodes = append(nodes, node) 774 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 775 } 776 777 // Generate a fake job with allocations 778 job := mock.Job() 779 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 780 781 var allocs []*structs.Allocation 782 for i := 0; i < 10; i++ { 783 alloc := mock.Alloc() 784 alloc.Job = job 785 alloc.JobID = job.ID 786 alloc.NodeID = nodes[i].ID 787 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 788 allocs = append(allocs, alloc) 789 } 790 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 791 792 // Add a few terminal status allocations, these should be ignored 793 var terminal []*structs.Allocation 794 for i := 0; i < 5; i++ { 795 alloc := mock.Alloc() 796 alloc.Job = job 797 alloc.JobID = job.ID 798 alloc.NodeID = nodes[i].ID 799 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 800 alloc.DesiredStatus = structs.AllocDesiredStatusFailed 801 terminal = append(terminal, alloc) 802 } 803 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 804 805 // Update the job to be count zero 806 job2 := mock.Job() 807 job2.ID = job.ID 808 job2.TaskGroups[0].Count = 0 809 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 810 811 // Create a mock evaluation to deal with drain 812 eval := &structs.Evaluation{ 813 ID: structs.GenerateUUID(), 814 Priority: 50, 815 TriggeredBy: structs.EvalTriggerJobRegister, 816 JobID: job.ID, 817 } 818 819 // Process the evaluation 820 err := h.Process(NewServiceScheduler, eval) 821 if err != nil { 822 t.Fatalf("err: %v", err) 823 } 824 825 // Ensure a single plan 826 if len(h.Plans) != 1 { 827 t.Fatalf("bad: %#v", h.Plans) 828 } 829 plan := h.Plans[0] 830 831 // Ensure the plan evicted all allocs 832 var update []*structs.Allocation 833 for _, updateList := range plan.NodeUpdate { 834 update = append(update, updateList...) 835 } 836 if len(update) != len(allocs) { 837 t.Fatalf("bad: %#v", plan) 838 } 839 840 // Ensure the plan didn't allocated 841 var planned []*structs.Allocation 842 for _, allocList := range plan.NodeAllocation { 843 planned = append(planned, allocList...) 844 } 845 if len(planned) != 0 { 846 t.Fatalf("bad: %#v", plan) 847 } 848 849 // Lookup the allocations by JobID 850 out, err := h.State.AllocsByJob(job.ID) 851 noErr(t, err) 852 853 // Ensure all allocations placed 854 out = structs.FilterTerminalAllocs(out) 855 if len(out) != 0 { 856 t.Fatalf("bad: %#v", out) 857 } 858 859 h.AssertEvalStatus(t, structs.EvalStatusComplete) 860 } 861 862 func TestServiceSched_JobModify_Rolling(t *testing.T) { 863 h := NewHarness(t) 864 865 // Create some nodes 866 var nodes []*structs.Node 867 for i := 0; i < 10; i++ { 868 node := mock.Node() 869 nodes = append(nodes, node) 870 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 871 } 872 873 // Generate a fake job with allocations 874 job := mock.Job() 875 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 876 877 var allocs []*structs.Allocation 878 for i := 0; i < 10; i++ { 879 alloc := mock.Alloc() 880 alloc.Job = job 881 alloc.JobID = job.ID 882 alloc.NodeID = nodes[i].ID 883 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 884 allocs = append(allocs, alloc) 885 } 886 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 887 888 // Update the job 889 job2 := mock.Job() 890 job2.ID = job.ID 891 job2.Update = structs.UpdateStrategy{ 892 Stagger: 30 * time.Second, 893 MaxParallel: 5, 894 } 895 896 // Update the task, such that it cannot be done in-place 897 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 898 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 899 900 // Create a mock evaluation to deal with drain 901 eval := &structs.Evaluation{ 902 ID: structs.GenerateUUID(), 903 Priority: 50, 904 TriggeredBy: structs.EvalTriggerJobRegister, 905 JobID: job.ID, 906 } 907 908 // Process the evaluation 909 err := h.Process(NewServiceScheduler, eval) 910 if err != nil { 911 t.Fatalf("err: %v", err) 912 } 913 914 // Ensure a single plan 915 if len(h.Plans) != 1 { 916 t.Fatalf("bad: %#v", h.Plans) 917 } 918 plan := h.Plans[0] 919 920 // Ensure the plan evicted only MaxParallel 921 var update []*structs.Allocation 922 for _, updateList := range plan.NodeUpdate { 923 update = append(update, updateList...) 924 } 925 if len(update) != job2.Update.MaxParallel { 926 t.Fatalf("bad: %#v", plan) 927 } 928 929 // Ensure the plan allocated 930 var planned []*structs.Allocation 931 for _, allocList := range plan.NodeAllocation { 932 planned = append(planned, allocList...) 933 } 934 if len(planned) != job2.Update.MaxParallel { 935 t.Fatalf("bad: %#v", plan) 936 } 937 938 h.AssertEvalStatus(t, structs.EvalStatusComplete) 939 940 // Ensure a follow up eval was created 941 eval = h.Evals[0] 942 if eval.NextEval == "" { 943 t.Fatalf("missing next eval") 944 } 945 946 // Check for create 947 if len(h.CreateEvals) == 0 { 948 t.Fatalf("missing created eval") 949 } 950 create := h.CreateEvals[0] 951 if eval.NextEval != create.ID { 952 t.Fatalf("ID mismatch") 953 } 954 if create.PreviousEval != eval.ID { 955 t.Fatalf("missing previous eval") 956 } 957 958 if create.TriggeredBy != structs.EvalTriggerRollingUpdate { 959 t.Fatalf("bad: %#v", create) 960 } 961 } 962 963 func TestServiceSched_JobModify_InPlace(t *testing.T) { 964 h := NewHarness(t) 965 966 // Create some nodes 967 var nodes []*structs.Node 968 for i := 0; i < 10; i++ { 969 node := mock.Node() 970 nodes = append(nodes, node) 971 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 972 } 973 974 // Generate a fake job with allocations 975 job := mock.Job() 976 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 977 978 var allocs []*structs.Allocation 979 for i := 0; i < 10; i++ { 980 alloc := mock.Alloc() 981 alloc.Job = job 982 alloc.JobID = job.ID 983 alloc.NodeID = nodes[i].ID 984 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 985 allocs = append(allocs, alloc) 986 } 987 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 988 989 // Update the job 990 job2 := mock.Job() 991 job2.ID = job.ID 992 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 993 994 // Create a mock evaluation to deal with drain 995 eval := &structs.Evaluation{ 996 ID: structs.GenerateUUID(), 997 Priority: 50, 998 TriggeredBy: structs.EvalTriggerJobRegister, 999 JobID: job.ID, 1000 } 1001 1002 // Process the evaluation 1003 err := h.Process(NewServiceScheduler, eval) 1004 if err != nil { 1005 t.Fatalf("err: %v", err) 1006 } 1007 1008 // Ensure a single plan 1009 if len(h.Plans) != 1 { 1010 t.Fatalf("bad: %#v", h.Plans) 1011 } 1012 plan := h.Plans[0] 1013 1014 // Ensure the plan did not evict any allocs 1015 var update []*structs.Allocation 1016 for _, updateList := range plan.NodeUpdate { 1017 update = append(update, updateList...) 1018 } 1019 if len(update) != 0 { 1020 t.Fatalf("bad: %#v", plan) 1021 } 1022 1023 // Ensure the plan updated the existing allocs 1024 var planned []*structs.Allocation 1025 for _, allocList := range plan.NodeAllocation { 1026 planned = append(planned, allocList...) 1027 } 1028 if len(planned) != 10 { 1029 t.Fatalf("bad: %#v", plan) 1030 } 1031 for _, p := range planned { 1032 if p.Job != job2 { 1033 t.Fatalf("should update job") 1034 } 1035 } 1036 1037 // Lookup the allocations by JobID 1038 out, err := h.State.AllocsByJob(job.ID) 1039 noErr(t, err) 1040 1041 // Ensure all allocations placed 1042 if len(out) != 10 { 1043 for _, alloc := range out { 1044 t.Logf("%#v", alloc) 1045 } 1046 t.Fatalf("bad: %#v", out) 1047 } 1048 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1049 1050 // Verify the network did not change 1051 rp := structs.Port{Label: "main", Value: 5000} 1052 for _, alloc := range out { 1053 for _, resources := range alloc.TaskResources { 1054 if resources.Networks[0].ReservedPorts[0] != rp { 1055 t.Fatalf("bad: %#v", alloc) 1056 } 1057 } 1058 } 1059 } 1060 1061 func TestServiceSched_JobDeregister(t *testing.T) { 1062 h := NewHarness(t) 1063 1064 // Generate a fake job with allocations 1065 job := mock.Job() 1066 1067 var allocs []*structs.Allocation 1068 for i := 0; i < 10; i++ { 1069 alloc := mock.Alloc() 1070 alloc.Job = job 1071 alloc.JobID = job.ID 1072 allocs = append(allocs, alloc) 1073 } 1074 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1075 1076 // Create a mock evaluation to deregister the job 1077 eval := &structs.Evaluation{ 1078 ID: structs.GenerateUUID(), 1079 Priority: 50, 1080 TriggeredBy: structs.EvalTriggerJobDeregister, 1081 JobID: job.ID, 1082 } 1083 1084 // Process the evaluation 1085 err := h.Process(NewServiceScheduler, eval) 1086 if err != nil { 1087 t.Fatalf("err: %v", err) 1088 } 1089 1090 // Ensure a single plan 1091 if len(h.Plans) != 1 { 1092 t.Fatalf("bad: %#v", h.Plans) 1093 } 1094 plan := h.Plans[0] 1095 1096 // Ensure the plan evicted all nodes 1097 if len(plan.NodeUpdate["12345678-abcd-efab-cdef-123456789abc"]) != len(allocs) { 1098 t.Fatalf("bad: %#v", plan) 1099 } 1100 1101 // Lookup the allocations by JobID 1102 out, err := h.State.AllocsByJob(job.ID) 1103 noErr(t, err) 1104 1105 // Ensure that the job field on the allocation is still populated 1106 for _, alloc := range out { 1107 if alloc.Job == nil { 1108 t.Fatalf("bad: %#v", alloc) 1109 } 1110 } 1111 1112 // Ensure no remaining allocations 1113 out = structs.FilterTerminalAllocs(out) 1114 if len(out) != 0 { 1115 t.Fatalf("bad: %#v", out) 1116 } 1117 1118 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1119 } 1120 1121 func TestServiceSched_NodeDrain(t *testing.T) { 1122 h := NewHarness(t) 1123 1124 // Register a draining node 1125 node := mock.Node() 1126 node.Drain = true 1127 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1128 1129 // Create some nodes 1130 for i := 0; i < 10; i++ { 1131 node := mock.Node() 1132 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1133 } 1134 1135 // Generate a fake job with allocations and an update policy. 1136 job := mock.Job() 1137 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1138 1139 var allocs []*structs.Allocation 1140 for i := 0; i < 10; i++ { 1141 alloc := mock.Alloc() 1142 alloc.Job = job 1143 alloc.JobID = job.ID 1144 alloc.NodeID = node.ID 1145 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1146 allocs = append(allocs, alloc) 1147 } 1148 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1149 1150 // Create a mock evaluation to deal with drain 1151 eval := &structs.Evaluation{ 1152 ID: structs.GenerateUUID(), 1153 Priority: 50, 1154 TriggeredBy: structs.EvalTriggerNodeUpdate, 1155 JobID: job.ID, 1156 NodeID: node.ID, 1157 } 1158 1159 // Process the evaluation 1160 err := h.Process(NewServiceScheduler, eval) 1161 if err != nil { 1162 t.Fatalf("err: %v", err) 1163 } 1164 1165 // Ensure a single plan 1166 if len(h.Plans) != 1 { 1167 t.Fatalf("bad: %#v", h.Plans) 1168 } 1169 plan := h.Plans[0] 1170 1171 // Ensure the plan evicted all allocs 1172 if len(plan.NodeUpdate[node.ID]) != len(allocs) { 1173 t.Fatalf("bad: %#v", plan) 1174 } 1175 1176 // Ensure the plan allocated 1177 var planned []*structs.Allocation 1178 for _, allocList := range plan.NodeAllocation { 1179 planned = append(planned, allocList...) 1180 } 1181 if len(planned) != 10 { 1182 t.Fatalf("bad: %#v", plan) 1183 } 1184 1185 // Lookup the allocations by JobID 1186 out, err := h.State.AllocsByJob(job.ID) 1187 noErr(t, err) 1188 1189 // Ensure all allocations placed 1190 out = structs.FilterTerminalAllocs(out) 1191 if len(out) != 10 { 1192 t.Fatalf("bad: %#v", out) 1193 } 1194 1195 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1196 } 1197 1198 func TestServiceSched_NodeDrain_UpdateStrategy(t *testing.T) { 1199 h := NewHarness(t) 1200 1201 // Register a draining node 1202 node := mock.Node() 1203 node.Drain = true 1204 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1205 1206 // Create some nodes 1207 for i := 0; i < 10; i++ { 1208 node := mock.Node() 1209 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1210 } 1211 1212 // Generate a fake job with allocations and an update policy. 1213 job := mock.Job() 1214 mp := 5 1215 job.Update = structs.UpdateStrategy{ 1216 Stagger: time.Second, 1217 MaxParallel: mp, 1218 } 1219 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1220 1221 var allocs []*structs.Allocation 1222 for i := 0; i < 10; i++ { 1223 alloc := mock.Alloc() 1224 alloc.Job = job 1225 alloc.JobID = job.ID 1226 alloc.NodeID = node.ID 1227 alloc.Name = fmt.Sprintf("my-job.web[%d]", i) 1228 allocs = append(allocs, alloc) 1229 } 1230 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 1231 1232 // Create a mock evaluation to deal with drain 1233 eval := &structs.Evaluation{ 1234 ID: structs.GenerateUUID(), 1235 Priority: 50, 1236 TriggeredBy: structs.EvalTriggerNodeUpdate, 1237 JobID: job.ID, 1238 NodeID: node.ID, 1239 } 1240 1241 // Process the evaluation 1242 err := h.Process(NewServiceScheduler, eval) 1243 if err != nil { 1244 t.Fatalf("err: %v", err) 1245 } 1246 1247 // Ensure a single plan 1248 if len(h.Plans) != 1 { 1249 t.Fatalf("bad: %#v", h.Plans) 1250 } 1251 plan := h.Plans[0] 1252 1253 // Ensure the plan evicted all allocs 1254 if len(plan.NodeUpdate[node.ID]) != mp { 1255 t.Fatalf("bad: %#v", plan) 1256 } 1257 1258 // Ensure the plan allocated 1259 var planned []*structs.Allocation 1260 for _, allocList := range plan.NodeAllocation { 1261 planned = append(planned, allocList...) 1262 } 1263 if len(planned) != mp { 1264 t.Fatalf("bad: %#v", plan) 1265 } 1266 1267 // Ensure there is a followup eval. 1268 if len(h.CreateEvals) != 1 || 1269 h.CreateEvals[0].TriggeredBy != structs.EvalTriggerRollingUpdate { 1270 t.Fatalf("bad: %#v", h.CreateEvals) 1271 } 1272 1273 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1274 } 1275 1276 func TestServiceSched_RetryLimit(t *testing.T) { 1277 h := NewHarness(t) 1278 h.Planner = &RejectPlan{h} 1279 1280 // Create some nodes 1281 for i := 0; i < 10; i++ { 1282 node := mock.Node() 1283 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1284 } 1285 1286 // Create a job 1287 job := mock.Job() 1288 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1289 1290 // Create a mock evaluation to register the job 1291 eval := &structs.Evaluation{ 1292 ID: structs.GenerateUUID(), 1293 Priority: job.Priority, 1294 TriggeredBy: structs.EvalTriggerJobRegister, 1295 JobID: job.ID, 1296 } 1297 1298 // Process the evaluation 1299 err := h.Process(NewServiceScheduler, eval) 1300 if err != nil { 1301 t.Fatalf("err: %v", err) 1302 } 1303 1304 // Ensure multiple plans 1305 if len(h.Plans) == 0 { 1306 t.Fatalf("bad: %#v", h.Plans) 1307 } 1308 1309 // Lookup the allocations by JobID 1310 out, err := h.State.AllocsByJob(job.ID) 1311 noErr(t, err) 1312 1313 // Ensure no allocations placed 1314 if len(out) != 0 { 1315 t.Fatalf("bad: %#v", out) 1316 } 1317 1318 // Should hit the retry limit 1319 h.AssertEvalStatus(t, structs.EvalStatusFailed) 1320 } 1321 1322 func TestBatchSched_Run_CompleteAlloc(t *testing.T) { 1323 h := NewHarness(t) 1324 1325 // Create a node 1326 node := mock.Node() 1327 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1328 1329 // Create a job 1330 job := mock.Job() 1331 job.TaskGroups[0].Count = 1 1332 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1333 1334 // Create a complete alloc 1335 alloc := mock.Alloc() 1336 alloc.Job = job 1337 alloc.JobID = job.ID 1338 alloc.NodeID = node.ID 1339 alloc.Name = "my-job.web[0]" 1340 alloc.ClientStatus = structs.AllocClientStatusComplete 1341 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 1342 1343 // Create a mock evaluation to register the job 1344 eval := &structs.Evaluation{ 1345 ID: structs.GenerateUUID(), 1346 Priority: job.Priority, 1347 TriggeredBy: structs.EvalTriggerJobRegister, 1348 JobID: job.ID, 1349 } 1350 1351 // Process the evaluation 1352 err := h.Process(NewBatchScheduler, eval) 1353 if err != nil { 1354 t.Fatalf("err: %v", err) 1355 } 1356 1357 // Ensure no plan as it should be a no-op 1358 if len(h.Plans) != 0 { 1359 t.Fatalf("bad: %#v", h.Plans) 1360 } 1361 1362 // Lookup the allocations by JobID 1363 out, err := h.State.AllocsByJob(job.ID) 1364 noErr(t, err) 1365 1366 // Ensure no allocations placed 1367 if len(out) != 1 { 1368 t.Fatalf("bad: %#v", out) 1369 } 1370 1371 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1372 } 1373 1374 func TestBatchSched_Run_DrainedAlloc(t *testing.T) { 1375 h := NewHarness(t) 1376 1377 // Create a node 1378 node := mock.Node() 1379 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1380 1381 // Create a job 1382 job := mock.Job() 1383 job.TaskGroups[0].Count = 1 1384 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1385 1386 // Create a complete alloc 1387 alloc := mock.Alloc() 1388 alloc.Job = job 1389 alloc.JobID = job.ID 1390 alloc.NodeID = node.ID 1391 alloc.Name = "my-job.web[0]" 1392 alloc.DesiredStatus = structs.AllocDesiredStatusStop 1393 alloc.ClientStatus = structs.AllocClientStatusComplete 1394 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 1395 1396 // Create a mock evaluation to register the job 1397 eval := &structs.Evaluation{ 1398 ID: structs.GenerateUUID(), 1399 Priority: job.Priority, 1400 TriggeredBy: structs.EvalTriggerJobRegister, 1401 JobID: job.ID, 1402 } 1403 1404 // Process the evaluation 1405 err := h.Process(NewBatchScheduler, eval) 1406 if err != nil { 1407 t.Fatalf("err: %v", err) 1408 } 1409 1410 // Ensure a plan 1411 if len(h.Plans) != 1 { 1412 t.Fatalf("bad: %#v", h.Plans) 1413 } 1414 1415 // Lookup the allocations by JobID 1416 out, err := h.State.AllocsByJob(job.ID) 1417 noErr(t, err) 1418 1419 // Ensure a replacement alloc was placed. 1420 if len(out) != 2 { 1421 t.Fatalf("bad: %#v", out) 1422 } 1423 1424 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1425 } 1426 1427 func TestBatchSched_Run_FailedAlloc(t *testing.T) { 1428 h := NewHarness(t) 1429 1430 // Create a node 1431 node := mock.Node() 1432 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1433 1434 // Create a job 1435 job := mock.Job() 1436 job.TaskGroups[0].Count = 1 1437 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1438 1439 // Create a failed alloc 1440 alloc := mock.Alloc() 1441 alloc.Job = job 1442 alloc.JobID = job.ID 1443 alloc.NodeID = node.ID 1444 alloc.Name = "my-job.web[0]" 1445 alloc.ClientStatus = structs.AllocClientStatusFailed 1446 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 1447 1448 // Create a mock evaluation to register the job 1449 eval := &structs.Evaluation{ 1450 ID: structs.GenerateUUID(), 1451 Priority: job.Priority, 1452 TriggeredBy: structs.EvalTriggerJobRegister, 1453 JobID: job.ID, 1454 } 1455 1456 // Process the evaluation 1457 err := h.Process(NewBatchScheduler, eval) 1458 if err != nil { 1459 t.Fatalf("err: %v", err) 1460 } 1461 1462 // Ensure a plan 1463 if len(h.Plans) != 1 { 1464 t.Fatalf("bad: %#v", h.Plans) 1465 } 1466 1467 // Lookup the allocations by JobID 1468 out, err := h.State.AllocsByJob(job.ID) 1469 noErr(t, err) 1470 1471 // Ensure a replacement alloc was placed. 1472 if len(out) != 2 { 1473 t.Fatalf("bad: %#v", out) 1474 } 1475 1476 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1477 } 1478 1479 func TestBatchSched_ReRun_SuccessfullyFinishedAlloc(t *testing.T) { 1480 h := NewHarness(t) 1481 1482 // Create two nodes, one that is drained and has a successfully finished 1483 // alloc and a fresh undrained one 1484 node := mock.Node() 1485 node.Drain = true 1486 node2 := mock.Node() 1487 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1488 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 1489 1490 // Create a job 1491 job := mock.Job() 1492 job.Type = structs.JobTypeBatch 1493 job.TaskGroups[0].Count = 1 1494 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1495 1496 // Create a successful alloc 1497 alloc := mock.Alloc() 1498 alloc.Job = job 1499 alloc.JobID = job.ID 1500 alloc.NodeID = node.ID 1501 alloc.Name = "my-job.web[0]" 1502 alloc.ClientStatus = structs.AllocClientStatusComplete 1503 alloc.TaskStates = map[string]*structs.TaskState{ 1504 "web": &structs.TaskState{ 1505 State: structs.TaskStateDead, 1506 Events: []*structs.TaskEvent{ 1507 { 1508 Type: structs.TaskTerminated, 1509 ExitCode: 0, 1510 }, 1511 }, 1512 }, 1513 } 1514 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 1515 1516 // Create a mock evaluation to rerun the job 1517 eval := &structs.Evaluation{ 1518 ID: structs.GenerateUUID(), 1519 Priority: job.Priority, 1520 TriggeredBy: structs.EvalTriggerJobRegister, 1521 JobID: job.ID, 1522 } 1523 1524 // Process the evaluation 1525 err := h.Process(NewBatchScheduler, eval) 1526 if err != nil { 1527 t.Fatalf("err: %v", err) 1528 } 1529 1530 // Ensure no plan 1531 if len(h.Plans) != 0 { 1532 t.Fatalf("bad: %#v", h.Plans) 1533 } 1534 1535 // Lookup the allocations by JobID 1536 out, err := h.State.AllocsByJob(job.ID) 1537 noErr(t, err) 1538 1539 // Ensure no replacement alloc was placed. 1540 if len(out) != 1 { 1541 t.Fatalf("bad: %#v", out) 1542 } 1543 1544 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1545 }