k8s.io/kubernetes@v1.29.3/pkg/quota/v1/evaluator/core/persistent_volume_claims.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 core 18 19 import ( 20 "fmt" 21 "strings" 22 23 corev1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/resource" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apiserver/pkg/admission" 28 quota "k8s.io/apiserver/pkg/quota/v1" 29 "k8s.io/apiserver/pkg/quota/v1/generic" 30 utilfeature "k8s.io/apiserver/pkg/util/feature" 31 storagehelpers "k8s.io/component-helpers/storage/volume" 32 api "k8s.io/kubernetes/pkg/apis/core" 33 k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1" 34 k8sfeatures "k8s.io/kubernetes/pkg/features" 35 ) 36 37 // the name used for object count quota 38 var pvcObjectCountName = generic.ObjectCountQuotaResourceNameFor(corev1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource()) 39 40 // pvcResources are the set of static resources managed by quota associated with pvcs. 41 // for each resource in this list, it may be refined dynamically based on storage class. 42 var pvcResources = []corev1.ResourceName{ 43 corev1.ResourcePersistentVolumeClaims, 44 corev1.ResourceRequestsStorage, 45 } 46 47 // storageClassSuffix is the suffix to the qualified portion of storage class resource name. 48 // For example, if you want to quota storage by storage class, you would have a declaration 49 // that follows <storage-class>.storageclass.storage.k8s.io/<resource>. 50 // For example: 51 // * gold.storageclass.storage.k8s.io/: 500Gi 52 // * bronze.storageclass.storage.k8s.io/requests.storage: 500Gi 53 const storageClassSuffix string = ".storageclass.storage.k8s.io/" 54 55 /* TODO: prune? 56 // ResourceByStorageClass returns a quota resource name by storage class. 57 func ResourceByStorageClass(storageClass string, resourceName corev1.ResourceName) corev1.ResourceName { 58 return corev1.ResourceName(string(storageClass + storageClassSuffix + string(resourceName))) 59 } 60 */ 61 62 // V1ResourceByStorageClass returns a quota resource name by storage class. 63 func V1ResourceByStorageClass(storageClass string, resourceName corev1.ResourceName) corev1.ResourceName { 64 return corev1.ResourceName(string(storageClass + storageClassSuffix + string(resourceName))) 65 } 66 67 // NewPersistentVolumeClaimEvaluator returns an evaluator that can evaluate persistent volume claims 68 func NewPersistentVolumeClaimEvaluator(f quota.ListerForResourceFunc) quota.Evaluator { 69 listFuncByNamespace := generic.ListResourceUsingListerFunc(f, corev1.SchemeGroupVersion.WithResource("persistentvolumeclaims")) 70 pvcEvaluator := &pvcEvaluator{listFuncByNamespace: listFuncByNamespace} 71 return pvcEvaluator 72 } 73 74 // pvcEvaluator knows how to evaluate quota usage for persistent volume claims 75 type pvcEvaluator struct { 76 // listFuncByNamespace knows how to list pvc claims 77 listFuncByNamespace generic.ListFuncByNamespace 78 } 79 80 // Constraints verifies that all required resources are present on the item. 81 func (p *pvcEvaluator) Constraints(required []corev1.ResourceName, item runtime.Object) error { 82 // no-op for persistent volume claims 83 return nil 84 } 85 86 // GroupResource that this evaluator tracks 87 func (p *pvcEvaluator) GroupResource() schema.GroupResource { 88 return corev1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource() 89 } 90 91 // Handles returns true if the evaluator should handle the specified operation. 92 func (p *pvcEvaluator) Handles(a admission.Attributes) bool { 93 op := a.GetOperation() 94 if op == admission.Create { 95 return true 96 } 97 if op == admission.Update { 98 return true 99 } 100 return false 101 } 102 103 // Matches returns true if the evaluator matches the specified quota with the provided input item 104 func (p *pvcEvaluator) Matches(resourceQuota *corev1.ResourceQuota, item runtime.Object) (bool, error) { 105 return generic.Matches(resourceQuota, item, p.MatchingResources, generic.MatchesNoScopeFunc) 106 } 107 108 // MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches. 109 func (p *pvcEvaluator) MatchingScopes(item runtime.Object, scopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) { 110 return []corev1.ScopedResourceSelectorRequirement{}, nil 111 } 112 113 // UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes. 114 // It returns the scopes which are in limited scopes but don't have a corresponding covering quota scope 115 func (p *pvcEvaluator) UncoveredQuotaScopes(limitedScopes []corev1.ScopedResourceSelectorRequirement, matchedQuotaScopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) { 116 return []corev1.ScopedResourceSelectorRequirement{}, nil 117 } 118 119 // MatchingResources takes the input specified list of resources and returns the set of resources it matches. 120 func (p *pvcEvaluator) MatchingResources(items []corev1.ResourceName) []corev1.ResourceName { 121 result := []corev1.ResourceName{} 122 for _, item := range items { 123 // match object count quota fields 124 if quota.Contains([]corev1.ResourceName{pvcObjectCountName}, item) { 125 result = append(result, item) 126 continue 127 } 128 // match pvc resources 129 if quota.Contains(pvcResources, item) { 130 result = append(result, item) 131 continue 132 } 133 // match pvc resources scoped by storage class (<storage-class-name>.storageclass.storage.k8s.io/<resource>) 134 for _, resource := range pvcResources { 135 byStorageClass := storageClassSuffix + string(resource) 136 if strings.HasSuffix(string(item), byStorageClass) { 137 result = append(result, item) 138 break 139 } 140 } 141 } 142 return result 143 } 144 145 // Usage knows how to measure usage associated with item. 146 func (p *pvcEvaluator) Usage(item runtime.Object) (corev1.ResourceList, error) { 147 result := corev1.ResourceList{} 148 pvc, err := toExternalPersistentVolumeClaimOrError(item) 149 if err != nil { 150 return result, err 151 } 152 153 // charge for claim 154 result[corev1.ResourcePersistentVolumeClaims] = *(resource.NewQuantity(1, resource.DecimalSI)) 155 result[pvcObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI)) 156 storageClassRef := storagehelpers.GetPersistentVolumeClaimClass(pvc) 157 if len(storageClassRef) > 0 { 158 storageClassClaim := corev1.ResourceName(storageClassRef + storageClassSuffix + string(corev1.ResourcePersistentVolumeClaims)) 159 result[storageClassClaim] = *(resource.NewQuantity(1, resource.DecimalSI)) 160 } 161 162 requestedStorage := p.getStorageUsage(pvc) 163 if requestedStorage != nil { 164 result[corev1.ResourceRequestsStorage] = *requestedStorage 165 // charge usage to the storage class (if present) 166 if len(storageClassRef) > 0 { 167 storageClassStorage := corev1.ResourceName(storageClassRef + storageClassSuffix + string(corev1.ResourceRequestsStorage)) 168 result[storageClassStorage] = *requestedStorage 169 } 170 } 171 172 return result, nil 173 } 174 175 func (p *pvcEvaluator) getStorageUsage(pvc *corev1.PersistentVolumeClaim) *resource.Quantity { 176 var result *resource.Quantity 177 roundUpFunc := func(i *resource.Quantity) *resource.Quantity { 178 roundedRequest := i.DeepCopy() 179 if !roundedRequest.RoundUp(0) { 180 // Ensure storage requests are counted as whole byte values, to pass resourcequota validation. 181 // See https://issue.k8s.io/94313 182 return &roundedRequest 183 } 184 return i 185 } 186 187 if userRequest, ok := pvc.Spec.Resources.Requests[corev1.ResourceStorage]; ok { 188 result = roundUpFunc(&userRequest) 189 } 190 191 if utilfeature.DefaultFeatureGate.Enabled(k8sfeatures.RecoverVolumeExpansionFailure) && result != nil { 192 if len(pvc.Status.AllocatedResources) == 0 { 193 return result 194 } 195 196 // if AllocatedResources is set and is greater than user request, we should use it. 197 if allocatedRequest, ok := pvc.Status.AllocatedResources[corev1.ResourceStorage]; ok { 198 if allocatedRequest.Cmp(*result) > 0 { 199 result = roundUpFunc(&allocatedRequest) 200 } 201 } 202 } 203 return result 204 } 205 206 // UsageStats calculates aggregate usage for the object. 207 func (p *pvcEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) { 208 return generic.CalculateUsageStats(options, p.listFuncByNamespace, generic.MatchesNoScopeFunc, p.Usage) 209 } 210 211 // ensure we implement required interface 212 var _ quota.Evaluator = &pvcEvaluator{} 213 214 func toExternalPersistentVolumeClaimOrError(obj runtime.Object) (*corev1.PersistentVolumeClaim, error) { 215 pvc := &corev1.PersistentVolumeClaim{} 216 switch t := obj.(type) { 217 case *corev1.PersistentVolumeClaim: 218 pvc = t 219 case *api.PersistentVolumeClaim: 220 if err := k8s_api_v1.Convert_core_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(t, pvc, nil); err != nil { 221 return nil, err 222 } 223 default: 224 return nil, fmt.Errorf("expect *api.PersistentVolumeClaim or *v1.PersistentVolumeClaim, got %v", t) 225 } 226 return pvc, nil 227 } 228 229 // RequiresQuotaReplenish enables quota monitoring for PVCs. 230 func RequiresQuotaReplenish(pvc, oldPVC *corev1.PersistentVolumeClaim) bool { 231 if utilfeature.DefaultFeatureGate.Enabled(k8sfeatures.RecoverVolumeExpansionFailure) { 232 if oldPVC.Status.AllocatedResources.Storage() != pvc.Status.AllocatedResources.Storage() { 233 return true 234 } 235 } 236 return false 237 }