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