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  }