mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
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 (
 | 
						|
	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						|
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						|
	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
 | 
						|
	"k8s.io/kubernetes/pkg/apis/resource"
 | 
						|
)
 | 
						|
 | 
						|
// validateResourceDriverName reuses the validation of a CSI driver because
 | 
						|
// the allowed values are exactly the same.
 | 
						|
var validateResourceDriverName = corevalidation.ValidateCSIDriverName
 | 
						|
 | 
						|
// ValidateClaim validates a ResourceClaim.
 | 
						|
func ValidateClaim(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"))...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validateResourceClaimSpec(spec *resource.ResourceClaimSpec, fldPath *field.Path) field.ErrorList {
 | 
						|
	allErrs := field.ErrorList{}
 | 
						|
	for _, msg := range corevalidation.ValidateClassName(spec.ResourceClassName, false) {
 | 
						|
		allErrs = append(allErrs, field.Invalid(fldPath.Child("resourceClassName"), spec.ResourceClassName, msg))
 | 
						|
	}
 | 
						|
	allErrs = append(allErrs, validateResourceClaimParameters(spec.ParametersRef, fldPath.Child("parametersRef"))...)
 | 
						|
	if !supportedAllocationModes.Has(string(spec.AllocationMode)) {
 | 
						|
		allErrs = append(allErrs, field.NotSupported(fldPath.Child("allocationMode"), spec.AllocationMode, supportedAllocationModes.List()))
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
var supportedAllocationModes = sets.NewString(string(resource.AllocationModeImmediate), string(resource.AllocationModeWaitForFirstConsumer))
 | 
						|
 | 
						|
// It would have been nice to use Go generics to reuse the same validation
 | 
						|
// function for Kind and Name in both types, but generics cannot be used to
 | 
						|
// access common fields in structs.
 | 
						|
 | 
						|
func validateResourceClaimParameters(ref *resource.ResourceClaimParametersReference, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	if ref != nil {
 | 
						|
		if ref.Kind == "" {
 | 
						|
			allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
 | 
						|
		}
 | 
						|
		if ref.Name == "" {
 | 
						|
			allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validateClassParameters(ref *resource.ResourceClassParametersReference, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	if ref != nil {
 | 
						|
		if ref.Kind == "" {
 | 
						|
			allErrs = append(allErrs, field.Required(fldPath.Child("kind"), ""))
 | 
						|
		}
 | 
						|
		if ref.Name == "" {
 | 
						|
			allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
 | 
						|
		}
 | 
						|
		if ref.Namespace != "" {
 | 
						|
			for _, msg := range apimachineryvalidation.ValidateNamespaceName(ref.Namespace, false) {
 | 
						|
				allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), ref.Namespace, msg))
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClass validates a ResourceClass.
 | 
						|
func ValidateClass(resourceClass *resource.ResourceClass) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMeta(&resourceClass.ObjectMeta, false, corevalidation.ValidateClassName, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, validateResourceDriverName(resourceClass.DriverName, field.NewPath("driverName"))...)
 | 
						|
	allErrs = append(allErrs, validateClassParameters(resourceClass.ParametersRef, field.NewPath("parametersRef"))...)
 | 
						|
	if resourceClass.SuitableNodes != nil {
 | 
						|
		allErrs = append(allErrs, corevalidation.ValidateNodeSelector(resourceClass.SuitableNodes, field.NewPath("suitableNodes"))...)
 | 
						|
	}
 | 
						|
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClassUpdate tests if an update to ResourceClass is valid.
 | 
						|
func ValidateClassUpdate(resourceClass, oldClass *resource.ResourceClass) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClass.ObjectMeta, &oldClass.ObjectMeta, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, ValidateClass(resourceClass)...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClaimUpdate tests if an update to ResourceClaim is valid.
 | 
						|
func ValidateClaimUpdate(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"))...)
 | 
						|
	allErrs = append(allErrs, ValidateClaim(resourceClaim)...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClaimStatusUpdate tests if an update to the status of a ResourceClaim is valid.
 | 
						|
func ValidateClaimStatusUpdate(resourceClaim, oldClaim *resource.ResourceClaim) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMetaUpdate(&resourceClaim.ObjectMeta, &oldClaim.ObjectMeta, field.NewPath("metadata"))
 | 
						|
	fldPath := field.NewPath("status")
 | 
						|
	// The name might not be set yet.
 | 
						|
	if resourceClaim.Status.DriverName != "" {
 | 
						|
		allErrs = append(allErrs, validateResourceDriverName(resourceClaim.Status.DriverName, fldPath.Child("driverName"))...)
 | 
						|
	} else if resourceClaim.Status.Allocation != nil {
 | 
						|
		allErrs = append(allErrs, field.Required(fldPath.Child("driverName"), "must be specified when `allocation` is set"))
 | 
						|
	}
 | 
						|
 | 
						|
	allErrs = append(allErrs, validateAllocationResult(resourceClaim.Status.Allocation, fldPath.Child("allocation"))...)
 | 
						|
	allErrs = append(allErrs, validateResourceClaimConsumers(resourceClaim.Status.ReservedFor, resource.ResourceClaimReservedForMaxSize, fldPath.Child("reservedFor"))...)
 | 
						|
 | 
						|
	// Now check for invariants that must be valid for a ResourceClaim.
 | 
						|
	if len(resourceClaim.Status.ReservedFor) > 0 {
 | 
						|
		if resourceClaim.Status.Allocation == nil {
 | 
						|
			allErrs = append(allErrs, field.Forbidden(fldPath.Child("reservedFor"), "may not be specified when `allocated` is not set"))
 | 
						|
		} else {
 | 
						|
			if !resourceClaim.Status.Allocation.Shareable && len(resourceClaim.Status.ReservedFor) > 1 {
 | 
						|
				allErrs = append(allErrs, field.Forbidden(fldPath.Child("reservedFor"), "may not be reserved more than once"))
 | 
						|
			}
 | 
						|
			// Items may be removed from ReservedFor while the claim is meant to be deallocated,
 | 
						|
			// but not added.
 | 
						|
			if resourceClaim.DeletionTimestamp != nil || resourceClaim.Status.DeallocationRequested {
 | 
						|
				oldSet := sets.New(oldClaim.Status.ReservedFor...)
 | 
						|
				newSet := sets.New(resourceClaim.Status.ReservedFor...)
 | 
						|
				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"))
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !oldClaim.Status.DeallocationRequested &&
 | 
						|
		resourceClaim.Status.DeallocationRequested &&
 | 
						|
		len(resourceClaim.Status.ReservedFor) > 0 {
 | 
						|
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("deallocationRequested"), "deallocation cannot be requested while `reservedFor` is set"))
 | 
						|
	}
 | 
						|
 | 
						|
	if resourceClaim.Status.Allocation == nil &&
 | 
						|
		resourceClaim.Status.DeallocationRequested {
 | 
						|
		// Either one or the other field was modified incorrectly.
 | 
						|
		// For the sake of simplicity this only reports the invalid
 | 
						|
		// end result.
 | 
						|
		allErrs = append(allErrs, field.Forbidden(fldPath, "`allocation` must be set when `deallocationRequested` is set"))
 | 
						|
	}
 | 
						|
 | 
						|
	// Once deallocation has been requested, that request cannot be removed
 | 
						|
	// anymore because the deallocation may already have started. The field
 | 
						|
	// can only get reset by the driver together with removing the
 | 
						|
	// allocation.
 | 
						|
	if oldClaim.Status.DeallocationRequested &&
 | 
						|
		!resourceClaim.Status.DeallocationRequested &&
 | 
						|
		resourceClaim.Status.Allocation != nil {
 | 
						|
		allErrs = append(allErrs, field.Forbidden(fldPath.Child("deallocationRequested"), "may not be cleared when `allocation` is set"))
 | 
						|
	}
 | 
						|
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validateAllocationResult(allocation *resource.AllocationResult, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	if allocation != nil {
 | 
						|
		if len(allocation.ResourceHandle) > resource.ResourceHandleMaxSize {
 | 
						|
			allErrs = append(allErrs, field.TooLongMaxLength(fldPath.Child("resourceHandle"), len(allocation.ResourceHandle), resource.ResourceHandleMaxSize))
 | 
						|
		}
 | 
						|
		if allocation.AvailableOnNodes != nil {
 | 
						|
			allErrs = append(allErrs, corevalidation.ValidateNodeSelector(allocation.AvailableOnNodes, fldPath.Child("availableOnNodes"))...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validateResourceClaimUserReference(ref resource.ResourceClaimConsumerReference, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	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"), ""))
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// validateSliceIsASet ensures that a slice contains no duplicates and does not exceed a certain maximum size.
 | 
						|
func validateSliceIsASet[T comparable](slice []T, maxSize int, validateItem func(item T, fldPath *field.Path) field.ErrorList, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	allItems := sets.New[T]()
 | 
						|
	for i, item := range slice {
 | 
						|
		idxPath := fldPath.Index(i)
 | 
						|
		if allItems.Has(item) {
 | 
						|
			allErrs = append(allErrs, field.Duplicate(idxPath, item))
 | 
						|
		} else {
 | 
						|
			allErrs = append(allErrs, validateItem(item, idxPath)...)
 | 
						|
			allItems.Insert(item)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if 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.
 | 
						|
		allErrs = append(allErrs, field.TooLongMaxLength(fldPath, len(slice), maxSize))
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// validateResourceClaimConsumers ensures that the slice contains no duplicate UIDs and does not exceed a certain maximum size.
 | 
						|
func validateResourceClaimConsumers(consumers []resource.ResourceClaimConsumerReference, maxSize int, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	allUIDs := sets.New[types.UID]()
 | 
						|
	for i, consumer := range consumers {
 | 
						|
		idxPath := fldPath.Index(i)
 | 
						|
		if allUIDs.Has(consumer.UID) {
 | 
						|
			allErrs = append(allErrs, field.Duplicate(idxPath.Child("uid"), consumer.UID))
 | 
						|
		} else {
 | 
						|
			allErrs = append(allErrs, validateResourceClaimUserReference(consumer, idxPath)...)
 | 
						|
			allUIDs.Insert(consumer.UID)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(consumers) > 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.
 | 
						|
		allErrs = append(allErrs, field.TooLongMaxLength(fldPath, len(consumers), maxSize))
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidatePodSchedulingContext validates a PodSchedulingContext.
 | 
						|
func ValidatePodSchedulingContexts(schedulingCtx *resource.PodSchedulingContext) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMeta(&schedulingCtx.ObjectMeta, true, corevalidation.ValidatePodName, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, validatePodSchedulingSpec(&schedulingCtx.Spec, field.NewPath("spec"))...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validatePodSchedulingSpec(spec *resource.PodSchedulingContextSpec, fldPath *field.Path) field.ErrorList {
 | 
						|
	allErrs := validateSliceIsASet(spec.PotentialNodes, resource.PodSchedulingNodeListMaxSize, validateNodeName, fldPath.Child("potentialNodes"))
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidatePodSchedulingContextUpdate tests if an update to PodSchedulingContext is valid.
 | 
						|
func ValidatePodSchedulingContextUpdate(schedulingCtx, oldSchedulingCtx *resource.PodSchedulingContext) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMetaUpdate(&schedulingCtx.ObjectMeta, &oldSchedulingCtx.ObjectMeta, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, ValidatePodSchedulingContexts(schedulingCtx)...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidatePodSchedulingContextStatusUpdate tests if an update to the status of a PodSchedulingContext is valid.
 | 
						|
func ValidatePodSchedulingContextStatusUpdate(schedulingCtx, oldSchedulingCtx *resource.PodSchedulingContext) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMetaUpdate(&schedulingCtx.ObjectMeta, &oldSchedulingCtx.ObjectMeta, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, validatePodSchedulingStatus(&schedulingCtx.Status, field.NewPath("status"))...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validatePodSchedulingStatus(status *resource.PodSchedulingContextStatus, fldPath *field.Path) field.ErrorList {
 | 
						|
	return validatePodSchedulingClaims(status.ResourceClaims, fldPath.Child("claims"))
 | 
						|
}
 | 
						|
 | 
						|
func validatePodSchedulingClaims(claimStatuses []resource.ResourceClaimSchedulingStatus, fldPath *field.Path) field.ErrorList {
 | 
						|
	var allErrs field.ErrorList
 | 
						|
	names := sets.NewString()
 | 
						|
	for i, claimStatus := range claimStatuses {
 | 
						|
		allErrs = append(allErrs, validatePodSchedulingClaim(claimStatus, fldPath.Index(i))...)
 | 
						|
		if names.Has(claimStatus.Name) {
 | 
						|
			allErrs = append(allErrs, field.Duplicate(fldPath.Index(i), claimStatus.Name))
 | 
						|
		} else {
 | 
						|
			names.Insert(claimStatus.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validatePodSchedulingClaim(status resource.ResourceClaimSchedulingStatus, fldPath *field.Path) field.ErrorList {
 | 
						|
	allErrs := validateSliceIsASet(status.UnsuitableNodes, resource.PodSchedulingNodeListMaxSize, validateNodeName, fldPath.Child("unsuitableNodes"))
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClaimTemplace validates a ResourceClaimTemplate.
 | 
						|
func ValidateClaimTemplate(template *resource.ResourceClaimTemplate) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMeta(&template.ObjectMeta, true, corevalidation.ValidateResourceClaimTemplateName, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, validateResourceClaimTemplateSpec(&template.Spec, field.NewPath("spec"))...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
func validateResourceClaimTemplateSpec(spec *resource.ResourceClaimTemplateSpec, fldPath *field.Path) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateTemplateObjectMeta(&spec.ObjectMeta, fldPath.Child("metadata"))
 | 
						|
	allErrs = append(allErrs, validateResourceClaimSpec(&spec.Spec, fldPath.Child("spec"))...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
// ValidateClaimTemplateUpdate tests if an update to template is valid.
 | 
						|
func ValidateClaimTemplateUpdate(template, oldTemplate *resource.ResourceClaimTemplate) field.ErrorList {
 | 
						|
	allErrs := corevalidation.ValidateObjectMetaUpdate(&template.ObjectMeta, &oldTemplate.ObjectMeta, field.NewPath("metadata"))
 | 
						|
	allErrs = append(allErrs, apimachineryvalidation.ValidateImmutableField(template.Spec, oldTemplate.Spec, field.NewPath("spec"))...)
 | 
						|
	allErrs = append(allErrs, ValidateClaimTemplate(template)...)
 | 
						|
	return allErrs
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
}
 |