Skip to content

Commit ae2fa32

Browse files
committed
[registry-facade] Properly retry fetching mainfests/config as well
1 parent 6428470 commit ae2fa32

File tree

2 files changed

+284
-80
lines changed

2 files changed

+284
-80
lines changed

components/registry-facade/pkg/registry/manifest.go

Lines changed: 125 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/opentracing/opentracing-go"
2828
"github.com/pkg/errors"
2929
"golang.org/x/xerrors"
30+
"k8s.io/apimachinery/pkg/util/wait"
3031

3132
"github.com/gitpod-io/gitpod/common-go/log"
3233
"github.com/gitpod-io/gitpod/common-go/tracing"
@@ -96,6 +97,15 @@ func (reg *Registry) handleManifest(ctx context.Context, r *http.Request) http.H
9697
return res
9798
}
9899

100+
// fetcherBackoffParams defines the backoff parameters for blob retrieval.
101+
// Aiming at ~10 seconds total time for retries
102+
var fetcherBackoffParams = wait.Backoff{
103+
Duration: 1 * time.Second,
104+
Factor: 1.2,
105+
Jitter: 0.2,
106+
Steps: 5,
107+
}
108+
99109
type manifestHandler struct {
100110
Context context.Context
101111

@@ -278,39 +288,51 @@ func DownloadConfig(ctx context.Context, fetch FetcherFunc, ref string, desc oci
278288

279289
return nil, xerrors.Errorf("unsupported media type: %s", desc.MediaType)
280290
}
291+
log := log.WithField("desc", desc)
281292

282293
var opts manifestDownloadOptions
283294
for _, o := range options {
284295
o(&opts)
285296
}
286297

287-
var rc io.ReadCloser
288-
if opts.Store != nil {
289-
r, err := opts.Store.ReaderAt(ctx, desc)
290-
if errors.Is(err, errdefs.ErrNotFound) {
291-
// not cached yet
292-
} else if err != nil {
293-
log.WithError(err).WithField("desc", desc).Warn("cannot read config from store - fetching again")
294-
} else {
295-
defer r.Close()
296-
rc = io.NopCloser(content.NewReader(r))
298+
var buf []byte
299+
err = wait.ExponentialBackoffWithContext(ctx, fetcherBackoffParams, func(ctx context.Context) (done bool, err error) {
300+
var rc io.ReadCloser
301+
if opts.Store != nil {
302+
r, err := opts.Store.ReaderAt(ctx, desc)
303+
if errors.Is(err, errdefs.ErrNotFound) {
304+
// not cached yet
305+
} else if err != nil {
306+
log.WithError(err).Warn("cannot read config from store - fetching again")
307+
} else {
308+
defer r.Close()
309+
rc = io.NopCloser(content.NewReader(r))
310+
}
297311
}
298-
}
299-
if rc == nil {
300-
fetcher, err := fetch()
301-
if err != nil {
302-
return nil, err
312+
if rc == nil {
313+
fetcher, err := fetch()
314+
if err != nil {
315+
log.WithError(err).Warn("cannot create fetcher")
316+
return false, nil // retry
317+
}
318+
rc, err = fetcher.Fetch(ctx, desc)
319+
if err != nil {
320+
log.WithError(err).Warn("cannot fetch config")
321+
return false, nil // retry
322+
}
323+
defer rc.Close()
303324
}
304-
rc, err = fetcher.Fetch(ctx, desc)
325+
326+
buf, err = io.ReadAll(rc)
305327
if err != nil {
306-
return nil, xerrors.Errorf("cannot download config: %w", err)
328+
log.WithError(err).Warn("cannot read config")
329+
return false, nil // retry
307330
}
308-
defer rc.Close()
309-
}
310331

311-
buf, err := io.ReadAll(rc)
332+
return true, nil
333+
})
312334
if err != nil {
313-
return nil, xerrors.Errorf("cannot read config: %w", err)
335+
return nil, xerrors.Errorf("failed to fetch config: %w", err)
314336
}
315337

316338
var res ociv1.Image
@@ -387,68 +409,80 @@ func AsFetcherFunc(f remotes.Fetcher) FetcherFunc {
387409
// DownloadManifest downloads and unmarshals the manifest of the given desc. If the desc points to manifest list
388410
// we choose the first manifest in that list.
389411
func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descriptor, options ...ManifestDownloadOption) (cfg *ociv1.Manifest, rdesc *ociv1.Descriptor, err error) {
412+
log := log.WithField("desc", desc)
413+
390414
var opts manifestDownloadOptions
391415
for _, o := range options {
392416
o(&opts)
393417
}
394418

395419
var (
396420
placeInStore bool
397-
rc io.ReadCloser
398421
mediaType = desc.MediaType
422+
inpt []byte
399423
)
400-
if opts.Store != nil {
401-
func() {
402-
nfo, err := opts.Store.Info(ctx, desc.Digest)
403-
if errors.Is(err, errdefs.ErrNotFound) {
404-
// not in store yet
405-
return
406-
}
424+
err = wait.ExponentialBackoffWithContext(ctx, fetcherBackoffParams, func(ctx context.Context) (done bool, err error) {
425+
var rc io.ReadCloser
426+
if opts.Store != nil {
427+
func() {
428+
nfo, err := opts.Store.Info(ctx, desc.Digest)
429+
if errors.Is(err, errdefs.ErrNotFound) {
430+
// not in store yet
431+
return
432+
}
433+
if err != nil {
434+
log.WithError(err).Warn("cannot get manifest from store")
435+
return
436+
}
437+
if nfo.Labels["Content-Type"] == "" {
438+
// we have broken data in the store - ignore it and overwrite
439+
return
440+
}
441+
442+
r, err := opts.Store.ReaderAt(ctx, desc)
443+
if errors.Is(err, errdefs.ErrNotFound) {
444+
// not in store yet
445+
return
446+
}
447+
if err != nil {
448+
log.WithError(err).Warn("cannot get manifest from store")
449+
return
450+
}
451+
452+
mediaType, rc = nfo.Labels["Content-Type"], &reader{ReaderAt: r}
453+
}()
454+
}
455+
if rc == nil {
456+
// did not find in store, or there was no store. Either way, let's fetch this
457+
// thing from the remote.
458+
placeInStore = true
459+
460+
var fetcher remotes.Fetcher
461+
fetcher, err = fetch()
407462
if err != nil {
408-
log.WithError(err).WithField("desc", desc).Warn("cannot get manifest from store")
409-
return
410-
}
411-
if nfo.Labels["Content-Type"] == "" {
412-
// we have broken data in the store - ignore it and overwrite
413-
return
463+
log.WithError(err).Warn("cannot create fetcher")
464+
return false, nil // retry
414465
}
415466

416-
r, err := opts.Store.ReaderAt(ctx, desc)
417-
if errors.Is(err, errdefs.ErrNotFound) {
418-
// not in store yet
419-
return
420-
}
467+
rc, err = fetcher.Fetch(ctx, desc)
421468
if err != nil {
422-
log.WithError(err).WithField("desc", desc).Warn("cannot get manifest from store")
423-
return
469+
log.WithError(err).Warn("cannot fetch manifest")
470+
return false, nil // retry
424471
}
425-
426-
mediaType, rc = nfo.Labels["Content-Type"], &reader{ReaderAt: r}
427-
}()
428-
}
429-
if rc == nil {
430-
// did not find in store, or there was no store. Either way, let's fetch this
431-
// thing from the remote.
432-
placeInStore = true
433-
434-
var fetcher remotes.Fetcher
435-
fetcher, err = fetch()
436-
if err != nil {
437-
return
472+
mediaType = desc.MediaType
438473
}
439474

440-
rc, err = fetcher.Fetch(ctx, desc)
475+
inpt, err = io.ReadAll(rc)
476+
rc.Close()
441477
if err != nil {
442-
err = xerrors.Errorf("cannot fetch manifest: %w", err)
443-
return
478+
log.WithError(err).Warn("cannot read manifest")
479+
return false, nil // retry
444480
}
445-
mediaType = desc.MediaType
446-
}
447481

448-
inpt, err := io.ReadAll(rc)
449-
rc.Close()
482+
return true, nil
483+
})
450484
if err != nil {
451-
err = xerrors.Errorf("cannot download manifest: %w", err)
485+
err = xerrors.Errorf("failed to fetch manifest: %w", err)
452486
return
453487
}
454488

@@ -457,7 +491,8 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
457491

458492
switch rdesc.MediaType {
459493
case images.MediaTypeDockerSchema2ManifestList, ociv1.MediaTypeImageIndex:
460-
log.WithField("desc", rdesc).Debug("resolving image index")
494+
log := log.WithField("desc", rdesc)
495+
log.Debug("resolving image index")
461496

462497
// we received a manifest list which means we'll pick the default platform
463498
// and fetch that manifest
@@ -472,24 +507,34 @@ func DownloadManifest(ctx context.Context, fetch FetcherFunc, desc ociv1.Descrip
472507
return
473508
}
474509

475-
var fetcher remotes.Fetcher
476-
fetcher, err = fetch()
477-
if err != nil {
478-
return
479-
}
510+
err = wait.ExponentialBackoffWithContext(ctx, fetcherBackoffParams, func(ctx context.Context) (done bool, err error) {
511+
var fetcher remotes.Fetcher
512+
fetcher, err = fetch()
513+
if err != nil {
514+
log.WithError(err).Warn("cannot create fetcher")
515+
return false, nil // retry
516+
}
480517

481-
// TODO(cw): choose by platform, not just the first manifest
482-
md := list.Manifests[0]
483-
rc, err = fetcher.Fetch(ctx, md)
484-
if err != nil {
485-
err = xerrors.Errorf("cannot download config: %w", err)
486-
return
487-
}
488-
rdesc = &md
489-
inpt, err = io.ReadAll(rc)
490-
rc.Close()
518+
// TODO(cw): choose by platform, not just the first manifest
519+
var rc io.ReadCloser
520+
md := list.Manifests[0]
521+
rc, err = fetcher.Fetch(ctx, md)
522+
if err != nil {
523+
log.WithError(err).Warn("cannot download config")
524+
return false, nil // retry
525+
}
526+
rdesc = &md
527+
inpt, err = io.ReadAll(rc)
528+
rc.Close()
529+
if err != nil {
530+
log.WithError(err).Warn("cannot download manifest")
531+
return false, nil // retry
532+
}
533+
534+
return true, nil
535+
})
491536
if err != nil {
492-
err = xerrors.Errorf("cannot download manifest: %w", err)
537+
err = xerrors.Errorf("failed to download config: %w", err)
493538
return
494539
}
495540
}

0 commit comments

Comments
 (0)