diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index df7dcfe9038..e25efda04ab 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -237,6 +237,7 @@ func StartControllers(s *options.CMServer, kubeClient *client.Client, kubeconfig api.Kind("ReplicationController"), api.Kind("PersistentVolumeClaim"), api.Kind("Secret"), + api.Kind("ConfigMap"), } resourceQuotaControllerOptions := &resourcequotacontroller.ResourceQuotaControllerOptions{ KubeClient: resourceQuotaControllerClient, diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index b037b53c850..56787767cdb 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -140,6 +140,7 @@ var standardQuotaResources = sets.NewString( string(ResourceReplicationControllers), string(ResourceSecrets), string(ResourcePersistentVolumeClaims), + string(ResourceConfigMaps), ) // IsStandardQuotaResourceName returns true if the resource is known to @@ -160,6 +161,7 @@ var standardResources = sets.NewString( string(ResourceServices), string(ResourceReplicationControllers), string(ResourceSecrets), + string(ResourceConfigMaps), string(ResourcePersistentVolumeClaims), string(ResourceStorage), ) @@ -175,6 +177,7 @@ var integerResources = sets.NewString( string(ResourceServices), string(ResourceReplicationControllers), string(ResourceSecrets), + string(ResourceConfigMaps), string(ResourcePersistentVolumeClaims), ) diff --git a/pkg/api/types.go b/pkg/api/types.go index 483864a867f..25f44cd06e1 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -2187,6 +2187,8 @@ const ( ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" // ResourcePersistentVolumeClaims, number ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" // CPU request, in cores. (500m = .5 cores) diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index cb52979602f..060afb8a3e5 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -2644,6 +2644,8 @@ const ( ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" // ResourcePersistentVolumeClaims, number ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" // CPU request, in cores. (500m = .5 cores) diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 6f9cd6bd0be..851e0a0dcc5 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -4082,6 +4082,8 @@ func TestValidateResourceQuota(t *testing.T) { api.ResourceServices: resource.MustParse("0"), api.ResourceReplicationControllers: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), + api.ResourceConfigMaps: resource.MustParse("10"), + api.ResourceSecrets: resource.MustParse("10"), }, } @@ -4129,6 +4131,8 @@ func TestValidateResourceQuota(t *testing.T) { api.ResourceServices: resource.MustParse("-10"), api.ResourceReplicationControllers: resource.MustParse("-10"), api.ResourceQuotas: resource.MustParse("-10"), + api.ResourceConfigMaps: resource.MustParse("-10"), + api.ResourceSecrets: resource.MustParse("-10"), }, } diff --git a/pkg/controller/resourcequota/replenishment_controller.go b/pkg/controller/resourcequota/replenishment_controller.go index 05907da79dc..f02b945a8f0 100644 --- a/pkg/controller/resourcequota/replenishment_controller.go +++ b/pkg/controller/resourcequota/replenishment_controller.go @@ -187,6 +187,22 @@ func (r *replenishmentControllerFactory) NewController(options *ReplenishmentCon DeleteFunc: ObjectReplenishmentDeleteFunc(options), }, ) + case api.Kind("ConfigMap"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().ConfigMaps(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().ConfigMaps(api.NamespaceAll).Watch(options) + }, + }, + &api.ConfigMap{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) default: return nil, fmt.Errorf("no replenishment controller available for %s", options.GroupKind) } diff --git a/pkg/quota/evaluator/core/configmap.go b/pkg/quota/evaluator/core/configmap.go new file mode 100644 index 00000000000..879beb1b935 --- /dev/null +++ b/pkg/quota/evaluator/core/configmap.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewConfigMapEvaluator returns an evaluator that can evaluate configMaps +func NewConfigMapEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourceConfigMaps} + return &generic.GenericEvaluator{ + Name: "Evaluator.ConfigMap", + InternalGroupKind: api.Kind("ConfigMap"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceConfigMaps), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourceConfigMaps), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().ConfigMaps(namespace).List(options) + }, + } +} diff --git a/pkg/quota/evaluator/core/registry.go b/pkg/quota/evaluator/core/registry.go index a9b3c44a975..69d14845594 100644 --- a/pkg/quota/evaluator/core/registry.go +++ b/pkg/quota/evaluator/core/registry.go @@ -30,6 +30,7 @@ func NewRegistry(kubeClient clientset.Interface) quota.Registry { replicationController := NewReplicationControllerEvaluator(kubeClient) resourceQuota := NewResourceQuotaEvaluator(kubeClient) secret := NewSecretEvaluator(kubeClient) + configMap := NewConfigMapEvaluator(kubeClient) persistentVolumeClaim := NewPersistentVolumeClaimEvaluator(kubeClient) return &generic.GenericRegistry{ InternalEvaluators: map[unversioned.GroupKind]quota.Evaluator{ @@ -37,6 +38,7 @@ func NewRegistry(kubeClient clientset.Interface) quota.Registry { service.GroupKind(): service, replicationController.GroupKind(): replicationController, secret.GroupKind(): secret, + configMap.GroupKind(): configMap, resourceQuota.GroupKind(): resourceQuota, persistentVolumeClaim.GroupKind(): persistentVolumeClaim, }, diff --git a/test/e2e/resource_quota.go b/test/e2e/resource_quota.go index 57866ba7eab..c32bb6b4485 100644 --- a/test/e2e/resource_quota.go +++ b/test/e2e/resource_quota.go @@ -182,6 +182,41 @@ var _ = Describe("ResourceQuota", func() { Expect(err).NotTo(HaveOccurred()) }) + It("should create a ResourceQuota and capture the life of a configMap.", func() { + By("Creating a ResourceQuota") + quotaName := "test-quota" + resourceQuota := newTestResourceQuota(quotaName) + resourceQuota, err := createResourceQuota(f.Client, f.Namespace.Name, resourceQuota) + Expect(err).NotTo(HaveOccurred()) + + By("Ensuring resource quota status is calculated") + usedResources := api.ResourceList{} + usedResources[api.ResourceQuotas] = resource.MustParse("1") + err = waitForResourceQuota(f.Client, f.Namespace.Name, quotaName, usedResources) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a ConfigMap") + configMap := newTestConfigMapForQuota("test-configmap") + configMap, err = f.Client.ConfigMaps(f.Namespace.Name).Create(configMap) + Expect(err).NotTo(HaveOccurred()) + + By("Ensuring resource quota status captures configMap creation") + usedResources = api.ResourceList{} + usedResources[api.ResourceQuotas] = resource.MustParse("1") + usedResources[api.ResourceConfigMaps] = resource.MustParse("1") + err = waitForResourceQuota(f.Client, f.Namespace.Name, quotaName, usedResources) + Expect(err).NotTo(HaveOccurred()) + + By("Deleting a ConfigMap") + err = f.Client.ConfigMaps(f.Namespace.Name).Delete(configMap.Name) + Expect(err).NotTo(HaveOccurred()) + + By("Ensuring resource quota status released usage") + usedResources[api.ResourceConfigMaps] = resource.MustParse("0") + err = waitForResourceQuota(f.Client, f.Namespace.Name, quotaName, usedResources) + Expect(err).NotTo(HaveOccurred()) + }) + It("should verify ResourceQuota with terminating scopes.", func() { By("Creating a ResourceQuota with terminating scope") quotaTerminatingName := "quota-terminating" @@ -387,6 +422,8 @@ func newTestResourceQuota(name string) *api.ResourceQuota { hard[api.ResourceQuotas] = resource.MustParse("1") hard[api.ResourceCPU] = resource.MustParse("1") hard[api.ResourceMemory] = resource.MustParse("500Mi") + hard[api.ResourceConfigMaps] = resource.MustParse("2") + hard[api.ResourceSecrets] = resource.MustParse("2") return &api.ResourceQuota{ ObjectMeta: api.ObjectMeta{Name: name}, Spec: api.ResourceQuotaSpec{Hard: hard}, @@ -429,6 +466,17 @@ func newTestServiceForQuota(name string) *api.Service { } } +func newTestConfigMapForQuota(name string) *api.ConfigMap { + return &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Data: map[string]string{ + "a": "b", + }, + } +} + func newTestSecretForQuota(name string) *api.Secret { return &api.Secret{ ObjectMeta: api.ObjectMeta{