k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/core/v1/validation/validation.go (about) 1 /* 2 Copyright 2014 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 "fmt" 21 "strings" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/resource" 25 "k8s.io/apimachinery/pkg/util/sets" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 "k8s.io/kubernetes/pkg/apis/core" 28 "k8s.io/kubernetes/pkg/apis/core/helper" 29 v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" 30 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 31 ) 32 33 const isNegativeErrorMsg string = `must be greater than or equal to 0` 34 const isNotIntegerErrorMsg string = `must be an integer` 35 36 // ValidateResourceRequirements will check if any of the resource 37 // Limits/Requests are of a valid value. Any incorrect value will be added to 38 // the ErrorList. 39 func ValidateResourceRequirements(requirements *v1.ResourceRequirements, fldPath *field.Path) field.ErrorList { 40 allErrs := field.ErrorList{} 41 limPath := fldPath.Child("limits") 42 reqPath := fldPath.Child("requests") 43 for resourceName, quantity := range requirements.Limits { 44 fldPath := limPath.Key(string(resourceName)) 45 // Validate resource name. 46 allErrs = append(allErrs, ValidateContainerResourceName(core.ResourceName(resourceName), fldPath)...) 47 48 // Validate resource quantity. 49 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceName(resourceName), quantity, fldPath)...) 50 51 } 52 for resourceName, quantity := range requirements.Requests { 53 fldPath := reqPath.Key(string(resourceName)) 54 // Validate resource name. 55 allErrs = append(allErrs, ValidateContainerResourceName(core.ResourceName(resourceName), fldPath)...) 56 // Validate resource quantity. 57 allErrs = append(allErrs, ValidateResourceQuantityValue(core.ResourceName(resourceName), quantity, fldPath)...) 58 59 // Check that request <= limit. 60 limitQuantity, exists := requirements.Limits[resourceName] 61 if exists { 62 // For GPUs, not only requests can't exceed limits, they also can't be lower, i.e. must be equal. 63 if quantity.Cmp(limitQuantity) != 0 && !v1helper.IsOvercommitAllowed(resourceName) { 64 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be equal to %s limit of %s", resourceName, limitQuantity.String()))) 65 } else if quantity.Cmp(limitQuantity) > 0 { 66 allErrs = append(allErrs, field.Invalid(reqPath, quantity.String(), fmt.Sprintf("must be less than or equal to %s limit of %s", resourceName, limitQuantity.String()))) 67 } 68 } 69 } 70 71 return allErrs 72 } 73 74 // ValidateContainerResourceName checks the name of resource specified for a container 75 func ValidateContainerResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList { 76 allErrs := validateResourceName(value, fldPath) 77 if len(strings.Split(string(value), "/")) == 1 { 78 if !helper.IsStandardContainerResourceName(value) { 79 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) 80 } 81 } else if !v1helper.IsNativeResource(v1.ResourceName(value)) { 82 if !v1helper.IsExtendedResourceName(v1.ResourceName(value)) { 83 return append(allErrs, field.Invalid(fldPath, value, "doesn't follow extended resource name standard")) 84 } 85 } 86 return allErrs 87 } 88 89 // ValidateResourceQuantityValue enforces that specified quantity is valid for specified resource 90 func ValidateResourceQuantityValue(resource core.ResourceName, value resource.Quantity, fldPath *field.Path) field.ErrorList { 91 allErrs := field.ErrorList{} 92 allErrs = append(allErrs, ValidateNonnegativeQuantity(value, fldPath)...) 93 if helper.IsIntegerResourceName(resource) { 94 if value.MilliValue()%int64(1000) != int64(0) { 95 allErrs = append(allErrs, field.Invalid(fldPath, value, isNotIntegerErrorMsg)) 96 } 97 } 98 return allErrs 99 } 100 101 // ValidateNonnegativeQuantity checks that a Quantity is not negative. 102 func ValidateNonnegativeQuantity(value resource.Quantity, fldPath *field.Path) field.ErrorList { 103 allErrs := field.ErrorList{} 104 if value.Cmp(resource.Quantity{}) < 0 { 105 allErrs = append(allErrs, field.Invalid(fldPath, value.String(), isNegativeErrorMsg)) 106 } 107 return allErrs 108 } 109 110 // Validate compute resource typename. 111 // Refer to docs/design/resources.md for more details. 112 func validateResourceName(value core.ResourceName, fldPath *field.Path) field.ErrorList { 113 allErrs := apivalidation.ValidateQualifiedName(string(value), fldPath) 114 if len(allErrs) != 0 { 115 return allErrs 116 } 117 118 if len(strings.Split(string(value), "/")) == 1 { 119 if !helper.IsStandardResourceName(value) { 120 return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource type or fully qualified")) 121 } 122 } 123 124 return allErrs 125 } 126 127 // ValidatePodLogOptions checks if options that are set are at the correct 128 // value. Any incorrect value will be returned to the ErrorList. 129 func ValidatePodLogOptions(opts *v1.PodLogOptions) field.ErrorList { 130 allErrs := field.ErrorList{} 131 if opts.TailLines != nil && *opts.TailLines < 0 { 132 allErrs = append(allErrs, field.Invalid(field.NewPath("tailLines"), *opts.TailLines, isNegativeErrorMsg)) 133 } 134 if opts.LimitBytes != nil && *opts.LimitBytes < 1 { 135 allErrs = append(allErrs, field.Invalid(field.NewPath("limitBytes"), *opts.LimitBytes, "must be greater than 0")) 136 } 137 switch { 138 case opts.SinceSeconds != nil && opts.SinceTime != nil: 139 allErrs = append(allErrs, field.Forbidden(field.NewPath(""), "at most one of `sinceTime` or `sinceSeconds` may be specified")) 140 case opts.SinceSeconds != nil: 141 if *opts.SinceSeconds < 1 { 142 allErrs = append(allErrs, field.Invalid(field.NewPath("sinceSeconds"), *opts.SinceSeconds, "must be greater than 0")) 143 } 144 } 145 return allErrs 146 } 147 148 // AccumulateUniqueHostPorts checks all the containers for duplicates ports. Any 149 // duplicate port will be returned in the ErrorList. 150 func AccumulateUniqueHostPorts(containers []v1.Container, accumulator *sets.String, fldPath *field.Path) field.ErrorList { 151 allErrs := field.ErrorList{} 152 153 for ci, ctr := range containers { 154 idxPath := fldPath.Index(ci) 155 portsPath := idxPath.Child("ports") 156 for pi := range ctr.Ports { 157 idxPath := portsPath.Index(pi) 158 port := ctr.Ports[pi].HostPort 159 if port == 0 { 160 continue 161 } 162 str := fmt.Sprintf("%d/%s", port, ctr.Ports[pi].Protocol) 163 if accumulator.Has(str) { 164 allErrs = append(allErrs, field.Duplicate(idxPath.Child("hostPort"), str)) 165 } else { 166 accumulator.Insert(str) 167 } 168 } 169 } 170 return allErrs 171 }