let resourcequota evaluator handle uninitialid pod and pvc

This commit is contained in:
Chao Xu 2017-08-22 21:14:13 -07:00
parent e2de110e26
commit 4928c8d1bf
8 changed files with 117 additions and 16 deletions

View File

@ -25,6 +25,7 @@ go_library(
"//pkg/api/helper/qos:go_default_library", "//pkg/api/helper/qos:go_default_library",
"//pkg/api/v1:go_default_library", "//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library", "//pkg/api/validation:go_default_library",
"//pkg/kubeapiserver/admission/util:go_default_library",
"//pkg/quota:go_default_library", "//pkg/quota:go_default_library",
"//pkg/quota/generic:go_default_library", "//pkg/quota/generic:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",

View File

@ -32,6 +32,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/helper" "k8s.io/kubernetes/pkg/api/helper"
k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/quota/generic"
) )
@ -141,8 +142,18 @@ func (p *pvcEvaluator) GroupKind() schema.GroupKind {
} }
// Handles returns true if the evaluator should handle the specified operation. // Handles returns true if the evaluator should handle the specified operation.
func (p *pvcEvaluator) Handles(operation admission.Operation) bool { func (p *pvcEvaluator) Handles(a admission.Attributes) bool {
return admission.Create == operation op := a.GetOperation()
if op == admission.Create {
return true
}
updateUninitialized, err := util.IsUpdatingUninitializedObject(a)
if err != nil {
// fail closed, will try to give an evaluation.
return true
}
// only uninitialized pvc might be updated.
return updateUninitialized
} }
// Matches returns true if the evaluator matches the specified quota with the provided input item // Matches returns true if the evaluator matches the specified quota with the provided input item

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/api/helper/qos" "k8s.io/kubernetes/pkg/api/helper/qos"
k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1" k8s_api_v1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota"
"k8s.io/kubernetes/pkg/quota/generic" "k8s.io/kubernetes/pkg/quota/generic"
) )
@ -131,10 +132,19 @@ func (p *podEvaluator) GroupKind() schema.GroupKind {
return api.Kind("Pod") return api.Kind("Pod")
} }
// Handles returns true of the evaluator should handle the specified operation. // Handles returns true of the evaluator should handle the specified attributes.
func (p *podEvaluator) Handles(operation admission.Operation) bool { func (p *podEvaluator) Handles(a admission.Attributes) bool {
// TODO: update this if/when pods support resizing resource requirements. op := a.GetOperation()
return admission.Create == operation if op == admission.Create {
return true
}
updateUninitialized, err := util.IsUpdatingUninitializedObject(a)
if err != nil {
// fail closed, will try to give an evaluation.
return true
}
// only uninitialized pods might be updated.
return updateUninitialized
} }
// Matches returns true if the evaluator matches the specified quota with the provided input item // Matches returns true if the evaluator matches the specified quota with the provided input item

View File

@ -108,7 +108,8 @@ func (p *serviceEvaluator) GroupKind() schema.GroupKind {
} }
// Handles returns true of the evaluator should handle the specified operation. // Handles returns true of the evaluator should handle the specified operation.
func (p *serviceEvaluator) Handles(operation admission.Operation) bool { func (p *serviceEvaluator) Handles(a admission.Attributes) bool {
operation := a.GetOperation()
// We handle create and update because a service type can change. // We handle create and update because a service type can change.
return admission.Create == operation || admission.Update == operation return admission.Create == operation || admission.Update == operation
} }

View File

@ -150,8 +150,9 @@ func (o *ObjectCountEvaluator) GroupKind() schema.GroupKind {
return o.InternalGroupKind return o.InternalGroupKind
} }
// Handles returns true if the object count evaluator needs to track this operation. // Handles returns true if the object count evaluator needs to track this attributes.
func (o *ObjectCountEvaluator) Handles(operation admission.Operation) 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)
} }

View File

@ -45,9 +45,9 @@ type Evaluator interface {
Constraints(required []api.ResourceName, item runtime.Object) error Constraints(required []api.ResourceName, item runtime.Object) error
// GroupKind returns the groupKind that this object knows how to evaluate // GroupKind returns the groupKind that this object knows how to evaluate
GroupKind() schema.GroupKind GroupKind() schema.GroupKind
// Handles determines if quota could be impacted by the specified operation. // 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. // If true, admission control must perform quota processing for the operation, otherwise it is safe to ignore quota.
Handles(operation admission.Operation) bool Handles(operation admission.Attributes) bool
// Matches returns true if the specified quota matches the input item // Matches returns true if the specified quota matches the input item
Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) (bool, error)
// MatchingResources takes the input specified list of resources and returns the set of resources evaluator matches. // MatchingResources takes the input specified list of resources and returns the set of resources evaluator matches.

View File

