github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/scheduler/util.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"math/rand"
     7  	"reflect"
     8  
     9  	memdb "github.com/hashicorp/go-memdb"
    10  	"github.com/ncodes/nomad/nomad/structs"
    11  )
    12  
    13  // allocTuple is a tuple of the allocation name and potential alloc ID
    14  type allocTuple struct {
    15  	Name      string
    16  	TaskGroup *structs.TaskGroup
    17  	Alloc     *structs.Allocation
    18  }
    19  
    20  // materializeTaskGroups is used to materialize all the task groups
    21  // a job requires. This is used to do the count expansion.
    22  func materializeTaskGroups(job *structs.Job) map[string]*structs.TaskGroup {
    23  	out := make(map[string]*structs.TaskGroup)
    24  	if job == nil {
    25  		return out
    26  	}
    27  
    28  	for _, tg := range job.TaskGroups {
    29  		for i := 0; i < tg.Count; i++ {
    30  			name := fmt.Sprintf("%s.%s[%d]", job.Name, tg.Name, i)
    31  			out[name] = tg
    32  		}
    33  	}
    34  	return out
    35  }
    36  
    37  // diffResult is used to return the sets that result from the diff
    38  type diffResult struct {
    39  	place, update, migrate, stop, ignore, lost []allocTuple
    40  }
    41  
    42  func (d *diffResult) GoString() string {
    43  	return fmt.Sprintf("allocs: (place %d) (update %d) (migrate %d) (stop %d) (ignore %d) (lost %d)",
    44  		len(d.place), len(d.update), len(d.migrate), len(d.stop), len(d.ignore), len(d.lost))
    45  }
    46  
    47  func (d *diffResult) Append(other *diffResult) {
    48  	d.place = append(d.place, other.place...)
    49  	d.update = append(d.update, other.update...)
    50  	d.migrate = append(d.migrate, other.migrate...)
    51  	d.stop = append(d.stop, other.stop...)
    52  	d.ignore = append(d.ignore, other.ignore...)
    53  	d.lost = append(d.lost, other.lost...)
    54  }
    55  
    56  // diffAllocs is used to do a set difference between the target allocations
    57  // and the existing allocations. This returns 6 sets of results, the list of
    58  // named task groups that need to be placed (no existing allocation), the
    59  // allocations that need to be updated (job definition is newer), allocs that
    60  // need to be migrated (node is draining), the allocs that need to be evicted
    61  // (no longer required), those that should be ignored and those that are lost
    62  // that need to be replaced (running on a lost node).
    63  //
    64  // job is the job whose allocs is going to be diff-ed.
    65  // taintedNodes is an index of the nodes which are either down or in drain mode
    66  // by name.
    67  // required is a set of allocations that must exist.
    68  // allocs is a list of non terminal allocations.
    69  // terminalAllocs is an index of the latest terminal allocations by name.
    70  func diffAllocs(job *structs.Job, taintedNodes map[string]*structs.Node,
    71  	required map[string]*structs.TaskGroup, allocs []*structs.Allocation,
    72  	terminalAllocs map[string]*structs.Allocation) *diffResult {
    73  	result := &diffResult{}
    74  
    75  	// Scan the existing updates
    76  	existing := make(map[string]struct{})
    77  	for _, exist := range allocs {
    78  		// Index the existing node
    79  		name := exist.Name
    80  		existing[name] = struct{}{}
    81  
    82  		// Check for the definition in the required set
    83  		tg, ok := required[name]
    84  
    85  		// If not required, we stop the alloc
    86  		if !ok {
    87  			result.stop = append(result.stop, allocTuple{
    88  				Name:      name,
    89  				TaskGroup: tg,
    90  				Alloc:     exist,
    91  			})
    92  			continue
    93  		}
    94  
    95  		// If we are on a tainted node, we must migrate if we are a service or
    96  		// if the batch allocation did not finish
    97  		if node, ok := taintedNodes[exist.NodeID]; ok {
    98  			// If the job is batch and finished successfully, the fact that the
    99  			// node is tainted does not mean it should be migrated or marked as
   100  			// lost as the work was already successfully finished. However for
   101  			// service/system jobs, tasks should never complete. The check of
   102  			// batch type, defends against client bugs.
   103  			if exist.Job.Type == structs.JobTypeBatch && exist.RanSuccessfully() {
   104  				goto IGNORE
   105  			}
   106  
   107  			if node == nil || node.TerminalStatus() {
   108  				result.lost = append(result.lost, allocTuple{
   109  					Name:      name,
   110  					TaskGroup: tg,
   111  					Alloc:     exist,
   112  				})
   113  			} else {
   114  				// This is the drain case
   115  				result.migrate = append(result.migrate, allocTuple{
   116  					Name:      name,
   117  					TaskGroup: tg,
   118  					Alloc:     exist,
   119  				})
   120  			}
   121  			continue
   122  		}
   123  
   124  		// If the definition is updated we need to update
   125  		if job.JobModifyIndex != exist.Job.JobModifyIndex {
   126  			result.update = append(result.update, allocTuple{
   127  				Name:      name,
   128  				TaskGroup: tg,
   129  				Alloc:     exist,
   130  			})
   131  			continue
   132  		}
   133  
   134  		// Everything is up-to-date
   135  	IGNORE:
   136  		result.ignore = append(result.ignore, allocTuple{
   137  			Name:      name,
   138  			TaskGroup: tg,
   139  			Alloc:     exist,
   140  		})
   141  	}
   142  
   143  	// Scan the required groups
   144  	for name, tg := range required {
   145  		// Check for an existing allocation
   146  		_, ok := existing[name]
   147  
   148  		// Require a placement if no existing allocation. If there
   149  		// is an existing allocation, we would have checked for a potential
   150  		// update or ignore above.
   151  		if !ok {
   152  			result.place = append(result.place, allocTuple{
   153  				Name:      name,
   154  				TaskGroup: tg,
   155  				Alloc:     terminalAllocs[name],
   156  			})
   157  		}
   158  	}
   159  	return result
   160  }
   161  
   162  // diffSystemAllocs is like diffAllocs however, the allocations in the
   163  // diffResult contain the specific nodeID they should be allocated on.
   164  //
   165  // job is the job whose allocs is going to be diff-ed.
   166  // nodes is a list of nodes in ready state.
   167  // taintedNodes is an index of the nodes which are either down or in drain mode
   168  // by name.
   169  // allocs is a list of non terminal allocations.
   170  // terminalAllocs is an index of the latest terminal allocations by name.
   171  func diffSystemAllocs(job *structs.Job, nodes []*structs.Node, taintedNodes map[string]*structs.Node,
   172  	allocs []*structs.Allocation, terminalAllocs map[string]*structs.Allocation) *diffResult {
   173  
   174  	// Build a mapping of nodes to all their allocs.
   175  	nodeAllocs := make(map[string][]*structs.Allocation, len(allocs))
   176  	for _, alloc := range allocs {
   177  		nallocs := append(nodeAllocs[alloc.NodeID], alloc)
   178  		nodeAllocs[alloc.NodeID] = nallocs
   179  	}
   180  
   181  	for _, node := range nodes {
   182  		if _, ok := nodeAllocs[node.ID]; !ok {
   183  			nodeAllocs[node.ID] = nil
   184  		}
   185  	}
   186  
   187  	// Create the required task groups.
   188  	required := materializeTaskGroups(job)
   189  
   190  	result := &diffResult{}
   191  	for nodeID, allocs := range nodeAllocs {
   192  		diff := diffAllocs(job, taintedNodes, required, allocs, terminalAllocs)
   193  
   194  		// If the node is tainted there should be no placements made
   195  		if _, ok := taintedNodes[nodeID]; ok {
   196  			diff.place = nil
   197  		} else {
   198  			// Mark the alloc as being for a specific node.
   199  			for i := range diff.place {
   200  				alloc := &diff.place[i]
   201  
   202  				// If the new allocation isn't annotated with a previous allocation
   203  				// or if the previous allocation isn't from the same node then we
   204  				// annotate the allocTuple with a new Allocation
   205  				if alloc.Alloc == nil || alloc.Alloc.NodeID != nodeID {
   206  					alloc.Alloc = &structs.Allocation{NodeID: nodeID}
   207  				}
   208  			}
   209  		}
   210  
   211  		// Migrate does not apply to system jobs and instead should be marked as
   212  		// stop because if a node is tainted, the job is invalid on that node.
   213  		diff.stop = append(diff.stop, diff.migrate...)
   214  		diff.migrate = nil
   215  
   216  		result.Append(diff)
   217  	}
   218  
   219  	return result
   220  }
   221  
   222  // readyNodesInDCs returns all the ready nodes in the given datacenters and a
   223  // mapping of each data center to the count of ready nodes.
   224  func readyNodesInDCs(state State, dcs []string) ([]*structs.Node, map[string]int, error) {
   225  	// Index the DCs
   226  	dcMap := make(map[string]int, len(dcs))
   227  	for _, dc := range dcs {
   228  		dcMap[dc] = 0
   229  	}
   230  
   231  	// Scan the nodes
   232  	ws := memdb.NewWatchSet()
   233  	var out []*structs.Node
   234  	iter, err := state.Nodes(ws)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  	for {
   239  		raw := iter.Next()
   240  		if raw == nil {
   241  			break
   242  		}
   243  
   244  		// Filter on datacenter and status
   245  		node := raw.(*structs.Node)
   246  		if node.Status != structs.NodeStatusReady {
   247  			continue
   248  		}
   249  		if node.Drain {
   250  			continue
   251  		}
   252  		if _, ok := dcMap[node.Datacenter]; !ok {
   253  			continue
   254  		}
   255  		out = append(out, node)
   256  		dcMap[node.Datacenter] += 1
   257  	}
   258  	return out, dcMap, nil
   259  }
   260  
   261  // retryMax is used to retry a callback until it returns success or
   262  // a maximum number of attempts is reached. An optional reset function may be
   263  // passed which is called after each failed iteration. If the reset function is
   264  // set and returns true, the number of attempts is reset back to max.
   265  func retryMax(max int, cb func() (bool, error), reset func() bool) error {
   266  	attempts := 0
   267  	for attempts < max {
   268  		done, err := cb()
   269  		if err != nil {
   270  			return err
   271  		}
   272  		if done {
   273  			return nil
   274  		}
   275  
   276  		// Check if we should reset the number attempts
   277  		if reset != nil && reset() {
   278  			attempts = 0
   279  		} else {
   280  			attempts += 1
   281  		}
   282  	}
   283  	return &SetStatusError{
   284  		Err:        fmt.Errorf("maximum attempts reached (%d)", max),
   285  		EvalStatus: structs.EvalStatusFailed,
   286  	}
   287  }
   288  
   289  // progressMade checks to see if the plan result made allocations or updates.
   290  // If the result is nil, false is returned.
   291  func progressMade(result *structs.PlanResult) bool {
   292  	return result != nil && (len(result.NodeUpdate) != 0 ||
   293  		len(result.NodeAllocation) != 0)
   294  }
   295  
   296  // taintedNodes is used to scan the allocations and then check if the
   297  // underlying nodes are tainted, and should force a migration of the allocation.
   298  // All the nodes returned in the map are tainted.
   299  func taintedNodes(state State, allocs []*structs.Allocation) (map[string]*structs.Node, error) {
   300  	out := make(map[string]*structs.Node)
   301  	for _, alloc := range allocs {
   302  		if _, ok := out[alloc.NodeID]; ok {
   303  			continue
   304  		}
   305  
   306  		ws := memdb.NewWatchSet()
   307  		node, err := state.NodeByID(ws, alloc.NodeID)
   308  		if err != nil {
   309  			return nil, err
   310  		}
   311  
   312  		// If the node does not exist, we should migrate
   313  		if node == nil {
   314  			out[alloc.NodeID] = nil
   315  			continue
   316  		}
   317  		if structs.ShouldDrainNode(node.Status) || node.Drain {
   318  			out[alloc.NodeID] = node
   319  		}
   320  	}
   321  	return out, nil
   322  }
   323  
   324  // shuffleNodes randomizes the slice order with the Fisher-Yates algorithm
   325  func shuffleNodes(nodes []*structs.Node) {
   326  	n := len(nodes)
   327  	for i := n - 1; i > 0; i-- {
   328  		j := rand.Intn(i + 1)
   329  		nodes[i], nodes[j] = nodes[j], nodes[i]
   330  	}
   331  }
   332  
   333  // tasksUpdated does a diff between task groups to see if the
   334  // tasks, their drivers, environment variables or config have updated. The
   335  // inputs are the task group name to diff and two jobs to diff.
   336  func tasksUpdated(jobA, jobB *structs.Job, taskGroup string) bool {
   337  	a := jobA.LookupTaskGroup(taskGroup)
   338  	b := jobB.LookupTaskGroup(taskGroup)
   339  
   340  	// If the number of tasks do not match, clearly there is an update
   341  	if len(a.Tasks) != len(b.Tasks) {
   342  		return true
   343  	}
   344  
   345  	// Check ephemeral disk
   346  	if !reflect.DeepEqual(a.EphemeralDisk, b.EphemeralDisk) {
   347  		return true
   348  	}
   349  
   350  	// Check each task
   351  	for _, at := range a.Tasks {
   352  		bt := b.LookupTask(at.Name)
   353  		if bt == nil {
   354  			return true
   355  		}
   356  		if at.Driver != bt.Driver {
   357  			return true
   358  		}
   359  		if at.User != bt.User {
   360  			return true
   361  		}
   362  		if !reflect.DeepEqual(at.Config, bt.Config) {
   363  			return true
   364  		}
   365  		if !reflect.DeepEqual(at.Env, bt.Env) {
   366  			return true
   367  		}
   368  		if !reflect.DeepEqual(at.Artifacts, bt.Artifacts) {
   369  			return true
   370  		}
   371  		if !reflect.DeepEqual(at.Vault, bt.Vault) {
   372  			return true
   373  		}
   374  		if !reflect.DeepEqual(at.Templates, bt.Templates) {
   375  			return true
   376  		}
   377  
   378  		// Check the metadata
   379  		if !reflect.DeepEqual(
   380  			jobA.CombinedTaskMeta(taskGroup, at.Name),
   381  			jobB.CombinedTaskMeta(taskGroup, bt.Name)) {
   382  			return true
   383  		}
   384  
   385  		// Inspect the network to see if the dynamic ports are different
   386  		if len(at.Resources.Networks) != len(bt.Resources.Networks) {
   387  			return true
   388  		}
   389  		for idx := range at.Resources.Networks {
   390  			an := at.Resources.Networks[idx]
   391  			bn := bt.Resources.Networks[idx]
   392  
   393  			if an.MBits != bn.MBits {
   394  				return true
   395  			}
   396  
   397  			aPorts, bPorts := networkPortMap(an), networkPortMap(bn)
   398  			if !reflect.DeepEqual(aPorts, bPorts) {
   399  				return true
   400  			}
   401  		}
   402  
   403  		// Inspect the non-network resources
   404  		if ar, br := at.Resources, bt.Resources; ar.CPU != br.CPU {
   405  			return true
   406  		} else if ar.MemoryMB != br.MemoryMB {
   407  			return true
   408  		} else if ar.IOPS != br.IOPS {
   409  			return true
   410  		}
   411  	}
   412  	return false
   413  }
   414  
   415  // networkPortMap takes a network resource and returns a map of port labels to
   416  // values. The value for dynamic ports is disregarded even if it is set. This
   417  // makes this function suitable for comparing two network resources for changes.
   418  func networkPortMap(n *structs.NetworkResource) map[string]int {
   419  	m := make(map[string]int, len(n.DynamicPorts)+len(n.ReservedPorts))
   420  	for _, p := range n.ReservedPorts {
   421  		m[p.Label] = p.Value
   422  	}
   423  	for _, p := range n.DynamicPorts {
   424  		m[p.Label] = -1
   425  	}
   426  	return m
   427  }
   428  
   429  // setStatus is used to update the status of the evaluation
   430  func setStatus(logger *log.Logger, planner Planner,
   431  	eval, nextEval, spawnedBlocked *structs.Evaluation,
   432  	tgMetrics map[string]*structs.AllocMetric, status, desc string,
   433  	queuedAllocs map[string]int) error {
   434  
   435  	logger.Printf("[DEBUG] sched: %#v: setting status to %s", eval, status)
   436  	newEval := eval.Copy()
   437  	newEval.Status = status
   438  	newEval.StatusDescription = desc
   439  	newEval.FailedTGAllocs = tgMetrics
   440  	if nextEval != nil {
   441  		newEval.NextEval = nextEval.ID
   442  	}
   443  	if spawnedBlocked != nil {
   444  		newEval.BlockedEval = spawnedBlocked.ID
   445  	}
   446  	if queuedAllocs != nil {
   447  		newEval.QueuedAllocations = queuedAllocs
   448  	}
   449  
   450  	return planner.UpdateEval(newEval)
   451  }
   452  
   453  // inplaceUpdate attempts to update allocations in-place where possible. It
   454  // returns the allocs that couldn't be done inplace and then those that could.
   455  func inplaceUpdate(ctx Context, eval *structs.Evaluation, job *structs.Job,
   456  	stack Stack, updates []allocTuple) (destructive, inplace []allocTuple) {
   457  
   458  	// doInplace manipulates the updates map to make the current allocation
   459  	// an inplace update.
   460  	doInplace := func(cur, last, inplaceCount *int) {
   461  		updates[*cur], updates[*last-1] = updates[*last-1], updates[*cur]
   462  		*cur--
   463  		*last--
   464  		*inplaceCount++
   465  	}
   466  
   467  	ws := memdb.NewWatchSet()
   468  	n := len(updates)
   469  	inplaceCount := 0
   470  	for i := 0; i < n; i++ {
   471  		// Get the update
   472  		update := updates[i]
   473  
   474  		// Check if the task drivers or config has changed, requires
   475  		// a rolling upgrade since that cannot be done in-place.
   476  		existing := update.Alloc.Job
   477  		if tasksUpdated(job, existing, update.TaskGroup.Name) {
   478  			continue
   479  		}
   480  
   481  		// Terminal batch allocations are not filtered when they are completed
   482  		// successfully. We should avoid adding the allocation to the plan in
   483  		// the case that it is an in-place update to avoid both additional data
   484  		// in the plan and work for the clients.
   485  		if update.Alloc.TerminalStatus() {
   486  			doInplace(&i, &n, &inplaceCount)
   487  			continue
   488  		}
   489  
   490  		// Get the existing node
   491  		node, err := ctx.State().NodeByID(ws, update.Alloc.NodeID)
   492  		if err != nil {
   493  			ctx.Logger().Printf("[ERR] sched: %#v failed to get node '%s': %v",
   494  				eval, update.Alloc.NodeID, err)
   495  			continue
   496  		}
   497  		if node == nil {
   498  			continue
   499  		}
   500  
   501  		// Set the existing node as the base set
   502  		stack.SetNodes([]*structs.Node{node})
   503  
   504  		// Stage an eviction of the current allocation. This is done so that
   505  		// the current allocation is discounted when checking for feasability.
   506  		// Otherwise we would be trying to fit the tasks current resources and
   507  		// updated resources. After select is called we can remove the evict.
   508  		ctx.Plan().AppendUpdate(update.Alloc, structs.AllocDesiredStatusStop,
   509  			allocInPlace, "")
   510  
   511  		// Attempt to match the task group
   512  		option, _ := stack.Select(update.TaskGroup)
   513  
   514  		// Pop the allocation
   515  		ctx.Plan().PopUpdate(update.Alloc)
   516  
   517  		// Skip if we could not do an in-place update
   518  		if option == nil {
   519  			continue
   520  		}
   521  
   522  		// Restore the network offers from the existing allocation.
   523  		// We do not allow network resources (reserved/dynamic ports)
   524  		// to be updated. This is guarded in taskUpdated, so we can
   525  		// safely restore those here.
   526  		for task, resources := range option.TaskResources {
   527  			existing := update.Alloc.TaskResources[task]
   528  			resources.Networks = existing.Networks
   529  		}
   530  
   531  		// Create a shallow copy
   532  		newAlloc := new(structs.Allocation)
   533  		*newAlloc = *update.Alloc
   534  
   535  		// Update the allocation
   536  		newAlloc.EvalID = eval.ID
   537  		newAlloc.Job = nil       // Use the Job in the Plan
   538  		newAlloc.Resources = nil // Computed in Plan Apply
   539  		newAlloc.TaskResources = option.TaskResources
   540  		newAlloc.Metrics = ctx.Metrics()
   541  		ctx.Plan().AppendAlloc(newAlloc)
   542  
   543  		// Remove this allocation from the slice
   544  		doInplace(&i, &n, &inplaceCount)
   545  	}
   546  
   547  	if len(updates) > 0 {
   548  		ctx.Logger().Printf("[DEBUG] sched: %#v: %d in-place updates of %d", eval, inplaceCount, len(updates))
   549  	}
   550  	return updates[:n], updates[n:]
   551  }
   552  
   553  // evictAndPlace is used to mark allocations for evicts and add them to the
   554  // placement queue. evictAndPlace modifies both the diffResult and the
   555  // limit. It returns true if the limit has been reached.
   556  func evictAndPlace(ctx Context, diff *diffResult, allocs []allocTuple, desc string, limit *int) bool {
   557  	n := len(allocs)
   558  	for i := 0; i < n && i < *limit; i++ {
   559  		a := allocs[i]
   560  		ctx.Plan().AppendUpdate(a.Alloc, structs.AllocDesiredStatusStop, desc, "")
   561  		diff.place = append(diff.place, a)
   562  	}
   563  	if n <= *limit {
   564  		*limit -= n
   565  		return false
   566  	}
   567  	*limit = 0
   568  	return true
   569  }
   570  
   571  // markLostAndPlace is used to mark allocations as lost and add them to the
   572  // placement queue. evictAndPlace modifies both the diffResult and the
   573  // limit. It returns true if the limit has been reached.
   574  func markLostAndPlace(ctx Context, diff *diffResult, allocs []allocTuple, desc string, limit *int) bool {
   575  	n := len(allocs)
   576  	for i := 0; i < n && i < *limit; i++ {
   577  		a := allocs[i]
   578  		ctx.Plan().AppendUpdate(a.Alloc, structs.AllocDesiredStatusStop, desc, structs.AllocClientStatusLost)
   579  		diff.place = append(diff.place, a)
   580  	}
   581  	if n <= *limit {
   582  		*limit -= n
   583  		return false
   584  	}
   585  	*limit = 0
   586  	return true
   587  }
   588  
   589  // tgConstrainTuple is used to store the total constraints of a task group.
   590  type tgConstrainTuple struct {
   591  	// Holds the combined constraints of the task group and all it's sub-tasks.
   592  	constraints []*structs.Constraint
   593  
   594  	// The set of required drivers within the task group.
   595  	drivers map[string]struct{}
   596  
   597  	// The combined resources of all tasks within the task group.
   598  	size *structs.Resources
   599  }
   600  
   601  // taskGroupConstraints collects the constraints, drivers and resources required by each
   602  // sub-task to aggregate the TaskGroup totals
   603  func taskGroupConstraints(tg *structs.TaskGroup) tgConstrainTuple {
   604  	c := tgConstrainTuple{
   605  		constraints: make([]*structs.Constraint, 0, len(tg.Constraints)),
   606  		drivers:     make(map[string]struct{}),
   607  		size:        &structs.Resources{DiskMB: tg.EphemeralDisk.SizeMB},
   608  	}
   609  
   610  	c.constraints = append(c.constraints, tg.Constraints...)
   611  	for _, task := range tg.Tasks {
   612  		c.drivers[task.Driver] = struct{}{}
   613  		c.constraints = append(c.constraints, task.Constraints...)
   614  		c.size.Add(task.Resources)
   615  	}
   616  
   617  	return c
   618  }
   619  
   620  // desiredUpdates takes the diffResult as well as the set of inplace and
   621  // destructive updates and returns a map of task groups to their set of desired
   622  // updates.
   623  func desiredUpdates(diff *diffResult, inplaceUpdates,
   624  	destructiveUpdates []allocTuple) map[string]*structs.DesiredUpdates {
   625  	desiredTgs := make(map[string]*structs.DesiredUpdates)
   626  
   627  	for _, tuple := range diff.place {
   628  		name := tuple.TaskGroup.Name
   629  		des, ok := desiredTgs[name]
   630  		if !ok {
   631  			des = &structs.DesiredUpdates{}
   632  			desiredTgs[name] = des
   633  		}
   634  
   635  		des.Place++
   636  	}
   637  
   638  	for _, tuple := range diff.stop {
   639  		name := tuple.Alloc.TaskGroup
   640  		des, ok := desiredTgs[name]
   641  		if !ok {
   642  			des = &structs.DesiredUpdates{}
   643  			desiredTgs[name] = des
   644  		}
   645  
   646  		des.Stop++
   647  	}
   648  
   649  	for _, tuple := range diff.ignore {
   650  		name := tuple.TaskGroup.Name
   651  		des, ok := desiredTgs[name]
   652  		if !ok {
   653  			des = &structs.DesiredUpdates{}
   654  			desiredTgs[name] = des
   655  		}
   656  
   657  		des.Ignore++
   658  	}
   659  
   660  	for _, tuple := range diff.migrate {
   661  		name := tuple.TaskGroup.Name
   662  		des, ok := desiredTgs[name]
   663  		if !ok {
   664  			des = &structs.DesiredUpdates{}
   665  			desiredTgs[name] = des
   666  		}
   667  
   668  		des.Migrate++
   669  	}
   670  
   671  	for _, tuple := range inplaceUpdates {
   672  		name := tuple.TaskGroup.Name
   673  		des, ok := desiredTgs[name]
   674  		if !ok {
   675  			des = &structs.DesiredUpdates{}
   676  			desiredTgs[name] = des
   677  		}
   678  
   679  		des.InPlaceUpdate++
   680  	}
   681  
   682  	for _, tuple := range destructiveUpdates {
   683  		name := tuple.TaskGroup.Name
   684  		des, ok := desiredTgs[name]
   685  		if !ok {
   686  			des = &structs.DesiredUpdates{}
   687  			desiredTgs[name] = des
   688  		}
   689  
   690  		des.DestructiveUpdate++
   691  	}
   692  
   693  	return desiredTgs
   694  }
   695  
   696  // adjustQueuedAllocations decrements the number of allocations pending per task
   697  // group based on the number of allocations successfully placed
   698  func adjustQueuedAllocations(logger *log.Logger, result *structs.PlanResult, queuedAllocs map[string]int) {
   699  	if result != nil {
   700  		for _, allocations := range result.NodeAllocation {
   701  			for _, allocation := range allocations {
   702  				// Ensure that the allocation is newly created. We check that
   703  				// the CreateIndex is equal to the ModifyIndex in order to check
   704  				// that the allocation was just created. We do not check that
   705  				// the CreateIndex is equal to the results AllocIndex because
   706  				// the allocations we get back have gone through the planner's
   707  				// optimistic snapshot and thus their indexes may not be
   708  				// correct, but they will be consistent.
   709  				if allocation.CreateIndex != allocation.ModifyIndex {
   710  					continue
   711  				}
   712  
   713  				if _, ok := queuedAllocs[allocation.TaskGroup]; ok {
   714  					queuedAllocs[allocation.TaskGroup] -= 1
   715  				} else {
   716  					logger.Printf("[ERR] sched: allocation %q placed but not in list of unplaced allocations", allocation.TaskGroup)
   717  				}
   718  			}
   719  		}
   720  	}
   721  }
   722  
   723  // updateNonTerminalAllocsToLost updates the allocations which are in pending/running state on tainted node
   724  // to lost
   725  func updateNonTerminalAllocsToLost(plan *structs.Plan, tainted map[string]*structs.Node, allocs []*structs.Allocation) {
   726  	for _, alloc := range allocs {
   727  		if _, ok := tainted[alloc.NodeID]; ok &&
   728  			alloc.DesiredStatus == structs.AllocDesiredStatusStop &&
   729  			(alloc.ClientStatus == structs.AllocClientStatusRunning ||
   730  				alloc.ClientStatus == structs.AllocClientStatusPending) {
   731  			plan.AppendUpdate(alloc, structs.AllocDesiredStatusStop, allocLost, structs.AllocClientStatusLost)
   732  		}
   733  	}
   734  }