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