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  }