evaluator.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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 generic
  14. import (
  15. "fmt"
  16. "sync/atomic"
  17. corev1 "k8s.io/api/core/v1"
  18. "k8s.io/apimachinery/pkg/api/resource"
  19. "k8s.io/apimachinery/pkg/labels"
  20. "k8s.io/apimachinery/pkg/runtime"
  21. "k8s.io/apimachinery/pkg/runtime/schema"
  22. "k8s.io/apiserver/pkg/admission"
  23. "k8s.io/client-go/informers"
  24. "k8s.io/client-go/tools/cache"
  25. quota "k8s.io/kubernetes/pkg/quota/v1"
  26. )
  27. // InformerForResourceFunc knows how to provision an informer
  28. type InformerForResourceFunc func(schema.GroupVersionResource) (informers.GenericInformer, error)
  29. // ListerFuncForResourceFunc knows how to provision a lister from an informer func.
  30. // The lister returns errors until the informer has synced.
  31. func ListerFuncForResourceFunc(f InformerForResourceFunc) quota.ListerForResourceFunc {
  32. return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) {
  33. informer, err := f(gvr)
  34. if err != nil {
  35. return nil, err
  36. }
  37. return &protectedLister{
  38. hasSynced: cachedHasSynced(informer.Informer().HasSynced),
  39. notReadyErr: fmt.Errorf("%v not yet synced", gvr),
  40. delegate: informer.Lister(),
  41. }, nil
  42. }
  43. }
  44. // cachedHasSynced returns a function that calls hasSynced() until it returns true once, then returns true
  45. func cachedHasSynced(hasSynced func() bool) func() bool {
  46. cache := &atomic.Value{}
  47. cache.Store(false)
  48. return func() bool {
  49. if cache.Load().(bool) {
  50. // short-circuit if already synced
  51. return true
  52. }
  53. if hasSynced() {
  54. // remember we synced
  55. cache.Store(true)
  56. return true
  57. }
  58. return false
  59. }
  60. }
  61. // protectedLister returns notReadyError if hasSynced returns false, otherwise delegates to delegate
  62. type protectedLister struct {
  63. hasSynced func() bool
  64. notReadyErr error
  65. delegate cache.GenericLister
  66. }
  67. func (p *protectedLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
  68. if !p.hasSynced() {
  69. return nil, p.notReadyErr
  70. }
  71. return p.delegate.List(selector)
  72. }
  73. func (p *protectedLister) Get(name string) (runtime.Object, error) {
  74. if !p.hasSynced() {
  75. return nil, p.notReadyErr
  76. }
  77. return p.delegate.Get(name)
  78. }
  79. func (p *protectedLister) ByNamespace(namespace string) cache.GenericNamespaceLister {
  80. return &protectedNamespaceLister{p.hasSynced, p.notReadyErr, p.delegate.ByNamespace(namespace)}
  81. }
  82. // protectedNamespaceLister returns notReadyError if hasSynced returns false, otherwise delegates to delegate
  83. type protectedNamespaceLister struct {
  84. hasSynced func() bool
  85. notReadyErr error
  86. delegate cache.GenericNamespaceLister
  87. }
  88. func (p *protectedNamespaceLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
  89. if !p.hasSynced() {
  90. return nil, p.notReadyErr
  91. }
  92. return p.delegate.List(selector)
  93. }
  94. func (p *protectedNamespaceLister) Get(name string) (runtime.Object, error) {
  95. if !p.hasSynced() {
  96. return nil, p.notReadyErr
  97. }
  98. return p.delegate.Get(name)
  99. }
  100. // ListResourceUsingListerFunc returns a listing function based on the shared informer factory for the specified resource.
  101. func ListResourceUsingListerFunc(l quota.ListerForResourceFunc, resource schema.GroupVersionResource) ListFuncByNamespace {
  102. return func(namespace string) ([]runtime.Object, error) {
  103. lister, err := l(resource)
  104. if err != nil {
  105. return nil, err
  106. }
  107. return lister.ByNamespace(namespace).List(labels.Everything())
  108. }
  109. }
  110. // ObjectCountQuotaResourceNameFor returns the object count quota name for specified groupResource
  111. func ObjectCountQuotaResourceNameFor(groupResource schema.GroupResource) corev1.ResourceName {
  112. if len(groupResource.Group) == 0 {
  113. return corev1.ResourceName("count/" + groupResource.Resource)
  114. }
  115. return corev1.ResourceName("count/" + groupResource.Resource + "." + groupResource.Group)
  116. }
  117. // ListFuncByNamespace knows how to list resources in a namespace
  118. type ListFuncByNamespace func(namespace string) ([]runtime.Object, error)
  119. // MatchesScopeFunc knows how to evaluate if an object matches a scope
  120. type MatchesScopeFunc func(scope corev1.ScopedResourceSelectorRequirement, object runtime.Object) (bool, error)
  121. // UsageFunc knows how to measure usage associated with an object
  122. type UsageFunc func(object runtime.Object) (corev1.ResourceList, error)
  123. // MatchingResourceNamesFunc is a function that returns the list of resources matched
  124. type MatchingResourceNamesFunc func(input []corev1.ResourceName) []corev1.ResourceName
  125. // MatchesNoScopeFunc returns false on all match checks
  126. func MatchesNoScopeFunc(scope corev1.ScopedResourceSelectorRequirement, object runtime.Object) (bool, error) {
  127. return false, nil
  128. }
  129. // Matches returns true if the quota matches the specified item.
  130. func Matches(
  131. resourceQuota *corev1.ResourceQuota, item runtime.Object,
  132. matchFunc MatchingResourceNamesFunc, scopeFunc MatchesScopeFunc) (bool, error) {
  133. if resourceQuota == nil {
  134. return false, fmt.Errorf("expected non-nil quota")
  135. }
  136. // verify the quota matches on at least one resource
  137. matchResource := len(matchFunc(quota.ResourceNames(resourceQuota.Status.Hard))) > 0
  138. // by default, no scopes matches all
  139. matchScope := true
  140. for _, scope := range getScopeSelectorsFromQuota(resourceQuota) {
  141. innerMatch, err := scopeFunc(scope, item)
  142. if err != nil {
  143. return false, err
  144. }
  145. matchScope = matchScope && innerMatch
  146. }
  147. return matchResource && matchScope, nil
  148. }
  149. func getScopeSelectorsFromQuota(quota *corev1.ResourceQuota) []corev1.ScopedResourceSelectorRequirement {
  150. selectors := []corev1.ScopedResourceSelectorRequirement{}
  151. for _, scope := range quota.Spec.Scopes {
  152. selectors = append(selectors, corev1.ScopedResourceSelectorRequirement{
  153. ScopeName: scope,
  154. Operator: corev1.ScopeSelectorOpExists})
  155. }
  156. if quota.Spec.ScopeSelector != nil {
  157. selectors = append(selectors, quota.Spec.ScopeSelector.MatchExpressions...)
  158. }
  159. return selectors
  160. }
  161. // CalculateUsageStats is a utility function that knows how to calculate aggregate usage.
  162. func CalculateUsageStats(options quota.UsageStatsOptions,
  163. listFunc ListFuncByNamespace,
  164. scopeFunc MatchesScopeFunc,
  165. usageFunc UsageFunc) (quota.UsageStats, error) {
  166. // default each tracked resource to zero
  167. result := quota.UsageStats{Used: corev1.ResourceList{}}
  168. for _, resourceName := range options.Resources {
  169. result.Used[resourceName] = resource.Quantity{Format: resource.DecimalSI}
  170. }
  171. items, err := listFunc(options.Namespace)
  172. if err != nil {
  173. return result, fmt.Errorf("failed to list content: %v", err)
  174. }
  175. for _, item := range items {
  176. // need to verify that the item matches the set of scopes
  177. matchesScopes := true
  178. for _, scope := range options.Scopes {
  179. innerMatch, err := scopeFunc(corev1.ScopedResourceSelectorRequirement{ScopeName: scope}, item)
  180. if err != nil {
  181. return result, nil
  182. }
  183. if !innerMatch {
  184. matchesScopes = false
  185. }
  186. }
  187. if options.ScopeSelector != nil {
  188. for _, selector := range options.ScopeSelector.MatchExpressions {
  189. innerMatch, err := scopeFunc(selector, item)
  190. if err != nil {
  191. return result, nil
  192. }
  193. matchesScopes = matchesScopes && innerMatch
  194. }
  195. }
  196. // only count usage if there was a match
  197. if matchesScopes {
  198. usage, err := usageFunc(item)
  199. if err != nil {
  200. return result, err
  201. }
  202. result.Used = quota.Add(result.Used, usage)
  203. }
  204. }
  205. return result, nil
  206. }
  207. // objectCountEvaluator provides an implementation for quota.Evaluator
  208. // that associates usage of the specified resource based on the number of items
  209. // returned by the specified listing function.
  210. type objectCountEvaluator struct {
  211. // GroupResource that this evaluator tracks.
  212. // It is used to construct a generic object count quota name
  213. groupResource schema.GroupResource
  214. // A function that knows how to list resources by namespace.
  215. // TODO move to dynamic client in future
  216. listFuncByNamespace ListFuncByNamespace
  217. // Names associated with this resource in the quota for generic counting.
  218. resourceNames []corev1.ResourceName
  219. }
  220. // Constraints returns an error if the configured resource name is not in the required set.
  221. func (o *objectCountEvaluator) Constraints(required []corev1.ResourceName, item runtime.Object) error {
  222. // no-op for object counting
  223. return nil
  224. }
  225. // Handles returns true if the object count evaluator needs to track this attributes.
  226. func (o *objectCountEvaluator) Handles(a admission.Attributes) bool {
  227. operation := a.GetOperation()
  228. return operation == admission.Create
  229. }
  230. // Matches returns true if the evaluator matches the specified quota with the provided input item
  231. func (o *objectCountEvaluator) Matches(resourceQuota *corev1.ResourceQuota, item runtime.Object) (bool, error) {
  232. return Matches(resourceQuota, item, o.MatchingResources, MatchesNoScopeFunc)
  233. }
  234. // MatchingResources takes the input specified list of resources and returns the set of resources it matches.
  235. func (o *objectCountEvaluator) MatchingResources(input []corev1.ResourceName) []corev1.ResourceName {
  236. return quota.Intersection(input, o.resourceNames)
  237. }
  238. // MatchingScopes takes the input specified list of scopes and input object. Returns the set of scopes resource matches.
  239. func (o *objectCountEvaluator) MatchingScopes(item runtime.Object, scopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) {
  240. return []corev1.ScopedResourceSelectorRequirement{}, nil
  241. }
  242. // UncoveredQuotaScopes takes the input matched scopes which are limited by configuration and the matched quota scopes.
  243. // It returns the scopes which are in limited scopes but dont have a corresponding covering quota scope
  244. func (o *objectCountEvaluator) UncoveredQuotaScopes(limitedScopes []corev1.ScopedResourceSelectorRequirement, matchedQuotaScopes []corev1.ScopedResourceSelectorRequirement) ([]corev1.ScopedResourceSelectorRequirement, error) {
  245. return []corev1.ScopedResourceSelectorRequirement{}, nil
  246. }
  247. // Usage returns the resource usage for the specified object
  248. func (o *objectCountEvaluator) Usage(object runtime.Object) (corev1.ResourceList, error) {
  249. quantity := resource.NewQuantity(1, resource.DecimalSI)
  250. resourceList := corev1.ResourceList{}
  251. for _, resourceName := range o.resourceNames {
  252. resourceList[resourceName] = *quantity
  253. }
  254. return resourceList, nil
  255. }
  256. // GroupResource tracked by this evaluator
  257. func (o *objectCountEvaluator) GroupResource() schema.GroupResource {
  258. return o.groupResource
  259. }
  260. // UsageStats calculates aggregate usage for the object.
  261. func (o *objectCountEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) {
  262. return CalculateUsageStats(options, o.listFuncByNamespace, MatchesNoScopeFunc, o.Usage)
  263. }
  264. // Verify implementation of interface at compile time.
  265. var _ quota.Evaluator = &objectCountEvaluator{}
  266. // NewObjectCountEvaluator returns an evaluator that can perform generic
  267. // object quota counting. It allows an optional alias for backwards compatibility
  268. // purposes for the legacy object counting names in quota. Unless its supporting
  269. // backward compatibility, alias should not be used.
  270. func NewObjectCountEvaluator(
  271. groupResource schema.GroupResource, listFuncByNamespace ListFuncByNamespace,
  272. alias corev1.ResourceName) quota.Evaluator {
  273. resourceNames := []corev1.ResourceName{ObjectCountQuotaResourceNameFor(groupResource)}
  274. if len(alias) > 0 {
  275. resourceNames = append(resourceNames, alias)
  276. }
  277. return &objectCountEvaluator{
  278. groupResource: groupResource,
  279. listFuncByNamespace: listFuncByNamespace,
  280. resourceNames: resourceNames,
  281. }
  282. }