@@ -27,6 +27,7 @@ import (
27
27
v1 "k8s.io/api/core/v1"
28
28
resourceapi "k8s.io/api/resource/v1beta1"
29
29
"k8s.io/apimachinery/pkg/util/sets"
30
+ "k8s.io/dynamic-resource-allocation/api"
30
31
draapi "k8s.io/dynamic-resource-allocation/api"
31
32
"k8s.io/dynamic-resource-allocation/cel"
32
33
"k8s.io/dynamic-resource-allocation/resourceclaim"
@@ -48,39 +49,39 @@ type deviceClassLister interface {
48
49
// available and the current state of the cluster (claims, classes, resource
49
50
// slices).
50
51
type Allocator struct {
51
- adminAccessEnabled bool
52
- prioritizedListEnabled bool
53
- deviceTaintsEnabled bool
54
- claimsToAllocate []* resourceapi.ResourceClaim
55
- allocatedDevices sets.Set [DeviceID ]
56
- classLister deviceClassLister
57
- slices []* resourceapi.ResourceSlice
58
- celCache * cel.Cache
52
+ features Features
53
+ claimsToAllocate []* resourceapi.ResourceClaim
54
+ allocatedDevices sets.Set [DeviceID ]
55
+ classLister deviceClassLister
56
+ slices []* resourceapi.ResourceSlice
57
+ celCache * cel.Cache
58
+ }
59
+
60
+ type Features struct {
61
+ AdminAccess bool
62
+ PrioritizedList bool
63
+ PartitionableDevices bool
59
64
}
60
65
61
66
// NewAllocator returns an allocator for a certain set of claims or an error if
62
67
// some problem was detected which makes it impossible to allocate claims.
63
68
//
64
69
// The returned Allocator can be used multiple times and is thread-safe.
65
70
func NewAllocator (ctx context.Context ,
66
- adminAccessEnabled bool ,
67
- prioritizedListEnabled bool ,
68
- deviceTaintsEnabled bool ,
71
+ features Features ,
69
72
claimsToAllocate []* resourceapi.ResourceClaim ,
70
73
allocatedDevices sets.Set [DeviceID ],
71
74
classLister deviceClassLister ,
72
75
slices []* resourceapi.ResourceSlice ,
73
76
celCache * cel.Cache ,
74
77
) (* Allocator , error ) {
75
78
return & Allocator {
76
- adminAccessEnabled : adminAccessEnabled ,
77
- prioritizedListEnabled : prioritizedListEnabled ,
78
- deviceTaintsEnabled : deviceTaintsEnabled ,
79
- claimsToAllocate : claimsToAllocate ,
80
- allocatedDevices : allocatedDevices ,
81
- classLister : classLister ,
82
- slices : slices ,
83
- celCache : celCache ,
79
+ features : features ,
80
+ claimsToAllocate : claimsToAllocate ,
81
+ allocatedDevices : allocatedDevices ,
82
+ classLister : classLister ,
83
+ slices : slices ,
84
+ celCache : celCache ,
84
85
}, nil
85
86
}
86
87
@@ -126,7 +127,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult []
126
127
defer alloc .logger .V (5 ).Info ("Done with allocation" , "success" , len (finalResult ) == len (alloc .claimsToAllocate ), "err" , finalErr )
127
128
128
129
// First determine all eligible pools.
129
- pools , err := GatherPools (ctx , alloc .slices , node )
130
+ pools , err := GatherPools (ctx , alloc .slices , node , a . features . PartitionableDevices )
130
131
if err != nil {
131
132
return nil , fmt .Errorf ("gather pool information: %w" , err )
132
133
}
@@ -513,9 +514,9 @@ type requestData struct {
513
514
}
514
515
515
516
type deviceWithID struct {
516
- id DeviceID
517
- basic * draapi.BasicDevice
518
- slice * draapi.ResourceSlice
517
+ id DeviceID
518
+ device * draapi.Device
519
+ slice * draapi.ResourceSlice
519
520
}
520
521
521
522
type internalAllocationResult struct {
@@ -526,6 +527,7 @@ type internalDeviceResult struct {
526
527
request string // name of the request (if no subrequests) or the subrequest
527
528
parentRequest string // name of the request which contains the subrequest, empty otherwise
528
529
id DeviceID
530
+ device * draapi.Device
529
531
slice * draapi.ResourceSlice
530
532
adminAccess * bool
531
533
}
@@ -621,7 +623,7 @@ func (m *matchAttributeConstraint) add(requestName, subRequestName string, devic
621
623
return true
622
624
}
623
625
624
- func (m * matchAttributeConstraint ) remove (requestName , subRequestName string , device * draapi.BasicDevice , deviceID DeviceID ) {
626
+ func (m * matchAttributeConstraint ) remove (requestName , subRequestName string , device * draapi.Device , deviceID DeviceID ) {
625
627
if m .requestNames .Len () > 0 && ! m .matches (requestName , subRequestName ) {
626
628
// Device not affected by constraint.
627
629
return
@@ -640,7 +642,7 @@ func (m *matchAttributeConstraint) matches(requestName, subRequestName string) b
640
642
}
641
643
}
642
644
643
- func lookupAttribute (device * draapi.BasicDevice , deviceID DeviceID , attributeName draapi.FullyQualifiedName ) * draapi.DeviceAttribute {
645
+ func lookupAttribute (device * draapi.Device , deviceID DeviceID , attributeName draapi.FullyQualifiedName ) * draapi.DeviceAttribute {
644
646
// Fully-qualified match?
645
647
if attr , ok := device .Attributes [draapi .QualifiedName (attributeName )]; ok {
646
648
return & attr
@@ -807,9 +809,9 @@ func (alloc *allocator) allocateOne(r deviceIndices, allocateSubRequest bool) (b
807
809
808
810
// Finally treat as allocated and move on to the next device.
809
811
device := deviceWithID {
810
- id : deviceID ,
811
- basic : slice .Spec .Devices [deviceIndex ]. Basic ,
812
- slice : slice ,
812
+ id : deviceID ,
813
+ device : & slice .Spec .Devices [deviceIndex ],
814
+ slice : slice ,
813
815
}
814
816
allocated , deallocate , err := alloc .allocateDevice (r , device , false )
815
817
if err != nil {
@@ -888,7 +890,7 @@ func (alloc *allocator) isSelectable(r requestIndices, requestData requestData,
888
890
889
891
}
890
892
891
- func (alloc * allocator ) selectorsMatch (r requestIndices , device * draapi.BasicDevice , deviceID DeviceID , class * resourceapi.DeviceClass , selectors []resourceapi.DeviceSelector ) (bool , error ) {
893
+ func (alloc * allocator ) selectorsMatch (r requestIndices , device * draapi.Device , deviceID DeviceID , class * resourceapi.DeviceClass , selectors []resourceapi.DeviceSelector ) (bool , error ) {
892
894
for i , selector := range selectors {
893
895
expr := alloc .celCache .GetOrCompile (selector .CEL .Expression )
894
896
if expr .Error != nil {
@@ -903,13 +905,15 @@ func (alloc *allocator) selectorsMatch(r requestIndices, device *draapi.BasicDev
903
905
return false , fmt .Errorf ("claim %s: selector #%d: CEL compile error: %w" , klog .KObj (alloc .claimsToAllocate [r .claimIndex ]), i , expr .Error )
904
906
}
905
907
906
- // If this conversion turns out to be expensive, the CEL package could be converted
907
- // to use unique strings.
908
- var d resourceapi.BasicDevice
909
- if err := draapi .Convert_api_BasicDevice_To_v1beta1_BasicDevice (device , & d , nil ); err != nil {
910
- return false , fmt .Errorf ("convert BasicDevice: %w" , err )
908
+ attributes := make (map [resourceapi.QualifiedName ]resourceapi.DeviceAttribute )
909
+ if err := draapi .Convert_api_Attributes_To_v1beta1_Attributes (device .Attributes , attributes ); err != nil {
910
+ return false , fmt .Errorf ("convert attributes: %w" , err )
911
+ }
912
+ capacity := make (map [resourceapi.QualifiedName ]resourceapi.DeviceCapacity )
913
+ if err := draapi .Convert_api_Capacity_To_v1beta1_Capacity (device .Capacity , capacity ); err != nil {
914
+ return false , fmt .Errorf ("convert capacity: %w" , err )
911
915
}
912
- matches , details , err := expr .DeviceMatches (alloc .ctx , cel.Device {Driver : deviceID .Driver .String (), Attributes : d . Attributes , Capacity : d . Capacity })
916
+ matches , details , err := expr .DeviceMatches (alloc .ctx , cel.Device {Driver : deviceID .Driver .String (), Attributes : attributes , Capacity : capacity })
913
917
if class != nil {
914
918
alloc .logger .V (7 ).Info ("CEL result" , "device" , deviceID , "class" , klog .KObj (class ), "selector" , i , "expression" , selector .CEL .Expression , "matches" , matches , "actualCost" , ptr .Deref (details .ActualCost (), 0 ), "err" , err )
915
919
} else {
@@ -949,6 +953,17 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
949
953
return false , nil , nil
950
954
}
951
955
956
+ // If a device consumes capacity from a capacity pool, verify that
957
+ // there is sufficient capacity available.
958
+ ok , err := alloc .checkAvailableCapacity (device )
959
+ if err != nil {
960
+ return false , nil , err
961
+ }
962
+ if ! ok {
963
+ alloc .logger .V (7 ).Info ("Insufficient capacity" , "device" , device .id )
964
+ return false , nil , nil
965
+ }
966
+
952
967
var parentRequestName string
953
968
var baseRequestName string
954
969
var subRequestName string
@@ -968,7 +983,7 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
968
983
969
984
// It's available. Now check constraints.
970
985
for i , constraint := range alloc .constraints [r .claimIndex ] {
971
- added := constraint .add (baseRequestName , subRequestName , device .basic , device .id )
986
+ added := constraint .add (baseRequestName , subRequestName , device .device , device .id )
972
987
if ! added {
973
988
if must {
974
989
// It does not make sense to declare a claim where a constraint prevents getting
@@ -978,7 +993,7 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
978
993
979
994
// Roll back for all previous constraints before we return.
980
995
for e := 0 ; e < i ; e ++ {
981
- alloc.constraints [r.claimIndex ][e ].remove (baseRequestName , subRequestName , device .basic , device .id )
996
+ alloc.constraints [r.claimIndex ][e ].remove (baseRequestName , subRequestName , device .device , device .id )
982
997
}
983
998
return false , nil , nil
984
999
}
@@ -994,6 +1009,7 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
994
1009
request : request .name (),
995
1010
parentRequest : parentRequestName ,
996
1011
id : device .id ,
1012
+ device : device .device ,
997
1013
slice : device .slice ,
998
1014
}
999
1015
if request .adminAccess () {
@@ -1004,7 +1020,7 @@ func (alloc *allocator) allocateDevice(r deviceIndices, device deviceWithID, mus
1004
1020
1005
1021
return true , func () {
1006
1022
for _ , constraint := range alloc .constraints [r .claimIndex ] {
1007
- constraint .remove (baseRequestName , subRequestName , device .basic , device .id )
1023
+ constraint .remove (baseRequestName , subRequestName , device .device , device .id )
1008
1024
}
1009
1025
if ! request .adminAccess () {
1010
1026
alloc .allocatingDevices [device .id ] = false
@@ -1033,48 +1049,126 @@ func taintTolerated(taint resourceapi.DeviceTaint, request requestAccessor) bool
1033
1049
return false
1034
1050
}
1035
1051
1052
+ func (alloc * allocator ) checkAvailableCapacity (device deviceWithID ) (bool , error ) {
1053
+ slice := device .slice
1054
+
1055
+ referencedCapacityPools := sets .New [api.UniqueString ]()
1056
+ for _ , consumedCapacity := range device .device .ConsumesCapacity {
1057
+ referencedCapacityPools .Insert (consumedCapacity .CapacityPool )
1058
+ }
1059
+
1060
+ // Create a structure that captures the initial capacity for all pools
1061
+ // referenced by the device.
1062
+ availableCapacities := make (map [api.UniqueString ]map [api.QualifiedName ]api.DeviceCapacity )
1063
+ for _ , capacityPool := range slice .Spec .CapacityPools {
1064
+ if ! referencedCapacityPools .Has (capacityPool .Name ) {
1065
+ continue
1066
+ }
1067
+ poolCapacity := make (map [api.QualifiedName ]api.DeviceCapacity )
1068
+ for name , cap := range capacityPool .Capacity {
1069
+ poolCapacity [name ] = cap
1070
+ }
1071
+ availableCapacities [capacityPool .Name ] = poolCapacity
1072
+ }
1073
+
1074
+ // Update the data structure to reflect capacity already in use.
1075
+ for _ , device := range slice .Spec .Devices {
1076
+ deviceID := DeviceID {
1077
+ Driver : slice .Spec .Driver ,
1078
+ Pool : slice .Spec .Pool .Name ,
1079
+ Device : device .Name ,
1080
+ }
1081
+ if ! (alloc .allocatedDevices .Has (deviceID ) || alloc .allocatingDevices [deviceID ]) {
1082
+ continue
1083
+ }
1084
+ for _ , consumedCapacity := range device .ConsumesCapacity {
1085
+ poolCapacity := availableCapacities [consumedCapacity .CapacityPool ]
1086
+ for name , cap := range consumedCapacity .Capacity {
1087
+ existingCap , ok := poolCapacity [name ]
1088
+ if ! ok {
1089
+ // Just continue for now, but this probably should be an error.
1090
+ continue
1091
+ }
1092
+ // This can potentially result in negative available capacity. That is fine,
1093
+ // we just treat it as no capacity available.
1094
+ existingCap .Value .Sub (cap .Value )
1095
+ poolCapacity [name ] = existingCap
1096
+ }
1097
+ }
1098
+ }
1099
+
1100
+ // Check if all consumed capacities for the device can be satisfied.
1101
+ for _ , deviceConsumedCapacity := range device .device .ConsumesCapacity {
1102
+ poolCapacity := availableCapacities [deviceConsumedCapacity .CapacityPool ]
1103
+ for name , cap := range deviceConsumedCapacity .Capacity {
1104
+ availableCap , found := poolCapacity [name ]
1105
+ // If the device requests a capacity that doesn't exist in
1106
+ // the pool, it can not be allocated.
1107
+ if ! found {
1108
+ return false , nil
1109
+ }
1110
+ // If the device requests more capacity than is available, it
1111
+ // can not be allocated.
1112
+ if availableCap .Value .Cmp (cap .Value ) < 0 {
1113
+ return false , nil
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ return true , nil
1119
+ }
1120
+
1036
1121
// createNodeSelector constructs a node selector for the allocation, if needed,
1037
1122
// otherwise it returns nil.
1038
1123
func (alloc * allocator ) createNodeSelector (result []internalDeviceResult ) (* v1.NodeSelector , error ) {
1039
1124
// Selector with one term. That term gets extended with additional
1040
1125
// requirements from the different devices.
1041
- nodeSelector := & v1.NodeSelector {
1126
+ ns := & v1.NodeSelector {
1042
1127
NodeSelectorTerms : []v1.NodeSelectorTerm {{}},
1043
1128
}
1044
1129
1045
1130
for i := range result {
1046
1131
slice := result [i ].slice
1047
- if slice .Spec .NodeName != draapi .NullUniqueString {
1132
+ var nodeName draapi.UniqueString
1133
+ var nodeSelector * v1.NodeSelector
1134
+ if slice .Spec .PerDeviceNodeSelection {
1135
+ nodeName = result [i ].device .NodeName
1136
+ nodeSelector = result [i ].device .NodeSelector
1137
+ } else {
1138
+ nodeName = slice .Spec .NodeName
1139
+ nodeSelector = slice .Spec .NodeSelector
1140
+ }
1141
+ if nodeName != draapi .NullUniqueString {
1048
1142
// At least one device is local to one node. This
1049
1143
// restricts the allocation to that node.
1050
1144
return & v1.NodeSelector {
1051
1145
NodeSelectorTerms : []v1.NodeSelectorTerm {{
1052
1146
MatchFields : []v1.NodeSelectorRequirement {{
1053
1147
Key : "metadata.name" ,
1054
1148
Operator : v1 .NodeSelectorOpIn ,
1055
- Values : []string {slice . Spec . NodeName .String ()},
1149
+ Values : []string {nodeName .String ()},
1056
1150
}},
1057
1151
}},
1058
1152
}, nil
1059
1153
}
1060
- if slice . Spec . NodeSelector != nil {
1061
- switch len (slice . Spec . NodeSelector .NodeSelectorTerms ) {
1154
+ if nodeSelector != nil {
1155
+ switch len (nodeSelector .NodeSelectorTerms ) {
1062
1156
case 0 :
1063
1157
// Nothing?
1064
1158
case 1 :
1065
1159
// Add all terms if they are not present already.
1066
- addNewNodeSelectorRequirements (slice . Spec . NodeSelector . NodeSelectorTerms [0 ].MatchFields , & nodeSelector .NodeSelectorTerms [0 ].MatchFields )
1067
- addNewNodeSelectorRequirements (slice . Spec . NodeSelector . NodeSelectorTerms [0 ].MatchExpressions , & nodeSelector .NodeSelectorTerms [0 ].MatchExpressions )
1160
+ addNewNodeSelectorRequirements (nodeSelector . NodeSelectorTerms [0 ].MatchFields , & ns .NodeSelectorTerms [0 ].MatchFields )
1161
+ addNewNodeSelectorRequirements (nodeSelector . NodeSelectorTerms [0 ].MatchExpressions , & ns .NodeSelectorTerms [0 ].MatchExpressions )
1068
1162
default :
1069
1163
// This shouldn't occur, validation must prevent creation of such slices.
1070
- return nil , fmt .Errorf ("unsupported ResourceSlice.NodeSelector with %d terms" , len (slice . Spec . NodeSelector .NodeSelectorTerms ))
1164
+ return nil , fmt .Errorf ("unsupported ResourceSlice.NodeSelector with %d terms" , len (nodeSelector .NodeSelectorTerms ))
1071
1165
}
1072
1166
}
1073
1167
}
1074
1168
1075
- if len (nodeSelector .NodeSelectorTerms [0 ].MatchFields ) > 0 || len (nodeSelector .NodeSelectorTerms [0 ].MatchExpressions ) > 0 {
1169
+ if len (ns .NodeSelectorTerms [0 ].MatchFields ) > 0 || len (ns .NodeSelectorTerms [0 ].MatchExpressions ) > 0 {
1076
1170
// We have a valid node selector.
1077
- return nodeSelector , nil
1171
+ return ns , nil
1078
1172
}
1079
1173
1080
1174
// Available everywhere.
0 commit comments