controller.go 25 KB

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