mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			171 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			171 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2014 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 (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	"k8s.io/apimachinery/pkg/api/resource"
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/util/validation/field"
 | |
| 	"k8s.io/kubernetes/pkg/apis/core/helper"
 | |
| 	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
 | |
| 	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
 | |
| )
 | |
| 
 | |
| const isNegativeErrorMsg string = `must be greater than or equal to 0`
 | |
| const isNotIntegerErrorMsg string = `must be an integer`
 | |
| 
 | |
| // ValidateResourceRequirements will check if any of the resource
 | |
| // Limits/Requests are of a valid value. Any incorrect value will be added to
 | |
| // the ErrorList.
 | |
| func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 	limPath := fldPath.Child("limits")
 | |
| 	reqPath := fldPath.Child("requests")
 | |
| 	for resourceName, quantity := range requirements.Limits {
 | |
| 		fldPath := limPath.Key(string(resourceName))
 | |
| 		// Validate resource name.
 | |
| 		allErrs = append(allErrs, ValidateContainerResourceName(string(resourceName), fldPath)...)
 | |
| 
 | |
| 		// Validate resource quantity.
 | |
| 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
 | |
| 
 | |
| 	}
 | |
| 	for resourceName, quantity := range requirements.Requests {
 | |
| 		fldPath := reqPath.Key(string(resourceName))
 | |
| 		// Validate resource name.
 | |
| 		allErrs = append(allErrs, ValidateContainerResourceName(string(resourceName), fldPath)...)
 | |
| 		// Validate resource quantity.
 | |
| 		allErrs = append(allErrs, ValidateResourceQuantityValue(string(resourceName), quantity, fldPath)...)
 | |
| 
 | |
| 		// Check that request <= limit.
 | |
| 		limitQuantity, exists := requirements.Limits[resourceName]
 | |
| 		if exists {
 | |
| 			// For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal.
 | |
| 			if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) {
 | |
| 				allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit", resourceName)))
 | |
| 			} else if quantity.Cmp(limitQuantity) > 0 {
 | |
| 				allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit", resourceName)))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // ValidateContainerResourceName checks the name of resource specified for a container
 | |
| func ValidateContainerResourceName(value string, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := validateResourceName(value, fldPath)
 | |
| 	if len(strings.Split(value, "/")) == 1 {
 | |
| 		if !helper.IsStandardContainerResourceName(value) {
 | |
| 			return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers"))
 | |
| 		}
 | |
| 	} else if !v1helper.IsNativeResource(v1.ResourceName(value)) {
 | |
| 		if !v1helper.IsExtendedResourceName(v1.ResourceName(value)) {
 | |
| 			return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard"))
 | |
| 		}
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource
 | |
| func ValidateResourceQuantityValue(resource string, value resource.Quantity, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 	allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...)
 | |
| 	if helper.IsIntegerResourceName(resource) {
 | |
| 		if value.MilliValue()%int64(1000) != int64(0) {
 | |
| 			allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg))
 | |
| 		}
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // ValidateNonnegativeQuantity checks that a Quantity is not negative.
 | |
| func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 	if value.Cmp(resource.Quantity{}) < 0 {
 | |
| 		allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg))
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // Validate compute resource typename.
 | |
| // Refer to docs/design/resources.md for more details.
 | |
| func validateResourceName(value string, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := apivalidation.ValidateQualifiedName(value, fldPath)
 | |
| 	if len(allErrs) != 0 {
 | |
| 		return allErrs
 | |
| 	}
 | |
| 
 | |
| 	if len(strings.Split(value, "/")) == 1 {
 | |
| 		if !helper.IsStandardResourceName(value) {
 | |
| 			return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified"))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // ValidatePodLogOptions checks if options that are set are at the correct
 | |
| // value. Any incorrect value will be returned to the ErrorList.
 | |
| func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 	if opts.TailLines != nil && *opts.TailLines < 0 {
 | |
| 		allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg))
 | |
| 	}
 | |
| 	if opts.LimitBytes != nil && *opts.LimitBytes < 1 {
 | |
| 		allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0"))
 | |
| 	}
 | |
| 	switch {
 | |
| 	case opts.SinceSeconds != nil && opts.SinceTime != nil:
 | |
| 		allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified"))
 | |
| 	case opts.SinceSeconds != nil:
 | |
| 		if *opts.SinceSeconds < 1 {
 | |
| 			allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0"))
 | |
| 		}
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 | |
| 
 | |
| // AccumulateUniqueHostPorts checks all the containers for duplicates ports. Any
 | |
| // duplicate port will be returned in the ErrorList.
 | |
| func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList {
 | |
| 	allErrs := field.ErrorList{}
 | |
| 
 | |
| 	for ci, ctr := range containers {
 | |
| 		idxPath := fldPath.Index(ci)
 | |
| 		portsPath := idxPath.Child("ports")
 | |
| 		for pi := range ctr.Ports {
 | |
| 			idxPath := portsPath.Index(pi)
 | |
| 			port := ctr.Ports[pi].HostPort
 | |
| 			if port == 0 {
 | |
| 				continue
 | |
| 			}
 | |
| 			str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol)
 | |
| 			if accumulator.Has(str) {
 | |
| 				allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str))
 | |
| 			} else {
 | |
| 				accumulator.Insert(str)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return allErrs
 | |
| }
 |