...

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

Documentation: k8s.io/kubernetes/pkg/apis/core/v1/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  	"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  }
   367  

View as plain text