k8s.io/apiserver@v0.31.1/pkg/admission/plugin/resourcequota/admission.go (about) 1 /* 2 Copyright 2014 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 "context" 21 "fmt" 22 "io" 23 "time" 24 25 corev1 "k8s.io/api/core/v1" 26 "k8s.io/apiserver/pkg/admission" 27 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" 28 resourcequotaapi "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota" 29 v1 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/v1" 30 "k8s.io/apiserver/pkg/admission/plugin/resourcequota/apis/resourcequota/validation" 31 quota "k8s.io/apiserver/pkg/quota/v1" 32 "k8s.io/apiserver/pkg/quota/v1/generic" 33 "k8s.io/client-go/informers" 34 "k8s.io/client-go/kubernetes" 35 ) 36 37 // PluginName is a string with the name of the plugin 38 const PluginName = "ResourceQuota" 39 40 var ( 41 namespaceGVK = v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() 42 stopChUnconfiguredErr = fmt.Errorf("quota configuration configured between stop channel") 43 ) 44 45 // Register registers a plugin 46 func Register(plugins *admission.Plugins) { 47 plugins.Register(PluginName, 48 func(config io.Reader) (admission.Interface, error) { 49 // load the configuration provided (if any) 50 configuration, err := LoadConfiguration(config) 51 if err != nil { 52 return nil, err 53 } 54 // validate the configuration (if any) 55 if configuration != nil { 56 if errs := validation.ValidateConfiguration(configuration); len(errs) != 0 { 57 return nil, errs.ToAggregate() 58 } 59 } 60 return NewResourceQuota(configuration, 5) 61 }) 62 } 63 64 // QuotaAdmission implements an admission controller that can enforce quota constraints 65 type QuotaAdmission struct { 66 *admission.Handler 67 config *resourcequotaapi.Configuration 68 stopCh <-chan struct{} 69 quotaConfiguration quota.Configuration 70 numEvaluators int 71 quotaAccessor *quotaAccessor 72 evaluator Evaluator 73 initializationErr error 74 } 75 76 var _ admission.ValidationInterface = &QuotaAdmission{} 77 var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&QuotaAdmission{}) 78 var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&QuotaAdmission{}) 79 var _ = genericadmissioninitializer.WantsQuotaConfiguration(&QuotaAdmission{}) 80 var _ = genericadmissioninitializer.WantsDrainedNotification(&QuotaAdmission{}) 81 82 type liveLookupEntry struct { 83 expiry time.Time 84 items []*corev1.ResourceQuota 85 } 86 87 // NewResourceQuota configures an admission controller that can enforce quota constraints 88 // using the provided registry. The registry must have the capability to handle group/kinds that 89 // are persisted by the server this admission controller is intercepting 90 func NewResourceQuota(config *resourcequotaapi.Configuration, numEvaluators int) (*QuotaAdmission, error) { 91 quotaAccessor, err := newQuotaAccessor() 92 if err != nil { 93 return nil, err 94 } 95 96 return &QuotaAdmission{ 97 Handler: admission.NewHandler(admission.Create, admission.Update), 98 stopCh: nil, 99 numEvaluators: numEvaluators, 100 config: config, 101 quotaAccessor: quotaAccessor, 102 }, nil 103 } 104 105 // SetDrainedNotification sets the stop channel into QuotaAdmission. 106 func (a *QuotaAdmission) SetDrainedNotification(stopCh <-chan struct{}) { 107 a.stopCh = stopCh 108 } 109 110 // SetExternalKubeClientSet registers the client into QuotaAdmission 111 func (a *QuotaAdmission) SetExternalKubeClientSet(client kubernetes.Interface) { 112 a.quotaAccessor.client = client 113 } 114 115 // SetExternalKubeInformerFactory registers an informer factory into QuotaAdmission 116 func (a *QuotaAdmission) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) { 117 a.quotaAccessor.lister = f.Core().V1().ResourceQuotas().Lister() 118 } 119 120 // SetQuotaConfiguration assigns and initializes configuration and evaluator for QuotaAdmission 121 func (a *QuotaAdmission) SetQuotaConfiguration(c quota.Configuration) { 122 a.quotaConfiguration = c 123 if a.stopCh == nil { 124 a.initializationErr = stopChUnconfiguredErr 125 return 126 } 127 a.evaluator = NewQuotaEvaluator(a.quotaAccessor, a.quotaConfiguration.IgnoredResources(), generic.NewRegistry(a.quotaConfiguration.Evaluators()), nil, a.config, a.numEvaluators, a.stopCh) 128 } 129 130 // ValidateInitialization ensures an authorizer is set. 131 func (a *QuotaAdmission) ValidateInitialization() error { 132 if a.initializationErr != nil { 133 return a.initializationErr 134 } 135 if a.stopCh == nil { 136 return fmt.Errorf("missing stopCh") 137 } 138 if a.quotaAccessor == nil { 139 return fmt.Errorf("missing quotaAccessor") 140 } 141 if a.quotaAccessor.client == nil { 142 return fmt.Errorf("missing quotaAccessor.client") 143 } 144 if a.quotaAccessor.lister == nil { 145 return fmt.Errorf("missing quotaAccessor.lister") 146 } 147 if a.quotaConfiguration == nil { 148 return fmt.Errorf("missing quotaConfiguration") 149 } 150 if a.evaluator == nil { 151 return fmt.Errorf("missing evaluator") 152 } 153 return nil 154 } 155 156 // Validate makes admission decisions while enforcing quota 157 func (a *QuotaAdmission) Validate(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) (err error) { 158 // ignore all operations that correspond to sub-resource actions 159 if attr.GetSubresource() != "" { 160 return nil 161 } 162 // ignore all operations that are not namespaced or creation of namespaces 163 if attr.GetNamespace() == "" || isNamespaceCreation(attr) { 164 return nil 165 } 166 return a.evaluator.Evaluate(attr) 167 } 168 169 func isNamespaceCreation(attr admission.Attributes) bool { 170 return attr.GetOperation() == admission.Create && attr.GetKind().GroupKind() == namespaceGVK 171 }