k8s.io/kubernetes@v1.29.3/pkg/apis/core/v1/helper/helpers.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 helper 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/labels" 26 "k8s.io/apimachinery/pkg/selection" 27 "k8s.io/apimachinery/pkg/util/validation" 28 "k8s.io/kubernetes/pkg/apis/core/helper" 29 ) 30 31 // IsExtendedResourceName returns true if: 32 // 1. the resource name is not in the default namespace; 33 // 2. resource name does not have "requests." prefix, 34 // to avoid confusion with the convention in quota 35 // 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name 36 func IsExtendedResourceName(name v1.ResourceName) bool { 37 if IsNativeResource(name) || strings.HasPrefix(string(name), v1.DefaultResourceRequestsPrefix) { 38 return false 39 } 40 // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name 41 nameForQuota := fmt.Sprintf("%s%s", v1.DefaultResourceRequestsPrefix, string(name)) 42 if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 { 43 return false 44 } 45 return true 46 } 47 48 // IsPrefixedNativeResource returns true if the resource name is in the 49 // *kubernetes.io/ namespace. 50 func IsPrefixedNativeResource(name v1.ResourceName) bool { 51 return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix) 52 } 53 54 // IsNativeResource returns true if the resource name is in the 55 // *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are 56 // implicitly in the kubernetes.io/ namespace. 57 func IsNativeResource(name v1.ResourceName) bool { 58 return !strings.Contains(string(name), "/") || 59 IsPrefixedNativeResource(name) 60 } 61 62 // IsHugePageResourceName returns true if the resource name has the huge page 63 // resource prefix. 64 func IsHugePageResourceName(name v1.ResourceName) bool { 65 return strings.HasPrefix(string(name), v1.ResourceHugePagesPrefix) 66 } 67 68 // HugePageResourceName returns a ResourceName with the canonical hugepage 69 // prefix prepended for the specified page size. The page size is converted 70 // to its canonical representation. 71 func HugePageResourceName(pageSize resource.Quantity) v1.ResourceName { 72 return v1.ResourceName(fmt.Sprintf("%s%s", v1.ResourceHugePagesPrefix, pageSize.String())) 73 } 74 75 // HugePageSizeFromResourceName returns the page size for the specified huge page 76 // resource name. If the specified input is not a valid huge page resource name 77 // an error is returned. 78 func HugePageSizeFromResourceName(name v1.ResourceName) (resource.Quantity, error) { 79 if !IsHugePageResourceName(name) { 80 return resource.Quantity{}, fmt.Errorf("resource name: %s is an invalid hugepage name", name) 81 } 82 pageSize := strings.TrimPrefix(string(name), v1.ResourceHugePagesPrefix) 83 return resource.ParseQuantity(pageSize) 84 } 85 86 // HugePageUnitSizeFromByteSize returns hugepage size has the format. 87 // `size` must be guaranteed to divisible into the largest units that can be expressed. 88 // <size><unit-prefix>B (1024 = "1KB", 1048576 = "1MB", etc). 89 func HugePageUnitSizeFromByteSize(size int64) (string, error) { 90 // hugePageSizeUnitList is borrowed from opencontainers/runc/libcontainer/cgroups/utils.go 91 var hugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"} 92 idx := 0 93 len := len(hugePageSizeUnitList) - 1 94 for size%1024 == 0 && idx < len { 95 size /= 1024 96 idx++ 97 } 98 if size > 1024 && idx < len { 99 return "", fmt.Errorf("size: %d%s must be guaranteed to divisible into the largest units", size, hugePageSizeUnitList[idx]) 100 } 101 return fmt.Sprintf("%d%s", size, hugePageSizeUnitList[idx]), nil 102 } 103 104 // IsHugePageMedium returns true if the volume medium is in 'HugePages[-size]' format 105 func IsHugePageMedium(medium v1.StorageMedium) bool { 106 if medium == v1.StorageMediumHugePages { 107 return true 108 } 109 return strings.HasPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix)) 110 } 111 112 // HugePageSizeFromMedium returns the page size for the specified huge page medium. 113 // If the specified input is not a valid huge page medium an error is returned. 114 func HugePageSizeFromMedium(medium v1.StorageMedium) (resource.Quantity, error) { 115 if !IsHugePageMedium(medium) { 116 return resource.Quantity{}, fmt.Errorf("medium: %s is not a hugepage medium", medium) 117 } 118 if medium == v1.StorageMediumHugePages { 119 return resource.Quantity{}, fmt.Errorf("medium: %s doesn't have size information", medium) 120 } 121 pageSize := strings.TrimPrefix(string(medium), string(v1.StorageMediumHugePagesPrefix)) 122 return resource.ParseQuantity(pageSize) 123 } 124 125 // IsOvercommitAllowed returns true if the resource is in the default 126 // namespace and is not hugepages. 127 func IsOvercommitAllowed(name v1.ResourceName) bool { 128 return IsNativeResource(name) && 129 !IsHugePageResourceName(name) 130 } 131 132 // IsAttachableVolumeResourceName returns true when the resource name is prefixed in attachable volume 133 func IsAttachableVolumeResourceName(name v1.ResourceName) bool { 134 return strings.HasPrefix(string(name), v1.ResourceAttachableVolumesPrefix) 135 } 136 137 // IsServiceIPSet aims to check if the service's ClusterIP is set or not 138 // the objective is not to perform validation here 139 func IsServiceIPSet(service *v1.Service) bool { 140 return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != "" 141 } 142 143 // LoadBalancerStatusEqual evaluates the given load balancers' ingress IP addresses 144 // and hostnames and returns true if equal or false if otherwise 145 // TODO: make method on LoadBalancerStatus? 146 func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool { 147 return ingressSliceEqual(l.Ingress, r.Ingress) 148 } 149 150 func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool { 151 if len(lhs) != len(rhs) { 152 return false 153 } 154 for i := range lhs { 155 if !ingressEqual(&lhs[i], &rhs[i]) { 156 return false 157 } 158 } 159 return true 160 } 161 162 func ingressEqual(lhs, rhs *v1.LoadBalancerIngress) bool { 163 if lhs.IP != rhs.IP { 164 return false 165 } 166 if lhs.Hostname != rhs.Hostname { 167 return false 168 } 169 return true 170 } 171 172 // GetAccessModesAsString returns a string representation of an array of access modes. 173 // modes, when present, are always in the same order: RWO,ROX,RWX,RWOP. 174 func GetAccessModesAsString(modes []v1.PersistentVolumeAccessMode) string { 175 modes = removeDuplicateAccessModes(modes) 176 modesStr := []string{} 177 if ContainsAccessMode(modes, v1.ReadWriteOnce) { 178 modesStr = append(modesStr, "RWO") 179 } 180 if ContainsAccessMode(modes, v1.ReadOnlyMany) { 181 modesStr = append(modesStr, "ROX") 182 } 183 if ContainsAccessMode(modes, v1.ReadWriteMany) { 184 modesStr = append(modesStr, "RWX") 185 } 186 if ContainsAccessMode(modes, v1.ReadWriteOncePod) { 187 modesStr = append(modesStr, "RWOP") 188 } 189 return strings.Join(modesStr, ",") 190 } 191 192 // GetAccessModesFromString returns an array of AccessModes from a string created by GetAccessModesAsString 193 func GetAccessModesFromString(modes string) []v1.PersistentVolumeAccessMode { 194 strmodes := strings.Split(modes, ",") 195 accessModes := []v1.PersistentVolumeAccessMode{} 196 for _, s := range strmodes { 197 s = strings.Trim(s, " ") 198 switch { 199 case s == "RWO": 200 accessModes = append(accessModes, v1.ReadWriteOnce) 201 case s == "ROX": 202 accessModes = append(accessModes, v1.ReadOnlyMany) 203 case s == "RWX": 204 accessModes = append(accessModes, v1.ReadWriteMany) 205 case s == "RWOP": 206 accessModes = append(accessModes, v1.ReadWriteOncePod) 207 } 208 } 209 return accessModes 210 } 211 212 // removeDuplicateAccessModes returns an array of access modes without any duplicates 213 func removeDuplicateAccessModes(modes []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode { 214 accessModes := []v1.PersistentVolumeAccessMode{} 215 for _, m := range modes { 216 if !ContainsAccessMode(accessModes, m) { 217 accessModes = append(accessModes, m) 218 } 219 } 220 return accessModes 221 } 222 223 func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool { 224 for _, m := range modes { 225 if m == mode { 226 return true 227 } 228 } 229 return false 230 } 231 232 // NodeSelectorRequirementKeysExistInNodeSelectorTerms checks if a NodeSelectorTerm with key is already specified in terms 233 func NodeSelectorRequirementKeysExistInNodeSelectorTerms(reqs []v1.NodeSelectorRequirement, terms []v1.NodeSelectorTerm) bool { 234 for _, req := range reqs { 235 for _, term := range terms { 236 for _, r := range term.MatchExpressions { 237 if r.Key == req.Key { 238 return true 239 } 240 } 241 } 242 } 243 return false 244 } 245 246 // TopologySelectorRequirementsAsSelector converts the []TopologySelectorLabelRequirement api type into a struct 247 // that implements labels.Selector. 248 func TopologySelectorRequirementsAsSelector(tsm []v1.TopologySelectorLabelRequirement) (labels.Selector, error) { 249 if len(tsm) == 0 { 250 return labels.Nothing(), nil 251 } 252 253 selector := labels.NewSelector() 254 for _, expr := range tsm { 255 r, err := labels.NewRequirement(expr.Key, selection.In, expr.Values) 256 if err != nil { 257 return nil, err 258 } 259 selector = selector.Add(*r) 260 } 261 262 return selector, nil 263 } 264 265 // MatchTopologySelectorTerms checks whether given labels match topology selector terms in ORed; 266 // nil or empty term matches no objects; while empty term list matches all objects. 267 func MatchTopologySelectorTerms(topologySelectorTerms []v1.TopologySelectorTerm, lbls labels.Set) bool { 268 if len(topologySelectorTerms) == 0 { 269 // empty term list matches all objects 270 return true 271 } 272 273 for _, req := range topologySelectorTerms { 274 // nil or empty term selects no objects 275 if len(req.MatchLabelExpressions) == 0 { 276 continue 277 } 278 279 labelSelector, err := TopologySelectorRequirementsAsSelector(req.MatchLabelExpressions) 280 if err != nil || !labelSelector.Matches(lbls) { 281 continue 282 } 283 284 return true 285 } 286 287 return false 288 } 289 290 // AddOrUpdateTolerationInPodSpec tries to add a toleration to the toleration list in PodSpec. 291 // Returns true if something was updated, false otherwise. 292 func AddOrUpdateTolerationInPodSpec(spec *v1.PodSpec, toleration *v1.Toleration) bool { 293 podTolerations := spec.Tolerations 294 295 var newTolerations []v1.Toleration 296 updated := false 297 for i := range podTolerations { 298 if toleration.MatchToleration(&podTolerations[i]) { 299 if helper.Semantic.DeepEqual(toleration, podTolerations[i]) { 300 return false 301 } 302 newTolerations = append(newTolerations, *toleration) 303 updated = true 304 continue 305 } 306 307 newTolerations = append(newTolerations, podTolerations[i]) 308 } 309 310 if !updated { 311 newTolerations = append(newTolerations, *toleration) 312 } 313 314 spec.Tolerations = newTolerations 315 return true 316 } 317 318 // GetMatchingTolerations returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise. 319 func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) { 320 if len(taints) == 0 { 321 return true, []v1.Toleration{} 322 } 323 if len(tolerations) == 0 && len(taints) > 0 { 324 return false, []v1.Toleration{} 325 } 326 result := []v1.Toleration{} 327 for i := range taints { 328 tolerated := false 329 for j := range tolerations { 330 if tolerations[j].ToleratesTaint(&taints[i]) { 331 result = append(result, tolerations[j]) 332 tolerated = true 333 break 334 } 335 } 336 if !tolerated { 337 return false, []v1.Toleration{} 338 } 339 } 340 return true, result 341 } 342 343 // ScopedResourceSelectorRequirementsAsSelector converts the ScopedResourceSelectorRequirement api type into a struct that implements 344 // labels.Selector. 345 func ScopedResourceSelectorRequirementsAsSelector(ssr v1.ScopedResourceSelectorRequirement) (labels.Selector, error) { 346 selector := labels.NewSelector() 347 var op selection.Operator 348 switch ssr.Operator { 349 case v1.ScopeSelectorOpIn: 350 op = selection.In 351 case v1.ScopeSelectorOpNotIn: 352 op = selection.NotIn 353 case v1.ScopeSelectorOpExists: 354 op = selection.Exists 355 case v1.ScopeSelectorOpDoesNotExist: 356 op = selection.DoesNotExist 357 default: 358 return nil, fmt.Errorf("%q is not a valid scope selector operator", ssr.Operator) 359 } 360 r, err := labels.NewRequirement(string(ssr.ScopeName), op, ssr.Values) 361 if err != nil { 362 return nil, err 363 } 364 selector = selector.Add(*r) 365 return selector, nil 366 }