diff --git a/plugin/pkg/admission/resourcequota/admission.go b/plugin/pkg/admission/resourcequota/admission.go index 4a4a1524214d2..17fca8bacd623 100644 --- a/plugin/pkg/admission/resourcequota/admission.go +++ b/plugin/pkg/admission/resourcequota/admission.go @@ -182,6 +182,16 @@ func (q *quotaAdmission) Admit(a admission.Attributes) (err error) { return nil } + // Usage of some resources cannot be counted in isolation. For example when + // the resource represents a number of unique references to external + // resource. In such a case an evaluator needs to process other objects in + // the same namespace which needs to be known. + if om, err := api.ObjectMetaFor(inputObject); namespace != "" && err == nil { + if om.Namespace == "" { + om.Namespace = namespace + } + } + // there is at least one quota that definitely matches our object // as a result, we need to measure the usage of this object for quota // on updates, we need to subtract the previous measured usage diff --git a/plugin/pkg/admission/resourcequota/admission_test.go b/plugin/pkg/admission/resourcequota/admission_test.go index 312c294838bb7..0f8720272d899 100644 --- a/plugin/pkg/admission/resourcequota/admission_test.go +++ b/plugin/pkg/admission/resourcequota/admission_test.go @@ -27,10 +27,15 @@ import ( "k8s.io/kubernetes/pkg/admission" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/evaluator/core" + "k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/quota/install" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/sets" ) @@ -566,3 +571,75 @@ func TestHasUsageStats(t *testing.T) { } } } + +// TestAdmissionSetsMissingNamespace verifies that if an object lacks a +// namespace, it will be set. +func TestAdmissionSetsMissingNamespace(t *testing.T) { + namespace := "test" + resourceQuota := &api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{Name: "quota", Namespace: namespace, ResourceVersion: "124"}, + Status: api.ResourceQuotaStatus{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("3"), + }, + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("1"), + }, + }, + } + kubeClient := fake.NewSimpleClientset(resourceQuota) + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}) + + computeResources := []api.ResourceName{ + api.ResourcePods, + api.ResourceCPU, + } + + usageFunc := func(object runtime.Object) api.ResourceList { + pod, ok := object.(*api.Pod) + if !ok { + t.Fatalf("Expected pod, got %T", object) + } + if pod.Namespace != namespace { + t.Errorf("Expected pod with different namespace: %q != %q", pod.Namespace, namespace) + } + return core.PodUsageFunc(pod) + } + + podEvaluator := &generic.GenericEvaluator{ + Name: "Test-Evaluator.Pod", + InternalGroupKind: api.Kind("Pod"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: computeResources, + }, + ConstraintsFunc: core.PodConstraintsFunc, + MatchedResourceNames: computeResources, + MatchesScopeFunc: core.PodMatchesScopeFunc, + UsageFunc: usageFunc, + } + + registry := &generic.GenericRegistry{ + InternalEvaluators: map[unversioned.GroupKind]quota.Evaluator{ + podEvaluator.GroupKind(): podEvaluator, + }, + } + handler := "aAdmission{ + Handler: admission.NewHandler(admission.Create, admission.Update), + client: kubeClient, + indexer: indexer, + registry: registry, + } + handler.indexer.Add(resourceQuota) + newPod := validPod("pod-without-namespace", 1, getResourceRequirements(getResourceList("1", "2Gi"), getResourceList("", ""))) + + // unset the namespace + newPod.ObjectMeta.Namespace = "" + + err := handler.Admit(admission.NewAttributesRecord(newPod, api.Kind("Pod"), namespace, newPod.Name, api.Resource("pods"), "", admission.Create, nil)) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + if newPod.Namespace != namespace { + t.Errorf("Got unexpected pod namespace: %q != %q", newPod.Namespace, namespace) + } +}