// inplaceUpdate attempts to update allocations in-place where possible. func inplaceUpdate(ctx Context, eval *structs.Evaluation, job *structs.Job, stack Stack, updates []allocTuple) []allocTuple { n := len(updates) inplace := 0 for i := 0; i < n; i++ { // Get the update update := updates[i] // Check if the task drivers or config has changed, requires // a rolling upgrade since that cannot be done in-place. existing := update.Alloc.Job.LookupTaskGroup(update.TaskGroup.Name) if tasksUpdated(update.TaskGroup, existing) { continue } // Get the existing node node, err := ctx.State().NodeByID(update.Alloc.NodeID) if err != nil { ctx.Logger().Printf("[ERR] sched: %#v failed to get node '%s': %v", eval, update.Alloc.NodeID, err) continue } if node == nil { continue } // Set the existing node as the base set stack.SetNodes([]*structs.Node{node}) // Stage an eviction of the current allocation. This is done so that // the current allocation is discounted when checking for feasability. // Otherwise we would be trying to fit the tasks current resources and // updated resources. After select is called we can remove the evict. ctx.Plan().AppendUpdate(update.Alloc, structs.AllocDesiredStatusStop, allocInPlace) // Attempt to match the task group option, size := stack.Select(update.TaskGroup) // Pop the allocation ctx.Plan().PopUpdate(update.Alloc) // Skip if we could not do an in-place update if option == nil { continue } // Restore the network offers from the existing allocation. // We do not allow network resources (reserved/dynamic ports) // to be updated. This is guarded in taskUpdated, so we can // safely restore those here. for task, resources := range option.TaskResources { existing := update.Alloc.TaskResources[task] resources.Networks = existing.Networks } // Create a shallow copy newAlloc := new(structs.Allocation) *newAlloc = *update.Alloc // Update the allocation newAlloc.EvalID = eval.ID newAlloc.Job = job newAlloc.Resources = size newAlloc.TaskResources = option.TaskResources newAlloc.Metrics = ctx.Metrics() newAlloc.DesiredStatus = structs.AllocDesiredStatusRun newAlloc.ClientStatus = structs.AllocClientStatusPending ctx.Plan().AppendAlloc(newAlloc) // Remove this allocation from the slice updates[i] = updates[n-1] i-- n-- inplace++ } if len(updates) > 0 { ctx.Logger().Printf("[DEBUG] sched: %#v: %d in-place updates of %d", eval, inplace, len(updates)) } return updates[:n] }
func TestPlanApply_applyPlan(t *testing.T) { s1 := testServer(t, nil) defer s1.Shutdown() testutil.WaitForLeader(t, s1.RPC) // Register ndoe node := mock.Node() testRegisterNode(t, s1, node) // Register alloc alloc := mock.Alloc() allocFail := mock.Alloc() plan := &structs.PlanResult{ NodeAllocation: map[string][]*structs.Allocation{ node.ID: []*structs.Allocation{alloc}, }, FailedAllocs: []*structs.Allocation{allocFail}, } // Snapshot the state snap, err := s1.State().Snapshot() if err != nil { t.Fatalf("err: %v", err) } // Apply the plan future, err := s1.applyPlan(alloc.Job, plan, snap) if err != nil { t.Fatalf("err: %v", err) } // Verify our optimistic snapshot is updated if out, err := snap.AllocByID(alloc.ID); err != nil || out == nil { t.Fatalf("bad: %v %v", out, err) } // Check plan does apply cleanly index, err := planWaitFuture(future) if err != nil { t.Fatalf("err: %v", err) } if index == 0 { t.Fatalf("bad: %d", index) } // Lookup the allocation out, err := s1.fsm.State().AllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } if out == nil { t.Fatalf("missing alloc") } // Lookup the allocation out, err = s1.fsm.State().AllocByID(allocFail.ID) if err != nil { t.Fatalf("err: %v", err) } if out == nil { t.Fatalf("missing alloc") } // Evict alloc, Register alloc2 allocEvict := new(structs.Allocation) *allocEvict = *alloc allocEvict.DesiredStatus = structs.AllocDesiredStatusEvict job := allocEvict.Job allocEvict.Job = nil alloc2 := mock.Alloc() alloc2.Job = nil plan = &structs.PlanResult{ NodeUpdate: map[string][]*structs.Allocation{ node.ID: []*structs.Allocation{allocEvict}, }, NodeAllocation: map[string][]*structs.Allocation{ node.ID: []*structs.Allocation{alloc2}, }, } // Snapshot the state snap, err = s1.State().Snapshot() if err != nil { t.Fatalf("err: %v", err) } // Apply the plan future, err = s1.applyPlan(job, plan, snap) if err != nil { t.Fatalf("err: %v", err) } // Check that our optimistic view is updated if out, _ := snap.AllocByID(allocEvict.ID); out.DesiredStatus != structs.AllocDesiredStatusEvict { t.Fatalf("bad: %#v", out) } // Verify plan applies cleanly index, err = planWaitFuture(future) if err != nil { t.Fatalf("err: %v", err) } if index == 0 { t.Fatalf("bad: %d", index) } // Lookup the allocation out, err = s1.fsm.State().AllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } if out.DesiredStatus != structs.AllocDesiredStatusEvict { t.Fatalf("should be evicted alloc: %#v", out) } if out.Job == nil { t.Fatalf("missing job") } // Lookup the allocation out, err = s1.fsm.State().AllocByID(alloc2.ID) if err != nil { t.Fatalf("err: %v", err) } if out == nil { t.Fatalf("missing alloc") } if out.Job == nil { t.Fatalf("missing job") } }
func TestFSM_UpsertAllocs_SharedJob(t *testing.T) { fsm := testFSM(t) alloc := mock.Alloc() fsm.State().UpsertJobSummary(1, mock.JobSummary(alloc.JobID)) job := alloc.Job alloc.Job = nil req := structs.AllocUpdateRequest{ Job: job, Alloc: []*structs.Allocation{alloc}, } buf, err := structs.Encode(structs.AllocUpdateRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered out, err := fsm.State().AllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } alloc.CreateIndex = out.CreateIndex alloc.ModifyIndex = out.ModifyIndex alloc.AllocModifyIndex = out.AllocModifyIndex // Job should be re-attached alloc.Job = job if !reflect.DeepEqual(alloc, out) { t.Fatalf("bad: %#v %#v", alloc, out) } // Ensure that the original job is used evictAlloc := new(structs.Allocation) *evictAlloc = *alloc job = mock.Job() job.Priority = 123 evictAlloc.Job = nil evictAlloc.DesiredStatus = structs.AllocDesiredStatusEvict req2 := structs.AllocUpdateRequest{ Job: job, Alloc: []*structs.Allocation{evictAlloc}, } buf, err = structs.Encode(structs.AllocUpdateRequestType, req2) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are evicted out, err = fsm.State().AllocByID(alloc.ID) if err != nil { t.Fatalf("err: %v", err) } if out.DesiredStatus != structs.AllocDesiredStatusEvict { t.Fatalf("alloc found!") } if out.Job == nil || out.Job.Priority == 123 { t.Fatalf("bad job") } }