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 }