k8s.io/apiserver@v0.31.1/pkg/quota/v1/resources.go (about)

     1  /*
     2  Copyright 2016 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 v1
    18  
    19  import (
    20  	"sort"
    21  	"strings"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  )
    28  
    29  // Equals returns true if the two lists are equivalent
    30  func Equals(a corev1.ResourceList, b corev1.ResourceList) bool {
    31  	if len(a) != len(b) {
    32  		return false
    33  	}
    34  
    35  	for key, value1 := range a {
    36  		value2, found := b[key]
    37  		if !found {
    38  			return false
    39  		}
    40  		if value1.Cmp(value2) != 0 {
    41  			return false
    42  		}
    43  	}
    44  
    45  	return true
    46  }
    47  
    48  // LessThanOrEqual returns true if a < b for each key in b
    49  // If false, it returns the keys in a that exceeded b
    50  func LessThanOrEqual(a corev1.ResourceList, b corev1.ResourceList) (bool, []corev1.ResourceName) {
    51  	result := true
    52  	resourceNames := []corev1.ResourceName{}
    53  	for key, value := range b {
    54  		if other, found := a[key]; found {
    55  			if other.Cmp(value) > 0 {
    56  				result = false
    57  				resourceNames = append(resourceNames, key)
    58  			}
    59  		}
    60  	}
    61  	return result, resourceNames
    62  }
    63  
    64  // Max returns the result of Max(a, b) for each named resource
    65  func Max(a corev1.ResourceList, b corev1.ResourceList) corev1.ResourceList {
    66  	result := corev1.ResourceList{}
    67  	for key, value := range a {
    68  		if other, found := b[key]; found {
    69  			if value.Cmp(other) <= 0 {
    70  				result[key] = other.DeepCopy()
    71  				continue
    72  			}
    73  		}
    74  		result[key] = value.DeepCopy()
    75  	}
    76  	for key, value := range b {
    77  		if _, found := result[key]; !found {
    78  			result[key] = value.DeepCopy()
    79  		}
    80  	}
    81  	return result
    82  }
    83  
    84  // Add returns the result of a + b for each named resource
    85  func Add(a corev1.ResourceList, b corev1.ResourceList) corev1.ResourceList {
    86  	result := corev1.ResourceList{}
    87  	for key, value := range a {
    88  		quantity := value.DeepCopy()
    89  		if other, found := b[key]; found {
    90  			quantity.Add(other)
    91  		}
    92  		result[key] = quantity
    93  	}
    94  	for key, value := range b {
    95  		if _, found := result[key]; !found {
    96  			result[key] = value.DeepCopy()
    97  		}
    98  	}
    99  	return result
   100  }
   101  
   102  // SubtractWithNonNegativeResult - subtracts and returns result of a - b but
   103  // makes sure we don't return negative values to prevent negative resource usage.
   104  func SubtractWithNonNegativeResult(a corev1.ResourceList, b corev1.ResourceList) corev1.ResourceList {
   105  	zero := resource.MustParse("0")
   106  
   107  	result := corev1.ResourceList{}
   108  	for key, value := range a {
   109  		quantity := value.DeepCopy()
   110  		if other, found := b[key]; found {
   111  			quantity.Sub(other)
   112  		}
   113  		if quantity.Cmp(zero) > 0 {
   114  			result[key] = quantity
   115  		} else {
   116  			result[key] = zero
   117  		}
   118  	}
   119  
   120  	for key := range b {
   121  		if _, found := result[key]; !found {
   122  			result[key] = zero
   123  		}
   124  	}
   125  	return result
   126  }
   127  
   128  // Subtract returns the result of a - b for each named resource
   129  func Subtract(a corev1.ResourceList, b corev1.ResourceList) corev1.ResourceList {
   130  	result := corev1.ResourceList{}
   131  	for key, value := range a {
   132  		quantity := value.DeepCopy()
   133  		if other, found := b[key]; found {
   134  			quantity.Sub(other)
   135  		}
   136  		result[key] = quantity
   137  	}
   138  	for key, value := range b {
   139  		if _, found := result[key]; !found {
   140  			quantity := value.DeepCopy()
   141  			quantity.Neg()
   142  			result[key] = quantity
   143  		}
   144  	}
   145  	return result
   146  }
   147  
   148  // Mask returns a new resource list that only has the values with the specified names
   149  func Mask(resources corev1.ResourceList, names []corev1.ResourceName) corev1.ResourceList {
   150  	nameSet := ToSet(names)
   151  	result := corev1.ResourceList{}
   152  	for key, value := range resources {
   153  		if nameSet.Has(string(key)) {
   154  			result[key] = value.DeepCopy()
   155  		}
   156  	}
   157  	return result
   158  }
   159  
   160  // ResourceNames returns a list of all resource names in the ResourceList
   161  func ResourceNames(resources corev1.ResourceList) []corev1.ResourceName {
   162  	result := []corev1.ResourceName{}
   163  	for resourceName := range resources {
   164  		result = append(result, resourceName)
   165  	}
   166  	return result
   167  }
   168  
   169  // Contains returns true if the specified item is in the list of items
   170  func Contains(items []corev1.ResourceName, item corev1.ResourceName) bool {
   171  	for _, i := range items {
   172  		if i == item {
   173  			return true
   174  		}
   175  	}
   176  	return false
   177  }
   178  
   179  // ContainsPrefix returns true if the specified item has a prefix that contained in given prefix Set
   180  func ContainsPrefix(prefixSet []string, item corev1.ResourceName) bool {
   181  	for _, prefix := range prefixSet {
   182  		if strings.HasPrefix(string(item), prefix) {
   183  			return true
   184  		}
   185  	}
   186  	return false
   187  }
   188  
   189  // Intersection returns the intersection of both list of resources, deduped and sorted
   190  func Intersection(a []corev1.ResourceName, b []corev1.ResourceName) []corev1.ResourceName {
   191  	result := make([]corev1.ResourceName, 0, len(a))
   192  	for _, item := range a {
   193  		if Contains(result, item) {
   194  			continue
   195  		}
   196  		if !Contains(b, item) {
   197  			continue
   198  		}
   199  		result = append(result, item)
   200  	}
   201  	sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
   202  	return result
   203  }
   204  
   205  // Difference returns the list of resources resulting from a-b, deduped and sorted
   206  func Difference(a []corev1.ResourceName, b []corev1.ResourceName) []corev1.ResourceName {
   207  	result := make([]corev1.ResourceName, 0, len(a))
   208  	for _, item := range a {
   209  		if Contains(b, item) || Contains(result, item) {
   210  			continue
   211  		}
   212  		result = append(result, item)
   213  	}
   214  	sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
   215  	return result
   216  }
   217  
   218  // IsZero returns true if each key maps to the quantity value 0
   219  func IsZero(a corev1.ResourceList) bool {
   220  	zero := resource.MustParse("0")
   221  	for _, v := range a {
   222  		if v.Cmp(zero) != 0 {
   223  			return false
   224  		}
   225  	}
   226  	return true
   227  }
   228  
   229  // RemoveZeros returns a new resource list that only has no zero values
   230  func RemoveZeros(a corev1.ResourceList) corev1.ResourceList {
   231  	result := corev1.ResourceList{}
   232  	for key, value := range a {
   233  		if !value.IsZero() {
   234  			result[key] = value
   235  		}
   236  	}
   237  	return result
   238  }
   239  
   240  // IsNegative returns the set of resource names that have a negative value.
   241  func IsNegative(a corev1.ResourceList) []corev1.ResourceName {
   242  	results := []corev1.ResourceName{}
   243  	zero := resource.MustParse("0")
   244  	for k, v := range a {
   245  		if v.Cmp(zero) < 0 {
   246  			results = append(results, k)
   247  		}
   248  	}
   249  	return results
   250  }
   251  
   252  // ToSet takes a list of resource names and converts to a string set
   253  func ToSet(resourceNames []corev1.ResourceName) sets.String {
   254  	result := sets.NewString()
   255  	for _, resourceName := range resourceNames {
   256  		result.Insert(string(resourceName))
   257  	}
   258  	return result
   259  }
   260  
   261  // CalculateUsage calculates and returns the requested ResourceList usage.
   262  // If an error is returned, usage only contains the resources which encountered no calculation errors.
   263  func CalculateUsage(namespaceName string, scopes []corev1.ResourceQuotaScope, hardLimits corev1.ResourceList, registry Registry, scopeSelector *corev1.ScopeSelector) (corev1.ResourceList, error) {
   264  	// find the intersection between the hard resources on the quota
   265  	// and the resources this controller can track to know what we can
   266  	// look to measure updated usage stats for
   267  	hardResources := ResourceNames(hardLimits)
   268  	potentialResources := []corev1.ResourceName{}
   269  	evaluators := registry.List()
   270  	for _, evaluator := range evaluators {
   271  		potentialResources = append(potentialResources, evaluator.MatchingResources(hardResources)...)
   272  	}
   273  	// NOTE: the intersection just removes duplicates since the evaluator match intersects with hard
   274  	matchedResources := Intersection(hardResources, potentialResources)
   275  
   276  	errors := []error{}
   277  
   278  	// sum the observed usage from each evaluator
   279  	newUsage := corev1.ResourceList{}
   280  	for _, evaluator := range evaluators {
   281  		// only trigger the evaluator if it matches a resource in the quota, otherwise, skip calculating anything
   282  		intersection := evaluator.MatchingResources(matchedResources)
   283  		if len(intersection) == 0 {
   284  			continue
   285  		}
   286  
   287  		usageStatsOptions := UsageStatsOptions{Namespace: namespaceName, Scopes: scopes, Resources: intersection, ScopeSelector: scopeSelector}
   288  		stats, err := evaluator.UsageStats(usageStatsOptions)
   289  		if err != nil {
   290  			// remember the error
   291  			errors = append(errors, err)
   292  			// exclude resources which encountered calculation errors
   293  			matchedResources = Difference(matchedResources, intersection)
   294  			continue
   295  		}
   296  		newUsage = Add(newUsage, stats.Used)
   297  	}
   298  
   299  	// mask the observed usage to only the set of resources tracked by this quota
   300  	// merge our observed usage with the quota usage status
   301  	// if the new usage is different than the last usage, we will need to do an update
   302  	newUsage = Mask(newUsage, matchedResources)
   303  	return newUsage, utilerrors.NewAggregate(errors)
   304  }