github.com/bigcommerce/nomad@v0.9.3-bc/scheduler/device.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"math"
     7  
     8  	"github.com/hashicorp/nomad/nomad/structs"
     9  )
    10  
    11  // deviceAllocator is used to allocate devices to allocations. The allocator
    12  // tracks availability as to not double allocate devices.
    13  type deviceAllocator struct {
    14  	*structs.DeviceAccounter
    15  
    16  	ctx Context
    17  }
    18  
    19  // newDeviceAllocator returns a new device allocator. The node is used to
    20  // populate the set of available devices based on what healthy device instances
    21  // exist on the node.
    22  func newDeviceAllocator(ctx Context, n *structs.Node) *deviceAllocator {
    23  	return &deviceAllocator{
    24  		ctx:             ctx,
    25  		DeviceAccounter: structs.NewDeviceAccounter(n),
    26  	}
    27  }
    28  
    29  // AssignDevice takes a device request and returns an assignment as well as a
    30  // score for the assignment. If no assignment could be made, an error is
    31  // returned explaining why.
    32  func (d *deviceAllocator) AssignDevice(ask *structs.RequestedDevice) (out *structs.AllocatedDeviceResource, score float64, err error) {
    33  	// Try to hot path
    34  	if len(d.Devices) == 0 {
    35  		return nil, 0.0, fmt.Errorf("no devices available")
    36  	}
    37  	if ask.Count == 0 {
    38  		return nil, 0.0, fmt.Errorf("invalid request of zero devices")
    39  	}
    40  
    41  	// Hold the current best offer
    42  	var offer *structs.AllocatedDeviceResource
    43  	var offerScore float64
    44  	var matchedWeights float64
    45  
    46  	// Determine the devices that are feasible based on availability and
    47  	// constraints
    48  	for id, devInst := range d.Devices {
    49  		// Check if we have enough unused instances to use this
    50  		assignable := uint64(0)
    51  		for _, v := range devInst.Instances {
    52  			if v == 0 {
    53  				assignable++
    54  			}
    55  		}
    56  
    57  		// This device doesn't have enough instances
    58  		if assignable < ask.Count {
    59  			continue
    60  		}
    61  
    62  		// Check if the device works
    63  		if !nodeDeviceMatches(d.ctx, devInst.Device, ask) {
    64  			continue
    65  		}
    66  
    67  		// Score the choice
    68  		var choiceScore float64
    69  
    70  		// Track the sum of matched affinity weights in a separate variable
    71  		// We return this if this device had the best score compared to other devices considered
    72  		var sumMatchedWeights float64
    73  		if l := len(ask.Affinities); l != 0 {
    74  			totalWeight := 0.0
    75  			for _, a := range ask.Affinities {
    76  				// Resolve the targets
    77  				lVal, lOk := resolveDeviceTarget(a.LTarget, devInst.Device)
    78  				rVal, rOk := resolveDeviceTarget(a.RTarget, devInst.Device)
    79  
    80  				totalWeight += math.Abs(float64(a.Weight))
    81  
    82  				// Check if satisfied
    83  				if !checkAttributeAffinity(d.ctx, a.Operand, lVal, rVal, lOk, rOk) {
    84  					continue
    85  				}
    86  				choiceScore += float64(a.Weight)
    87  				sumMatchedWeights += float64(a.Weight)
    88  			}
    89  
    90  			// normalize
    91  			choiceScore /= totalWeight
    92  		}
    93  
    94  		// Only use the device if it is a higher score than we have already seen
    95  		if offer != nil && choiceScore < offerScore {
    96  			continue
    97  		}
    98  
    99  		// Set the new highest score
   100  		offerScore = choiceScore
   101  
   102  		// Set the new sum of matching affinity weights
   103  		matchedWeights = sumMatchedWeights
   104  
   105  		// Build the choice
   106  		offer = &structs.AllocatedDeviceResource{
   107  			Vendor:    id.Vendor,
   108  			Type:      id.Type,
   109  			Name:      id.Name,
   110  			DeviceIDs: make([]string, 0, ask.Count),
   111  		}
   112  
   113  		assigned := uint64(0)
   114  		for id, v := range devInst.Instances {
   115  			if v == 0 && assigned < ask.Count {
   116  				assigned++
   117  				offer.DeviceIDs = append(offer.DeviceIDs, id)
   118  				if assigned == ask.Count {
   119  					break
   120  				}
   121  			}
   122  		}
   123  	}
   124  
   125  	// Failed to find a match
   126  	if offer == nil {
   127  		return nil, 0.0, fmt.Errorf("no devices match request")
   128  	}
   129  
   130  	return offer, matchedWeights, nil
   131  }