mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #54320 from derekwaynecarr/quota-update
Automatic merge from submit-queue (batch tested with PRs 54331, 54655, 54320, 54639, 54288). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Ability to do object count quota for all namespaced resources **What this PR does / why we need it**: - Defines syntax for generic object count quota `count/<resource>.<group>` - Migrates existing objects to support new syntax with old syntax - Adds support to quota all standard namespace resources - Updates the controller to do discovery and replenishment on those resources - Updates unit tests - Tweaks admission configuration around quota - Add e2e test for replicasets (demonstrate dynamic generic counting) ``` $ kubectl create quota test --hard=count/deployments.extensions=2,count/replicasets.extensions=4,count/pods=3,count/secrets=4 resourcequota "test" created $ kubectl run nginx --image=nginx --replicas=2 $ kubectl describe quota Name: test Namespace: default Resource Used Hard -------- ---- ---- count/deployments.extensions 1 2 count/pods 2 3 count/replicasets.extensions 1 4 count/secrets 1 4 ``` **Special notes for your reviewer**: - simple object count quotas no longer require writing code - deferring support for custom resources pending investigation about how to share caches with garbage collector. in addition, i would like to see how this integrates with downstream quota usage in openshift. **Release note**: ```release-note Object count quotas supported on all standard resources using `count/<resource>.<group>` syntax ```
This commit is contained in:
		@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@ go_library(
 | 
			
		||||
    importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app",
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//cmd/kube-controller-manager/app/options:go_default_library",
 | 
			
		||||
        "//pkg/api:go_default_library",
 | 
			
		||||
        "//pkg/api/install:go_default_library",
 | 
			
		||||
        "//pkg/api/legacyscheme:go_default_library",
 | 
			
		||||
        "//pkg/apis/apps/install:go_default_library",
 | 
			
		||||
@@ -78,6 +77,7 @@ go_library(
 | 
			
		||||
        "//pkg/controller/volume/expand:go_default_library",
 | 
			
		||||
        "//pkg/controller/volume/persistentvolume:go_default_library",
 | 
			
		||||
        "//pkg/features:go_default_library",
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//pkg/quota/install:go_default_library",
 | 
			
		||||
        "//pkg/serviceaccount:go_default_library",
 | 
			
		||||
        "//pkg/util/configz:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,6 @@ import (
 | 
			
		||||
	cacheddiscovery "k8s.io/client-go/discovery/cached"
 | 
			
		||||
	"k8s.io/client-go/dynamic"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/legacyscheme"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
	endpointcontroller "k8s.io/kubernetes/pkg/controller/endpoint"
 | 
			
		||||
@@ -55,6 +54,7 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller/volume/expand"
 | 
			
		||||
	persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/features"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
	quotainstall "k8s.io/kubernetes/pkg/quota/install"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/metrics"
 | 
			
		||||
)
 | 
			
		||||
@@ -240,31 +240,34 @@ func startPodGCController(ctx ControllerContext) (bool, error) {
 | 
			
		||||
 | 
			
		||||
func startResourceQuotaController(ctx ControllerContext) (bool, error) {
 | 
			
		||||
	resourceQuotaControllerClient := ctx.ClientBuilder.ClientOrDie("resourcequota-controller")
 | 
			
		||||
	resourceQuotaRegistry := quotainstall.NewRegistry(resourceQuotaControllerClient, ctx.InformerFactory)
 | 
			
		||||
	groupKindsToReplenish := []schema.GroupKind{
 | 
			
		||||
		api.Kind("Pod"),
 | 
			
		||||
		api.Kind("Service"),
 | 
			
		||||
		api.Kind("ReplicationController"),
 | 
			
		||||
		api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
		api.Kind("Secret"),
 | 
			
		||||
		api.Kind("ConfigMap"),
 | 
			
		||||
	}
 | 
			
		||||
	discoveryFunc := resourceQuotaControllerClient.Discovery().ServerPreferredNamespacedResources
 | 
			
		||||
	listerFuncForResource := generic.ListerFuncForResourceFunc(ctx.InformerFactory.ForResource)
 | 
			
		||||
	quotaConfiguration := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)
 | 
			
		||||
 | 
			
		||||
	resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:               resourceQuotaControllerClient.CoreV1(),
 | 
			
		||||
		ResourceQuotaInformer:     ctx.InformerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:              controller.StaticResyncPeriodFunc(ctx.Options.ResourceQuotaSyncPeriod.Duration),
 | 
			
		||||
		Registry:                  resourceQuotaRegistry,
 | 
			
		||||
		ControllerFactory:         resourcequotacontroller.NewReplenishmentControllerFactory(ctx.InformerFactory),
 | 
			
		||||
		InformerFactory:           ctx.InformerFactory,
 | 
			
		||||
		ReplenishmentResyncPeriod: ResyncPeriod(&ctx.Options),
 | 
			
		||||
		GroupKindsToReplenish:     groupKindsToReplenish,
 | 
			
		||||
		DiscoveryFunc:             discoveryFunc,
 | 
			
		||||
		IgnoredResourcesFunc:      quotaConfiguration.IgnoredResources,
 | 
			
		||||
		InformersStarted:          ctx.InformersStarted,
 | 
			
		||||
		Registry:                  generic.NewRegistry(quotaConfiguration.Evaluators()),
 | 
			
		||||
	}
 | 
			
		||||
	if resourceQuotaControllerClient.CoreV1().RESTClient().GetRateLimiter() != nil {
 | 
			
		||||
		metrics.RegisterMetricAndTrackRateLimiterUsage("resource_quota_controller", resourceQuotaControllerClient.CoreV1().RESTClient().GetRateLimiter())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go resourcequotacontroller.NewResourceQuotaController(
 | 
			
		||||
		resourceQuotaControllerOptions,
 | 
			
		||||
	).Run(int(ctx.Options.ConcurrentResourceQuotaSyncs), ctx.Stop)
 | 
			
		||||
	resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	go resourceQuotaController.Run(int(ctx.Options.ConcurrentResourceQuotaSyncs), ctx.Stop)
 | 
			
		||||
 | 
			
		||||
	// Periodically the quota controller to detect new resource types
 | 
			
		||||
	go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, ctx.Stop)
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -235,7 +235,6 @@ pkg/proxy/util
 | 
			
		||||
pkg/proxy/winkernel
 | 
			
		||||
pkg/proxy/winuserspace
 | 
			
		||||
pkg/quota/evaluator/core
 | 
			
		||||
pkg/quota/generic
 | 
			
		||||
pkg/registry/admissionregistration/externaladmissionhookconfiguration/storage
 | 
			
		||||
pkg/registry/admissionregistration/initializerconfiguration/storage
 | 
			
		||||
pkg/registry/admissionregistration/rest
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@ go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "doc.go",
 | 
			
		||||
        "replenishment_controller.go",
 | 
			
		||||
        "resource_quota_controller.go",
 | 
			
		||||
        "resource_quota_monitor.go",
 | 
			
		||||
    ],
 | 
			
		||||
    importpath = "k8s.io/kubernetes/pkg/controller/resourcequota",
 | 
			
		||||
    deps = [
 | 
			
		||||
@@ -20,6 +20,7 @@ go_library(
 | 
			
		||||
        "//pkg/controller:go_default_library",
 | 
			
		||||
        "//pkg/quota:go_default_library",
 | 
			
		||||
        "//pkg/quota/evaluator/core:go_default_library",
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//vendor/github.com/golang/glog:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
 | 
			
		||||
@@ -27,11 +28,12 @@ go_library(
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/discovery:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
 | 
			
		||||
@@ -43,15 +45,12 @@ go_library(
 | 
			
		||||
 | 
			
		||||
go_test(
 | 
			
		||||
    name = "go_default_test",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "replenishment_controller_test.go",
 | 
			
		||||
        "resource_quota_controller_test.go",
 | 
			
		||||
    ],
 | 
			
		||||
    srcs = ["resource_quota_controller_test.go"],
 | 
			
		||||
    importpath = "k8s.io/kubernetes/pkg/controller/resourcequota",
 | 
			
		||||
    library = ":go_default_library",
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//pkg/api: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",
 | 
			
		||||
        "//vendor/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
@@ -59,12 +58,12 @@ go_test(
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/testing:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/tools/cache:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,233 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package resourcequota
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/meta"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/evaluator/core"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReplenishmentFunc is a function that is invoked when controller sees a change
 | 
			
		||||
// that may require a quota to be replenished (i.e. object deletion, or object moved to terminal state)
 | 
			
		||||
type ReplenishmentFunc func(groupKind schema.GroupKind, namespace string, object runtime.Object)
 | 
			
		||||
 | 
			
		||||
// ReplenishmentControllerOptions is an options struct that tells a factory
 | 
			
		||||
// how to configure a controller that can inform the quota system it should
 | 
			
		||||
// replenish quota
 | 
			
		||||
type ReplenishmentControllerOptions struct {
 | 
			
		||||
	// The kind monitored for replenishment
 | 
			
		||||
	GroupKind schema.GroupKind
 | 
			
		||||
	// The period that should be used to re-sync the monitored resource
 | 
			
		||||
	ResyncPeriod controller.ResyncPeriodFunc
 | 
			
		||||
	// The function to invoke when a change is observed that should trigger
 | 
			
		||||
	// replenishment
 | 
			
		||||
	ReplenishmentFunc ReplenishmentFunc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PodReplenishmentUpdateFunc will replenish if the old pod was quota tracked but the new is not
 | 
			
		||||
func PodReplenishmentUpdateFunc(options *ReplenishmentControllerOptions, clock clock.Clock) func(oldObj, newObj interface{}) {
 | 
			
		||||
	return func(oldObj, newObj interface{}) {
 | 
			
		||||
		oldPod := oldObj.(*v1.Pod)
 | 
			
		||||
		newPod := newObj.(*v1.Pod)
 | 
			
		||||
		if core.QuotaV1Pod(oldPod, clock) && !core.QuotaV1Pod(newPod, clock) {
 | 
			
		||||
			options.ReplenishmentFunc(options.GroupKind, newPod.Namespace, oldPod)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ObjectReplenishmentDeleteFunc will replenish on every delete
 | 
			
		||||
func ObjectReplenishmentDeleteFunc(options *ReplenishmentControllerOptions) func(obj interface{}) {
 | 
			
		||||
	return func(obj interface{}) {
 | 
			
		||||
		metaObject, err := meta.Accessor(obj)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				glog.Errorf("replenishment controller could not get object from tombstone %+v, could take up to %v before quota is replenished", obj, options.ResyncPeriod())
 | 
			
		||||
				utilruntime.HandleError(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			metaObject, err = meta.Accessor(tombstone.Obj)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				glog.Errorf("replenishment controller tombstone contained object that is not a meta %+v, could take up to %v before quota is replenished", tombstone.Obj, options.ResyncPeriod())
 | 
			
		||||
				utilruntime.HandleError(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		options.ReplenishmentFunc(options.GroupKind, metaObject.GetNamespace(), nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplenishmentControllerFactory knows how to build replenishment controllers
 | 
			
		||||
type ReplenishmentControllerFactory interface {
 | 
			
		||||
	// NewController returns a controller configured with the specified options.
 | 
			
		||||
	// This method is NOT thread-safe.
 | 
			
		||||
	NewController(options *ReplenishmentControllerOptions) (cache.Controller, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// replenishmentControllerFactory implements ReplenishmentControllerFactory
 | 
			
		||||
type replenishmentControllerFactory struct {
 | 
			
		||||
	sharedInformerFactory informers.SharedInformerFactory
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewReplenishmentControllerFactory returns a factory that knows how to build controllers
 | 
			
		||||
// to replenish resources when updated or deleted
 | 
			
		||||
func NewReplenishmentControllerFactory(f informers.SharedInformerFactory) ReplenishmentControllerFactory {
 | 
			
		||||
	return &replenishmentControllerFactory{
 | 
			
		||||
		sharedInformerFactory: f,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *replenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (cache.Controller, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		informer informers.GenericInformer
 | 
			
		||||
		err      error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	switch options.GroupKind {
 | 
			
		||||
	case api.Kind("Pod"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("pods"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		clock := clock.RealClock{}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				UpdateFunc: PodReplenishmentUpdateFunc(options, clock),
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	case api.Kind("Service"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("services"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				UpdateFunc: ServiceReplenishmentUpdateFunc(options),
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	case api.Kind("ReplicationController"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("replicationcontrollers"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	case api.Kind("PersistentVolumeClaim"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("persistentvolumeclaims"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	case api.Kind("Secret"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("secrets"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	case api.Kind("ConfigMap"):
 | 
			
		||||
		informer, err = r.sharedInformerFactory.ForResource(v1.SchemeGroupVersion.WithResource("configmaps"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer.Informer().AddEventHandlerWithResyncPeriod(
 | 
			
		||||
			cache.ResourceEventHandlerFuncs{
 | 
			
		||||
				DeleteFunc: ObjectReplenishmentDeleteFunc(options),
 | 
			
		||||
			},
 | 
			
		||||
			options.ResyncPeriod(),
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, NewUnhandledGroupKindError(options.GroupKind)
 | 
			
		||||
	}
 | 
			
		||||
	return informer.Informer().GetController(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServiceReplenishmentUpdateFunc will replenish if the service was quota tracked has changed service type
 | 
			
		||||
func ServiceReplenishmentUpdateFunc(options *ReplenishmentControllerOptions) func(oldObj, newObj interface{}) {
 | 
			
		||||
	return func(oldObj, newObj interface{}) {
 | 
			
		||||
		oldService := oldObj.(*v1.Service)
 | 
			
		||||
		newService := newObj.(*v1.Service)
 | 
			
		||||
		if core.GetQuotaServiceType(oldService) != core.GetQuotaServiceType(newService) {
 | 
			
		||||
			options.ReplenishmentFunc(options.GroupKind, newService.Namespace, nil)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type unhandledKindErr struct {
 | 
			
		||||
	kind schema.GroupKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e unhandledKindErr) Error() string {
 | 
			
		||||
	return fmt.Sprintf("no replenishment controller available for %s", e.kind)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewUnhandledGroupKindError(kind schema.GroupKind) error {
 | 
			
		||||
	return unhandledKindErr{kind: kind}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsUnhandledGroupKindError(err error) bool {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	_, ok := err.(unhandledKindErr)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnionReplenishmentControllerFactory iterates through its constituent factories ignoring, UnhandledGroupKindErrors
 | 
			
		||||
// returning the first success or failure it hits.  If there are no hits either way, it return an UnhandledGroupKind error
 | 
			
		||||
type UnionReplenishmentControllerFactory []ReplenishmentControllerFactory
 | 
			
		||||
 | 
			
		||||
func (f UnionReplenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (cache.Controller, error) {
 | 
			
		||||
	for _, factory := range f {
 | 
			
		||||
		controller, err := factory.NewController(options)
 | 
			
		||||
		if !IsUnhandledGroupKindError(err) {
 | 
			
		||||
			return controller, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, NewUnhandledGroupKindError(options.GroupKind)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,160 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package resourcequota
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/intstr"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// testReplenishment lets us test replenishment functions are invoked
 | 
			
		||||
type testReplenishment struct {
 | 
			
		||||
	groupKind schema.GroupKind
 | 
			
		||||
	namespace string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// mock function that holds onto the last kind that was replenished
 | 
			
		||||
func (t *testReplenishment) Replenish(groupKind schema.GroupKind, namespace string, object runtime.Object) {
 | 
			
		||||
	t.groupKind = groupKind
 | 
			
		||||
	t.namespace = namespace
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPodReplenishmentUpdateFunc(t *testing.T) {
 | 
			
		||||
	mockReplenish := &testReplenishment{}
 | 
			
		||||
	options := ReplenishmentControllerOptions{
 | 
			
		||||
		GroupKind:         api.Kind("Pod"),
 | 
			
		||||
		ReplenishmentFunc: mockReplenish.Replenish,
 | 
			
		||||
		ResyncPeriod:      controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	oldPod := &v1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"},
 | 
			
		||||
		Status:     v1.PodStatus{Phase: v1.PodRunning},
 | 
			
		||||
	}
 | 
			
		||||
	newPod := &v1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"},
 | 
			
		||||
		Status:     v1.PodStatus{Phase: v1.PodFailed},
 | 
			
		||||
	}
 | 
			
		||||
	fakeClock := clock.NewFakeClock(time.Now())
 | 
			
		||||
	updateFunc := PodReplenishmentUpdateFunc(&options, fakeClock)
 | 
			
		||||
	updateFunc(oldPod, newPod)
 | 
			
		||||
	if mockReplenish.groupKind != api.Kind("Pod") {
 | 
			
		||||
		t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
 | 
			
		||||
	}
 | 
			
		||||
	if mockReplenish.namespace != oldPod.Namespace {
 | 
			
		||||
		t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestObjectReplenishmentDeleteFunc(t *testing.T) {
 | 
			
		||||
	mockReplenish := &testReplenishment{}
 | 
			
		||||
	options := ReplenishmentControllerOptions{
 | 
			
		||||
		GroupKind:         api.Kind("Pod"),
 | 
			
		||||
		ReplenishmentFunc: mockReplenish.Replenish,
 | 
			
		||||
		ResyncPeriod:      controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	oldPod := &v1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "pod"},
 | 
			
		||||
		Status:     v1.PodStatus{Phase: v1.PodRunning},
 | 
			
		||||
	}
 | 
			
		||||
	deleteFunc := ObjectReplenishmentDeleteFunc(&options)
 | 
			
		||||
	deleteFunc(oldPod)
 | 
			
		||||
	if mockReplenish.groupKind != api.Kind("Pod") {
 | 
			
		||||
		t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
 | 
			
		||||
	}
 | 
			
		||||
	if mockReplenish.namespace != oldPod.Namespace {
 | 
			
		||||
		t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServiceReplenishmentUpdateFunc(t *testing.T) {
 | 
			
		||||
	mockReplenish := &testReplenishment{}
 | 
			
		||||
	options := ReplenishmentControllerOptions{
 | 
			
		||||
		GroupKind:         api.Kind("Service"),
 | 
			
		||||
		ReplenishmentFunc: mockReplenish.Replenish,
 | 
			
		||||
		ResyncPeriod:      controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	oldService := &v1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"},
 | 
			
		||||
		Spec: v1.ServiceSpec{
 | 
			
		||||
			Type: v1.ServiceTypeNodePort,
 | 
			
		||||
			Ports: []v1.ServicePort{{
 | 
			
		||||
				Port:       80,
 | 
			
		||||
				TargetPort: intstr.FromInt(80),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	newService := &v1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"},
 | 
			
		||||
		Spec: v1.ServiceSpec{
 | 
			
		||||
			Type: v1.ServiceTypeClusterIP,
 | 
			
		||||
			Ports: []v1.ServicePort{{
 | 
			
		||||
				Port:       80,
 | 
			
		||||
				TargetPort: intstr.FromInt(80),
 | 
			
		||||
			}}},
 | 
			
		||||
	}
 | 
			
		||||
	updateFunc := ServiceReplenishmentUpdateFunc(&options)
 | 
			
		||||
	updateFunc(oldService, newService)
 | 
			
		||||
	if mockReplenish.groupKind != api.Kind("Service") {
 | 
			
		||||
		t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
 | 
			
		||||
	}
 | 
			
		||||
	if mockReplenish.namespace != oldService.Namespace {
 | 
			
		||||
		t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mockReplenish = &testReplenishment{}
 | 
			
		||||
	options = ReplenishmentControllerOptions{
 | 
			
		||||
		GroupKind:         api.Kind("Service"),
 | 
			
		||||
		ReplenishmentFunc: mockReplenish.Replenish,
 | 
			
		||||
		ResyncPeriod:      controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	oldService = &v1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"},
 | 
			
		||||
		Spec: v1.ServiceSpec{
 | 
			
		||||
			Type: v1.ServiceTypeNodePort,
 | 
			
		||||
			Ports: []v1.ServicePort{{
 | 
			
		||||
				Port:       80,
 | 
			
		||||
				TargetPort: intstr.FromInt(80),
 | 
			
		||||
			}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	newService = &v1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "mysvc"},
 | 
			
		||||
		Spec: v1.ServiceSpec{
 | 
			
		||||
			Type: v1.ServiceTypeNodePort,
 | 
			
		||||
			Ports: []v1.ServicePort{{
 | 
			
		||||
				Port:       81,
 | 
			
		||||
				TargetPort: intstr.FromInt(81),
 | 
			
		||||
			}}},
 | 
			
		||||
	}
 | 
			
		||||
	updateFunc = ServiceReplenishmentUpdateFunc(&options)
 | 
			
		||||
	updateFunc(oldService, newService)
 | 
			
		||||
	if mockReplenish.groupKind == api.Kind("Service") {
 | 
			
		||||
		t.Errorf("Unexpected group kind %v", mockReplenish.groupKind)
 | 
			
		||||
	}
 | 
			
		||||
	if mockReplenish.namespace == oldService.Namespace {
 | 
			
		||||
		t.Errorf("Unexpected namespace %v", mockReplenish.namespace)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -18,6 +18,8 @@ package resourcequota
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
@@ -27,10 +29,11 @@ import (
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/labels"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
			
		||||
	"k8s.io/client-go/discovery"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	coreinformers "k8s.io/client-go/informers/core/v1"
 | 
			
		||||
	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
			
		||||
	corelisters "k8s.io/client-go/listers/core/v1"
 | 
			
		||||
@@ -42,6 +45,19 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NamespacedResourcesFunc knows how to discover namespaced resources.
 | 
			
		||||
type NamespacedResourcesFunc func() ([]*metav1.APIResourceList, error)
 | 
			
		||||
 | 
			
		||||
// ReplenishmentFunc is a signal that a resource changed in specified namespace
 | 
			
		||||
// that may require quota to be recalculated.
 | 
			
		||||
type ReplenishmentFunc func(groupResource schema.GroupResource, namespace string)
 | 
			
		||||
 | 
			
		||||
// InformerFactory is all the quota system needs to interface with informers.
 | 
			
		||||
type InformerFactory interface {
 | 
			
		||||
	ForResource(resource schema.GroupVersionResource) (informers.GenericInformer, error)
 | 
			
		||||
	Start(stopCh <-chan struct{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResourceQuotaControllerOptions holds options for creating a quota controller
 | 
			
		||||
type ResourceQuotaControllerOptions struct {
 | 
			
		||||
	// Must have authority to list all quotas, and update quota status
 | 
			
		||||
@@ -50,15 +66,18 @@ type ResourceQuotaControllerOptions struct {
 | 
			
		||||
	ResourceQuotaInformer coreinformers.ResourceQuotaInformer
 | 
			
		||||
	// Controls full recalculation of quota usage
 | 
			
		||||
	ResyncPeriod controller.ResyncPeriodFunc
 | 
			
		||||
	// Knows how to calculate usage
 | 
			
		||||
	// Maintains evaluators that know how to calculate usage for group resource
 | 
			
		||||
	Registry quota.Registry
 | 
			
		||||
	// Knows how to build controllers that notify replenishment events
 | 
			
		||||
	ControllerFactory ReplenishmentControllerFactory
 | 
			
		||||
	// Discover list of supported resources on the server.
 | 
			
		||||
	DiscoveryFunc NamespacedResourcesFunc
 | 
			
		||||
	// A function that returns the list of resources to ignore
 | 
			
		||||
	IgnoredResourcesFunc func() map[schema.GroupResource]struct{}
 | 
			
		||||
	// InformersStarted knows if informers were started.
 | 
			
		||||
	InformersStarted <-chan struct{}
 | 
			
		||||
	// InformerFactory interfaces with informers.
 | 
			
		||||
	InformerFactory InformerFactory
 | 
			
		||||
	// Controls full resync of objects monitored for replenishment.
 | 
			
		||||
	ReplenishmentResyncPeriod controller.ResyncPeriodFunc
 | 
			
		||||
	// List of GroupKind objects that should be monitored for replenishment at
 | 
			
		||||
	// a faster frequency than the quota controller recalculation interval
 | 
			
		||||
	GroupKindsToReplenish []schema.GroupKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResourceQuotaController is responsible for tracking quota usage status in the system
 | 
			
		||||
@@ -79,9 +98,16 @@ type ResourceQuotaController struct {
 | 
			
		||||
	resyncPeriod controller.ResyncPeriodFunc
 | 
			
		||||
	// knows how to calculate usage
 | 
			
		||||
	registry quota.Registry
 | 
			
		||||
	// knows how to monitor all the resources tracked by quota and trigger replenishment
 | 
			
		||||
	quotaMonitor *QuotaMonitor
 | 
			
		||||
	// controls the workers that process quotas
 | 
			
		||||
	// this lock is acquired to control write access to the monitors and ensures that all
 | 
			
		||||
	// monitors are synced before the controller can process quotas.
 | 
			
		||||
	workerLock sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *ResourceQuotaController {
 | 
			
		||||
// NewResourceQuotaController creates a quota controller with specified options
 | 
			
		||||
func NewResourceQuotaController(options *ResourceQuotaControllerOptions) (*ResourceQuotaController, error) {
 | 
			
		||||
	// build the resource quota controller
 | 
			
		||||
	rq := &ResourceQuotaController{
 | 
			
		||||
		rqClient:            options.QuotaClient,
 | 
			
		||||
@@ -122,21 +148,30 @@ func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *Resour
 | 
			
		||||
		rq.resyncPeriod(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for _, groupKindToReplenish := range options.GroupKindsToReplenish {
 | 
			
		||||
		controllerOptions := &ReplenishmentControllerOptions{
 | 
			
		||||
			GroupKind:         groupKindToReplenish,
 | 
			
		||||
			ResyncPeriod:      options.ReplenishmentResyncPeriod,
 | 
			
		||||
			ReplenishmentFunc: rq.replenishQuota,
 | 
			
		||||
	qm := &QuotaMonitor{
 | 
			
		||||
		informersStarted:  options.InformersStarted,
 | 
			
		||||
		informerFactory:   options.InformerFactory,
 | 
			
		||||
		ignoredResources:  options.IgnoredResourcesFunc(),
 | 
			
		||||
		resourceChanges:   workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resource_quota_controller_resource_changes"),
 | 
			
		||||
		resyncPeriod:      options.ReplenishmentResyncPeriod,
 | 
			
		||||
		replenishmentFunc: rq.replenishQuota,
 | 
			
		||||
		registry:          rq.registry,
 | 
			
		||||
	}
 | 
			
		||||
		replenishmentController, err := options.ControllerFactory.NewController(controllerOptions)
 | 
			
		||||
	rq.quotaMonitor = qm
 | 
			
		||||
 | 
			
		||||
	// do initial quota monitor setup
 | 
			
		||||
	resources, err := GetQuotableResources(options.DiscoveryFunc)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
			glog.Warningf("quota controller unable to replenish %s due to %v, changes only accounted during full resync", groupKindToReplenish, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			// make sure we wait for each shared informer's cache to sync
 | 
			
		||||
			rq.informerSyncedFuncs = append(rq.informerSyncedFuncs, replenishmentController.HasSynced)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err = qm.syncMonitors(resources); err != nil {
 | 
			
		||||
		utilruntime.HandleError(fmt.Errorf("initial monitor sync has error: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	return rq
 | 
			
		||||
 | 
			
		||||
	// only start quota once all informers synced
 | 
			
		||||
	rq.informerSyncedFuncs = append(rq.informerSyncedFuncs, qm.IsSynced)
 | 
			
		||||
 | 
			
		||||
	return rq, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// enqueueAll is called at the fullResyncPeriod interval to force a full recalculation of quota usage statistics
 | 
			
		||||
@@ -186,7 +221,7 @@ func (rq *ResourceQuotaController) addQuota(obj interface{}) {
 | 
			
		||||
	for constraint := range resourceQuota.Status.Hard {
 | 
			
		||||
		if _, usageFound := resourceQuota.Status.Used[constraint]; !usageFound {
 | 
			
		||||
			matchedResources := []api.ResourceName{api.ResourceName(constraint)}
 | 
			
		||||
			for _, evaluator := range rq.registry.Evaluators() {
 | 
			
		||||
			for _, evaluator := range rq.registry.List() {
 | 
			
		||||
				if intersection := evaluator.MatchingResources(matchedResources); len(intersection) > 0 {
 | 
			
		||||
					rq.missingUsageQueue.Add(key)
 | 
			
		||||
					return
 | 
			
		||||
@@ -202,6 +237,10 @@ func (rq *ResourceQuotaController) addQuota(obj interface{}) {
 | 
			
		||||
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
 | 
			
		||||
func (rq *ResourceQuotaController) worker(queue workqueue.RateLimitingInterface) func() {
 | 
			
		||||
	workFunc := func() bool {
 | 
			
		||||
 | 
			
		||||
		rq.workerLock.RLock()
 | 
			
		||||
		defer rq.workerLock.RUnlock()
 | 
			
		||||
 | 
			
		||||
		key, quit := queue.Get()
 | 
			
		||||
		if quit {
 | 
			
		||||
			return true
 | 
			
		||||
@@ -235,6 +274,8 @@ func (rq *ResourceQuotaController) Run(workers int, stopCh <-chan struct{}) {
 | 
			
		||||
	glog.Infof("Starting resource quota controller")
 | 
			
		||||
	defer glog.Infof("Shutting down resource quota controller")
 | 
			
		||||
 | 
			
		||||
	go rq.quotaMonitor.Run(stopCh)
 | 
			
		||||
 | 
			
		||||
	if !controller.WaitForCacheSync("resource quota", stopCh, rq.informerSyncedFuncs...) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -336,11 +377,10 @@ func (rq *ResourceQuotaController) syncResourceQuota(v1ResourceQuota *v1.Resourc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// replenishQuota is a replenishment function invoked by a controller to notify that a quota should be recalculated
 | 
			
		||||
func (rq *ResourceQuotaController) replenishQuota(groupKind schema.GroupKind, namespace string, object runtime.Object) {
 | 
			
		||||
	// check if the quota controller can evaluate this kind, if not, ignore it altogether...
 | 
			
		||||
	evaluators := rq.registry.Evaluators()
 | 
			
		||||
	evaluator, found := evaluators[groupKind]
 | 
			
		||||
	if !found {
 | 
			
		||||
func (rq *ResourceQuotaController) replenishQuota(groupResource schema.GroupResource, namespace string) {
 | 
			
		||||
	// check if the quota controller can evaluate this groupResource, if not, ignore it altogether...
 | 
			
		||||
	evaluator := rq.registry.Get(groupResource)
 | 
			
		||||
	if evaluator == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -373,3 +413,66 @@ func (rq *ResourceQuotaController) replenishQuota(groupKind schema.GroupKind, na
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sync periodically resyncs the controller when new resources are observed from discovery.
 | 
			
		||||
func (rq *ResourceQuotaController) Sync(discoveryFunc NamespacedResourcesFunc, period time.Duration, stopCh <-chan struct{}) {
 | 
			
		||||
	// Something has changed, so track the new state and perform a sync.
 | 
			
		||||
	oldResources := make(map[schema.GroupVersionResource]struct{})
 | 
			
		||||
	wait.Until(func() {
 | 
			
		||||
		// Get the current resource list from discovery.
 | 
			
		||||
		newResources, err := GetQuotableResources(discoveryFunc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			utilruntime.HandleError(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Decide whether discovery has reported a change.
 | 
			
		||||
		if reflect.DeepEqual(oldResources, newResources) {
 | 
			
		||||
			glog.V(4).Infof("no resource updates from discovery, skipping resource quota sync")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Something has changed, so track the new state and perform a sync.
 | 
			
		||||
		glog.V(2).Infof("syncing resource quota controller with updated resources from discovery: %v", newResources)
 | 
			
		||||
		oldResources = newResources
 | 
			
		||||
 | 
			
		||||
		// Ensure workers are paused to avoid processing events before informers
 | 
			
		||||
		// have resynced.
 | 
			
		||||
		rq.workerLock.Lock()
 | 
			
		||||
		defer rq.workerLock.Unlock()
 | 
			
		||||
 | 
			
		||||
		// Perform the monitor resync and wait for controllers to report cache sync.
 | 
			
		||||
		if err := rq.resyncMonitors(newResources); err != nil {
 | 
			
		||||
			utilruntime.HandleError(fmt.Errorf("failed to sync resource monitors: %v", err))
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !controller.WaitForCacheSync("resource quota", stopCh, rq.quotaMonitor.IsSynced) {
 | 
			
		||||
			utilruntime.HandleError(fmt.Errorf("timed out waiting for quota monitor sync"))
 | 
			
		||||
		}
 | 
			
		||||
	}, period, stopCh)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resyncMonitors starts or stops quota monitors as needed to ensure that all
 | 
			
		||||
// (and only) those resources present in the map are monitored.
 | 
			
		||||
func (rq *ResourceQuotaController) resyncMonitors(resources map[schema.GroupVersionResource]struct{}) error {
 | 
			
		||||
	if err := rq.quotaMonitor.syncMonitors(resources); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	rq.quotaMonitor.startMonitors()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetQuotableResources returns all resources that the quota system should recognize.
 | 
			
		||||
// It requires a resource supports the following verbs: 'create','list','delete'
 | 
			
		||||
func GetQuotableResources(discoveryFunc NamespacedResourcesFunc) (map[schema.GroupVersionResource]struct{}, error) {
 | 
			
		||||
	possibleResources, err := discoveryFunc()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to discover resources: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	quotableResources := discovery.FilteredBy(discovery.SupportsAllVerbs{Verbs: []string{"create", "list", "delete"}}, possibleResources)
 | 
			
		||||
	quotableGroupVersionResources, err := discovery.GroupVersionResources(quotableResources)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse resources: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return quotableGroupVersionResources, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,19 +17,23 @@ limitations under the License.
 | 
			
		||||
package resourcequota
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	"k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/client-go/kubernetes/fake"
 | 
			
		||||
	core "k8s.io/client-go/testing"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/install"
 | 
			
		||||
)
 | 
			
		||||
@@ -52,10 +56,60 @@ func getResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequir
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
	podList := v1.PodList{
 | 
			
		||||
		Items: []v1.Pod{
 | 
			
		||||
			{
 | 
			
		||||
func mockDiscoveryFunc() ([]*metav1.APIResourceList, error) {
 | 
			
		||||
	return []*metav1.APIResourceList{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mockListerForResourceFunc(listersForResource map[schema.GroupVersionResource]cache.GenericLister) quota.ListerForResourceFunc {
 | 
			
		||||
	return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) {
 | 
			
		||||
		lister, found := listersForResource[gvr]
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("no lister found for resource")
 | 
			
		||||
		}
 | 
			
		||||
		return lister, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newGenericLister(groupResource schema.GroupResource, items []runtime.Object) cache.GenericLister {
 | 
			
		||||
	store := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc})
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		store.Add(item)
 | 
			
		||||
	}
 | 
			
		||||
	return cache.NewGenericLister(store, groupResource)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type quotaController struct {
 | 
			
		||||
	*ResourceQuotaController
 | 
			
		||||
	stop chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupQuotaController(t *testing.T, kubeClient kubernetes.Interface, lister quota.ListerForResourceFunc) quotaController {
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForControllers(lister)
 | 
			
		||||
	alwaysStarted := make(chan struct{})
 | 
			
		||||
	close(alwaysStarted)
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:               kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer:     informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:              controller.NoResyncPeriodFunc,
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
		IgnoredResourcesFunc:      quotaConfiguration.IgnoredResources,
 | 
			
		||||
		DiscoveryFunc:             mockDiscoveryFunc,
 | 
			
		||||
		Registry:                  generic.NewRegistry(quotaConfiguration.Evaluators()),
 | 
			
		||||
		InformersStarted:          alwaysStarted,
 | 
			
		||||
	}
 | 
			
		||||
	qc, err := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	stop := make(chan struct{})
 | 
			
		||||
	go informerFactory.Start(stop)
 | 
			
		||||
	return quotaController{qc, stop}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTestPods() []runtime.Object {
 | 
			
		||||
	return []runtime.Object{
 | 
			
		||||
		&v1.Pod{
 | 
			
		||||
			ObjectMeta: metav1.ObjectMeta{Name: "pod-running", Namespace: "testing"},
 | 
			
		||||
			Status:     v1.PodStatus{Phase: v1.PodRunning},
 | 
			
		||||
			Spec: v1.PodSpec{
 | 
			
		||||
@@ -63,7 +117,7 @@ func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
				Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
			{
 | 
			
		||||
		&v1.Pod{
 | 
			
		||||
			ObjectMeta: metav1.ObjectMeta{Name: "pod-running-2", Namespace: "testing"},
 | 
			
		||||
			Status:     v1.PodStatus{Phase: v1.PodRunning},
 | 
			
		||||
			Spec: v1.PodSpec{
 | 
			
		||||
@@ -71,7 +125,7 @@ func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
				Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
			{
 | 
			
		||||
		&v1.Pod{
 | 
			
		||||
			ObjectMeta: metav1.ObjectMeta{Name: "pod-failed", Namespace: "testing"},
 | 
			
		||||
			Status:     v1.PodStatus{Phase: v1.PodFailed},
 | 
			
		||||
			Spec: v1.PodSpec{
 | 
			
		||||
@@ -79,9 +133,20 @@ func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
				Containers: []v1.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	resourceQuota := v1.ResourceQuota{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		gvr               schema.GroupVersionResource
 | 
			
		||||
		items             []runtime.Object
 | 
			
		||||
		quota             v1.ResourceQuota
 | 
			
		||||
		status            v1.ResourceQuotaStatus
 | 
			
		||||
		expectedActionSet sets.String
 | 
			
		||||
	}{
 | 
			
		||||
		"pods": {
 | 
			
		||||
			gvr: v1.SchemeGroupVersion.WithResource("pods"),
 | 
			
		||||
			quota: v1.ResourceQuota{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{Name: "quota", Namespace: "testing"},
 | 
			
		||||
				Spec: v1.ResourceQuotaSpec{
 | 
			
		||||
					Hard: v1.ResourceList{
 | 
			
		||||
@@ -90,9 +155,8 @@ func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
						v1.ResourcePods:   resource.MustParse("5"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
	}
 | 
			
		||||
	expectedUsage := v1.ResourceQuota{
 | 
			
		||||
		Status: v1.ResourceQuotaStatus{
 | 
			
		||||
			},
 | 
			
		||||
			status: v1.ResourceQuotaStatus{
 | 
			
		||||
				Hard: v1.ResourceList{
 | 
			
		||||
					v1.ResourceCPU:    resource.MustParse("3"),
 | 
			
		||||
					v1.ResourceMemory: resource.MustParse("100Gi"),
 | 
			
		||||
@@ -104,65 +168,14 @@ func TestSyncResourceQuota(t *testing.T) {
 | 
			
		||||
					v1.ResourcePods:   resource.MustParse("2"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset(&podList, &resourceQuota)
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:           kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:          controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:              install.NewRegistry(kubeClient, nil),
 | 
			
		||||
		GroupKindsToReplenish: []schema.GroupKind{
 | 
			
		||||
			api.Kind("Pod"),
 | 
			
		||||
			api.Kind("Service"),
 | 
			
		||||
			api.Kind("ReplicationController"),
 | 
			
		||||
			api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
		},
 | 
			
		||||
		ControllerFactory:         NewReplenishmentControllerFactory(informerFactory),
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	quotaController := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	err := quotaController.syncResourceQuota(&resourceQuota)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	expectedActionSet := sets.NewString(
 | 
			
		||||
		strings.Join([]string{"list", "pods", ""}, "-"),
 | 
			
		||||
			expectedActionSet: sets.NewString(
 | 
			
		||||
				strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
 | 
			
		||||
	)
 | 
			
		||||
	actionSet := sets.NewString()
 | 
			
		||||
	for _, action := range kubeClient.Actions() {
 | 
			
		||||
		actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
 | 
			
		||||
	}
 | 
			
		||||
	if !actionSet.HasAll(expectedActionSet.List()...) {
 | 
			
		||||
		t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lastActionIndex := len(kubeClient.Actions()) - 1
 | 
			
		||||
	usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota)
 | 
			
		||||
 | 
			
		||||
	// ensure hard and used limits are what we expected
 | 
			
		||||
	for k, v := range expectedUsage.Status.Hard {
 | 
			
		||||
		actual := usage.Status.Hard[k]
 | 
			
		||||
		actualValue := actual.String()
 | 
			
		||||
		expectedValue := v.String()
 | 
			
		||||
		if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range expectedUsage.Status.Used {
 | 
			
		||||
		actual := usage.Status.Used[k]
 | 
			
		||||
		actualValue := actual.String()
 | 
			
		||||
		expectedValue := v.String()
 | 
			
		||||
		if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSyncResourceQuotaSpecChange(t *testing.T) {
 | 
			
		||||
	resourceQuota := v1.ResourceQuota{
 | 
			
		||||
			),
 | 
			
		||||
			items: newTestPods(),
 | 
			
		||||
		},
 | 
			
		||||
		"quota-spec-hard-updated": {
 | 
			
		||||
			gvr: v1.SchemeGroupVersion.WithResource("pods"),
 | 
			
		||||
			quota: v1.ResourceQuota{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Namespace: "default",
 | 
			
		||||
					Name:      "rq",
 | 
			
		||||
@@ -180,10 +193,8 @@ func TestSyncResourceQuotaSpecChange(t *testing.T) {
 | 
			
		||||
						v1.ResourceCPU: resource.MustParse("0"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedUsage := v1.ResourceQuota{
 | 
			
		||||
		Status: v1.ResourceQuotaStatus{
 | 
			
		||||
			},
 | 
			
		||||
			status: v1.ResourceQuotaStatus{
 | 
			
		||||
				Hard: v1.ResourceList{
 | 
			
		||||
					v1.ResourceCPU: resource.MustParse("4"),
 | 
			
		||||
				},
 | 
			
		||||
@@ -191,66 +202,14 @@ func TestSyncResourceQuotaSpecChange(t *testing.T) {
 | 
			
		||||
					v1.ResourceCPU: resource.MustParse("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset(&resourceQuota)
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:           kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:          controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:              install.NewRegistry(kubeClient, nil),
 | 
			
		||||
		GroupKindsToReplenish: []schema.GroupKind{
 | 
			
		||||
			api.Kind("Pod"),
 | 
			
		||||
			api.Kind("Service"),
 | 
			
		||||
			api.Kind("ReplicationController"),
 | 
			
		||||
			api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
		},
 | 
			
		||||
		ControllerFactory:         NewReplenishmentControllerFactory(informerFactory),
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	quotaController := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	err := quotaController.syncResourceQuota(&resourceQuota)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedActionSet := sets.NewString(
 | 
			
		||||
		strings.Join([]string{"list", "pods", ""}, "-"),
 | 
			
		||||
			expectedActionSet: sets.NewString(
 | 
			
		||||
				strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
 | 
			
		||||
	)
 | 
			
		||||
	actionSet := sets.NewString()
 | 
			
		||||
	for _, action := range kubeClient.Actions() {
 | 
			
		||||
		actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
 | 
			
		||||
	}
 | 
			
		||||
	if !actionSet.HasAll(expectedActionSet.List()...) {
 | 
			
		||||
		t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lastActionIndex := len(kubeClient.Actions()) - 1
 | 
			
		||||
	usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota)
 | 
			
		||||
 | 
			
		||||
	// ensure hard and used limits are what we expected
 | 
			
		||||
	for k, v := range expectedUsage.Status.Hard {
 | 
			
		||||
		actual := usage.Status.Hard[k]
 | 
			
		||||
		actualValue := actual.String()
 | 
			
		||||
		expectedValue := v.String()
 | 
			
		||||
		if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range expectedUsage.Status.Used {
 | 
			
		||||
		actual := usage.Status.Used[k]
 | 
			
		||||
		actualValue := actual.String()
 | 
			
		||||
		expectedValue := v.String()
 | 
			
		||||
		if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
func TestSyncResourceQuotaSpecHardChange(t *testing.T) {
 | 
			
		||||
	resourceQuota := v1.ResourceQuota{
 | 
			
		||||
			),
 | 
			
		||||
			items: []runtime.Object{},
 | 
			
		||||
		},
 | 
			
		||||
		"quota-unchanged": {
 | 
			
		||||
			gvr: v1.SchemeGroupVersion.WithResource("pods"),
 | 
			
		||||
			quota: v1.ResourceQuota{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Namespace: "default",
 | 
			
		||||
					Name:      "rq",
 | 
			
		||||
@@ -262,18 +221,11 @@ func TestSyncResourceQuotaSpecHardChange(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
				Status: v1.ResourceQuotaStatus{
 | 
			
		||||
					Hard: v1.ResourceList{
 | 
			
		||||
				v1.ResourceCPU:    resource.MustParse("3"),
 | 
			
		||||
				v1.ResourceMemory: resource.MustParse("1Gi"),
 | 
			
		||||
			},
 | 
			
		||||
			Used: v1.ResourceList{
 | 
			
		||||
						v1.ResourceCPU: resource.MustParse("0"),
 | 
			
		||||
				v1.ResourceMemory: resource.MustParse("0"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedUsage := v1.ResourceQuota{
 | 
			
		||||
		Status: v1.ResourceQuotaStatus{
 | 
			
		||||
			},
 | 
			
		||||
			status: v1.ResourceQuotaStatus{
 | 
			
		||||
				Hard: v1.ResourceList{
 | 
			
		||||
					v1.ResourceCPU: resource.MustParse("4"),
 | 
			
		||||
				},
 | 
			
		||||
@@ -281,154 +233,72 @@ func TestSyncResourceQuotaSpecHardChange(t *testing.T) {
 | 
			
		||||
					v1.ResourceCPU: resource.MustParse("0"),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset(&resourceQuota)
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:           kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:          controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:              install.NewRegistry(kubeClient, nil),
 | 
			
		||||
		GroupKindsToReplenish: []schema.GroupKind{
 | 
			
		||||
			api.Kind("Pod"),
 | 
			
		||||
			api.Kind("Service"),
 | 
			
		||||
			api.Kind("ReplicationController"),
 | 
			
		||||
			api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
			expectedActionSet: sets.NewString(),
 | 
			
		||||
			items:             []runtime.Object{},
 | 
			
		||||
		},
 | 
			
		||||
		ControllerFactory:         NewReplenishmentControllerFactory(informerFactory),
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	quotaController := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	err := quotaController.syncResourceQuota(&resourceQuota)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectedActionSet := sets.NewString(
 | 
			
		||||
		strings.Join([]string{"list", "pods", ""}, "-"),
 | 
			
		||||
		strings.Join([]string{"update", "resourcequotas", "status"}, "-"),
 | 
			
		||||
	)
 | 
			
		||||
	for testName, testCase := range testCases {
 | 
			
		||||
		kubeClient := fake.NewSimpleClientset(&testCase.quota)
 | 
			
		||||
		listersForResourceConfig := map[schema.GroupVersionResource]cache.GenericLister{
 | 
			
		||||
			testCase.gvr: newGenericLister(testCase.gvr.GroupResource(), testCase.items),
 | 
			
		||||
		}
 | 
			
		||||
		qc := setupQuotaController(t, kubeClient, mockListerForResourceFunc(listersForResourceConfig))
 | 
			
		||||
		defer close(qc.stop)
 | 
			
		||||
 | 
			
		||||
		if err := qc.syncResourceQuota(&testCase.quota); err != nil {
 | 
			
		||||
			t.Fatalf("test: %s, unexpected error: %v", testName, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		actionSet := sets.NewString()
 | 
			
		||||
		for _, action := range kubeClient.Actions() {
 | 
			
		||||
			actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
 | 
			
		||||
		}
 | 
			
		||||
	if !actionSet.HasAll(expectedActionSet.List()...) {
 | 
			
		||||
		t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
 | 
			
		||||
		if !actionSet.HasAll(testCase.expectedActionSet.List()...) {
 | 
			
		||||
			t.Errorf("test: %s,\nExpected actions:\n%v\n but got:\n%v\nDifference:\n%v", testName, testCase.expectedActionSet, actionSet, testCase.expectedActionSet.Difference(actionSet))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lastActionIndex := len(kubeClient.Actions()) - 1
 | 
			
		||||
		usage := kubeClient.Actions()[lastActionIndex].(core.UpdateAction).GetObject().(*v1.ResourceQuota)
 | 
			
		||||
 | 
			
		||||
	// ensure hard and used limits are what we expected
 | 
			
		||||
	for k, v := range expectedUsage.Status.Hard {
 | 
			
		||||
		// ensure usage is as expected
 | 
			
		||||
		if len(usage.Status.Hard) != len(testCase.status.Hard) {
 | 
			
		||||
			t.Errorf("test: %s, status hard lengths do not match", testName)
 | 
			
		||||
		}
 | 
			
		||||
		if len(usage.Status.Used) != len(testCase.status.Used) {
 | 
			
		||||
			t.Errorf("test: %s, status used lengths do not match", testName)
 | 
			
		||||
		}
 | 
			
		||||
		for k, v := range testCase.status.Hard {
 | 
			
		||||
			actual := usage.Status.Hard[k]
 | 
			
		||||
			actualValue := actual.String()
 | 
			
		||||
			expectedValue := v.String()
 | 
			
		||||
			if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
				t.Errorf("test: %s, Usage Hard: Key: %v, Expected: %v, Actual: %v", testName, k, expectedValue, actualValue)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	for k, v := range expectedUsage.Status.Used {
 | 
			
		||||
		for k, v := range testCase.status.Used {
 | 
			
		||||
			actual := usage.Status.Used[k]
 | 
			
		||||
			actualValue := actual.String()
 | 
			
		||||
			expectedValue := v.String()
 | 
			
		||||
			if expectedValue != actualValue {
 | 
			
		||||
			t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue)
 | 
			
		||||
				t.Errorf("test: %s, Usage Used: Key: %v, Expected: %v, Actual: %v", testName, k, expectedValue, actualValue)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	// ensure usage hard and used are are synced with spec hard, not have dirty resource
 | 
			
		||||
	for k, v := range usage.Status.Hard {
 | 
			
		||||
		if k == v1.ResourceMemory {
 | 
			
		||||
			t.Errorf("Unexpected Usage Hard: Key: %v, Value: %v", k, v.String())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k, v := range usage.Status.Used {
 | 
			
		||||
		if k == v1.ResourceMemory {
 | 
			
		||||
			t.Errorf("Unexpected Usage Used: Key: %v, Value: %v", k, v.String())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSyncResourceQuotaNoChange(t *testing.T) {
 | 
			
		||||
	resourceQuota := v1.ResourceQuota{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Name:      "rq",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: v1.ResourceQuotaSpec{
 | 
			
		||||
			Hard: v1.ResourceList{
 | 
			
		||||
				v1.ResourceCPU: resource.MustParse("4"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Status: v1.ResourceQuotaStatus{
 | 
			
		||||
			Hard: v1.ResourceList{
 | 
			
		||||
				v1.ResourceCPU: resource.MustParse("4"),
 | 
			
		||||
			},
 | 
			
		||||
			Used: v1.ResourceList{
 | 
			
		||||
				v1.ResourceCPU: resource.MustParse("0"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset(&v1.PodList{}, &resourceQuota)
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:           kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:          controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:              install.NewRegistry(kubeClient, nil),
 | 
			
		||||
		GroupKindsToReplenish: []schema.GroupKind{
 | 
			
		||||
			api.Kind("Pod"),
 | 
			
		||||
			api.Kind("Service"),
 | 
			
		||||
			api.Kind("ReplicationController"),
 | 
			
		||||
			api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
		},
 | 
			
		||||
		ControllerFactory:         NewReplenishmentControllerFactory(informerFactory),
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
	}
 | 
			
		||||
	quotaController := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	err := quotaController.syncResourceQuota(&resourceQuota)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	expectedActionSet := sets.NewString(
 | 
			
		||||
		strings.Join([]string{"list", "pods", ""}, "-"),
 | 
			
		||||
	)
 | 
			
		||||
	actionSet := sets.NewString()
 | 
			
		||||
	for _, action := range kubeClient.Actions() {
 | 
			
		||||
		actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource().Resource, action.GetSubresource()}, "-"))
 | 
			
		||||
	}
 | 
			
		||||
	if !actionSet.HasAll(expectedActionSet.List()...) {
 | 
			
		||||
		t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddQuota(t *testing.T) {
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	informerFactory := informers.NewSharedInformerFactory(kubeClient, controller.NoResyncPeriodFunc())
 | 
			
		||||
	resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:           kubeClient.Core(),
 | 
			
		||||
		ResourceQuotaInformer: informerFactory.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:          controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:              install.NewRegistry(kubeClient, nil),
 | 
			
		||||
		GroupKindsToReplenish: []schema.GroupKind{
 | 
			
		||||
			api.Kind("Pod"),
 | 
			
		||||
			api.Kind("ReplicationController"),
 | 
			
		||||
			api.Kind("PersistentVolumeClaim"),
 | 
			
		||||
		},
 | 
			
		||||
		ControllerFactory:         NewReplenishmentControllerFactory(informerFactory),
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
	gvr := v1.SchemeGroupVersion.WithResource("pods")
 | 
			
		||||
	listersForResourceConfig := map[schema.GroupVersionResource]cache.GenericLister{
 | 
			
		||||
		gvr: newGenericLister(gvr.GroupResource(), newTestPods()),
 | 
			
		||||
	}
 | 
			
		||||
	quotaController := NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
 | 
			
		||||
	delete(quotaController.registry.(*generic.GenericRegistry).InternalEvaluators, api.Kind("Service"))
 | 
			
		||||
	qc := setupQuotaController(t, kubeClient, mockListerForResourceFunc(listersForResourceConfig))
 | 
			
		||||
	defer close(qc.stop)
 | 
			
		||||
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		name             string
 | 
			
		||||
 | 
			
		||||
		quota            *v1.ResourceQuota
 | 
			
		||||
		expectedPriority bool
 | 
			
		||||
	}{
 | 
			
		||||
@@ -491,7 +361,7 @@ func TestAddQuota(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:             "status, missing usage, but don't care",
 | 
			
		||||
			name:             "status, missing usage, but don't care (no informer)",
 | 
			
		||||
			expectedPriority: false,
 | 
			
		||||
			quota: &v1.ResourceQuota{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
@@ -500,12 +370,12 @@ func TestAddQuota(t *testing.T) {
 | 
			
		||||
				},
 | 
			
		||||
				Spec: v1.ResourceQuotaSpec{
 | 
			
		||||
					Hard: v1.ResourceList{
 | 
			
		||||
						v1.ResourceServices: resource.MustParse("4"),
 | 
			
		||||
						"count/foobars.example.com": resource.MustParse("4"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Status: v1.ResourceQuotaStatus{
 | 
			
		||||
					Hard: v1.ResourceList{
 | 
			
		||||
						v1.ResourceServices: resource.MustParse("4"),
 | 
			
		||||
						"count/foobars.example.com": resource.MustParse("4"),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -536,30 +406,29 @@ func TestAddQuota(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		quotaController.addQuota(tc.quota)
 | 
			
		||||
		qc.addQuota(tc.quota)
 | 
			
		||||
		if tc.expectedPriority {
 | 
			
		||||
			if e, a := 1, quotaController.missingUsageQueue.Len(); e != a {
 | 
			
		||||
			if e, a := 1, qc.missingUsageQueue.Len(); e != a {
 | 
			
		||||
				t.Errorf("%s: expected %v, got %v", tc.name, e, a)
 | 
			
		||||
			}
 | 
			
		||||
			if e, a := 0, quotaController.queue.Len(); e != a {
 | 
			
		||||
			if e, a := 0, qc.queue.Len(); e != a {
 | 
			
		||||
				t.Errorf("%s: expected %v, got %v", tc.name, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if e, a := 0, quotaController.missingUsageQueue.Len(); e != a {
 | 
			
		||||
			if e, a := 0, qc.missingUsageQueue.Len(); e != a {
 | 
			
		||||
				t.Errorf("%s: expected %v, got %v", tc.name, e, a)
 | 
			
		||||
			}
 | 
			
		||||
			if e, a := 1, quotaController.queue.Len(); e != a {
 | 
			
		||||
			if e, a := 1, qc.queue.Len(); e != a {
 | 
			
		||||
				t.Errorf("%s: expected %v, got %v", tc.name, e, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for quotaController.missingUsageQueue.Len() > 0 {
 | 
			
		||||
			key, _ := quotaController.missingUsageQueue.Get()
 | 
			
		||||
			quotaController.missingUsageQueue.Done(key)
 | 
			
		||||
		for qc.missingUsageQueue.Len() > 0 {
 | 
			
		||||
			key, _ := qc.missingUsageQueue.Get()
 | 
			
		||||
			qc.missingUsageQueue.Done(key)
 | 
			
		||||
		}
 | 
			
		||||
		for quotaController.queue.Len() > 0 {
 | 
			
		||||
			key, _ := quotaController.queue.Get()
 | 
			
		||||
			quotaController.queue.Done(key)
 | 
			
		||||
		for qc.queue.Len() > 0 {
 | 
			
		||||
			key, _ := qc.queue.Get()
 | 
			
		||||
			qc.queue.Done(key)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										341
									
								
								pkg/controller/resourcequota/resource_quota_monitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										341
									
								
								pkg/controller/resourcequota/resource_quota_monitor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,341 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package resourcequota
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/meta"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/client-go/util/workqueue"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/evaluator/core"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type eventType int
 | 
			
		||||
 | 
			
		||||
func (e eventType) String() string {
 | 
			
		||||
	switch e {
 | 
			
		||||
	case addEvent:
 | 
			
		||||
		return "add"
 | 
			
		||||
	case updateEvent:
 | 
			
		||||
		return "update"
 | 
			
		||||
	case deleteEvent:
 | 
			
		||||
		return "delete"
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Sprintf("unknown(%d)", int(e))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	addEvent eventType = iota
 | 
			
		||||
	updateEvent
 | 
			
		||||
	deleteEvent
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type event struct {
 | 
			
		||||
	eventType eventType
 | 
			
		||||
	obj       interface{}
 | 
			
		||||
	oldObj    interface{}
 | 
			
		||||
	gvr       schema.GroupVersionResource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type QuotaMonitor struct {
 | 
			
		||||
	// each monitor list/watches a resource and determines if we should replenish quota
 | 
			
		||||
	monitors    monitors
 | 
			
		||||
	monitorLock sync.Mutex
 | 
			
		||||
	// informersStarted is closed after after all of the controllers have been initialized and are running.
 | 
			
		||||
	// After that it is safe to start them here, before that it is not.
 | 
			
		||||
	informersStarted <-chan struct{}
 | 
			
		||||
 | 
			
		||||
	// stopCh drives shutdown. If it is nil, it indicates that Run() has not been
 | 
			
		||||
	// called yet. If it is non-nil, then when closed it indicates everything
 | 
			
		||||
	// should shut down.
 | 
			
		||||
	//
 | 
			
		||||
	// This channel is also protected by monitorLock.
 | 
			
		||||
	stopCh <-chan struct{}
 | 
			
		||||
 | 
			
		||||
	// monitors are the producer of the resourceChanges queue
 | 
			
		||||
	resourceChanges workqueue.RateLimitingInterface
 | 
			
		||||
 | 
			
		||||
	// interfaces with informers
 | 
			
		||||
	informerFactory InformerFactory
 | 
			
		||||
 | 
			
		||||
	// list of resources to ignore
 | 
			
		||||
	ignoredResources map[schema.GroupResource]struct{}
 | 
			
		||||
 | 
			
		||||
	// The period that should be used to re-sync the monitored resource
 | 
			
		||||
	resyncPeriod controller.ResyncPeriodFunc
 | 
			
		||||
 | 
			
		||||
	// callback to alert that a change may require quota recalculation
 | 
			
		||||
	replenishmentFunc ReplenishmentFunc
 | 
			
		||||
 | 
			
		||||
	// maintains list of evaluators
 | 
			
		||||
	registry quota.Registry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// monitor runs a Controller with a local stop channel.
 | 
			
		||||
type monitor struct {
 | 
			
		||||
	controller cache.Controller
 | 
			
		||||
 | 
			
		||||
	// stopCh stops Controller. If stopCh is nil, the monitor is considered to be
 | 
			
		||||
	// not yet started.
 | 
			
		||||
	stopCh chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run is intended to be called in a goroutine. Multiple calls of this is an
 | 
			
		||||
// error.
 | 
			
		||||
func (m *monitor) Run() {
 | 
			
		||||
	m.controller.Run(m.stopCh)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type monitors map[schema.GroupVersionResource]*monitor
 | 
			
		||||
 | 
			
		||||
func (qm *QuotaMonitor) controllerFor(resource schema.GroupVersionResource) (cache.Controller, error) {
 | 
			
		||||
	// TODO: pass this down
 | 
			
		||||
	clock := clock.RealClock{}
 | 
			
		||||
	handlers := cache.ResourceEventHandlerFuncs{
 | 
			
		||||
		UpdateFunc: func(oldObj, newObj interface{}) {
 | 
			
		||||
			// TODO: leaky abstraction!  live w/ it for now, but should pass down an update filter func.
 | 
			
		||||
			// we only want to queue the updates we care about though as too much noise will overwhelm queue.
 | 
			
		||||
			notifyUpdate := false
 | 
			
		||||
			switch resource.GroupResource() {
 | 
			
		||||
			case schema.GroupResource{Resource: "pods"}:
 | 
			
		||||
				oldPod := oldObj.(*v1.Pod)
 | 
			
		||||
				newPod := newObj.(*v1.Pod)
 | 
			
		||||
				notifyUpdate = core.QuotaV1Pod(oldPod, clock) && !core.QuotaV1Pod(newPod, clock)
 | 
			
		||||
			case schema.GroupResource{Resource: "services"}:
 | 
			
		||||
				oldService := oldObj.(*v1.Service)
 | 
			
		||||
				newService := newObj.(*v1.Service)
 | 
			
		||||
				notifyUpdate = core.GetQuotaServiceType(oldService) != core.GetQuotaServiceType(newService)
 | 
			
		||||
			}
 | 
			
		||||
			if notifyUpdate {
 | 
			
		||||
				event := &event{
 | 
			
		||||
					eventType: updateEvent,
 | 
			
		||||
					obj:       newObj,
 | 
			
		||||
					oldObj:    oldObj,
 | 
			
		||||
					gvr:       resource,
 | 
			
		||||
				}
 | 
			
		||||
				qm.resourceChanges.Add(event)
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
		DeleteFunc: func(obj interface{}) {
 | 
			
		||||
			// delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it
 | 
			
		||||
			if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok {
 | 
			
		||||
				obj = deletedFinalStateUnknown.Obj
 | 
			
		||||
			}
 | 
			
		||||
			event := &event{
 | 
			
		||||
				eventType: deleteEvent,
 | 
			
		||||
				obj:       obj,
 | 
			
		||||
				gvr:       resource,
 | 
			
		||||
			}
 | 
			
		||||
			qm.resourceChanges.Add(event)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	shared, err := qm.informerFactory.ForResource(resource)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		glog.V(4).Infof("QuotaMonitor using a shared informer for resource %q", resource.String())
 | 
			
		||||
		shared.Informer().AddEventHandlerWithResyncPeriod(handlers, qm.resyncPeriod())
 | 
			
		||||
		return shared.Informer().GetController(), nil
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(4).Infof("QuotaMonitor unable to use a shared informer for resource %q: %v", resource.String(), err)
 | 
			
		||||
 | 
			
		||||
	// TODO: if we can share storage with garbage collector, it may make sense to support other resources
 | 
			
		||||
	// until that time, aggregated api servers will have to run their own controller to reconcile their own quota.
 | 
			
		||||
	return nil, fmt.Errorf("unable to monitor quota for resource %q", resource.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// syncMonitors rebuilds the monitor set according to the supplied resources,
 | 
			
		||||
// creating or deleting monitors as necessary. It will return any error
 | 
			
		||||
// encountered, but will make an attempt to create a monitor for each resource
 | 
			
		||||
// instead of immediately exiting on an error. It may be called before or after
 | 
			
		||||
// Run. Monitors are NOT started as part of the sync. To ensure all existing
 | 
			
		||||
// monitors are started, call startMonitors.
 | 
			
		||||
func (qm *QuotaMonitor) syncMonitors(resources map[schema.GroupVersionResource]struct{}) error {
 | 
			
		||||
	qm.monitorLock.Lock()
 | 
			
		||||
	defer qm.monitorLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	toRemove := qm.monitors
 | 
			
		||||
	if toRemove == nil {
 | 
			
		||||
		toRemove = monitors{}
 | 
			
		||||
	}
 | 
			
		||||
	current := monitors{}
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	kept := 0
 | 
			
		||||
	added := 0
 | 
			
		||||
	for resource := range resources {
 | 
			
		||||
		if _, ok := qm.ignoredResources[resource.GroupResource()]; ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if m, ok := toRemove[resource]; ok {
 | 
			
		||||
			current[resource] = m
 | 
			
		||||
			delete(toRemove, resource)
 | 
			
		||||
			kept++
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		c, err := qm.controllerFor(resource)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("couldn't start monitor for resource %q: %v", resource, err))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// check if we need to create an evaluator for this resource (if none previously registered)
 | 
			
		||||
		evaluator := qm.registry.Get(resource.GroupResource())
 | 
			
		||||
		if evaluator == nil {
 | 
			
		||||
			listerFunc := generic.ListerFuncForResourceFunc(qm.informerFactory.ForResource)
 | 
			
		||||
			listResourceFunc := generic.ListResourceUsingListerFunc(listerFunc, resource)
 | 
			
		||||
			evaluator = generic.NewObjectCountEvaluator(false, resource.GroupResource(), listResourceFunc, "")
 | 
			
		||||
			qm.registry.Add(evaluator)
 | 
			
		||||
			glog.Infof("QuotaMonitor created object count evaluator for %s", resource.GroupResource())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// track the monitor
 | 
			
		||||
		current[resource] = &monitor{controller: c}
 | 
			
		||||
		added++
 | 
			
		||||
	}
 | 
			
		||||
	qm.monitors = current
 | 
			
		||||
 | 
			
		||||
	for _, monitor := range toRemove {
 | 
			
		||||
		if monitor.stopCh != nil {
 | 
			
		||||
			close(monitor.stopCh)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	glog.V(4).Infof("quota synced monitors; added %d, kept %d, removed %d", added, kept, len(toRemove))
 | 
			
		||||
	// NewAggregate returns nil if errs is 0-length
 | 
			
		||||
	return utilerrors.NewAggregate(errs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// startMonitors ensures the current set of monitors are running. Any newly
 | 
			
		||||
// started monitors will also cause shared informers to be started.
 | 
			
		||||
//
 | 
			
		||||
// If called before Run, startMonitors does nothing (as there is no stop channel
 | 
			
		||||
// to support monitor/informer execution).
 | 
			
		||||
func (qm *QuotaMonitor) startMonitors() {
 | 
			
		||||
	qm.monitorLock.Lock()
 | 
			
		||||
	defer qm.monitorLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if qm.stopCh == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// we're waiting until after the informer start that happens once all the controllers are initialized.  This ensures
 | 
			
		||||
	// that they don't get unexpected events on their work queues.
 | 
			
		||||
	<-qm.informersStarted
 | 
			
		||||
 | 
			
		||||
	monitors := qm.monitors
 | 
			
		||||
	started := 0
 | 
			
		||||
	for _, monitor := range monitors {
 | 
			
		||||
		if monitor.stopCh == nil {
 | 
			
		||||
			monitor.stopCh = make(chan struct{})
 | 
			
		||||
			qm.informerFactory.Start(qm.stopCh)
 | 
			
		||||
			go monitor.Run()
 | 
			
		||||
			started++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(4).Infof("QuotaMonitor started %d new monitors, %d currently running", started, len(monitors))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsSynced returns true if any monitors exist AND all those monitors'
 | 
			
		||||
// controllers HasSynced functions return true. This means IsSynced could return
 | 
			
		||||
// true at one time, and then later return false if all monitors were
 | 
			
		||||
// reconstructed.
 | 
			
		||||
func (qm *QuotaMonitor) IsSynced() bool {
 | 
			
		||||
	qm.monitorLock.Lock()
 | 
			
		||||
	defer qm.monitorLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if len(qm.monitors) == 0 {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, monitor := range qm.monitors {
 | 
			
		||||
		if !monitor.controller.HasSynced() {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run sets the stop channel and starts monitor execution until stopCh is
 | 
			
		||||
// closed. Any running monitors will be stopped before Run returns.
 | 
			
		||||
func (qm *QuotaMonitor) Run(stopCh <-chan struct{}) {
 | 
			
		||||
	glog.Infof("QuotaMonitor running")
 | 
			
		||||
	defer glog.Infof("QuotaMonitor stopping")
 | 
			
		||||
 | 
			
		||||
	// Set up the stop channel.
 | 
			
		||||
	qm.monitorLock.Lock()
 | 
			
		||||
	qm.stopCh = stopCh
 | 
			
		||||
	qm.monitorLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	// Start monitors and begin change processing until the stop channel is
 | 
			
		||||
	// closed.
 | 
			
		||||
	qm.startMonitors()
 | 
			
		||||
	wait.Until(qm.runProcessResourceChanges, 1*time.Second, stopCh)
 | 
			
		||||
 | 
			
		||||
	// Stop any running monitors.
 | 
			
		||||
	qm.monitorLock.Lock()
 | 
			
		||||
	defer qm.monitorLock.Unlock()
 | 
			
		||||
	monitors := qm.monitors
 | 
			
		||||
	stopped := 0
 | 
			
		||||
	for _, monitor := range monitors {
 | 
			
		||||
		if monitor.stopCh != nil {
 | 
			
		||||
			stopped++
 | 
			
		||||
			close(monitor.stopCh)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("QuotaMonitor stopped %d of %d monitors", stopped, len(monitors))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (qm *QuotaMonitor) runProcessResourceChanges() {
 | 
			
		||||
	for qm.processResourceChanges() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dequeueing an event from resourceChanges to process
 | 
			
		||||
func (qm *QuotaMonitor) processResourceChanges() bool {
 | 
			
		||||
	item, quit := qm.resourceChanges.Get()
 | 
			
		||||
	if quit {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	defer qm.resourceChanges.Done(item)
 | 
			
		||||
	event, ok := item.(*event)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		utilruntime.HandleError(fmt.Errorf("expect a *event, got %v", item))
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	obj := event.obj
 | 
			
		||||
	accessor, err := meta.Accessor(obj)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utilruntime.HandleError(fmt.Errorf("cannot access obj: %v", err))
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(4).Infof("QuotaMonitor process object: %s, namespace %s, name %s, uid %s, event type %v", event.gvr.String(), accessor.GetNamespace(), accessor.GetName(), string(accessor.GetUID()), event.eventType)
 | 
			
		||||
	qm.replenishmentFunc(event.gvr.GroupResource(), accessor.GetNamespace())
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ go_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/client-go/tools/cache:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,10 @@ load(
 | 
			
		||||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "configmap.go",
 | 
			
		||||
        "doc.go",
 | 
			
		||||
        "persistent_volume_claims.go",
 | 
			
		||||
        "pods.go",
 | 
			
		||||
        "registry.go",
 | 
			
		||||
        "replication_controllers.go",
 | 
			
		||||
        "resource_quotas.go",
 | 
			
		||||
        "secrets.go",
 | 
			
		||||
        "services.go",
 | 
			
		||||
    ],
 | 
			
		||||
    importpath = "k8s.io/kubernetes/pkg/quota/evaluator/core",
 | 
			
		||||
@@ -32,7 +28,6 @@ go_library(
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/api/core/v1: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:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
 | 
			
		||||
@@ -43,8 +38,6 @@ go_library(
 | 
			
		||||
        "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apiserver/pkg/features:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -60,11 +53,12 @@ go_test(
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//pkg/api:go_default_library",
 | 
			
		||||
        "//pkg/quota:go_default_library",
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//pkg/util/node: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/clock:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// listConfigMapsByNamespaceFuncUsingClient returns a configMap listing function based on the provided client.
 | 
			
		||||
func listConfigMapsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().ConfigMaps(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfigMapEvaluator returns an evaluator that can evaluate configMaps
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewConfigMapEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listConfigMapsByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("configmaps"))
 | 
			
		||||
	}
 | 
			
		||||
	return &generic.ObjectCountEvaluator{
 | 
			
		||||
		AllowCreateOnUpdate: false,
 | 
			
		||||
		InternalGroupKind:   api.Kind("ConfigMap"),
 | 
			
		||||
		ResourceName:        api.ResourceConfigMaps,
 | 
			
		||||
		ListFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -22,17 +22,13 @@ import (
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/initialization"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/apiserver/pkg/features"
 | 
			
		||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/helper"
 | 
			
		||||
	k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
@@ -42,6 +38,9 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// the name used for object count quota
 | 
			
		||||
var pvcObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource())
 | 
			
		||||
 | 
			
		||||
// pvcResources are the set of static resources managed by quota associated with pvcs.
 | 
			
		||||
// for each resouce in this list, it may be refined dynamically based on storage class.
 | 
			
		||||
var pvcResources = []api.ResourceName{
 | 
			
		||||
@@ -67,34 +66,11 @@ func V1ResourceByStorageClass(storageClass string, resourceName v1.ResourceName)
 | 
			
		||||
	return v1.ResourceName(string(storageClass + storageClassSuffix + string(resourceName)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// listPersistentVolumeClaimsByNamespaceFuncUsingClient returns a pvc listing function based on the provided client.
 | 
			
		||||
func listPersistentVolumeClaimsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().PersistentVolumeClaims(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPersistentVolumeClaimEvaluator returns an evaluator that can evaluate persistent volume claims
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewPersistentVolumeClaimEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listPersistentVolumeClaimsByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("persistentvolumeclaims"))
 | 
			
		||||
	}
 | 
			
		||||
	return &pvcEvaluator{
 | 
			
		||||
		listFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
func NewPersistentVolumeClaimEvaluator(f quota.ListerForResourceFunc) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("persistentvolumeclaims"))
 | 
			
		||||
	pvcEvaluator := &pvcEvaluator{listFuncByNamespace: listFuncByNamespace}
 | 
			
		||||
	return pvcEvaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pvcEvaluator knows how to evaluate quota usage for persistent volume claims
 | 
			
		||||
@@ -105,45 +81,13 @@ type pvcEvaluator struct {
 | 
			
		||||
 | 
			
		||||
// Constraints verifies that all required resources are present on the item.
 | 
			
		||||
func (p *pvcEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
 | 
			
		||||
	pvc, ok := item.(*api.PersistentVolumeClaim)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("unexpected input object %v", item)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// these are the items that we will be handling based on the objects actual storage-class
 | 
			
		||||
	pvcRequiredSet := append([]api.ResourceName{}, pvcResources...)
 | 
			
		||||
	if storageClassRef := helper.GetPersistentVolumeClaimClass(pvc); len(storageClassRef) > 0 {
 | 
			
		||||
		pvcRequiredSet = append(pvcRequiredSet, ResourceByStorageClass(storageClassRef, api.ResourcePersistentVolumeClaims))
 | 
			
		||||
		pvcRequiredSet = append(pvcRequiredSet, ResourceByStorageClass(storageClassRef, api.ResourceRequestsStorage))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// in effect, this will remove things from the required set that are not tied to this pvcs storage class
 | 
			
		||||
	// for example, if a quota has bronze and gold storage class items defined, we should not error a bronze pvc for not being gold.
 | 
			
		||||
	// but we should error a bronze pvc if it doesn't make a storage request size...
 | 
			
		||||
	requiredResources := quota.Intersection(required, pvcRequiredSet)
 | 
			
		||||
	requiredSet := quota.ToSet(requiredResources)
 | 
			
		||||
 | 
			
		||||
	// usage for this pvc will only include global pvc items + this storage class specific items
 | 
			
		||||
	pvcUsage, err := p.Usage(item)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// determine what required resources were not tracked by usage.
 | 
			
		||||
	missingSet := sets.NewString()
 | 
			
		||||
	pvcSet := quota.ToSet(quota.ResourceNames(pvcUsage))
 | 
			
		||||
	if diff := requiredSet.Difference(pvcSet); len(diff) > 0 {
 | 
			
		||||
		missingSet.Insert(diff.List()...)
 | 
			
		||||
	}
 | 
			
		||||
	if len(missingSet) == 0 {
 | 
			
		||||
	// no-op for persistent volume claims
 | 
			
		||||
	return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ","))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GroupKind that this evaluator tracks
 | 
			
		||||
func (p *pvcEvaluator) GroupKind() schema.GroupKind {
 | 
			
		||||
	return api.Kind("PersistentVolumeClaim")
 | 
			
		||||
// GroupResource that this evaluator tracks
 | 
			
		||||
func (p *pvcEvaluator) GroupResource() schema.GroupResource {
 | 
			
		||||
	return v1.SchemeGroupVersion.WithResource("persistentvolumeclaims").GroupResource()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handles returns true if the evaluator should handle the specified operation.
 | 
			
		||||
@@ -183,6 +127,12 @@ func (p *pvcEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Ob
 | 
			
		||||
func (p *pvcEvaluator) MatchingResources(items []api.ResourceName) []api.ResourceName {
 | 
			
		||||
	result := []api.ResourceName{}
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		// match object count quota fields
 | 
			
		||||
		if quota.Contains([]api.ResourceName{pvcObjectCountName}, item) {
 | 
			
		||||
			result = append(result, item)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// match pvc resources
 | 
			
		||||
		if quota.Contains(pvcResources, item) {
 | 
			
		||||
			result = append(result, item)
 | 
			
		||||
			continue
 | 
			
		||||
@@ -208,7 +158,8 @@ func (p *pvcEvaluator) Usage(item runtime.Object) (api.ResourceList, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// charge for claim
 | 
			
		||||
	result[api.ResourcePersistentVolumeClaims] = resource.MustParse("1")
 | 
			
		||||
	result[api.ResourcePersistentVolumeClaims] = *(resource.NewQuantity(1, resource.DecimalSI))
 | 
			
		||||
	result[pvcObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI))
 | 
			
		||||
	if utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
 | 
			
		||||
		if !initialization.IsInitialized(pvc.Initializers) {
 | 
			
		||||
			// Only charge pvc count for uninitialized pvc.
 | 
			
		||||
@@ -218,7 +169,7 @@ func (p *pvcEvaluator) Usage(item runtime.Object) (api.ResourceList, error) {
 | 
			
		||||
	storageClassRef := helper.GetPersistentVolumeClaimClass(pvc)
 | 
			
		||||
	if len(storageClassRef) > 0 {
 | 
			
		||||
		storageClassClaim := api.ResourceName(storageClassRef + storageClassSuffix + string(api.ResourcePersistentVolumeClaims))
 | 
			
		||||
		result[storageClassClaim] = resource.MustParse("1")
 | 
			
		||||
		result[storageClassClaim] = *(resource.NewQuantity(1, resource.DecimalSI))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// charge for storage
 | 
			
		||||
 
 | 
			
		||||
@@ -21,9 +21,10 @@ import (
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/client-go/kubernetes/fake"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeClaimSpec) *api.PersistentVolumeClaim {
 | 
			
		||||
@@ -33,168 +34,6 @@ func testVolumeClaim(name string, namespace string, spec api.PersistentVolumeCla
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPersistentVolumeClaimsConstraintsFunc(t *testing.T) {
 | 
			
		||||
	classGold := "gold"
 | 
			
		||||
	classBronze := "bronze"
 | 
			
		||||
 | 
			
		||||
	validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
		Selector: &metav1.LabelSelector{
 | 
			
		||||
			MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
			
		||||
				{
 | 
			
		||||
					Key:      "key2",
 | 
			
		||||
					Operator: "Exists",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		AccessModes: []api.PersistentVolumeAccessMode{
 | 
			
		||||
			api.ReadWriteOnce,
 | 
			
		||||
			api.ReadOnlyMany,
 | 
			
		||||
		},
 | 
			
		||||
		Resources: api.ResourceRequirements{
 | 
			
		||||
			Requests: api.ResourceList{
 | 
			
		||||
				api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	validClaimGoldStorageClass := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
		Selector: &metav1.LabelSelector{
 | 
			
		||||
			MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
			
		||||
				{
 | 
			
		||||
					Key:      "key2",
 | 
			
		||||
					Operator: "Exists",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		AccessModes: []api.PersistentVolumeAccessMode{
 | 
			
		||||
			api.ReadWriteOnce,
 | 
			
		||||
			api.ReadOnlyMany,
 | 
			
		||||
		},
 | 
			
		||||
		Resources: api.ResourceRequirements{
 | 
			
		||||
			Requests: api.ResourceList{
 | 
			
		||||
				api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		StorageClassName: &classGold,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validClaimBronzeStorageClass := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
		Selector: &metav1.LabelSelector{
 | 
			
		||||
			MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
			
		||||
				{
 | 
			
		||||
					Key:      "key2",
 | 
			
		||||
					Operator: "Exists",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		AccessModes: []api.PersistentVolumeAccessMode{
 | 
			
		||||
			api.ReadWriteOnce,
 | 
			
		||||
			api.ReadOnlyMany,
 | 
			
		||||
		},
 | 
			
		||||
		Resources: api.ResourceRequirements{
 | 
			
		||||
			Requests: api.ResourceList{
 | 
			
		||||
				api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		StorageClassName: &classBronze,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	missingStorage := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
		Selector: &metav1.LabelSelector{
 | 
			
		||||
			MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
			
		||||
				{
 | 
			
		||||
					Key:      "key2",
 | 
			
		||||
					Operator: "Exists",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		AccessModes: []api.PersistentVolumeAccessMode{
 | 
			
		||||
			api.ReadWriteOnce,
 | 
			
		||||
			api.ReadOnlyMany,
 | 
			
		||||
		},
 | 
			
		||||
		Resources: api.ResourceRequirements{
 | 
			
		||||
			Requests: api.ResourceList{},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	missingGoldStorage := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
		Selector: &metav1.LabelSelector{
 | 
			
		||||
			MatchExpressions: []metav1.LabelSelectorRequirement{
 | 
			
		||||
				{
 | 
			
		||||
					Key:      "key2",
 | 
			
		||||
					Operator: "Exists",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		AccessModes: []api.PersistentVolumeAccessMode{
 | 
			
		||||
			api.ReadWriteOnce,
 | 
			
		||||
			api.ReadOnlyMany,
 | 
			
		||||
		},
 | 
			
		||||
		Resources: api.ResourceRequirements{
 | 
			
		||||
			Requests: api.ResourceList{},
 | 
			
		||||
		},
 | 
			
		||||
		StorageClassName: &classGold,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		pvc      *api.PersistentVolumeClaim
 | 
			
		||||
		required []api.ResourceName
 | 
			
		||||
		err      string
 | 
			
		||||
	}{
 | 
			
		||||
		"missing storage": {
 | 
			
		||||
			pvc:      missingStorage,
 | 
			
		||||
			required: []api.ResourceName{api.ResourceRequestsStorage},
 | 
			
		||||
			err:      `must specify requests.storage`,
 | 
			
		||||
		},
 | 
			
		||||
		"missing gold storage": {
 | 
			
		||||
			pvc:      missingGoldStorage,
 | 
			
		||||
			required: []api.ResourceName{ResourceByStorageClass(classGold, api.ResourceRequestsStorage)},
 | 
			
		||||
			err:      `must specify gold.storageclass.storage.k8s.io/requests.storage`,
 | 
			
		||||
		},
 | 
			
		||||
		"valid-claim-quota-storage": {
 | 
			
		||||
			pvc:      validClaim,
 | 
			
		||||
			required: []api.ResourceName{api.ResourceRequestsStorage},
 | 
			
		||||
		},
 | 
			
		||||
		"valid-claim-quota-pvc": {
 | 
			
		||||
			pvc:      validClaim,
 | 
			
		||||
			required: []api.ResourceName{api.ResourcePersistentVolumeClaims},
 | 
			
		||||
		},
 | 
			
		||||
		"valid-claim-quota-storage-and-pvc": {
 | 
			
		||||
			pvc:      validClaim,
 | 
			
		||||
			required: []api.ResourceName{api.ResourceRequestsStorage, api.ResourcePersistentVolumeClaims},
 | 
			
		||||
		},
 | 
			
		||||
		"valid-claim-gold-quota-gold": {
 | 
			
		||||
			pvc: validClaimGoldStorageClass,
 | 
			
		||||
			required: []api.ResourceName{
 | 
			
		||||
				api.ResourceRequestsStorage,
 | 
			
		||||
				api.ResourcePersistentVolumeClaims,
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourceRequestsStorage),
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"valid-claim-bronze-with-quota-gold": {
 | 
			
		||||
			pvc: validClaimBronzeStorageClass,
 | 
			
		||||
			required: []api.ResourceName{
 | 
			
		||||
				api.ResourceRequestsStorage,
 | 
			
		||||
				api.ResourcePersistentVolumeClaims,
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourceRequestsStorage),
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewPersistentVolumeClaimEvaluator(kubeClient, nil)
 | 
			
		||||
	for testName, test := range testCases {
 | 
			
		||||
		err := evaluator.Constraints(test.required, test.pvc)
 | 
			
		||||
		switch {
 | 
			
		||||
		case err != nil && len(test.err) == 0,
 | 
			
		||||
			err == nil && len(test.err) != 0,
 | 
			
		||||
			err != nil && test.err != err.Error():
 | 
			
		||||
			t.Errorf("%s unexpected error: %v", testName, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) {
 | 
			
		||||
	classGold := "gold"
 | 
			
		||||
	validClaim := testVolumeClaim("foo", "ns", api.PersistentVolumeClaimSpec{
 | 
			
		||||
@@ -237,8 +76,7 @@ func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) {
 | 
			
		||||
		StorageClassName: &classGold,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewPersistentVolumeClaimEvaluator(kubeClient, nil)
 | 
			
		||||
	evaluator := NewPersistentVolumeClaimEvaluator(nil)
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		pvc   *api.PersistentVolumeClaim
 | 
			
		||||
		usage api.ResourceList
 | 
			
		||||
@@ -248,6 +86,7 @@ func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) {
 | 
			
		||||
			usage: api.ResourceList{
 | 
			
		||||
				api.ResourceRequestsStorage:                                                                       resource.MustParse("10Gi"),
 | 
			
		||||
				api.ResourcePersistentVolumeClaims:                                                                resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "persistentvolumeclaims"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"pvc-usage-by-class": {
 | 
			
		||||
@@ -257,6 +96,7 @@ func TestPersistentVolumeClaimEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourcePersistentVolumeClaims:                                                                resource.MustParse("1"),
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourceRequestsStorage):                                    resource.MustParse("10Gi"),
 | 
			
		||||
				ResourceByStorageClass(classGold, api.ResourcePersistentVolumeClaims):                             resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "persistentvolumeclaims"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,20 +23,14 @@ import (
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/initialization"
 | 
			
		||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/apiserver/pkg/features"
 | 
			
		||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/helper/qos"
 | 
			
		||||
	k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
@@ -46,8 +40,12 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// the name used for object count quota
 | 
			
		||||
var podObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("pods").GroupResource())
 | 
			
		||||
 | 
			
		||||
// podResources are the set of resources managed by quota associated with pods.
 | 
			
		||||
var podResources = []api.ResourceName{
 | 
			
		||||
	podObjectCountName,
 | 
			
		||||
	api.ResourceCPU,
 | 
			
		||||
	api.ResourceMemory,
 | 
			
		||||
	api.ResourceEphemeralStorage,
 | 
			
		||||
@@ -60,35 +58,24 @@ var podResources = []api.ResourceName{
 | 
			
		||||
	api.ResourcePods,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// listPodsByNamespaceFuncUsingClient returns a pod listing function based on the provided client.
 | 
			
		||||
func listPodsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().Pods(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
// NOTE: it was a mistake, but if a quota tracks cpu or memory related resources,
 | 
			
		||||
// the incoming pod is required to have those values set.  we should not repeat
 | 
			
		||||
// this mistake for other future resources (gpus, ephemeral-storage,etc).
 | 
			
		||||
// do not add more resources to this list!
 | 
			
		||||
var validationSet = sets.NewString(
 | 
			
		||||
	string(api.ResourceCPU),
 | 
			
		||||
	string(api.ResourceMemory),
 | 
			
		||||
	string(api.ResourceRequestsCPU),
 | 
			
		||||
	string(api.ResourceRequestsMemory),
 | 
			
		||||
	string(api.ResourceLimitsCPU),
 | 
			
		||||
	string(api.ResourceLimitsMemory),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewPodEvaluator returns an evaluator that can evaluate pods
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewPodEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory, clock clock.Clock) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listPodsByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("pods"))
 | 
			
		||||
	}
 | 
			
		||||
	return &podEvaluator{
 | 
			
		||||
		listFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
		clock:               clock,
 | 
			
		||||
	}
 | 
			
		||||
func NewPodEvaluator(f quota.ListerForResourceFunc, clock clock.Clock) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("pods"))
 | 
			
		||||
	podEvaluator := &podEvaluator{listFuncByNamespace: listFuncByNamespace, clock: clock}
 | 
			
		||||
	return podEvaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// podEvaluator knows how to measure usage of pods.
 | 
			
		||||
@@ -110,6 +97,7 @@ func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Obj
 | 
			
		||||
	// Pod level resources are often set during admission control
 | 
			
		||||
	// As a consequence, we want to verify that resources are valid prior
 | 
			
		||||
	// to ever charging quota prematurely in case they are not.
 | 
			
		||||
	// TODO remove this entire section when we have a validation step in admission.
 | 
			
		||||
	allErrs := field.ErrorList{}
 | 
			
		||||
	fldPath := field.NewPath("spec").Child("containers")
 | 
			
		||||
	for i, ctr := range pod.Spec.Containers {
 | 
			
		||||
@@ -123,10 +111,11 @@ func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Obj
 | 
			
		||||
		return allErrs.ToAggregate()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: fix this when we have pod level resource requirements
 | 
			
		||||
	// since we do not yet pod level requests/limits, we need to ensure each
 | 
			
		||||
	// container makes an explict request or limit for a quota tracked resource
 | 
			
		||||
	requiredSet := quota.ToSet(required)
 | 
			
		||||
	// BACKWARD COMPATIBILITY REQUIREMENT: if we quota cpu or memory, then each container
 | 
			
		||||
	// must make an explicit request for the resource.  this was a mistake.  it coupled
 | 
			
		||||
	// validation with resource counting, but we did this before QoS was even defined.
 | 
			
		||||
	// let's not make that mistake again with other resources now that QoS is defined.
 | 
			
		||||
	requiredSet := quota.ToSet(required).Intersection(validationSet)
 | 
			
		||||
	missingSet := sets.NewString()
 | 
			
		||||
	for i := range pod.Spec.Containers {
 | 
			
		||||
		enforcePodContainerConstraints(&pod.Spec.Containers[i], requiredSet, missingSet)
 | 
			
		||||
@@ -140,9 +129,9 @@ func (p *podEvaluator) Constraints(required []api.ResourceName, item runtime.Obj
 | 
			
		||||
	return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ","))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GroupKind that this evaluator tracks
 | 
			
		||||
func (p *podEvaluator) GroupKind() schema.GroupKind {
 | 
			
		||||
	return api.Kind("Pod")
 | 
			
		||||
// GroupResource that this evaluator tracks
 | 
			
		||||
func (p *podEvaluator) GroupResource() schema.GroupResource {
 | 
			
		||||
	return v1.SchemeGroupVersion.WithResource("pods").GroupResource()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handles returns true if the evaluator should handle the specified attributes.
 | 
			
		||||
@@ -190,7 +179,7 @@ var _ quota.Evaluator = &podEvaluator{}
 | 
			
		||||
func enforcePodContainerConstraints(container *api.Container, requiredSet, missingSet sets.String) {
 | 
			
		||||
	requests := container.Resources.Requests
 | 
			
		||||
	limits := container.Resources.Limits
 | 
			
		||||
	containerUsage := podUsageHelper(requests, limits)
 | 
			
		||||
	containerUsage := podComputeUsageHelper(requests, limits)
 | 
			
		||||
	containerSet := quota.ToSet(quota.ResourceNames(containerUsage))
 | 
			
		||||
	if !containerSet.Equal(requiredSet) {
 | 
			
		||||
		difference := requiredSet.Difference(containerSet)
 | 
			
		||||
@@ -198,8 +187,8 @@ func enforcePodContainerConstraints(container *api.Container, requiredSet, missi
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// podUsageHelper can summarize the pod quota usage based on requests and limits
 | 
			
		||||
func podUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList {
 | 
			
		||||
// podComputeUsageHelper can summarize the pod compute quota usage based on requests and limits
 | 
			
		||||
func podComputeUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList {
 | 
			
		||||
	result := api.ResourceList{}
 | 
			
		||||
	result[api.ResourcePods] = resource.MustParse("1")
 | 
			
		||||
	if request, found := requests[api.ResourceCPU]; found {
 | 
			
		||||
@@ -269,18 +258,21 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (api.ResourceList, erro
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return api.ResourceList{}, err
 | 
			
		||||
	}
 | 
			
		||||
	// by convention, we do not quota pods that have reached end-of life
 | 
			
		||||
	if !QuotaPod(pod, clock) {
 | 
			
		||||
		return api.ResourceList{}, nil
 | 
			
		||||
 | 
			
		||||
	// always quota the object count (even if the pod is end of life)
 | 
			
		||||
	// object count quotas track all objects that are in storage.
 | 
			
		||||
	// where "pods" tracks all pods that have not reached a terminal state,
 | 
			
		||||
	// count/pods tracks all pods independent of state.
 | 
			
		||||
	result := api.ResourceList{
 | 
			
		||||
		podObjectCountName: *(resource.NewQuantity(1, resource.DecimalSI)),
 | 
			
		||||
	}
 | 
			
		||||
	// Only charge pod count for uninitialized pod.
 | 
			
		||||
	if utilfeature.DefaultFeatureGate.Enabled(features.Initializers) {
 | 
			
		||||
		if !initialization.IsInitialized(pod.Initializers) {
 | 
			
		||||
			result := api.ResourceList{}
 | 
			
		||||
			result[api.ResourcePods] = resource.MustParse("1")
 | 
			
		||||
 | 
			
		||||
	// by convention, we do not quota compute resources that have reached end-of life
 | 
			
		||||
	// note: the "pods" resource is considered a compute resource since it is tied to life-cycle.
 | 
			
		||||
	if !QuotaPod(pod, clock) {
 | 
			
		||||
		return result, nil
 | 
			
		||||
	}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	requests := api.ResourceList{}
 | 
			
		||||
	limits := api.ResourceList{}
 | 
			
		||||
	// TODO: ideally, we have pod level requests and limits in the future.
 | 
			
		||||
@@ -296,7 +288,8 @@ func PodUsageFunc(obj runtime.Object, clock clock.Clock) (api.ResourceList, erro
 | 
			
		||||
		limits = quota.Max(limits, pod.Spec.InitContainers[i].Resources.Limits)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return podUsageHelper(requests, limits), nil
 | 
			
		||||
	result = quota.Add(result, podComputeUsageHelper(requests, limits))
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isBestEffort(pod *api.Pod) bool {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,10 +22,11 @@ import (
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	"k8s.io/client-go/kubernetes/fake"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/node"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -90,8 +91,7 @@ func TestPodConstraintsFunc(t *testing.T) {
 | 
			
		||||
			err:      `must specify memory`,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewPodEvaluator(kubeClient, nil, clock.RealClock{})
 | 
			
		||||
	evaluator := NewPodEvaluator(nil, clock.RealClock{})
 | 
			
		||||
	for testName, test := range testCases {
 | 
			
		||||
		err := evaluator.Constraints(test.required, test.pod)
 | 
			
		||||
		switch {
 | 
			
		||||
@@ -104,9 +104,8 @@ func TestPodConstraintsFunc(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	fakeClock := clock.NewFakeClock(time.Now())
 | 
			
		||||
	evaluator := NewPodEvaluator(kubeClient, nil, fakeClock)
 | 
			
		||||
	evaluator := NewPodEvaluator(nil, fakeClock)
 | 
			
		||||
 | 
			
		||||
	// fields use to simulate a pod undergoing termination
 | 
			
		||||
	// note: we set the deletion time in the past
 | 
			
		||||
@@ -135,6 +134,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceLimitsCPU:   resource.MustParse("2m"),
 | 
			
		||||
				api.ResourcePods:        resource.MustParse("1"),
 | 
			
		||||
				api.ResourceCPU:         resource.MustParse("1m"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"init container MEM": {
 | 
			
		||||
@@ -153,6 +153,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceLimitsMemory:   resource.MustParse("2m"),
 | 
			
		||||
				api.ResourcePods:           resource.MustParse("1"),
 | 
			
		||||
				api.ResourceMemory:         resource.MustParse("1m"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"init container local ephemeral storage": {
 | 
			
		||||
@@ -171,6 +172,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"),
 | 
			
		||||
				api.ResourceLimitsEphemeralStorage:   resource.MustParse("64Mi"),
 | 
			
		||||
				api.ResourcePods:                     resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"container CPU": {
 | 
			
		||||
@@ -189,6 +191,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceLimitsCPU:   resource.MustParse("2m"),
 | 
			
		||||
				api.ResourcePods:        resource.MustParse("1"),
 | 
			
		||||
				api.ResourceCPU:         resource.MustParse("1m"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"container MEM": {
 | 
			
		||||
@@ -207,6 +210,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceLimitsMemory:   resource.MustParse("2m"),
 | 
			
		||||
				api.ResourcePods:           resource.MustParse("1"),
 | 
			
		||||
				api.ResourceMemory:         resource.MustParse("1m"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"container local ephemeral storage": {
 | 
			
		||||
@@ -225,6 +229,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceRequestsEphemeralStorage: resource.MustParse("32Mi"),
 | 
			
		||||
				api.ResourceLimitsEphemeralStorage:   resource.MustParse("64Mi"),
 | 
			
		||||
				api.ResourcePods:                     resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"init container maximums override sum of containers": {
 | 
			
		||||
@@ -292,6 +297,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourcePods:           resource.MustParse("1"),
 | 
			
		||||
				api.ResourceCPU:            resource.MustParse("4"),
 | 
			
		||||
				api.ResourceMemory:         resource.MustParse("100M"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"pod deletion timestamp exceeded": {
 | 
			
		||||
@@ -321,7 +327,9 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			usage: api.ResourceList{},
 | 
			
		||||
			usage: api.ResourceList{
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"pod deletion timestamp not exceeded": {
 | 
			
		||||
			pod: &api.Pod{
 | 
			
		||||
@@ -352,6 +360,7 @@ func TestPodEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceLimitsCPU:   resource.MustParse("2"),
 | 
			
		||||
				api.ResourcePods:        resource.MustParse("1"),
 | 
			
		||||
				api.ResourceCPU:         resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "pods"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,33 +17,34 @@ limitations under the License.
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/clock"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewRegistry returns a registry that knows how to deal with core kubernetes resources
 | 
			
		||||
// If an informer factory is provided, evaluators will use them.
 | 
			
		||||
func NewRegistry(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Registry {
 | 
			
		||||
	pod := NewPodEvaluator(kubeClient, f, clock.RealClock{})
 | 
			
		||||
	service := NewServiceEvaluator(kubeClient, f)
 | 
			
		||||
	replicationController := NewReplicationControllerEvaluator(kubeClient, f)
 | 
			
		||||
	resourceQuota := NewResourceQuotaEvaluator(kubeClient, f)
 | 
			
		||||
	secret := NewSecretEvaluator(kubeClient, f)
 | 
			
		||||
	configMap := NewConfigMapEvaluator(kubeClient, f)
 | 
			
		||||
	persistentVolumeClaim := NewPersistentVolumeClaimEvaluator(kubeClient, f)
 | 
			
		||||
	return &generic.GenericRegistry{
 | 
			
		||||
		InternalEvaluators: map[schema.GroupKind]quota.Evaluator{
 | 
			
		||||
			pod.GroupKind():                   pod,
 | 
			
		||||
			service.GroupKind():               service,
 | 
			
		||||
			replicationController.GroupKind(): replicationController,
 | 
			
		||||
			secret.GroupKind():                secret,
 | 
			
		||||
			configMap.GroupKind():             configMap,
 | 
			
		||||
			resourceQuota.GroupKind():         resourceQuota,
 | 
			
		||||
			persistentVolumeClaim.GroupKind(): persistentVolumeClaim,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
// legacyObjectCountAliases are what we used to do simple object counting quota with mapped to alias
 | 
			
		||||
var legacyObjectCountAliases = map[schema.GroupVersionResource]api.ResourceName{
 | 
			
		||||
	v1.SchemeGroupVersion.WithResource("configmaps"):             api.ResourceConfigMaps,
 | 
			
		||||
	v1.SchemeGroupVersion.WithResource("resourcequotas"):         api.ResourceQuotas,
 | 
			
		||||
	v1.SchemeGroupVersion.WithResource("replicationcontrollers"): api.ResourceReplicationControllers,
 | 
			
		||||
	v1.SchemeGroupVersion.WithResource("secrets"):                api.ResourceSecrets,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewEvaluators returns the list of static evaluators that manage more than counts
 | 
			
		||||
func NewEvaluators(f quota.ListerForResourceFunc) []quota.Evaluator {
 | 
			
		||||
	// these evaluators have special logic
 | 
			
		||||
	result := []quota.Evaluator{
 | 
			
		||||
		NewPodEvaluator(f, clock.RealClock{}),
 | 
			
		||||
		NewServiceEvaluator(f),
 | 
			
		||||
		NewPersistentVolumeClaimEvaluator(f),
 | 
			
		||||
	}
 | 
			
		||||
	// these evaluators require an alias for backwards compatibility
 | 
			
		||||
	for gvr, alias := range legacyObjectCountAliases {
 | 
			
		||||
		result = append(result,
 | 
			
		||||
			generic.NewObjectCountEvaluator(false, gvr.GroupResource(), generic.ListResourceUsingListerFunc(f, gvr), alias))
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// listReplicationControllersByNamespaceFuncUsingClient returns a replicationController listing function based on the provided client.
 | 
			
		||||
func listReplicationControllersByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().ReplicationControllers(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewReplicationControllerEvaluator returns an evaluator that can evaluate replicationControllers
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewReplicationControllerEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listReplicationControllersByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("replicationcontrollers"))
 | 
			
		||||
	}
 | 
			
		||||
	return &generic.ObjectCountEvaluator{
 | 
			
		||||
		AllowCreateOnUpdate: false,
 | 
			
		||||
		InternalGroupKind:   api.Kind("ReplicationController"),
 | 
			
		||||
		ResourceName:        api.ResourceReplicationControllers,
 | 
			
		||||
		ListFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// listResourceQuotasByNamespaceFuncUsingClient returns a resourceQuota listing function based on the provided client.
 | 
			
		||||
func listResourceQuotasByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().ResourceQuotas(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewResourceQuotaEvaluator returns an evaluator that can evaluate resourceQuotas
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewResourceQuotaEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listResourceQuotasByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("resourcequotas"))
 | 
			
		||||
	}
 | 
			
		||||
	return &generic.ObjectCountEvaluator{
 | 
			
		||||
		AllowCreateOnUpdate: false,
 | 
			
		||||
		InternalGroupKind:   api.Kind("ResourceQuota"),
 | 
			
		||||
		ResourceName:        api.ResourceQuotas,
 | 
			
		||||
		ListFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// listSecretsByNamespaceFuncUsingClient returns a secret listing function based on the provided client.
 | 
			
		||||
func listSecretsByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().Secrets(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSecretEvaluator returns an evaluator that can evaluate secrets
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewSecretEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listSecretsByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("secrets"))
 | 
			
		||||
	}
 | 
			
		||||
	return &generic.ObjectCountEvaluator{
 | 
			
		||||
		AllowCreateOnUpdate: false,
 | 
			
		||||
		InternalGroupKind:   api.Kind("Secret"),
 | 
			
		||||
		ResourceName:        api.ResourceSecrets,
 | 
			
		||||
		ListFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -18,58 +18,34 @@ package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// the name used for object count quota
 | 
			
		||||
var serviceObjectCountName = generic.ObjectCountQuotaResourceNameFor(v1.SchemeGroupVersion.WithResource("services").GroupResource())
 | 
			
		||||
 | 
			
		||||
// serviceResources are the set of resources managed by quota associated with services.
 | 
			
		||||
var serviceResources = []api.ResourceName{
 | 
			
		||||
	serviceObjectCountName,
 | 
			
		||||
	api.ResourceServices,
 | 
			
		||||
	api.ResourceServicesNodePorts,
 | 
			
		||||
	api.ResourceServicesLoadBalancers,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// listServicesByNamespaceFuncUsingClient returns a service listing function based on the provided client.
 | 
			
		||||
func listServicesByNamespaceFuncUsingClient(kubeClient clientset.Interface) generic.ListFuncByNamespace {
 | 
			
		||||
	// TODO: ideally, we could pass dynamic client pool down into this code, and have one way of doing this.
 | 
			
		||||
	// unfortunately, dynamic client works with Unstructured objects, and when we calculate Usage, we require
 | 
			
		||||
	// structured objects.
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		itemList, err := kubeClient.CoreV1().Services(namespace).List(options)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results := make([]runtime.Object, 0, len(itemList.Items))
 | 
			
		||||
		for i := range itemList.Items {
 | 
			
		||||
			results = append(results, &itemList.Items[i])
 | 
			
		||||
		}
 | 
			
		||||
		return results, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewServiceEvaluator returns an evaluator that can evaluate services
 | 
			
		||||
// if the specified shared informer factory is not nil, evaluator may use it to support listing functions.
 | 
			
		||||
func NewServiceEvaluator(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := listServicesByNamespaceFuncUsingClient(kubeClient)
 | 
			
		||||
	if f != nil {
 | 
			
		||||
		listFuncByNamespace = generic.ListResourceUsingInformerFunc(f, v1.SchemeGroupVersion.WithResource("services"))
 | 
			
		||||
	}
 | 
			
		||||
	return &serviceEvaluator{
 | 
			
		||||
		listFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
	}
 | 
			
		||||
// NewServiceEvaluator returns an evaluator that can evaluate services.
 | 
			
		||||
func NewServiceEvaluator(f quota.ListerForResourceFunc) quota.Evaluator {
 | 
			
		||||
	listFuncByNamespace := generic.ListResourceUsingListerFunc(f, v1.SchemeGroupVersion.WithResource("services"))
 | 
			
		||||
	serviceEvaluator := &serviceEvaluator{listFuncByNamespace: listFuncByNamespace}
 | 
			
		||||
	return serviceEvaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// serviceEvaluator knows how to measure usage for services.
 | 
			
		||||
@@ -80,31 +56,13 @@ type serviceEvaluator struct {
 | 
			
		||||
 | 
			
		||||
// Constraints verifies that all required resources are present on the item
 | 
			
		||||
func (p *serviceEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
 | 
			
		||||
	service, ok := item.(*api.Service)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("unexpected input object %v", item)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	requiredSet := quota.ToSet(required)
 | 
			
		||||
	missingSet := sets.NewString()
 | 
			
		||||
	serviceUsage, err := p.Usage(service)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	serviceSet := quota.ToSet(quota.ResourceNames(serviceUsage))
 | 
			
		||||
	if diff := requiredSet.Difference(serviceSet); len(diff) > 0 {
 | 
			
		||||
		missingSet.Insert(diff.List()...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(missingSet) == 0 {
 | 
			
		||||
	// this is a no-op for services
 | 
			
		||||
	return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ","))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GroupKind that this evaluator tracks
 | 
			
		||||
func (p *serviceEvaluator) GroupKind() schema.GroupKind {
 | 
			
		||||
	return api.Kind("Service")
 | 
			
		||||
// GroupResource that this evaluator tracks
 | 
			
		||||
func (p *serviceEvaluator) GroupResource() schema.GroupResource {
 | 
			
		||||
	return v1.SchemeGroupVersion.WithResource("services").GroupResource()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handles returns true of the evaluator should handle the specified operation.
 | 
			
		||||
@@ -149,6 +107,7 @@ func (p *serviceEvaluator) Usage(item runtime.Object) (api.ResourceList, error)
 | 
			
		||||
	}
 | 
			
		||||
	ports := len(svc.Spec.Ports)
 | 
			
		||||
	// default service usage
 | 
			
		||||
	result[serviceObjectCountName] = *(resource.NewQuantity(1, resource.DecimalSI))
 | 
			
		||||
	result[api.ResourceServices] = *(resource.NewQuantity(1, resource.DecimalSI))
 | 
			
		||||
	result[api.ResourceServicesLoadBalancers] = resource.Quantity{Format: resource.DecimalSI}
 | 
			
		||||
	result[api.ResourceServicesNodePorts] = resource.Quantity{Format: resource.DecimalSI}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,14 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	"k8s.io/client-go/kubernetes/fake"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestServiceEvaluatorMatchesResources(t *testing.T) {
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewServiceEvaluator(kubeClient, nil)
 | 
			
		||||
	evaluator := NewServiceEvaluator(nil)
 | 
			
		||||
	// we give a lot of resources
 | 
			
		||||
	input := []api.ResourceName{
 | 
			
		||||
		api.ResourceConfigMaps,
 | 
			
		||||
@@ -49,8 +49,7 @@ func TestServiceEvaluatorMatchesResources(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewServiceEvaluator(kubeClient, nil)
 | 
			
		||||
	evaluator := NewServiceEvaluator(nil)
 | 
			
		||||
	testCases := map[string]struct {
 | 
			
		||||
		service *api.Service
 | 
			
		||||
		usage   api.ResourceList
 | 
			
		||||
@@ -65,6 +64,7 @@ func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceServicesNodePorts:     resource.MustParse("0"),
 | 
			
		||||
				api.ResourceServicesLoadBalancers: resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServices:              resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"loadbalancer_ports": {
 | 
			
		||||
@@ -82,6 +82,7 @@ func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceServicesNodePorts:     resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServicesLoadBalancers: resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServices:              resource.MustParse("1"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"clusterip": {
 | 
			
		||||
@@ -94,6 +95,7 @@ func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceServices:                                                                resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServicesNodePorts:                                                       resource.MustParse("0"),
 | 
			
		||||
				api.ResourceServicesLoadBalancers:                                                   resource.MustParse("0"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"nodeports": {
 | 
			
		||||
@@ -111,6 +113,7 @@ func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceServices:                                                                resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServicesNodePorts:                                                       resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServicesLoadBalancers:                                                   resource.MustParse("0"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"multi-nodeports": {
 | 
			
		||||
@@ -131,6 +134,7 @@ func TestServiceEvaluatorUsage(t *testing.T) {
 | 
			
		||||
				api.ResourceServices:                                                                resource.MustParse("1"),
 | 
			
		||||
				api.ResourceServicesNodePorts:                                                       resource.MustParse("2"),
 | 
			
		||||
				api.ResourceServicesLoadBalancers:                                                   resource.MustParse("0"),
 | 
			
		||||
				generic.ObjectCountQuotaResourceNameFor(schema.GroupResource{Resource: "services"}): resource.MustParse("1"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -198,8 +202,7 @@ func TestServiceConstraintsFunc(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeClient := fake.NewSimpleClientset()
 | 
			
		||||
	evaluator := NewServiceEvaluator(kubeClient, nil)
 | 
			
		||||
	evaluator := NewServiceEvaluator(nil)
 | 
			
		||||
	for testName, test := range testCases {
 | 
			
		||||
		err := evaluator.Constraints(test.required, test.service)
 | 
			
		||||
		switch {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ load(
 | 
			
		||||
go_library(
 | 
			
		||||
    name = "go_default_library",
 | 
			
		||||
    srcs = [
 | 
			
		||||
        "configuration.go",
 | 
			
		||||
        "evaluator.go",
 | 
			
		||||
        "registry.go",
 | 
			
		||||
    ],
 | 
			
		||||
@@ -16,12 +17,12 @@ go_library(
 | 
			
		||||
        "//pkg/api:go_default_library",
 | 
			
		||||
        "//pkg/quota: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/labels:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/tools/cache:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								pkg/quota/generic/configuration.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/quota/generic/configuration.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package generic
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// implements a basic configuration
 | 
			
		||||
type simpleConfiguration struct {
 | 
			
		||||
	evaluators       []quota.Evaluator
 | 
			
		||||
	ignoredResources map[schema.GroupResource]struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfiguration creates a quota configuration
 | 
			
		||||
func NewConfiguration(evaluators []quota.Evaluator, ignoredResources map[schema.GroupResource]struct{}) quota.Configuration {
 | 
			
		||||
	return &simpleConfiguration{
 | 
			
		||||
		evaluators:       evaluators,
 | 
			
		||||
		ignoredResources: ignoredResources,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *simpleConfiguration) IgnoredResources() map[schema.GroupResource]struct{} {
 | 
			
		||||
	return c.ignoredResources
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *simpleConfiguration) Evaluators() []quota.Evaluator {
 | 
			
		||||
	return c.evaluators
 | 
			
		||||
}
 | 
			
		||||
@@ -20,33 +20,51 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/labels"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ListResourceUsingInformerFunc returns a listing function based on the shared informer factory for the specified resource.
 | 
			
		||||
func ListResourceUsingInformerFunc(f informers.SharedInformerFactory, resource schema.GroupVersionResource) ListFuncByNamespace {
 | 
			
		||||
	return func(namespace string, options metav1.ListOptions) ([]runtime.Object, error) {
 | 
			
		||||
		labelSelector, err := labels.Parse(options.LabelSelector)
 | 
			
		||||
// InformerForResourceFunc knows how to provision an informer
 | 
			
		||||
type InformerForResourceFunc func(schema.GroupVersionResource) (informers.GenericInformer, error)
 | 
			
		||||
 | 
			
		||||
// ListerFuncForResourceFunc knows how to provision a lister from an informer func
 | 
			
		||||
func ListerFuncForResourceFunc(f InformerForResourceFunc) quota.ListerForResourceFunc {
 | 
			
		||||
	return func(gvr schema.GroupVersionResource) (cache.GenericLister, error) {
 | 
			
		||||
		informer, err := f(gvr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		informer, err := f.ForResource(resource)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return informer.Lister().ByNamespace(namespace).List(labelSelector)
 | 
			
		||||
		return informer.Lister(), nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListResourceUsingListerFunc returns a listing function based on the shared informer factory for the specified resource.
 | 
			
		||||
func ListResourceUsingListerFunc(l quota.ListerForResourceFunc, resource schema.GroupVersionResource) ListFuncByNamespace {
 | 
			
		||||
	return func(namespace string) ([]runtime.Object, error) {
 | 
			
		||||
		lister, err := l(resource)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		return lister.ByNamespace(namespace).List(labels.Everything())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ObjectCountQuotaResourceNameFor returns the object count quota name for specified groupResource
 | 
			
		||||
func ObjectCountQuotaResourceNameFor(groupResource schema.GroupResource) api.ResourceName {
 | 
			
		||||
	if len(groupResource.Group) == 0 {
 | 
			
		||||
		return api.ResourceName("count/" + groupResource.Resource)
 | 
			
		||||
	}
 | 
			
		||||
	return api.ResourceName("count/" + groupResource.Resource + "." + groupResource.Group)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListFuncByNamespace knows how to list resources in a namespace
 | 
			
		||||
type ListFuncByNamespace func(namespace string, options metav1.ListOptions) ([]runtime.Object, error)
 | 
			
		||||
type ListFuncByNamespace func(namespace string) ([]runtime.Object, error)
 | 
			
		||||
 | 
			
		||||
// MatchesScopeFunc knows how to evaluate if an object matches a scope
 | 
			
		||||
type MatchesScopeFunc func(scope api.ResourceQuotaScope, object runtime.Object) (bool, error)
 | 
			
		||||
@@ -91,9 +109,7 @@ func CalculateUsageStats(options quota.UsageStatsOptions,
 | 
			
		||||
	for _, resourceName := range options.Resources {
 | 
			
		||||
		result.Used[resourceName] = resource.Quantity{Format: resource.DecimalSI}
 | 
			
		||||
	}
 | 
			
		||||
	items, err := listFunc(options.Namespace, metav1.ListOptions{
 | 
			
		||||
		LabelSelector: labels.Everything().String(),
 | 
			
		||||
	})
 | 
			
		||||
	items, err := listFunc(options.Namespace)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return result, fmt.Errorf("failed to list content: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -121,63 +137,86 @@ func CalculateUsageStats(options quota.UsageStatsOptions,
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ObjectCountEvaluator provides an implementation for quota.Evaluator
 | 
			
		||||
// objectCountEvaluator provides an implementation for quota.Evaluator
 | 
			
		||||
// that associates usage of the specified resource based on the number of items
 | 
			
		||||
// returned by the specified listing function.
 | 
			
		||||
type ObjectCountEvaluator struct {
 | 
			
		||||
	// AllowCreateOnUpdate if true will ensure the evaluator tracks create
 | 
			
		||||
type objectCountEvaluator struct {
 | 
			
		||||
	// allowCreateOnUpdate if true will ensure the evaluator tracks create
 | 
			
		||||
	// and update operations.
 | 
			
		||||
	AllowCreateOnUpdate bool
 | 
			
		||||
	// GroupKind that this evaluator tracks.
 | 
			
		||||
	InternalGroupKind schema.GroupKind
 | 
			
		||||
	allowCreateOnUpdate bool
 | 
			
		||||
	// GroupResource that this evaluator tracks.
 | 
			
		||||
	// It is used to construct a generic object count quota name
 | 
			
		||||
	groupResource schema.GroupResource
 | 
			
		||||
	// A function that knows how to list resources by namespace.
 | 
			
		||||
	// TODO move to dynamic client in future
 | 
			
		||||
	ListFuncByNamespace ListFuncByNamespace
 | 
			
		||||
	// Name associated with this resource in the quota.
 | 
			
		||||
	ResourceName api.ResourceName
 | 
			
		||||
	listFuncByNamespace ListFuncByNamespace
 | 
			
		||||
	// Names associated with this resource in the quota for generic counting.
 | 
			
		||||
	resourceNames []api.ResourceName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Constraints returns an error if the configured resource name is not in the required set.
 | 
			
		||||
func (o *ObjectCountEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
 | 
			
		||||
	if !quota.Contains(required, o.ResourceName) {
 | 
			
		||||
		return fmt.Errorf("missing %s", o.ResourceName)
 | 
			
		||||
	}
 | 
			
		||||
func (o *objectCountEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error {
 | 
			
		||||
	// no-op for object counting
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GroupKind that this evaluator tracks
 | 
			
		||||
func (o *ObjectCountEvaluator) GroupKind() schema.GroupKind {
 | 
			
		||||
	return o.InternalGroupKind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handles returns true if the object count evaluator needs to track this attributes.
 | 
			
		||||
func (o *ObjectCountEvaluator) Handles(a admission.Attributes) bool {
 | 
			
		||||
func (o *objectCountEvaluator) Handles(a admission.Attributes) bool {
 | 
			
		||||
	operation := a.GetOperation()
 | 
			
		||||
	return operation == admission.Create || (o.AllowCreateOnUpdate && operation == admission.Update)
 | 
			
		||||
	return operation == admission.Create || (o.allowCreateOnUpdate && operation == admission.Update)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Matches returns true if the evaluator matches the specified quota with the provided input item
 | 
			
		||||
func (o *ObjectCountEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error) {
 | 
			
		||||
func (o *objectCountEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error) {
 | 
			
		||||
	return Matches(resourceQuota, item, o.MatchingResources, MatchesNoScopeFunc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MatchingResources takes the input specified list of resources and returns the set of resources it matches.
 | 
			
		||||
func (o *ObjectCountEvaluator) MatchingResources(input []api.ResourceName) []api.ResourceName {
 | 
			
		||||
	return quota.Intersection(input, []api.ResourceName{o.ResourceName})
 | 
			
		||||
func (o *objectCountEvaluator) MatchingResources(input []api.ResourceName) []api.ResourceName {
 | 
			
		||||
	return quota.Intersection(input, o.resourceNames)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Usage returns the resource usage for the specified object
 | 
			
		||||
func (o *ObjectCountEvaluator) Usage(object runtime.Object) (api.ResourceList, error) {
 | 
			
		||||
func (o *objectCountEvaluator) Usage(object runtime.Object) (api.ResourceList, error) {
 | 
			
		||||
	quantity := resource.NewQuantity(1, resource.DecimalSI)
 | 
			
		||||
	return api.ResourceList{
 | 
			
		||||
		o.ResourceName: *quantity,
 | 
			
		||||
	}, nil
 | 
			
		||||
	resourceList := api.ResourceList{}
 | 
			
		||||
	for _, resourceName := range o.resourceNames {
 | 
			
		||||
		resourceList[resourceName] = *quantity
 | 
			
		||||
	}
 | 
			
		||||
	return resourceList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GroupResource tracked by this evaluator
 | 
			
		||||
func (o *objectCountEvaluator) GroupResource() schema.GroupResource {
 | 
			
		||||
	return o.groupResource
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UsageStats calculates aggregate usage for the object.
 | 
			
		||||
func (o *ObjectCountEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) {
 | 
			
		||||
	return CalculateUsageStats(options, o.ListFuncByNamespace, MatchesNoScopeFunc, o.Usage)
 | 
			
		||||
func (o *objectCountEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) {
 | 
			
		||||
	return CalculateUsageStats(options, o.listFuncByNamespace, MatchesNoScopeFunc, o.Usage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify implementation of interface at compile time.
 | 
			
		||||
var _ quota.Evaluator = &ObjectCountEvaluator{}
 | 
			
		||||
var _ quota.Evaluator = &objectCountEvaluator{}
 | 
			
		||||
 | 
			
		||||
// NewObjectCountEvaluator returns an evaluator that can perform generic
 | 
			
		||||
// object quota counting.  It allows an optional alias for backwards compatibilty
 | 
			
		||||
// purposes for the legacy object counting names in quota.  Unless its supporting
 | 
			
		||||
// backward compatibility, alias should not be used.
 | 
			
		||||
func NewObjectCountEvaluator(
 | 
			
		||||
	allowCreateOnUpdate bool,
 | 
			
		||||
	groupResource schema.GroupResource, listFuncByNamespace ListFuncByNamespace,
 | 
			
		||||
	alias api.ResourceName) quota.Evaluator {
 | 
			
		||||
 | 
			
		||||
	resourceNames := []api.ResourceName{ObjectCountQuotaResourceNameFor(groupResource)}
 | 
			
		||||
	if len(alias) > 0 {
 | 
			
		||||
		resourceNames = append(resourceNames, alias)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &objectCountEvaluator{
 | 
			
		||||
		allowCreateOnUpdate: allowCreateOnUpdate,
 | 
			
		||||
		groupResource:       groupResource,
 | 
			
		||||
		listFuncByNamespace: listFuncByNamespace,
 | 
			
		||||
		resourceNames:       resourceNames,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,20 +17,65 @@ limitations under the License.
 | 
			
		||||
package generic
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Ensure it implements the required interface
 | 
			
		||||
var _ quota.Registry = &GenericRegistry{}
 | 
			
		||||
 | 
			
		||||
// GenericRegistry implements Registry
 | 
			
		||||
type GenericRegistry struct {
 | 
			
		||||
	// internal evaluators by group kind
 | 
			
		||||
	InternalEvaluators map[schema.GroupKind]quota.Evaluator
 | 
			
		||||
// implements a basic registry
 | 
			
		||||
type simpleRegistry struct {
 | 
			
		||||
	lock sync.RWMutex
 | 
			
		||||
	// evaluators tracked by the registry
 | 
			
		||||
	evaluators map[schema.GroupResource]quota.Evaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Evaluators returns the map of evaluators by groupKind
 | 
			
		||||
func (r *GenericRegistry) Evaluators() map[schema.GroupKind]quota.Evaluator {
 | 
			
		||||
	return r.InternalEvaluators
 | 
			
		||||
// NewRegistry creates a simple registry with initial list of evaluators
 | 
			
		||||
func NewRegistry(evaluators []quota.Evaluator) quota.Registry {
 | 
			
		||||
	return &simpleRegistry{
 | 
			
		||||
		evaluators: evaluatorsByGroupResource(evaluators),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *simpleRegistry) Add(e quota.Evaluator) {
 | 
			
		||||
	r.lock.Lock()
 | 
			
		||||
	defer r.lock.Unlock()
 | 
			
		||||
	r.evaluators[e.GroupResource()] = e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *simpleRegistry) Remove(e quota.Evaluator) {
 | 
			
		||||
	r.lock.Lock()
 | 
			
		||||
	defer r.lock.Unlock()
 | 
			
		||||
	delete(r.evaluators, e.GroupResource())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *simpleRegistry) Get(gr schema.GroupResource) quota.Evaluator {
 | 
			
		||||
	r.lock.RLock()
 | 
			
		||||
	defer r.lock.RUnlock()
 | 
			
		||||
	return r.evaluators[gr]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *simpleRegistry) List() []quota.Evaluator {
 | 
			
		||||
	r.lock.RLock()
 | 
			
		||||
	defer r.lock.RUnlock()
 | 
			
		||||
 | 
			
		||||
	return evaluatorsList(r.evaluators)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evaluatorsByGroupResource converts a list of evaluators to a map by group resource.
 | 
			
		||||
func evaluatorsByGroupResource(items []quota.Evaluator) map[schema.GroupResource]quota.Evaluator {
 | 
			
		||||
	result := map[schema.GroupResource]quota.Evaluator{}
 | 
			
		||||
	for _, item := range items {
 | 
			
		||||
		result[item.GroupResource()] = item
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// evaluatorsList converts a map of evaluators to list
 | 
			
		||||
func evaluatorsList(input map[schema.GroupResource]quota.Evaluator) []quota.Evaluator {
 | 
			
		||||
	var result []quota.Evaluator
 | 
			
		||||
	for _, item := range input {
 | 
			
		||||
		result = append(result, item)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ go_library(
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//pkg/quota:go_default_library",
 | 
			
		||||
        "//pkg/quota/evaluator/core:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,15 +17,42 @@ limitations under the License.
 | 
			
		||||
package install
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/evaluator/core"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewRegistry returns a registry of quota evaluators.
 | 
			
		||||
// If a shared informer factory is provided, it is used by evaluators rather than performing direct queries.
 | 
			
		||||
func NewRegistry(kubeClient clientset.Interface, f informers.SharedInformerFactory) quota.Registry {
 | 
			
		||||
	// TODO: when quota supports resources in other api groups, we will need to merge
 | 
			
		||||
	return core.NewRegistry(kubeClient, f)
 | 
			
		||||
// NewQuotaConfigurationForAdmission returns a quota configuration for admission control.
 | 
			
		||||
func NewQuotaConfigurationForAdmission() quota.Configuration {
 | 
			
		||||
	evaluators := core.NewEvaluators(nil)
 | 
			
		||||
	return generic.NewConfiguration(evaluators, DefaultIgnoredResources())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewQuotaConfigurationForControllers returns a quota configuration for controllers.
 | 
			
		||||
func NewQuotaConfigurationForControllers(f quota.ListerForResourceFunc) quota.Configuration {
 | 
			
		||||
	evaluators := core.NewEvaluators(f)
 | 
			
		||||
	return generic.NewConfiguration(evaluators, DefaultIgnoredResources())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ignoredResources are ignored by quota by default
 | 
			
		||||
var ignoredResources = map[schema.GroupResource]struct{}{
 | 
			
		||||
	{Group: "extensions", Resource: "replicationcontrollers"}:              {},
 | 
			
		||||
	{Group: "extensions", Resource: "networkpolicies"}:                     {},
 | 
			
		||||
	{Group: "", Resource: "bindings"}:                                      {},
 | 
			
		||||
	{Group: "", Resource: "componentstatuses"}:                             {},
 | 
			
		||||
	{Group: "", Resource: "events"}:                                        {},
 | 
			
		||||
	{Group: "authentication.k8s.io", Resource: "tokenreviews"}:             {},
 | 
			
		||||
	{Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}:      {},
 | 
			
		||||
	{Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}:  {},
 | 
			
		||||
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: {},
 | 
			
		||||
	{Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}:   {},
 | 
			
		||||
	{Group: "apiregistration.k8s.io", Resource: "apiservices"}:             {},
 | 
			
		||||
	{Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}: {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultIgnoredResources returns the default set of resources that quota system
 | 
			
		||||
// should ignore. This is exposed so downstream integrators can have access to them.
 | 
			
		||||
func DefaultIgnoredResources() map[schema.GroupResource]struct{} {
 | 
			
		||||
	return ignoredResources
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import (
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/client-go/tools/cache"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -39,12 +40,12 @@ type UsageStats struct {
 | 
			
		||||
	Used api.ResourceList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Evaluator knows how to evaluate quota usage for a particular group kind
 | 
			
		||||
// Evaluator knows how to evaluate quota usage for a particular group resource
 | 
			
		||||
type Evaluator interface {
 | 
			
		||||
	// Constraints ensures that each required resource is present on item
 | 
			
		||||
	Constraints(required []api.ResourceName, item runtime.Object) error
 | 
			
		||||
	// GroupKind returns the groupKind that this object knows how to evaluate
 | 
			
		||||
	GroupKind() schema.GroupKind
 | 
			
		||||
	// GroupResource returns the groupResource that this object knows how to evaluate
 | 
			
		||||
	GroupResource() schema.GroupResource
 | 
			
		||||
	// Handles determines if quota could be impacted by the specified attribute.
 | 
			
		||||
	// If true, admission control must perform quota processing for the operation, otherwise it is safe to ignore quota.
 | 
			
		||||
	Handles(operation admission.Attributes) bool
 | 
			
		||||
@@ -58,25 +59,25 @@ type Evaluator interface {
 | 
			
		||||
	UsageStats(options UsageStatsOptions) (UsageStats, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Registry holds the list of evaluators associated to a particular group kind
 | 
			
		||||
// Configuration defines how the quota system is configured.
 | 
			
		||||
type Configuration interface {
 | 
			
		||||
	// IgnoredResources are ignored by quota.
 | 
			
		||||
	IgnoredResources() map[schema.GroupResource]struct{}
 | 
			
		||||
	// Evaluators for quota evaluation.
 | 
			
		||||
	Evaluators() []Evaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Registry maintains a list of evaluators
 | 
			
		||||
type Registry interface {
 | 
			
		||||
	// Evaluators returns the set Evaluator objects registered to a groupKind
 | 
			
		||||
	Evaluators() map[schema.GroupKind]Evaluator
 | 
			
		||||
	// Add to registry
 | 
			
		||||
	Add(e Evaluator)
 | 
			
		||||
	// Remove from registry
 | 
			
		||||
	Remove(e Evaluator)
 | 
			
		||||
	// Get by group resource
 | 
			
		||||
	Get(gr schema.GroupResource) Evaluator
 | 
			
		||||
	// List from registry
 | 
			
		||||
	List() []Evaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UnionRegistry combines multiple registries.  Order matters because first registry to claim a GroupKind
 | 
			
		||||
// is the "winner"
 | 
			
		||||
type UnionRegistry []Registry
 | 
			
		||||
 | 
			
		||||
// Evaluators returns a mapping of evaluators by group kind.
 | 
			
		||||
func (r UnionRegistry) Evaluators() map[schema.GroupKind]Evaluator {
 | 
			
		||||
	ret := map[schema.GroupKind]Evaluator{}
 | 
			
		||||
 | 
			
		||||
	for i := len(r) - 1; i >= 0; i-- {
 | 
			
		||||
		for k, v := range r[i].Evaluators() {
 | 
			
		||||
			ret[k] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret
 | 
			
		||||
}
 | 
			
		||||
// ListerForResourceFunc knows how to get a lister for a specific resource
 | 
			
		||||
type ListerForResourceFunc func(schema.GroupVersionResource) (cache.GenericLister, error)
 | 
			
		||||
 
 | 
			
		||||
@@ -247,7 +247,7 @@ func CalculateUsage(namespaceName string, scopes []api.ResourceQuotaScope, hardL
 | 
			
		||||
	// look to measure updated usage stats for
 | 
			
		||||
	hardResources := ResourceNames(hardLimits)
 | 
			
		||||
	potentialResources := []api.ResourceName{}
 | 
			
		||||
	evaluators := registry.Evaluators()
 | 
			
		||||
	evaluators := registry.List()
 | 
			
		||||
	for _, evaluator := range evaluators {
 | 
			
		||||
		potentialResources = append(potentialResources, evaluator.MatchingResources(hardResources)...)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -55,14 +55,14 @@ type quotaAdmission struct {
 | 
			
		||||
	*admission.Handler
 | 
			
		||||
	config             *resourcequotaapi.Configuration
 | 
			
		||||
	stopCh             <-chan struct{}
 | 
			
		||||
	registry      quota.Registry
 | 
			
		||||
	quotaConfiguration quota.Configuration
 | 
			
		||||
	numEvaluators      int
 | 
			
		||||
	quotaAccessor      *quotaAccessor
 | 
			
		||||
	evaluator          Evaluator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet("aAdmission{})
 | 
			
		||||
var _ = kubeapiserveradmission.WantsQuotaRegistry("aAdmission{})
 | 
			
		||||
var _ = kubeapiserveradmission.WantsQuotaConfiguration("aAdmission{})
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@ import (
 | 
			
		||||
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
			
		||||
@@ -36,8 +35,6 @@ import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
 | 
			
		||||
	informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/controller"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/install"
 | 
			
		||||
	resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
 | 
			
		||||
)
 | 
			
		||||
@@ -135,7 +132,8 @@ func TestAdmissionIgnoresDelete(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -170,7 +168,8 @@ func TestAdmissionIgnoresSubresources(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -214,7 +213,8 @@ func TestAdmitBelowQuotaLimit(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -297,7 +297,8 @@ func TestAdmitHandlesOldObjects(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -403,7 +404,8 @@ func TestAdmitHandlesNegativePVCUpdates(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -469,7 +471,8 @@ func TestAdmitHandlesPVCUpdates(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -567,7 +570,8 @@ func TestAdmitHandlesCreatingUpdates(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -661,7 +665,8 @@ func TestAdmitExceedQuotaLimit(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -705,7 +710,8 @@ func TestAdmitEnforceQuotaConstraints(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -759,7 +765,8 @@ func TestAdmitPodInNamespaceWithoutQuota(t *testing.T) {
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	quotaAccessor.liveLookupCache = liveLookupCache
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -825,7 +832,8 @@ func TestAdmitBelowTerminatingQuotaLimit(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -930,7 +938,8 @@ func TestAdmitBelowBestEffortQuotaLimit(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1022,7 +1031,8 @@ func TestAdmitBestEffortQuotaLimitIgnoresBurstable(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1098,17 +1108,6 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create a dummy evaluator so we can trigger quota
 | 
			
		||||
	podEvaluator := &generic.ObjectCountEvaluator{
 | 
			
		||||
		AllowCreateOnUpdate: false,
 | 
			
		||||
		InternalGroupKind:   api.Kind("Pod"),
 | 
			
		||||
		ResourceName:        api.ResourcePods,
 | 
			
		||||
	}
 | 
			
		||||
	registry := &generic.GenericRegistry{
 | 
			
		||||
		InternalEvaluators: map[schema.GroupKind]quota.Evaluator{
 | 
			
		||||
			podEvaluator.GroupKind(): podEvaluator,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	stopCh := make(chan struct{})
 | 
			
		||||
	defer close(stopCh)
 | 
			
		||||
 | 
			
		||||
@@ -1118,8 +1117,8 @@ func TestAdmissionSetsMissingNamespace(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	evaluator.(*quotaEvaluator).registry = registry
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1164,7 +1163,8 @@ func TestAdmitRejectsNegativeUsage(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1210,7 +1210,8 @@ func TestAdmitWhenUnrelatedResourceExceedsQuota(t *testing.T) {
 | 
			
		||||
	quotaAccessor.client = kubeClient
 | 
			
		||||
	quotaAccessor.lister = informerFactory.Core().InternalVersion().ResourceQuotas().Lister()
 | 
			
		||||
	config := &resourcequotaapi.Configuration{}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1246,7 +1247,8 @@ func TestAdmitLimitedResourceNoQuota(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1279,7 +1281,8 @@ func TestAdmitLimitedResourceNoQuotaIgnoresNonMatchingResources(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1325,7 +1328,8 @@ func TestAdmitLimitedResourceWithQuota(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1383,7 +1387,8 @@ func TestAdmitLimitedResourceWithMultipleQuota(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
@@ -1431,7 +1436,8 @@ func TestAdmitLimitedResourceWithQuotaThatDoesNotCover(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, install.NewRegistry(nil, nil), nil, config, 5, stopCh)
 | 
			
		||||
	quotaConfiguration := install.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	evaluator := NewQuotaEvaluator(quotaAccessor, quotaConfiguration, nil, config, 5, stopCh)
 | 
			
		||||
 | 
			
		||||
	handler := "aAdmission{
 | 
			
		||||
		Handler:   admission.NewHandler(admission.Create, admission.Update),
 | 
			
		||||
 
 | 
			
		||||
@@ -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 "aEvaluator{
 | 
			
		||||
		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)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@ go_library(
 | 
			
		||||
        "//vendor/github.com/onsi/gomega:go_default_library",
 | 
			
		||||
        "//vendor/github.com/stretchr/testify/assert:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/api/core/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/api/scheduling/v1alpha1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/core/v1"
 | 
			
		||||
	extensions "k8s.io/api/extensions/v1beta1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
@@ -409,6 +410,41 @@ var _ = SIGDescribe("ResourceQuota", func() {
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	It("should create a ResourceQuota and capture the life of a replica set.", func() {
 | 
			
		||||
		By("Creating a ResourceQuota")
 | 
			
		||||
		quotaName := "test-quota"
 | 
			
		||||
		resourceQuota := newTestResourceQuota(quotaName)
 | 
			
		||||
		resourceQuota, err := createResourceQuota(f.ClientSet, f.Namespace.Name, resourceQuota)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
		By("Ensuring resource quota status is calculated")
 | 
			
		||||
		usedResources := v1.ResourceList{}
 | 
			
		||||
		usedResources[v1.ResourceQuotas] = resource.MustParse("1")
 | 
			
		||||
		usedResources[v1.ResourceName("count/replicasets.extensions")] = resource.MustParse("0")
 | 
			
		||||
		err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
		By("Creating a ReplicaSet")
 | 
			
		||||
		replicaSet := newTestReplicaSetForQuota("test-rs", "nginx", 0)
 | 
			
		||||
		replicaSet, err = f.ClientSet.Extensions().ReplicaSets(f.Namespace.Name).Create(replicaSet)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
		By("Ensuring resource quota status captures replicaset creation")
 | 
			
		||||
		usedResources = v1.ResourceList{}
 | 
			
		||||
		usedResources[v1.ResourceName("count/replicasets.extensions")] = resource.MustParse("1")
 | 
			
		||||
		err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
		By("Deleting a ReplicaSet")
 | 
			
		||||
		err = f.ClientSet.Extensions().ReplicaSets(f.Namespace.Name).Delete(replicaSet.Name, nil)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
 | 
			
		||||
		By("Ensuring resource quota status released usage")
 | 
			
		||||
		usedResources[v1.ResourceName("count/replicasets.extensions")] = resource.MustParse("0")
 | 
			
		||||
		err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
 | 
			
		||||
		Expect(err).NotTo(HaveOccurred())
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	It("should create a ResourceQuota and capture the life of a persistent volume claim. [sig-storage]", func() {
 | 
			
		||||
		By("Creating a ResourceQuota")
 | 
			
		||||
		quotaName := "test-quota"
 | 
			
		||||
@@ -708,6 +744,8 @@ func newTestResourceQuota(name string) *v1.ResourceQuota {
 | 
			
		||||
	hard[v1.ResourceRequestsStorage] = resource.MustParse("10Gi")
 | 
			
		||||
	hard[core.V1ResourceByStorageClass(classGold, v1.ResourcePersistentVolumeClaims)] = resource.MustParse("10")
 | 
			
		||||
	hard[core.V1ResourceByStorageClass(classGold, v1.ResourceRequestsStorage)] = resource.MustParse("10Gi")
 | 
			
		||||
	// test quota on discovered resource type
 | 
			
		||||
	hard[v1.ResourceName("count/replicasets.extensions")] = resource.MustParse("5")
 | 
			
		||||
	return &v1.ResourceQuota{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{Name: name},
 | 
			
		||||
		Spec:       v1.ResourceQuotaSpec{Hard: hard},
 | 
			
		||||
@@ -784,6 +822,33 @@ func newTestReplicationControllerForQuota(name, image string, replicas int32) *v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newTestReplicaSetForQuota returns a simple replica set
 | 
			
		||||
func newTestReplicaSetForQuota(name, image string, replicas int32) *extensions.ReplicaSet {
 | 
			
		||||
	zero := int64(0)
 | 
			
		||||
	return &extensions.ReplicaSet{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: name,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: extensions.ReplicaSetSpec{
 | 
			
		||||
			Replicas: &replicas,
 | 
			
		||||
			Template: v1.PodTemplateSpec{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Labels: map[string]string{"name": name},
 | 
			
		||||
				},
 | 
			
		||||
				Spec: v1.PodSpec{
 | 
			
		||||
					TerminationGracePeriodSeconds: &zero,
 | 
			
		||||
					Containers: []v1.Container{
 | 
			
		||||
						{
 | 
			
		||||
							Name:  name,
 | 
			
		||||
							Image: image,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newTestServiceForQuota returns a simple service
 | 
			
		||||
func newTestServiceForQuota(name string, serviceType v1.ServiceType) *v1.Service {
 | 
			
		||||
	return &v1.Service{
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ go_test(
 | 
			
		||||
    importpath = "k8s.io/kubernetes/test/integration/quota",
 | 
			
		||||
    tags = ["integration"],
 | 
			
		||||
    deps = [
 | 
			
		||||
        "//pkg/api:go_default_library",
 | 
			
		||||
        "//pkg/api/testapi:go_default_library",
 | 
			
		||||
        "//pkg/client/clientset_generated/internalclientset:go_default_library",
 | 
			
		||||
        "//pkg/client/informers/informers_generated/internalversion:go_default_library",
 | 
			
		||||
@@ -23,6 +22,7 @@ go_test(
 | 
			
		||||
        "//pkg/controller/replication:go_default_library",
 | 
			
		||||
        "//pkg/controller/resourcequota:go_default_library",
 | 
			
		||||
        "//pkg/kubeapiserver/admission:go_default_library",
 | 
			
		||||
        "//pkg/quota/generic:go_default_library",
 | 
			
		||||
        "//pkg/quota/install:go_default_library",
 | 
			
		||||
        "//plugin/pkg/admission/resourcequota:go_default_library",
 | 
			
		||||
        "//plugin/pkg/admission/resourcequota/apis/resourcequota:go_default_library",
 | 
			
		||||
@@ -32,7 +32,6 @@ go_test(
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
 | 
			
		||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
			
		||||
 
 | 
			
		||||
@@ -28,14 +28,12 @@ import (
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/fields"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/labels"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/watch"
 | 
			
		||||
	"k8s.io/client-go/informers"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	restclient "k8s.io/client-go/rest"
 | 
			
		||||
	"k8s.io/client-go/tools/record"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api/testapi"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
 | 
			
		||||
	internalinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
 | 
			
		||||
@@ -43,6 +41,7 @@ import (
 | 
			
		||||
	replicationcontroller "k8s.io/kubernetes/pkg/controller/replication"
 | 
			
		||||
	resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota"
 | 
			
		||||
	kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/quota/generic"
 | 
			
		||||
	quotainstall "k8s.io/kubernetes/pkg/quota/install"
 | 
			
		||||
	"k8s.io/kubernetes/plugin/pkg/admission/resourcequota"
 | 
			
		||||
	resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
 | 
			
		||||
@@ -74,8 +73,8 @@ func TestQuota(t *testing.T) {
 | 
			
		||||
	admission.(kubeadmission.WantsInternalKubeClientSet).SetInternalKubeClientSet(internalClientset)
 | 
			
		||||
	internalInformers := internalinformers.NewSharedInformerFactory(internalClientset, controller.NoResyncPeriodFunc())
 | 
			
		||||
	admission.(kubeadmission.WantsInternalKubeInformerFactory).SetInternalKubeInformerFactory(internalInformers)
 | 
			
		||||
	quotaRegistry := quotainstall.NewRegistry(nil, nil)
 | 
			
		||||
	admission.(kubeadmission.WantsQuotaRegistry).SetQuotaRegistry(quotaRegistry)
 | 
			
		||||
	qca := quotainstall.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	admission.(kubeadmission.WantsQuotaConfiguration).SetQuotaConfiguration(qca)
 | 
			
		||||
	defer close(admissionCh)
 | 
			
		||||
 | 
			
		||||
	masterConfig := framework.NewIntegrationTestMasterConfig()
 | 
			
		||||
@@ -101,22 +100,33 @@ func TestQuota(t *testing.T) {
 | 
			
		||||
	rm.SetEventRecorder(&record.FakeRecorder{})
 | 
			
		||||
	go rm.Run(3, controllerCh)
 | 
			
		||||
 | 
			
		||||
	resourceQuotaRegistry := quotainstall.NewRegistry(clientset, nil)
 | 
			
		||||
	groupKindsToReplenish := []schema.GroupKind{
 | 
			
		||||
		api.Kind("Pod"),
 | 
			
		||||
	}
 | 
			
		||||
	discoveryFunc := clientset.Discovery().ServerPreferredNamespacedResources
 | 
			
		||||
	listerFuncForResource := generic.ListerFuncForResourceFunc(informers.ForResource)
 | 
			
		||||
	qc := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)
 | 
			
		||||
	informersStarted := make(chan struct{})
 | 
			
		||||
	resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:               clientset.Core(),
 | 
			
		||||
		ResourceQuotaInformer:     informers.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:              controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:                  resourceQuotaRegistry,
 | 
			
		||||
		GroupKindsToReplenish:     groupKindsToReplenish,
 | 
			
		||||
		InformerFactory:           informers,
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
		ControllerFactory:         resourcequotacontroller.NewReplenishmentControllerFactory(informers),
 | 
			
		||||
		DiscoveryFunc:             discoveryFunc,
 | 
			
		||||
		IgnoredResourcesFunc:      qc.IgnoredResources,
 | 
			
		||||
		InformersStarted:          informersStarted,
 | 
			
		||||
		Registry:                  generic.NewRegistry(qc.Evaluators()),
 | 
			
		||||
	}
 | 
			
		||||
	go resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions).Run(2, controllerCh)
 | 
			
		||||
	resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	go resourceQuotaController.Run(2, controllerCh)
 | 
			
		||||
 | 
			
		||||
	// Periodically the quota controller to detect new resource types
 | 
			
		||||
	go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, controllerCh)
 | 
			
		||||
 | 
			
		||||
	internalInformers.Start(controllerCh)
 | 
			
		||||
	informers.Start(controllerCh)
 | 
			
		||||
	close(informersStarted)
 | 
			
		||||
 | 
			
		||||
	startTime := time.Now()
 | 
			
		||||
	scale(t, ns2.Name, clientset)
 | 
			
		||||
@@ -158,7 +168,6 @@ func waitForQuota(t *testing.T, quota *v1.ResourceQuota, clientset *clientset.Cl
 | 
			
		||||
		default:
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch cast := event.Object.(type) {
 | 
			
		||||
		case *v1.ResourceQuota:
 | 
			
		||||
			if len(cast.Status.Hard) > 0 {
 | 
			
		||||
@@ -254,7 +263,7 @@ func TestQuotaLimitedResourceDenial(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	quotaRegistry := quotainstall.NewRegistry(nil, nil)
 | 
			
		||||
	qca := quotainstall.NewQuotaConfigurationForAdmission()
 | 
			
		||||
	admission, err := resourcequota.NewResourceQuota(config, 5, admissionCh)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
@@ -262,7 +271,7 @@ func TestQuotaLimitedResourceDenial(t *testing.T) {
 | 
			
		||||
	admission.(kubeadmission.WantsInternalKubeClientSet).SetInternalKubeClientSet(internalClientset)
 | 
			
		||||
	internalInformers := internalinformers.NewSharedInformerFactory(internalClientset, controller.NoResyncPeriodFunc())
 | 
			
		||||
	admission.(kubeadmission.WantsInternalKubeInformerFactory).SetInternalKubeInformerFactory(internalInformers)
 | 
			
		||||
	admission.(kubeadmission.WantsQuotaRegistry).SetQuotaRegistry(quotaRegistry)
 | 
			
		||||
	admission.(kubeadmission.WantsQuotaConfiguration).SetQuotaConfiguration(qca)
 | 
			
		||||
	defer close(admissionCh)
 | 
			
		||||
 | 
			
		||||
	masterConfig := framework.NewIntegrationTestMasterConfig()
 | 
			
		||||
@@ -286,22 +295,33 @@ func TestQuotaLimitedResourceDenial(t *testing.T) {
 | 
			
		||||
	rm.SetEventRecorder(&record.FakeRecorder{})
 | 
			
		||||
	go rm.Run(3, controllerCh)
 | 
			
		||||
 | 
			
		||||
	resourceQuotaRegistry := quotainstall.NewRegistry(clientset, nil)
 | 
			
		||||
	groupKindsToReplenish := []schema.GroupKind{
 | 
			
		||||
		api.Kind("Pod"),
 | 
			
		||||
	}
 | 
			
		||||
	discoveryFunc := clientset.Discovery().ServerPreferredNamespacedResources
 | 
			
		||||
	listerFuncForResource := generic.ListerFuncForResourceFunc(informers.ForResource)
 | 
			
		||||
	qc := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource)
 | 
			
		||||
	informersStarted := make(chan struct{})
 | 
			
		||||
	resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{
 | 
			
		||||
		QuotaClient:               clientset.Core(),
 | 
			
		||||
		ResourceQuotaInformer:     informers.Core().V1().ResourceQuotas(),
 | 
			
		||||
		ResyncPeriod:              controller.NoResyncPeriodFunc,
 | 
			
		||||
		Registry:                  resourceQuotaRegistry,
 | 
			
		||||
		GroupKindsToReplenish:     groupKindsToReplenish,
 | 
			
		||||
		InformerFactory:           informers,
 | 
			
		||||
		ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc,
 | 
			
		||||
		ControllerFactory:         resourcequotacontroller.NewReplenishmentControllerFactory(informers),
 | 
			
		||||
		DiscoveryFunc:             discoveryFunc,
 | 
			
		||||
		IgnoredResourcesFunc:      qc.IgnoredResources,
 | 
			
		||||
		InformersStarted:          informersStarted,
 | 
			
		||||
		Registry:                  generic.NewRegistry(qc.Evaluators()),
 | 
			
		||||
	}
 | 
			
		||||
	go resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions).Run(2, controllerCh)
 | 
			
		||||
	resourceQuotaController, err := resourcequotacontroller.NewResourceQuotaController(resourceQuotaControllerOptions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	go resourceQuotaController.Run(2, controllerCh)
 | 
			
		||||
 | 
			
		||||
	// Periodically the quota controller to detect new resource types
 | 
			
		||||
	go resourceQuotaController.Sync(discoveryFunc, 30*time.Second, controllerCh)
 | 
			
		||||
 | 
			
		||||
	internalInformers.Start(controllerCh)
 | 
			
		||||
	informers.Start(controllerCh)
 | 
			
		||||
	close(informersStarted)
 | 
			
		||||
 | 
			
		||||
	// try to create a pod
 | 
			
		||||
	pod := &v1.Pod{
 | 
			
		||||
@@ -323,6 +343,7 @@ func TestQuotaLimitedResourceDenial(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// now create a covering quota
 | 
			
		||||
	// note: limited resource does a matchContains, so we now have "pods" matching "pods" and "count/pods"
 | 
			
		||||
	quota := &v1.ResourceQuota{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "quota",
 | 
			
		||||
@@ -331,6 +352,7 @@ func TestQuotaLimitedResourceDenial(t *testing.T) {
 | 
			
		||||
		Spec: v1.ResourceQuotaSpec{
 | 
			
		||||
			Hard: v1.ResourceList{
 | 
			
		||||
				v1.ResourcePods:               resource.MustParse("1000"),
 | 
			
		||||
				v1.ResourceName("count/pods"): resource.MustParse("1000"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user