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  }