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