Skip to content

Commit 38167c8

Browse files
committed
fixup! feat: Add waiter for object
- Retry get only if object is not found, fail immediately otherwise.
1 parent 3c78f08 commit 38167c8

File tree

2 files changed

+107
-64
lines changed

2 files changed

+107
-64
lines changed

pkg/wait/wait.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"time"
99

10+
apierrors "k8s.io/apimachinery/pkg/api/errors"
1011
"k8s.io/apimachinery/pkg/util/wait"
1112
"sigs.k8s.io/controller-runtime/pkg/client"
1213
)
@@ -51,8 +52,10 @@ func ForObject[T client.Object](
5152
true,
5253
func(checkCtx context.Context) (bool, error) {
5354
if getErr = input.Reader.Get(checkCtx, key, input.Target); getErr != nil {
54-
// Retry if get fails.
55-
return false, nil
55+
if apierrors.IsNotFound(getErr) {
56+
return false, nil
57+
}
58+
return false, getErr
5659
}
5760

5861
if ok, err := input.Check(checkCtx, input.Target); err != nil {

pkg/wait/wait_test.go

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,58 @@ import (
1313
apierrors "k8s.io/apimachinery/pkg/api/errors"
1414
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1515
"k8s.io/apimachinery/pkg/util/wait"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1617
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1718
)
1819

20+
var brokenReaderError = errors.New("broken")
21+
22+
type brokenReader struct{}
23+
24+
func (r *brokenReader) Get(
25+
ctx context.Context,
26+
key client.ObjectKey,
27+
obj client.Object,
28+
opts ...client.GetOption,
29+
) error {
30+
return brokenReaderError
31+
}
32+
33+
func (r *brokenReader) List(
34+
ctx context.Context,
35+
list client.ObjectList,
36+
opts ...client.ListOption,
37+
) error {
38+
return brokenReaderError
39+
}
40+
41+
var _ client.Reader = &brokenReader{}
42+
1943
func TestWait(t *testing.T) {
20-
// We use the corev1.Namespace concrete type for the test, because we want to
21-
// verify behavior for a concrete type, and because the Wait function is
22-
// generic, and will behave identically for all concrete types.
23-
type args struct {
24-
input ForObjectInput[*corev1.Namespace]
25-
}
2644
tests := []struct {
27-
name string
28-
args args
45+
name string
46+
// We use the corev1.Namespace concrete type for the test, because we want to
47+
// verify behavior for a concrete type, and because the Wait function is
48+
// generic, and will behave identically for all concrete types.
49+
input ForObjectInput[*corev1.Namespace]
2950
errCheck func(error) bool
3051
}{
3152
{
32-
name: "time out while get fails; report get error",
33-
args: args{
34-
input: ForObjectInput[*corev1.Namespace]{
35-
Reader: fake.NewFakeClient(),
36-
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
37-
return true, nil
53+
name: "time out while get does not find object; report get error",
54+
input: ForObjectInput[*corev1.Namespace]{
55+
Reader: fake.NewFakeClient(),
56+
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
57+
return true, nil
58+
},
59+
Interval: time.Nanosecond,
60+
Timeout: time.Millisecond,
61+
Target: &corev1.Namespace{
62+
TypeMeta: v1.TypeMeta{
63+
Kind: "Namespace",
64+
APIVersion: "v1",
3865
},
39-
Interval: time.Nanosecond,
40-
Timeout: time.Millisecond,
41-
Target: &corev1.Namespace{
42-
TypeMeta: v1.TypeMeta{
43-
Kind: "Namespace",
44-
APIVersion: "v1",
45-
},
46-
ObjectMeta: v1.ObjectMeta{
47-
Name: "example",
48-
},
66+
ObjectMeta: v1.ObjectMeta{
67+
Name: "example",
4968
},
5069
},
5170
},
@@ -55,26 +74,35 @@ func TestWait(t *testing.T) {
5574
},
5675
},
5776
{
58-
name: "time out while check returns false; no check error to report",
59-
args: args{
60-
input: ForObjectInput[*corev1.Namespace]{
61-
Reader: fake.NewFakeClient(
62-
&corev1.Namespace{
63-
TypeMeta: v1.TypeMeta{
64-
Kind: "Namespace",
65-
APIVersion: "v1",
66-
},
67-
ObjectMeta: v1.ObjectMeta{
68-
Name: "example",
69-
},
70-
},
71-
),
72-
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
73-
return false, nil
77+
name: "return immediately when get fails; report get error",
78+
input: ForObjectInput[*corev1.Namespace]{
79+
Reader: &brokenReader{},
80+
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
81+
return true, nil
82+
},
83+
Interval: time.Nanosecond,
84+
Timeout: time.Millisecond,
85+
Target: &corev1.Namespace{
86+
TypeMeta: v1.TypeMeta{
87+
Kind: "Namespace",
88+
APIVersion: "v1",
89+
},
90+
ObjectMeta: v1.ObjectMeta{
91+
Name: "example",
7492
},
75-
Interval: time.Nanosecond,
76-
Timeout: time.Millisecond,
77-
Target: &corev1.Namespace{
93+
},
94+
},
95+
errCheck: func(err error) bool {
96+
return !wait.Interrupted(err) &&
97+
!apierrors.IsNotFound(err) &&
98+
errors.Is(err, brokenReaderError)
99+
},
100+
},
101+
{
102+
name: "time out while check returns false; no check error to report",
103+
input: ForObjectInput[*corev1.Namespace]{
104+
Reader: fake.NewFakeClient(
105+
&corev1.Namespace{
78106
TypeMeta: v1.TypeMeta{
79107
Kind: "Namespace",
80108
APIVersion: "v1",
@@ -83,30 +111,29 @@ func TestWait(t *testing.T) {
83111
Name: "example",
84112
},
85113
},
114+
),
115+
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
116+
return false, nil
117+
},
118+
Interval: time.Nanosecond,
119+
Timeout: time.Millisecond,
120+
Target: &corev1.Namespace{
121+
TypeMeta: v1.TypeMeta{
122+
Kind: "Namespace",
123+
APIVersion: "v1",
124+
},
125+
ObjectMeta: v1.ObjectMeta{
126+
Name: "example",
127+
},
86128
},
87129
},
88130
errCheck: wait.Interrupted,
89131
},
90132
{
91133
name: "return immediately when check returns an error; report the error",
92-
args: args{
93-
input: ForObjectInput[*corev1.Namespace]{
94-
Reader: fake.NewFakeClient(
95-
&corev1.Namespace{
96-
TypeMeta: v1.TypeMeta{
97-
Kind: "Namespace",
98-
APIVersion: "v1",
99-
},
100-
ObjectMeta: v1.ObjectMeta{
101-
Name: "example",
102-
},
103-
},
104-
),
105-
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
106-
return false, fmt.Errorf("condition failed")
107-
},
108-
Interval: time.Nanosecond,
109-
Timeout: time.Millisecond, Target: &corev1.Namespace{
134+
input: ForObjectInput[*corev1.Namespace]{
135+
Reader: fake.NewFakeClient(
136+
&corev1.Namespace{
110137
TypeMeta: v1.TypeMeta{
111138
Kind: "Namespace",
112139
APIVersion: "v1",
@@ -115,6 +142,19 @@ func TestWait(t *testing.T) {
115142
Name: "example",
116143
},
117144
},
145+
),
146+
Check: func(_ context.Context, _ *corev1.Namespace) (bool, error) {
147+
return false, fmt.Errorf("condition failed")
148+
},
149+
Interval: time.Nanosecond,
150+
Timeout: time.Millisecond, Target: &corev1.Namespace{
151+
TypeMeta: v1.TypeMeta{
152+
Kind: "Namespace",
153+
APIVersion: "v1",
154+
},
155+
ObjectMeta: v1.ObjectMeta{
156+
Name: "example",
157+
},
118158
},
119159
},
120160
errCheck: func(err error) bool {
@@ -127,7 +167,7 @@ func TestWait(t *testing.T) {
127167
t.Run(tt.name, func(t *testing.T) {
128168
err := ForObject(
129169
context.Background(),
130-
tt.args.input,
170+
tt.input,
131171
)
132172
if !tt.errCheck(err) {
133173
t.Errorf("error did not pass check: %s", err)

0 commit comments

Comments
 (0)