Commit 5b4ca143 authored by Derek Carr's avatar Derek Carr

Update admission control framework for quota

parent 7f88e918
......@@ -506,11 +506,9 @@ func BuildAdmissionPluginInitializer(s *options.ServerRunOptions, client interna
// TODO: use a dynamic restmapper. See https://github.com/kubernetes/kubernetes/pull/42615.
restMapper := legacyscheme.Registry.RESTMapper()
// NOTE: we do not provide informers to the quota registry because admission level decisions
// do not require us to open watches for all items tracked by quota.
quotaRegistry := quotainstall.NewRegistry(nil, nil)
quotaConfiguration := quotainstall.NewQuotaConfigurationForAdmission()
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaRegistry, webhookAuthWrapper, serviceResolver)
pluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaConfiguration, webhookAuthWrapper, serviceResolver)
return pluginInitializer, nil
}
......
......@@ -53,9 +53,9 @@ type WantsRESTMapper interface {
SetRESTMapper(meta.RESTMapper)
}
// WantsQuotaRegistry defines a function which sets quota registry for admission plugins that need it.
type WantsQuotaRegistry interface {
SetQuotaRegistry(quota.Registry)
// WantsQuotaConfiguration defines a function which sets quota configuration for admission plugins that need it.
type WantsQuotaConfiguration interface {
SetQuotaConfiguration(quota.Configuration)
admission.Validator
}
......@@ -85,7 +85,7 @@ type PluginInitializer struct {
authorizer authorizer.Authorizer
cloudConfig []byte
restMapper meta.RESTMapper
quotaRegistry quota.Registry
quotaConfiguration quota.Configuration
serviceResolver webhook.ServiceResolver
authenticationInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper
}
......@@ -100,7 +100,7 @@ func NewPluginInitializer(
sharedInformers informers.SharedInformerFactory,
cloudConfig []byte,
restMapper meta.RESTMapper,
quotaRegistry quota.Registry,
quotaConfiguration quota.Configuration,
authenticationInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper,
serviceResolver webhook.ServiceResolver,
) *PluginInitializer {
......@@ -109,7 +109,7 @@ func NewPluginInitializer(
informers: sharedInformers,
cloudConfig: cloudConfig,
restMapper: restMapper,
quotaRegistry: quotaRegistry,
quotaConfiguration: quotaConfiguration,
authenticationInfoResolverWrapper: authenticationInfoResolverWrapper,
serviceResolver: serviceResolver,
}
......@@ -134,8 +134,8 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
wants.SetRESTMapper(i.restMapper)
}
if wants, ok := plugin.(WantsQuotaRegistry); ok {
wants.SetQuotaRegistry(i.quotaRegistry)
if wants, ok := plugin.(WantsQuotaConfiguration); ok {
wants.SetQuotaConfiguration(i.quotaConfiguration)
}
if wants, ok := plugin.(WantsServiceResolver); ok {
......
......@@ -23,6 +23,7 @@ go_library(
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/quota:go_default_library",
"//pkg/quota/generic:go_default_library",
"//pkg/util/reflector/prometheus:go_default_library",
"//pkg/util/workqueue/prometheus:go_default_library",
"//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library",
......@@ -58,14 +59,11 @@ go_test(
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/quota:go_default_library",
"//pkg/quota/generic:go_default_library",
"//pkg/quota/install:go_default_library",
"//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library",
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
......
......@@ -53,16 +53,16 @@ func Register(plugins *admission.Plugins) {
// quotaAdmission implements an admission controller that can enforce quota constraints
type quotaAdmission struct {
*admission.Handler
config *resourcequotaapi.Configuration
stopCh <-chan struct{}
registry quota.Registry
numEvaluators int
quotaAccessor *quotaAccessor
evaluator Evaluator
config *resourcequotaapi.Configuration
stopCh <-chan struct{}
quotaConfiguration quota.Configuration
numEvaluators int
quotaAccessor *quotaAccessor
evaluator Evaluator
}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&quotaAdmission{})
var _ = kubeapiserveradmission.WantsQuotaRegistry(&quotaAdmission{})
var _ = kubeapiserveradmission.WantsQuotaConfiguration(&quotaAdmission{})
type liveLookupEntry struct {
expiry time.Time
......@@ -95,9 +95,9 @@ func (a *quotaAdmission) SetInternalKubeInformerFactory(f informers.SharedInform
a.quotaAccessor.lister = f.Core().InternalVersion().ResourceQuotas().Lister()
}
func (a *quotaAdmission) SetQuotaRegistry(r quota.Registry) {
a.registry = r
a.evaluator = NewQuotaEvaluator(a.quotaAccessor, a.registry, nil, a.config, a.numEvaluators, a.stopCh)
func (a *quotaAdmission) SetQuotaConfiguration(c quota.Configuration) {
a.quotaConfiguration = c
a.evaluator = NewQuotaEvaluator(a.quotaAccessor, a.quotaConfiguration, nil, a.config, a.numEvaluators, a.stopCh)
}
// Validate ensures an authorizer is set.
......@@ -111,8 +111,8 @@ func (a *quotaAdmission) Validate() error {
if a.quotaAccessor.lister == nil {
return fmt.Errorf("missing quotaAccessor.lister")
}
if a.registry == nil {
return fmt.Errorf("missing registry")
if a.quotaConfiguration == nil {
return fmt.Errorf("missing quotaConfiguration")
}
if a.evaluator == nil {
return fmt.Errorf("missing evaluator")
......@@ -126,5 +126,9 @@ func (a *quotaAdmission) Admit(attr admission.Attributes) (err error) {
if attr.GetSubresource() != "" {
return nil
}
// ignore all operations that are not namespaced
if attr.GetNamespace() == "" {
return nil
}
return a.evaluator.Evaluate(attr)
}
......@@ -34,6 +34,7 @@ import (
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic"
_ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration
_ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration
resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
......@@ -51,6 +52,9 @@ type quotaEvaluator struct {
// lockAcquisitionFunc acquires any required locks and returns a cleanup method to defer
lockAcquisitionFunc func([]api.ResourceQuota) func()
// how quota was configured
quotaConfiguration quota.Configuration
// registry that knows how to measure usage for objects
registry quota.Registry
......@@ -106,16 +110,18 @@ func newAdmissionWaiter(a admission.Attributes) *admissionWaiter {
// NewQuotaEvaluator configures an admission controller that can enforce quota constraints
// using the provided registry. The registry must have the capability to handle group/kinds that
// are persisted by the server this admission controller is intercepting
func NewQuotaEvaluator(quotaAccessor QuotaAccessor, registry quota.Registry, lockAcquisitionFunc func([]api.ResourceQuota) func(), config *resourcequotaapi.Configuration, workers int, stopCh <-chan struct{}) Evaluator {
func NewQuotaEvaluator(quotaAccessor QuotaAccessor, quotaConfiguration quota.Configuration, lockAcquisitionFunc func([]api.ResourceQuota) func(), config *resourcequotaapi.Configuration, workers int, stopCh <-chan struct{}) Evaluator {
// if we get a nil config, just create an empty default.
if config == nil {
config = &resourcequotaapi.Configuration{}
}
return &quotaEvaluator{
quotaAccessor: quotaAccessor,
lockAcquisitionFunc: lockAcquisitionFunc,
registry: registry,
quotaConfiguration: quotaConfiguration,
registry: generic.NewRegistry(quotaConfiguration.Evaluators()),
queue: workqueue.NewNamed("admission_quota_controller"),
work: map[string][]*admissionWaiter{},
......@@ -365,9 +371,8 @@ func limitedByDefault(usage api.ResourceList, limitedResources []resourcequotaap
// that capture what the usage would be if the request succeeded. It return an error if there is insufficient quota to satisfy the request
func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.Attributes) ([]api.ResourceQuota, error) {
namespace := a.GetNamespace()
evaluators := e.registry.Evaluators()
evaluator, found := evaluators[a.GetKind().GroupKind()]
if !found {
evaluator := e.registry.Get(a.GetResource().GroupResource())
if evaluator == nil {
return quotas, nil
}
......@@ -516,18 +521,27 @@ func (e *quotaEvaluator) Evaluate(a admission.Attributes) error {
go e.run()
})
// if we do not know how to evaluate use for this kind, just ignore
evaluators := e.registry.Evaluators()
evaluator, found := evaluators[a.GetKind().GroupKind()]
if !found {
// is this resource ignored?
gvr := a.GetResource()
gr := gvr.GroupResource()
if _, ok := e.quotaConfiguration.IgnoredResources()[gr]; ok {
return nil
}
// if we do not know how to evaluate use for this resource, create an evaluator
evaluator := e.registry.Get(gr)
if evaluator == nil {
// create an object count evaluator if no evaluator previously registered
// note, we do not need aggregate usage here, so we pass a nil infomer func
evaluator = generic.NewObjectCountEvaluator(false, gr, nil, "")
e.registry.Add(evaluator)
glog.Infof("quota admission added evaluator for: %s", gr)
}
// for this kind, check if the operation could mutate any quota resources
// if no resources tracked by quota are impacted, then just return
if !evaluator.Handles(a) {
return nil
}
waiter := newAdmissionWaiter(a)
e.addWork(waiter)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment