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 }