github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/plan_apply_test.go (about) 1 package nomad 2 3 import ( 4 "reflect" 5 "testing" 6 7 memdb "github.com/hashicorp/go-memdb" 8 "github.com/hashicorp/nomad/helper/testlog" 9 "github.com/hashicorp/nomad/helper/uuid" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/hashicorp/nomad/testutil" 13 "github.com/hashicorp/raft" 14 "github.com/stretchr/testify/assert" 15 ) 16 17 const ( 18 // workerPoolSize is the size of the worker pool 19 workerPoolSize = 2 20 ) 21 22 // planWaitFuture is used to wait for the Raft future to complete 23 func planWaitFuture(future raft.ApplyFuture) (uint64, error) { 24 if err := future.Error(); err != nil { 25 return 0, err 26 } 27 return future.Index(), nil 28 } 29 30 func testRegisterNode(t *testing.T, s *Server, n *structs.Node) { 31 // Create the register request 32 req := &structs.NodeRegisterRequest{ 33 Node: n, 34 WriteRequest: structs.WriteRequest{Region: "global"}, 35 } 36 37 // Fetch the response 38 var resp structs.NodeUpdateResponse 39 if err := s.RPC("Node.Register", req, &resp); err != nil { 40 t.Fatalf("err: %v", err) 41 } 42 if resp.Index == 0 { 43 t.Fatalf("bad index: %d", resp.Index) 44 } 45 } 46 47 func testRegisterJob(t *testing.T, s *Server, j *structs.Job) { 48 // Create the register request 49 req := &structs.JobRegisterRequest{ 50 Job: j, 51 WriteRequest: structs.WriteRequest{Region: "global"}, 52 } 53 54 // Fetch the response 55 var resp structs.JobRegisterResponse 56 if err := s.RPC("Job.Register", req, &resp); err != nil { 57 t.Fatalf("err: %v", err) 58 } 59 if resp.Index == 0 { 60 t.Fatalf("bad index: %d", resp.Index) 61 } 62 } 63 64 func TestPlanApply_applyPlan(t *testing.T) { 65 t.Parallel() 66 s1 := TestServer(t, nil) 67 defer s1.Shutdown() 68 testutil.WaitForLeader(t, s1.RPC) 69 70 // Register node 71 node := mock.Node() 72 testRegisterNode(t, s1, node) 73 74 // Register a fake deployment 75 oldDeployment := mock.Deployment() 76 if err := s1.State().UpsertDeployment(900, oldDeployment); err != nil { 77 t.Fatalf("UpsertDeployment failed: %v", err) 78 } 79 80 // Create a deployment 81 dnew := mock.Deployment() 82 83 // Create a deployment update for the old deployment id 84 desiredStatus, desiredStatusDescription := "foo", "bar" 85 updates := []*structs.DeploymentStatusUpdate{ 86 { 87 DeploymentID: oldDeployment.ID, 88 Status: desiredStatus, 89 StatusDescription: desiredStatusDescription, 90 }, 91 } 92 93 // Register alloc, deployment and deployment update 94 alloc := mock.Alloc() 95 s1.State().UpsertJobSummary(1000, mock.JobSummary(alloc.JobID)) 96 // Create an eval 97 eval := mock.Eval() 98 eval.JobID = alloc.JobID 99 if err := s1.State().UpsertEvals(1, []*structs.Evaluation{eval}); err != nil { 100 t.Fatalf("err: %v", err) 101 } 102 103 planRes := &structs.PlanResult{ 104 NodeAllocation: map[string][]*structs.Allocation{ 105 node.ID: {alloc}, 106 }, 107 Deployment: dnew, 108 DeploymentUpdates: updates, 109 } 110 111 // Snapshot the state 112 snap, err := s1.State().Snapshot() 113 if err != nil { 114 t.Fatalf("err: %v", err) 115 } 116 117 // Create the plan with a deployment 118 plan := &structs.Plan{ 119 Job: alloc.Job, 120 Deployment: dnew, 121 DeploymentUpdates: updates, 122 EvalID: eval.ID, 123 } 124 125 // Apply the plan 126 future, err := s1.applyPlan(plan, planRes, snap) 127 assert := assert.New(t) 128 assert.Nil(err) 129 130 // Verify our optimistic snapshot is updated 131 ws := memdb.NewWatchSet() 132 allocOut, err := snap.AllocByID(ws, alloc.ID) 133 assert.Nil(err) 134 assert.NotNil(allocOut) 135 136 deploymentOut, err := snap.DeploymentByID(ws, plan.Deployment.ID) 137 assert.Nil(err) 138 assert.NotNil(deploymentOut) 139 140 // Check plan does apply cleanly 141 index, err := planWaitFuture(future) 142 assert.Nil(err) 143 assert.NotEqual(0, index) 144 145 // Lookup the allocation 146 fsmState := s1.fsm.State() 147 allocOut, err = fsmState.AllocByID(ws, alloc.ID) 148 assert.Nil(err) 149 assert.NotNil(allocOut) 150 assert.True(allocOut.CreateTime > 0) 151 assert.True(allocOut.ModifyTime > 0) 152 assert.Equal(allocOut.CreateTime, allocOut.ModifyTime) 153 154 // Lookup the new deployment 155 dout, err := fsmState.DeploymentByID(ws, plan.Deployment.ID) 156 assert.Nil(err) 157 assert.NotNil(dout) 158 159 // Lookup the updated deployment 160 dout2, err := fsmState.DeploymentByID(ws, oldDeployment.ID) 161 assert.Nil(err) 162 assert.NotNil(dout2) 163 assert.Equal(desiredStatus, dout2.Status) 164 assert.Equal(desiredStatusDescription, dout2.StatusDescription) 165 166 // Lookup updated eval 167 evalOut, err := fsmState.EvalByID(ws, eval.ID) 168 assert.Nil(err) 169 assert.NotNil(evalOut) 170 assert.Equal(index, evalOut.ModifyIndex) 171 172 // Evict alloc, Register alloc2 173 allocEvict := new(structs.Allocation) 174 *allocEvict = *alloc 175 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 176 job := allocEvict.Job 177 allocEvict.Job = nil 178 alloc2 := mock.Alloc() 179 s1.State().UpsertJobSummary(1500, mock.JobSummary(alloc2.JobID)) 180 planRes = &structs.PlanResult{ 181 NodeUpdate: map[string][]*structs.Allocation{ 182 node.ID: {allocEvict}, 183 }, 184 NodeAllocation: map[string][]*structs.Allocation{ 185 node.ID: {alloc2}, 186 }, 187 } 188 189 // Snapshot the state 190 snap, err = s1.State().Snapshot() 191 assert.Nil(err) 192 193 // Apply the plan 194 plan = &structs.Plan{ 195 Job: job, 196 EvalID: eval.ID, 197 } 198 future, err = s1.applyPlan(plan, planRes, snap) 199 assert.Nil(err) 200 201 // Check that our optimistic view is updated 202 out, _ := snap.AllocByID(ws, allocEvict.ID) 203 assert.Equal(structs.AllocDesiredStatusEvict, out.DesiredStatus) 204 205 // Verify plan applies cleanly 206 index, err = planWaitFuture(future) 207 assert.Nil(err) 208 assert.NotEqual(0, index) 209 210 // Lookup the allocation 211 allocOut, err = s1.fsm.State().AllocByID(ws, alloc.ID) 212 assert.Nil(err) 213 assert.Equal(structs.AllocDesiredStatusEvict, allocOut.DesiredStatus) 214 assert.NotNil(allocOut.Job) 215 assert.True(allocOut.ModifyTime > 0) 216 217 // Lookup the allocation 218 allocOut, err = s1.fsm.State().AllocByID(ws, alloc2.ID) 219 assert.Nil(err) 220 assert.NotNil(allocOut) 221 assert.NotNil(allocOut.Job) 222 223 // Lookup updated eval 224 evalOut, err = fsmState.EvalByID(ws, eval.ID) 225 assert.Nil(err) 226 assert.NotNil(evalOut) 227 assert.Equal(index, evalOut.ModifyIndex) 228 } 229 230 func TestPlanApply_EvalPlan_Simple(t *testing.T) { 231 t.Parallel() 232 state := testStateStore(t) 233 node := mock.Node() 234 state.UpsertNode(1000, node) 235 snap, _ := state.Snapshot() 236 237 alloc := mock.Alloc() 238 plan := &structs.Plan{ 239 Job: alloc.Job, 240 NodeAllocation: map[string][]*structs.Allocation{ 241 node.ID: {alloc}, 242 }, 243 Deployment: mock.Deployment(), 244 DeploymentUpdates: []*structs.DeploymentStatusUpdate{ 245 { 246 DeploymentID: uuid.Generate(), 247 Status: "foo", 248 StatusDescription: "bar", 249 }, 250 }, 251 } 252 253 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 254 defer pool.Shutdown() 255 256 result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t)) 257 if err != nil { 258 t.Fatalf("err: %v", err) 259 } 260 if result == nil { 261 t.Fatalf("missing result") 262 } 263 if !reflect.DeepEqual(result.NodeAllocation, plan.NodeAllocation) { 264 t.Fatalf("incorrect node allocations") 265 } 266 if !reflect.DeepEqual(result.Deployment, plan.Deployment) { 267 t.Fatalf("incorrect deployment") 268 } 269 if !reflect.DeepEqual(result.DeploymentUpdates, plan.DeploymentUpdates) { 270 t.Fatalf("incorrect deployment updates") 271 } 272 } 273 274 func TestPlanApply_EvalPlan_Partial(t *testing.T) { 275 t.Parallel() 276 state := testStateStore(t) 277 node := mock.Node() 278 state.UpsertNode(1000, node) 279 node2 := mock.Node() 280 state.UpsertNode(1001, node2) 281 snap, _ := state.Snapshot() 282 283 alloc := mock.Alloc() 284 alloc2 := mock.Alloc() // Ensure alloc2 does not fit 285 alloc2.Resources = node2.Resources 286 287 // Create a deployment where the allocs are markeda as canaries 288 d := mock.Deployment() 289 d.TaskGroups["web"].PlacedCanaries = []string{alloc.ID, alloc2.ID} 290 291 plan := &structs.Plan{ 292 Job: alloc.Job, 293 NodeAllocation: map[string][]*structs.Allocation{ 294 node.ID: {alloc}, 295 node2.ID: {alloc2}, 296 }, 297 Deployment: d, 298 } 299 300 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 301 defer pool.Shutdown() 302 303 result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t)) 304 if err != nil { 305 t.Fatalf("err: %v", err) 306 } 307 if result == nil { 308 t.Fatalf("missing result") 309 } 310 311 if _, ok := result.NodeAllocation[node.ID]; !ok { 312 t.Fatalf("should allow alloc") 313 } 314 if _, ok := result.NodeAllocation[node2.ID]; ok { 315 t.Fatalf("should not allow alloc2") 316 } 317 318 // Check the deployment was updated 319 if result.Deployment == nil || len(result.Deployment.TaskGroups) == 0 { 320 t.Fatalf("bad: %v", result.Deployment) 321 } 322 placedCanaries := result.Deployment.TaskGroups["web"].PlacedCanaries 323 if len(placedCanaries) != 1 || placedCanaries[0] != alloc.ID { 324 t.Fatalf("bad: %v", placedCanaries) 325 } 326 327 if result.RefreshIndex != 1001 { 328 t.Fatalf("bad: %d", result.RefreshIndex) 329 } 330 } 331 332 func TestPlanApply_EvalPlan_Partial_AllAtOnce(t *testing.T) { 333 t.Parallel() 334 state := testStateStore(t) 335 node := mock.Node() 336 state.UpsertNode(1000, node) 337 node2 := mock.Node() 338 state.UpsertNode(1001, node2) 339 snap, _ := state.Snapshot() 340 341 alloc := mock.Alloc() 342 alloc2 := mock.Alloc() // Ensure alloc2 does not fit 343 alloc2.Resources = node2.Resources 344 plan := &structs.Plan{ 345 Job: alloc.Job, 346 AllAtOnce: true, // Require all to make progress 347 NodeAllocation: map[string][]*structs.Allocation{ 348 node.ID: {alloc}, 349 node2.ID: {alloc2}, 350 }, 351 Deployment: mock.Deployment(), 352 DeploymentUpdates: []*structs.DeploymentStatusUpdate{ 353 { 354 DeploymentID: uuid.Generate(), 355 Status: "foo", 356 StatusDescription: "bar", 357 }, 358 }, 359 } 360 361 pool := NewEvaluatePool(workerPoolSize, workerPoolBufferSize) 362 defer pool.Shutdown() 363 364 result, err := evaluatePlan(pool, snap, plan, testlog.Logger(t)) 365 if err != nil { 366 t.Fatalf("err: %v", err) 367 } 368 if result == nil { 369 t.Fatalf("missing result") 370 } 371 372 if len(result.NodeAllocation) != 0 { 373 t.Fatalf("should not alloc: %v", result.NodeAllocation) 374 } 375 if result.RefreshIndex != 1001 { 376 t.Fatalf("bad: %d", result.RefreshIndex) 377 } 378 if result.Deployment != nil || len(result.DeploymentUpdates) != 0 { 379 t.Fatalf("bad: %v", result) 380 } 381 } 382 383 func TestPlanApply_EvalNodePlan_Simple(t *testing.T) { 384 t.Parallel() 385 state := testStateStore(t) 386 node := mock.Node() 387 state.UpsertNode(1000, node) 388 snap, _ := state.Snapshot() 389 390 alloc := mock.Alloc() 391 plan := &structs.Plan{ 392 Job: alloc.Job, 393 NodeAllocation: map[string][]*structs.Allocation{ 394 node.ID: {alloc}, 395 }, 396 } 397 398 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 399 if err != nil { 400 t.Fatalf("err: %v", err) 401 } 402 if !fit { 403 t.Fatalf("bad") 404 } 405 if reason != "" { 406 t.Fatalf("bad") 407 } 408 } 409 410 func TestPlanApply_EvalNodePlan_NodeNotReady(t *testing.T) { 411 t.Parallel() 412 state := testStateStore(t) 413 node := mock.Node() 414 node.Status = structs.NodeStatusInit 415 state.UpsertNode(1000, node) 416 snap, _ := state.Snapshot() 417 418 alloc := mock.Alloc() 419 plan := &structs.Plan{ 420 Job: alloc.Job, 421 NodeAllocation: map[string][]*structs.Allocation{ 422 node.ID: {alloc}, 423 }, 424 } 425 426 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 427 if err != nil { 428 t.Fatalf("err: %v", err) 429 } 430 if fit { 431 t.Fatalf("bad") 432 } 433 if reason == "" { 434 t.Fatalf("bad") 435 } 436 } 437 438 func TestPlanApply_EvalNodePlan_NodeDrain(t *testing.T) { 439 t.Parallel() 440 state := testStateStore(t) 441 node := mock.Node() 442 node.Drain = true 443 state.UpsertNode(1000, node) 444 snap, _ := state.Snapshot() 445 446 alloc := mock.Alloc() 447 plan := &structs.Plan{ 448 Job: alloc.Job, 449 NodeAllocation: map[string][]*structs.Allocation{ 450 node.ID: {alloc}, 451 }, 452 } 453 454 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 455 if err != nil { 456 t.Fatalf("err: %v", err) 457 } 458 if fit { 459 t.Fatalf("bad") 460 } 461 if reason == "" { 462 t.Fatalf("bad") 463 } 464 } 465 466 func TestPlanApply_EvalNodePlan_NodeNotExist(t *testing.T) { 467 t.Parallel() 468 state := testStateStore(t) 469 snap, _ := state.Snapshot() 470 471 nodeID := "12345678-abcd-efab-cdef-123456789abc" 472 alloc := mock.Alloc() 473 plan := &structs.Plan{ 474 Job: alloc.Job, 475 NodeAllocation: map[string][]*structs.Allocation{ 476 nodeID: {alloc}, 477 }, 478 } 479 480 fit, reason, err := evaluateNodePlan(snap, plan, nodeID) 481 if err != nil { 482 t.Fatalf("err: %v", err) 483 } 484 if fit { 485 t.Fatalf("bad") 486 } 487 if reason == "" { 488 t.Fatalf("bad") 489 } 490 } 491 492 func TestPlanApply_EvalNodePlan_NodeFull(t *testing.T) { 493 t.Parallel() 494 alloc := mock.Alloc() 495 state := testStateStore(t) 496 node := mock.Node() 497 alloc.NodeID = node.ID 498 node.Resources = alloc.Resources 499 node.Reserved = nil 500 state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)) 501 state.UpsertNode(1000, node) 502 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 503 504 alloc2 := mock.Alloc() 505 alloc2.NodeID = node.ID 506 state.UpsertJobSummary(1200, mock.JobSummary(alloc2.JobID)) 507 508 snap, _ := state.Snapshot() 509 plan := &structs.Plan{ 510 Job: alloc.Job, 511 NodeAllocation: map[string][]*structs.Allocation{ 512 node.ID: {alloc2}, 513 }, 514 } 515 516 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 517 if err != nil { 518 t.Fatalf("err: %v", err) 519 } 520 if fit { 521 t.Fatalf("bad") 522 } 523 if reason == "" { 524 t.Fatalf("bad") 525 } 526 } 527 528 func TestPlanApply_EvalNodePlan_UpdateExisting(t *testing.T) { 529 t.Parallel() 530 alloc := mock.Alloc() 531 state := testStateStore(t) 532 node := mock.Node() 533 alloc.NodeID = node.ID 534 node.Resources = alloc.Resources 535 node.Reserved = nil 536 state.UpsertNode(1000, node) 537 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 538 snap, _ := state.Snapshot() 539 540 plan := &structs.Plan{ 541 Job: alloc.Job, 542 NodeAllocation: map[string][]*structs.Allocation{ 543 node.ID: {alloc}, 544 }, 545 } 546 547 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 548 if err != nil { 549 t.Fatalf("err: %v", err) 550 } 551 if !fit { 552 t.Fatalf("bad") 553 } 554 if reason != "" { 555 t.Fatalf("bad") 556 } 557 } 558 559 func TestPlanApply_EvalNodePlan_NodeFull_Evict(t *testing.T) { 560 t.Parallel() 561 alloc := mock.Alloc() 562 state := testStateStore(t) 563 node := mock.Node() 564 alloc.NodeID = node.ID 565 node.Resources = alloc.Resources 566 node.Reserved = nil 567 state.UpsertNode(1000, node) 568 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 569 snap, _ := state.Snapshot() 570 571 allocEvict := new(structs.Allocation) 572 *allocEvict = *alloc 573 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 574 alloc2 := mock.Alloc() 575 plan := &structs.Plan{ 576 Job: alloc.Job, 577 NodeUpdate: map[string][]*structs.Allocation{ 578 node.ID: {allocEvict}, 579 }, 580 NodeAllocation: map[string][]*structs.Allocation{ 581 node.ID: {alloc2}, 582 }, 583 } 584 585 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 586 if err != nil { 587 t.Fatalf("err: %v", err) 588 } 589 if !fit { 590 t.Fatalf("bad") 591 } 592 if reason != "" { 593 t.Fatalf("bad") 594 } 595 } 596 597 func TestPlanApply_EvalNodePlan_NodeFull_AllocEvict(t *testing.T) { 598 t.Parallel() 599 alloc := mock.Alloc() 600 state := testStateStore(t) 601 node := mock.Node() 602 alloc.NodeID = node.ID 603 alloc.DesiredStatus = structs.AllocDesiredStatusEvict 604 node.Resources = alloc.Resources 605 node.Reserved = nil 606 state.UpsertNode(1000, node) 607 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 608 snap, _ := state.Snapshot() 609 610 alloc2 := mock.Alloc() 611 plan := &structs.Plan{ 612 Job: alloc.Job, 613 NodeAllocation: map[string][]*structs.Allocation{ 614 node.ID: {alloc2}, 615 }, 616 } 617 618 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 619 if err != nil { 620 t.Fatalf("err: %v", err) 621 } 622 if !fit { 623 t.Fatalf("bad") 624 } 625 if reason != "" { 626 t.Fatalf("bad") 627 } 628 } 629 630 func TestPlanApply_EvalNodePlan_NodeDown_EvictOnly(t *testing.T) { 631 t.Parallel() 632 alloc := mock.Alloc() 633 state := testStateStore(t) 634 node := mock.Node() 635 alloc.NodeID = node.ID 636 node.Resources = alloc.Resources 637 node.Reserved = nil 638 node.Status = structs.NodeStatusDown 639 state.UpsertNode(1000, node) 640 state.UpsertAllocs(1001, []*structs.Allocation{alloc}) 641 snap, _ := state.Snapshot() 642 643 allocEvict := new(structs.Allocation) 644 *allocEvict = *alloc 645 allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict 646 plan := &structs.Plan{ 647 Job: alloc.Job, 648 NodeUpdate: map[string][]*structs.Allocation{ 649 node.ID: {allocEvict}, 650 }, 651 } 652 653 fit, reason, err := evaluateNodePlan(snap, plan, node.ID) 654 if err != nil { 655 t.Fatalf("err: %v", err) 656 } 657 if !fit { 658 t.Fatalf("bad") 659 } 660 if reason != "" { 661 t.Fatalf("bad") 662 } 663 }