k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/resource/validation/validation.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    21  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    22  	"k8s.io/apimachinery/pkg/types"
    23  	"k8s.io/apimachinery/pkg/util/sets"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    26  	"k8s.io/kubernetes/pkg/apis/resource"
    27  	namedresourcesvalidation "k8s.io/kubernetes/pkg/apis/resource/structured/namedresources/validation"
    28  )
    29  
    30  // validateResourceDriverName reuses the validation of a CSI driver because
    31  // the allowed values are exactly the same.
    32  var validateResourceDriverName = corevalidation.ValidateCSIDriverName
    33  
    34  // ValidateClaim validates a ResourceClaim.
    35  func ValidateClaim(resourceClaim *resource.ResourceClaim) field.ErrorList {
    36  	allErrs := corevalidation.ValidateObjectMeta(&resourceClaim.ObjectMeta, true, corevalidation.ValidateResourceClaimName, field.NewPath("metadata"))
    37  	allErrs = append(allErrs, validateResourceClaimSpec(&resourceClaim.Spec, field.NewPath("spec"))...)
    38  	return allErrs
    39  }
    40  
    41  func validateResourceClaimSpec(spec *resource.ResourceClaimSpec, fldPath *field.Path) field.ErrorList {
    42  	allErrs := field.ErrorList{}
    43  	for _, msg := range corevalidation.ValidateClassName(spec.ResourceClassName, false) {
    44  		allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClassName"), spec.ResourceClassName, msg))
    45  	}
    46  	allErrs = append(allErrs, validateResourceClaimParametersRef(spec.ParametersRef, fldPath.Child("parametersRef"))...)
    47  	if !supportedAllocationModes.Has(string(spec.AllocationMode)) {
    48  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), spec.AllocationMode, supportedAllocationModes.List()))
    49  	}
    50  	return allErrs
    51  }
    52  
    53  var supportedAllocationModes = sets.NewString(string(resource.AllocationModeImmediate), string(resource.AllocationModeWaitForFirstConsumer))
    54  
    55  // It would have been nice to use Go generics to reuse the same validation
    56  // function for Kind and Name in both types, but generics cannot be used to
    57  // access common fields in structs.
    58  
    59  func validateResourceClaimParametersRef(ref *resource.ResourceClaimParametersReference, fldPath *field.Path) field.ErrorList {
    60  	var allErrs field.ErrorList
    61  	if ref != nil {
    62  		if ref.Kind == "" {
    63  			allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
    64  		}
    65  		if ref.Name == "" {
    66  			allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
    67  		}
    68  	}
    69  	return allErrs
    70  }
    71  
    72  func validateClassParameters(ref *resource.ResourceClassParametersReference, fldPath *field.Path) field.ErrorList {
    73  	var allErrs field.ErrorList
    74  	if ref != nil {
    75  		if ref.Kind == "" {
    76  			allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
    77  		}
    78  		if ref.Name == "" {
    79  			allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
    80  		}
    81  		if ref.Namespace != "" {
    82  			for _, msg := range apimachineryvalidation.ValidateNamespaceName(ref.Namespace, false) {
    83  				allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), ref.Namespace, msg))
    84  			}
    85  		}
    86  	}
    87  	return allErrs
    88  }
    89  
    90  // ValidateClass validates a ResourceClass.
    91  func ValidateClass(resourceClass *resource.ResourceClass) field.ErrorList {
    92  	allErrs := corevalidation.ValidateObjectMeta(&resourceClass.ObjectMeta, false, corevalidation.ValidateClassName, field.NewPath("metadata"))
    93  	allErrs = append(allErrs, validateResourceDriverName(resourceClass.DriverName, field.NewPath("driverName"))...)
    94  	allErrs = append(allErrs, validateClassParameters(resourceClass.ParametersRef, field.NewPath("parametersRef"))...)
    95  	if resourceClass.SuitableNodes != nil {
    96  		allErrs = append(allErrs, corevalidation.ValidateNodeSelector(resourceClass.SuitableNodes, field.NewPath("suitableNodes"))...)
    97  	}
    98  
    99  	return allErrs
   100  }
   101  
   102  // ValidateClassUpdate tests if an update to ResourceClass is valid.
   103  func ValidateClassUpdate(resourceClass, oldClass *resource.ResourceClass) field.ErrorList {
   104  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClass.ObjectMeta, &oldClass.ObjectMeta, field.NewPath("metadata"))
   105  	allErrs = append(allErrs, ValidateClass(resourceClass)...)
   106  	return allErrs
   107  }
   108  
   109  // ValidateClaimUpdate tests if an update to ResourceClaim is valid.
   110  func ValidateClaimUpdate(resourceClaim, oldClaim *resource.ResourceClaim) field.ErrorList {
   111  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClaim.ObjectMeta, &oldClaim.ObjectMeta, field.NewPath("metadata"))
   112  	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(resourceClaim.Spec, oldClaim.Spec, field.NewPath("spec"))...)
   113  	allErrs = append(allErrs, ValidateClaim(resourceClaim)...)
   114  	return allErrs
   115  }
   116  
   117  // ValidateClaimStatusUpdate tests if an update to the status of a ResourceClaim is valid.
   118  func ValidateClaimStatusUpdate(resourceClaim, oldClaim *resource.ResourceClaim) field.ErrorList {
   119  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClaim.ObjectMeta, &oldClaim.ObjectMeta, field.NewPath("metadata"))
   120  	fldPath := field.NewPath("status")
   121  	// The name might not be set yet.
   122  	if resourceClaim.Status.DriverName != "" {
   123  		allErrs = append(allErrs, validateResourceDriverName(resourceClaim.Status.DriverName, fldPath.Child("driverName"))...)
   124  	} else if resourceClaim.Status.Allocation != nil {
   125  		allErrs = append(allErrs, field.Required(fldPath.Child("driverName"), "must be specified when `allocation` is set"))
   126  	}
   127  
   128  	allErrs = append(allErrs, validateAllocationResult(resourceClaim.Status.Allocation, fldPath.Child("allocation"))...)
   129  	allErrs = append(allErrs, validateResourceClaimConsumers(resourceClaim.Status.ReservedFor, resource.ResourceClaimReservedForMaxSize, fldPath.Child("reservedFor"))...)
   130  
   131  	// Now check for invariants that must be valid for a ResourceClaim.
   132  	if len(resourceClaim.Status.ReservedFor) > 0 {
   133  		if resourceClaim.Status.Allocation == nil {
   134  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("reservedFor"), "may not be specified when `allocated` is not set"))
   135  		} else {
   136  			if !resourceClaim.Status.Allocation.Shareable && len(resourceClaim.Status.ReservedFor) > 1 {
   137  				allErrs = append(allErrs, field.Forbidden(fldPath.Child("reservedFor"), "may not be reserved more than once"))
   138  			}
   139  			// Items may be removed from ReservedFor while the claim is meant to be deallocated,
   140  			// but not added.
   141  			if resourceClaim.DeletionTimestamp != nil || resourceClaim.Status.DeallocationRequested {
   142  				oldSet := sets.New(oldClaim.Status.ReservedFor...)
   143  				newSet := sets.New(resourceClaim.Status.ReservedFor...)
   144  				newItems := newSet.Difference(oldSet)
   145  				if len(newItems) > 0 {
   146  					allErrs = append(allErrs, field.Forbidden(fldPath.Child("reservedFor"), "new entries may not be added while `deallocationRequested` or `deletionTimestamp` are set"))
   147  				}
   148  			}
   149  		}
   150  	}
   151  
   152  	// Updates to a populated resourceClaim.Status.Allocation are not allowed
   153  	if oldClaim.Status.Allocation != nil && resourceClaim.Status.Allocation != nil {
   154  		allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(resourceClaim.Status.Allocation, oldClaim.Status.Allocation, fldPath.Child("allocation"))...)
   155  	}
   156  
   157  	if !oldClaim.Status.DeallocationRequested &&
   158  		resourceClaim.Status.DeallocationRequested &&
   159  		len(resourceClaim.Status.ReservedFor) > 0 {
   160  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("deallocationRequested"), "deallocation cannot be requested while `reservedFor` is set"))
   161  	}
   162  
   163  	if resourceClaim.Status.Allocation == nil &&
   164  		resourceClaim.Status.DeallocationRequested {
   165  		// Either one or the other field was modified incorrectly.
   166  		// For the sake of simplicity this only reports the invalid
   167  		// end result.
   168  		allErrs = append(allErrs, field.Forbidden(fldPath, "`allocation` must be set when `deallocationRequested` is set"))
   169  	}
   170  
   171  	// Once deallocation has been requested, that request cannot be removed
   172  	// anymore because the deallocation may already have started. The field
   173  	// can only get reset by the driver together with removing the
   174  	// allocation.
   175  	if oldClaim.Status.DeallocationRequested &&
   176  		!resourceClaim.Status.DeallocationRequested &&
   177  		resourceClaim.Status.Allocation != nil {
   178  		allErrs = append(allErrs, field.Forbidden(fldPath.Child("deallocationRequested"), "may not be cleared when `allocation` is set"))
   179  	}
   180  
   181  	return allErrs
   182  }
   183  
   184  func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path) field.ErrorList {
   185  	var allErrs field.ErrorList
   186  	if allocation != nil {
   187  		if len(allocation.ResourceHandles) > 0 {
   188  			allErrs = append(allErrs, validateResourceHandles(allocation.ResourceHandles, resource.AllocationResultResourceHandlesMaxSize, fldPath.Child("resourceHandles"))...)
   189  		}
   190  		if allocation.AvailableOnNodes != nil {
   191  			allErrs = append(allErrs, corevalidation.ValidateNodeSelector(allocation.AvailableOnNodes, fldPath.Child("availableOnNodes"))...)
   192  		}
   193  	}
   194  	return allErrs
   195  }
   196  
   197  func validateResourceHandles(resourceHandles []resource.ResourceHandle, maxSize int, fldPath *field.Path) field.ErrorList {
   198  	var allErrs field.ErrorList
   199  	for i, resourceHandle := range resourceHandles {
   200  		idxPath := fldPath.Index(i)
   201  		allErrs = append(allErrs, validateResourceDriverName(resourceHandle.DriverName, idxPath.Child("driverName"))...)
   202  		if len(resourceHandle.Data) > resource.ResourceHandleDataMaxSize {
   203  			allErrs = append(allErrs, field.TooLongMaxLength(idxPath.Child("data"), len(resourceHandle.Data), resource.ResourceHandleDataMaxSize))
   204  		}
   205  		if resourceHandle.StructuredData != nil {
   206  			allErrs = append(allErrs, validateStructuredResourceHandle(resourceHandle.StructuredData, idxPath.Child("structuredData"))...)
   207  		}
   208  		if len(resourceHandle.Data) > 0 && resourceHandle.StructuredData != nil {
   209  			allErrs = append(allErrs, field.Invalid(idxPath, nil, "data and structuredData are mutually exclusive"))
   210  		}
   211  	}
   212  	if len(resourceHandles) > maxSize {
   213  		// Dumping the entire field into the error message is likely to be too long,
   214  		// in particular when it is already beyond the maximum size. Instead this
   215  		// just shows the number of entries.
   216  		allErrs = append(allErrs, field.TooLongMaxLength(fldPath, len(resourceHandles), maxSize))
   217  	}
   218  	return allErrs
   219  }
   220  
   221  func validateStructuredResourceHandle(handle *resource.StructuredResourceHandle, fldPath *field.Path) field.ErrorList {
   222  	var allErrs field.ErrorList
   223  	if handle.NodeName != "" {
   224  		allErrs = append(allErrs, validateNodeName(handle.NodeName, fldPath.Child("nodeName"))...)
   225  	}
   226  	allErrs = append(allErrs, validateDriverAllocationResults(handle.Results, fldPath.Child("results"))...)
   227  	return allErrs
   228  }
   229  
   230  func validateDriverAllocationResults(results []resource.DriverAllocationResult, fldPath *field.Path) field.ErrorList {
   231  	var allErrs field.ErrorList
   232  	for index, result := range results {
   233  		idxPath := fldPath.Index(index)
   234  		allErrs = append(allErrs, validateAllocationResultModel(&result.AllocationResultModel, idxPath)...)
   235  	}
   236  	return allErrs
   237  }
   238  
   239  func validateAllocationResultModel(model *resource.AllocationResultModel, fldPath *field.Path) field.ErrorList {
   240  	var allErrs field.ErrorList
   241  	entries := sets.New[string]()
   242  	if model.NamedResources != nil {
   243  		entries.Insert("namedResources")
   244  		allErrs = append(allErrs, namedresourcesvalidation.ValidateAllocationResult(model.NamedResources, fldPath.Child("namedResources"))...)
   245  	}
   246  	switch len(entries) {
   247  	case 0:
   248  		allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
   249  	case 1:
   250  		// Okay.
   251  	default:
   252  		allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
   253  	}
   254  	return allErrs
   255  }
   256  
   257  func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerReference, fldPath *field.Path) field.ErrorList {
   258  	var allErrs field.ErrorList
   259  	if ref.Resource == "" {
   260  		allErrs = append(allErrs, field.Required(fldPath.Child("resource"), ""))
   261  	}
   262  	if ref.Name == "" {
   263  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
   264  	}
   265  	if ref.UID == "" {
   266  		allErrs = append(allErrs, field.Required(fldPath.Child("uid"), ""))
   267  	}
   268  	return allErrs
   269  }
   270  
   271  // validateSliceIsASet ensures that a slice contains no duplicates and does not exceed a certain maximum size.
   272  func validateSliceIsASet[T comparable](slice []T, maxSize int, validateItem func(item T, fldPath *field.Path) field.ErrorList, fldPath *field.Path) field.ErrorList {
   273  	var allErrs field.ErrorList
   274  	allItems := sets.New[T]()
   275  	for i, item := range slice {
   276  		idxPath := fldPath.Index(i)
   277  		if allItems.Has(item) {
   278  			allErrs = append(allErrs, field.Duplicate(idxPath, item))
   279  		} else {
   280  			allErrs = append(allErrs, validateItem(item, idxPath)...)
   281  			allItems.Insert(item)
   282  		}
   283  	}
   284  	if len(slice) > maxSize {
   285  		// Dumping the entire field into the error message is likely to be too long,
   286  		// in particular when it is already beyond the maximum size. Instead this
   287  		// just shows the number of entries.
   288  		allErrs = append(allErrs, field.TooLongMaxLength(fldPath, len(slice), maxSize))
   289  	}
   290  	return allErrs
   291  }
   292  
   293  // validateResourceClaimConsumers ensures that the slice contains no duplicate UIDs and does not exceed a certain maximum size.
   294  func validateResourceClaimConsumers(consumers []resource.ResourceClaimConsumerReference, maxSize int, fldPath *field.Path) field.ErrorList {
   295  	var allErrs field.ErrorList
   296  	allUIDs := sets.New[types.UID]()
   297  	for i, consumer := range consumers {
   298  		idxPath := fldPath.Index(i)
   299  		if allUIDs.Has(consumer.UID) {
   300  			allErrs = append(allErrs, field.Duplicate(idxPath.Child("uid"), consumer.UID))
   301  		} else {
   302  			allErrs = append(allErrs, validateResourceClaimUserReference(consumer, idxPath)...)
   303  			allUIDs.Insert(consumer.UID)
   304  		}
   305  	}
   306  	if len(consumers) > maxSize {
   307  		// Dumping the entire field into the error message is likely to be too long,
   308  		// in particular when it is already beyond the maximum size. Instead this
   309  		// just shows the number of entries.
   310  		allErrs = append(allErrs, field.TooLongMaxLength(fldPath, len(consumers), maxSize))
   311  	}
   312  	return allErrs
   313  }
   314  
   315  // ValidatePodSchedulingContext validates a PodSchedulingContext.
   316  func ValidatePodSchedulingContexts(schedulingCtx *resource.PodSchedulingContext) field.ErrorList {
   317  	allErrs := corevalidation.ValidateObjectMeta(&schedulingCtx.ObjectMeta, true, corevalidation.ValidatePodName, field.NewPath("metadata"))
   318  	allErrs = append(allErrs, validatePodSchedulingSpec(&schedulingCtx.Spec, field.NewPath("spec"))...)
   319  	return allErrs
   320  }
   321  
   322  func validatePodSchedulingSpec(spec *resource.PodSchedulingContextSpec, fldPath *field.Path) field.ErrorList {
   323  	allErrs := validateSliceIsASet(spec.PotentialNodes, resource.PodSchedulingNodeListMaxSize, validateNodeName, fldPath.Child("potentialNodes"))
   324  	return allErrs
   325  }
   326  
   327  // ValidatePodSchedulingContextUpdate tests if an update to PodSchedulingContext is valid.
   328  func ValidatePodSchedulingContextUpdate(schedulingCtx, oldSchedulingCtx *resource.PodSchedulingContext) field.ErrorList {
   329  	allErrs := corevalidation.ValidateObjectMetaUpdate(&schedulingCtx.ObjectMeta, &oldSchedulingCtx.ObjectMeta, field.NewPath("metadata"))
   330  	allErrs = append(allErrs, ValidatePodSchedulingContexts(schedulingCtx)...)
   331  	return allErrs
   332  }
   333  
   334  // ValidatePodSchedulingContextStatusUpdate tests if an update to the status of a PodSchedulingContext is valid.
   335  func ValidatePodSchedulingContextStatusUpdate(schedulingCtx, oldSchedulingCtx *resource.PodSchedulingContext) field.ErrorList {
   336  	allErrs := corevalidation.ValidateObjectMetaUpdate(&schedulingCtx.ObjectMeta, &oldSchedulingCtx.ObjectMeta, field.NewPath("metadata"))
   337  	allErrs = append(allErrs, validatePodSchedulingStatus(&schedulingCtx.Status, field.NewPath("status"))...)
   338  	return allErrs
   339  }
   340  
   341  func validatePodSchedulingStatus(status *resource.PodSchedulingContextStatus, fldPath *field.Path) field.ErrorList {
   342  	return validatePodSchedulingClaims(status.ResourceClaims, fldPath.Child("claims"))
   343  }
   344  
   345  func validatePodSchedulingClaims(claimStatuses []resource.ResourceClaimSchedulingStatus, fldPath *field.Path) field.ErrorList {
   346  	var allErrs field.ErrorList
   347  	names := sets.NewString()
   348  	for i, claimStatus := range claimStatuses {
   349  		allErrs = append(allErrs, validatePodSchedulingClaim(claimStatus, fldPath.Index(i))...)
   350  		if names.Has(claimStatus.Name) {
   351  			allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), claimStatus.Name))
   352  		} else {
   353  			names.Insert(claimStatus.Name)
   354  		}
   355  	}
   356  	return allErrs
   357  }
   358  
   359  func validatePodSchedulingClaim(status resource.ResourceClaimSchedulingStatus, fldPath *field.Path) field.ErrorList {
   360  	allErrs := validateSliceIsASet(status.UnsuitableNodes, resource.PodSchedulingNodeListMaxSize, validateNodeName, fldPath.Child("unsuitableNodes"))
   361  	return allErrs
   362  }
   363  
   364  // ValidateClaimTemplace validates a ResourceClaimTemplate.
   365  func ValidateClaimTemplate(template *resource.ResourceClaimTemplate) field.ErrorList {
   366  	allErrs := corevalidation.ValidateObjectMeta(&template.ObjectMeta, true, corevalidation.ValidateResourceClaimTemplateName, field.NewPath("metadata"))
   367  	allErrs = append(allErrs, validateResourceClaimTemplateSpec(&template.Spec, field.NewPath("spec"))...)
   368  	return allErrs
   369  }
   370  
   371  func validateResourceClaimTemplateSpec(spec *resource.ResourceClaimTemplateSpec, fldPath *field.Path) field.ErrorList {
   372  	allErrs := corevalidation.ValidateTemplateObjectMeta(&spec.ObjectMeta, fldPath.Child("metadata"))
   373  	allErrs = append(allErrs, validateResourceClaimSpec(&spec.Spec, fldPath.Child("spec"))...)
   374  	return allErrs
   375  }
   376  
   377  // ValidateClaimTemplateUpdate tests if an update to template is valid.
   378  func ValidateClaimTemplateUpdate(template, oldTemplate *resource.ResourceClaimTemplate) field.ErrorList {
   379  	allErrs := corevalidation.ValidateObjectMetaUpdate(&template.ObjectMeta, &oldTemplate.ObjectMeta, field.NewPath("metadata"))
   380  	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(template.Spec, oldTemplate.Spec, field.NewPath("spec"))...)
   381  	allErrs = append(allErrs, ValidateClaimTemplate(template)...)
   382  	return allErrs
   383  }
   384  
   385  func validateNodeName(name string, fldPath *field.Path) field.ErrorList {
   386  	var allErrs field.ErrorList
   387  	for _, msg := range corevalidation.ValidateNodeName(name, false) {
   388  		allErrs = append(allErrs, field.Invalid(fldPath, name, msg))
   389  	}
   390  	return allErrs
   391  }
   392  
   393  // ValidateResourceSlice tests if a ResourceSlice object is valid.
   394  func ValidateResourceSlice(resourceSlice *resource.ResourceSlice) field.ErrorList {
   395  	allErrs := corevalidation.ValidateObjectMeta(&resourceSlice.ObjectMeta, false, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
   396  	if resourceSlice.NodeName != "" {
   397  		allErrs = append(allErrs, validateNodeName(resourceSlice.NodeName, field.NewPath("nodeName"))...)
   398  	}
   399  	allErrs = append(allErrs, validateResourceDriverName(resourceSlice.DriverName, field.NewPath("driverName"))...)
   400  	allErrs = append(allErrs, validateResourceModel(&resourceSlice.ResourceModel, nil)...)
   401  	return allErrs
   402  }
   403  
   404  func validateResourceModel(model *resource.ResourceModel, fldPath *field.Path) field.ErrorList {
   405  	var allErrs field.ErrorList
   406  	entries := sets.New[string]()
   407  	if model.NamedResources != nil {
   408  		entries.Insert("namedResources")
   409  		allErrs = append(allErrs, namedresourcesvalidation.ValidateResources(model.NamedResources, fldPath.Child("namedResources"))...)
   410  	}
   411  	switch len(entries) {
   412  	case 0:
   413  		allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
   414  	case 1:
   415  		// Okay.
   416  	default:
   417  		allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
   418  	}
   419  	return allErrs
   420  }
   421  
   422  // ValidateResourceSlice tests if a ResourceSlice update is valid.
   423  func ValidateResourceSliceUpdate(resourceSlice, oldResourceSlice *resource.ResourceSlice) field.ErrorList {
   424  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceSlice.ObjectMeta, &oldResourceSlice.ObjectMeta, field.NewPath("metadata"))
   425  	allErrs = append(allErrs, ValidateResourceSlice(resourceSlice)...)
   426  	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(resourceSlice.NodeName, oldResourceSlice.NodeName, field.NewPath("nodeName"))...)
   427  	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(resourceSlice.DriverName, oldResourceSlice.DriverName, field.NewPath("driverName"))...)
   428  	return allErrs
   429  }
   430  
   431  // ValidateResourceClaimParameters tests if a ResourceClaimParameters object is valid for creation.
   432  func ValidateResourceClaimParameters(parameters *resource.ResourceClaimParameters) field.ErrorList {
   433  	return validateResourceClaimParameters(parameters, false)
   434  }
   435  
   436  func validateResourceClaimParameters(parameters *resource.ResourceClaimParameters, requestStored bool) field.ErrorList {
   437  	allErrs := corevalidation.ValidateObjectMeta(&parameters.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
   438  	allErrs = append(allErrs, validateResourceClaimParametersRef(parameters.GeneratedFrom, field.NewPath("generatedFrom"))...)
   439  	allErrs = append(allErrs, validateDriverRequests(parameters.DriverRequests, field.NewPath("driverRequests"), requestStored)...)
   440  	return allErrs
   441  }
   442  
   443  func validateDriverRequests(requests []resource.DriverRequests, fldPath *field.Path, requestStored bool) field.ErrorList {
   444  	var allErrs field.ErrorList
   445  	driverNames := sets.New[string]()
   446  	for i, request := range requests {
   447  		idxPath := fldPath.Index(i)
   448  		driverName := request.DriverName
   449  		allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
   450  		if driverNames.Has(driverName) {
   451  			allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
   452  		} else {
   453  			driverNames.Insert(driverName)
   454  		}
   455  		allErrs = append(allErrs, validateResourceRequests(request.Requests, idxPath.Child("requests"), requestStored)...)
   456  	}
   457  	return allErrs
   458  }
   459  
   460  func validateResourceRequests(requests []resource.ResourceRequest, fldPath *field.Path, requestStored bool) field.ErrorList {
   461  	var allErrs field.ErrorList
   462  	for i, request := range requests {
   463  		idxPath := fldPath.Index(i)
   464  		allErrs = append(allErrs, validateResourceRequestModel(&request.ResourceRequestModel, idxPath, requestStored)...)
   465  	}
   466  	if len(requests) == 0 {
   467  		// We could allow this, it just doesn't make sense: the entire entry would get ignored and thus
   468  		// should have been left out entirely.
   469  		allErrs = append(allErrs, field.Required(fldPath, "empty entries with no requests are not allowed"))
   470  	}
   471  	return allErrs
   472  }
   473  
   474  func validateResourceRequestModel(model *resource.ResourceRequestModel, fldPath *field.Path, requestStored bool) field.ErrorList {
   475  	var allErrs field.ErrorList
   476  	entries := sets.New[string]()
   477  	if model.NamedResources != nil {
   478  		entries.Insert("namedResources")
   479  		allErrs = append(allErrs, namedresourcesvalidation.ValidateRequest(namedresourcesvalidation.Options{StoredExpressions: requestStored}, model.NamedResources, fldPath.Child("namedResources"))...)
   480  	}
   481  	switch len(entries) {
   482  	case 0:
   483  		allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
   484  	case 1:
   485  		// Okay.
   486  	default:
   487  		allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
   488  	}
   489  	return allErrs
   490  }
   491  
   492  // ValidateResourceClaimParameters tests if a ResourceClaimParameters update is valid.
   493  func ValidateResourceClaimParametersUpdate(resourceClaimParameters, oldResourceClaimParameters *resource.ResourceClaimParameters) field.ErrorList {
   494  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClaimParameters.ObjectMeta, &oldResourceClaimParameters.ObjectMeta, field.NewPath("metadata"))
   495  	requestStored := apiequality.Semantic.DeepEqual(oldResourceClaimParameters.DriverRequests, resourceClaimParameters.DriverRequests)
   496  	allErrs = append(allErrs, validateResourceClaimParameters(resourceClaimParameters, requestStored)...)
   497  	return allErrs
   498  }
   499  
   500  // ValidateResourceClassParameters tests if a ResourceClassParameters object is valid for creation.
   501  func ValidateResourceClassParameters(parameters *resource.ResourceClassParameters) field.ErrorList {
   502  	return validateResourceClassParameters(parameters, false)
   503  }
   504  
   505  func validateResourceClassParameters(parameters *resource.ResourceClassParameters, filtersStored bool) field.ErrorList {
   506  	allErrs := corevalidation.ValidateObjectMeta(&parameters.ObjectMeta, true, apimachineryvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
   507  	allErrs = append(allErrs, validateClassParameters(parameters.GeneratedFrom, field.NewPath("generatedFrom"))...)
   508  	allErrs = append(allErrs, validateResourceFilters(parameters.Filters, field.NewPath("filters"), filtersStored)...)
   509  	allErrs = append(allErrs, validateVendorParameters(parameters.VendorParameters, field.NewPath("vendorParameters"))...)
   510  	return allErrs
   511  }
   512  
   513  func validateResourceFilters(filters []resource.ResourceFilter, fldPath *field.Path, filtersStored bool) field.ErrorList {
   514  	var allErrs field.ErrorList
   515  	driverNames := sets.New[string]()
   516  	for i, filter := range filters {
   517  		idxPath := fldPath.Index(i)
   518  		driverName := filter.DriverName
   519  		allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
   520  		if driverNames.Has(driverName) {
   521  			allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
   522  		} else {
   523  			driverNames.Insert(driverName)
   524  		}
   525  		allErrs = append(allErrs, validateResourceFilterModel(&filter.ResourceFilterModel, idxPath, filtersStored)...)
   526  	}
   527  	return allErrs
   528  }
   529  
   530  func validateResourceFilterModel(model *resource.ResourceFilterModel, fldPath *field.Path, filtersStored bool) field.ErrorList {
   531  	var allErrs field.ErrorList
   532  	entries := sets.New[string]()
   533  	if model.NamedResources != nil {
   534  		entries.Insert("namedResources")
   535  		allErrs = append(allErrs, namedresourcesvalidation.ValidateFilter(namedresourcesvalidation.Options{StoredExpressions: filtersStored}, model.NamedResources, fldPath.Child("namedResources"))...)
   536  	}
   537  	switch len(entries) {
   538  	case 0:
   539  		allErrs = append(allErrs, field.Required(fldPath, "exactly one structured model field must be set"))
   540  	case 1:
   541  		// Okay.
   542  	default:
   543  		allErrs = append(allErrs, field.Invalid(fldPath, sets.List(entries), "exactly one field must be set, not several"))
   544  	}
   545  	return allErrs
   546  }
   547  
   548  func validateVendorParameters(parameters []resource.VendorParameters, fldPath *field.Path) field.ErrorList {
   549  	var allErrs field.ErrorList
   550  	driverNames := sets.New[string]()
   551  	for i, parameters := range parameters {
   552  		idxPath := fldPath.Index(i)
   553  		driverName := parameters.DriverName
   554  		allErrs = append(allErrs, validateResourceDriverName(driverName, idxPath.Child("driverName"))...)
   555  		if driverNames.Has(driverName) {
   556  			allErrs = append(allErrs, field.Duplicate(idxPath.Child("driverName"), driverName))
   557  		} else {
   558  			driverNames.Insert(driverName)
   559  		}
   560  	}
   561  	return allErrs
   562  }
   563  
   564  // ValidateResourceClassParameters tests if a ResourceClassParameters update is valid.
   565  func ValidateResourceClassParametersUpdate(resourceClassParameters, oldResourceClassParameters *resource.ResourceClassParameters) field.ErrorList {
   566  	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClassParameters.ObjectMeta, &oldResourceClassParameters.ObjectMeta, field.NewPath("metadata"))
   567  	allErrs = append(allErrs, ValidateResourceClassParameters(resourceClassParameters)...)
   568  	return allErrs
   569  }