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