2022-11-04 21:01:00 +08:00
/ *
Copyright 2022 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package validation
import (
2024-06-18 23:47:29 +08:00
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
2024-02-14 21:38:42 +08:00
apiequality "k8s.io/apimachinery/pkg/api/equality"
2022-11-04 21:01:00 +08:00
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
2024-10-02 17:02:33 +08:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
2023-01-27 03:37:00 +08:00
"k8s.io/apimachinery/pkg/types"
2022-11-04 21:01:00 +08:00
"k8s.io/apimachinery/pkg/util/sets"
2024-06-18 23:47:29 +08:00
"k8s.io/apimachinery/pkg/util/validation"
2022-11-04 21:01:00 +08:00
"k8s.io/apimachinery/pkg/util/validation/field"
2024-06-18 23:47:29 +08:00
"k8s.io/apiserver/pkg/cel"
"k8s.io/apiserver/pkg/cel/environment"
2024-10-02 17:02:33 +08:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2024-06-18 23:47:29 +08:00
dracel "k8s.io/dynamic-resource-allocation/cel"
2024-10-02 17:02:33 +08:00
"k8s.io/dynamic-resource-allocation/structured"
2022-11-04 21:01:00 +08:00
corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/apis/resource"
2024-10-02 17:02:33 +08:00
"k8s.io/kubernetes/pkg/features"
2022-11-04 21:01:00 +08:00
)
2024-06-18 23:47:29 +08:00
var (
// validateResourceDriverName reuses the validation of a CSI driver because
// the allowed values are exactly the same.
validateDriverName = corevalidation . ValidateCSIDriverName
validateDeviceName = corevalidation . ValidateDNS1123Label
validateDeviceClassName = corevalidation . ValidateDNS1123Subdomain
validateRequestName = corevalidation . ValidateDNS1123Label
)
2022-11-04 21:01:00 +08:00
2024-06-18 23:47:29 +08:00
func validatePoolName ( name string , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
if name == "" {
allErrs = append ( allErrs , field . Required ( fldPath , "" ) )
} else {
if len ( name ) > resource . PoolNameMaxLength {
2024-11-06 01:11:03 +08:00
allErrs = append ( allErrs , field . TooLong ( fldPath , "" /*unused*/ , resource . PoolNameMaxLength ) )
2024-06-18 23:47:29 +08:00
}
parts := strings . Split ( name , "/" )
for _ , part := range parts {
allErrs = append ( allErrs , corevalidation . ValidateDNS1123Subdomain ( part , fldPath ) ... )
}
}
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateResourceClaim validates a ResourceClaim.
func ValidateResourceClaim ( resourceClaim * resource . ResourceClaim ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMeta ( & resourceClaim . ObjectMeta , true , corevalidation . ValidateResourceClaimName , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , validateResourceClaimSpec ( & resourceClaim . Spec , field . NewPath ( "spec" ) , false ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateResourceClaimUpdate tests if an update to ResourceClaim is valid.
func ValidateResourceClaimUpdate ( resourceClaim , oldClaim * resource . ResourceClaim ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMetaUpdate ( & resourceClaim . ObjectMeta , & oldClaim . ObjectMeta , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( resourceClaim . Spec , oldClaim . Spec , field . NewPath ( "spec" ) ) ... )
// Because the spec is immutable, all CEL expressions in it must have been stored.
// If the user tries an update, this is not true and checking is less strict, but
// as there are errors, it doesn't matter.
allErrs = append ( allErrs , validateResourceClaimSpec ( & resourceClaim . Spec , field . NewPath ( "spec" ) , true ) ... )
return allErrs
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
// ValidateResourceClaimStatusUpdate tests if an update to the status of a ResourceClaim is valid.
func ValidateResourceClaimStatusUpdate ( resourceClaim , oldClaim * resource . ResourceClaim ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMetaUpdate ( & resourceClaim . ObjectMeta , & oldClaim . ObjectMeta , field . NewPath ( "metadata" ) )
requestNames := gatherRequestNames ( & resourceClaim . Spec . Devices )
allErrs = append ( allErrs , validateResourceClaimStatusUpdate ( & resourceClaim . Status , & oldClaim . Status , resourceClaim . DeletionTimestamp != nil , requestNames , field . NewPath ( "status" ) ) ... )
return allErrs
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
func validateResourceClaimSpec ( spec * resource . ResourceClaimSpec , fldPath * field . Path , stored bool ) field . ErrorList {
allErrs := field . ErrorList { }
allErrs = append ( allErrs , validateDeviceClaim ( & spec . Devices , fldPath . Child ( "devices" ) , stored ) ... )
return allErrs
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
func validateDeviceClaim ( deviceClaim * resource . DeviceClaim , fldPath * field . Path , stored bool ) field . ErrorList {
allErrs := field . ErrorList { }
requestNames := gatherRequestNames ( deviceClaim )
allErrs = append ( allErrs , validateSet ( deviceClaim . Requests , resource . DeviceRequestsMaxSize ,
func ( request resource . DeviceRequest , fldPath * field . Path ) field . ErrorList {
return validateDeviceRequest ( request , fldPath , stored )
} ,
func ( request resource . DeviceRequest ) ( string , string ) {
return request . Name , "name"
} ,
fldPath . Child ( "requests" ) ) ... )
allErrs = append ( allErrs , validateSlice ( deviceClaim . Constraints , resource . DeviceConstraintsMaxSize ,
func ( constraint resource . DeviceConstraint , fldPath * field . Path ) field . ErrorList {
return validateDeviceConstraint ( constraint , fldPath , requestNames )
} , fldPath . Child ( "constraints" ) ) ... )
allErrs = append ( allErrs , validateSlice ( deviceClaim . Config , resource . DeviceConfigMaxSize ,
func ( config resource . DeviceClaimConfiguration , fldPath * field . Path ) field . ErrorList {
2024-11-06 16:42:47 +08:00
return validateDeviceClaimConfiguration ( config , fldPath , requestNames , stored )
2024-06-18 23:47:29 +08:00
} , fldPath . Child ( "config" ) ) ... )
return allErrs
}
func gatherRequestNames ( deviceClaim * resource . DeviceClaim ) sets . Set [ string ] {
requestNames := sets . New [ string ] ( )
for _ , request := range deviceClaim . Requests {
requestNames . Insert ( request . Name )
}
return requestNames
}
2024-10-02 17:02:33 +08:00
func gatherAllocatedDevices ( allocationResult * resource . DeviceAllocationResult ) sets . Set [ structured . DeviceID ] {
allocatedDevices := sets . New [ structured . DeviceID ] ( )
for _ , result := range allocationResult . Results {
deviceID := structured . DeviceID { Driver : result . Driver , Pool : result . Pool , Device : result . Device }
allocatedDevices . Insert ( deviceID )
}
return allocatedDevices
}
2024-06-18 23:47:29 +08:00
func validateDeviceRequest ( request resource . DeviceRequest , fldPath * field . Path , stored bool ) field . ErrorList {
allErrs := validateRequestName ( request . Name , fldPath . Child ( "name" ) )
if request . DeviceClassName == "" {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "deviceClassName" ) , "" ) )
2024-05-30 17:53:16 +08:00
} else {
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateDeviceClassName ( request . DeviceClassName , fldPath . Child ( "deviceClassName" ) ) ... )
}
allErrs = append ( allErrs , validateSlice ( request . Selectors , resource . DeviceSelectorsMaxSize ,
func ( selector resource . DeviceSelector , fldPath * field . Path ) field . ErrorList {
return validateSelector ( selector , fldPath , stored )
} ,
fldPath . Child ( "selectors" ) ) ... )
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" ) )
2024-05-30 17:53:16 +08:00
}
2024-06-18 23:47:29 +08:00
default :
allErrs = append ( allErrs , field . NotSupported ( fldPath . Child ( "allocationMode" ) , request . AllocationMode , [ ] resource . DeviceAllocationMode { resource . DeviceAllocationModeAll , resource . DeviceAllocationModeExactCount } ) )
2024-05-30 17:53:16 +08:00
}
2024-06-18 23:47:29 +08:00
return allErrs
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
func validateSelector ( selector resource . DeviceSelector , fldPath * field . Path , stored bool ) field . ErrorList {
var allErrs field . ErrorList
if selector . CEL == nil {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "cel" ) , "" ) )
2024-05-30 17:53:16 +08:00
} else {
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateCELSelector ( * selector . CEL , fldPath . Child ( "cel" ) , stored ) ... )
2022-11-04 21:01:00 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateCELSelector ( celSelector resource . CELDeviceSelector , fldPath * field . Path , stored bool ) field . ErrorList {
2022-11-04 21:01:00 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
envType := environment . NewExpressions
if stored {
envType = environment . StoredExpressions
2024-05-30 17:53:16 +08:00
}
2024-10-01 00:09:33 +08:00
if len ( celSelector . Expression ) > resource . CELSelectorExpressionMaxLength {
2024-11-06 01:11:03 +08:00
allErrs = append ( allErrs , field . TooLong ( fldPath . Child ( "expression" ) , "" /*unused*/ , resource . CELSelectorExpressionMaxLength ) )
2024-10-01 00:09:33 +08:00
// Don't bother compiling too long expressions.
return allErrs
}
2024-10-24 03:15:05 +08:00
result := dracel . GetCompiler ( ) . CompileCELExpression ( celSelector . Expression , dracel . Options { EnvType : & envType } )
2024-06-18 23:47:29 +08:00
if result . Error != nil {
allErrs = append ( allErrs , convertCELErrorToValidationError ( fldPath . Child ( "expression" ) , celSelector . Expression , result . Error ) )
2024-10-01 00:09:33 +08:00
} else if result . MaxCost > resource . CELSelectorExpressionMaxCost {
allErrs = append ( allErrs , field . Forbidden ( fldPath . Child ( "expression" ) , "too complex, exceeds cost limit" ) )
2024-05-30 17:53:16 +08:00
}
2024-10-01 00:09:33 +08:00
2024-06-18 23:47:29 +08:00
return allErrs
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
func convertCELErrorToValidationError ( fldPath * field . Path , expression string , err error ) * field . Error {
var celErr * cel . Error
if errors . As ( err , & celErr ) {
switch celErr . Type {
case cel . ErrorTypeRequired :
return field . Required ( fldPath , celErr . Detail )
case cel . ErrorTypeInvalid :
return field . Invalid ( fldPath , expression , celErr . Detail )
case cel . ErrorTypeInternal :
return field . InternalError ( fldPath , celErr )
2022-11-04 21:01:00 +08:00
}
2024-05-30 17:53:16 +08:00
}
2024-06-18 23:47:29 +08:00
return field . InternalError ( fldPath , fmt . Errorf ( "unsupported error type: %w" , err ) )
}
2024-05-30 17:53:16 +08:00
2024-06-18 23:47:29 +08:00
func validateDeviceConstraint ( constraint resource . DeviceConstraint , fldPath * field . Path , requestNames sets . Set [ string ] ) field . ErrorList {
var allErrs field . ErrorList
allErrs = append ( allErrs , validateSet ( constraint . Requests , resource . DeviceRequestsMaxSize ,
func ( name string , fldPath * field . Path ) field . ErrorList {
return validateRequestNameRef ( name , fldPath , requestNames )
} ,
stringKey , fldPath . Child ( "requests" ) ) ... )
if constraint . MatchAttribute == nil {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "matchAttribute" ) , "" ) )
2024-05-30 17:53:16 +08:00
} else {
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateFullyQualifiedName ( * constraint . MatchAttribute , fldPath . Child ( "matchAttribute" ) ) ... )
2022-11-04 21:01:00 +08:00
}
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateDeviceClaimConfiguration ( config resource . DeviceClaimConfiguration , fldPath * field . Path , requestNames sets . Set [ string ] , stored bool ) field . ErrorList {
2024-06-18 23:47:29 +08:00
var allErrs field . ErrorList
allErrs = append ( allErrs , validateSet ( config . Requests , resource . DeviceRequestsMaxSize ,
func ( name string , fldPath * field . Path ) field . ErrorList {
return validateRequestNameRef ( name , fldPath , requestNames )
} , stringKey , fldPath . Child ( "requests" ) ) ... )
2024-11-06 16:42:47 +08:00
allErrs = append ( allErrs , validateDeviceConfiguration ( config . DeviceConfiguration , fldPath , stored ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateRequestNameRef ( name string , fldPath * field . Path , requestNames sets . Set [ string ] ) field . ErrorList {
allErrs := validateRequestName ( name , fldPath )
if ! requestNames . Has ( name ) {
allErrs = append ( allErrs , field . Invalid ( fldPath , name , "must be the name of a request in the claim" ) )
}
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateDeviceConfiguration ( config resource . DeviceConfiguration , fldPath * field . Path , stored bool ) field . ErrorList {
2024-06-18 23:47:29 +08:00
var allErrs field . ErrorList
if config . Opaque == nil {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "opaque" ) , "" ) )
} else {
2024-11-06 16:42:47 +08:00
allErrs = append ( allErrs , validateOpaqueConfiguration ( * config . Opaque , fldPath . Child ( "opaque" ) , stored ) ... )
2024-06-18 23:47:29 +08:00
}
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateOpaqueConfiguration ( config resource . OpaqueDeviceConfiguration , fldPath * field . Path , stored bool ) field . ErrorList {
2024-06-18 23:47:29 +08:00
var allErrs field . ErrorList
allErrs = append ( allErrs , validateDriverName ( config . Driver , fldPath . Child ( "driver" ) ) ... )
2024-10-28 21:41:18 +08:00
allErrs = append ( allErrs , validateRawExtension ( config . Parameters , fldPath . Child ( "parameters" ) ) ... )
2024-06-18 23:47:29 +08:00
return allErrs
}
func validateResourceClaimStatusUpdate ( status , oldStatus * resource . ResourceClaimStatus , claimDeleted bool , requestNames sets . Set [ string ] , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
allErrs = append ( allErrs , validateSet ( status . ReservedFor , resource . ResourceClaimReservedForMaxSize ,
validateResourceClaimUserReference ,
func ( consumer resource . ResourceClaimConsumerReference ) ( types . UID , string ) { return consumer . UID , "uid" } ,
fldPath . Child ( "reservedFor" ) ) ... )
2022-11-04 21:01:00 +08:00
2024-10-02 17:02:33 +08:00
if utilfeature . DefaultFeatureGate . Enabled ( features . DRAResourceClaimDeviceStatus ) {
var allocatedDevices sets . Set [ structured . DeviceID ]
if status . Allocation != nil {
allocatedDevices = gatherAllocatedDevices ( & status . Allocation . Devices )
}
allErrs = append ( allErrs , validateSet ( status . Devices , - 1 ,
func ( device resource . AllocatedDeviceStatus , fldPath * field . Path ) field . ErrorList {
return validateDeviceStatus ( device , fldPath , allocatedDevices )
} ,
func ( device resource . AllocatedDeviceStatus ) ( structured . DeviceID , string ) {
return structured . DeviceID { Driver : device . Driver , Pool : device . Pool , Device : device . Device } , "deviceID"
} ,
fldPath . Child ( "devices" ) ) ... )
}
2022-11-04 21:01:00 +08:00
// Now check for invariants that must be valid for a ResourceClaim.
2024-06-18 23:47:29 +08:00
if len ( status . ReservedFor ) > 0 {
if status . Allocation == nil {
2022-11-04 21:01:00 +08:00
allErrs = append ( allErrs , field . Forbidden ( fldPath . Child ( "reservedFor" ) , "may not be specified when `allocated` is not set" ) )
} else {
// Items may be removed from ReservedFor while the claim is meant to be deallocated,
// but not added.
2024-10-11 22:08:54 +08:00
if claimDeleted {
2024-06-18 23:47:29 +08:00
oldSet := sets . New ( oldStatus . ReservedFor ... )
newSet := sets . New ( status . ReservedFor ... )
2022-11-04 21:01:00 +08:00
newItems := newSet . Difference ( oldSet )
if len ( newItems ) > 0 {
allErrs = append ( allErrs , field . Forbidden ( fldPath . Child ( "reservedFor" ) , "new entries may not be added while `deallocationRequested` or `deletionTimestamp` are set" ) )
}
}
}
}
2024-09-04 14:03:22 +08:00
// Updates to a populated status.Allocation are not allowed.
// Unmodified fields don't need to be validated again and,
// in this particular case, must not be validated again because
// validation for new results is tighter than it was before.
2024-06-18 23:47:29 +08:00
if oldStatus . Allocation != nil && status . Allocation != nil {
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( status . Allocation , oldStatus . Allocation , fldPath . Child ( "allocation" ) ) ... )
2024-09-04 14:03:22 +08:00
} else if status . Allocation != nil {
2024-11-06 16:42:47 +08:00
allErrs = append ( allErrs , validateAllocationResult ( status . Allocation , fldPath . Child ( "allocation" ) , requestNames , false ) ... )
2023-03-07 23:09:52 +08:00
}
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateResourceClaimUserReference ( ref resource . ResourceClaimConsumerReference , fldPath * field . Path ) field . ErrorList {
2022-11-04 21:01:00 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
if ref . Resource == "" {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "resource" ) , "" ) )
}
if ref . Name == "" {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "name" ) , "" ) )
}
if ref . UID == "" {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "uid" ) , "" ) )
2022-11-04 21:01:00 +08:00
}
return allErrs
}
2024-09-04 14:03:22 +08:00
// validateAllocationResult enforces constraints for *new* results, which in at
// least one case (admin access) are more strict than before. Therefore it
// may not be called to re-validate results which were stored earlier.
2024-11-06 16:42:47 +08:00
func validateAllocationResult ( allocation * resource . AllocationResult , fldPath * field . Path , requestNames sets . Set [ string ] , stored bool ) field . ErrorList {
2023-03-07 23:09:52 +08:00
var allErrs field . ErrorList
2024-11-06 16:42:47 +08:00
allErrs = append ( allErrs , validateDeviceAllocationResult ( allocation . Devices , fldPath . Child ( "devices" ) , requestNames , stored ) ... )
2024-06-18 23:47:29 +08:00
if allocation . NodeSelector != nil {
allErrs = append ( allErrs , corevalidation . ValidateNodeSelector ( allocation . NodeSelector , fldPath . Child ( "nodeSelector" ) ) ... )
2023-03-07 23:09:52 +08:00
}
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateDeviceAllocationResult ( allocation resource . DeviceAllocationResult , fldPath * field . Path , requestNames sets . Set [ string ] , stored bool ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateSlice ( allocation . Results , resource . AllocationResultsMaxSize ,
func ( result resource . DeviceRequestAllocationResult , fldPath * field . Path ) field . ErrorList {
return validateDeviceRequestAllocationResult ( result , fldPath , requestNames )
} , fldPath . Child ( "results" ) ) ... )
allErrs = append ( allErrs , validateSlice ( allocation . Config , 2 * resource . DeviceConfigMaxSize , /* class + claim */
func ( config resource . DeviceAllocationConfiguration , fldPath * field . Path ) field . ErrorList {
2024-11-06 16:42:47 +08:00
return validateDeviceAllocationConfiguration ( config , fldPath , requestNames , stored )
2024-06-18 23:47:29 +08:00
} , fldPath . Child ( "config" ) ) ... )
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateDeviceRequestAllocationResult ( result resource . DeviceRequestAllocationResult , fldPath * field . Path , requestNames sets . Set [ string ] ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateRequestNameRef ( result . Request , fldPath . Child ( "request" ) , requestNames ) ... )
allErrs = append ( allErrs , validateDriverName ( result . Driver , fldPath . Child ( "driver" ) ) ... )
allErrs = append ( allErrs , validatePoolName ( result . Pool , fldPath . Child ( "pool" ) ) ... )
allErrs = append ( allErrs , validateDeviceName ( result . Device , fldPath . Child ( "device" ) ) ... )
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateDeviceAllocationConfiguration ( config resource . DeviceAllocationConfiguration , fldPath * field . Path , requestNames sets . Set [ string ] , stored bool ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateAllocationConfigSource ( config . Source , fldPath . Child ( "source" ) ) ... )
allErrs = append ( allErrs , validateSet ( config . Requests , resource . DeviceRequestsMaxSize ,
func ( name string , fldPath * field . Path ) field . ErrorList {
return validateRequestNameRef ( name , fldPath , requestNames )
} , stringKey , fldPath . Child ( "requests" ) ) ... )
2024-11-06 16:42:47 +08:00
allErrs = append ( allErrs , validateDeviceConfiguration ( config . DeviceConfiguration , fldPath , stored ) ... )
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateAllocationConfigSource ( source resource . AllocationConfigSource , fldPath * field . Path ) field . ErrorList {
2022-11-04 21:01:00 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
switch source {
case "" :
allErrs = append ( allErrs , field . Required ( fldPath , "" ) )
case resource . AllocationConfigSourceClaim , resource . AllocationConfigSourceClass :
default :
allErrs = append ( allErrs , field . NotSupported ( fldPath , source , [ ] resource . AllocationConfigSource { resource . AllocationConfigSourceClaim , resource . AllocationConfigSourceClass } ) )
2022-11-04 21:01:00 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateClass validates a DeviceClass.
func ValidateDeviceClass ( class * resource . DeviceClass ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMeta ( & class . ObjectMeta , false , corevalidation . ValidateClassName , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , validateDeviceClassSpec ( & class . Spec , nil , field . NewPath ( "spec" ) ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateClassUpdate tests if an update to DeviceClass is valid.
func ValidateDeviceClassUpdate ( class , oldClass * resource . DeviceClass ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMetaUpdate ( & class . ObjectMeta , & oldClass . ObjectMeta , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , validateDeviceClassSpec ( & class . Spec , & oldClass . Spec , field . NewPath ( "spec" ) ) ... )
return allErrs
}
func validateDeviceClassSpec ( spec , oldSpec * resource . DeviceClassSpec , fldPath * field . Path ) field . ErrorList {
2023-01-27 03:37:00 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
// If the selectors are exactly as before, we treat the CEL expressions as "stored".
// Any change, including merely reordering selectors, triggers validation as new
// expressions.
stored := false
if oldSpec != nil {
stored = apiequality . Semantic . DeepEqual ( spec . Selectors , oldSpec . Selectors )
2023-01-27 03:37:00 +08:00
}
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateSlice ( spec . Selectors , resource . DeviceSelectorsMaxSize ,
func ( selector resource . DeviceSelector , fldPath * field . Path ) field . ErrorList {
return validateSelector ( selector , fldPath , stored )
} ,
fldPath . Child ( "selectors" ) ) ... )
2024-11-06 16:42:47 +08:00
// Same logic as above for configs.
if oldSpec != nil {
stored = apiequality . Semantic . DeepEqual ( spec . Config , oldSpec . Config )
}
allErrs = append ( allErrs , validateSlice ( spec . Config , resource . DeviceConfigMaxSize ,
func ( config resource . DeviceClassConfiguration , fldPath * field . Path ) field . ErrorList {
return validateDeviceClassConfiguration ( config , fldPath , stored )
} ,
fldPath . Child ( "config" ) ) ... )
2023-01-27 03:37:00 +08:00
return allErrs
}
2024-11-06 16:42:47 +08:00
func validateDeviceClassConfiguration ( config resource . DeviceClassConfiguration , fldPath * field . Path , stored bool ) field . ErrorList {
return validateDeviceConfiguration ( config . DeviceConfiguration , fldPath , stored )
2024-06-18 23:47:29 +08:00
}
// ValidateResourceClaimTemplate validates a ResourceClaimTemplate.
func ValidateResourceClaimTemplate ( template * resource . ResourceClaimTemplate ) field . ErrorList {
2023-03-14 18:58:41 +08:00
allErrs := corevalidation . ValidateObjectMeta ( & template . ObjectMeta , true , corevalidation . ValidateResourceClaimTemplateName , field . NewPath ( "metadata" ) )
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateResourceClaimTemplateSpec ( & template . Spec , field . NewPath ( "spec" ) , false ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateResourceClaimTemplateSpec ( spec * resource . ResourceClaimTemplateSpec , fldPath * field . Path , stored bool ) field . ErrorList {
2022-11-04 21:01:00 +08:00
allErrs := corevalidation . ValidateTemplateObjectMeta ( & spec . ObjectMeta , fldPath . Child ( "metadata" ) )
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateResourceClaimSpec ( & spec . Spec , fldPath . Child ( "spec" ) , stored ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateResourceClaimTemplateUpdate tests if an update to template is valid.
func ValidateResourceClaimTemplateUpdate ( template , oldTemplate * resource . ResourceClaimTemplate ) field . ErrorList {
2022-11-04 21:01:00 +08:00
allErrs := corevalidation . ValidateObjectMetaUpdate ( & template . ObjectMeta , & oldTemplate . ObjectMeta , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( template . Spec , oldTemplate . Spec , field . NewPath ( "spec" ) ) ... )
2024-06-18 23:47:29 +08:00
// Because the spec is immutable, all CEL expressions in it must have been stored.
// If the user tries an update, this is not true and checking is less strict, but
// as there are errors, it doesn't matter.
allErrs = append ( allErrs , validateResourceClaimTemplateSpec ( & template . Spec , field . NewPath ( "spec" ) , true ) ... )
2022-11-04 21:01:00 +08:00
return allErrs
}
2022-11-06 01:04:04 +08:00
func validateNodeName ( name string , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
for _ , msg := range corevalidation . ValidateNodeName ( name , false ) {
allErrs = append ( allErrs , field . Invalid ( fldPath , name , msg ) )
}
return allErrs
}
2024-02-14 21:38:42 +08:00
2024-03-07 17:14:11 +08:00
// ValidateResourceSlice tests if a ResourceSlice object is valid.
2024-06-18 23:47:29 +08:00
func ValidateResourceSlice ( slice * resource . ResourceSlice ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMeta ( & slice . ObjectMeta , false , apimachineryvalidation . NameIsDNSSubdomain , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , validateResourceSliceSpec ( & slice . Spec , nil , field . NewPath ( "spec" ) ) ... )
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
// ValidateResourceSlice tests if a ResourceSlice update is valid.
func ValidateResourceSliceUpdate ( resourceSlice , oldResourceSlice * resource . ResourceSlice ) field . ErrorList {
allErrs := corevalidation . ValidateObjectMetaUpdate ( & resourceSlice . ObjectMeta , & oldResourceSlice . ObjectMeta , field . NewPath ( "metadata" ) )
allErrs = append ( allErrs , validateResourceSliceSpec ( & resourceSlice . Spec , & oldResourceSlice . Spec , field . NewPath ( "spec" ) ) ... )
return allErrs
}
func validateResourceSliceSpec ( spec , oldSpec * resource . ResourceSliceSpec , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateDriverName ( spec . Driver , fldPath . Child ( "driver" ) ) ... )
allErrs = append ( allErrs , validateResourcePool ( spec . Pool , fldPath . Child ( "pool" ) ) ... )
if oldSpec != nil {
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( spec . Pool . Name , oldSpec . Pool . Name , fldPath . Child ( "pool" , "name" ) ) ... )
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( spec . Driver , oldSpec . Driver , fldPath . Child ( "driver" ) ) ... )
allErrs = append ( allErrs , apimachineryvalidation . ValidateImmutableField ( spec . NodeName , oldSpec . NodeName , fldPath . Child ( "nodeName" ) ) ... )
}
numNodeSelectionFields := 0
if spec . NodeName != "" {
numNodeSelectionFields ++
allErrs = append ( allErrs , validateNodeName ( spec . NodeName , fldPath . Child ( "nodeName" ) ) ... )
}
if spec . NodeSelector != nil {
numNodeSelectionFields ++
allErrs = append ( allErrs , corevalidation . ValidateNodeSelector ( spec . NodeSelector , fldPath . Child ( "nodeSelector" ) ) ... )
if len ( spec . NodeSelector . NodeSelectorTerms ) != 1 {
// This additional constraint simplifies merging of different selectors
// when devices are allocated from different slices.
2024-09-25 00:52:24 +08:00
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "nodeSelector" , "nodeSelectorTerms" ) , spec . NodeSelector . NodeSelectorTerms , "must have exactly one node selector term" ) )
2024-06-18 23:47:29 +08:00
}
}
if spec . AllNodes {
numNodeSelectionFields ++
2024-02-23 22:22:02 +08:00
}
2024-06-18 23:47:29 +08:00
switch numNodeSelectionFields {
2024-02-14 21:38:42 +08:00
case 0 :
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , field . Required ( fldPath , "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required" ) )
2024-02-14 21:38:42 +08:00
case 1 :
default :
2024-09-25 00:52:24 +08:00
allErrs = append ( allErrs , field . Invalid ( fldPath , nil , "exactly one of `nodeName`, `nodeSelector`, or `allNodes` is required" ) )
2024-02-14 21:38:42 +08:00
}
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateSet ( spec . Devices , resource . ResourceSliceMaxDevices , validateDevice ,
func ( device resource . Device ) ( string , string ) {
return device . Name , "name"
} , fldPath . Child ( "devices" ) ) ... )
2024-02-14 21:38:42 +08:00
2024-06-18 23:47:29 +08:00
return allErrs
2024-02-14 21:38:42 +08:00
}
2024-06-18 23:47:29 +08:00
func validateResourcePool ( pool resource . ResourcePool , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
allErrs = append ( allErrs , validatePoolName ( pool . Name , fldPath . Child ( "name" ) ) ... )
if pool . ResourceSliceCount <= 0 {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "resourceSliceCount" ) , pool . ResourceSliceCount , "must be greater than zero" ) )
}
if pool . Generation < 0 {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "generation" ) , pool . Generation , "must be greater than or equal to zero" ) )
}
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateDevice ( device resource . Device , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , validateDeviceName ( device . Name , fldPath . Child ( "name" ) ) ... )
if device . Basic == nil {
allErrs = append ( allErrs , field . Required ( fldPath . Child ( "basic" ) , "" ) )
} else {
allErrs = append ( allErrs , validateBasicDevice ( * device . Basic , fldPath . Child ( "basic" ) ) ... )
2024-02-14 21:38:42 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateBasicDevice ( device resource . BasicDevice , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
// Warn about exceeding the maximum length only once. If any individual
// field is too large, then so is the combination.
2024-09-25 00:52:24 +08:00
maxKeyLen := resource . DeviceMaxDomainLength + 1 + resource . DeviceMaxIDLength
allErrs = append ( allErrs , validateMap ( device . Attributes , - 1 , maxKeyLen , validateQualifiedName , validateDeviceAttribute , fldPath . Child ( "attributes" ) ) ... )
2024-09-26 22:56:48 +08:00
allErrs = append ( allErrs , validateMap ( device . Capacity , - 1 , maxKeyLen , validateQualifiedName , validateDeviceCapacity , fldPath . Child ( "capacity" ) ) ... )
2024-06-18 23:47:29 +08:00
if combinedLen , max := len ( device . Attributes ) + len ( device . Capacity ) , resource . ResourceSliceMaxAttributesAndCapacitiesPerDevice ; combinedLen > max {
allErrs = append ( allErrs , field . Invalid ( fldPath , combinedLen , fmt . Sprintf ( "the total number of attributes and capacities must not exceed %d" , max ) ) )
2024-02-14 21:38:42 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
var (
numericIdentifier = ` (0|[1-9]\d*) `
preReleaseIdentifier = ` (0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*) `
buildIdentifier = ` [0-9a-zA-Z-]+ `
semverRe = regexp . MustCompile ( ` ^ ` +
// dot-separated version segments (e.g. 1.2.3)
numericIdentifier + ` \. ` + numericIdentifier + ` \. ` + numericIdentifier +
// optional dot-separated prerelease segments (e.g. -alpha.PRERELEASE.1)
` (- ` + preReleaseIdentifier + ` (\. ` + preReleaseIdentifier + ` )*)? ` +
// optional dot-separated build identifier segments (e.g. +build.id.20240305)
` (\+ ` + buildIdentifier + ` (\. ` + buildIdentifier + ` )*)? ` +
` $ ` )
)
func validateDeviceAttribute ( attribute resource . DeviceAttribute , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
numFields := 0
if attribute . BoolValue != nil {
numFields ++
}
if attribute . IntValue != nil {
numFields ++
}
if attribute . StringValue != nil {
if len ( * attribute . StringValue ) > resource . DeviceAttributeMaxValueLength {
2024-11-06 01:11:03 +08:00
allErrs = append ( allErrs , field . TooLong ( fldPath . Child ( "string" ) , "" /*unused*/ , resource . DeviceAttributeMaxValueLength ) )
2024-06-18 23:47:29 +08:00
}
numFields ++
}
if attribute . VersionValue != nil {
numFields ++
if ! semverRe . MatchString ( * attribute . VersionValue ) {
allErrs = append ( allErrs , field . Invalid ( fldPath . Child ( "version" ) , * attribute . VersionValue , "must be a string compatible with semver.org spec 2.0.0" ) )
}
if len ( * attribute . VersionValue ) > resource . DeviceAttributeMaxValueLength {
2024-11-06 01:11:03 +08:00
allErrs = append ( allErrs , field . TooLong ( fldPath . Child ( "version" ) , "" /*unused*/ , resource . DeviceAttributeMaxValueLength ) )
2024-06-18 23:47:29 +08:00
}
2024-02-23 22:22:02 +08:00
}
2024-06-18 23:47:29 +08:00
switch numFields {
2024-02-14 21:38:42 +08:00
case 0 :
2024-06-18 23:47:29 +08:00
allErrs = append ( allErrs , field . Required ( fldPath , "exactly one value must be specified" ) )
2024-02-14 21:38:42 +08:00
case 1 :
// Okay.
default :
2024-09-25 00:52:24 +08:00
allErrs = append ( allErrs , field . Invalid ( fldPath , attribute , "exactly one value must be specified" ) )
2024-02-14 21:38:42 +08:00
}
return allErrs
}
2024-09-26 22:56:48 +08:00
func validateDeviceCapacity ( capacity resource . DeviceCapacity , fldPath * field . Path ) field . ErrorList {
2024-06-18 23:47:29 +08:00
// Any parsed quantity is valid.
return nil
2024-02-14 21:38:42 +08:00
}
2024-06-18 23:47:29 +08:00
func validateQualifiedName ( name resource . QualifiedName , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
if name == "" {
allErrs = append ( allErrs , field . Required ( fldPath , "name required" ) )
return allErrs
}
parts := strings . Split ( string ( name ) , "/" )
switch len ( parts ) {
case 1 :
allErrs = append ( allErrs , validateCIdentifier ( parts [ 0 ] , fldPath ) ... )
case 2 :
if len ( parts [ 0 ] ) == 0 {
2024-09-25 00:52:24 +08:00
allErrs = append ( allErrs , field . Required ( fldPath , "the domain must not be empty" ) )
2024-06-18 23:47:29 +08:00
} else {
allErrs = append ( allErrs , validateDriverName ( parts [ 0 ] , fldPath ) ... )
}
if len ( parts [ 1 ] ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath , "the name must not be empty" ) )
} else {
allErrs = append ( allErrs , validateCIdentifier ( parts [ 1 ] , fldPath ) ... )
}
}
return allErrs
2024-02-14 21:38:42 +08:00
}
2024-06-18 23:47:29 +08:00
func validateFullyQualifiedName ( name resource . FullyQualifiedName , fldPath * field . Path ) field . ErrorList {
allErrs := validateQualifiedName ( resource . QualifiedName ( name ) , fldPath )
2024-09-25 00:52:24 +08:00
// validateQualifiedName checks that the name isn't empty and both parts are valid.
// What we need to enforce here is that there really is a domain.
if name != "" && ! strings . Contains ( string ( name ) , "/" ) {
allErrs = append ( allErrs , field . Invalid ( fldPath , name , "must include a domain" ) )
2024-06-18 23:47:29 +08:00
}
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-06-18 23:47:29 +08:00
func validateCIdentifier ( id string , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
if len ( id ) > resource . DeviceMaxIDLength {
2024-11-06 01:11:03 +08:00
allErrs = append ( allErrs , field . TooLong ( fldPath , "" /*unused*/ , resource . DeviceMaxIDLength ) )
2024-06-18 23:47:29 +08:00
}
for _ , msg := range validation . IsCIdentifier ( id ) {
allErrs = append ( allErrs , field . TypeInvalid ( fldPath , id , msg ) )
2024-02-14 21:38:42 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
// validateSlice ensures that a slice does not exceed a certain maximum size
// and that all entries are valid.
// A negative maxSize disables the length check.
func validateSlice [ T any ] ( slice [ ] T , maxSize int , validateItem func ( T , * field . Path ) field . ErrorList , fldPath * field . Path ) field . ErrorList {
2024-02-14 21:38:42 +08:00
var allErrs field . ErrorList
2024-06-18 23:47:29 +08:00
for i , item := range slice {
idxPath := fldPath . Index ( i )
allErrs = append ( allErrs , validateItem ( item , idxPath ) ... )
2024-02-23 22:22:02 +08:00
}
2024-06-18 23:47:29 +08:00
if maxSize >= 0 && len ( slice ) > maxSize {
// Dumping the entire field into the error message is likely to be too long,
// in particular when it is already beyond the maximum size. Instead this
// just shows the number of entries.
2024-11-05 07:16:29 +08:00
allErrs = append ( allErrs , field . TooMany ( fldPath , len ( slice ) , maxSize ) )
2024-02-14 21:38:42 +08:00
}
return allErrs
}
2024-06-18 23:47:29 +08:00
// validateSet ensures that a slice contains no duplicates, does not
// exceed a certain maximum size and that all entries are valid.
func validateSet [ T any , K comparable ] ( slice [ ] T , maxSize int , validateItem func ( item T , fldPath * field . Path ) field . ErrorList , itemKey func ( T ) ( K , string ) , fldPath * field . Path ) field . ErrorList {
allErrs := validateSlice ( slice , maxSize , validateItem , fldPath )
allItems := sets . New [ K ] ( )
for i , item := range slice {
2024-02-14 21:38:42 +08:00
idxPath := fldPath . Index ( i )
2024-06-18 23:47:29 +08:00
key , fieldName := itemKey ( item )
childPath := idxPath
if fieldName != "" {
childPath = childPath . Child ( fieldName )
}
if allItems . Has ( key ) {
allErrs = append ( allErrs , field . Duplicate ( childPath , key ) )
2024-02-14 21:38:42 +08:00
} else {
2024-06-18 23:47:29 +08:00
allItems . Insert ( key )
2024-02-14 21:38:42 +08:00
}
}
return allErrs
}
2024-06-18 23:47:29 +08:00
// stringKey uses the item itself as a key for validateSet.
func stringKey ( item string ) ( string , string ) {
return item , ""
}
// validateMap validates keys, items and the maximum length of a map.
// A negative maxSize disables the length check.
2024-09-25 00:52:24 +08:00
//
// Keys larger than truncateKeyLen get truncated in the middle. A very
// small limit gets increased because it is okay to include more details.
// This is not used for validation of keys, which has to be done by
// the callback function.
func validateMap [ K ~ string , T any ] ( m map [ K ] T , maxSize , truncateKeyLen int , validateKey func ( K , * field . Path ) field . ErrorList , validateItem func ( T , * field . Path ) field . ErrorList , fldPath * field . Path ) field . ErrorList {
2024-06-18 23:47:29 +08:00
var allErrs field . ErrorList
if maxSize >= 0 && len ( m ) > maxSize {
2024-11-05 07:16:29 +08:00
allErrs = append ( allErrs , field . TooMany ( fldPath , len ( m ) , maxSize ) )
2024-06-18 23:47:29 +08:00
}
for key , item := range m {
2024-09-25 00:52:24 +08:00
keyPath := fldPath . Key ( truncateIfTooLong ( string ( key ) , truncateKeyLen ) )
allErrs = append ( allErrs , validateKey ( key , keyPath ) ... )
allErrs = append ( allErrs , validateItem ( item , keyPath ) ... )
2024-06-18 23:47:29 +08:00
}
2024-02-14 21:38:42 +08:00
return allErrs
}
2024-09-25 00:52:24 +08:00
func truncateIfTooLong ( str string , maxLen int ) string {
// The caller was overly restrictive. Increase the length to something reasonable
// (https://github.com/kubernetes/kubernetes/pull/127511#discussion_r1826206362).
if maxLen < 16 {
maxLen = 16
}
if len ( str ) <= maxLen {
return str
}
ellipsis := "..."
remaining := maxLen - len ( ellipsis )
return str [ 0 : ( remaining + 1 ) / 2 ] + ellipsis + str [ len ( str ) - remaining / 2 : ]
}
2024-10-02 17:02:33 +08:00
func validateDeviceStatus ( device resource . AllocatedDeviceStatus , fldPath * field . Path , allocatedDevices sets . Set [ structured . DeviceID ] ) field . ErrorList {
var allErrs field . ErrorList
deviceID := structured . DeviceID { Driver : device . Driver , Pool : device . Pool , Device : device . Device }
if ! allocatedDevices . Has ( deviceID ) {
allErrs = append ( allErrs , field . Invalid ( fldPath , deviceID , "must be an allocated device in the claim" ) )
}
allErrs = append ( allErrs , validateSlice ( device . Conditions , - 1 ,
func ( condition metav1 . Condition , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
return allErrs
} , fldPath . Child ( "conditions" ) ) ... )
2024-10-28 21:41:18 +08:00
if len ( device . Data . Raw ) > 0 { // Data is an optional field.
allErrs = append ( allErrs , validateRawExtension ( device . Data , fldPath . Child ( "data" ) ) ... )
}
2024-10-02 17:02:33 +08:00
allErrs = append ( allErrs , validateNetworkDeviceData ( device . NetworkData , fldPath . Child ( "networkData" ) ) ... )
return allErrs
}
2024-10-28 21:41:18 +08:00
func validateRawExtension ( rawExtension runtime . RawExtension , fldPath * field . Path ) field . ErrorList {
2024-10-02 17:02:33 +08:00
var allErrs field . ErrorList
2024-10-23 00:08:57 +08:00
// Validation of RawExtension as in https://github.com/kubernetes/kubernetes/pull/125549/
2024-10-02 17:02:33 +08:00
var v any
2024-10-28 21:41:18 +08:00
if len ( rawExtension . Raw ) == 0 {
allErrs = append ( allErrs , field . Required ( fldPath , "" ) )
} else if err := json . Unmarshal ( rawExtension . Raw , & v ) ; err != nil {
2024-10-02 17:02:33 +08:00
allErrs = append ( allErrs , field . Invalid ( fldPath , "<value omitted>" , fmt . Sprintf ( "error parsing data: %v" , err . Error ( ) ) ) )
2024-10-28 21:41:18 +08:00
} else if v == nil {
allErrs = append ( allErrs , field . Required ( fldPath , "" ) )
2024-10-02 17:02:33 +08:00
} else if _ , isObject := v . ( map [ string ] any ) ; ! isObject {
allErrs = append ( allErrs , field . Invalid ( fldPath , "<value omitted>" , "parameters must be a valid JSON object" ) )
}
return allErrs
}
func validateNetworkDeviceData ( networkDeviceData * resource . NetworkDeviceData , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
if networkDeviceData == nil {
return allErrs
}
allErrs = append ( allErrs , validateSlice ( networkDeviceData . Addresses , - 1 ,
func ( address string , fldPath * field . Path ) field . ErrorList {
var allErrs field . ErrorList
allErrs = append ( allErrs , validation . IsValidCIDR ( fldPath , address ) ... )
return allErrs
} , fldPath . Child ( "addresses" ) ) ... )
return allErrs
}