mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #127565 from pohly/dra-structured-all-mode-fix
DRA: structured "all" mode fix
This commit is contained in:
commit
cdf077da6f
@ -68,7 +68,7 @@ func NewAllocator(ctx context.Context,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClaimsToAllocate returns the claims that the allocated was created for.
|
// ClaimsToAllocate returns the claims that the allocator was created for.
|
||||||
func (a *Allocator) ClaimsToAllocate() []*resourceapi.ResourceClaim {
|
func (a *Allocator) ClaimsToAllocate() []*resourceapi.ResourceClaim {
|
||||||
return a.claimsToAllocate
|
return a.claimsToAllocate
|
||||||
}
|
}
|
||||||
@ -169,9 +169,13 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult []
|
|||||||
return nil, fmt.Errorf("claim %s, request %s: could not retrieve device class %s: %w", klog.KObj(claim), request.Name, request.DeviceClassName, err)
|
return nil, fmt.Errorf("claim %s, request %s: could not retrieve device class %s: %w", klog.KObj(claim), request.Name, request.DeviceClassName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start collecting information about the request.
|
||||||
|
// The class must be set and stored before calling isSelectable.
|
||||||
requestData := requestData{
|
requestData := requestData{
|
||||||
class: class,
|
class: class,
|
||||||
}
|
}
|
||||||
|
requestKey := requestIndices{claimIndex: claimIndex, requestIndex: requestIndex}
|
||||||
|
alloc.requestData[requestKey] = requestData
|
||||||
|
|
||||||
switch request.AllocationMode {
|
switch request.AllocationMode {
|
||||||
case resourceapi.DeviceAllocationModeExactCount:
|
case resourceapi.DeviceAllocationModeExactCount:
|
||||||
@ -190,7 +194,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult []
|
|||||||
|
|
||||||
for _, slice := range pool.Slices {
|
for _, slice := range pool.Slices {
|
||||||
for deviceIndex := range slice.Spec.Devices {
|
for deviceIndex := range slice.Spec.Devices {
|
||||||
selectable, err := alloc.isSelectable(requestIndices{claimIndex: claimIndex, requestIndex: requestIndex}, slice, deviceIndex)
|
selectable, err := alloc.isSelectable(requestKey, slice, deviceIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -205,7 +209,7 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult []
|
|||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("claim %s, request %s: unsupported count mode %s", klog.KObj(claim), request.Name, request.AllocationMode)
|
return nil, fmt.Errorf("claim %s, request %s: unsupported count mode %s", klog.KObj(claim), request.Name, request.AllocationMode)
|
||||||
}
|
}
|
||||||
alloc.requestData[requestIndices{claimIndex: claimIndex, requestIndex: requestIndex}] = requestData
|
alloc.requestData[requestKey] = requestData
|
||||||
numDevices += requestData.numDevices
|
numDevices += requestData.numDevices
|
||||||
}
|
}
|
||||||
alloc.logger.V(6).Info("Checked claim", "claim", klog.KObj(claim), "numDevices", numDevices)
|
alloc.logger.V(6).Info("Checked claim", "claim", klog.KObj(claim), "numDevices", numDevices)
|
||||||
@ -290,10 +294,13 @@ func (a *Allocator) Allocate(ctx context.Context, node *v1.Node) (finalResult []
|
|||||||
// All errors get created such that they can be returned by Allocate
|
// All errors get created such that they can be returned by Allocate
|
||||||
// without further wrapping.
|
// without further wrapping.
|
||||||
done, err := alloc.allocateOne(deviceIndices{})
|
done, err := alloc.allocateOne(deviceIndices{})
|
||||||
|
if errors.Is(err, errStop) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if errors.Is(err, errStop) || !done {
|
if !done {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +354,6 @@ type allocator struct {
|
|||||||
constraints [][]constraint // one list of constraints per claim
|
constraints [][]constraint // one list of constraints per claim
|
||||||
requestData map[requestIndices]requestData // one entry per request
|
requestData map[requestIndices]requestData // one entry per request
|
||||||
allocated map[DeviceID]bool
|
allocated map[DeviceID]bool
|
||||||
skippedUnknownDevice bool
|
|
||||||
result []*resourceapi.AllocationResult
|
result []*resourceapi.AllocationResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,20 +540,23 @@ func (alloc *allocator) allocateOne(r deviceIndices) (bool, error) {
|
|||||||
// For "all" devices we already know which ones we need. We
|
// For "all" devices we already know which ones we need. We
|
||||||
// just need to check whether we can use them.
|
// just need to check whether we can use them.
|
||||||
deviceWithID := requestData.allDevices[r.deviceIndex]
|
deviceWithID := requestData.allDevices[r.deviceIndex]
|
||||||
_, _, err := alloc.allocateDevice(r, deviceWithID.device, deviceWithID.DeviceID, true)
|
success, _, err := alloc.allocateDevice(r, deviceWithID.device, deviceWithID.DeviceID, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if !success {
|
||||||
|
// The order in which we allocate "all" devices doesn't matter,
|
||||||
|
// so we only try with the one which was up next. If we couldn't
|
||||||
|
// get all of them, then there is no solution and we have to stop.
|
||||||
|
return false, errStop
|
||||||
|
}
|
||||||
done, err := alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, deviceIndex: r.deviceIndex + 1})
|
done, err := alloc.allocateOne(deviceIndices{claimIndex: r.claimIndex, requestIndex: r.requestIndex, deviceIndex: r.deviceIndex + 1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The order in which we allocate "all" devices doesn't matter,
|
|
||||||
// so we only try with the one which was up next. If we couldn't
|
|
||||||
// get all of them, then there is no solution and we have to stop.
|
|
||||||
if !done {
|
if !done {
|
||||||
return false, errStop
|
// Backtrack.
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
return done, nil
|
return done, nil
|
||||||
}
|
}
|
||||||
@ -610,9 +619,6 @@ func (alloc *allocator) isSelectable(r requestIndices, slice *resourceapi.Resour
|
|||||||
device := slice.Spec.Devices[deviceIndex].Basic
|
device := slice.Spec.Devices[deviceIndex].Basic
|
||||||
if device == nil {
|
if device == nil {
|
||||||
// Must be some future, unknown device type. We cannot select it.
|
// Must be some future, unknown device type. We cannot select it.
|
||||||
// If we don't find anything else, then this will get reported
|
|
||||||
// in the final result, so remember that we skipped some device.
|
|
||||||
alloc.skippedUnknownDevice = true
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ const (
|
|||||||
slice2 = "slice-2"
|
slice2 = "slice-2"
|
||||||
device1 = "device-1"
|
device1 = "device-1"
|
||||||
device2 = "device-2"
|
device2 = "device-2"
|
||||||
|
device3 = "device-3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -581,11 +582,10 @@ func TestAllocator(t *testing.T) {
|
|||||||
|
|
||||||
expectResults: nil,
|
expectResults: nil,
|
||||||
},
|
},
|
||||||
"all-devices": {
|
"all-devices-single": {
|
||||||
claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
Name: req0,
|
Name: req0,
|
||||||
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
Count: 1,
|
|
||||||
DeviceClassName: classA,
|
DeviceClassName: classA,
|
||||||
})),
|
})),
|
||||||
classes: objects(class(classA, driverA)),
|
classes: objects(class(classA, driverA)),
|
||||||
@ -597,6 +597,25 @@ func TestAllocator(t *testing.T) {
|
|||||||
deviceAllocationResult(req0, driverA, pool1, device1),
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
)},
|
)},
|
||||||
},
|
},
|
||||||
|
"all-devices-many": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
})),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverA),
|
||||||
|
sliceWithOneDevice(slice1, node1, pool2, driverA),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool2, device1),
|
||||||
|
)},
|
||||||
|
},
|
||||||
"all-devices-of-the-incomplete-pool": {
|
"all-devices-of-the-incomplete-pool": {
|
||||||
claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
Name: req0,
|
Name: req0,
|
||||||
@ -618,6 +637,200 @@ func TestAllocator(t *testing.T) {
|
|||||||
expectResults: nil,
|
expectResults: nil,
|
||||||
expectError: gomega.MatchError(gomega.ContainSubstring("claim claim-0, request req-0: asks for all devices, but resource pool driver-a/pool-1 is currently being updated")),
|
expectError: gomega.MatchError(gomega.ContainSubstring("claim claim-0, request req-0: asks for all devices, but resource pool driver-a/pool-1 is currently being updated")),
|
||||||
},
|
},
|
||||||
|
"all-devices-plus-another": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classB,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
class(classB, driverB),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverA),
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverB),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
),
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverB, pool1, device1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all-devices-plus-another-reversed": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classB,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
class(classB, driverB),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverA),
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverB),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverB, pool1, device1),
|
||||||
|
),
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all-devices-many-plus-another": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classB,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
class(classB, driverB),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
slice(slice1, node1, pool1, driverA,
|
||||||
|
device(device1, nil, nil),
|
||||||
|
device(device2, nil, nil),
|
||||||
|
),
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverB),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device2),
|
||||||
|
),
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverB, pool1, device1),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all-devices-many-plus-another-reversed": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classB,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
class(classB, driverB),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
slice(slice1, node1, pool1, driverA,
|
||||||
|
device(device1, nil, nil),
|
||||||
|
device(device2, nil, nil),
|
||||||
|
),
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverB),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverB, pool1, device1),
|
||||||
|
),
|
||||||
|
allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device2),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all-devices-no-solution": {
|
||||||
|
// One device, two claims both trying to allocate it.
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverA),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
},
|
||||||
|
"all-devices-no-solution-reversed": {
|
||||||
|
// One device, two claims both trying to allocate it.
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim0, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeAll,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
claimWithRequests(claim1, nil, resourceapi.DeviceRequest{
|
||||||
|
Name: req0,
|
||||||
|
AllocationMode: resourceapi.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
DeviceClassName: classA,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
classes: objects(
|
||||||
|
class(classA, driverA),
|
||||||
|
),
|
||||||
|
slices: objects(
|
||||||
|
sliceWithOneDevice(slice1, node1, pool1, driverA),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
},
|
||||||
"network-attached-device": {
|
"network-attached-device": {
|
||||||
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
||||||
classes: objects(class(classA, driverA)),
|
classes: objects(class(classA, driverA)),
|
||||||
@ -765,7 +978,7 @@ func TestAllocator(t *testing.T) {
|
|||||||
claimsToAllocate: objects(claimWithRequests(
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
claim0,
|
claim0,
|
||||||
[]resourceapi.DeviceConstraint{{MatchAttribute: &intAttribute}},
|
[]resourceapi.DeviceConstraint{{MatchAttribute: &intAttribute}},
|
||||||
request(req0, classA, 3)),
|
request(req0, classA, 2)),
|
||||||
),
|
),
|
||||||
classes: objects(class(classA, driverA), class(classB, driverB)),
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
slices: objects(slice(slice1, node1, pool1, driverA,
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
@ -780,30 +993,36 @@ func TestAllocator(t *testing.T) {
|
|||||||
|
|
||||||
expectResults: nil,
|
expectResults: nil,
|
||||||
},
|
},
|
||||||
"with-constraint-not-matching-version-attribute": {
|
"with-constraint-not-matching-int-attribute-all-devices": {
|
||||||
claimsToAllocate: objects(claimWithRequests(
|
claimsToAllocate: objects(
|
||||||
claim0,
|
func() *resourceapi.ResourceClaim {
|
||||||
[]resourceapi.DeviceConstraint{{MatchAttribute: &versionAttribute}},
|
claim := claimWithRequests(
|
||||||
request(req0, classA, 3)),
|
claim0,
|
||||||
|
[]resourceapi.DeviceConstraint{{MatchAttribute: &intAttribute}},
|
||||||
|
request(req0, classA, 0),
|
||||||
|
)
|
||||||
|
claim.Spec.Devices.Requests[0].AllocationMode = resourceapi.DeviceAllocationModeAll
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
),
|
),
|
||||||
classes: objects(class(classA, driverA), class(classB, driverB)),
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
slices: objects(slice(slice1, node1, pool1, driverA,
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
"driverVersion": {VersionValue: ptr.To("1.0.0")},
|
"numa": {IntValue: ptr.To(int64(1))},
|
||||||
}),
|
}),
|
||||||
device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
"driverVersion": {VersionValue: ptr.To("2.0.0")},
|
"numa": {IntValue: ptr.To(int64(2))},
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
node: node(node1, region1),
|
node: node(node1, region1),
|
||||||
|
|
||||||
expectResults: nil,
|
expectError: gomega.MatchError(gomega.ContainSubstring("claim claim-0, request req-0: cannot add device driver-a/pool-1/device-2 because a claim constraint would not be satisfied")),
|
||||||
},
|
},
|
||||||
"with-constraint-not-matching-string-attribute": {
|
"with-constraint-not-matching-string-attribute": {
|
||||||
claimsToAllocate: objects(claimWithRequests(
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
claim0,
|
claim0,
|
||||||
[]resourceapi.DeviceConstraint{{MatchAttribute: &stringAttribute}},
|
[]resourceapi.DeviceConstraint{{MatchAttribute: &stringAttribute}},
|
||||||
request(req0, classA, 3)),
|
request(req0, classA, 2)),
|
||||||
),
|
),
|
||||||
classes: objects(class(classA, driverA), class(classB, driverB)),
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
slices: objects(slice(slice1, node1, pool1, driverA,
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
@ -822,7 +1041,7 @@ func TestAllocator(t *testing.T) {
|
|||||||
claimsToAllocate: objects(claimWithRequests(
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
claim0,
|
claim0,
|
||||||
[]resourceapi.DeviceConstraint{{MatchAttribute: &boolAttribute}},
|
[]resourceapi.DeviceConstraint{{MatchAttribute: &boolAttribute}},
|
||||||
request(req0, classA, 3)),
|
request(req0, classA, 2)),
|
||||||
),
|
),
|
||||||
classes: objects(class(classA, driverA), class(classB, driverB)),
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
slices: objects(slice(slice1, node1, pool1, driverA,
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
@ -837,6 +1056,95 @@ func TestAllocator(t *testing.T) {
|
|||||||
|
|
||||||
expectResults: nil,
|
expectResults: nil,
|
||||||
},
|
},
|
||||||
|
"with-constraint-not-matching-version-attribute": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
|
claim0,
|
||||||
|
[]resourceapi.DeviceConstraint{{MatchAttribute: &versionAttribute}},
|
||||||
|
request(req0, classA, 2)),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
|
device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("1.0.0")},
|
||||||
|
}),
|
||||||
|
device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("2.0.0")},
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: nil,
|
||||||
|
},
|
||||||
|
"with-constraint-for-request": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
|
claim0,
|
||||||
|
[]resourceapi.DeviceConstraint{
|
||||||
|
{
|
||||||
|
Requests: []string{req0},
|
||||||
|
MatchAttribute: &versionAttribute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request(req0, classA, 1),
|
||||||
|
request(req1, classA, 1),
|
||||||
|
)),
|
||||||
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
|
device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("1.0.0")},
|
||||||
|
}),
|
||||||
|
device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("2.0.0")},
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device1),
|
||||||
|
deviceAllocationResult(req1, driverA, pool1, device2),
|
||||||
|
)},
|
||||||
|
},
|
||||||
|
"with-constraint-for-request-retry": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(
|
||||||
|
claim0,
|
||||||
|
[]resourceapi.DeviceConstraint{
|
||||||
|
{
|
||||||
|
Requests: []string{req0},
|
||||||
|
MatchAttribute: &versionAttribute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MatchAttribute: &stringAttribute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request(req0, classA, 1),
|
||||||
|
request(req1, classA, 1),
|
||||||
|
)),
|
||||||
|
classes: objects(class(classA, driverA), class(classB, driverB)),
|
||||||
|
slices: objects(slice(slice1, node1, pool1, driverA,
|
||||||
|
// This device does not satisfy the second
|
||||||
|
// match attribute, so the allocator must
|
||||||
|
// backtrack.
|
||||||
|
device(device1, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("1.0.0")},
|
||||||
|
"stringAttribute": {StringValue: ptr.To("a")},
|
||||||
|
}),
|
||||||
|
device(device2, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("2.0.0")},
|
||||||
|
"stringAttribute": {StringValue: ptr.To("b")},
|
||||||
|
}),
|
||||||
|
device(device3, nil, map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
|
||||||
|
"driverVersion": {VersionValue: ptr.To("3.0.0")},
|
||||||
|
"stringAttribute": {StringValue: ptr.To("b")},
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectResults: []any{allocationResult(
|
||||||
|
localNodeSelector(node1),
|
||||||
|
deviceAllocationResult(req0, driverA, pool1, device2),
|
||||||
|
deviceAllocationResult(req1, driverA, pool1, device3),
|
||||||
|
)},
|
||||||
|
},
|
||||||
"with-class-device-config": {
|
"with-class-device-config": {
|
||||||
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
||||||
classes: objects(classWithConfig(classA, driverA, "classAttribute")),
|
classes: objects(classWithConfig(classA, driverA, "classAttribute")),
|
||||||
@ -869,6 +1177,138 @@ func TestAllocator(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"unknown-selector": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
func() *resourceapi.ResourceClaim {
|
||||||
|
claim := claim(claim0, req0, classA)
|
||||||
|
claim.Spec.Devices.Requests[0].Selectors = []resourceapi.DeviceSelector{
|
||||||
|
{ /* empty = unknown future selector */ },
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("CEL expression empty (unsupported selector type?)")),
|
||||||
|
},
|
||||||
|
"unknown-allocation-mode": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
func() *resourceapi.ResourceClaim {
|
||||||
|
claim := claim(claim0, req0, classA)
|
||||||
|
claim.Spec.Devices.Requests[0].AllocationMode = resourceapi.DeviceAllocationMode("future-mode")
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("unsupported count mode future-mode")),
|
||||||
|
},
|
||||||
|
"unknown-constraint": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
func() *resourceapi.ResourceClaim {
|
||||||
|
claim := claim(claim0, req0, classA)
|
||||||
|
claim.Spec.Devices.Constraints = []resourceapi.DeviceConstraint{
|
||||||
|
{ /* empty = unknown */ },
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("empty constraint (unsupported constraint type?)")),
|
||||||
|
},
|
||||||
|
"unknown-device": {
|
||||||
|
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(
|
||||||
|
func() *resourceapi.ResourceSlice {
|
||||||
|
slice := sliceWithOneDevice(slice1, node1, pool1, driverA)
|
||||||
|
slice.Spec.Devices[0].Basic = nil /* empty = unknown future extension */
|
||||||
|
return slice
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
node: node(node1, region1),
|
||||||
|
},
|
||||||
|
"invalid-CEL-one-device": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
func() *resourceapi.ResourceClaim {
|
||||||
|
claim := claim(claim0, req0, classA)
|
||||||
|
claim.Spec.Devices.Requests[0].Selectors = []resourceapi.DeviceSelector{
|
||||||
|
{CEL: &resourceapi.CELDeviceSelector{Expression: "noSuchVar"}},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("undeclared reference")),
|
||||||
|
},
|
||||||
|
"invalid-CEL-one-device-class": {
|
||||||
|
claimsToAllocate: objects(claim(claim0, req0, classA)),
|
||||||
|
classes: objects(
|
||||||
|
func() *resourceapi.DeviceClass {
|
||||||
|
c := class(classA, driverA)
|
||||||
|
c.Spec.Selectors[0].CEL.Expression = "noSuchVar"
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("undeclared reference")),
|
||||||
|
},
|
||||||
|
"invalid-CEL-all-devices": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
func() *resourceapi.ResourceClaim {
|
||||||
|
claim := claim(claim0, req0, classA)
|
||||||
|
claim.Spec.Devices.Requests[0].Selectors = []resourceapi.DeviceSelector{
|
||||||
|
{CEL: &resourceapi.CELDeviceSelector{Expression: "noSuchVar"}},
|
||||||
|
}
|
||||||
|
claim.Spec.Devices.Requests[0].AllocationMode = resourceapi.DeviceAllocationModeAll
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
slices: objects(sliceWithOneDevice(slice1, node1, pool1, driverA)),
|
||||||
|
node: node(node1, region1),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("undeclared reference")),
|
||||||
|
},
|
||||||
|
"too-many-devices-single-request": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, request(req0, classA, 500))),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("exceeds the claim limit")),
|
||||||
|
},
|
||||||
|
"many-devices-okay": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, request(req0, classA, resourceapi.AllocationResultsMaxSize))),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
},
|
||||||
|
"too-many-devices-total": {
|
||||||
|
claimsToAllocate: objects(
|
||||||
|
claimWithRequests(claim0, nil,
|
||||||
|
request(req0, classA, resourceapi.AllocationResultsMaxSize),
|
||||||
|
request(req1, classA, 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("exceeds the claim limit")),
|
||||||
|
},
|
||||||
|
"all-devices-invalid-CEL": {
|
||||||
|
claimsToAllocate: objects(claimWithRequests(claim0, nil, request(req0, classA, 500))),
|
||||||
|
classes: objects(class(classA, driverA)),
|
||||||
|
|
||||||
|
expectError: gomega.MatchError(gomega.ContainSubstring("exceeds the claim limit")),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testcases {
|
for name, tc := range testcases {
|
||||||
|
Loading…
Reference in New Issue
Block a user