mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-14 14:23:37 +00:00
DRA: Update validation for Prioritized Alternatives in Device Requests
This commit is contained in:
parent
68040a3173
commit
a716095a8a
@ -119,10 +119,41 @@ func validateDeviceClaim(deviceClaim *resource.DeviceClaim, fldPath *field.Path,
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func gatherRequestNames(deviceClaim *resource.DeviceClaim) sets.Set[string] {
|
type requestNames map[string]sets.Set[string]
|
||||||
requestNames := sets.New[string]()
|
|
||||||
|
func (r requestNames) Has(s string) bool {
|
||||||
|
segments := strings.Split(s, "/")
|
||||||
|
// If there are more than one / in the string, we
|
||||||
|
// know there can't be any match.
|
||||||
|
if len(segments) > 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the first segment doesn't have a match, we
|
||||||
|
// don't need to check the other one.
|
||||||
|
subRequestNames, found := r[segments[0]]
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(segments) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// If the first segment matched and we have another one,
|
||||||
|
// check for a match for that too.
|
||||||
|
return subRequestNames.Has(segments[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatherRequestNames(deviceClaim *resource.DeviceClaim) requestNames {
|
||||||
|
requestNames := make(requestNames)
|
||||||
for _, request := range deviceClaim.Requests {
|
for _, request := range deviceClaim.Requests {
|
||||||
requestNames.Insert(request.Name)
|
if len(request.FirstAvailable) == 0 {
|
||||||
|
requestNames[request.Name] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
subRequestNames := sets.New[string]()
|
||||||
|
for _, subRequest := range request.FirstAvailable {
|
||||||
|
subRequestNames.Insert(subRequest.Name)
|
||||||
|
}
|
||||||
|
requestNames[request.Name] = subRequestNames
|
||||||
}
|
}
|
||||||
return requestNames
|
return requestNames
|
||||||
}
|
}
|
||||||
@ -138,29 +169,79 @@ func gatherAllocatedDevices(allocationResult *resource.DeviceAllocationResult) s
|
|||||||
|
|
||||||
func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path, stored bool) field.ErrorList {
|
func validateDeviceRequest(request resource.DeviceRequest, fldPath *field.Path, stored bool) field.ErrorList {
|
||||||
allErrs := validateRequestName(request.Name, fldPath.Child("name"))
|
allErrs := validateRequestName(request.Name, fldPath.Child("name"))
|
||||||
if request.DeviceClassName == "" {
|
if len(request.FirstAvailable) > 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("deviceClassName"), ""))
|
if request.DeviceClassName != "" {
|
||||||
} else {
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("deviceClassName"), request.DeviceClassName, "must not be specified when firstAvailable is set"))
|
||||||
allErrs = append(allErrs, validateDeviceClassName(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
|
}
|
||||||
|
if request.Selectors != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("selectors"), request.Selectors, "must not be specified when firstAvailable is set"))
|
||||||
|
}
|
||||||
|
if request.AllocationMode != "" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("allocationMode"), request.AllocationMode, "must not be specified when firstAvailable is set"))
|
||||||
|
}
|
||||||
|
if request.Count != 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must not be specified when firstAvailable is set"))
|
||||||
|
}
|
||||||
|
if request.AdminAccess != nil {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("adminAccess"), request.AdminAccess, "must not be specified when firstAvailable is set"))
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, validateSet(request.FirstAvailable, resource.FirstAvailableDeviceRequestMaxSize,
|
||||||
|
func(subRequest resource.DeviceSubRequest, fldPath *field.Path) field.ErrorList {
|
||||||
|
return validateDeviceSubRequest(subRequest, fldPath, stored)
|
||||||
|
},
|
||||||
|
func(subRequest resource.DeviceSubRequest) (string, string) {
|
||||||
|
return subRequest.Name, "name"
|
||||||
|
},
|
||||||
|
fldPath.Child("firstAvailable"))...)
|
||||||
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateSlice(request.Selectors, resource.DeviceSelectorsMaxSize,
|
allErrs = append(allErrs, validateDeviceClass(request.DeviceClassName, fldPath.Child("deviceClassName"))...)
|
||||||
|
allErrs = append(allErrs, validateSelectorSlice(request.Selectors, fldPath.Child("selectors"), stored)...)
|
||||||
|
allErrs = append(allErrs, validateDeviceAllocationMode(request.AllocationMode, request.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDeviceSubRequest(subRequest resource.DeviceSubRequest, fldPath *field.Path, stored bool) field.ErrorList {
|
||||||
|
allErrs := validateRequestName(subRequest.Name, fldPath.Child("name"))
|
||||||
|
allErrs = append(allErrs, validateDeviceClass(subRequest.DeviceClassName, fldPath.Child("deviceClassName"))...)
|
||||||
|
allErrs = append(allErrs, validateSelectorSlice(subRequest.Selectors, fldPath.Child("selectors"), stored)...)
|
||||||
|
allErrs = append(allErrs, validateDeviceAllocationMode(subRequest.AllocationMode, subRequest.Count, fldPath.Child("allocationMode"), fldPath.Child("count"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDeviceAllocationMode(deviceAllocationMode resource.DeviceAllocationMode, count int64, allocModeFldPath, countFldPath *field.Path) field.ErrorList {
|
||||||
|
var allErrs field.ErrorList
|
||||||
|
switch deviceAllocationMode {
|
||||||
|
case resource.DeviceAllocationModeAll:
|
||||||
|
if count != 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(countFldPath, count, fmt.Sprintf("must not be specified when allocationMode is '%s'", deviceAllocationMode)))
|
||||||
|
}
|
||||||
|
case resource.DeviceAllocationModeExactCount:
|
||||||
|
if count <= 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(countFldPath, count, "must be greater than zero"))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
allErrs = append(allErrs, field.NotSupported(allocModeFldPath, deviceAllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateDeviceClass(deviceClass string, fldPath *field.Path) field.ErrorList {
|
||||||
|
var allErrs field.ErrorList
|
||||||
|
if deviceClass == "" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath, ""))
|
||||||
|
} else {
|
||||||
|
allErrs = append(allErrs, validateDeviceClassName(deviceClass, fldPath)...)
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSelectorSlice(selectors []resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
|
||||||
|
return validateSlice(selectors, resource.DeviceSelectorsMaxSize,
|
||||||
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
|
func(selector resource.DeviceSelector, fldPath *field.Path) field.ErrorList {
|
||||||
return validateSelector(selector, fldPath, stored)
|
return validateSelector(selector, fldPath, stored)
|
||||||
},
|
},
|
||||||
fldPath.Child("selectors"))...)
|
fldPath)
|
||||||
switch request.AllocationMode {
|
|
||||||
case resource.DeviceAllocationModeAll:
|
|
||||||
if request.Count != 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, fmt.Sprintf("must not be specified when allocationMode is '%s'", request.AllocationMode)))
|
|
||||||
}
|
|
||||||
case resource.DeviceAllocationModeExactCount:
|
|
||||||
if request.Count <= 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("count"), request.Count, "must be greater than zero"))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), request.AllocationMode, []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}))
|
|
||||||
}
|
|
||||||
return allErrs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSelector(selector resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
|
func validateSelector(selector resource.DeviceSelector, fldPath *field.Path, stored bool) field.ErrorList {
|
||||||
@ -210,7 +291,7 @@ func convertCELErrorToValidationError(fldPath *field.Path, expression string, er
|
|||||||
return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err))
|
return field.InternalError(fldPath, fmt.Errorf("unsupported error type: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
|
func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *field.Path, requestNames requestNames) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateSet(constraint.Requests, resource.DeviceRequestsMaxSize,
|
allErrs = append(allErrs, validateSet(constraint.Requests, resource.DeviceRequestsMaxSize,
|
||||||
func(name string, fldPath *field.Path) field.ErrorList {
|
func(name string, fldPath *field.Path) field.ErrorList {
|
||||||
@ -225,7 +306,7 @@ func validateDeviceConstraint(constraint resource.DeviceConstraint, fldPath *fie
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
|
func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
|
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
|
||||||
func(name string, fldPath *field.Path) field.ErrorList {
|
func(name string, fldPath *field.Path) field.ErrorList {
|
||||||
@ -235,10 +316,20 @@ func validateDeviceClaimConfiguration(config resource.DeviceClaimConfiguration,
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRequestNameRef(name string, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
|
func validateRequestNameRef(name string, fldPath *field.Path, requestNames requestNames) field.ErrorList {
|
||||||
allErrs := validateRequestName(name, fldPath)
|
var allErrs field.ErrorList
|
||||||
|
segments := strings.Split(name, "/")
|
||||||
|
if len(segments) > 2 {
|
||||||
|
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 '/'"))
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range segments {
|
||||||
|
allErrs = append(allErrs, validateRequestName(segments[i], fldPath)...)
|
||||||
|
}
|
||||||
|
|
||||||
if !requestNames.Has(name) {
|
if !requestNames.Has(name) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, name, "must be the name of a request in the claim"))
|
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 '/'"))
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -260,7 +351,7 @@ func validateOpaqueConfiguration(config resource.OpaqueDeviceConfiguration, fldP
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames sets.Set[string], fldPath *field.Path) field.ErrorList {
|
func validateResourceClaimStatusUpdate(status, oldStatus *resource.ResourceClaimStatus, claimDeleted bool, requestNames requestNames, fldPath *field.Path) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateSet(status.ReservedFor, resource.ResourceClaimReservedForMaxSize,
|
allErrs = append(allErrs, validateSet(status.ReservedFor, resource.ResourceClaimReservedForMaxSize,
|
||||||
validateResourceClaimUserReference,
|
validateResourceClaimUserReference,
|
||||||
@ -328,7 +419,7 @@ func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerRefere
|
|||||||
// validateAllocationResult enforces constraints for *new* results, which in at
|
// validateAllocationResult enforces constraints for *new* results, which in at
|
||||||
// least one case (admin access) are more strict than before. Therefore it
|
// least one case (admin access) are more strict than before. Therefore it
|
||||||
// may not be called to re-validate results which were stored earlier.
|
// may not be called to re-validate results which were stored earlier.
|
||||||
func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
|
func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateDeviceAllocationResult(allocation.Devices, fldPath.Child("devices"), requestNames, stored)...)
|
allErrs = append(allErrs, validateDeviceAllocationResult(allocation.Devices, fldPath.Child("devices"), requestNames, stored)...)
|
||||||
if allocation.NodeSelector != nil {
|
if allocation.NodeSelector != nil {
|
||||||
@ -337,7 +428,7 @@ func validateAllocationResult(allocation *resource.AllocationResult, fldPath *fi
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
|
func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateSlice(allocation.Results, resource.AllocationResultsMaxSize,
|
allErrs = append(allErrs, validateSlice(allocation.Results, resource.AllocationResultsMaxSize,
|
||||||
func(result resource.DeviceRequestAllocationResult, fldPath *field.Path) field.ErrorList {
|
func(result resource.DeviceRequestAllocationResult, fldPath *field.Path) field.ErrorList {
|
||||||
@ -351,7 +442,7 @@ func validateDeviceAllocationResult(allocation resource.DeviceAllocationResult,
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames sets.Set[string]) field.ErrorList {
|
func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocationResult, fldPath *field.Path, requestNames requestNames) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateRequestNameRef(result.Request, fldPath.Child("request"), requestNames)...)
|
allErrs = append(allErrs, validateRequestNameRef(result.Request, fldPath.Child("request"), requestNames)...)
|
||||||
allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"))...)
|
allErrs = append(allErrs, validateDriverName(result.Driver, fldPath.Child("driver"))...)
|
||||||
@ -360,7 +451,7 @@ func validateDeviceRequestAllocationResult(result resource.DeviceRequestAllocati
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames sets.Set[string], stored bool) field.ErrorList {
|
func validateDeviceAllocationConfiguration(config resource.DeviceAllocationConfiguration, fldPath *field.Path, requestNames requestNames, stored bool) field.ErrorList {
|
||||||
var allErrs field.ErrorList
|
var allErrs field.ErrorList
|
||||||
allErrs = append(allErrs, validateAllocationConfigSource(config.Source, fldPath.Child("source"))...)
|
allErrs = append(allErrs, validateAllocationConfigSource(config.Source, fldPath.Child("source"))...)
|
||||||
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
|
allErrs = append(allErrs, validateSet(config.Requests, resource.DeviceRequestsMaxSize,
|
||||||
|
@ -46,13 +46,17 @@ func testClaim(name, namespace string, spec resource.ResourceClaimSpec) *resourc
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goodName = "foo"
|
goodName = "foo"
|
||||||
badName = "!@#$%^"
|
goodName2 = "bar"
|
||||||
goodNS = "ns"
|
badName = "!@#$%^"
|
||||||
|
goodNS = "ns"
|
||||||
|
badSubrequestName = "&^%$"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validClaimSpec = resource.ResourceClaimSpec{
|
badRequestFormat = fmt.Sprintf("%s/%s/%s", goodName, goodName, goodName)
|
||||||
|
badFullSubrequestName = fmt.Sprintf("%s/%s", badName, badSubrequestName)
|
||||||
|
validClaimSpec = resource.ResourceClaimSpec{
|
||||||
Devices: resource.DeviceClaim{
|
Devices: resource.DeviceClaim{
|
||||||
Requests: []resource.DeviceRequest{{
|
Requests: []resource.DeviceRequest{{
|
||||||
Name: goodName,
|
Name: goodName,
|
||||||
@ -62,6 +66,34 @@ var (
|
|||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
validClaimSpecWithFirstAvailable = resource.ResourceClaimSpec{
|
||||||
|
Devices: resource.DeviceClaim{
|
||||||
|
Requests: []resource.DeviceRequest{{
|
||||||
|
Name: goodName,
|
||||||
|
FirstAvailable: []resource.DeviceSubRequest{
|
||||||
|
{
|
||||||
|
Name: goodName,
|
||||||
|
DeviceClassName: goodName,
|
||||||
|
AllocationMode: resource.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: goodName2,
|
||||||
|
DeviceClassName: goodName,
|
||||||
|
AllocationMode: resource.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
validSelector = []resource.DeviceSelector{
|
||||||
|
{
|
||||||
|
CEL: &resource.CELDeviceSelector{
|
||||||
|
Expression: `device.driver == "dra.example.com"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
validClaim = testClaim(goodName, goodNS, validClaimSpec)
|
validClaim = testClaim(goodName, goodNS, validClaimSpec)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -225,14 +257,14 @@ func TestValidateClaim(t *testing.T) {
|
|||||||
wantFailures: field.ErrorList{
|
wantFailures: field.ErrorList{
|
||||||
field.TooMany(field.NewPath("spec", "devices", "requests"), resource.DeviceRequestsMaxSize+1, resource.DeviceRequestsMaxSize),
|
field.TooMany(field.NewPath("spec", "devices", "requests"), resource.DeviceRequestsMaxSize+1, resource.DeviceRequestsMaxSize),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
|
field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"),
|
||||||
field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"),
|
field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"),
|
||||||
field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""),
|
field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""),
|
||||||
field.TooMany(field.NewPath("spec", "devices", "constraints"), resource.DeviceConstraintsMaxSize+1, resource.DeviceConstraintsMaxSize),
|
field.TooMany(field.NewPath("spec", "devices", "constraints"), resource.DeviceConstraintsMaxSize+1, resource.DeviceConstraintsMaxSize),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
field.TooMany(field.NewPath("spec", "devices", "config"), resource.DeviceConfigMaxSize+1, resource.DeviceConfigMaxSize),
|
field.TooMany(field.NewPath("spec", "devices", "config"), resource.DeviceConfigMaxSize+1, resource.DeviceConfigMaxSize),
|
||||||
},
|
},
|
||||||
claim: func() *resource.ResourceClaim {
|
claim: func() *resource.ResourceClaim {
|
||||||
@ -312,13 +344,13 @@ func TestValidateClaim(t *testing.T) {
|
|||||||
"invalid-spec": {
|
"invalid-spec": {
|
||||||
wantFailures: field.ErrorList{
|
wantFailures: field.ErrorList{
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
|
field.TypeInvalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), "missing-domain", "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_' (e.g. 'my_name', or 'MY_NAME', or 'MyName', regex used for validation is '[A-Za-z_][A-Za-z0-9_]*')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"),
|
field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("matchAttribute"), resource.FullyQualifiedName("missing-domain"), "must include a domain"),
|
||||||
field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"),
|
field.Required(field.NewPath("spec", "devices", "constraints").Index(1).Child("matchAttribute"), "name required"),
|
||||||
field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""),
|
field.Required(field.NewPath("spec", "devices", "constraints").Index(2).Child("matchAttribute"), ""),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
},
|
},
|
||||||
claim: func() *resource.ResourceClaim {
|
claim: func() *resource.ResourceClaim {
|
||||||
claim := testClaim(goodName, goodNS, validClaimSpec)
|
claim := testClaim(goodName, goodNS, validClaimSpec)
|
||||||
@ -537,6 +569,176 @@ func TestValidateClaim(t *testing.T) {
|
|||||||
return claim
|
return claim
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
"prioritized-list-valid": {
|
||||||
|
wantFailures: nil,
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-field-on-parent": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("deviceClassName"), goodName, "must not be specified when firstAvailable is set"),
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("selectors"), validSelector, "must not be specified when firstAvailable is set"),
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("allocationMode"), resource.DeviceAllocationModeAll, "must not be specified when firstAvailable is set"),
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("count"), int64(2), "must not be specified when firstAvailable is set"),
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("adminAccess"), ptr.To(true), "must not be specified when firstAvailable is set"),
|
||||||
|
},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Requests[0].DeviceClassName = goodName
|
||||||
|
claim.Spec.Devices.Requests[0].Selectors = validSelector
|
||||||
|
claim.Spec.Devices.Requests[0].AllocationMode = resource.DeviceAllocationModeAll
|
||||||
|
claim.Spec.Devices.Requests[0].Count = 2
|
||||||
|
claim.Spec.Devices.Requests[0].AdminAccess = ptr.To(true)
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-invalid-nested-request": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("name"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
|
field.Required(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), ""),
|
||||||
|
field.NotSupported(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("allocationMode"), resource.DeviceAllocationMode(""), []resource.DeviceAllocationMode{resource.DeviceAllocationModeAll, resource.DeviceAllocationModeExactCount}),
|
||||||
|
},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Requests[0].FirstAvailable[0] = resource.DeviceSubRequest{
|
||||||
|
Name: badName,
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-nested-requests-same-name": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Duplicate(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable").Index(1).Child("name"), "foo"),
|
||||||
|
},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Requests[0].FirstAvailable[1].Name = goodName
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-too-many-subrequests": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.TooMany(field.NewPath("spec", "devices", "requests").Index(0).Child("firstAvailable"), 9, 8),
|
||||||
|
},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpec)
|
||||||
|
claim.Spec.Devices.Requests[0].DeviceClassName = ""
|
||||||
|
claim.Spec.Devices.Requests[0].AllocationMode = ""
|
||||||
|
claim.Spec.Devices.Requests[0].Count = 0
|
||||||
|
var subRequests []resource.DeviceSubRequest
|
||||||
|
for i := 0; i <= 8; i++ {
|
||||||
|
subRequests = append(subRequests, resource.DeviceSubRequest{
|
||||||
|
Name: fmt.Sprintf("subreq-%d", i),
|
||||||
|
DeviceClassName: goodName,
|
||||||
|
AllocationMode: resource.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
claim.Spec.Devices.Requests[0].FirstAvailable = subRequests
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-config-requests-with-subrequest-reference": {
|
||||||
|
wantFailures: nil,
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo/bar"},
|
||||||
|
DeviceConfiguration: resource.DeviceConfiguration{
|
||||||
|
Opaque: &resource.OpaqueDeviceConfiguration{
|
||||||
|
Driver: "dra.example.com",
|
||||||
|
Parameters: runtime.RawExtension{
|
||||||
|
Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-config-requests-with-parent-request-reference": {
|
||||||
|
wantFailures: nil,
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo"},
|
||||||
|
DeviceConfiguration: resource.DeviceConfiguration{
|
||||||
|
Opaque: &resource.OpaqueDeviceConfiguration{
|
||||||
|
Driver: "dra.example.com",
|
||||||
|
Parameters: runtime.RawExtension{
|
||||||
|
Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-config-requests-with-invalid-subrequest-reference": {
|
||||||
|
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "config").Index(0).Child("requests").Index(0), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Config = []resource.DeviceClaimConfiguration{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo/baz"},
|
||||||
|
DeviceConfiguration: resource.DeviceConfiguration{
|
||||||
|
Opaque: &resource.OpaqueDeviceConfiguration{
|
||||||
|
Driver: "dra.example.com",
|
||||||
|
Parameters: runtime.RawExtension{
|
||||||
|
Raw: []byte(`{"kind": "foo", "apiVersion": "dra.example.com/v1"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-constraints-requests-with-subrequest-reference": {
|
||||||
|
wantFailures: nil,
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Constraints = []resource.DeviceConstraint{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo/bar"},
|
||||||
|
MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-constraints-requests-with-parent-request-reference": {
|
||||||
|
wantFailures: nil,
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Constraints = []resource.DeviceConstraint{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo"},
|
||||||
|
MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-constraints-requests-with-invalid-subrequest-reference": {
|
||||||
|
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "devices", "constraints").Index(0).Child("requests").Index(0), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'")},
|
||||||
|
claim: func() *resource.ResourceClaim {
|
||||||
|
claim := testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
claim.Spec.Devices.Constraints = []resource.DeviceConstraint{
|
||||||
|
{
|
||||||
|
Requests: []string{"foo/baz"},
|
||||||
|
MatchAttribute: ptr.To(resource.FullyQualifiedName("dra.example.com/driverVersion")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
}(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
@ -617,11 +819,12 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
|
|||||||
validAllocatedClaimOld.Status.Allocation.Devices.Results[0].AdminAccess = nil // Not required in 1.31.
|
validAllocatedClaimOld.Status.Allocation.Devices.Results[0].AdminAccess = nil // Not required in 1.31.
|
||||||
|
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
adminAccess bool
|
adminAccess bool
|
||||||
deviceStatusFeatureGate bool
|
deviceStatusFeatureGate bool
|
||||||
oldClaim *resource.ResourceClaim
|
prioritizedListFeatureGate bool
|
||||||
update func(claim *resource.ResourceClaim) *resource.ResourceClaim
|
oldClaim *resource.ResourceClaim
|
||||||
wantFailures field.ErrorList
|
update func(claim *resource.ResourceClaim) *resource.ResourceClaim
|
||||||
|
wantFailures field.ErrorList
|
||||||
}{
|
}{
|
||||||
"valid-no-op-update": {
|
"valid-no-op-update": {
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
@ -654,7 +857,7 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
|
|||||||
"invalid-add-allocation-bad-request": {
|
"invalid-add-allocation-bad-request": {
|
||||||
wantFailures: field.ErrorList{
|
wantFailures: field.ErrorList{
|
||||||
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
},
|
},
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
@ -862,7 +1065,7 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
|
|||||||
"invalid-request-name": {
|
"invalid-request-name": {
|
||||||
wantFailures: field.ErrorList{
|
wantFailures: field.ErrorList{
|
||||||
field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim"),
|
field.Invalid(field.NewPath("status", "allocation", "devices", "config").Index(0).Child("requests").Index(1), badName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
},
|
},
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
@ -1334,12 +1537,115 @@ func TestValidateClaimStatusUpdate(t *testing.T) {
|
|||||||
return claim
|
return claim
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"valid-add-allocation-with-sub-requests": {
|
||||||
|
oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
|
claim.Status.Allocation = &resource.AllocationResult{
|
||||||
|
Devices: resource.DeviceAllocationResult{
|
||||||
|
Results: []resource.DeviceRequestAllocationResult{{
|
||||||
|
Request: fmt.Sprintf("%s/%s", goodName, goodName),
|
||||||
|
Driver: goodName,
|
||||||
|
Pool: goodName,
|
||||||
|
Device: goodName,
|
||||||
|
AdminAccess: ptr.To(false),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
},
|
||||||
|
prioritizedListFeatureGate: true,
|
||||||
|
},
|
||||||
|
"invalid-add-allocation-with-sub-requests-invalid-format": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badRequestFormat, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
|
},
|
||||||
|
oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
|
claim.Status.Allocation = &resource.AllocationResult{
|
||||||
|
Devices: resource.DeviceAllocationResult{
|
||||||
|
Results: []resource.DeviceRequestAllocationResult{{
|
||||||
|
Request: badRequestFormat,
|
||||||
|
Driver: goodName,
|
||||||
|
Pool: goodName,
|
||||||
|
Device: goodName,
|
||||||
|
AdminAccess: ptr.To(false),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
},
|
||||||
|
prioritizedListFeatureGate: true,
|
||||||
|
},
|
||||||
|
"invalid-add-allocation-with-sub-requests-no-corresponding-sub-request": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), "foo/baz", "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
|
},
|
||||||
|
oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
|
claim.Status.Allocation = &resource.AllocationResult{
|
||||||
|
Devices: resource.DeviceAllocationResult{
|
||||||
|
Results: []resource.DeviceRequestAllocationResult{{
|
||||||
|
Request: "foo/baz",
|
||||||
|
Driver: goodName,
|
||||||
|
Pool: goodName,
|
||||||
|
Device: goodName,
|
||||||
|
AdminAccess: ptr.To(false),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
},
|
||||||
|
prioritizedListFeatureGate: true,
|
||||||
|
},
|
||||||
|
"invalid-add-allocation-with-sub-requests-invalid-request-names": {
|
||||||
|
wantFailures: field.ErrorList{
|
||||||
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badSubrequestName, "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')"),
|
||||||
|
field.Invalid(field.NewPath("status", "allocation", "devices", "results").Index(0).Child("request"), badFullSubrequestName, "must be the name of a request in the claim or the name of a request and a subrequest separated by '/'"),
|
||||||
|
},
|
||||||
|
oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
|
claim.Status.Allocation = &resource.AllocationResult{
|
||||||
|
Devices: resource.DeviceAllocationResult{
|
||||||
|
Results: []resource.DeviceRequestAllocationResult{{
|
||||||
|
Request: badFullSubrequestName,
|
||||||
|
Driver: goodName,
|
||||||
|
Pool: goodName,
|
||||||
|
Device: goodName,
|
||||||
|
AdminAccess: ptr.To(false),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
},
|
||||||
|
prioritizedListFeatureGate: true,
|
||||||
|
},
|
||||||
|
"add-allocation-old-claim-with-prioritized-list": {
|
||||||
|
wantFailures: nil,
|
||||||
|
oldClaim: testClaim(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(claim *resource.ResourceClaim) *resource.ResourceClaim {
|
||||||
|
claim.Status.Allocation = &resource.AllocationResult{
|
||||||
|
Devices: resource.DeviceAllocationResult{
|
||||||
|
Results: []resource.DeviceRequestAllocationResult{{
|
||||||
|
Request: "foo/bar",
|
||||||
|
Driver: goodName,
|
||||||
|
Pool: goodName,
|
||||||
|
Device: goodName,
|
||||||
|
AdminAccess: ptr.To(false),
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return claim
|
||||||
|
},
|
||||||
|
prioritizedListFeatureGate: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, scenario.adminAccess)
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, scenario.adminAccess)
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAResourceClaimDeviceStatus, scenario.deviceStatusFeatureGate)
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAResourceClaimDeviceStatus, scenario.deviceStatusFeatureGate)
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, scenario.prioritizedListFeatureGate)
|
||||||
|
|
||||||
scenario.oldClaim.ResourceVersion = "1"
|
scenario.oldClaim.ResourceVersion = "1"
|
||||||
errs := ValidateResourceClaimStatusUpdate(scenario.update(scenario.oldClaim.DeepCopy()), scenario.oldClaim)
|
errs := ValidateResourceClaimStatusUpdate(scenario.update(scenario.oldClaim.DeepCopy()), scenario.oldClaim)
|
||||||
|
@ -185,6 +185,26 @@ func TestValidateClaimTemplate(t *testing.T) {
|
|||||||
return template
|
return template
|
||||||
}(),
|
}(),
|
||||||
},
|
},
|
||||||
|
"prioritized-list": {
|
||||||
|
wantFailures: nil,
|
||||||
|
template: testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
},
|
||||||
|
"proritized-list-class-name-on-parent": {
|
||||||
|
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0).Child("deviceClassName"), goodName, "must not be specified when firstAvailable is set")},
|
||||||
|
template: func() *resource.ResourceClaimTemplate {
|
||||||
|
template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
template.Spec.Spec.Devices.Requests[0].DeviceClassName = goodName
|
||||||
|
return template
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
"prioritized-list-bad-class-name-on-subrequest": {
|
||||||
|
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec", "spec", "devices", "requests").Index(0).Child("firstAvailable").Index(0).Child("deviceClassName"), badName, "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
|
||||||
|
template: func() *resource.ResourceClaimTemplate {
|
||||||
|
template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName = badName
|
||||||
|
return template
|
||||||
|
}(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
@ -219,6 +239,18 @@ func TestValidateClaimTemplateUpdate(t *testing.T) {
|
|||||||
return template
|
return template
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"prioritized-listinvalid-update-class": {
|
||||||
|
wantFailures: field.ErrorList{field.Invalid(field.NewPath("spec"), func() resource.ResourceClaimTemplateSpec {
|
||||||
|
template := testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable)
|
||||||
|
template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName += "2"
|
||||||
|
return template.Spec
|
||||||
|
}(), "field is immutable")},
|
||||||
|
oldClaimTemplate: testClaimTemplate(goodName, goodNS, validClaimSpecWithFirstAvailable),
|
||||||
|
update: func(template *resource.ResourceClaimTemplate) *resource.ResourceClaimTemplate {
|
||||||
|
template.Spec.Spec.Devices.Requests[0].FirstAvailable[0].DeviceClassName += "2"
|
||||||
|
return template
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
|
@ -183,10 +183,38 @@ func toSelectableFields(claim *resource.ResourceClaim) fields.Set {
|
|||||||
|
|
||||||
// dropDisabledFields removes fields which are covered by a feature gate.
|
// dropDisabledFields removes fields which are covered by a feature gate.
|
||||||
func dropDisabledFields(newClaim, oldClaim *resource.ResourceClaim) {
|
func dropDisabledFields(newClaim, oldClaim *resource.ResourceClaim) {
|
||||||
|
dropDisabledDRAPrioritizedListFields(newClaim, oldClaim)
|
||||||
dropDisabledDRAAdminAccessFields(newClaim, oldClaim)
|
dropDisabledDRAAdminAccessFields(newClaim, oldClaim)
|
||||||
dropDisabledDRAResourceClaimDeviceStatusFields(newClaim, oldClaim)
|
dropDisabledDRAResourceClaimDeviceStatusFields(newClaim, oldClaim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dropDisabledDRAPrioritizedListFields(newClaim, oldClaim *resource.ResourceClaim) {
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if draPrioritizedListFeatureInUse(oldClaim) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range newClaim.Spec.Devices.Requests {
|
||||||
|
newClaim.Spec.Devices.Requests[i].FirstAvailable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func draPrioritizedListFeatureInUse(claim *resource.ResourceClaim) bool {
|
||||||
|
if claim == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, request := range claim.Spec.Devices.Requests {
|
||||||
|
if len(request.FirstAvailable) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func dropDisabledDRAAdminAccessFields(newClaim, oldClaim *resource.ResourceClaim) {
|
func dropDisabledDRAAdminAccessFields(newClaim, oldClaim *resource.ResourceClaim) {
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) {
|
||||||
// No need to drop anything.
|
// No need to drop anything.
|
||||||
|
@ -133,6 +133,30 @@ var objWithAdminAccessStatus = &resource.ResourceClaim{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var objWithPrioritizedList = &resource.ResourceClaim{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "valid-claim",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: resource.ResourceClaimSpec{
|
||||||
|
Devices: resource.DeviceClaim{
|
||||||
|
Requests: []resource.DeviceRequest{
|
||||||
|
{
|
||||||
|
Name: "req-0",
|
||||||
|
FirstAvailable: []resource.DeviceSubRequest{
|
||||||
|
{
|
||||||
|
Name: "subreq-0",
|
||||||
|
DeviceClassName: "class",
|
||||||
|
AllocationMode: resource.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
testRequest = "test-request"
|
testRequest = "test-request"
|
||||||
testDriver = "test-driver"
|
testDriver = "test-driver"
|
||||||
@ -155,6 +179,7 @@ func TestStrategyCreate(t *testing.T) {
|
|||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
obj *resource.ResourceClaim
|
obj *resource.ResourceClaim
|
||||||
adminAccess bool
|
adminAccess bool
|
||||||
|
prioritizedList bool
|
||||||
expectValidationError bool
|
expectValidationError bool
|
||||||
expectObj *resource.ResourceClaim
|
expectObj *resource.ResourceClaim
|
||||||
}{
|
}{
|
||||||
@ -180,11 +205,22 @@ func TestStrategyCreate(t *testing.T) {
|
|||||||
adminAccess: true,
|
adminAccess: true,
|
||||||
expectObj: objWithAdminAccess,
|
expectObj: objWithAdminAccess,
|
||||||
},
|
},
|
||||||
|
"drop-fields-prioritized-list": {
|
||||||
|
obj: objWithPrioritizedList,
|
||||||
|
prioritizedList: false,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
"keep-fields-prioritized-list": {
|
||||||
|
obj: objWithPrioritizedList,
|
||||||
|
prioritizedList: true,
|
||||||
|
expectObj: objWithPrioritizedList,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testcases {
|
for name, tc := range testcases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList)
|
||||||
|
|
||||||
obj := tc.obj.DeepCopy()
|
obj := tc.obj.DeepCopy()
|
||||||
Strategy.PrepareForCreate(ctx, obj)
|
Strategy.PrepareForCreate(ctx, obj)
|
||||||
@ -212,6 +248,7 @@ func TestStrategyUpdate(t *testing.T) {
|
|||||||
oldObj *resource.ResourceClaim
|
oldObj *resource.ResourceClaim
|
||||||
newObj *resource.ResourceClaim
|
newObj *resource.ResourceClaim
|
||||||
adminAccess bool
|
adminAccess bool
|
||||||
|
prioritizedList bool
|
||||||
expectValidationError bool
|
expectValidationError bool
|
||||||
expectObj *resource.ResourceClaim
|
expectObj *resource.ResourceClaim
|
||||||
}{
|
}{
|
||||||
@ -247,11 +284,36 @@ func TestStrategyUpdate(t *testing.T) {
|
|||||||
adminAccess: true,
|
adminAccess: true,
|
||||||
expectObj: objWithAdminAccess,
|
expectObj: objWithAdminAccess,
|
||||||
},
|
},
|
||||||
|
"drop-fields-prioritized-list": {
|
||||||
|
oldObj: obj,
|
||||||
|
newObj: objWithPrioritizedList,
|
||||||
|
prioritizedList: false,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
"keep-fields-prioritized-list": {
|
||||||
|
oldObj: obj,
|
||||||
|
newObj: objWithPrioritizedList,
|
||||||
|
prioritizedList: true,
|
||||||
|
expectValidationError: true, // Spec is immutable.
|
||||||
|
},
|
||||||
|
"keep-existing-fields-prioritized-list": {
|
||||||
|
oldObj: objWithPrioritizedList,
|
||||||
|
newObj: objWithPrioritizedList,
|
||||||
|
prioritizedList: true,
|
||||||
|
expectObj: objWithPrioritizedList,
|
||||||
|
},
|
||||||
|
"keep-existing-fields-prioritized-list-disabled-feature": {
|
||||||
|
oldObj: objWithPrioritizedList,
|
||||||
|
newObj: objWithPrioritizedList,
|
||||||
|
prioritizedList: false,
|
||||||
|
expectObj: objWithPrioritizedList,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testcases {
|
for name, tc := range testcases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList)
|
||||||
|
|
||||||
oldObj := tc.oldObj.DeepCopy()
|
oldObj := tc.oldObj.DeepCopy()
|
||||||
newObj := tc.newObj.DeepCopy()
|
newObj := tc.newObj.DeepCopy()
|
||||||
|
@ -100,9 +100,37 @@ func toSelectableFields(template *resource.ResourceClaimTemplate) fields.Set {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dropDisabledFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) {
|
func dropDisabledFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) {
|
||||||
|
dropDisabledDRAPrioritizedListFields(newClaimTemplate, oldClaimTemplate)
|
||||||
dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate)
|
dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dropDisabledDRAPrioritizedListFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) {
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.DRAPrioritizedList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if draPrioritizedListFeatureInUse(oldClaimTemplate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range newClaimTemplate.Spec.Spec.Devices.Requests {
|
||||||
|
newClaimTemplate.Spec.Spec.Devices.Requests[i].FirstAvailable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func draPrioritizedListFeatureInUse(claimTemplate *resource.ResourceClaimTemplate) bool {
|
||||||
|
if claimTemplate == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, request := range claimTemplate.Spec.Spec.Devices.Requests {
|
||||||
|
if len(request.FirstAvailable) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) {
|
func dropDisabledDRAAdminAccessFields(newClaimTemplate, oldClaimTemplate *resource.ResourceClaimTemplate) {
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.DRAAdminAccess) {
|
||||||
// No need to drop anything.
|
// No need to drop anything.
|
||||||
|
@ -71,6 +71,32 @@ var objWithAdminAccess = &resource.ResourceClaimTemplate{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var objWithPrioritizedList = &resource.ResourceClaimTemplate{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "valid-claim-template",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: resource.ResourceClaimTemplateSpec{
|
||||||
|
Spec: resource.ResourceClaimSpec{
|
||||||
|
Devices: resource.DeviceClaim{
|
||||||
|
Requests: []resource.DeviceRequest{
|
||||||
|
{
|
||||||
|
Name: "req-0",
|
||||||
|
FirstAvailable: []resource.DeviceSubRequest{
|
||||||
|
{
|
||||||
|
Name: "subreq-0",
|
||||||
|
DeviceClassName: "class",
|
||||||
|
AllocationMode: resource.DeviceAllocationModeExactCount,
|
||||||
|
Count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestClaimTemplateStrategy(t *testing.T) {
|
func TestClaimTemplateStrategy(t *testing.T) {
|
||||||
if !Strategy.NamespaceScoped() {
|
if !Strategy.NamespaceScoped() {
|
||||||
t.Errorf("ResourceClaimTemplate must be namespace scoped")
|
t.Errorf("ResourceClaimTemplate must be namespace scoped")
|
||||||
@ -86,6 +112,7 @@ func TestClaimTemplateStrategyCreate(t *testing.T) {
|
|||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
obj *resource.ResourceClaimTemplate
|
obj *resource.ResourceClaimTemplate
|
||||||
adminAccess bool
|
adminAccess bool
|
||||||
|
prioritizedList bool
|
||||||
expectValidationError bool
|
expectValidationError bool
|
||||||
expectObj *resource.ResourceClaimTemplate
|
expectObj *resource.ResourceClaimTemplate
|
||||||
}{
|
}{
|
||||||
@ -111,11 +138,22 @@ func TestClaimTemplateStrategyCreate(t *testing.T) {
|
|||||||
adminAccess: true,
|
adminAccess: true,
|
||||||
expectObj: objWithAdminAccess,
|
expectObj: objWithAdminAccess,
|
||||||
},
|
},
|
||||||
|
"drop-fields-prioritized-list": {
|
||||||
|
obj: objWithPrioritizedList,
|
||||||
|
prioritizedList: false,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
"keep-fields-prioritized-list": {
|
||||||
|
obj: objWithPrioritizedList,
|
||||||
|
prioritizedList: true,
|
||||||
|
expectObj: objWithPrioritizedList,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range testcases {
|
for name, tc := range testcases {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAAdminAccess, tc.adminAccess)
|
||||||
|
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DRAPrioritizedList, tc.prioritizedList)
|
||||||
|
|
||||||
obj := tc.obj.DeepCopy()
|
obj := tc.obj.DeepCopy()
|
||||||
Strategy.PrepareForCreate(ctx, obj)
|
Strategy.PrepareForCreate(ctx, obj)
|
||||||
|
Loading…
Reference in New Issue
Block a user