github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/scheduler/context.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "log" 6 "regexp" 7 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 111 existingAlloc, err := e.state.AllocsByNode(nodeID) 112 if err != nil { 113 return nil, err 114 } 115 116 // Filter on alloc state 117 existingAlloc = structs.FilterTerminalAllocs(existingAlloc) 118 119 // Determine the proposed allocation by first removing allocations 120 // that are planned evictions and adding the new allocations. 121 proposed := existingAlloc 122 if update := e.plan.NodeUpdate[nodeID]; len(update) > 0 { 123 proposed = structs.RemoveAllocs(existingAlloc, update) 124 } 125 proposed = append(proposed, e.plan.NodeAllocation[nodeID]...) 126 127 // Ensure the return is not nil 128 if proposed == nil { 129 proposed = make([]*structs.Allocation, 0) 130 } 131 return proposed, nil 132 } 133 134 func (e *EvalContext) Eligibility() *EvalEligibility { 135 if e.eligibility == nil { 136 e.eligibility = NewEvalEligibility() 137 } 138 139 return e.eligibility 140 } 141 142 type ComputedClassFeasibility byte 143 144 const ( 145 // EvalComputedClassUnknown is the initial state until the eligibility has 146 // been explicitely marked to eligible/ineligible or escaped. 147 EvalComputedClassUnknown ComputedClassFeasibility = iota 148 149 // EvalComputedClassIneligible is used to mark the computed class as 150 // ineligible for the evaluation. 151 EvalComputedClassIneligible 152 153 // EvalComputedClassIneligible is used to mark the computed class as 154 // eligible for the evaluation. 155 EvalComputedClassEligible 156 157 // EvalComputedClassEscaped signals that computed class can not determine 158 // eligibility because a constraint exists that is not captured by computed 159 // node classes. 160 EvalComputedClassEscaped 161 ) 162 163 // EvalEligibility tracks eligibility of nodes by computed node class over the 164 // course of an evaluation. 165 type EvalEligibility struct { 166 // job tracks the eligibility at the job level per computed node class. 167 job map[uint64]ComputedClassFeasibility 168 169 // jobEscaped marks whether constraints have escaped at the job level. 170 jobEscaped bool 171 172 // taskGroups tracks the eligibility at the task group level per computed 173 // node class. 174 taskGroups map[string]map[uint64]ComputedClassFeasibility 175 176 // tgEscapedConstraints is a map of task groups to whether constraints have 177 // escaped. 178 tgEscapedConstraints map[string]bool 179 } 180 181 // NewEvalEligibility returns an eligibility tracker for the context of an evaluation. 182 func NewEvalEligibility() *EvalEligibility { 183 return &EvalEligibility{ 184 job: make(map[uint64]ComputedClassFeasibility), 185 taskGroups: make(map[string]map[uint64]ComputedClassFeasibility), 186 tgEscapedConstraints: make(map[string]bool), 187 } 188 } 189 190 // SetJob takes the job being evaluated and calculates the escaped constraints 191 // at the job and task group level. 192 func (e *EvalEligibility) SetJob(job *structs.Job) { 193 // Determine whether the job has escaped constraints. 194 e.jobEscaped = len(structs.EscapedConstraints(job.Constraints)) != 0 195 196 // Determine the escaped constraints per task group. 197 for _, tg := range job.TaskGroups { 198 constraints := tg.Constraints 199 for _, task := range tg.Tasks { 200 constraints = append(constraints, task.Constraints...) 201 } 202 203 e.tgEscapedConstraints[tg.Name] = len(structs.EscapedConstraints(constraints)) != 0 204 } 205 } 206 207 // HasEscaped returns whether any of the constraints in the passed job have 208 // escaped computed node classes. 209 func (e *EvalEligibility) HasEscaped() bool { 210 if e.jobEscaped { 211 return true 212 } 213 214 for _, escaped := range e.tgEscapedConstraints { 215 if escaped { 216 return true 217 } 218 } 219 220 return false 221 } 222 223 // JobStatus returns the eligibility status of the job. 224 func (e *EvalEligibility) JobStatus(class uint64) ComputedClassFeasibility { 225 // COMPAT: Computed node class was introduced in 0.3. Clients running < 0.3 226 // will not have a computed class. The safest value to return is the escaped 227 // case, since it disables any optimization. 228 if e.jobEscaped || class == 0 { 229 fmt.Println(e.jobEscaped, class) 230 return EvalComputedClassEscaped 231 } 232 233 if status, ok := e.job[class]; ok { 234 return status 235 } 236 return EvalComputedClassUnknown 237 } 238 239 // SetJobEligibility sets the eligibility status of the job for the computed 240 // node class. 241 func (e *EvalEligibility) SetJobEligibility(eligible bool, class uint64) { 242 if eligible { 243 e.job[class] = EvalComputedClassEligible 244 } else { 245 e.job[class] = EvalComputedClassIneligible 246 } 247 } 248 249 // TaskGroupStatus returns the eligibility status of the task group. 250 func (e *EvalEligibility) TaskGroupStatus(tg string, class uint64) ComputedClassFeasibility { 251 // COMPAT: Computed node class was introduced in 0.3. Clients running < 0.3 252 // will not have a computed class. The safest value to return is the escaped 253 // case, since it disables any optimization. 254 if class == 0 { 255 return EvalComputedClassEscaped 256 } 257 258 if escaped, ok := e.tgEscapedConstraints[tg]; ok { 259 if escaped { 260 return EvalComputedClassEscaped 261 } 262 } 263 264 if classes, ok := e.taskGroups[tg]; ok { 265 if status, ok := classes[class]; ok { 266 return status 267 } 268 } 269 return EvalComputedClassUnknown 270 } 271 272 // SetTaskGroupEligibility sets the eligibility status of the task group for the 273 // computed node class. 274 func (e *EvalEligibility) SetTaskGroupEligibility(eligible bool, tg string, class uint64) { 275 var eligibility ComputedClassFeasibility 276 if eligible { 277 eligibility = EvalComputedClassEligible 278 } else { 279 eligibility = EvalComputedClassIneligible 280 } 281 282 if classes, ok := e.taskGroups[tg]; ok { 283 classes[class] = eligibility 284 } else { 285 e.taskGroups[tg] = map[uint64]ComputedClassFeasibility{class: eligibility} 286 } 287 }