k8s.io/apiserver@v0.31.1/pkg/admission/plugin/resourcequota/controller.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 resourcequota
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"k8s.io/klog/v2"
    27  
    28  	corev1 "k8s.io/api/core/v1"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/apiserver/pkg/admission"
    37  	resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota"
    38  	quota "k8s.io/apiserver/pkg/quota/v1"
    39  	"k8s.io/apiserver/pkg/quota/v1/generic"
    40  	"k8s.io/client-go/util/workqueue"
    41  )
    42  
    43  // Evaluator is used to see if quota constraints are satisfied.
    44  type Evaluator interface {
    45  	// Evaluate takes an operation and checks to see if quota constraints are satisfied.  It returns an error if they are not.
    46  	// The default implementation processes related operations in chunks when possible.
    47  	Evaluate(a admission.Attributes) error
    48  }
    49  
    50  type quotaEvaluator struct {
    51  	quotaAccessor QuotaAccessor
    52  	// lockAcquisitionFunc acquires any required locks and returns a cleanup method to defer
    53  	lockAcquisitionFunc func([]corev1.ResourceQuota) func()
    54  
    55  	ignoredResources map[schema.GroupResource]struct{}
    56  
    57  	// registry that knows how to measure usage for objects
    58  	registry quota.Registry
    59  
    60  	// TODO these are used together to bucket items by namespace and then batch them up for processing.
    61  	// The technique is valuable for rollup activities to avoid fanout and reduce resource contention.
    62  	// We could move this into a library if another component needed it.
    63  	// queue is indexed by namespace, so that we bundle up on a per-namespace basis
    64  	queue      *workqueue.Typed[string]
    65  	workLock   sync.Mutex
    66  	work       map[string][]*admissionWaiter
    67  	dirtyWork  map[string][]*admissionWaiter
    68  	inProgress sets.String
    69  
    70  	// controls the run method so that we can cleanly conform to the Evaluator interface
    71  	workers int
    72  	stopCh  <-chan struct{}
    73  	init    sync.Once
    74  
    75  	// lets us know what resources are limited by default
    76  	config *resourcequotaapi.Configuration
    77  }
    78  
    79  type admissionWaiter struct {
    80  	attributes admission.Attributes
    81  	finished   chan struct{}
    82  	result     error
    83  }
    84  
    85  type defaultDeny struct{}
    86  
    87  func (defaultDeny) Error() string {
    88  	return "DEFAULT DENY"
    89  }
    90  
    91  // IsDefaultDeny returns true if the error is defaultDeny
    92  func IsDefaultDeny(err error) bool {
    93  	if err == nil {
    94  		return false
    95  	}
    96  
    97  	_, ok := err.(defaultDeny)
    98  	return ok
    99  }
   100  
   101  func newAdmissionWaiter(a admission.Attributes) *admissionWaiter {
   102  	return &admissionWaiter{
   103  		attributes: a,
   104  		finished:   make(chan struct{}),
   105  		result:     defaultDeny{},
   106  	}
   107  }
   108  
   109  // NewQuotaEvaluator configures an admission controller that can enforce quota constraints
   110  // using the provided registry.  The registry must have the capability to handle group/kinds that
   111  // are persisted by the server this admission controller is intercepting
   112  func NewQuotaEvaluator(quotaAccessor QuotaAccessor, ignoredResources map[schema.GroupResource]struct{}, quotaRegistry quota.Registry, lockAcquisitionFunc func([]corev1.ResourceQuota) func(), config *resourcequotaapi.Configuration, workers int, stopCh <-chan struct{}) Evaluator {
   113  	// if we get a nil config, just create an empty default.
   114  	if config == nil {
   115  		config = &resourcequotaapi.Configuration{}
   116  	}
   117  
   118  	evaluator := &quotaEvaluator{
   119  		quotaAccessor:       quotaAccessor,
   120  		lockAcquisitionFunc: lockAcquisitionFunc,
   121  
   122  		ignoredResources: ignoredResources,
   123  		registry:         quotaRegistry,
   124  
   125  		queue:      workqueue.NewTypedWithConfig(workqueue.TypedQueueConfig[string]{Name: "admission_quota_controller"}),
   126  		work:       map[string][]*admissionWaiter{},
   127  		dirtyWork:  map[string][]*admissionWaiter{},
   128  		inProgress: sets.String{},
   129  
   130  		workers: workers,
   131  		stopCh:  stopCh,
   132  		config:  config,
   133  	}
   134  
   135  	// The queue underneath is starting a goroutine for metrics
   136  	// exportint that is only stopped on calling ShutDown.
   137  	// Given that QuotaEvaluator is created for each layer of apiserver
   138  	// and often not started for some of those (e.g. aggregated apiserver)
   139  	// we explicitly shut it down on stopCh signal even if it wasn't
   140  	// effectively started.
   141  	go evaluator.shutdownOnStop()
   142  
   143  	return evaluator
   144  }
   145  
   146  // start begins watching and syncing.
   147  func (e *quotaEvaluator) start() {
   148  	defer utilruntime.HandleCrash()
   149  
   150  	for i := 0; i < e.workers; i++ {
   151  		go wait.Until(e.doWork, time.Second, e.stopCh)
   152  	}
   153  }
   154  
   155  func (e *quotaEvaluator) shutdownOnStop() {
   156  	<-e.stopCh
   157  	klog.Infof("Shutting down quota evaluator")
   158  	e.queue.ShutDown()
   159  }
   160  
   161  func (e *quotaEvaluator) doWork() {
   162  	workFunc := func() bool {
   163  		ns, admissionAttributes, quit := e.getWork()
   164  		if quit {
   165  			return true
   166  		}
   167  		defer e.completeWork(ns)
   168  		if len(admissionAttributes) == 0 {
   169  			return false
   170  		}
   171  		e.checkAttributes(ns, admissionAttributes)
   172  		return false
   173  	}
   174  	for {
   175  		if quit := workFunc(); quit {
   176  			klog.Infof("quota evaluator worker shutdown")
   177  			return
   178  		}
   179  	}
   180  }
   181  
   182  // checkAttributes iterates evaluates all the waiting admissionAttributes.  It will always notify all waiters
   183  // before returning.  The default is to deny.
   184  func (e *quotaEvaluator) checkAttributes(ns string, admissionAttributes []*admissionWaiter) {
   185  	// notify all on exit
   186  	defer func() {
   187  		for _, admissionAttribute := range admissionAttributes {
   188  			close(admissionAttribute.finished)
   189  		}
   190  	}()
   191  
   192  	quotas, err := e.quotaAccessor.GetQuotas(ns)
   193  	if err != nil {
   194  		for _, admissionAttribute := range admissionAttributes {
   195  			admissionAttribute.result = err
   196  		}
   197  		return
   198  	}
   199  	// if limited resources are disabled, we can just return safely when there are no quotas.
   200  	limitedResourcesDisabled := len(e.config.LimitedResources) == 0
   201  	if len(quotas) == 0 && limitedResourcesDisabled {
   202  		for _, admissionAttribute := range admissionAttributes {
   203  			admissionAttribute.result = nil
   204  		}
   205  		return
   206  	}
   207  
   208  	if e.lockAcquisitionFunc != nil {
   209  		releaseLocks := e.lockAcquisitionFunc(quotas)
   210  		defer releaseLocks()
   211  	}
   212  
   213  	e.checkQuotas(quotas, admissionAttributes, 3)
   214  }
   215  
   216  // checkQuotas checks the admission attributes against the passed quotas.  If a quota applies, it will attempt to update it
   217  // AFTER it has checked all the admissionAttributes.  The method breaks down into phase like this:
   218  //  0. make a copy of the quotas to act as a "running" quota so we know what we need to update and can still compare against the
   219  //     originals
   220  //  1. check each admission attribute to see if it fits within *all* the quotas.  If it didn't fit, mark the waiter as failed
   221  //     and the running quota doesn't change.  If it did fit, check to see if any quota was changed.  If there was no quota change
   222  //     mark the waiter as succeeded.  If some quota did change, update the running quotas
   223  //  2. If no running quota was changed, return now since no updates are needed.
   224  //  3. for each quota that has changed, attempt an update.  If all updates succeeded, update all unset waiters to success status and return.  If the some
   225  //     updates failed on conflict errors and we have retries left, re-get the failed quota from our cache for the latest version
   226  //     and recurse into this method with the subset.  It's safe for us to evaluate ONLY the subset, because the other quota
   227  //     documents for these waiters have already been evaluated.  Step 1, will mark all the ones that should already have succeeded.
   228  func (e *quotaEvaluator) checkQuotas(quotas []corev1.ResourceQuota, admissionAttributes []*admissionWaiter, remainingRetries int) {
   229  	// yet another copy to compare against originals to see if we actually have deltas
   230  	originalQuotas, err := copyQuotas(quotas)
   231  	if err != nil {
   232  		utilruntime.HandleError(err)
   233  		return
   234  	}
   235  
   236  	atLeastOneChanged := false
   237  	for i := range admissionAttributes {
   238  		admissionAttribute := admissionAttributes[i]
   239  		newQuotas, err := e.checkRequest(quotas, admissionAttribute.attributes)
   240  		if err != nil {
   241  			admissionAttribute.result = err
   242  			continue
   243  		}
   244  
   245  		// Don't update quota for admissionAttributes that correspond to dry-run requests
   246  		if admissionAttribute.attributes.IsDryRun() {
   247  			admissionAttribute.result = nil
   248  			continue
   249  		}
   250  
   251  		// if the new quotas are the same as the old quotas, then this particular one doesn't issue any updates
   252  		// that means that no quota docs applied, so it can get a pass
   253  		atLeastOneChangeForThisWaiter := false
   254  		for j := range newQuotas {
   255  			if !quota.Equals(quotas[j].Status.Used, newQuotas[j].Status.Used) {
   256  				atLeastOneChanged = true
   257  				atLeastOneChangeForThisWaiter = true
   258  				break
   259  			}
   260  		}
   261  
   262  		if !atLeastOneChangeForThisWaiter {
   263  			admissionAttribute.result = nil
   264  		}
   265  
   266  		quotas = newQuotas
   267  	}
   268  
   269  	// if none of the requests changed anything, there's no reason to issue an update, just fail them all now
   270  	if !atLeastOneChanged {
   271  		return
   272  	}
   273  
   274  	// now go through and try to issue updates.  Things get a little weird here:
   275  	// 1. check to see if the quota changed.  If not, skip.
   276  	// 2. if the quota changed and the update passes, be happy
   277  	// 3. if the quota changed and the update fails, add the original to a retry list
   278  	var updatedFailedQuotas []corev1.ResourceQuota
   279  	var lastErr error
   280  	for i := range quotas {
   281  		newQuota := quotas[i]
   282  
   283  		// if this quota didn't have its status changed, skip it
   284  		if quota.Equals(originalQuotas[i].Status.Used, newQuota.Status.Used) {
   285  			continue
   286  		}
   287  
   288  		if err := e.quotaAccessor.UpdateQuotaStatus(&newQuota); err != nil {
   289  			updatedFailedQuotas = append(updatedFailedQuotas, newQuota)
   290  			lastErr = err
   291  		}
   292  	}
   293  
   294  	if len(updatedFailedQuotas) == 0 {
   295  		// all the updates succeeded.  At this point, anything with the default deny error was just waiting to
   296  		// get a successful update, so we can mark and notify
   297  		for _, admissionAttribute := range admissionAttributes {
   298  			if IsDefaultDeny(admissionAttribute.result) {
   299  				admissionAttribute.result = nil
   300  			}
   301  		}
   302  		return
   303  	}
   304  
   305  	// at this point, errors are fatal.  Update all waiters without status to failed and return
   306  	if remainingRetries <= 0 {
   307  		for _, admissionAttribute := range admissionAttributes {
   308  			if IsDefaultDeny(admissionAttribute.result) {
   309  				admissionAttribute.result = lastErr
   310  			}
   311  		}
   312  		return
   313  	}
   314  
   315  	// this retry logic has the same bug that its possible to be checking against quota in a state that never actually exists where
   316  	// you've added a new documented, then updated an old one, your resource matches both and you're only checking one
   317  	// updates for these quota names failed.  Get the current quotas in the namespace, compare by name, check to see if the
   318  	// resource versions have changed.  If not, we're going to fall through an fail everything.  If they all have, then we can try again
   319  	newQuotas, err := e.quotaAccessor.GetQuotas(quotas[0].Namespace)
   320  	if err != nil {
   321  		// this means that updates failed.  Anything with a default deny error has failed and we need to let them know
   322  		for _, admissionAttribute := range admissionAttributes {
   323  			if IsDefaultDeny(admissionAttribute.result) {
   324  				admissionAttribute.result = lastErr
   325  			}
   326  		}
   327  		return
   328  	}
   329  
   330  	// this logic goes through our cache to find the new version of all quotas that failed update.  If something has been removed
   331  	// it is skipped on this retry.  After all, you removed it.
   332  	quotasToCheck := []corev1.ResourceQuota{}
   333  	for _, newQuota := range newQuotas {
   334  		for _, oldQuota := range updatedFailedQuotas {
   335  			if newQuota.Name == oldQuota.Name {
   336  				quotasToCheck = append(quotasToCheck, newQuota)
   337  				break
   338  			}
   339  		}
   340  	}
   341  	e.checkQuotas(quotasToCheck, admissionAttributes, remainingRetries-1)
   342  }
   343  
   344  func copyQuotas(in []corev1.ResourceQuota) ([]corev1.ResourceQuota, error) {
   345  	out := make([]corev1.ResourceQuota, 0, len(in))
   346  	for _, quota := range in {
   347  		out = append(out, *quota.DeepCopy())
   348  	}
   349  
   350  	return out, nil
   351  }
   352  
   353  // filterLimitedResourcesByGroupResource filters the input that match the specified groupResource
   354  func filterLimitedResourcesByGroupResource(input []resourcequotaapi.LimitedResource, groupResource schema.GroupResource) []resourcequotaapi.LimitedResource {
   355  	result := []resourcequotaapi.LimitedResource{}
   356  	for i := range input {
   357  		limitedResource := input[i]
   358  		limitedGroupResource := schema.GroupResource{Group: limitedResource.APIGroup, Resource: limitedResource.Resource}
   359  		if limitedGroupResource == groupResource {
   360  			result = append(result, limitedResource)
   361  		}
   362  	}
   363  	return result
   364  }
   365  
   366  // limitedByDefault determines from the specified usage and limitedResources the set of resources names
   367  // that must be present in a covering quota.  It returns empty set if it was unable to determine if
   368  // a resource was not limited by default.
   369  func limitedByDefault(usage corev1.ResourceList, limitedResources []resourcequotaapi.LimitedResource) []corev1.ResourceName {
   370  	result := []corev1.ResourceName{}
   371  	for _, limitedResource := range limitedResources {
   372  		for k, v := range usage {
   373  			// if a resource is consumed, we need to check if it matches on the limited resource list.
   374  			if v.Sign() == 1 {
   375  				// if we get a match, we add it to limited set
   376  				for _, matchContain := range limitedResource.MatchContains {
   377  					if strings.Contains(string(k), matchContain) {
   378  						result = append(result, k)
   379  						break
   380  					}
   381  				}
   382  			}
   383  		}
   384  	}
   385  	return result
   386  }
   387  
   388  func getMatchedLimitedScopes(evaluator quota.Evaluator, inputObject runtime.Object, limitedResources []resourcequotaapi.LimitedResource) ([]corev1.ScopedResourceSelectorRequirement, error) {
   389  	scopes := []corev1.ScopedResourceSelectorRequirement{}
   390  	for _, limitedResource := range limitedResources {
   391  		matched, err := evaluator.MatchingScopes(inputObject, limitedResource.MatchScopes)
   392  		if err != nil {
   393  			klog.ErrorS(err, "Error while matching limited Scopes")
   394  			return []corev1.ScopedResourceSelectorRequirement{}, err
   395  		}
   396  		scopes = append(scopes, matched...)
   397  	}
   398  	return scopes, nil
   399  }
   400  
   401  // checkRequest verifies that the request does not exceed any quota constraint. it returns a copy of quotas not yet persisted
   402  // that capture what the usage would be if the request succeeded.  It return an error if there is insufficient quota to satisfy the request
   403  func (e *quotaEvaluator) checkRequest(quotas []corev1.ResourceQuota, a admission.Attributes) ([]corev1.ResourceQuota, error) {
   404  	evaluator := e.registry.Get(a.GetResource().GroupResource())
   405  	if evaluator == nil {
   406  		return quotas, nil
   407  	}
   408  	return CheckRequest(quotas, a, evaluator, e.config.LimitedResources)
   409  }
   410  
   411  // CheckRequest is a static version of quotaEvaluator.checkRequest, possible to be called from outside.
   412  func CheckRequest(quotas []corev1.ResourceQuota, a admission.Attributes, evaluator quota.Evaluator,
   413  	limited []resourcequotaapi.LimitedResource) ([]corev1.ResourceQuota, error) {
   414  	if !evaluator.Handles(a) {
   415  		return quotas, nil
   416  	}
   417  
   418  	// if we have limited resources enabled for this resource, always calculate usage
   419  	inputObject := a.GetObject()
   420  
   421  	// Check if object matches AdmissionConfiguration matchScopes
   422  	limitedScopes, err := getMatchedLimitedScopes(evaluator, inputObject, limited)
   423  	if err != nil {
   424  		return quotas, nil
   425  	}
   426  
   427  	// determine the set of resource names that must exist in a covering quota
   428  	limitedResourceNames := []corev1.ResourceName{}
   429  	limitedResources := filterLimitedResourcesByGroupResource(limited, a.GetResource().GroupResource())
   430  	if len(limitedResources) > 0 {
   431  		deltaUsage, err := evaluator.Usage(inputObject)
   432  		if err != nil {
   433  			return quotas, err
   434  		}
   435  		limitedResourceNames = limitedByDefault(deltaUsage, limitedResources)
   436  	}
   437  	limitedResourceNamesSet := quota.ToSet(limitedResourceNames)
   438  
   439  	// find the set of quotas that are pertinent to this request
   440  	// reject if we match the quota, but usage is not calculated yet
   441  	// reject if the input object does not satisfy quota constraints
   442  	// if there are no pertinent quotas, we can just return
   443  	interestingQuotaIndexes := []int{}
   444  	// track the cumulative set of resources that were required across all quotas
   445  	// this is needed to know if we have satisfied any constraints where consumption
   446  	// was limited by default.
   447  	restrictedResourcesSet := sets.String{}
   448  	restrictedScopes := []corev1.ScopedResourceSelectorRequirement{}
   449  	for i := range quotas {
   450  		resourceQuota := quotas[i]
   451  		scopeSelectors := getScopeSelectorsFromQuota(resourceQuota)
   452  		localRestrictedScopes, err := evaluator.MatchingScopes(inputObject, scopeSelectors)
   453  		if err != nil {
   454  			return nil, fmt.Errorf("error matching scopes of quota %s, err: %v", resourceQuota.Name, err)
   455  		}
   456  		restrictedScopes = append(restrictedScopes, localRestrictedScopes...)
   457  
   458  		match, err := evaluator.Matches(&resourceQuota, inputObject)
   459  		if err != nil {
   460  			klog.ErrorS(err, "Error occurred while matching resource quota against input object",
   461  				"resourceQuota", resourceQuota)
   462  			return quotas, err
   463  		}
   464  		if !match {
   465  			continue
   466  		}
   467  
   468  		hardResources := quota.ResourceNames(resourceQuota.Status.Hard)
   469  		restrictedResources := evaluator.MatchingResources(hardResources)
   470  		if err := evaluator.Constraints(restrictedResources, inputObject); err != nil {
   471  			return nil, admission.NewForbidden(a, fmt.Errorf("failed quota: %s: %v", resourceQuota.Name, err))
   472  		}
   473  		if !hasUsageStats(&resourceQuota, restrictedResources) {
   474  			return nil, admission.NewForbidden(a, fmt.Errorf("status unknown for quota: %s, resources: %s", resourceQuota.Name, prettyPrintResourceNames(restrictedResources)))
   475  		}
   476  		interestingQuotaIndexes = append(interestingQuotaIndexes, i)
   477  		localRestrictedResourcesSet := quota.ToSet(restrictedResources)
   478  		restrictedResourcesSet.Insert(localRestrictedResourcesSet.List()...)
   479  	}
   480  
   481  	// Usage of some resources cannot be counted in isolation. For example, when
   482  	// the resource represents a number of unique references to external
   483  	// resource. In such a case an evaluator needs to process other objects in
   484  	// the same namespace which needs to be known.
   485  	namespace := a.GetNamespace()
   486  	if accessor, err := meta.Accessor(inputObject); namespace != "" && err == nil {
   487  		if accessor.GetNamespace() == "" {
   488  			accessor.SetNamespace(namespace)
   489  		}
   490  	}
   491  	// there is at least one quota that definitely matches our object
   492  	// as a result, we need to measure the usage of this object for quota
   493  	// on updates, we need to subtract the previous measured usage
   494  	// if usage shows no change, just return since it has no impact on quota
   495  	deltaUsage, err := evaluator.Usage(inputObject)
   496  	if err != nil {
   497  		return quotas, err
   498  	}
   499  
   500  	// ensure that usage for input object is never negative (this would mean a resource made a negative resource requirement)
   501  	if negativeUsage := quota.IsNegative(deltaUsage); len(negativeUsage) > 0 {
   502  		return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage)))
   503  	}
   504  
   505  	if admission.Update == a.GetOperation() {
   506  		prevItem := a.GetOldObject()
   507  		if prevItem == nil {
   508  			return nil, admission.NewForbidden(a, fmt.Errorf("unable to get previous usage since prior version of object was not found"))
   509  		}
   510  
   511  		// if we can definitively determine that this is not a case of "create on update",
   512  		// then charge based on the delta.  Otherwise, bill the maximum
   513  		metadata, err := meta.Accessor(prevItem)
   514  		if err == nil && len(metadata.GetResourceVersion()) > 0 {
   515  			prevUsage, innerErr := evaluator.Usage(prevItem)
   516  			if innerErr != nil {
   517  				return quotas, innerErr
   518  			}
   519  			deltaUsage = quota.SubtractWithNonNegativeResult(deltaUsage, prevUsage)
   520  		}
   521  	}
   522  
   523  	// ignore items in deltaUsage with zero usage
   524  	deltaUsage = quota.RemoveZeros(deltaUsage)
   525  	// if there is no remaining non-zero usage, short-circuit and return
   526  	if len(deltaUsage) == 0 {
   527  		return quotas, nil
   528  	}
   529  
   530  	// verify that for every resource that had limited by default consumption
   531  	// enabled that there was a corresponding quota that covered its use.
   532  	// if not, we reject the request.
   533  	hasNoCoveringQuota := limitedResourceNamesSet.Difference(restrictedResourcesSet)
   534  	if len(hasNoCoveringQuota) > 0 {
   535  		return quotas, admission.NewForbidden(a, fmt.Errorf("insufficient quota to consume: %v", strings.Join(hasNoCoveringQuota.List(), ",")))
   536  	}
   537  
   538  	// verify that for every scope that had limited access enabled
   539  	// that there was a corresponding quota that covered it.
   540  	// if not, we reject the request.
   541  	scopesHasNoCoveringQuota, err := evaluator.UncoveredQuotaScopes(limitedScopes, restrictedScopes)
   542  	if err != nil {
   543  		return quotas, err
   544  	}
   545  	if len(scopesHasNoCoveringQuota) > 0 {
   546  		return quotas, fmt.Errorf("insufficient quota to match these scopes: %v", scopesHasNoCoveringQuota)
   547  	}
   548  
   549  	if len(interestingQuotaIndexes) == 0 {
   550  		return quotas, nil
   551  	}
   552  
   553  	outQuotas, err := copyQuotas(quotas)
   554  	if err != nil {
   555  		return nil, err
   556  	}
   557  
   558  	for _, index := range interestingQuotaIndexes {
   559  		resourceQuota := outQuotas[index]
   560  
   561  		hardResources := quota.ResourceNames(resourceQuota.Status.Hard)
   562  		requestedUsage := quota.Mask(deltaUsage, hardResources)
   563  		newUsage := quota.Add(resourceQuota.Status.Used, requestedUsage)
   564  		maskedNewUsage := quota.Mask(newUsage, quota.ResourceNames(requestedUsage))
   565  
   566  		if allowed, exceeded := quota.LessThanOrEqual(maskedNewUsage, resourceQuota.Status.Hard); !allowed {
   567  			failedRequestedUsage := quota.Mask(requestedUsage, exceeded)
   568  			failedUsed := quota.Mask(resourceQuota.Status.Used, exceeded)
   569  			failedHard := quota.Mask(resourceQuota.Status.Hard, exceeded)
   570  			return nil, admission.NewForbidden(a,
   571  				fmt.Errorf("exceeded quota: %s, requested: %s, used: %s, limited: %s",
   572  					resourceQuota.Name,
   573  					prettyPrint(failedRequestedUsage),
   574  					prettyPrint(failedUsed),
   575  					prettyPrint(failedHard)))
   576  		}
   577  
   578  		// update to the new usage number
   579  		outQuotas[index].Status.Used = newUsage
   580  	}
   581  
   582  	return outQuotas, nil
   583  }
   584  
   585  func getScopeSelectorsFromQuota(quota corev1.ResourceQuota) []corev1.ScopedResourceSelectorRequirement {
   586  	selectors := []corev1.ScopedResourceSelectorRequirement{}
   587  	for _, scope := range quota.Spec.Scopes {
   588  		selectors = append(selectors, corev1.ScopedResourceSelectorRequirement{
   589  			ScopeName: scope,
   590  			Operator:  corev1.ScopeSelectorOpExists})
   591  	}
   592  	if quota.Spec.ScopeSelector != nil {
   593  		selectors = append(selectors, quota.Spec.ScopeSelector.MatchExpressions...)
   594  	}
   595  	return selectors
   596  }
   597  
   598  func (e *quotaEvaluator) Evaluate(a admission.Attributes) error {
   599  	e.init.Do(e.start)
   600  
   601  	// is this resource ignored?
   602  	gvr := a.GetResource()
   603  	gr := gvr.GroupResource()
   604  	if _, ok := e.ignoredResources[gr]; ok {
   605  		return nil
   606  	}
   607  
   608  	// if we do not know how to evaluate use for this resource, create an evaluator
   609  	evaluator := e.registry.Get(gr)
   610  	if evaluator == nil {
   611  		// create an object count evaluator if no evaluator previously registered
   612  		// note, we do not need aggregate usage here, so we pass a nil informer func
   613  		evaluator = generic.NewObjectCountEvaluator(gr, nil, "")
   614  		e.registry.Add(evaluator)
   615  		klog.Infof("quota admission added evaluator for: %s", gr)
   616  	}
   617  	// for this kind, check if the operation could mutate any quota resources
   618  	// if no resources tracked by quota are impacted, then just return
   619  	if !evaluator.Handles(a) {
   620  		return nil
   621  	}
   622  	waiter := newAdmissionWaiter(a)
   623  
   624  	e.addWork(waiter)
   625  
   626  	// wait for completion or timeout
   627  	select {
   628  	case <-waiter.finished:
   629  	case <-time.After(10 * time.Second):
   630  		return apierrors.NewInternalError(fmt.Errorf("resource quota evaluation timed out"))
   631  	}
   632  
   633  	return waiter.result
   634  }
   635  
   636  func (e *quotaEvaluator) addWork(a *admissionWaiter) {
   637  	e.workLock.Lock()
   638  	defer e.workLock.Unlock()
   639  
   640  	ns := a.attributes.GetNamespace()
   641  	// this Add can trigger a Get BEFORE the work is added to a list, but this is ok because the getWork routine
   642  	// waits the worklock before retrieving the work to do, so the writes in this method will be observed
   643  	e.queue.Add(ns)
   644  
   645  	if e.inProgress.Has(ns) {
   646  		e.dirtyWork[ns] = append(e.dirtyWork[ns], a)
   647  		return
   648  	}
   649  
   650  	e.work[ns] = append(e.work[ns], a)
   651  }
   652  
   653  func (e *quotaEvaluator) completeWork(ns string) {
   654  	e.workLock.Lock()
   655  	defer e.workLock.Unlock()
   656  
   657  	e.queue.Done(ns)
   658  	e.work[ns] = e.dirtyWork[ns]
   659  	delete(e.dirtyWork, ns)
   660  	e.inProgress.Delete(ns)
   661  }
   662  
   663  // getWork returns a namespace, a list of work items in that
   664  // namespace, and a shutdown boolean.  If not shutdown then the return
   665  // must eventually be followed by a call on completeWork for the
   666  // returned namespace (regardless of whether the work item list is
   667  // empty).
   668  func (e *quotaEvaluator) getWork() (string, []*admissionWaiter, bool) {
   669  	ns, shutdown := e.queue.Get()
   670  	if shutdown {
   671  		return "", []*admissionWaiter{}, shutdown
   672  	}
   673  
   674  	e.workLock.Lock()
   675  	defer e.workLock.Unlock()
   676  	// at this point, we know we have a coherent view of e.work.  It is entirely possible
   677  	// that our workqueue has another item requeued to it, but we'll pick it up early.  This ok
   678  	// because the next time will go into our dirty list
   679  
   680  	work := e.work[ns]
   681  	delete(e.work, ns)
   682  	delete(e.dirtyWork, ns)
   683  	e.inProgress.Insert(ns)
   684  	return ns, work, false
   685  }
   686  
   687  // prettyPrint formats a resource list for usage in errors
   688  // it outputs resources sorted in increasing order
   689  func prettyPrint(item corev1.ResourceList) string {
   690  	parts := []string{}
   691  	keys := []string{}
   692  	for key := range item {
   693  		keys = append(keys, string(key))
   694  	}
   695  	sort.Strings(keys)
   696  	for _, key := range keys {
   697  		value := item[corev1.ResourceName(key)]
   698  		constraint := key + "=" + value.String()
   699  		parts = append(parts, constraint)
   700  	}
   701  	return strings.Join(parts, ",")
   702  }
   703  
   704  func prettyPrintResourceNames(a []corev1.ResourceName) string {
   705  	values := []string{}
   706  	for _, value := range a {
   707  		values = append(values, string(value))
   708  	}
   709  	sort.Strings(values)
   710  	return strings.Join(values, ",")
   711  }
   712  
   713  // hasUsageStats returns true if for each hard constraint in interestingResources there is a value for its current usage
   714  func hasUsageStats(resourceQuota *corev1.ResourceQuota, interestingResources []corev1.ResourceName) bool {
   715  	interestingSet := quota.ToSet(interestingResources)
   716  	for resourceName := range resourceQuota.Status.Hard {
   717  		if !interestingSet.Has(string(resourceName)) {
   718  			continue
   719  		}
   720  		if _, found := resourceQuota.Status.Used[resourceName]; !found {
   721  			return false
   722  		}
   723  	}
   724  	return true
   725  }