Skip to content

Commit a716095

Browse files
committed
DRA: Update validation for Prioritized Alternatives in Device Requests
1 parent 68040a3 commit a716095

File tree

7 files changed

+628
-43
lines changed

7 files changed

+628
-43
lines changed

pkg/apis/resource/validation/validation.go

Lines changed: 119 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,41 @@ func validateDeviceClaim(deviceClaim *resource.DeviceClaim, fldPath *field.Path,
119119
return allErrs
120120
}
121121

122-
func gatherRequestNames(deviceClaim *resource.DeviceClaim) sets.Set[string] {
123-
requestNames := sets.New[string]()
122+
type requestNames map[string]sets.Set[string]
123+
124+
func (r requestNames) Has(s string) bool {
125+
segments := strings.Split(s, "/")
126+
// If there are more than one / in the string, we
127+
// know there can't be any match.
128+
if len(segments) > 2 {
129+
return false
130+
}
131+
// If the first segment doesn't have a match, we
132+
// don't need to check the other one.
133+
subRequestNames, found := r[segments[0]]
134+
if !found {
135+
return false
136+
}
137+
if len(segments) == 1 {
138+
return true
139+
}
140+
// If the first segment matched and we have another one,
141+
// check for a match for that too.
142+
return subRequestNames.Has(segments[1])
143+
}
144+
145+
func gatherRequestNames(deviceClaim *resource.DeviceClaim) requestNames {
146+
requestNames := make(requestNames)
124147
for _, request := range deviceClaim.Requests {
125-
requestNames.Insert(request.Name)
148+
if len(request.FirstAvailable) == 0 {
149+
requestNames[request.Name] = nil
150+
continue
151+
}
152+
subRequestNames := sets.New[string]()
153+
for _, subRequest := range request.FirstAvailable {
154+
subRequestNames.Insert(subRequest.Name)
155+
}
156+
requestNames[request.Name] = subRequestNames
126157
}
127158
return requestNames
128159
}
@@ -138,31 +169,81 @@ func gatherAllocatedDevices(allocationResult *resource.DeviceAllocationResult) s
138169

139170
func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path, stored bool) field.ErrorList {
140171
allErrs := validateRequestName(request.Name, fldPath.Child("name"))
141-
if request.DeviceClassName == "" {
142-
allErrs = append(allErrs, field.Required(fldPath.Child("deviceClassName"), ""))
143-
} else {
144-
allErrs = append(allErrs, validateDeviceClassName(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
172+
if len(request.FirstAvailable) > 0 {
173+
if request.DeviceClassName != "" {
174+
allErrs = append(allErrs, field.Invalid(fldPath.Child("deviceClassName"), request.DeviceClassName, "must not be specified when firstAvailable is set"))
175+
}
176+
if request.Selectors != nil {
177+
allErrs = append(allErrs, field.Invalid(fldPath.Child("selectors"), request.Selectors, "must not be specified when firstAvailable is set"))
178+
}
179+
if request.AllocationMode != "" {
180+
allErrs = append(allErrs, field.Invalid(fldPath.Child("allocationMode"), request.AllocationMode, "must not be specified when firstAvailable is set"))
181+
}
182+
if request.Count != 0 {
183+
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must not be specified when firstAvailable is set"))
184+
}
185+
if request.AdminAccess != nil {
186+
allErrs = append(allErrs, field.Invalid(fldPath.Child("adminAccess"), request.AdminAccess, "must not be specified when firstAvailable is set"))
187+
}
188+
allErrs = append(allErrs, validateSet(request.FirstAvailable, resource.FirstAvailableDeviceRequestMaxSize,
189+
func(subRequest resource.DeviceSubRequest, fldPath *field.Path) field.ErrorList {
190+
return validateDeviceSubRequest(subRequest, fldPath, stored)
191+
},
192+
func(subRequest resource.DeviceSubRequest) (string, string) {
193+
return subRequest.Name, "name"
194+
},
195+
fldPath.Child("firstAvailable"))...)
196+
return allErrs
145197
}
146-
allErrs = append(allErrs, validateSlice(request.Selectors, resource.DeviceSelectorsMaxSize,
147-
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
148-
return validateSelector(selector, fldPath, stored)
149-
},
150-
fldPath.Child("selectors"))...)
151-
switch request.AllocationMode {
198+
allErrs = append(allErrs, validateDeviceClass(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
199+
allErrs = append(allErrs, validateSelectorSlice(request.Selectors, fldPath.Child("selectors"), stored)...)
200+
allErrs = append(allErrs, validateDeviceAllocationMode(request.AllocationMode, request.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
201+
return allErrs
202+
}
203+
204+
func validateDeviceSubRequest(subRequest resource.DeviceSubRequest, fldPath *field.Path, stored bool) field.ErrorList {
205+
allErrs := validateRequestName(subRequest.Name, fldPath.Child("name"))
206+
allErrs = append(allErrs, validateDeviceClass(subRequest.DeviceClassName, fldPath.Child("deviceClassName"))...)
207+
allErrs = append(allErrs, validateSelectorSlice(subRequest.Selectors, fldPath.Child("selectors"), stored)...)
208+
allErrs = append(allErrs, validateDeviceAllocationMode(subRequest.AllocationMode, subRequest.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
209+
return allErrs
210+
}
211+
212+
func validateDeviceAllocationMode(deviceAllocationMode resource.DeviceAllocationMode, count int64, allocModeFldPath, countFldPath *field.Path) field.ErrorList {
213+
var allErrs field.ErrorList
214+
switch deviceAllocationMode {
152215
case resource.DeviceAllocationModeAll:
153-
if request.Count != 0 {
154-
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, fmt.Sprintf("must not be specified when allocationMode is '%s'", request.AllocationMode)))
216+
if count != 0 {
217+
allErrs = append(allErrs, field.Invalid(countFldPath, count, fmt.Sprintf("must not be specified when allocationMode is '%s'", deviceAllocationMode)))
155218
}
156219
case resource.DeviceAllocationModeExactCount:
157-
if request.Count <= 0 {
158-
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must be greater than zero"))
220+
if count <= 0 {
221+
allErrs = append(allErrs, field.Invalid(countFldPath, count, "must be greater than zero"))
159222
}
160223
default:
161-
allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), request.AllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}))
224+
allErrs = append(allErrs, field.NotSupported(allocModeFldPath, deviceAllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}))
225+
}
226+
return allErrs
227+
}
228+
229+
func validateDeviceClass(deviceClass string, fldPath *field.Path) field.ErrorList {
230+
var allErrs field.ErrorList
231+
if deviceClass == "" {
232+
allErrs = append(allErrs, field.Required(fldPath, ""))
233+
} else {
234+
allErrs = append(allErrs, validateDeviceClassName(deviceClass, fldPath)...)
162235
}
163236
return allErrs
164237
}
165238

239+
func validateSelectorSlice(selectors []resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
240+
return validateSlice(selectors, resource.DeviceSelectorsMaxSize,
241+
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
242+
return validateSelector(selector, fldPath, stored)
243+
},
244+
fldPath)
245+
}
246+
166247
func validateSelector(selector resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
167248
var allErrs field.ErrorList
168249
if selector.CEL == nil {
@@ -210,7 +291,7 @@ func convertCELErrorToValidationError(fldPath *field.Path, expression string, er
210291
return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err))
211292
}
212293

213-
func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
294+
func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames requestNames) field.ErrorList {
214295
var allErrs field.ErrorList
215296
allErrs = append(allErrs, validateSet(constraint.Requests, resource.DeviceRequestsMaxSize,
216297
func(name string, fldPath *field.Path) field.ErrorList {
@@ -225,7 +306,7 @@ func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *fie
225306
return allErrs
226307
}
227308

228-
func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
309+
func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
229310
var allErrs field.ErrorList
230311
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
231312
func(name string, fldPath *field.Path) field.ErrorList {
@@ -235,10 +316,20 @@ func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration,
235316
return allErrs
236317
}
237318

238-
func validateRequestNameRef(name string, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
239-
allErrs := validateRequestName(name, fldPath)
319+
func validateRequestNameRef(name string, fldPath *field.Path, requestNames requestNames) field.ErrorList {
320+
var allErrs field.ErrorList
321+
segments := strings.Split(name, "/")
322+
if len(segments) > 2 {
323+
allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"))
324+
return allErrs
325+
}
326+
327+
for i := range segments {
328+
allErrs = append(allErrs, validateRequestName(segments[i], fldPath)...)
329+
}
330+
240331
if !requestNames.Has(name) {
241-
allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim"))
332+
allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"))
242333
}
243334
return allErrs
244335
}
@@ -260,7 +351,7 @@ func validateOpaqueConfiguration(config resource.OpaqueDeviceConfiguration, fldP
260351
return allErrs
261352
}
262353

263-
func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames sets.Set[string], fldPath *field.Path) field.ErrorList {
354+
func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames requestNames, fldPath *field.Path) field.ErrorList {
264355
var allErrs field.ErrorList
265356
allErrs = append(allErrs, validateSet(status.ReservedFor, resource.ResourceClaimReservedForMaxSize,
266357
validateResourceClaimUserReference,
@@ -328,7 +419,7 @@ func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerRefere
328419
// validateAllocationResult enforces constraints for *new* results, which in at
329420
// least one case (admin access) are more strict than before. Therefore it
330421
// may not be called to re-validate results which were stored earlier.
331-
func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
422+
func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
332423
var allErrs field.ErrorList
333424
allErrs = append(allErrs, validateDeviceAllocationResult(allocation.Devices, fldPath.Child("devices"), requestNames, stored)...)
334425
if allocation.NodeSelector != nil {
@@ -337,7 +428,7 @@ func validateAllocationResult(allocation *resource.AllocationResult, fldPath *fi
337428
return allErrs
338429
}
339430

340-
func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
431+
func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
341432
var allErrs field.ErrorList
342433
allErrs = append(allErrs, validateSlice(allocation.Results, resource.AllocationResultsMaxSize,
343434
func(result resource.DeviceRequestAllocationResult, fldPath *field.Path) field.ErrorList {
@@ -351,7 +442,7 @@ func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult,
351442
return allErrs
352443
}
353444

354-
func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
445+
func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames requestNames) field.ErrorList {
355446
var allErrs field.ErrorList
356447
allErrs = append(allErrs, validateRequestNameRef(result.Request, fldPath.Child("request"), requestNames)...)
357448
allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"))...)
@@ -360,7 +451,7 @@ func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocati
360451
return allErrs
361452
}
362453

363-
func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
454+
func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
364455
var allErrs field.ErrorList
365456
allErrs = append(allErrs, validateAllocationConfigSource(config.Source, fldPath.Child("source"))...)
366457
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,

0 commit comments

Comments
 (0)