diff --git a/plugin/pkg/admission/runtimeclass/BUILD b/plugin/pkg/admission/runtimeclass/BUILD index 2769ae8862f..8b55378a447 100644 --- a/plugin/pkg/admission/runtimeclass/BUILD +++ b/plugin/pkg/admission/runtimeclass/BUILD @@ -32,6 +32,7 @@ go_test( "//staging/src/k8s.io/api/node/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", diff --git a/plugin/pkg/admission/runtimeclass/admission.go b/plugin/pkg/admission/runtimeclass/admission.go index aa06f9343b5..3642d3967c4 100644 --- a/plugin/pkg/admission/runtimeclass/admission.go +++ b/plugin/pkg/admission/runtimeclass/admission.go @@ -112,9 +112,10 @@ func (r *RuntimeClass) Validate(attributes admission.Attributes, o admission.Obj if err != nil { return err } - if utilfeature.DefaultFeatureGate.Enabled(features.PodOverhead) { - err = validateOverhead(attributes, pod, runtimeClass) + if err = validateOverhead(attributes, pod, runtimeClass); err != nil { + return err + } } return nil @@ -136,28 +137,17 @@ func (r *RuntimeClass) prepareObjects(attributes admission.Attributes) (pod *api } // get RuntimeClass object - runtimeClass, err = r.getRuntimeClass(pod, pod.Spec.RuntimeClassName) - if err != nil { - return pod, nil, err + if pod.Spec.RuntimeClassName != nil { + runtimeClass, err = r.runtimeClassLister.Get(*pod.Spec.RuntimeClassName) + if err != nil { + return pod, nil, err + } } // return the pod and runtimeClass. If no RuntimeClass is specified in PodSpec, runtimeClass will be nil return pod, runtimeClass, nil } -// getRuntimeClass will return a reference to the RuntimeClass object if it is found. If it cannot be found, or a RuntimeClassName -// is not provided in the pod spec, *node.RuntimeClass returned will be nil -func (r *RuntimeClass) getRuntimeClass(pod *api.Pod, runtimeClassName *string) (runtimeClass *v1beta1.RuntimeClass, err error) { - - runtimeClass = nil - - if runtimeClassName != nil { - runtimeClass, err = r.runtimeClassLister.Get(*runtimeClassName) - } - - return runtimeClass, err -} - func setOverhead(a admission.Attributes, pod *api.Pod, runtimeClass *v1beta1.RuntimeClass) (err error) { if runtimeClass != nil { diff --git a/plugin/pkg/admission/runtimeclass/admission_test.go b/plugin/pkg/admission/runtimeclass/admission_test.go index 4f856964ea9..0bff4d5d9d0 100644 --- a/plugin/pkg/admission/runtimeclass/admission_test.go +++ b/plugin/pkg/admission/runtimeclass/admission_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/api/node/v1beta1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authentication/user" utilfeature "k8s.io/apiserver/pkg/util/feature" @@ -154,9 +155,79 @@ func TestSetOverhead(t *testing.T) { }) } } +func NewObjectInterfacesForTest() admission.ObjectInterfaces { + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + return admission.NewObjectInterfacesFromScheme(scheme) +} + +func TestValidate(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)() + + tests := []struct { + name string + runtimeClass *v1beta1.RuntimeClass + pod *core.Pod + expectError bool + }{ + { + name: "No Overhead in RunntimeClass, Overhead set in pod", + runtimeClass: &v1beta1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Handler: "bar", + }, + pod: validPod("no-resource-req-no-overhead", 1, getGuaranteedRequirements(), true), + expectError: true, + }, + { + name: "Non-matching Overheads", + runtimeClass: &v1beta1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Handler: "bar", + Overhead: &v1beta1.Overhead{ + PodFixed: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("10"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("10G"), + }, + }, + }, + pod: validPod("no-resource-req-no-overhead", 1, core.ResourceRequirements{}, true), + expectError: true, + }, + { + name: "Matching Overheads", + runtimeClass: &v1beta1.RuntimeClass{ + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + Handler: "bar", + Overhead: &v1beta1.Overhead{ + PodFixed: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceCPU): resource.MustParse("100m"), + corev1.ResourceName(corev1.ResourceMemory): resource.MustParse("1"), + }, + }, + }, + pod: validPod("no-resource-req-no-overhead", 1, core.ResourceRequirements{}, false), + expectError: false, + }, + } + rt := NewRuntimeClass() + o := NewObjectInterfacesForTest() + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + attrs := admission.NewAttributesRecord(tc.pod, nil, core.Kind("Pod").WithVersion("version"), tc.pod.Namespace, tc.pod.Name, core.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{}) + + errs := rt.Validate(attrs, o) + if tc.expectError { + assert.NotEmpty(t, errs) + } else { + assert.Empty(t, errs) + } + }) + } +} func TestValidateOverhead(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodOverhead, true)() tests := []struct {