github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/scheduler/feasible.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/go-version" 11 "github.com/hashicorp/nomad/nomad/structs" 12 ) 13 14 // FeasibleIterator is used to iteratively yield nodes that 15 // match feasibility constraints. The iterators may manage 16 // some state for performance optimizations. 17 type FeasibleIterator interface { 18 // Next yields a feasible node or nil if exhausted 19 Next() *structs.Node 20 21 // Reset is invoked when an allocation has been placed 22 // to reset any stale state. 23 Reset() 24 } 25 26 // StaticIterator is a FeasibleIterator which returns nodes 27 // in a static order. This is used at the base of the iterator 28 // chain only for testing due to deterministic behavior. 29 type StaticIterator struct { 30 ctx Context 31 nodes []*structs.Node 32 offset int 33 seen int 34 } 35 36 // NewStaticIterator constructs a random iterator from a list of nodes 37 func NewStaticIterator(ctx Context, nodes []*structs.Node) *StaticIterator { 38 iter := &StaticIterator{ 39 ctx: ctx, 40 nodes: nodes, 41 } 42 return iter 43 } 44 45 func (iter *StaticIterator) Next() *structs.Node { 46 // Check if exhausted 47 n := len(iter.nodes) 48 if iter.offset == n || iter.seen == n { 49 if iter.seen != n { 50 iter.offset = 0 51 } else { 52 return nil 53 } 54 } 55 56 // Return the next offset 57 offset := iter.offset 58 iter.offset += 1 59 iter.seen += 1 60 iter.ctx.Metrics().EvaluateNode() 61 return iter.nodes[offset] 62 } 63 64 func (iter *StaticIterator) Reset() { 65 iter.seen = 0 66 } 67 68 func (iter *StaticIterator) SetNodes(nodes []*structs.Node) { 69 iter.nodes = nodes 70 iter.offset = 0 71 iter.seen = 0 72 } 73 74 // NewRandomIterator constructs a static iterator from a list of nodes 75 // after applying the Fisher-Yates algorithm for a random shuffle. This 76 // is applied in-place 77 func NewRandomIterator(ctx Context, nodes []*structs.Node) *StaticIterator { 78 // shuffle with the Fisher-Yates algorithm 79 shuffleNodes(nodes) 80 81 // Create a static iterator 82 return NewStaticIterator(ctx, nodes) 83 } 84 85 // DriverIterator is a FeasibleIterator which returns nodes that 86 // have the drivers necessary to scheduler a task group. 87 type DriverIterator struct { 88 ctx Context 89 source FeasibleIterator 90 drivers map[string]struct{} 91 } 92 93 // NewDriverIterator creates a DriverIterator from a source and set of drivers 94 func NewDriverIterator(ctx Context, source FeasibleIterator, drivers map[string]struct{}) *DriverIterator { 95 iter := &DriverIterator{ 96 ctx: ctx, 97 source: source, 98 drivers: drivers, 99 } 100 return iter 101 } 102 103 func (iter *DriverIterator) SetDrivers(d map[string]struct{}) { 104 iter.drivers = d 105 } 106 107 func (iter *DriverIterator) Next() *structs.Node { 108 for { 109 // Get the next option from the source 110 option := iter.source.Next() 111 if option == nil { 112 return nil 113 } 114 115 // Use this node if possible 116 if iter.hasDrivers(option) { 117 return option 118 } 119 iter.ctx.Metrics().FilterNode(option, "missing drivers") 120 } 121 } 122 123 func (iter *DriverIterator) Reset() { 124 iter.source.Reset() 125 } 126 127 // hasDrivers is used to check if the node has all the appropriate 128 // drivers for this task group. Drivers are registered as node attribute 129 // like "driver.docker=1" with their corresponding version. 130 func (iter *DriverIterator) hasDrivers(option *structs.Node) bool { 131 for driver := range iter.drivers { 132 driverStr := fmt.Sprintf("driver.%s", driver) 133 value, ok := option.Attributes[driverStr] 134 if !ok { 135 return false 136 } 137 138 enabled, err := strconv.ParseBool(value) 139 if err != nil { 140 iter.ctx.Logger(). 141 Printf("[WARN] scheduler.DriverIterator: node %v has invalid driver setting %v: %v", 142 option.ID, driverStr, value) 143 return false 144 } 145 146 if !enabled { 147 return false 148 } 149 } 150 return true 151 } 152 153 // ConstraintIterator is a FeasibleIterator which returns nodes 154 // that match a given set of constraints. This is used to filter 155 // on job, task group, and task constraints. 156 type ConstraintIterator struct { 157 ctx Context 158 source FeasibleIterator 159 constraints []*structs.Constraint 160 } 161 162 // NewConstraintIterator creates a ConstraintIterator from a source and set of constraints 163 func NewConstraintIterator(ctx Context, source FeasibleIterator, constraints []*structs.Constraint) *ConstraintIterator { 164 iter := &ConstraintIterator{ 165 ctx: ctx, 166 source: source, 167 constraints: constraints, 168 } 169 return iter 170 } 171 172 func (iter *ConstraintIterator) SetConstraints(c []*structs.Constraint) { 173 iter.constraints = c 174 } 175 176 func (iter *ConstraintIterator) Next() *structs.Node { 177 for { 178 // Get the next option from the source 179 option := iter.source.Next() 180 if option == nil { 181 return nil 182 } 183 184 // Use this node if possible 185 if iter.meetsConstraints(option) { 186 return option 187 } 188 } 189 } 190 191 func (iter *ConstraintIterator) Reset() { 192 iter.source.Reset() 193 } 194 195 func (iter *ConstraintIterator) meetsConstraints(option *structs.Node) bool { 196 for _, constraint := range iter.constraints { 197 if !iter.meetsConstraint(constraint, option) { 198 iter.ctx.Metrics().FilterNode(option, constraint.String()) 199 return false 200 } 201 } 202 return true 203 } 204 205 func (iter *ConstraintIterator) meetsConstraint(constraint *structs.Constraint, option *structs.Node) bool { 206 // Only enforce hard constraints, soft constraints are used for ranking 207 if !constraint.Hard { 208 return true 209 } 210 211 // Resolve the targets 212 lVal, ok := resolveConstraintTarget(constraint.LTarget, option) 213 if !ok { 214 return false 215 } 216 rVal, ok := resolveConstraintTarget(constraint.RTarget, option) 217 if !ok { 218 return false 219 } 220 221 // Check if satisfied 222 return checkConstraint(iter.ctx, constraint.Operand, lVal, rVal) 223 } 224 225 // resolveConstraintTarget is used to resolve the LTarget and RTarget of a Constraint 226 func resolveConstraintTarget(target string, node *structs.Node) (interface{}, bool) { 227 // If no prefix, this must be a literal value 228 if !strings.HasPrefix(target, "$") { 229 return target, true 230 } 231 232 // Handle the interpolations 233 switch { 234 case "$node.id" == target: 235 return node.ID, true 236 237 case "$node.datacenter" == target: 238 return node.Datacenter, true 239 240 case "$node.name" == target: 241 return node.Name, true 242 243 case strings.HasPrefix(target, "$attr."): 244 attr := strings.TrimPrefix(target, "$attr.") 245 val, ok := node.Attributes[attr] 246 return val, ok 247 248 case strings.HasPrefix(target, "$meta."): 249 meta := strings.TrimPrefix(target, "$meta.") 250 val, ok := node.Meta[meta] 251 return val, ok 252 253 default: 254 return nil, false 255 } 256 } 257 258 // checkConstraint checks if a constraint is satisfied 259 func checkConstraint(ctx Context, operand string, lVal, rVal interface{}) bool { 260 switch operand { 261 case "=", "==", "is": 262 return reflect.DeepEqual(lVal, rVal) 263 case "!=", "not": 264 return !reflect.DeepEqual(lVal, rVal) 265 case "<", "<=", ">", ">=": 266 return checkLexicalOrder(operand, lVal, rVal) 267 case "version": 268 return checkVersionConstraint(ctx, lVal, rVal) 269 case "regexp": 270 return checkRegexpConstraint(ctx, lVal, rVal) 271 default: 272 return false 273 } 274 } 275 276 // checkLexicalOrder is used to check for lexical ordering 277 func checkLexicalOrder(op string, lVal, rVal interface{}) bool { 278 // Ensure the values are strings 279 lStr, ok := lVal.(string) 280 if !ok { 281 return false 282 } 283 rStr, ok := rVal.(string) 284 if !ok { 285 return false 286 } 287 288 switch op { 289 case "<": 290 return lStr < rStr 291 case "<=": 292 return lStr <= rStr 293 case ">": 294 return lStr > rStr 295 case ">=": 296 return lStr >= rStr 297 default: 298 return false 299 } 300 } 301 302 // checkVersionConstraint is used to compare a version on the 303 // left hand side with a set of constraints on the right hand side 304 func checkVersionConstraint(ctx Context, lVal, rVal interface{}) bool { 305 // Parse the version 306 var versionStr string 307 switch v := lVal.(type) { 308 case string: 309 versionStr = v 310 case int: 311 versionStr = fmt.Sprintf("%d", v) 312 default: 313 return false 314 } 315 316 // Parse the verison 317 vers, err := version.NewVersion(versionStr) 318 if err != nil { 319 return false 320 } 321 322 // Constraint must be a string 323 constraintStr, ok := rVal.(string) 324 if !ok { 325 return false 326 } 327 328 // Check the cache for a match 329 cache := ctx.ConstraintCache() 330 constraints := cache[constraintStr] 331 332 // Parse the constraints 333 if constraints == nil { 334 constraints, err = version.NewConstraint(constraintStr) 335 if err != nil { 336 return false 337 } 338 cache[constraintStr] = constraints 339 } 340 341 // Check the constraints against the version 342 return constraints.Check(vers) 343 } 344 345 // checkRegexpConstraint is used to compare a value on the 346 // left hand side with a regexp on the right hand side 347 func checkRegexpConstraint(ctx Context, lVal, rVal interface{}) bool { 348 // Ensure left-hand is string 349 lStr, ok := lVal.(string) 350 if !ok { 351 return false 352 } 353 354 // Regexp must be a string 355 regexpStr, ok := rVal.(string) 356 if !ok { 357 return false 358 } 359 360 // Check the cache 361 cache := ctx.RegexpCache() 362 re := cache[regexpStr] 363 364 // Parse the regexp 365 if re == nil { 366 var err error 367 re, err = regexp.Compile(regexpStr) 368 if err != nil { 369 return false 370 } 371 cache[regexpStr] = re 372 } 373 374 // Look for a match 375 return re.MatchString(lStr) 376 }