github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 }