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(¶meters.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(¶meters.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 }