github.com/bigcommerce/nomad@v0.9.3-bc/nomad/plan_apply_test.go (about) 1 package nomad 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/go-memdb" 9 "github.com/hashicorp/nomad/helper/testlog" 10 "github.com/hashicorp/nomad/helper/uuid" 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/testutil" 14 "github.com/hashicorp/raft" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 const ( 20 // workerPoolSize is the size of the worker pool 21 workerPoolSize = 2 22 ) 23 24 // planWaitFuture is used to wait for the Raft future to complete 25 func planWaitFuture(future raft.ApplyFuture) (uint64, error) { 26 if err := future.Error(); err != nil { 27 return 0, err 28 } 29 return future.Index(), nil 30 } 31 32 func testRegisterNode(t *testing.T, s *Server, n *structs.Node) { 33 // Create the register request 34 req := &structs.NodeRegisterRequest{ 35 Node: n, 36 WriteRequest: structs.WriteRequest{Region: "global"}, 37 } 38 39 // Fetch the response 40 var resp structs.NodeUpdateResponse 41 if err := s.RPC("Node.Register", req, &resp); err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 if resp.Index == 0 { 45 t.Fatalf("bad index: %d", resp.Index) 46 } 47 } 48 49 func testRegisterJob(t *testing.T, s *Server, j *structs.Job) { 50 // Create the register request 51 req := &structs.JobRegisterRequest{ 52 Job: j, 53 WriteRequest: structs.WriteRequest{Region: "global"}, 54 } 55 56 // Fetch the response 57 var resp structs.JobRegisterResponse 58 if err := s.RPC("Job.Register", req, &resp); err != nil { 59 t.Fatalf("err: %v", err) 60 } 61 if resp.Index == 0 { 62 t.Fatalf("bad index: %d", resp.Index) 63 } 64 } 65 66 // COMPAT 0.11: Tests the older unoptimized code path for applyPlan 67 func TestPlanApply_applyPlan(t *testing.T) { 68 t.Parallel() 69 s1 := TestServer(t, nil) 70 defer s1.Shutdown() 71 testutil.WaitForLeader(t, s1.RPC) 72 73 // Register node 74 node := mock.Node() 75 testRegisterNode(t, s1, node) 76 77 // Register a fake deployment 78 oldDeployment := mock.Deployment() 79 if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil { 80 t.Fatalf("UpsertDeployment failed: %v", err) 81 } 82 83 // Create a deployment 84 dnew := mock.Deployment() 85 86 // Create a deployment update for the old deployment id 87 desiredStatus, desiredStatusDescription := "foo", "bar" 88 updates := []*structs.DeploymentStatusUpdate{ 89 { 90 DeploymentID: oldDeployment.ID, 91 Status: desiredStatus, 92 StatusDescription: desiredStatusDescription, 93 }, 94 } 95 96 // Register alloc, deployment and deployment update 97 alloc := mock.Alloc() 98 s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID)) 99 // Create an eval 100 eval := mock.Eval() 101 eval.JobID = alloc.JobID 102 if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil { 103 t.Fatalf("err: %v", err) 104 } 105 106 planRes := &structs.PlanResult{ 107 NodeAllocation: map[string][]*structs.Allocation{ 108 node.ID: {alloc}, 109 }, 110 Deployment: dnew, 111 DeploymentUpdates: updates, 112 } 113 114 // Snapshot the state 115 snap, err := s1.State().Snapshot() 116 if err != nil { 117 t.Fatalf("err: %v", err) 118 } 119 120 // Create the plan with a deployment 121 plan := &structs.Plan{ 122 Job: alloc.Job, 123 Deployment: dnew, 124 DeploymentUpdates: updates, 125 EvalID: eval.ID, 126 } 127 128 // Apply the plan 129 future, err := s1.applyPlan(plan, planRes, snap) 130 assert := assert.New(t) 131 assert.Nil(err) 132 133 // Verify our optimistic snapshot is updated 134 ws := memdb.NewWatchSet() 135 allocOut, err := snap.AllocByID(ws, alloc.ID) 136 assert.Nil(err) 137 assert.NotNil(allocOut) 138 139 deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID) 140 assert.Nil(err) 141 assert.NotNil(deploymentOut) 142 143 // Check plan does apply cleanly 144 index, err := planWaitFuture(future) 145 assert.Nil(err) 146 assert.NotEqual(0, index) 147 148 // Lookup the allocation 149 fsmState := s1.fsm.State() 150 allocOut, err = fsmState.AllocByID(ws, alloc.ID) 151 assert.Nil(err) 152 assert.NotNil(allocOut) 153 assert.True(allocOut.CreateTime > 0) 154 assert.True(allocOut.ModifyTime > 0) 155 assert.Equal(allocOut.CreateTime, allocOut.ModifyTime) 156 157 // Lookup the new deployment 158 dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID) 159 assert.Nil(err) 160 assert.NotNil(dout) 161 162 // Lookup the updated deployment 163 dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID) 164 assert.Nil(err) 165 assert.NotNil(dout2) 166 assert.Equal(desiredStatus, dout2.Status) 167 assert.Equal(desiredStatusDescription, dout2.StatusDescription) 168 169 // Lookup updated eval 170 evalOut, err := fsmState.EvalByID(ws, eval.ID) 171 assert.Nil(err) 172 assert.NotNil(evalOut) 173 assert.Equal(index, evalOut.ModifyIndex) 174 175 // Evict alloc, Register alloc2 176 allocEvict := new(structs.Allocation) 177 *allocEvict = *alloc 178 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 179 job := allocEvict.Job 180 allocEvict.Job = nil 181 alloc2 := mock.Alloc() 182 s1.State().UpsertJobSummary(1500, mock.JobSummary(alloc2.JobID)) 183 planRes = &structs.PlanResult{ 184 NodeUpdate: map[string][]*structs.Allocation{ 185 node.ID: {allocEvict}, 186 }, 187 NodeAllocation: map[string][]*structs.Allocation{ 188 node.ID: {alloc2}, 189 }, 190 } 191 192 // Snapshot the state 193 snap, err = s1.State().Snapshot() 194 assert.Nil(err) 195 196 // Apply the plan 197 plan = &structs.Plan{ 198 Job: job, 199 EvalID: eval.ID, 200 } 201 future, err = s1.applyPlan(plan, planRes, snap) 202 assert.Nil(err) 203 204 // Check that our optimistic view is updated 205 out, _ := snap.AllocByID(ws, allocEvict.ID) 206 assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus) 207 208 // Verify plan applies cleanly 209 index, err = planWaitFuture(future) 210 assert.Nil(err) 211 assert.NotEqual(0, index) 212 213 // Lookup the allocation 214 allocOut, err = s1.fsm.State().AllocByID(ws, alloc.ID) 215 assert.Nil(err) 216 assert.Equal(structs.AllocDesiredStatusEvict, allocOut.DesiredStatus) 217 assert.NotNil(allocOut.Job) 218 assert.True(allocOut.ModifyTime > 0) 219 220 // Lookup the allocation 221 allocOut, err = s1.fsm.State().AllocByID(ws, alloc2.ID) 222 assert.Nil(err) 223 assert.NotNil(allocOut) 224 assert.NotNil(allocOut.Job) 225 226 // Lookup updated eval 227 evalOut, err = fsmState.EvalByID(ws, eval.ID) 228 assert.Nil(err) 229 assert.NotNil(evalOut) 230 assert.Equal(index, evalOut.ModifyIndex) 231 } 232 233 // Verifies that applyPlan properly updates the constituent objects in MemDB, 234 // when the plan contains normalized allocs. 235 func TestPlanApply_applyPlanWithNormalizedAllocs(t *testing.T) { 236 t.Parallel() 237 s1 := TestServer(t, func(c *Config) { 238 c.Build = "0.9.2" 239 }) 240 defer s1.Shutdown() 241 testutil.WaitForLeader(t, s1.RPC) 242 243 // Register node 244 node := mock.Node() 245 testRegisterNode(t, s1, node) 246 247 // Register a fake deployment 248 oldDeployment := mock.Deployment() 249 if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil { 250 t.Fatalf("UpsertDeployment failed: %v", err) 251 } 252 253 // Create a deployment 254 dnew := mock.Deployment() 255 256 // Create a deployment update for the old deployment id 257 desiredStatus, desiredStatusDescription := "foo", "bar" 258 updates := []*structs.DeploymentStatusUpdate{ 259 { 260 DeploymentID: oldDeployment.ID, 261 Status: desiredStatus, 262 StatusDescription: desiredStatusDescription, 263 }, 264 } 265 266 // Register allocs, deployment and deployment update 267 alloc := mock.Alloc() 268 stoppedAlloc := mock.Alloc() 269 stoppedAllocDiff := &structs.Allocation{ 270 ID: stoppedAlloc.ID, 271 DesiredDescription: "Desired Description", 272 ClientStatus: structs.AllocClientStatusLost, 273 } 274 preemptedAlloc := mock.Alloc() 275 preemptedAllocDiff := &structs.Allocation{ 276 ID: preemptedAlloc.ID, 277 PreemptedByAllocation: alloc.ID, 278 } 279 s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID)) 280 s1.State().UpsertAllocs(1100, []*structs.Allocation{stoppedAlloc, preemptedAlloc}) 281 // Create an eval 282 eval := mock.Eval() 283 eval.JobID = alloc.JobID 284 if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil { 285 t.Fatalf("err: %v", err) 286 } 287 288 timestampBeforeCommit := time.Now().UTC().UnixNano() 289 planRes := &structs.PlanResult{ 290 NodeAllocation: map[string][]*structs.Allocation{ 291 node.ID: {alloc}, 292 }, 293 NodeUpdate: map[string][]*structs.Allocation{ 294 stoppedAlloc.NodeID: {stoppedAllocDiff}, 295 }, 296 NodePreemptions: map[string][]*structs.Allocation{ 297 preemptedAlloc.NodeID: {preemptedAllocDiff}, 298 }, 299 Deployment: dnew, 300 DeploymentUpdates: updates, 301 } 302 303 // Snapshot the state 304 snap, err := s1.State().Snapshot() 305 if err != nil { 306 t.Fatalf("err: %v", err) 307 } 308 309 // Create the plan with a deployment 310 plan := &structs.Plan{ 311 Job: alloc.Job, 312 Deployment: dnew, 313 DeploymentUpdates: updates, 314 EvalID: eval.ID, 315 } 316 317 require := require.New(t) 318 assert := assert.New(t) 319 320 // Apply the plan 321 future, err := s1.applyPlan(plan, planRes, snap) 322 require.NoError(err) 323 324 // Verify our optimistic snapshot is updated 325 ws := memdb.NewWatchSet() 326 allocOut, err := snap.AllocByID(ws, alloc.ID) 327 require.NoError(err) 328 require.NotNil(allocOut) 329 330 deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID) 331 require.NoError(err) 332 require.NotNil(deploymentOut) 333 334 // Check plan does apply cleanly 335 index, err := planWaitFuture(future) 336 require.NoError(err) 337 assert.NotEqual(0, index) 338 339 // Lookup the allocation 340 fsmState := s1.fsm.State() 341 allocOut, err = fsmState.AllocByID(ws, alloc.ID) 342 require.NoError(err) 343 require.NotNil(allocOut) 344 assert.True(allocOut.CreateTime > 0) 345 assert.True(allocOut.ModifyTime > 0) 346 assert.Equal(allocOut.CreateTime, allocOut.ModifyTime) 347 348 // Verify stopped alloc diff applied cleanly 349 updatedStoppedAlloc, err := fsmState.AllocByID(ws, stoppedAlloc.ID) 350 require.NoError(err) 351 require.NotNil(updatedStoppedAlloc) 352 assert.True(updatedStoppedAlloc.ModifyTime > timestampBeforeCommit) 353 assert.Equal(updatedStoppedAlloc.DesiredDescription, stoppedAllocDiff.DesiredDescription) 354 assert.Equal(updatedStoppedAlloc.ClientStatus, stoppedAllocDiff.ClientStatus) 355 assert.Equal(updatedStoppedAlloc.DesiredStatus, structs.AllocDesiredStatusStop) 356 357 // Verify preempted alloc diff applied cleanly 358 updatedPreemptedAlloc, err := fsmState.AllocByID(ws, preemptedAlloc.ID) 359 require.NoError(err) 360 require.NotNil(updatedPreemptedAlloc) 361 assert.True(updatedPreemptedAlloc.ModifyTime > timestampBeforeCommit) 362 assert.Equal(updatedPreemptedAlloc.DesiredDescription, 363 "Preempted by alloc ID "+preemptedAllocDiff.PreemptedByAllocation) 364 assert.Equal(updatedPreemptedAlloc.DesiredStatus, structs.AllocDesiredStatusEvict) 365 366 // Lookup the new deployment 367 dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID) 368 require.NoError(err) 369 require.NotNil(dout) 370 371 // Lookup the updated deployment 372 dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID) 373 require.NoError(err) 374 require.NotNil(dout2) 375 assert.Equal(desiredStatus, dout2.Status) 376 assert.Equal(desiredStatusDescription, dout2.StatusDescription) 377 378 // Lookup updated eval 379 evalOut, err := fsmState.EvalByID(ws, eval.ID) 380 require.NoError(err) 381 require.NotNil(evalOut) 382 assert.Equal(index, evalOut.ModifyIndex) 383 } 384 385 func TestPlanApply_EvalPlan_Simple(t *testing.T) { 386 t.Parallel() 387 state := testStateStore(t) 388 node := mock.Node() 389 state.UpsertNode(1000, node) 390 snap, _ := state.Snapshot() 391 392 alloc := mock.Alloc() 393 plan := &structs.Plan{ 394 Job: alloc.Job, 395 NodeAllocation: map[string][]*structs.Allocation{ 396 node.ID: {alloc}, 397 }, 398 Deployment: mock.Deployment(), 399 DeploymentUpdates: []*structs.DeploymentStatusUpdate{ 400 { 401 DeploymentID: uuid.Generate(), 402 Status: "foo", 403 StatusDescription: "bar", 404 }, 405 }, 406 } 407 408 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 409 defer pool.Shutdown() 410 411 result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t)) 412 if err != nil { 413 t.Fatalf("err: %v", err) 414 } 415 if result == nil { 416 t.Fatalf("missing result") 417 } 418 if !reflect.DeepEqual(result.NodeAllocation, plan.NodeAllocation) { 419 t.Fatalf("incorrect node allocations") 420 } 421 if !reflect.DeepEqual(result.Deployment, plan.Deployment) { 422 t.Fatalf("incorrect deployment") 423 } 424 if !reflect.DeepEqual(result.DeploymentUpdates, plan.DeploymentUpdates) { 425 t.Fatalf("incorrect deployment updates") 426 } 427 } 428 429 func TestPlanApply_EvalPlan_Preemption(t *testing.T) { 430 t.Parallel() 431 state := testStateStore(t) 432 node := mock.Node() 433 node.NodeResources = &structs.NodeResources{ 434 Cpu: structs.NodeCpuResources{ 435 CpuShares: 2000, 436 }, 437 Memory: structs.NodeMemoryResources{ 438 MemoryMB: 4192, 439 }, 440 Disk: structs.NodeDiskResources{ 441 DiskMB: 30 * 1024, 442 }, 443 Networks: []*structs.NetworkResource{ 444 { 445 Device: "eth0", 446 CIDR: "192.168.0.100/32", 447 MBits: 1000, 448 }, 449 }, 450 } 451 state.UpsertNode(1000, node) 452 453 preemptedAlloc := mock.Alloc() 454 preemptedAlloc.NodeID = node.ID 455 preemptedAlloc.AllocatedResources = &structs.AllocatedResources{ 456 Shared: structs.AllocatedSharedResources{ 457 DiskMB: 25 * 1024, 458 }, 459 Tasks: map[string]*structs.AllocatedTaskResources{ 460 "web": { 461 Cpu: structs.AllocatedCpuResources{ 462 CpuShares: 1500, 463 }, 464 Memory: structs.AllocatedMemoryResources{ 465 MemoryMB: 4000, 466 }, 467 Networks: []*structs.NetworkResource{ 468 { 469 Device: "eth0", 470 IP: "192.168.0.100", 471 ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}}, 472 MBits: 800, 473 DynamicPorts: []structs.Port{{Label: "http", Value: 9876}}, 474 }, 475 }, 476 }, 477 }, 478 } 479 480 // Insert a preempted alloc such that the alloc will fit only after preemption 481 state.UpsertAllocs(1001, []*structs.Allocation{preemptedAlloc}) 482 483 alloc := mock.Alloc() 484 alloc.AllocatedResources = &structs.AllocatedResources{ 485 Shared: structs.AllocatedSharedResources{ 486 DiskMB: 24 * 1024, 487 }, 488 Tasks: map[string]*structs.AllocatedTaskResources{ 489 "web": { 490 Cpu: structs.AllocatedCpuResources{ 491 CpuShares: 1500, 492 }, 493 Memory: structs.AllocatedMemoryResources{ 494 MemoryMB: 3200, 495 }, 496 Networks: []*structs.NetworkResource{ 497 { 498 Device: "eth0", 499 IP: "192.168.0.100", 500 ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}}, 501 MBits: 800, 502 DynamicPorts: []structs.Port{{Label: "http", Value: 9876}}, 503 }, 504 }, 505 }, 506 }, 507 } 508 plan := &structs.Plan{ 509 Job: alloc.Job, 510 NodeAllocation: map[string][]*structs.Allocation{ 511 node.ID: {alloc}, 512 }, 513 NodePreemptions: map[string][]*structs.Allocation{ 514 node.ID: {preemptedAlloc}, 515 }, 516 Deployment: mock.Deployment(), 517 DeploymentUpdates: []*structs.DeploymentStatusUpdate{ 518 { 519 DeploymentID: uuid.Generate(), 520 Status: "foo", 521 StatusDescription: "bar", 522 }, 523 }, 524 } 525 snap, _ := state.Snapshot() 526 527 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 528 defer pool.Shutdown() 529 530 result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t)) 531 532 require := require.New(t) 533 require.NoError(err) 534 require.NotNil(result) 535 536 require.Equal(result.NodeAllocation, plan.NodeAllocation) 537 require.Equal(result.Deployment, plan.Deployment) 538 require.Equal(result.DeploymentUpdates, plan.DeploymentUpdates) 539 require.Equal(result.NodePreemptions, plan.NodePreemptions) 540 541 } 542 543 func TestPlanApply_EvalPlan_Partial(t *testing.T) { 544 t.Parallel() 545 state := testStateStore(t) 546 node := mock.Node() 547 state.UpsertNode(1000, node) 548 node2 := mock.Node() 549 state.UpsertNode(1001, node2) 550 snap, _ := state.Snapshot() 551 552 alloc := mock.Alloc() 553 alloc2 := mock.Alloc() // Ensure alloc2 does not fit 554 alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources) 555 556 // Create a deployment where the allocs are markeda as canaries 557 d := mock.Deployment() 558 d.TaskGroups["web"].PlacedCanaries = []string{alloc.ID, alloc2.ID} 559 560 plan := &structs.Plan{ 561 Job: alloc.Job, 562 NodeAllocation: map[string][]*structs.Allocation{ 563 node.ID: {alloc}, 564 node2.ID: {alloc2}, 565 }, 566 Deployment: d, 567 } 568 569 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 570 defer pool.Shutdown() 571 572 result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t)) 573 if err != nil { 574 t.Fatalf("err: %v", err) 575 } 576 if result == nil { 577 t.Fatalf("missing result") 578 } 579 580 if _, ok := result.NodeAllocation[node.ID]; !ok { 581 t.Fatalf("should allow alloc") 582 } 583 if _, ok := result.NodeAllocation[node2.ID]; ok { 584 t.Fatalf("should not allow alloc2") 585 } 586 587 // Check the deployment was updated 588 if result.Deployment == nil || len(result.Deployment.TaskGroups) == 0 { 589 t.Fatalf("bad: %v", result.Deployment) 590 } 591 placedCanaries := result.Deployment.TaskGroups["web"].PlacedCanaries 592 if len(placedCanaries) != 1 || placedCanaries[0] != alloc.ID { 593 t.Fatalf("bad: %v", placedCanaries) 594 } 595 596 if result.RefreshIndex != 1001 { 597 t.Fatalf("bad: %d", result.RefreshIndex) 598 } 599 } 600 601 func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) { 602 t.Parallel() 603 state := testStateStore(t) 604 node := mock.Node() 605 state.UpsertNode(1000, node) 606 node2 := mock.Node() 607 state.UpsertNode(1001, node2) 608 snap, _ := state.Snapshot() 609 610 alloc := mock.Alloc() 611 alloc2 := mock.Alloc() // Ensure alloc2 does not fit 612 alloc2.AllocatedResources = structs.NodeResourcesToAllocatedResources(node2.NodeResources) 613 plan := &structs.Plan{ 614 Job: alloc.Job, 615 AllAtOnce: true, // Require all to make progress 616 NodeAllocation: map[string][]*structs.Allocation{ 617 node.ID: {alloc}, 618 node2.ID: {alloc2}, 619 }, 620 Deployment: mock.Deployment(), 621 DeploymentUpdates: []*structs.DeploymentStatusUpdate{ 622 { 623 DeploymentID: uuid.Generate(), 624 Status: "foo", 625 StatusDescription: "bar", 626 }, 627 }, 628 } 629 630 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 631 defer pool.Shutdown() 632 633 result, err := evaluatePlan(pool, snap, plan, testlog.HCLogger(t)) 634 if err != nil { 635 t.Fatalf("err: %v", err) 636 } 637 if result == nil { 638 t.Fatalf("missing result") 639 } 640 641 if len(result.NodeAllocation) != 0 { 642 t.Fatalf("should not alloc: %v", result.NodeAllocation) 643 } 644 if result.RefreshIndex != 1001 { 645 t.Fatalf("bad: %d", result.RefreshIndex) 646 } 647 if result.Deployment != nil || len(result.DeploymentUpdates) != 0 { 648 t.Fatalf("bad: %v", result) 649 } 650 } 651 652 func TestPlanApply_EvalNodePlan_Simple(t *testing.T) { 653 t.Parallel() 654 state := testStateStore(t) 655 node := mock.Node() 656 state.UpsertNode(1000, node) 657 snap, _ := state.Snapshot() 658 659 alloc := mock.Alloc() 660 plan := &structs.Plan{ 661 Job: alloc.Job, 662 NodeAllocation: map[string][]*structs.Allocation{ 663 node.ID: {alloc}, 664 }, 665 } 666 667 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 668 if err != nil { 669 t.Fatalf("err: %v", err) 670 } 671 if !fit { 672 t.Fatalf("bad") 673 } 674 if reason != "" { 675 t.Fatalf("bad") 676 } 677 } 678 679 func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) { 680 t.Parallel() 681 state := testStateStore(t) 682 node := mock.Node() 683 node.Status = structs.NodeStatusInit 684 state.UpsertNode(1000, node) 685 snap, _ := state.Snapshot() 686 687 alloc := mock.Alloc() 688 plan := &structs.Plan{ 689 Job: alloc.Job, 690 NodeAllocation: map[string][]*structs.Allocation{ 691 node.ID: {alloc}, 692 }, 693 } 694 695 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 696 if err != nil { 697 t.Fatalf("err: %v", err) 698 } 699 if fit { 700 t.Fatalf("bad") 701 } 702 if reason == "" { 703 t.Fatalf("bad") 704 } 705 } 706 707 func TestPlanApply_EvalNodePlan_NodeDrain(t *testing.T) { 708 t.Parallel() 709 state := testStateStore(t) 710 node := mock.Node() 711 node.Drain = true 712 state.UpsertNode(1000, node) 713 snap, _ := state.Snapshot() 714 715 alloc := mock.Alloc() 716 plan := &structs.Plan{ 717 Job: alloc.Job, 718 NodeAllocation: map[string][]*structs.Allocation{ 719 node.ID: {alloc}, 720 }, 721 } 722 723 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 724 if err != nil { 725 t.Fatalf("err: %v", err) 726 } 727 if fit { 728 t.Fatalf("bad") 729 } 730 if reason == "" { 731 t.Fatalf("bad") 732 } 733 } 734 735 func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) { 736 t.Parallel() 737 state := testStateStore(t) 738 snap, _ := state.Snapshot() 739 740 nodeID := "12345678-abcd-efab-cdef-123456789abc" 741 alloc := mock.Alloc() 742 plan := &structs.Plan{ 743 Job: alloc.Job, 744 NodeAllocation: map[string][]*structs.Allocation{ 745 nodeID: {alloc}, 746 }, 747 } 748 749 fit, reason, err := evaluateNodePlan(snap, plan, nodeID) 750 if err != nil { 751 t.Fatalf("err: %v", err) 752 } 753 if fit { 754 t.Fatalf("bad") 755 } 756 if reason == "" { 757 t.Fatalf("bad") 758 } 759 } 760 761 func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) { 762 t.Parallel() 763 alloc := mock.Alloc() 764 state := testStateStore(t) 765 node := mock.Node() 766 node.ReservedResources = nil 767 alloc.NodeID = node.ID 768 alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources) 769 state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)) 770 state.UpsertNode(1000, node) 771 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 772 773 alloc2 := mock.Alloc() 774 alloc2.NodeID = node.ID 775 state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID)) 776 777 snap, _ := state.Snapshot() 778 plan := &structs.Plan{ 779 Job: alloc.Job, 780 NodeAllocation: map[string][]*structs.Allocation{ 781 node.ID: {alloc2}, 782 }, 783 } 784 785 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 786 if err != nil { 787 t.Fatalf("err: %v", err) 788 } 789 if fit { 790 t.Fatalf("bad") 791 } 792 if reason == "" { 793 t.Fatalf("bad") 794 } 795 } 796 797 // Test that we detect device oversubscription 798 func TestPlanApply_EvalNodePlan_NodeFull_Device(t *testing.T) { 799 t.Parallel() 800 require := require.New(t) 801 alloc := mock.Alloc() 802 state := testStateStore(t) 803 node := mock.NvidiaNode() 804 node.ReservedResources = nil 805 806 nvidia0 := node.NodeResources.Devices[0].Instances[0].ID 807 808 // Have the allocation use a Nvidia device 809 alloc.NodeID = node.ID 810 alloc.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{ 811 { 812 Type: "gpu", 813 Vendor: "nvidia", 814 Name: "1080ti", 815 DeviceIDs: []string{nvidia0}, 816 }, 817 } 818 819 state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)) 820 state.UpsertNode(1000, node) 821 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 822 823 // Alloc2 tries to use the same device 824 alloc2 := mock.Alloc() 825 alloc2.AllocatedResources.Tasks["web"].Networks = nil 826 alloc2.AllocatedResources.Tasks["web"].Devices = []*structs.AllocatedDeviceResource{ 827 { 828 Type: "gpu", 829 Vendor: "nvidia", 830 Name: "1080ti", 831 DeviceIDs: []string{nvidia0}, 832 }, 833 } 834 alloc2.NodeID = node.ID 835 state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID)) 836 837 snap, _ := state.Snapshot() 838 plan := &structs.Plan{ 839 Job: alloc.Job, 840 NodeAllocation: map[string][]*structs.Allocation{ 841 node.ID: {alloc2}, 842 }, 843 } 844 845 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 846 require.NoError(err) 847 require.False(fit) 848 require.Equal("device oversubscribed", reason) 849 } 850 851 func TestPlanApply_EvalNodePlan_UpdateExisting(t *testing.T) { 852 t.Parallel() 853 alloc := mock.Alloc() 854 state := testStateStore(t) 855 node := mock.Node() 856 node.ReservedResources = nil 857 node.Reserved = nil 858 alloc.NodeID = node.ID 859 alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources) 860 state.UpsertNode(1000, node) 861 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 862 snap, _ := state.Snapshot() 863 864 plan := &structs.Plan{ 865 Job: alloc.Job, 866 NodeAllocation: map[string][]*structs.Allocation{ 867 node.ID: {alloc}, 868 }, 869 } 870 871 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 872 if err != nil { 873 t.Fatalf("err: %v", err) 874 } 875 if !fit { 876 t.Fatalf("bad") 877 } 878 if reason != "" { 879 t.Fatalf("bad") 880 } 881 } 882 883 func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) { 884 t.Parallel() 885 alloc := mock.Alloc() 886 state := testStateStore(t) 887 node := mock.Node() 888 node.ReservedResources = nil 889 alloc.NodeID = node.ID 890 alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources) 891 state.UpsertNode(1000, node) 892 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 893 snap, _ := state.Snapshot() 894 895 allocEvict := new(structs.Allocation) 896 *allocEvict = *alloc 897 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 898 alloc2 := mock.Alloc() 899 plan := &structs.Plan{ 900 Job: alloc.Job, 901 NodeUpdate: map[string][]*structs.Allocation{ 902 node.ID: {allocEvict}, 903 }, 904 NodeAllocation: map[string][]*structs.Allocation{ 905 node.ID: {alloc2}, 906 }, 907 } 908 909 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 910 if err != nil { 911 t.Fatalf("err: %v", err) 912 } 913 if !fit { 914 t.Fatalf("bad") 915 } 916 if reason != "" { 917 t.Fatalf("bad") 918 } 919 } 920 921 func TestPlanApply_EvalNodePlan_NodeFull_AllocEvict(t *testing.T) { 922 t.Parallel() 923 alloc := mock.Alloc() 924 state := testStateStore(t) 925 node := mock.Node() 926 node.ReservedResources = nil 927 alloc.NodeID = node.ID 928 alloc.DesiredStatus = structs.AllocDesiredStatusEvict 929 alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources) 930 state.UpsertNode(1000, node) 931 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 932 snap, _ := state.Snapshot() 933 934 alloc2 := mock.Alloc() 935 plan := &structs.Plan{ 936 Job: alloc.Job, 937 NodeAllocation: map[string][]*structs.Allocation{ 938 node.ID: {alloc2}, 939 }, 940 } 941 942 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 943 if err != nil { 944 t.Fatalf("err: %v", err) 945 } 946 if !fit { 947 t.Fatalf("bad") 948 } 949 if reason != "" { 950 t.Fatalf("bad") 951 } 952 } 953 954 func TestPlanApply_EvalNodePlan_NodeDown_EvictOnly(t *testing.T) { 955 t.Parallel() 956 alloc := mock.Alloc() 957 state := testStateStore(t) 958 node := mock.Node() 959 alloc.NodeID = node.ID 960 alloc.AllocatedResources = structs.NodeResourcesToAllocatedResources(node.NodeResources) 961 node.ReservedResources = nil 962 node.Status = structs.NodeStatusDown 963 state.UpsertNode(1000, node) 964 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 965 snap, _ := state.Snapshot() 966 967 allocEvict := new(structs.Allocation) 968 *allocEvict = *alloc 969 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 970 plan := &structs.Plan{ 971 Job: alloc.Job, 972 NodeUpdate: map[string][]*structs.Allocation{ 973 node.ID: {allocEvict}, 974 }, 975 } 976 977 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 978 if err != nil { 979 t.Fatalf("err: %v", err) 980 } 981 if !fit { 982 t.Fatalf("bad") 983 } 984 if reason != "" { 985 t.Fatalf("bad") 986 } 987 }