k8s.io/kubernetes@v1.29.3/pkg/apis/core/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 "encoding/json" 21 "fmt" 22 "strconv" 23 "strings" 24 25 "k8s.io/apimachinery/pkg/api/resource" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/conversion" 28 "k8s.io/apimachinery/pkg/fields" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/util/sets" 31 "k8s.io/apimachinery/pkg/util/validation" 32 "k8s.io/kubernetes/pkg/apis/core" 33 ) 34 35 // IsHugePageResourceName returns true if the resource name has the huge page 36 // resource prefix. 37 func IsHugePageResourceName(name core.ResourceName) bool { 38 return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) 39 } 40 41 // IsHugePageResourceValueDivisible returns true if the resource value of storage is 42 // integer multiple of page size. 43 func IsHugePageResourceValueDivisible(name core.ResourceName, quantity resource.Quantity) bool { 44 pageSize, err := HugePageSizeFromResourceName(name) 45 if err != nil { 46 return false 47 } 48 49 if pageSize.Sign() <= 0 || pageSize.MilliValue()%int64(1000) != int64(0) { 50 return false 51 } 52 53 return quantity.Value()%pageSize.Value() == 0 54 } 55 56 // IsQuotaHugePageResourceName returns true if the resource name has the quota 57 // related huge page resource prefix. 58 func IsQuotaHugePageResourceName(name core.ResourceName) bool { 59 return strings.HasPrefix(string(name), core.ResourceHugePagesPrefix) || strings.HasPrefix(string(name), core.ResourceRequestsHugePagesPrefix) 60 } 61 62 // HugePageResourceName returns a ResourceName with the canonical hugepage 63 // prefix prepended for the specified page size. The page size is converted 64 // to its canonical representation. 65 func HugePageResourceName(pageSize resource.Quantity) core.ResourceName { 66 return core.ResourceName(fmt.Sprintf("%s%s", core.ResourceHugePagesPrefix, pageSize.String())) 67 } 68 69 // HugePageSizeFromResourceName returns the page size for the specified huge page 70 // resource name. If the specified input is not a valid huge page resource name 71 // an error is returned. 72 func HugePageSizeFromResourceName(name core.ResourceName) (resource.Quantity, error) { 73 if !IsHugePageResourceName(name) { 74 return resource.Quantity{}, fmt.Errorf("resource name: %s is an invalid hugepage name", name) 75 } 76 pageSize := strings.TrimPrefix(string(name), core.ResourceHugePagesPrefix) 77 return resource.ParseQuantity(pageSize) 78 } 79 80 // NonConvertibleFields iterates over the provided map and filters out all but 81 // any keys with the "non-convertible.kubernetes.io" prefix. 82 func NonConvertibleFields(annotations map[string]string) map[string]string { 83 nonConvertibleKeys := map[string]string{} 84 for key, value := range annotations { 85 if strings.HasPrefix(key, core.NonConvertibleAnnotationPrefix) { 86 nonConvertibleKeys[key] = value 87 } 88 } 89 return nonConvertibleKeys 90 } 91 92 // Semantic can do semantic deep equality checks for core objects. 93 // Example: apiequality.Semantic.DeepEqual(aPod, aPodWithNonNilButEmptyMaps) == true 94 var Semantic = conversion.EqualitiesOrDie( 95 func(a, b resource.Quantity) bool { 96 // Ignore formatting, only care that numeric value stayed the same. 97 // TODO: if we decide it's important, it should be safe to start comparing the format. 98 // 99 // Uninitialized quantities are equivalent to 0 quantities. 100 return a.Cmp(b) == 0 101 }, 102 func(a, b metav1.MicroTime) bool { 103 return a.UTC() == b.UTC() 104 }, 105 func(a, b metav1.Time) bool { 106 return a.UTC() == b.UTC() 107 }, 108 func(a, b labels.Selector) bool { 109 return a.String() == b.String() 110 }, 111 func(a, b fields.Selector) bool { 112 return a.String() == b.String() 113 }, 114 ) 115 116 var standardResourceQuotaScopes = sets.New( 117 core.ResourceQuotaScopeTerminating, 118 core.ResourceQuotaScopeNotTerminating, 119 core.ResourceQuotaScopeBestEffort, 120 core.ResourceQuotaScopeNotBestEffort, 121 core.ResourceQuotaScopePriorityClass, 122 ) 123 124 // IsStandardResourceQuotaScope returns true if the scope is a standard value 125 func IsStandardResourceQuotaScope(scope core.ResourceQuotaScope) bool { 126 return standardResourceQuotaScopes.Has(scope) || scope == core.ResourceQuotaScopeCrossNamespacePodAffinity 127 } 128 129 var podObjectCountQuotaResources = sets.New( 130 core.ResourcePods, 131 ) 132 133 var podComputeQuotaResources = sets.New( 134 core.ResourceCPU, 135 core.ResourceMemory, 136 core.ResourceLimitsCPU, 137 core.ResourceLimitsMemory, 138 core.ResourceRequestsCPU, 139 core.ResourceRequestsMemory, 140 ) 141 142 // IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope 143 func IsResourceQuotaScopeValidForResource(scope core.ResourceQuotaScope, resource core.ResourceName) bool { 144 switch scope { 145 case core.ResourceQuotaScopeTerminating, core.ResourceQuotaScopeNotTerminating, core.ResourceQuotaScopeNotBestEffort, 146 core.ResourceQuotaScopePriorityClass, core.ResourceQuotaScopeCrossNamespacePodAffinity: 147 return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource) 148 case core.ResourceQuotaScopeBestEffort: 149 return podObjectCountQuotaResources.Has(resource) 150 default: 151 return true 152 } 153 } 154 155 var standardContainerResources = sets.New( 156 core.ResourceCPU, 157 core.ResourceMemory, 158 core.ResourceEphemeralStorage, 159 ) 160 161 // IsStandardContainerResourceName returns true if the container can make a resource request 162 // for the specified resource 163 func IsStandardContainerResourceName(name core.ResourceName) bool { 164 return standardContainerResources.Has(name) || IsHugePageResourceName(name) 165 } 166 167 // IsExtendedResourceName returns true if: 168 // 1. the resource name is not in the default namespace; 169 // 2. resource name does not have "requests." prefix, 170 // to avoid confusion with the convention in quota 171 // 3. it satisfies the rules in IsQualifiedName() after converted into quota resource name 172 func IsExtendedResourceName(name core.ResourceName) bool { 173 if IsNativeResource(name) || strings.HasPrefix(string(name), core.DefaultResourceRequestsPrefix) { 174 return false 175 } 176 // Ensure it satisfies the rules in IsQualifiedName() after converted into quota resource name 177 nameForQuota := fmt.Sprintf("%s%s", core.DefaultResourceRequestsPrefix, string(name)) 178 if errs := validation.IsQualifiedName(nameForQuota); len(errs) != 0 { 179 return false 180 } 181 return true 182 } 183 184 // IsNativeResource returns true if the resource name is in the 185 // *kubernetes.io/ namespace. Partially-qualified (unprefixed) names are 186 // implicitly in the kubernetes.io/ namespace. 187 func IsNativeResource(name core.ResourceName) bool { 188 return !strings.Contains(string(name), "/") || 189 strings.Contains(string(name), core.ResourceDefaultNamespacePrefix) 190 } 191 192 // IsOvercommitAllowed returns true if the resource is in the default 193 // namespace and is not hugepages. 194 func IsOvercommitAllowed(name core.ResourceName) bool { 195 return IsNativeResource(name) && 196 !IsHugePageResourceName(name) 197 } 198 199 var standardLimitRangeTypes = sets.New( 200 core.LimitTypePod, 201 core.LimitTypeContainer, 202 core.LimitTypePersistentVolumeClaim, 203 ) 204 205 // IsStandardLimitRangeType returns true if the type is Pod or Container 206 func IsStandardLimitRangeType(value core.LimitType) bool { 207 return standardLimitRangeTypes.Has(value) 208 } 209 210 var standardQuotaResources = sets.New( 211 core.ResourceCPU, 212 core.ResourceMemory, 213 core.ResourceEphemeralStorage, 214 core.ResourceRequestsCPU, 215 core.ResourceRequestsMemory, 216 core.ResourceRequestsStorage, 217 core.ResourceRequestsEphemeralStorage, 218 core.ResourceLimitsCPU, 219 core.ResourceLimitsMemory, 220 core.ResourceLimitsEphemeralStorage, 221 core.ResourcePods, 222 core.ResourceQuotas, 223 core.ResourceServices, 224 core.ResourceReplicationControllers, 225 core.ResourceSecrets, 226 core.ResourcePersistentVolumeClaims, 227 core.ResourceConfigMaps, 228 core.ResourceServicesNodePorts, 229 core.ResourceServicesLoadBalancers, 230 ) 231 232 // IsStandardQuotaResourceName returns true if the resource is known to 233 // the quota tracking system 234 func IsStandardQuotaResourceName(name core.ResourceName) bool { 235 return standardQuotaResources.Has(name) || IsQuotaHugePageResourceName(name) 236 } 237 238 var standardResources = sets.New( 239 core.ResourceCPU, 240 core.ResourceMemory, 241 core.ResourceEphemeralStorage, 242 core.ResourceRequestsCPU, 243 core.ResourceRequestsMemory, 244 core.ResourceRequestsEphemeralStorage, 245 core.ResourceLimitsCPU, 246 core.ResourceLimitsMemory, 247 core.ResourceLimitsEphemeralStorage, 248 core.ResourcePods, 249 core.ResourceQuotas, 250 core.ResourceServices, 251 core.ResourceReplicationControllers, 252 core.ResourceSecrets, 253 core.ResourceConfigMaps, 254 core.ResourcePersistentVolumeClaims, 255 core.ResourceStorage, 256 core.ResourceRequestsStorage, 257 core.ResourceServicesNodePorts, 258 core.ResourceServicesLoadBalancers, 259 ) 260 261 // IsStandardResourceName returns true if the resource is known to the system 262 func IsStandardResourceName(name core.ResourceName) bool { 263 return standardResources.Has(name) || IsQuotaHugePageResourceName(name) 264 } 265 266 var integerResources = sets.New( 267 core.ResourcePods, 268 core.ResourceQuotas, 269 core.ResourceServices, 270 core.ResourceReplicationControllers, 271 core.ResourceSecrets, 272 core.ResourceConfigMaps, 273 core.ResourcePersistentVolumeClaims, 274 core.ResourceServicesNodePorts, 275 core.ResourceServicesLoadBalancers, 276 ) 277 278 // IsIntegerResourceName returns true if the resource is measured in integer values 279 func IsIntegerResourceName(name core.ResourceName) bool { 280 return integerResources.Has(name) || IsExtendedResourceName(name) 281 } 282 283 // IsServiceIPSet aims to check if the service's ClusterIP is set or not 284 // the objective is not to perform validation here 285 func IsServiceIPSet(service *core.Service) bool { 286 // This function assumes that the service is semantically validated 287 // it does not test if the IP is valid, just makes sure that it is set. 288 return len(service.Spec.ClusterIP) > 0 && 289 service.Spec.ClusterIP != core.ClusterIPNone 290 } 291 292 var standardFinalizers = sets.New( 293 string(core.FinalizerKubernetes), 294 metav1.FinalizerOrphanDependents, 295 metav1.FinalizerDeleteDependents, 296 ) 297 298 // IsStandardFinalizerName checks if the input string is a standard finalizer name 299 func IsStandardFinalizerName(str string) bool { 300 return standardFinalizers.Has(str) 301 } 302 303 // GetAccessModesAsString returns a string representation of an array of access modes. 304 // modes, when present, are always in the same order: RWO,ROX,RWX,RWOP. 305 func GetAccessModesAsString(modes []core.PersistentVolumeAccessMode) string { 306 modes = removeDuplicateAccessModes(modes) 307 modesStr := []string{} 308 if ContainsAccessMode(modes, core.ReadWriteOnce) { 309 modesStr = append(modesStr, "RWO") 310 } 311 if ContainsAccessMode(modes, core.ReadOnlyMany) { 312 modesStr = append(modesStr, "ROX") 313 } 314 if ContainsAccessMode(modes, core.ReadWriteMany) { 315 modesStr = append(modesStr, "RWX") 316 } 317 if ContainsAccessMode(modes, core.ReadWriteOncePod) { 318 modesStr = append(modesStr, "RWOP") 319 } 320 return strings.Join(modesStr, ",") 321 } 322 323 // GetAccessModesFromString returns an array of AccessModes from a string created by GetAccessModesAsString 324 func GetAccessModesFromString(modes string) []core.PersistentVolumeAccessMode { 325 strmodes := strings.Split(modes, ",") 326 accessModes := []core.PersistentVolumeAccessMode{} 327 for _, s := range strmodes { 328 s = strings.Trim(s, " ") 329 switch { 330 case s == "RWO": 331 accessModes = append(accessModes, core.ReadWriteOnce) 332 case s == "ROX": 333 accessModes = append(accessModes, core.ReadOnlyMany) 334 case s == "RWX": 335 accessModes = append(accessModes, core.ReadWriteMany) 336 case s == "RWOP": 337 accessModes = append(accessModes, core.ReadWriteOncePod) 338 } 339 } 340 return accessModes 341 } 342 343 // removeDuplicateAccessModes returns an array of access modes without any duplicates 344 func removeDuplicateAccessModes(modes []core.PersistentVolumeAccessMode) []core.PersistentVolumeAccessMode { 345 accessModes := []core.PersistentVolumeAccessMode{} 346 for _, m := range modes { 347 if !ContainsAccessMode(accessModes, m) { 348 accessModes = append(accessModes, m) 349 } 350 } 351 return accessModes 352 } 353 354 func ContainsAccessMode(modes []core.PersistentVolumeAccessMode, mode core.PersistentVolumeAccessMode) bool { 355 for _, m := range modes { 356 if m == mode { 357 return true 358 } 359 } 360 return false 361 } 362 363 func ClaimContainsAllocatedResources(pvc *core.PersistentVolumeClaim) bool { 364 if pvc == nil { 365 return false 366 } 367 368 if pvc.Status.AllocatedResources != nil { 369 return true 370 } 371 return false 372 } 373 374 func ClaimContainsAllocatedResourceStatus(pvc *core.PersistentVolumeClaim) bool { 375 if pvc == nil { 376 return false 377 } 378 379 if pvc.Status.AllocatedResourceStatuses != nil { 380 return true 381 } 382 return false 383 } 384 385 // GetTolerationsFromPodAnnotations gets the json serialized tolerations data from Pod.Annotations 386 // and converts it to the []Toleration type in core. 387 func GetTolerationsFromPodAnnotations(annotations map[string]string) ([]core.Toleration, error) { 388 var tolerations []core.Toleration 389 if len(annotations) > 0 && annotations[core.TolerationsAnnotationKey] != "" { 390 err := json.Unmarshal([]byte(annotations[core.TolerationsAnnotationKey]), &tolerations) 391 if err != nil { 392 return tolerations, err 393 } 394 } 395 return tolerations, nil 396 } 397 398 // AddOrUpdateTolerationInPod tries to add a toleration to the pod's toleration list. 399 // Returns true if something was updated, false otherwise. 400 func AddOrUpdateTolerationInPod(pod *core.Pod, toleration *core.Toleration) bool { 401 podTolerations := pod.Spec.Tolerations 402 403 var newTolerations []core.Toleration 404 updated := false 405 for i := range podTolerations { 406 if toleration.MatchToleration(&podTolerations[i]) { 407 if Semantic.DeepEqual(toleration, podTolerations[i]) { 408 return false 409 } 410 newTolerations = append(newTolerations, *toleration) 411 updated = true 412 continue 413 } 414 415 newTolerations = append(newTolerations, podTolerations[i]) 416 } 417 418 if !updated { 419 newTolerations = append(newTolerations, *toleration) 420 } 421 422 pod.Spec.Tolerations = newTolerations 423 return true 424 } 425 426 // GetTaintsFromNodeAnnotations gets the json serialized taints data from Pod.Annotations 427 // and converts it to the []Taint type in core. 428 func GetTaintsFromNodeAnnotations(annotations map[string]string) ([]core.Taint, error) { 429 var taints []core.Taint 430 if len(annotations) > 0 && annotations[core.TaintsAnnotationKey] != "" { 431 err := json.Unmarshal([]byte(annotations[core.TaintsAnnotationKey]), &taints) 432 if err != nil { 433 return []core.Taint{}, err 434 } 435 } 436 return taints, nil 437 } 438 439 // GetPersistentVolumeClass returns StorageClassName. 440 func GetPersistentVolumeClass(volume *core.PersistentVolume) string { 441 // Use beta annotation first 442 if class, found := volume.Annotations[core.BetaStorageClassAnnotation]; found { 443 return class 444 } 445 446 return volume.Spec.StorageClassName 447 } 448 449 // GetPersistentVolumeClaimClass returns StorageClassName. If no storage class was 450 // requested, it returns "". 451 func GetPersistentVolumeClaimClass(claim *core.PersistentVolumeClaim) string { 452 // Use beta annotation first 453 if class, found := claim.Annotations[core.BetaStorageClassAnnotation]; found { 454 return class 455 } 456 457 if claim.Spec.StorageClassName != nil { 458 return *claim.Spec.StorageClassName 459 } 460 461 return "" 462 } 463 464 // PersistentVolumeClaimHasClass returns true if given claim has set StorageClassName field. 465 func PersistentVolumeClaimHasClass(claim *core.PersistentVolumeClaim) bool { 466 // Use beta annotation first 467 if _, found := claim.Annotations[core.BetaStorageClassAnnotation]; found { 468 return true 469 } 470 471 if claim.Spec.StorageClassName != nil { 472 return true 473 } 474 475 return false 476 } 477 478 // GetDeletionCostFromPodAnnotations returns the integer value of pod-deletion-cost. Returns 0 479 // if not set or the value is invalid. 480 func GetDeletionCostFromPodAnnotations(annotations map[string]string) (int32, error) { 481 if value, exist := annotations[core.PodDeletionCost]; exist { 482 // values that start with plus sign (e.g, "+10") or leading zeros (e.g., "008") are not valid. 483 if !validFirstDigit(value) { 484 return 0, fmt.Errorf("invalid value %q", value) 485 } 486 487 i, err := strconv.ParseInt(value, 10, 32) 488 if err != nil { 489 // make sure we default to 0 on error. 490 return 0, err 491 } 492 return int32(i), nil 493 } 494 return 0, nil 495 } 496 497 func validFirstDigit(str string) bool { 498 if len(str) == 0 { 499 return false 500 } 501 return str[0] == '-' || (str[0] == '0' && str == "0") || (str[0] >= '1' && str[0] <= '9') 502 }