@ -375,8 +375,7 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
return quotas, nil return quotas, nil
} }
op := a.GetOperation() if !evaluator.Handles(a) {
if !evaluator.Handles(op) {
return quotas, nil return quotas, nil
} }
@ -463,7 +462,7 @@ func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.At
return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage))) return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage)))
} }
if admission.Update == op { if admission.Update == a.GetOperation() {
prevItem := a.GetOldObject() prevItem := a.GetOldObject()
if prevItem == nil { if prevItem == nil {
return nil, admission.NewForbidden(a, fmt.Errorf("unable to get previous usage since prior version of object was not found")) return nil, admission.NewForbidden(a, fmt.Errorf("unable to get previous usage since prior version of object was not found"))
@ -529,8 +528,7 @@ func (e *quotaEvaluator) Evaluate(a admission.Attributes) error {
} }
// for this kind, check if the operation could mutate any quota resources // for this kind, check if the operation could mutate any quota resources
// if no resources tracked by quota are impacted, then just return // if no resources tracked by quota are impacted, then just return
op := a.GetOperation() if !evaluator.Handles(a) {
if !evaluator.Handles(op) {
return nil return nil
} }

View File

@ -147,6 +147,85 @@ var _ = framework.KubeDescribe("ResourceQuota", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
It("should create a ResourceQuota and capture the life of an uninitialized pod.", func() {
// TODO: uncomment the test when #50344 is merged.
// 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")
// err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
// Expect(err).NotTo(HaveOccurred())
// By("Creating an uninitialized Pod that fits quota")
// podName := "test-pod"
// requests := v1.ResourceList{}
// requests[v1.ResourceCPU] = resource.MustParse("500m")
// requests[v1.ResourceMemory] = resource.MustParse("252Mi")
// pod := newTestPodForQuota(f, podName, requests, v1.ResourceList{})
// pod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "unhandled"}}}
// _, err = f.ClientSet.Core().Pods(f.Namespace.Name).Create(pod)
// // because no one is handling the initializer, server will return a 504 timeout
// if err != nil && !errors.IsTimeout(err) {
// framework.Failf("expect err to be timeout error, got %v", err)
// }
// podToUpdate, err := f.ClientSet.Core().Pods(f.Namespace.Name).Get(podName, metav1.GetOptions{})
// Expect(err).NotTo(HaveOccurred())
// By("Ensuring ResourceQuota status captures the pod usage")
// usedResources[v1.ResourceQuotas] = resource.MustParse("1")
// usedResources[v1.ResourcePods] = resource.MustParse("1")
// usedResources[v1.ResourceCPU] = requests[v1.ResourceCPU]
// usedResources[v1.ResourceMemory] = requests[v1.ResourceMemory]
// err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
// Expect(err).NotTo(HaveOccurred())
// By("Not allowing an uninitialized pod to be created that exceeds remaining quota")
// requests = v1.ResourceList{}
// requests[v1.ResourceCPU] = resource.MustParse("600m")
// requests[v1.ResourceMemory] = resource.MustParse("100Mi")
// pod = newTestPodForQuota(f, "fail-pod", requests, v1.ResourceList{})
// pod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "unhandled"}}}
// pod, err = f.ClientSet.Core().Pods(f.Namespace.Name).Create(pod)
// Expect(err).To(HaveOccurred())
// fmt.Println("CHAO: err=", err)
// By("Ensuring an uninitialized pod can update its resource requirements")
// // a pod cannot dynamically update its resource requirements.
// requests = v1.ResourceList{}
// requests[v1.ResourceCPU] = resource.MustParse("100m")
// requests[v1.ResourceMemory] = resource.MustParse("100Mi")
// podToUpdate.Spec.Containers[0].Resources.Requests = requests
// _, err = f.ClientSet.Core().Pods(f.Namespace.Name).Update(podToUpdate)
// Expect(err).NotTo(HaveOccurred())
// By("Ensuring attempts to update pod resource requirements did change quota usage")
// usedResources[v1.ResourceQuotas] = resource.MustParse("1")
// usedResources[v1.ResourcePods] = resource.MustParse("1")
// usedResources[v1.ResourceCPU] = requests[v1.ResourceCPU]
// usedResources[v1.ResourceMemory] = requests[v1.ResourceMemory]
// err = waitForResourceQuota(f.ClientSet, f.Namespace.Name, quotaName, usedResources)
// Expect(err).NotTo(HaveOccurred())
// TODO: uncomment the test when the replenishment_controller uses the
// sharedInformer that list/watches uninitialized objects.
// By("Deleting the pod")
// err = f.ClientSet.Core().Pods(f.Namespace.Name).Delete(podName, metav1.NewDeleteOptions(0))
// Expect(err).NotTo(HaveOccurred())
//
// By("Ensuring resource quota status released the pod usage")
// usedResources[v1.ResourceQuotas] = resource.MustParse("1")
// usedResources[v1.ResourcePods] = resource.MustParse("0")
// usedResources[v1.ResourceCPU] = resource.MustParse("0")
// usedResources[v1.ResourceMemory] = 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 pod.", func() { It("should create a ResourceQuota and capture the life of a pod.", func() {
By("Creating a ResourceQuota") By("Creating a ResourceQuota")
quotaName := "test-quota" quotaName := "test-quota"