github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/scheduler/system_sched_test.go (about) 1 package scheduler 2 3 import ( 4 "reflect" 5 "sort" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/nomad/mock" 10 "github.com/hashicorp/nomad/nomad/structs" 11 ) 12 13 func TestSystemSched_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.SystemJob() 24 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 25 26 // Create a mock evaluation to deregister 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(NewSystemScheduler, 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 plan allocated 52 var planned []*structs.Allocation 53 for _, allocList := range plan.NodeAllocation { 54 planned = append(planned, allocList...) 55 } 56 if len(planned) != 10 { 57 t.Fatalf("bad: %#v", plan) 58 } 59 60 // Lookup the allocations by JobID 61 out, err := h.State.AllocsByJob(job.ID) 62 noErr(t, err) 63 64 // Ensure all allocations placed 65 if len(out) != 10 { 66 t.Fatalf("bad: %#v", out) 67 } 68 69 // Check the available nodes 70 if count, ok := out[0].Metrics.NodesAvailable["dc1"]; !ok || count != 10 { 71 t.Fatalf("bad: %#v", out[0].Metrics) 72 } 73 74 // Ensure no allocations are queued 75 queued := h.Evals[0].QueuedAllocations["web"] 76 if queued != 0 { 77 t.Fatalf("expected queued allocations: %v, actual: %v", 0, queued) 78 } 79 80 h.AssertEvalStatus(t, structs.EvalStatusComplete) 81 } 82 83 func TestSystemeSched_JobRegister_StickyAllocs(t *testing.T) { 84 h := NewHarness(t) 85 86 // Create some nodes 87 for i := 0; i < 10; i++ { 88 node := mock.Node() 89 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 90 } 91 92 // Create a job 93 job := mock.SystemJob() 94 job.TaskGroups[0].EphemeralDisk.Sticky = true 95 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 96 97 // Create a mock evaluation to register the job 98 eval := &structs.Evaluation{ 99 ID: structs.GenerateUUID(), 100 Priority: job.Priority, 101 TriggeredBy: structs.EvalTriggerJobRegister, 102 JobID: job.ID, 103 } 104 105 // Process the evaluation 106 if err := h.Process(NewSystemScheduler, eval); err != nil { 107 t.Fatalf("err: %v", err) 108 } 109 110 // Ensure the plan allocated 111 plan := h.Plans[0] 112 var planned []*structs.Allocation 113 for _, allocList := range plan.NodeAllocation { 114 planned = append(planned, allocList...) 115 } 116 if len(planned) != 10 { 117 t.Fatalf("bad: %#v", plan) 118 } 119 120 // Get an allocation and mark it as failed 121 alloc := planned[4].Copy() 122 alloc.ClientStatus = structs.AllocClientStatusFailed 123 noErr(t, h.State.UpdateAllocsFromClient(h.NextIndex(), []*structs.Allocation{alloc})) 124 125 // Create a mock evaluation to handle the update 126 eval = &structs.Evaluation{ 127 ID: structs.GenerateUUID(), 128 Priority: job.Priority, 129 TriggeredBy: structs.EvalTriggerNodeUpdate, 130 JobID: job.ID, 131 } 132 h1 := NewHarnessWithState(t, h.State) 133 if err := h1.Process(NewSystemScheduler, eval); err != nil { 134 t.Fatalf("err: %v", err) 135 } 136 137 // Ensure we have created only one new allocation 138 plan = h1.Plans[0] 139 var newPlanned []*structs.Allocation 140 for _, allocList := range plan.NodeAllocation { 141 newPlanned = append(newPlanned, allocList...) 142 } 143 if len(newPlanned) != 1 { 144 t.Fatalf("bad plan: %#v", plan) 145 } 146 // Ensure that the new allocation was placed on the same node as the older 147 // one 148 if newPlanned[0].NodeID != alloc.NodeID || newPlanned[0].PreviousAllocation != alloc.ID { 149 t.Fatalf("expected: %#v, actual: %#v", alloc, newPlanned[0]) 150 } 151 } 152 153 func TestSystemSched_JobRegister_EphemeralDiskConstraint(t *testing.T) { 154 h := NewHarness(t) 155 156 // Create a nodes 157 node := mock.Node() 158 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 159 160 // Create a job 161 job := mock.SystemJob() 162 job.TaskGroups[0].EphemeralDisk.SizeMB = 60 * 1024 163 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 164 165 // Create another job with a lot of disk resource ask so that it doesn't fit 166 // the node 167 job1 := mock.SystemJob() 168 job1.TaskGroups[0].EphemeralDisk.SizeMB = 60 * 1024 169 noErr(t, h.State.UpsertJob(h.NextIndex(), job1)) 170 171 // Create a mock evaluation to register the job 172 eval := &structs.Evaluation{ 173 ID: structs.GenerateUUID(), 174 Priority: job.Priority, 175 TriggeredBy: structs.EvalTriggerJobRegister, 176 JobID: job.ID, 177 } 178 179 // Process the evaluation 180 if err := h.Process(NewSystemScheduler, eval); err != nil { 181 t.Fatalf("err: %v", err) 182 } 183 184 // Lookup the allocations by JobID 185 out, err := h.State.AllocsByJob(job.ID) 186 noErr(t, err) 187 188 // Ensure all allocations placed 189 if len(out) != 1 { 190 t.Fatalf("bad: %#v", out) 191 } 192 193 // Create a new harness to test the scheduling result for the second job 194 h1 := NewHarnessWithState(t, h.State) 195 // Create a mock evaluation to register the job 196 eval1 := &structs.Evaluation{ 197 ID: structs.GenerateUUID(), 198 Priority: job1.Priority, 199 TriggeredBy: structs.EvalTriggerJobRegister, 200 JobID: job1.ID, 201 } 202 203 // Process the evaluation 204 if err := h1.Process(NewSystemScheduler, eval1); err != nil { 205 t.Fatalf("err: %v", err) 206 } 207 208 out, err = h1.State.AllocsByJob(job1.ID) 209 noErr(t, err) 210 if len(out) != 0 { 211 t.Fatalf("bad: %#v", out) 212 } 213 } 214 215 func TestSystemSched_ExhaustResources(t *testing.T) { 216 h := NewHarness(t) 217 218 // Create a nodes 219 node := mock.Node() 220 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 221 222 // Create a service job which consumes most of the system resources 223 svcJob := mock.Job() 224 svcJob.TaskGroups[0].Count = 1 225 svcJob.TaskGroups[0].Tasks[0].Resources.CPU = 3600 226 noErr(t, h.State.UpsertJob(h.NextIndex(), svcJob)) 227 228 // Create a mock evaluation to register the job 229 eval := &structs.Evaluation{ 230 ID: structs.GenerateUUID(), 231 Priority: svcJob.Priority, 232 TriggeredBy: structs.EvalTriggerJobRegister, 233 JobID: svcJob.ID, 234 } 235 236 // Process the evaluation 237 err := h.Process(NewServiceScheduler, eval) 238 if err != nil { 239 t.Fatalf("err: %v", err) 240 } 241 242 // Create a system job 243 job := mock.SystemJob() 244 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 245 246 // Create a mock evaluation to register the job 247 eval1 := &structs.Evaluation{ 248 ID: structs.GenerateUUID(), 249 Priority: job.Priority, 250 TriggeredBy: structs.EvalTriggerJobRegister, 251 JobID: job.ID, 252 } 253 254 // Process the evaluation 255 if err := h.Process(NewSystemScheduler, eval1); err != nil { 256 t.Fatalf("err: %v", err) 257 } 258 259 // Ensure that we have one allocation queued from the system job eval 260 queued := h.Evals[1].QueuedAllocations["web"] 261 if queued != 1 { 262 t.Fatalf("expected: %v, actual: %v", 1, queued) 263 } 264 } 265 266 func TestSystemSched_JobRegister_Annotate(t *testing.T) { 267 h := NewHarness(t) 268 269 // Create some nodes 270 for i := 0; i < 10; i++ { 271 node := mock.Node() 272 if i < 9 { 273 node.NodeClass = "foo" 274 } else { 275 node.NodeClass = "bar" 276 } 277 node.ComputeClass() 278 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 279 } 280 281 // Create a job constraining on node class 282 job := mock.SystemJob() 283 fooConstraint := &structs.Constraint{ 284 LTarget: "${node.class}", 285 RTarget: "foo", 286 Operand: "==", 287 } 288 job.Constraints = append(job.Constraints, fooConstraint) 289 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 290 291 // Create a mock evaluation to deregister the job 292 eval := &structs.Evaluation{ 293 ID: structs.GenerateUUID(), 294 Priority: job.Priority, 295 TriggeredBy: structs.EvalTriggerJobRegister, 296 JobID: job.ID, 297 AnnotatePlan: true, 298 } 299 300 // Process the evaluation 301 err := h.Process(NewSystemScheduler, eval) 302 if err != nil { 303 t.Fatalf("err: %v", err) 304 } 305 306 // Ensure a single plan 307 if len(h.Plans) != 1 { 308 t.Fatalf("bad: %#v", h.Plans) 309 } 310 plan := h.Plans[0] 311 312 // Ensure the plan allocated 313 var planned []*structs.Allocation 314 for _, allocList := range plan.NodeAllocation { 315 planned = append(planned, allocList...) 316 } 317 if len(planned) != 9 { 318 t.Fatalf("bad: %#v %d", planned, len(planned)) 319 } 320 321 // Lookup the allocations by JobID 322 out, err := h.State.AllocsByJob(job.ID) 323 noErr(t, err) 324 325 // Ensure all allocations placed 326 if len(out) != 9 { 327 t.Fatalf("bad: %#v", out) 328 } 329 330 // Check the available nodes 331 if count, ok := out[0].Metrics.NodesAvailable["dc1"]; !ok || count != 10 { 332 t.Fatalf("bad: %#v", out[0].Metrics) 333 } 334 335 h.AssertEvalStatus(t, structs.EvalStatusComplete) 336 337 // Ensure the plan had annotations. 338 if plan.Annotations == nil { 339 t.Fatalf("expected annotations") 340 } 341 342 desiredTGs := plan.Annotations.DesiredTGUpdates 343 if l := len(desiredTGs); l != 1 { 344 t.Fatalf("incorrect number of task groups; got %v; want %v", l, 1) 345 } 346 347 desiredChanges, ok := desiredTGs["web"] 348 if !ok { 349 t.Fatalf("expected task group web to have desired changes") 350 } 351 352 expected := &structs.DesiredUpdates{Place: 9} 353 if !reflect.DeepEqual(desiredChanges, expected) { 354 t.Fatalf("Unexpected desired updates; got %#v; want %#v", desiredChanges, expected) 355 } 356 } 357 358 func TestSystemSched_JobRegister_AddNode(t *testing.T) { 359 h := NewHarness(t) 360 361 // Create some nodes 362 var nodes []*structs.Node 363 for i := 0; i < 10; i++ { 364 node := mock.Node() 365 nodes = append(nodes, node) 366 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 367 } 368 369 // Generate a fake job with allocations 370 job := mock.SystemJob() 371 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 372 373 var allocs []*structs.Allocation 374 for _, node := range nodes { 375 alloc := mock.Alloc() 376 alloc.Job = job 377 alloc.JobID = job.ID 378 alloc.NodeID = node.ID 379 alloc.Name = "my-job.web[0]" 380 allocs = append(allocs, alloc) 381 } 382 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 383 384 // Add a new node. 385 node := mock.Node() 386 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 387 388 // Create a mock evaluation to deal with the node update 389 eval := &structs.Evaluation{ 390 ID: structs.GenerateUUID(), 391 Priority: 50, 392 TriggeredBy: structs.EvalTriggerNodeUpdate, 393 JobID: job.ID, 394 } 395 396 // Process the evaluation 397 err := h.Process(NewSystemScheduler, eval) 398 if err != nil { 399 t.Fatalf("err: %v", err) 400 } 401 402 // Ensure a single plan 403 if len(h.Plans) != 1 { 404 t.Fatalf("bad: %#v", h.Plans) 405 } 406 plan := h.Plans[0] 407 408 // Ensure the plan had no node updates 409 var update []*structs.Allocation 410 for _, updateList := range plan.NodeUpdate { 411 update = append(update, updateList...) 412 } 413 if len(update) != 0 { 414 t.Log(len(update)) 415 t.Fatalf("bad: %#v", plan) 416 } 417 418 // Ensure the plan allocated on the new node 419 var planned []*structs.Allocation 420 for _, allocList := range plan.NodeAllocation { 421 planned = append(planned, allocList...) 422 } 423 if len(planned) != 1 { 424 t.Fatalf("bad: %#v", plan) 425 } 426 427 // Ensure it allocated on the right node 428 if _, ok := plan.NodeAllocation[node.ID]; !ok { 429 t.Fatalf("allocated on wrong node: %#v", plan) 430 } 431 432 // Lookup the allocations by JobID 433 out, err := h.State.AllocsByJob(job.ID) 434 noErr(t, err) 435 436 // Ensure all allocations placed 437 out, _ = structs.FilterTerminalAllocs(out) 438 if len(out) != 11 { 439 t.Fatalf("bad: %#v", out) 440 } 441 442 h.AssertEvalStatus(t, structs.EvalStatusComplete) 443 } 444 445 func TestSystemSched_JobRegister_AllocFail(t *testing.T) { 446 h := NewHarness(t) 447 448 // Create NO nodes 449 // Create a job 450 job := mock.SystemJob() 451 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 452 453 // Create a mock evaluation to register the job 454 eval := &structs.Evaluation{ 455 ID: structs.GenerateUUID(), 456 Priority: job.Priority, 457 TriggeredBy: structs.EvalTriggerJobRegister, 458 JobID: job.ID, 459 } 460 461 // Process the evaluation 462 err := h.Process(NewSystemScheduler, eval) 463 if err != nil { 464 t.Fatalf("err: %v", err) 465 } 466 467 // Ensure no plan as this should be a no-op. 468 if len(h.Plans) != 0 { 469 t.Fatalf("bad: %#v", h.Plans) 470 } 471 472 h.AssertEvalStatus(t, structs.EvalStatusComplete) 473 } 474 475 func TestSystemSched_JobModify(t *testing.T) { 476 h := NewHarness(t) 477 478 // Create some nodes 479 var nodes []*structs.Node 480 for i := 0; i < 10; i++ { 481 node := mock.Node() 482 nodes = append(nodes, node) 483 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 484 } 485 486 // Generate a fake job with allocations 487 job := mock.SystemJob() 488 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 489 490 var allocs []*structs.Allocation 491 for _, node := range nodes { 492 alloc := mock.Alloc() 493 alloc.Job = job 494 alloc.JobID = job.ID 495 alloc.NodeID = node.ID 496 alloc.Name = "my-job.web[0]" 497 allocs = append(allocs, alloc) 498 } 499 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 500 501 // Add a few terminal status allocations, these should be ignored 502 var terminal []*structs.Allocation 503 for i := 0; i < 5; i++ { 504 alloc := mock.Alloc() 505 alloc.Job = job 506 alloc.JobID = job.ID 507 alloc.NodeID = nodes[i].ID 508 alloc.Name = "my-job.web[0]" 509 alloc.DesiredStatus = structs.AllocDesiredStatusStop 510 terminal = append(terminal, alloc) 511 } 512 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 513 514 // Update the job 515 job2 := mock.SystemJob() 516 job2.ID = job.ID 517 518 // Update the task, such that it cannot be done in-place 519 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 520 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 521 522 // Create a mock evaluation to deal with drain 523 eval := &structs.Evaluation{ 524 ID: structs.GenerateUUID(), 525 Priority: 50, 526 TriggeredBy: structs.EvalTriggerJobRegister, 527 JobID: job.ID, 528 } 529 530 // Process the evaluation 531 err := h.Process(NewSystemScheduler, eval) 532 if err != nil { 533 t.Fatalf("err: %v", err) 534 } 535 536 // Ensure a single plan 537 if len(h.Plans) != 1 { 538 t.Fatalf("bad: %#v", h.Plans) 539 } 540 plan := h.Plans[0] 541 542 // Ensure the plan evicted all allocs 543 var update []*structs.Allocation 544 for _, updateList := range plan.NodeUpdate { 545 update = append(update, updateList...) 546 } 547 if len(update) != len(allocs) { 548 t.Fatalf("bad: %#v", plan) 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 out, _ = structs.FilterTerminalAllocs(out) 566 if len(out) != 10 { 567 t.Fatalf("bad: %#v", out) 568 } 569 570 h.AssertEvalStatus(t, structs.EvalStatusComplete) 571 } 572 573 func TestSystemSched_JobModify_Rolling(t *testing.T) { 574 h := NewHarness(t) 575 576 // Create some nodes 577 var nodes []*structs.Node 578 for i := 0; i < 10; i++ { 579 node := mock.Node() 580 nodes = append(nodes, node) 581 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 582 } 583 584 // Generate a fake job with allocations 585 job := mock.SystemJob() 586 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 587 588 var allocs []*structs.Allocation 589 for _, node := range nodes { 590 alloc := mock.Alloc() 591 alloc.Job = job 592 alloc.JobID = job.ID 593 alloc.NodeID = node.ID 594 alloc.Name = "my-job.web[0]" 595 allocs = append(allocs, alloc) 596 } 597 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 598 599 // Update the job 600 job2 := mock.SystemJob() 601 job2.ID = job.ID 602 job2.Update = structs.UpdateStrategy{ 603 Stagger: 30 * time.Second, 604 MaxParallel: 5, 605 } 606 607 // Update the task, such that it cannot be done in-place 608 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 609 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 610 611 // Create a mock evaluation to deal with drain 612 eval := &structs.Evaluation{ 613 ID: structs.GenerateUUID(), 614 Priority: 50, 615 TriggeredBy: structs.EvalTriggerJobRegister, 616 JobID: job.ID, 617 } 618 619 // Process the evaluation 620 err := h.Process(NewSystemScheduler, eval) 621 if err != nil { 622 t.Fatalf("err: %v", err) 623 } 624 625 // Ensure a single plan 626 if len(h.Plans) != 1 { 627 t.Fatalf("bad: %#v", h.Plans) 628 } 629 plan := h.Plans[0] 630 631 // Ensure the plan evicted only MaxParallel 632 var update []*structs.Allocation 633 for _, updateList := range plan.NodeUpdate { 634 update = append(update, updateList...) 635 } 636 if len(update) != job2.Update.MaxParallel { 637 t.Fatalf("bad: %#v", plan) 638 } 639 640 // Ensure the plan allocated 641 var planned []*structs.Allocation 642 for _, allocList := range plan.NodeAllocation { 643 planned = append(planned, allocList...) 644 } 645 if len(planned) != job2.Update.MaxParallel { 646 t.Fatalf("bad: %#v", plan) 647 } 648 649 h.AssertEvalStatus(t, structs.EvalStatusComplete) 650 651 // Ensure a follow up eval was created 652 eval = h.Evals[0] 653 if eval.NextEval == "" { 654 t.Fatalf("missing next eval") 655 } 656 657 // Check for create 658 if len(h.CreateEvals) == 0 { 659 t.Fatalf("missing created eval") 660 } 661 create := h.CreateEvals[0] 662 if eval.NextEval != create.ID { 663 t.Fatalf("ID mismatch") 664 } 665 if create.PreviousEval != eval.ID { 666 t.Fatalf("missing previous eval") 667 } 668 669 if create.TriggeredBy != structs.EvalTriggerRollingUpdate { 670 t.Fatalf("bad: %#v", create) 671 } 672 } 673 674 func TestSystemSched_JobModify_InPlace(t *testing.T) { 675 h := NewHarness(t) 676 677 // Create some nodes 678 var nodes []*structs.Node 679 for i := 0; i < 10; i++ { 680 node := mock.Node() 681 nodes = append(nodes, node) 682 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 683 } 684 685 // Generate a fake job with allocations 686 job := mock.SystemJob() 687 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 688 689 var allocs []*structs.Allocation 690 for _, node := range nodes { 691 alloc := mock.Alloc() 692 alloc.Job = job 693 alloc.JobID = job.ID 694 alloc.NodeID = node.ID 695 alloc.Name = "my-job.web[0]" 696 allocs = append(allocs, alloc) 697 } 698 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 699 700 // Update the job 701 job2 := mock.SystemJob() 702 job2.ID = job.ID 703 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 704 705 // Create a mock evaluation to deal with drain 706 eval := &structs.Evaluation{ 707 ID: structs.GenerateUUID(), 708 Priority: 50, 709 TriggeredBy: structs.EvalTriggerJobRegister, 710 JobID: job.ID, 711 } 712 713 // Process the evaluation 714 err := h.Process(NewSystemScheduler, eval) 715 if err != nil { 716 t.Fatalf("err: %v", err) 717 } 718 719 // Ensure a single plan 720 if len(h.Plans) != 1 { 721 t.Fatalf("bad: %#v", h.Plans) 722 } 723 plan := h.Plans[0] 724 725 // Ensure the plan did not evict any allocs 726 var update []*structs.Allocation 727 for _, updateList := range plan.NodeUpdate { 728 update = append(update, updateList...) 729 } 730 if len(update) != 0 { 731 t.Fatalf("bad: %#v", plan) 732 } 733 734 // Ensure the plan updated the existing allocs 735 var planned []*structs.Allocation 736 for _, allocList := range plan.NodeAllocation { 737 planned = append(planned, allocList...) 738 } 739 if len(planned) != 10 { 740 t.Fatalf("bad: %#v", plan) 741 } 742 for _, p := range planned { 743 if p.Job != job2 { 744 t.Fatalf("should update job") 745 } 746 } 747 748 // Lookup the allocations by JobID 749 out, err := h.State.AllocsByJob(job.ID) 750 noErr(t, err) 751 752 // Ensure all allocations placed 753 if len(out) != 10 { 754 t.Fatalf("bad: %#v", out) 755 } 756 h.AssertEvalStatus(t, structs.EvalStatusComplete) 757 758 // Verify the network did not change 759 rp := structs.Port{Label: "main", Value: 5000} 760 for _, alloc := range out { 761 for _, resources := range alloc.TaskResources { 762 if resources.Networks[0].ReservedPorts[0] != rp { 763 t.Fatalf("bad: %#v", alloc) 764 } 765 } 766 } 767 } 768 769 func TestSystemSched_JobDeregister(t *testing.T) { 770 h := NewHarness(t) 771 772 // Create some nodes 773 var nodes []*structs.Node 774 for i := 0; i < 10; i++ { 775 node := mock.Node() 776 nodes = append(nodes, node) 777 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 778 } 779 780 // Generate a fake job with allocations 781 job := mock.SystemJob() 782 783 var allocs []*structs.Allocation 784 for _, node := range nodes { 785 alloc := mock.Alloc() 786 alloc.Job = job 787 alloc.JobID = job.ID 788 alloc.NodeID = node.ID 789 alloc.Name = "my-job.web[0]" 790 allocs = append(allocs, alloc) 791 } 792 for _, alloc := range allocs { 793 noErr(t, h.State.UpsertJobSummary(h.NextIndex(), mock.JobSummary(alloc.JobID))) 794 } 795 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 796 797 // Create a mock evaluation to deregister the job 798 eval := &structs.Evaluation{ 799 ID: structs.GenerateUUID(), 800 Priority: 50, 801 TriggeredBy: structs.EvalTriggerJobDeregister, 802 JobID: job.ID, 803 } 804 805 // Process the evaluation 806 err := h.Process(NewSystemScheduler, eval) 807 if err != nil { 808 t.Fatalf("err: %v", err) 809 } 810 811 // Ensure a single plan 812 if len(h.Plans) != 1 { 813 t.Fatalf("bad: %#v", h.Plans) 814 } 815 plan := h.Plans[0] 816 817 // Ensure the plan evicted the job from all nodes. 818 for _, node := range nodes { 819 if len(plan.NodeUpdate[node.ID]) != 1 { 820 t.Fatalf("bad: %#v", plan) 821 } 822 } 823 824 // Lookup the allocations by JobID 825 out, err := h.State.AllocsByJob(job.ID) 826 noErr(t, err) 827 828 // Ensure no remaining allocations 829 out, _ = structs.FilterTerminalAllocs(out) 830 if len(out) != 0 { 831 t.Fatalf("bad: %#v", out) 832 } 833 834 h.AssertEvalStatus(t, structs.EvalStatusComplete) 835 } 836 837 func TestSystemSched_NodeDown(t *testing.T) { 838 h := NewHarness(t) 839 840 // Register a down node 841 node := mock.Node() 842 node.Status = structs.NodeStatusDown 843 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 844 845 // Generate a fake job allocated on that node. 846 job := mock.SystemJob() 847 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 848 849 alloc := mock.Alloc() 850 alloc.Job = job 851 alloc.JobID = job.ID 852 alloc.NodeID = node.ID 853 alloc.Name = "my-job.web[0]" 854 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 855 856 // Create a mock evaluation to deal with drain 857 eval := &structs.Evaluation{ 858 ID: structs.GenerateUUID(), 859 Priority: 50, 860 TriggeredBy: structs.EvalTriggerNodeUpdate, 861 JobID: job.ID, 862 NodeID: node.ID, 863 } 864 865 // Process the evaluation 866 err := h.Process(NewSystemScheduler, eval) 867 if err != nil { 868 t.Fatalf("err: %v", err) 869 } 870 871 // Ensure a single plan 872 if len(h.Plans) != 1 { 873 t.Fatalf("bad: %#v", h.Plans) 874 } 875 plan := h.Plans[0] 876 877 // Ensure the plan evicted all allocs 878 if len(plan.NodeUpdate[node.ID]) != 1 { 879 t.Fatalf("bad: %#v", plan) 880 } 881 882 // Ensure the plan updated the allocation. 883 var planned []*structs.Allocation 884 for _, allocList := range plan.NodeUpdate { 885 planned = append(planned, allocList...) 886 } 887 if len(planned) != 1 { 888 t.Fatalf("bad: %#v", plan) 889 } 890 891 // Ensure the allocations is stopped 892 if p := planned[0]; p.DesiredStatus != structs.AllocDesiredStatusStop && 893 p.ClientStatus != structs.AllocClientStatusLost { 894 t.Fatalf("bad: %#v", planned[0]) 895 } 896 897 h.AssertEvalStatus(t, structs.EvalStatusComplete) 898 } 899 900 func TestSystemSched_NodeDrain_Down(t *testing.T) { 901 h := NewHarness(t) 902 903 // Register a draining node 904 node := mock.Node() 905 node.Drain = true 906 node.Status = structs.NodeStatusDown 907 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 908 909 // Generate a fake job allocated on that node. 910 job := mock.SystemJob() 911 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 912 913 alloc := mock.Alloc() 914 alloc.Job = job 915 alloc.JobID = job.ID 916 alloc.NodeID = node.ID 917 alloc.Name = "my-job.web[0]" 918 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 919 920 // Create a mock evaluation to deal with the node update 921 eval := &structs.Evaluation{ 922 ID: structs.GenerateUUID(), 923 Priority: 50, 924 TriggeredBy: structs.EvalTriggerNodeUpdate, 925 JobID: job.ID, 926 NodeID: node.ID, 927 } 928 929 // Process the evaluation 930 err := h.Process(NewServiceScheduler, eval) 931 if err != nil { 932 t.Fatalf("err: %v", err) 933 } 934 935 // Ensure a single plan 936 if len(h.Plans) != 1 { 937 t.Fatalf("bad: %#v", h.Plans) 938 } 939 plan := h.Plans[0] 940 941 // Ensure the plan evicted non terminal allocs 942 if len(plan.NodeUpdate[node.ID]) != 1 { 943 t.Fatalf("bad: %#v", plan) 944 } 945 946 // Ensure that the allocation is marked as lost 947 var lostAllocs []string 948 for _, alloc := range plan.NodeUpdate[node.ID] { 949 lostAllocs = append(lostAllocs, alloc.ID) 950 } 951 expected := []string{alloc.ID} 952 953 if !reflect.DeepEqual(lostAllocs, expected) { 954 t.Fatalf("expected: %v, actual: %v", expected, lostAllocs) 955 } 956 h.AssertEvalStatus(t, structs.EvalStatusComplete) 957 } 958 959 func TestSystemSched_NodeDrain(t *testing.T) { 960 h := NewHarness(t) 961 962 // Register a draining node 963 node := mock.Node() 964 node.Drain = true 965 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 966 967 // Generate a fake job allocated on that node. 968 job := mock.SystemJob() 969 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 970 971 alloc := mock.Alloc() 972 alloc.Job = job 973 alloc.JobID = job.ID 974 alloc.NodeID = node.ID 975 alloc.Name = "my-job.web[0]" 976 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 977 978 // Create a mock evaluation to deal with drain 979 eval := &structs.Evaluation{ 980 ID: structs.GenerateUUID(), 981 Priority: 50, 982 TriggeredBy: structs.EvalTriggerNodeUpdate, 983 JobID: job.ID, 984 NodeID: node.ID, 985 } 986 987 // Process the evaluation 988 err := h.Process(NewSystemScheduler, eval) 989 if err != nil { 990 t.Fatalf("err: %v", err) 991 } 992 993 // Ensure a single plan 994 if len(h.Plans) != 1 { 995 t.Fatalf("bad: %#v", h.Plans) 996 } 997 plan := h.Plans[0] 998 999 // Ensure the plan evicted all allocs 1000 if len(plan.NodeUpdate[node.ID]) != 1 { 1001 t.Fatalf("bad: %#v", plan) 1002 } 1003 1004 // Ensure the plan updated the allocation. 1005 var planned []*structs.Allocation 1006 for _, allocList := range plan.NodeUpdate { 1007 planned = append(planned, allocList...) 1008 } 1009 if len(planned) != 1 { 1010 t.Log(len(planned)) 1011 t.Fatalf("bad: %#v", plan) 1012 } 1013 1014 // Ensure the allocations is stopped 1015 if planned[0].DesiredStatus != structs.AllocDesiredStatusStop { 1016 t.Fatalf("bad: %#v", planned[0]) 1017 } 1018 1019 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1020 } 1021 1022 func TestSystemSched_NodeUpdate(t *testing.T) { 1023 h := NewHarness(t) 1024 1025 // Register a node 1026 node := mock.Node() 1027 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1028 1029 // Generate a fake job allocated on that node. 1030 job := mock.SystemJob() 1031 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1032 1033 alloc := mock.Alloc() 1034 alloc.Job = job 1035 alloc.JobID = job.ID 1036 alloc.NodeID = node.ID 1037 alloc.Name = "my-job.web[0]" 1038 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 1039 1040 // Create a mock evaluation to deal 1041 eval := &structs.Evaluation{ 1042 ID: structs.GenerateUUID(), 1043 Priority: 50, 1044 TriggeredBy: structs.EvalTriggerNodeUpdate, 1045 JobID: job.ID, 1046 NodeID: node.ID, 1047 } 1048 1049 // Process the evaluation 1050 err := h.Process(NewSystemScheduler, eval) 1051 if err != nil { 1052 t.Fatalf("err: %v", err) 1053 } 1054 1055 // Ensure that queued allocations is zero 1056 if val, ok := h.Evals[0].QueuedAllocations["web"]; !ok || val != 0 { 1057 t.Fatalf("bad queued allocations: %#v", h.Evals[0].QueuedAllocations) 1058 } 1059 1060 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1061 } 1062 1063 func TestSystemSched_RetryLimit(t *testing.T) { 1064 h := NewHarness(t) 1065 h.Planner = &RejectPlan{h} 1066 1067 // Create some nodes 1068 for i := 0; i < 10; i++ { 1069 node := mock.Node() 1070 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1071 } 1072 1073 // Create a job 1074 job := mock.SystemJob() 1075 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1076 1077 // Create a mock evaluation to deregister the job 1078 eval := &structs.Evaluation{ 1079 ID: structs.GenerateUUID(), 1080 Priority: job.Priority, 1081 TriggeredBy: structs.EvalTriggerJobRegister, 1082 JobID: job.ID, 1083 } 1084 1085 // Process the evaluation 1086 err := h.Process(NewSystemScheduler, eval) 1087 if err != nil { 1088 t.Fatalf("err: %v", err) 1089 } 1090 1091 // Ensure multiple plans 1092 if len(h.Plans) == 0 { 1093 t.Fatalf("bad: %#v", h.Plans) 1094 } 1095 1096 // Lookup the allocations by JobID 1097 out, err := h.State.AllocsByJob(job.ID) 1098 noErr(t, err) 1099 1100 // Ensure no allocations placed 1101 if len(out) != 0 { 1102 t.Fatalf("bad: %#v", out) 1103 } 1104 1105 // Should hit the retry limit 1106 h.AssertEvalStatus(t, structs.EvalStatusFailed) 1107 } 1108 1109 // This test ensures that the scheduler doesn't increment the queued allocation 1110 // count for a task group when allocations can't be created on currently 1111 // availabe nodes because of constrain mismatches. 1112 func TestSystemSched_Queued_With_Constraints(t *testing.T) { 1113 h := NewHarness(t) 1114 1115 // Register a node 1116 node := mock.Node() 1117 node.Attributes["kernel.name"] = "darwin" 1118 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1119 1120 // Generate a system job which can't be placed on the node 1121 job := mock.SystemJob() 1122 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1123 1124 // Create a mock evaluation to deal 1125 eval := &structs.Evaluation{ 1126 ID: structs.GenerateUUID(), 1127 Priority: 50, 1128 TriggeredBy: structs.EvalTriggerNodeUpdate, 1129 JobID: job.ID, 1130 NodeID: node.ID, 1131 } 1132 1133 // Process the evaluation 1134 err := h.Process(NewSystemScheduler, eval) 1135 if err != nil { 1136 t.Fatalf("err: %v", err) 1137 } 1138 1139 // Ensure that queued allocations is zero 1140 if val, ok := h.Evals[0].QueuedAllocations["web"]; !ok || val != 0 { 1141 t.Fatalf("bad queued allocations: %#v", h.Evals[0].QueuedAllocations) 1142 } 1143 } 1144 1145 func TestSystemSched_ChainedAlloc(t *testing.T) { 1146 h := NewHarness(t) 1147 1148 // Create some nodes 1149 for i := 0; i < 10; i++ { 1150 node := mock.Node() 1151 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1152 } 1153 1154 // Create a job 1155 job := mock.SystemJob() 1156 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1157 1158 // Create a mock evaluation to register the job 1159 eval := &structs.Evaluation{ 1160 ID: structs.GenerateUUID(), 1161 Priority: job.Priority, 1162 TriggeredBy: structs.EvalTriggerJobRegister, 1163 JobID: job.ID, 1164 } 1165 // Process the evaluation 1166 if err := h.Process(NewSystemScheduler, eval); err != nil { 1167 t.Fatalf("err: %v", err) 1168 } 1169 1170 var allocIDs []string 1171 for _, allocList := range h.Plans[0].NodeAllocation { 1172 for _, alloc := range allocList { 1173 allocIDs = append(allocIDs, alloc.ID) 1174 } 1175 } 1176 sort.Strings(allocIDs) 1177 1178 // Create a new harness to invoke the scheduler again 1179 h1 := NewHarnessWithState(t, h.State) 1180 job1 := mock.SystemJob() 1181 job1.ID = job.ID 1182 job1.TaskGroups[0].Tasks[0].Env["foo"] = "bar" 1183 noErr(t, h1.State.UpsertJob(h1.NextIndex(), job1)) 1184 1185 // Insert two more nodes 1186 for i := 0; i < 2; i++ { 1187 node := mock.Node() 1188 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1189 } 1190 1191 // Create a mock evaluation to update the job 1192 eval1 := &structs.Evaluation{ 1193 ID: structs.GenerateUUID(), 1194 Priority: job1.Priority, 1195 TriggeredBy: structs.EvalTriggerJobRegister, 1196 JobID: job1.ID, 1197 } 1198 // Process the evaluation 1199 if err := h1.Process(NewSystemScheduler, eval1); err != nil { 1200 t.Fatalf("err: %v", err) 1201 } 1202 1203 plan := h1.Plans[0] 1204 1205 // Collect all the chained allocation ids and the new allocations which 1206 // don't have any chained allocations 1207 var prevAllocs []string 1208 var newAllocs []string 1209 for _, allocList := range plan.NodeAllocation { 1210 for _, alloc := range allocList { 1211 if alloc.PreviousAllocation == "" { 1212 newAllocs = append(newAllocs, alloc.ID) 1213 continue 1214 } 1215 prevAllocs = append(prevAllocs, alloc.PreviousAllocation) 1216 } 1217 } 1218 sort.Strings(prevAllocs) 1219 1220 // Ensure that the new allocations has their corresponging original 1221 // allocation ids 1222 if !reflect.DeepEqual(prevAllocs, allocIDs) { 1223 t.Fatalf("expected: %v, actual: %v", len(allocIDs), len(prevAllocs)) 1224 } 1225 1226 // Ensuring two new allocations don't have any chained allocations 1227 if len(newAllocs) != 2 { 1228 t.Fatalf("expected: %v, actual: %v", 2, len(newAllocs)) 1229 } 1230 } 1231 1232 func TestSystemSched_PlanWithDrainedNode(t *testing.T) { 1233 h := NewHarness(t) 1234 1235 // Register two nodes with two different classes 1236 node := mock.Node() 1237 node.NodeClass = "green" 1238 node.Drain = true 1239 node.ComputeClass() 1240 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1241 1242 node2 := mock.Node() 1243 node2.NodeClass = "blue" 1244 node2.ComputeClass() 1245 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 1246 1247 // Create a Job with two task groups, each constrianed on node class 1248 job := mock.SystemJob() 1249 tg1 := job.TaskGroups[0] 1250 tg1.Constraints = append(tg1.Constraints, 1251 &structs.Constraint{ 1252 LTarget: "${node.class}", 1253 RTarget: "green", 1254 Operand: "==", 1255 }) 1256 1257 tg2 := tg1.Copy() 1258 tg2.Name = "web2" 1259 tg2.Constraints[0].RTarget = "blue" 1260 job.TaskGroups = append(job.TaskGroups, tg2) 1261 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1262 1263 // Create an allocation on each node 1264 alloc := mock.Alloc() 1265 alloc.Job = job 1266 alloc.JobID = job.ID 1267 alloc.NodeID = node.ID 1268 alloc.Name = "my-job.web[0]" 1269 alloc.TaskGroup = "web" 1270 1271 alloc2 := mock.Alloc() 1272 alloc2.Job = job 1273 alloc2.JobID = job.ID 1274 alloc2.NodeID = node2.ID 1275 alloc2.Name = "my-job.web2[0]" 1276 alloc2.TaskGroup = "web2" 1277 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc, alloc2})) 1278 1279 // Create a mock evaluation to deal with drain 1280 eval := &structs.Evaluation{ 1281 ID: structs.GenerateUUID(), 1282 Priority: 50, 1283 TriggeredBy: structs.EvalTriggerNodeUpdate, 1284 JobID: job.ID, 1285 NodeID: node.ID, 1286 } 1287 1288 // Process the evaluation 1289 err := h.Process(NewSystemScheduler, eval) 1290 if err != nil { 1291 t.Fatalf("err: %v", err) 1292 } 1293 1294 // Ensure a single plan 1295 if len(h.Plans) != 1 { 1296 t.Fatalf("bad: %#v", h.Plans) 1297 } 1298 plan := h.Plans[0] 1299 1300 // Ensure the plan evicted the alloc on the failed node 1301 planned := plan.NodeUpdate[node.ID] 1302 if len(planned) != 1 { 1303 t.Fatalf("bad: %#v", plan) 1304 } 1305 1306 // Ensure the plan didn't place 1307 if len(plan.NodeAllocation) != 0 { 1308 t.Fatalf("bad: %#v", plan) 1309 } 1310 1311 // Ensure the allocations is stopped 1312 if planned[0].DesiredStatus != structs.AllocDesiredStatusStop { 1313 t.Fatalf("bad: %#v", planned[0]) 1314 } 1315 1316 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1317 } 1318 1319 func TestSystemSched_QueuedAllocsMultTG(t *testing.T) { 1320 h := NewHarness(t) 1321 1322 // Register two nodes with two different classes 1323 node := mock.Node() 1324 node.NodeClass = "green" 1325 node.ComputeClass() 1326 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 1327 1328 node2 := mock.Node() 1329 node2.NodeClass = "blue" 1330 node2.ComputeClass() 1331 noErr(t, h.State.UpsertNode(h.NextIndex(), node2)) 1332 1333 // Create a Job with two task groups, each constrianed on node class 1334 job := mock.SystemJob() 1335 tg1 := job.TaskGroups[0] 1336 tg1.Constraints = append(tg1.Constraints, 1337 &structs.Constraint{ 1338 LTarget: "${node.class}", 1339 RTarget: "green", 1340 Operand: "==", 1341 }) 1342 1343 tg2 := tg1.Copy() 1344 tg2.Name = "web2" 1345 tg2.Constraints[0].RTarget = "blue" 1346 job.TaskGroups = append(job.TaskGroups, tg2) 1347 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 1348 1349 // Create a mock evaluation to deal with drain 1350 eval := &structs.Evaluation{ 1351 ID: structs.GenerateUUID(), 1352 Priority: 50, 1353 TriggeredBy: structs.EvalTriggerNodeUpdate, 1354 JobID: job.ID, 1355 NodeID: node.ID, 1356 } 1357 1358 // Process the evaluation 1359 err := h.Process(NewSystemScheduler, eval) 1360 if err != nil { 1361 t.Fatalf("err: %v", err) 1362 } 1363 1364 // Ensure a single plan 1365 if len(h.Plans) != 1 { 1366 t.Fatalf("bad: %#v", h.Plans) 1367 } 1368 1369 qa := h.Evals[0].QueuedAllocations 1370 if qa["web"] != 0 || qa["web2"] != 0 { 1371 t.Fatalf("bad queued allocations %#v", qa) 1372 } 1373 1374 h.AssertEvalStatus(t, structs.EvalStatusComplete) 1375 }