Skip to content

Commit 6b8e2cc

Browse files
committed
Fix runtime admission flaky test due to race condition
1 parent 87e5d4e commit 6b8e2cc

File tree

3 files changed

+176
-4
lines changed

3 files changed

+176
-4
lines changed

plugin/pkg/admission/runtimeclass/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ go_library(
1414
"//staging/src/k8s.io/api/node/v1beta1:go_default_library",
1515
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
1616
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
17+
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
1718
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
1819
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
1920
"//staging/src/k8s.io/client-go/informers:go_default_library",
21+
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
22+
"//staging/src/k8s.io/client-go/kubernetes/typed/node/v1beta1:go_default_library",
2023
"//staging/src/k8s.io/client-go/listers/node/v1beta1:go_default_library",
2124
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
2225
],

plugin/pkg/admission/runtimeclass/admission.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ import (
2929
v1beta1 "k8s.io/api/node/v1beta1"
3030
apiequality "k8s.io/apimachinery/pkg/api/equality"
3131
apierrors "k8s.io/apimachinery/pkg/api/errors"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3233
"k8s.io/apiserver/pkg/admission"
3334
genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer"
3435
"k8s.io/client-go/informers"
36+
"k8s.io/client-go/kubernetes"
37+
nodev1beta1client "k8s.io/client-go/kubernetes/typed/node/v1beta1"
3538
nodev1beta1listers "k8s.io/client-go/listers/node/v1beta1"
3639
"k8s.io/component-base/featuregate"
3740
api "k8s.io/kubernetes/pkg/apis/core"
@@ -58,6 +61,7 @@ func Register(plugins *admission.Plugins) {
5861
type RuntimeClass struct {
5962
*admission.Handler
6063
runtimeClassLister nodev1beta1listers.RuntimeClassLister
64+
runtimeClassClient nodev1beta1client.RuntimeClassInterface
6165

6266
inspectedFeatures bool
6367
runtimeClassEnabled bool
@@ -68,6 +72,11 @@ var _ admission.MutationInterface = &RuntimeClass{}
6872
var _ admission.ValidationInterface = &RuntimeClass{}
6973

7074
var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &RuntimeClass{}
75+
var _ genericadmissioninitailizer.WantsExternalKubeClientSet = &RuntimeClass{}
76+
77+
func (r *RuntimeClass) SetExternalKubeClientSet(client kubernetes.Interface) {
78+
r.runtimeClassClient = client.NodeV1beta1().RuntimeClasses()
79+
}
7180

7281
// InspectFeatureGates allows setting bools without taking a dep on a global variable
7382
func (r *RuntimeClass) InspectFeatureGates(featureGates featuregate.FeatureGate) {
@@ -97,6 +106,9 @@ func (r *RuntimeClass) ValidateInitialization() error {
97106
if r.runtimeClassLister == nil {
98107
return fmt.Errorf("missing RuntimeClass lister")
99108
}
109+
if r.runtimeClassClient == nil {
110+
return fmt.Errorf("missing RuntimeClass client")
111+
}
100112
return nil
101113
}
102114

@@ -111,7 +123,7 @@ func (r *RuntimeClass) Admit(ctx context.Context, attributes admission.Attribute
111123
return nil
112124
}
113125

114-
pod, runtimeClass, err := r.prepareObjects(attributes)
126+
pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
115127
if err != nil {
116128
return err
117129
}
@@ -139,7 +151,7 @@ func (r *RuntimeClass) Validate(ctx context.Context, attributes admission.Attrib
139151
return nil
140152
}
141153

142-
pod, runtimeClass, err := r.prepareObjects(attributes)
154+
pod, runtimeClass, err := r.prepareObjects(ctx, attributes)
143155
if err != nil {
144156
return err
145157
}
@@ -160,7 +172,7 @@ func NewRuntimeClass() *RuntimeClass {
160172
}
161173

162174
// prepareObjects returns pod and runtimeClass types from the given admission attributes
163-
func (r *RuntimeClass) prepareObjects(attributes admission.Attributes) (pod *api.Pod, runtimeClass *v1beta1.RuntimeClass, err error) {
175+
func (r *RuntimeClass) prepareObjects(ctx context.Context, attributes admission.Attributes) (pod *api.Pod, runtimeClass *v1beta1.RuntimeClass, err error) {
164176
pod, ok := attributes.GetObject().(*api.Pod)
165177
if !ok {
166178
return nil, nil, apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
@@ -173,7 +185,11 @@ func (r *RuntimeClass) prepareObjects(attributes admission.Attributes) (pod *api
173185
// get RuntimeClass object
174186
runtimeClass, err = r.runtimeClassLister.Get(*pod.Spec.RuntimeClassName)
175187
if apierrors.IsNotFound(err) {
176-
return pod, nil, admission.NewForbidden(attributes, fmt.Errorf("pod rejected: RuntimeClass %q not found", *pod.Spec.RuntimeClassName))
188+
// if not found, our informer cache could be lagging, do a live lookup
189+
runtimeClass, err = r.runtimeClassClient.Get(ctx, *pod.Spec.RuntimeClassName, metav1.GetOptions{})
190+
if apierrors.IsNotFound(err) {
191+
return pod, nil, admission.NewForbidden(attributes, fmt.Errorf("pod rejected: RuntimeClass %q not found", *pod.Spec.RuntimeClassName))
192+
}
177193
}
178194

179195
// return the pod and runtimeClass.

plugin/pkg/admission/runtimeclass/admission_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
/*
23
Copyright 2019 The Kubernetes Authors.
34
@@ -30,6 +31,13 @@ import (
3031
"k8s.io/apiserver/pkg/admission"
3132
"k8s.io/apiserver/pkg/authentication/user"
3233
"k8s.io/kubernetes/pkg/apis/core"
34+
"k8s.io/client-go/kubernetes/fake"
35+
api "k8s.io/kubernetes/pkg/apis/core"
36+
"k8s.io/client-go/informers"
37+
"k8s.io/client-go/kubernetes"
38+
"k8s.io/component-base/featuregate"
39+
"k8s.io/kubernetes/pkg/controller"
40+
"k8s.io/kubernetes/pkg/features"
3341

3442
"github.com/stretchr/testify/assert"
3543
)
@@ -315,6 +323,151 @@ func NewObjectInterfacesForTest() admission.ObjectInterfaces {
315323
return admission.NewObjectInterfacesFromScheme(scheme)
316324
}
317325

326+
func newRuntimeClassForTest(runtimeClassEnabled bool,
327+
featureInspection bool,
328+
addLister bool,
329+
listerObject *v1beta1.RuntimeClass,
330+
addClient bool,
331+
clientObject *v1beta1.RuntimeClass) *RuntimeClass {
332+
runtimeClass := NewRuntimeClass()
333+
334+
if featureInspection {
335+
relevantFeatures := map[featuregate.Feature]featuregate.FeatureSpec{
336+
features.RuntimeClass: {Default: runtimeClassEnabled},
337+
features.PodOverhead: {Default: false},
338+
}
339+
fg := featuregate.NewFeatureGate()
340+
fg.Add(relevantFeatures)
341+
runtimeClass.InspectFeatureGates(fg)
342+
}
343+
344+
if addLister {
345+
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
346+
runtimeClass.SetExternalKubeInformerFactory(informerFactory)
347+
if listerObject != nil {
348+
informerFactory.Node().V1beta1().RuntimeClasses().Informer().GetStore().Add(listerObject)
349+
}
350+
}
351+
352+
if addClient {
353+
var client kubernetes.Interface
354+
if clientObject != nil {
355+
client = fake.NewSimpleClientset(clientObject)
356+
} else {
357+
client = fake.NewSimpleClientset()
358+
}
359+
runtimeClass.SetExternalKubeClientSet(client)
360+
}
361+
362+
return runtimeClass
363+
}
364+
365+
func TestValidateInitialization(t *testing.T) {
366+
tests := []struct {
367+
name string
368+
expectError bool
369+
runtimeClass *RuntimeClass
370+
}{
371+
{
372+
name: "runtimeClass disabled, success",
373+
expectError: false,
374+
runtimeClass: newRuntimeClassForTest(false, true, true, nil, true, nil),
375+
},
376+
{
377+
name: "runtimeClass enabled, success",
378+
expectError: false,
379+
runtimeClass: newRuntimeClassForTest(true, true, true, nil, true, nil),
380+
},
381+
{
382+
name: "runtimeClass enabled, no feature inspection",
383+
expectError: true,
384+
runtimeClass: newRuntimeClassForTest(true, false, true, nil, true, nil),
385+
},
386+
{
387+
name: "runtimeClass enabled, no lister",
388+
expectError: true,
389+
runtimeClass: newRuntimeClassForTest(true, true, false, nil, true, nil,),
390+
},
391+
{
392+
name: "runtimeClass enabled, no client",
393+
expectError: true,
394+
runtimeClass: newRuntimeClassForTest(true, true, true, nil, false, nil),
395+
},
396+
}
397+
398+
for _, tc := range tests {
399+
t.Run(tc.name, func(t *testing.T) {
400+
err := tc.runtimeClass.ValidateInitialization()
401+
if tc.expectError {
402+
assert.NotEmpty(t, err)
403+
} else {
404+
assert.Empty(t, err)
405+
}
406+
})
407+
}
408+
}
409+
410+
func TestAdmit(t *testing.T) {
411+
runtimeClassName := "runtimeClassName"
412+
413+
rc := &v1beta1.RuntimeClass{
414+
ObjectMeta: metav1.ObjectMeta{Name: runtimeClassName},
415+
}
416+
417+
pod := api.Pod{
418+
ObjectMeta: metav1.ObjectMeta{Name: "podname"},
419+
Spec: api.PodSpec{
420+
RuntimeClassName: &runtimeClassName,
421+
},
422+
}
423+
424+
attributes := admission.NewAttributesRecord(&pod,
425+
nil,
426+
api.Kind("kind").WithVersion("version"),
427+
"",
428+
"",
429+
api.Resource("pods").WithVersion("version"),
430+
"",
431+
admission.Create,
432+
nil,
433+
false,
434+
nil)
435+
436+
437+
tests := []struct {
438+
name string
439+
expectError bool
440+
runtimeClass *RuntimeClass
441+
}{
442+
{
443+
name: "runtimeClass found by lister",
444+
expectError: false,
445+
runtimeClass: newRuntimeClassForTest(true, true, true, rc, true, nil),
446+
},
447+
{
448+
name: "runtimeClass found by client",
449+
expectError: false,
450+
runtimeClass: newRuntimeClassForTest(true, true, true, nil, true, rc),
451+
},
452+
{
453+
name: "runtimeClass not found by lister nor client",
454+
expectError: true,
455+
runtimeClass: newRuntimeClassForTest(true, true, true, nil, true, nil),
456+
},
457+
}
458+
459+
for _, tc := range tests {
460+
t.Run(tc.name, func(t *testing.T) {
461+
err := tc.runtimeClass.Admit(context.TODO(), attributes, nil)
462+
if tc.expectError {
463+
assert.NotEmpty(t, err)
464+
} else {
465+
assert.Empty(t, err)
466+
}
467+
})
468+
}
469+
}
470+
318471
func TestValidate(t *testing.T) {
319472
tests := []struct {
320473
name string

0 commit comments

Comments
 (0)