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