...

Source file src/k8s.io/kubernetes/pkg/apis/core/helper/helpers.go

Documentation: k8s.io/kubernetes/pkg/apis/core/helper

     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  }
   503  

View as plain text