github.com/banmanh482/nomad@v0.11.8/scheduler/context.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"regexp"
     5  
     6  	log "github.com/hashicorp/go-hclog"
     7  	memdb "github.com/hashicorp/go-memdb"
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  // Context is used to track contextual information used for placement
    12  type Context interface {
    13  	// State is used to inspect the current global state
    14  	State() State
    15  
    16  	// Plan returns the current plan
    17  	Plan() *structs.Plan
    18  
    19  	// Logger provides a way to log
    20  	Logger() log.Logger
    21  
    22  	// Metrics returns the current metrics
    23  	Metrics() *structs.AllocMetric
    24  
    25  	// Reset is invoked after making a placement
    26  	Reset()
    27  
    28  	// ProposedAllocs returns the proposed allocations for a node which are
    29  	// the existing allocations, removing evictions, and adding any planned
    30  	// placements.
    31  	ProposedAllocs(nodeID string) ([]*structs.Allocation, error)
    32  
    33  	// RegexpCache is a cache of regular expressions
    34  	RegexpCache() map[string]*regexp.Regexp
    35  
    36  	// VersionConstraintCache is a cache of version constraints
    37  	VersionConstraintCache() map[string]VerConstraints
    38  
    39  	// SemverConstraintCache is a cache of semver constraints
    40  	SemverConstraintCache() map[string]VerConstraints
    41  
    42  	// Eligibility returns a tracker for node eligibility in the context of the
    43  	// eval.
    44  	Eligibility() *EvalEligibility
    45  }
    46  
    47  // EvalCache is used to cache certain things during an evaluation
    48  type EvalCache struct {
    49  	reCache      map[string]*regexp.Regexp
    50  	versionCache map[string]VerConstraints
    51  	semverCache  map[string]VerConstraints
    52  }
    53  
    54  func (e *EvalCache) RegexpCache() map[string]*regexp.Regexp {
    55  	if e.reCache == nil {
    56  		e.reCache = make(map[string]*regexp.Regexp)
    57  	}
    58  	return e.reCache
    59  }
    60  
    61  func (e *EvalCache) VersionConstraintCache() map[string]VerConstraints {
    62  	if e.versionCache == nil {
    63  		e.versionCache = make(map[string]VerConstraints)
    64  	}
    65  	return e.versionCache
    66  }
    67  
    68  func (e *EvalCache) SemverConstraintCache() map[string]VerConstraints {
    69  	if e.semverCache == nil {
    70  		e.semverCache = make(map[string]VerConstraints)
    71  	}
    72  	return e.semverCache
    73  }
    74  
    75  // EvalContext is a Context used during an Evaluation
    76  type EvalContext struct {
    77  	EvalCache
    78  	state       State
    79  	plan        *structs.Plan
    80  	logger      log.Logger
    81  	metrics     *structs.AllocMetric
    82  	eligibility *EvalEligibility
    83  }
    84  
    85  // NewEvalContext constructs a new EvalContext
    86  func NewEvalContext(s State, p *structs.Plan, log log.Logger) *EvalContext {
    87  	ctx := &EvalContext{
    88  		state:   s,
    89  		plan:    p,
    90  		logger:  log,
    91  		metrics: new(structs.AllocMetric),
    92  	}
    93  	return ctx
    94  }
    95  
    96  func (e *EvalContext) State() State {
    97  	return e.state
    98  }
    99  
   100  func (e *EvalContext) Plan() *structs.Plan {
   101  	return e.plan
   102  }
   103  
   104  func (e *EvalContext) Logger() log.Logger {
   105  	return e.logger
   106  }
   107  
   108  func (e *EvalContext) Metrics() *structs.AllocMetric {
   109  	return e.metrics
   110  }
   111  
   112  func (e *EvalContext) SetState(s State) {
   113  	e.state = s
   114  }
   115  
   116  func (e *EvalContext) Reset() {
   117  	e.metrics = new(structs.AllocMetric)
   118  }
   119  
   120  func (e *EvalContext) ProposedAllocs(nodeID string) ([]*structs.Allocation, error) {
   121  	// Get the existing allocations that are non-terminal
   122  	ws := memdb.NewWatchSet()
   123  	proposed, err := e.state.AllocsByNodeTerminal(ws, nodeID, false)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	// Determine the proposed allocation by first removing allocations
   129  	// that are planned evictions and adding the new allocations.
   130  	if update := e.plan.NodeUpdate[nodeID]; len(update) > 0 {
   131  		proposed = structs.RemoveAllocs(proposed, update)
   132  	}
   133  
   134  	// Remove any allocs that are being preempted
   135  	nodePreemptedAllocs := e.plan.NodePreemptions[nodeID]
   136  	if len(nodePreemptedAllocs) > 0 {
   137  		proposed = structs.RemoveAllocs(proposed, nodePreemptedAllocs)
   138  	}
   139  
   140  	// We create an index of the existing allocations so that if an inplace
   141  	// update occurs, we do not double count and we override the old allocation.
   142  	proposedIDs := make(map[string]*structs.Allocation, len(proposed))
   143  	for _, alloc := range proposed {
   144  		proposedIDs[alloc.ID] = alloc
   145  	}
   146  	for _, alloc := range e.plan.NodeAllocation[nodeID] {
   147  		proposedIDs[alloc.ID] = alloc
   148  	}
   149  
   150  	// Materialize the proposed slice
   151  	proposed = make([]*structs.Allocation, 0, len(proposedIDs))
   152  	for _, alloc := range proposedIDs {
   153  		proposed = append(proposed, alloc)
   154  	}
   155  
   156  	return proposed, nil
   157  }
   158  
   159  func (e *EvalContext) Eligibility() *EvalEligibility {
   160  	if e.eligibility == nil {
   161  		e.eligibility = NewEvalEligibility()
   162  	}
   163  
   164  	return e.eligibility
   165  }
   166  
   167  type ComputedClassFeasibility byte
   168  
   169  const (
   170  	// EvalComputedClassUnknown is the initial state until the eligibility has
   171  	// been explicitly marked to eligible/ineligible or escaped.
   172  	EvalComputedClassUnknown ComputedClassFeasibility = iota
   173  
   174  	// EvalComputedClassIneligible is used to mark the computed class as
   175  	// ineligible for the evaluation.
   176  	EvalComputedClassIneligible
   177  
   178  	// EvalComputedClassIneligible is used to mark the computed class as
   179  	// eligible for the evaluation.
   180  	EvalComputedClassEligible
   181  
   182  	// EvalComputedClassEscaped signals that computed class can not determine
   183  	// eligibility because a constraint exists that is not captured by computed
   184  	// node classes.
   185  	EvalComputedClassEscaped
   186  )
   187  
   188  // EvalEligibility tracks eligibility of nodes by computed node class over the
   189  // course of an evaluation.
   190  type EvalEligibility struct {
   191  	// job tracks the eligibility at the job level per computed node class.
   192  	job map[string]ComputedClassFeasibility
   193  
   194  	// jobEscaped marks whether constraints have escaped at the job level.
   195  	jobEscaped bool
   196  
   197  	// taskGroups tracks the eligibility at the task group level per computed
   198  	// node class.
   199  	taskGroups map[string]map[string]ComputedClassFeasibility
   200  
   201  	// tgEscapedConstraints is a map of task groups to whether constraints have
   202  	// escaped.
   203  	tgEscapedConstraints map[string]bool
   204  
   205  	// quotaReached marks that the quota limit has been reached for the given
   206  	// quota
   207  	quotaReached string
   208  }
   209  
   210  // NewEvalEligibility returns an eligibility tracker for the context of an evaluation.
   211  func NewEvalEligibility() *EvalEligibility {
   212  	return &EvalEligibility{
   213  		job:                  make(map[string]ComputedClassFeasibility),
   214  		taskGroups:           make(map[string]map[string]ComputedClassFeasibility),
   215  		tgEscapedConstraints: make(map[string]bool),
   216  	}
   217  }
   218  
   219  // SetJob takes the job being evaluated and calculates the escaped constraints
   220  // at the job and task group level.
   221  func (e *EvalEligibility) SetJob(job *structs.Job) {
   222  	// Determine whether the job has escaped constraints.
   223  	e.jobEscaped = len(structs.EscapedConstraints(job.Constraints)) != 0
   224  
   225  	// Determine the escaped constraints per task group.
   226  	for _, tg := range job.TaskGroups {
   227  		constraints := tg.Constraints
   228  		for _, task := range tg.Tasks {
   229  			constraints = append(constraints, task.Constraints...)
   230  		}
   231  
   232  		e.tgEscapedConstraints[tg.Name] = len(structs.EscapedConstraints(constraints)) != 0
   233  	}
   234  }
   235  
   236  // HasEscaped returns whether any of the constraints in the passed job have
   237  // escaped computed node classes.
   238  func (e *EvalEligibility) HasEscaped() bool {
   239  	if e.jobEscaped {
   240  		return true
   241  	}
   242  
   243  	for _, escaped := range e.tgEscapedConstraints {
   244  		if escaped {
   245  			return true
   246  		}
   247  	}
   248  
   249  	return false
   250  }
   251  
   252  // GetClasses returns the tracked classes to their eligibility, across the job
   253  // and task groups.
   254  func (e *EvalEligibility) GetClasses() map[string]bool {
   255  	elig := make(map[string]bool)
   256  
   257  	// Go through the task groups.
   258  	for _, classes := range e.taskGroups {
   259  		for class, feas := range classes {
   260  			switch feas {
   261  			case EvalComputedClassEligible:
   262  				elig[class] = true
   263  			case EvalComputedClassIneligible:
   264  				// Only mark as ineligible if it hasn't been marked before. This
   265  				// prevents one task group marking a class as ineligible when it
   266  				// is eligible on another task group.
   267  				if _, ok := elig[class]; !ok {
   268  					elig[class] = false
   269  				}
   270  			}
   271  		}
   272  	}
   273  
   274  	// Go through the job.
   275  	for class, feas := range e.job {
   276  		switch feas {
   277  		case EvalComputedClassEligible:
   278  			// Only mark as eligible if it hasn't been marked before. This
   279  			// prevents the job marking a class as eligible when it is ineligible
   280  			// to all the task groups.
   281  			if _, ok := elig[class]; !ok {
   282  				elig[class] = true
   283  			}
   284  		case EvalComputedClassIneligible:
   285  			elig[class] = false
   286  		}
   287  	}
   288  
   289  	return elig
   290  }
   291  
   292  // JobStatus returns the eligibility status of the job.
   293  func (e *EvalEligibility) JobStatus(class string) ComputedClassFeasibility {
   294  	if e.jobEscaped {
   295  		return EvalComputedClassEscaped
   296  	}
   297  
   298  	if status, ok := e.job[class]; ok {
   299  		return status
   300  	}
   301  	return EvalComputedClassUnknown
   302  }
   303  
   304  // SetJobEligibility sets the eligibility status of the job for the computed
   305  // node class.
   306  func (e *EvalEligibility) SetJobEligibility(eligible bool, class string) {
   307  	if eligible {
   308  		e.job[class] = EvalComputedClassEligible
   309  	} else {
   310  		e.job[class] = EvalComputedClassIneligible
   311  	}
   312  }
   313  
   314  // TaskGroupStatus returns the eligibility status of the task group.
   315  func (e *EvalEligibility) TaskGroupStatus(tg, class string) ComputedClassFeasibility {
   316  	if escaped, ok := e.tgEscapedConstraints[tg]; ok {
   317  		if escaped {
   318  			return EvalComputedClassEscaped
   319  		}
   320  	}
   321  
   322  	if classes, ok := e.taskGroups[tg]; ok {
   323  		if status, ok := classes[class]; ok {
   324  			return status
   325  		}
   326  	}
   327  	return EvalComputedClassUnknown
   328  }
   329  
   330  // SetTaskGroupEligibility sets the eligibility status of the task group for the
   331  // computed node class.
   332  func (e *EvalEligibility) SetTaskGroupEligibility(eligible bool, tg, class string) {
   333  	var eligibility ComputedClassFeasibility
   334  	if eligible {
   335  		eligibility = EvalComputedClassEligible
   336  	} else {
   337  		eligibility = EvalComputedClassIneligible
   338  	}
   339  
   340  	if classes, ok := e.taskGroups[tg]; ok {
   341  		classes[class] = eligibility
   342  	} else {
   343  		e.taskGroups[tg] = map[string]ComputedClassFeasibility{class: eligibility}
   344  	}
   345  }
   346  
   347  // SetQuotaLimitReached marks that the quota limit has been reached for the
   348  // given quota
   349  func (e *EvalEligibility) SetQuotaLimitReached(quota string) {
   350  	e.quotaReached = quota
   351  }
   352  
   353  // QuotaLimitReached returns the quota name if the quota limit has been reached.
   354  func (e *EvalEligibility) QuotaLimitReached() string {
   355  	return e.quotaReached
   356  }