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