Skip to content

Commit 334ce07

Browse files
Retry get Sourcepackage operation from listers with exponential backoff (#1098)
## Release Notes ```release-note `Fixed` race condition like situation where reconciling status/presence of a source package races with consuming source package during the "kf push" operation. ```
2 parents 47a3c3b + e54ef19 commit 334ce07

File tree

3 files changed

+85
-54
lines changed

3 files changed

+85
-54
lines changed

cmd/subresource-apiserver/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ func (h *handler) post(req *restful.Request, resp *restful.Response) {
165165
})
166166
},
167167
)
168-
if _, err := u.Upload(ctx, ns, sr, req.Request.Body); err != nil {
168+
// Retrying 13 times should take around 2.73 minutes.
169+
const maxRetriesForGetSourcePackage = 13
170+
if _, err := u.Upload(ctx, ns, sr, maxRetriesForGetSourcePackage, req.Request.Body); err != nil {
169171
h.logger.Warnf("failed to save image %s/%s: %v", ns, sr, err)
170172
resp.WriteErrorString(http.StatusInternalServerError, "failed to save image")
171173
return

pkg/sourceimage/uploader.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"io/ioutil"
2626
"os"
2727
"path"
28+
"time"
2829

2930
"github.com/google/go-containerregistry/pkg/name"
3031
"github.com/google/kf/v2/pkg/apis/kf/v1alpha1"
@@ -62,21 +63,36 @@ func (u *Uploader) Upload(
6263
ctx context.Context,
6364
spaceName string,
6465
sourcePackageName string,
66+
maxRetriesForGetSourcePackage int,
6567
r io.Reader,
6668
) (name.Reference, error) {
69+
logger := logging.FromContext(ctx)
70+
6771
// Fetch the Space to read the configured container registry.
6872
space, err := u.spaceLister.Get(spaceName)
6973
if err != nil {
7074
return nil, fmt.Errorf("failed to find Space: %v", err)
7175
}
7276

73-
// Fetch the SourcePackage to lookup the spec.
74-
sourcePackage, err := u.
75-
sourcePackageLister.
76-
SourcePackages(spaceName).
77-
Get(sourcePackageName)
78-
if err != nil {
79-
return nil, fmt.Errorf("failed to find SourcePackage: %v", err)
77+
// Fetch the SourcePackage to lookup the spec, retry on error with exponential backoff.
78+
var sleepDuration = 10 * time.Millisecond
79+
var sourcePackage *v1alpha1.SourcePackage
80+
for i := 0; i <= maxRetriesForGetSourcePackage; i++ {
81+
sourcePackage, err = u.
82+
sourcePackageLister.
83+
SourcePackages(spaceName).
84+
Get(sourcePackageName)
85+
if err == nil {
86+
break
87+
} else {
88+
if i == maxRetriesForGetSourcePackage {
89+
return nil, fmt.Errorf("failed to find SourcePackage, retries exhausted: %v", err)
90+
} else {
91+
logger.Warnf("failed to find SourcePackage: %s, retrying %dth time. Error: %v", sourcePackageName, i+1, err)
92+
time.Sleep(sleepDuration)
93+
sleepDuration *= 2
94+
}
95+
}
8096
}
8197

8298
// Ensure the sourcePackage is still pending. Otherwise, it is considered

pkg/sourceimage/uploader_test.go

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,22 @@ func TestUploader(t *testing.T) {
8282
}
8383

8484
testCases := []struct {
85-
name string
86-
spaceName string
87-
sourcePackageName string
88-
data io.Reader
89-
setup func(t *testing.T, f fakes)
90-
assert func(t *testing.T, o output)
85+
name string
86+
spaceName string
87+
sourcePackageName string
88+
maxRetriesForGetSourcePackage int
89+
data io.Reader
90+
setup func(t *testing.T, f fakes)
91+
assert func(t *testing.T, o output)
9192

9293
imagePusher func(t *testing.T, path, imageName string) (name.Reference, error)
9394
statusUpdater func(t *testing.T, s *v1alpha1.SourcePackage) error
9495
}{
9596
{
96-
name: "pushes the correct image",
97-
spaceName: "some-space",
98-
sourcePackageName: "some-name",
97+
name: "pushes the correct image",
98+
spaceName: "some-space",
99+
sourcePackageName: "some-name",
100+
maxRetriesForGetSourcePackage: 0,
99101
setup: func(t *testing.T, f fakes) {
100102
setupNormal(f, "some-space", "some-name")
101103
},
@@ -126,8 +128,9 @@ func TestUploader(t *testing.T) {
126128
},
127129
},
128130
{
129-
name: "getting Space fails",
130-
spaceName: "some-space",
131+
name: "getting Space fails",
132+
spaceName: "some-space",
133+
maxRetriesForGetSourcePackage: 0,
131134
setup: func(t *testing.T, f fakes) {
132135
f.fs.EXPECT().
133136
Get("some-space").
@@ -138,28 +141,30 @@ func TestUploader(t *testing.T) {
138141
},
139142
},
140143
{
141-
name: "getting SourcePackage fails",
142-
spaceName: "some-space",
143-
sourcePackageName: "some-name",
144+
name: "getting SourcePackage fails",
145+
spaceName: "some-space",
146+
sourcePackageName: "some-name",
147+
maxRetriesForGetSourcePackage: 4,
144148
setup: func(t *testing.T, f fakes) {
145149
f.fs.EXPECT().Get("some-space")
146150

147151
f.fp.EXPECT().
148152
SourcePackages("some-space").
149-
Return(f.fnp)
153+
Return(f.fnp).Times(5) // One initial attempt and then 4 retries.
150154

151155
f.fnp.EXPECT().
152156
Get("some-name").
153-
Return(nil, errors.New("some-error"))
157+
Return(nil, errors.New("some-error")).Times(5) // One initial attempt and then 4 retries.
154158
},
155159
assert: func(t *testing.T, o output) {
156-
testutil.AssertErrorsEqual(t, errors.New("failed to find SourcePackage: some-error"), o.err)
160+
testutil.AssertErrorsEqual(t, errors.New("failed to find SourcePackage, retries exhausted: some-error"), o.err)
157161
},
158162
},
159163
{
160-
name: "SourcePackage is not pending",
161-
spaceName: "some-space",
162-
sourcePackageName: "some-name",
164+
name: "SourcePackage is not pending",
165+
spaceName: "some-space",
166+
sourcePackageName: "some-name",
167+
maxRetriesForGetSourcePackage: 0,
163168
setup: func(t *testing.T, f fakes) {
164169
f.fs.EXPECT().Get("some-space")
165170

@@ -191,10 +196,11 @@ func TestUploader(t *testing.T) {
191196
},
192197
},
193198
{
194-
name: "building and pushing image fails",
195-
spaceName: "some-space",
196-
sourcePackageName: "some-name",
197-
data: strings.NewReader(normalData),
199+
name: "building and pushing image fails",
200+
spaceName: "some-space",
201+
sourcePackageName: "some-name",
202+
maxRetriesForGetSourcePackage: 0,
203+
data: strings.NewReader(normalData),
198204
setup: func(t *testing.T, f fakes) {
199205
setupNormal(f, "some-space", "some-name")
200206
},
@@ -206,10 +212,11 @@ func TestUploader(t *testing.T) {
206212
},
207213
},
208214
{
209-
name: "updating status fails",
210-
spaceName: "some-space",
211-
sourcePackageName: "some-name",
212-
data: strings.NewReader(normalData),
215+
name: "updating status fails",
216+
spaceName: "some-space",
217+
sourcePackageName: "some-name",
218+
maxRetriesForGetSourcePackage: 0,
219+
data: strings.NewReader(normalData),
213220
setup: func(t *testing.T, f fakes) {
214221
setupNormal(f, "some-space", "some-name")
215222
},
@@ -221,10 +228,11 @@ func TestUploader(t *testing.T) {
221228
},
222229
},
223230
{
224-
name: "saving data fails",
225-
spaceName: "some-space",
226-
sourcePackageName: "some-name",
227-
data: &errReader{err: errors.New("some-error")},
231+
name: "saving data fails",
232+
spaceName: "some-space",
233+
sourcePackageName: "some-name",
234+
maxRetriesForGetSourcePackage: 0,
235+
data: &errReader{err: errors.New("some-error")},
228236
setup: func(t *testing.T, f fakes) {
229237
setupNormal(f, "some-space", "some-name")
230238
},
@@ -233,9 +241,10 @@ func TestUploader(t *testing.T) {
233241
},
234242
},
235243
{
236-
name: "checksum doesn't match",
237-
spaceName: "some-space",
238-
sourcePackageName: "some-name",
244+
name: "checksum doesn't match",
245+
spaceName: "some-space",
246+
sourcePackageName: "some-name",
247+
maxRetriesForGetSourcePackage: 0,
239248

240249
// NOTE: the variable normalData is associated with
241250
// normalChecksumValue which is set by the setupNormal function.
@@ -248,10 +257,11 @@ func TestUploader(t *testing.T) {
248257
},
249258
},
250259
{
251-
name: "size doesn't match spec",
252-
spaceName: "some-space",
253-
sourcePackageName: "some-name",
254-
data: strings.NewReader(normalData),
260+
name: "size doesn't match spec",
261+
spaceName: "some-space",
262+
sourcePackageName: "some-name",
263+
maxRetriesForGetSourcePackage: 0,
264+
data: strings.NewReader(normalData),
255265
setup: func(t *testing.T, f fakes) {
256266
setupWithSize(f, "some-space", "some-name", uint64(len(normalData)+1))
257267
},
@@ -260,10 +270,11 @@ func TestUploader(t *testing.T) {
260270
},
261271
},
262272
{
263-
name: "unknown checksum type",
264-
spaceName: "some-space",
265-
sourcePackageName: "some-name",
266-
data: strings.NewReader(normalData),
273+
name: "unknown checksum type",
274+
spaceName: "some-space",
275+
sourcePackageName: "some-name",
276+
maxRetriesForGetSourcePackage: 0,
277+
data: strings.NewReader(normalData),
267278
setup: func(t *testing.T, f fakes) {
268279
setup(f, "some-space", "some-name", "invalid", uint64(len(normalData)))
269280
},
@@ -272,9 +283,10 @@ func TestUploader(t *testing.T) {
272283
},
273284
},
274285
{
275-
name: "limits how much it reads",
276-
spaceName: "some-space",
277-
sourcePackageName: "some-name",
286+
name: "limits how much it reads",
287+
spaceName: "some-space",
288+
sourcePackageName: "some-name",
289+
maxRetriesForGetSourcePackage: 0,
278290
// This reader will never stop feeding the test data. Unless it
279291
// has properly guarded itself by limiting how much data it reads,
280292
// it will timeout.
@@ -336,6 +348,7 @@ func TestUploader(t *testing.T) {
336348
context.Background(),
337349
tc.spaceName,
338350
tc.sourcePackageName,
351+
tc.maxRetriesForGetSourcePackage,
339352
tc.data,
340353
)
341354

0 commit comments

Comments
 (0)