github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/scheduler/system_sched_test.go (about) 1 package scheduler 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/nomad/nomad/mock" 8 "github.com/hashicorp/nomad/nomad/structs" 9 ) 10 11 func TestSystemSched_JobRegister(t *testing.T) { 12 h := NewHarness(t) 13 14 // Create some nodes 15 for i := 0; i < 10; i++ { 16 node := mock.Node() 17 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 18 } 19 20 // Create a job 21 job := mock.SystemJob() 22 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 23 24 // Create a mock evaluation to deregister the job 25 eval := &structs.Evaluation{ 26 ID: structs.GenerateUUID(), 27 Priority: job.Priority, 28 TriggeredBy: structs.EvalTriggerJobRegister, 29 JobID: job.ID, 30 } 31 32 // Process the evaluation 33 err := h.Process(NewSystemScheduler, eval) 34 if err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 38 // Ensure a single plan 39 if len(h.Plans) != 1 { 40 t.Fatalf("bad: %#v", h.Plans) 41 } 42 plan := h.Plans[0] 43 44 // Ensure the plan allocated 45 var planned []*structs.Allocation 46 for _, allocList := range plan.NodeAllocation { 47 planned = append(planned, allocList...) 48 } 49 if len(planned) != 10 { 50 t.Fatalf("bad: %#v", plan) 51 } 52 53 // Lookup the allocations by JobID 54 out, err := h.State.AllocsByJob(job.ID) 55 noErr(t, err) 56 57 // Ensure all allocations placed 58 if len(out) != 10 { 59 t.Fatalf("bad: %#v", out) 60 } 61 62 // Check the available nodes 63 if count, ok := out[0].Metrics.NodesAvailable["dc1"]; !ok || count != 10 { 64 t.Fatalf("bad: %#v", out[0].Metrics) 65 } 66 67 h.AssertEvalStatus(t, structs.EvalStatusComplete) 68 } 69 70 func TestSystemSched_JobRegister_AddNode(t *testing.T) { 71 h := NewHarness(t) 72 73 // Create some nodes 74 var nodes []*structs.Node 75 for i := 0; i < 10; i++ { 76 node := mock.Node() 77 nodes = append(nodes, node) 78 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 79 } 80 81 // Generate a fake job with allocations 82 job := mock.SystemJob() 83 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 84 85 var allocs []*structs.Allocation 86 for _, node := range nodes { 87 alloc := mock.Alloc() 88 alloc.Job = job 89 alloc.JobID = job.ID 90 alloc.NodeID = node.ID 91 alloc.Name = "my-job.web[0]" 92 allocs = append(allocs, alloc) 93 } 94 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 95 96 // Add a new node. 97 node := mock.Node() 98 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 99 100 // Create a mock evaluation to deal with the node update 101 eval := &structs.Evaluation{ 102 ID: structs.GenerateUUID(), 103 Priority: 50, 104 TriggeredBy: structs.EvalTriggerNodeUpdate, 105 JobID: job.ID, 106 } 107 108 // Process the evaluation 109 err := h.Process(NewSystemScheduler, eval) 110 if err != nil { 111 t.Fatalf("err: %v", err) 112 } 113 114 // Ensure a single plan 115 if len(h.Plans) != 1 { 116 t.Fatalf("bad: %#v", h.Plans) 117 } 118 plan := h.Plans[0] 119 120 // Ensure the plan had no node updates 121 var update []*structs.Allocation 122 for _, updateList := range plan.NodeUpdate { 123 update = append(update, updateList...) 124 } 125 if len(update) != 0 { 126 t.Log(len(update)) 127 t.Fatalf("bad: %#v", plan) 128 } 129 130 // Ensure the plan allocated on the new node 131 var planned []*structs.Allocation 132 for _, allocList := range plan.NodeAllocation { 133 planned = append(planned, allocList...) 134 } 135 if len(planned) != 1 { 136 t.Fatalf("bad: %#v", plan) 137 } 138 139 // Ensure it allocated on the right node 140 if _, ok := plan.NodeAllocation[node.ID]; !ok { 141 t.Fatalf("allocated on wrong node: %#v", plan) 142 } 143 144 // Lookup the allocations by JobID 145 out, err := h.State.AllocsByJob(job.ID) 146 noErr(t, err) 147 148 // Ensure all allocations placed 149 out = structs.FilterTerminalAllocs(out) 150 if len(out) != 11 { 151 t.Fatalf("bad: %#v", out) 152 } 153 154 h.AssertEvalStatus(t, structs.EvalStatusComplete) 155 } 156 157 func TestSystemSched_JobRegister_AllocFail(t *testing.T) { 158 h := NewHarness(t) 159 160 // Create NO nodes 161 // Create a job 162 job := mock.SystemJob() 163 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 164 165 // Create a mock evaluation to register the job 166 eval := &structs.Evaluation{ 167 ID: structs.GenerateUUID(), 168 Priority: job.Priority, 169 TriggeredBy: structs.EvalTriggerJobRegister, 170 JobID: job.ID, 171 } 172 173 // Process the evaluation 174 err := h.Process(NewSystemScheduler, eval) 175 if err != nil { 176 t.Fatalf("err: %v", err) 177 } 178 179 // Ensure no plan as this should be a no-op. 180 if len(h.Plans) != 0 { 181 t.Fatalf("bad: %#v", h.Plans) 182 } 183 184 h.AssertEvalStatus(t, structs.EvalStatusComplete) 185 } 186 187 func TestSystemSched_JobModify(t *testing.T) { 188 h := NewHarness(t) 189 190 // Create some nodes 191 var nodes []*structs.Node 192 for i := 0; i < 10; i++ { 193 node := mock.Node() 194 nodes = append(nodes, node) 195 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 196 } 197 198 // Generate a fake job with allocations 199 job := mock.SystemJob() 200 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 201 202 var allocs []*structs.Allocation 203 for _, node := range nodes { 204 alloc := mock.Alloc() 205 alloc.Job = job 206 alloc.JobID = job.ID 207 alloc.NodeID = node.ID 208 alloc.Name = "my-job.web[0]" 209 allocs = append(allocs, alloc) 210 } 211 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 212 213 // Add a few terminal status allocations, these should be ignored 214 var terminal []*structs.Allocation 215 for i := 0; i < 5; i++ { 216 alloc := mock.Alloc() 217 alloc.Job = job 218 alloc.JobID = job.ID 219 alloc.NodeID = nodes[i].ID 220 alloc.Name = "my-job.web[0]" 221 alloc.DesiredStatus = structs.AllocDesiredStatusFailed 222 terminal = append(terminal, alloc) 223 } 224 noErr(t, h.State.UpsertAllocs(h.NextIndex(), terminal)) 225 226 // Update the job 227 job2 := mock.SystemJob() 228 job2.ID = job.ID 229 230 // Update the task, such that it cannot be done in-place 231 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 232 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 233 234 // Create a mock evaluation to deal with drain 235 eval := &structs.Evaluation{ 236 ID: structs.GenerateUUID(), 237 Priority: 50, 238 TriggeredBy: structs.EvalTriggerJobRegister, 239 JobID: job.ID, 240 } 241 242 // Process the evaluation 243 err := h.Process(NewSystemScheduler, eval) 244 if err != nil { 245 t.Fatalf("err: %v", err) 246 } 247 248 // Ensure a single plan 249 if len(h.Plans) != 1 { 250 t.Fatalf("bad: %#v", h.Plans) 251 } 252 plan := h.Plans[0] 253 254 // Ensure the plan evicted all allocs 255 var update []*structs.Allocation 256 for _, updateList := range plan.NodeUpdate { 257 update = append(update, updateList...) 258 } 259 if len(update) != len(allocs) { 260 t.Fatalf("bad: %#v", plan) 261 } 262 263 // Ensure the plan allocated 264 var planned []*structs.Allocation 265 for _, allocList := range plan.NodeAllocation { 266 planned = append(planned, allocList...) 267 } 268 if len(planned) != 10 { 269 t.Fatalf("bad: %#v", plan) 270 } 271 272 // Lookup the allocations by JobID 273 out, err := h.State.AllocsByJob(job.ID) 274 noErr(t, err) 275 276 // Ensure all allocations placed 277 out = structs.FilterTerminalAllocs(out) 278 if len(out) != 10 { 279 t.Fatalf("bad: %#v", out) 280 } 281 282 h.AssertEvalStatus(t, structs.EvalStatusComplete) 283 } 284 285 func TestSystemSched_JobModify_Rolling(t *testing.T) { 286 h := NewHarness(t) 287 288 // Create some nodes 289 var nodes []*structs.Node 290 for i := 0; i < 10; i++ { 291 node := mock.Node() 292 nodes = append(nodes, node) 293 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 294 } 295 296 // Generate a fake job with allocations 297 job := mock.SystemJob() 298 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 299 300 var allocs []*structs.Allocation 301 for _, node := range nodes { 302 alloc := mock.Alloc() 303 alloc.Job = job 304 alloc.JobID = job.ID 305 alloc.NodeID = node.ID 306 alloc.Name = "my-job.web[0]" 307 allocs = append(allocs, alloc) 308 } 309 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 310 311 // Update the job 312 job2 := mock.SystemJob() 313 job2.ID = job.ID 314 job2.Update = structs.UpdateStrategy{ 315 Stagger: 30 * time.Second, 316 MaxParallel: 5, 317 } 318 319 // Update the task, such that it cannot be done in-place 320 job2.TaskGroups[0].Tasks[0].Config["command"] = "/bin/other" 321 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 322 323 // Create a mock evaluation to deal with drain 324 eval := &structs.Evaluation{ 325 ID: structs.GenerateUUID(), 326 Priority: 50, 327 TriggeredBy: structs.EvalTriggerJobRegister, 328 JobID: job.ID, 329 } 330 331 // Process the evaluation 332 err := h.Process(NewSystemScheduler, eval) 333 if err != nil { 334 t.Fatalf("err: %v", err) 335 } 336 337 // Ensure a single plan 338 if len(h.Plans) != 1 { 339 t.Fatalf("bad: %#v", h.Plans) 340 } 341 plan := h.Plans[0] 342 343 // Ensure the plan evicted only MaxParallel 344 var update []*structs.Allocation 345 for _, updateList := range plan.NodeUpdate { 346 update = append(update, updateList...) 347 } 348 if len(update) != job2.Update.MaxParallel { 349 t.Fatalf("bad: %#v", plan) 350 } 351 352 // Ensure the plan allocated 353 var planned []*structs.Allocation 354 for _, allocList := range plan.NodeAllocation { 355 planned = append(planned, allocList...) 356 } 357 if len(planned) != job2.Update.MaxParallel { 358 t.Fatalf("bad: %#v", plan) 359 } 360 361 h.AssertEvalStatus(t, structs.EvalStatusComplete) 362 363 // Ensure a follow up eval was created 364 eval = h.Evals[0] 365 if eval.NextEval == "" { 366 t.Fatalf("missing next eval") 367 } 368 369 // Check for create 370 if len(h.CreateEvals) == 0 { 371 t.Fatalf("missing created eval") 372 } 373 create := h.CreateEvals[0] 374 if eval.NextEval != create.ID { 375 t.Fatalf("ID mismatch") 376 } 377 if create.PreviousEval != eval.ID { 378 t.Fatalf("missing previous eval") 379 } 380 381 if create.TriggeredBy != structs.EvalTriggerRollingUpdate { 382 t.Fatalf("bad: %#v", create) 383 } 384 } 385 386 func TestSystemSched_JobModify_InPlace(t *testing.T) { 387 h := NewHarness(t) 388 389 // Create some nodes 390 var nodes []*structs.Node 391 for i := 0; i < 10; i++ { 392 node := mock.Node() 393 nodes = append(nodes, node) 394 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 395 } 396 397 // Generate a fake job with allocations 398 job := mock.SystemJob() 399 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 400 401 var allocs []*structs.Allocation 402 for _, node := range nodes { 403 alloc := mock.Alloc() 404 alloc.Job = job 405 alloc.JobID = job.ID 406 alloc.NodeID = node.ID 407 alloc.Name = "my-job.web[0]" 408 allocs = append(allocs, alloc) 409 } 410 noErr(t, h.State.UpsertAllocs(h.NextIndex(), allocs)) 411 412 // Update the job 413 job2 := mock.SystemJob() 414 job2.ID = job.ID 415 noErr(t, h.State.UpsertJob(h.NextIndex(), job2)) 416 417 // Create a mock evaluation to deal with drain 418 eval := &structs.Evaluation{ 419 ID: structs.GenerateUUID(), 420 Priority: 50, 421 TriggeredBy: structs.EvalTriggerJobRegister, 422 JobID: job.ID, 423 } 424 425 // Process the evaluation 426 err := h.Process(NewSystemScheduler, eval) 427 if err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 431 // Ensure a single plan 432 if len(h.Plans) != 1 { 433 t.Fatalf("bad: %#v", h.Plans) 434 } 435 plan := h.Plans[0] 436 437 // Ensure the plan did not evict any allocs 438 var update []*structs.Allocation 439 for _, updateList := range plan.NodeUpdate { 440 update = append(update, updateList...) 441 } 442 if len(update) != 0 { 443 t.Fatalf("bad: %#v", plan) 444 } 445 446 // Ensure the plan updated the existing allocs 447 var planned []*structs.Allocation 448 for _, allocList := range plan.NodeAllocation { 449 planned = append(planned, allocList...) 450 } 451 if len(planned) != 10 { 452 t.Fatalf("bad: %#v", plan) 453 } 454 for _, p := range planned { 455 if p.Job != job2 { 456 t.Fatalf("should update job") 457 } 458 } 459 460 // Lookup the allocations by JobID 461 out, err := h.State.AllocsByJob(job.ID) 462 noErr(t, err) 463 464 // Ensure all allocations placed 465 if len(out) != 10 { 466 t.Fatalf("bad: %#v", out) 467 } 468 h.AssertEvalStatus(t, structs.EvalStatusComplete) 469 470 // Verify the network did not change 471 rp := structs.Port{"main", 5000} 472 for _, alloc := range out { 473 for _, resources := range alloc.TaskResources { 474 if resources.Networks[0].ReservedPorts[0] != rp { 475 t.Fatalf("bad: %#v", alloc) 476 } 477 } 478 } 479 } 480 481 func TestSystemSched_JobDeregister(t *testing.T) { 482 h := NewHarness(t) 483 484 // Create some nodes 485 var nodes []*structs.Node 486 for i := 0; i < 10; i++ { 487 node := mock.Node() 488 nodes = append(nodes, node) 489 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 490 } 491 492 // Generate a fake job with allocations 493 job := mock.SystemJob() 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 // Create a mock evaluation to deregister the job 507 eval := &structs.Evaluation{ 508 ID: structs.GenerateUUID(), 509 Priority: 50, 510 TriggeredBy: structs.EvalTriggerJobDeregister, 511 JobID: job.ID, 512 } 513 514 // Process the evaluation 515 err := h.Process(NewSystemScheduler, eval) 516 if err != nil { 517 t.Fatalf("err: %v", err) 518 } 519 520 // Ensure a single plan 521 if len(h.Plans) != 1 { 522 t.Fatalf("bad: %#v", h.Plans) 523 } 524 plan := h.Plans[0] 525 526 // Ensure the plan evicted the job from all nodes. 527 for _, node := range nodes { 528 if len(plan.NodeUpdate[node.ID]) != 1 { 529 t.Fatalf("bad: %#v", plan) 530 } 531 } 532 533 // Lookup the allocations by JobID 534 out, err := h.State.AllocsByJob(job.ID) 535 noErr(t, err) 536 537 // Ensure no remaining allocations 538 out = structs.FilterTerminalAllocs(out) 539 if len(out) != 0 { 540 t.Fatalf("bad: %#v", out) 541 } 542 543 h.AssertEvalStatus(t, structs.EvalStatusComplete) 544 } 545 546 func TestSystemSched_NodeDrain(t *testing.T) { 547 h := NewHarness(t) 548 549 // Register a draining node 550 node := mock.Node() 551 node.Drain = true 552 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 553 554 // Generate a fake job allocated on that node. 555 job := mock.SystemJob() 556 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 557 558 alloc := mock.Alloc() 559 alloc.Job = job 560 alloc.JobID = job.ID 561 alloc.NodeID = node.ID 562 alloc.Name = "my-job.web[0]" 563 noErr(t, h.State.UpsertAllocs(h.NextIndex(), []*structs.Allocation{alloc})) 564 565 // Create a mock evaluation to deal with drain 566 eval := &structs.Evaluation{ 567 ID: structs.GenerateUUID(), 568 Priority: 50, 569 TriggeredBy: structs.EvalTriggerNodeUpdate, 570 JobID: job.ID, 571 NodeID: node.ID, 572 } 573 574 // Process the evaluation 575 err := h.Process(NewSystemScheduler, eval) 576 if err != nil { 577 t.Fatalf("err: %v", err) 578 } 579 580 // Ensure a single plan 581 if len(h.Plans) != 1 { 582 t.Fatalf("bad: %#v", h.Plans) 583 } 584 plan := h.Plans[0] 585 586 // Ensure the plan evicted all allocs 587 if len(plan.NodeUpdate[node.ID]) != 1 { 588 t.Fatalf("bad: %#v", plan) 589 } 590 591 // Ensure the plan updated the allocation. 592 var planned []*structs.Allocation 593 for _, allocList := range plan.NodeUpdate { 594 planned = append(planned, allocList...) 595 } 596 if len(planned) != 1 { 597 t.Log(len(planned)) 598 t.Fatalf("bad: %#v", plan) 599 } 600 601 // Lookup the allocations by JobID 602 out, err := h.State.AllocsByJob(job.ID) 603 noErr(t, err) 604 605 // Ensure the allocations is stopped 606 if planned[0].DesiredStatus != structs.AllocDesiredStatusStop { 607 t.Fatalf("bad: %#v", out) 608 } 609 610 h.AssertEvalStatus(t, structs.EvalStatusComplete) 611 } 612 613 func TestSystemSched_RetryLimit(t *testing.T) { 614 h := NewHarness(t) 615 h.Planner = &RejectPlan{h} 616 617 // Create some nodes 618 for i := 0; i < 10; i++ { 619 node := mock.Node() 620 noErr(t, h.State.UpsertNode(h.NextIndex(), node)) 621 } 622 623 // Create a job 624 job := mock.SystemJob() 625 noErr(t, h.State.UpsertJob(h.NextIndex(), job)) 626 627 // Create a mock evaluation to deregister the job 628 eval := &structs.Evaluation{ 629 ID: structs.GenerateUUID(), 630 Priority: job.Priority, 631 TriggeredBy: structs.EvalTriggerJobRegister, 632 JobID: job.ID, 633 } 634 635 // Process the evaluation 636 err := h.Process(NewSystemScheduler, eval) 637 if err != nil { 638 t.Fatalf("err: %v", err) 639 } 640 641 // Ensure multiple plans 642 if len(h.Plans) == 0 { 643 t.Fatalf("bad: %#v", h.Plans) 644 } 645 646 // Lookup the allocations by JobID 647 out, err := h.State.AllocsByJob(job.ID) 648 noErr(t, err) 649 650 // Ensure no allocations placed 651 if len(out) != 0 { 652 t.Fatalf("bad: %#v", out) 653 } 654 655 // Should hit the retry limit 656 h.AssertEvalStatus(t, structs.EvalStatusFailed) 657 }