Attempt to resume downloads after certain errors
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
f425529e7e
commit
056bf9f25e
1 changed files with 86 additions and 23 deletions
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/distribution/metadata"
|
||||
"github.com/docker/docker/distribution/xfer"
|
||||
"github.com/docker/docker/image"
|
||||
|
@ -115,6 +116,7 @@ type v2LayerDescriptor struct {
|
|||
repo distribution.Repository
|
||||
V2MetadataService *metadata.V2MetadataService
|
||||
tmpFile *os.File
|
||||
verifier digest.Verifier
|
||||
}
|
||||
|
||||
func (ld *v2LayerDescriptor) Key() string {
|
||||
|
@ -132,15 +134,33 @@ func (ld *v2LayerDescriptor) DiffID() (layer.DiffID, error) {
|
|||
func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) {
|
||||
logrus.Debugf("pulling blob %q", ld.digest)
|
||||
|
||||
var err error
|
||||
var (
|
||||
err error
|
||||
offset int64
|
||||
)
|
||||
|
||||
if ld.tmpFile == nil {
|
||||
ld.tmpFile, err = createDownloadFile()
|
||||
if err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
} else {
|
||||
_, err = ld.tmpFile.Seek(0, os.SEEK_SET)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
offset, err = ld.tmpFile.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
logrus.Debugf("error seeking to end of download file: %v", err)
|
||||
offset = 0
|
||||
|
||||
ld.tmpFile.Close()
|
||||
if err := os.Remove(ld.tmpFile.Name()); err != nil {
|
||||
logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name())
|
||||
}
|
||||
ld.tmpFile, err = createDownloadFile()
|
||||
if err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
} else if offset != 0 {
|
||||
logrus.Debugf("attempting to resume download of %q from %d bytes", ld.digest, offset)
|
||||
}
|
||||
}
|
||||
|
||||
tmpFile := ld.tmpFile
|
||||
|
@ -148,13 +168,22 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
|
|||
|
||||
layerDownload, err := blobs.Open(ctx, ld.digest)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error statting layer: %v", err)
|
||||
logrus.Debugf("Error initiating layer download: %v", err)
|
||||
if err == distribution.ErrBlobUnknown {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
return nil, 0, retryOnError(err)
|
||||
}
|
||||
|
||||
if offset != 0 {
|
||||
_, err := layerDownload.Seek(offset, os.SEEK_SET)
|
||||
if err != nil {
|
||||
if err := ld.truncateDownloadFile(); err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
size, err := layerDownload.Seek(0, os.SEEK_END)
|
||||
if err != nil {
|
||||
// Seek failed, perhaps because there was no Content-Length
|
||||
|
@ -162,43 +191,59 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
|
|||
// still continue without a progress bar.
|
||||
size = 0
|
||||
} else {
|
||||
// Restore the seek offset at the beginning of the stream.
|
||||
_, err = layerDownload.Seek(0, os.SEEK_SET)
|
||||
if size != 0 && offset > size {
|
||||
logrus.Debugf("Partial download is larger than full blob. Starting over")
|
||||
offset = 0
|
||||
if err := ld.truncateDownloadFile(); err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the seek offset either at the beginning of the
|
||||
// stream, or just after the last byte we have from previous
|
||||
// attempts.
|
||||
_, err = layerDownload.Seek(offset, os.SEEK_SET)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerDownload), progressOutput, size, ld.ID(), "Downloading")
|
||||
reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerDownload), progressOutput, size-offset, ld.ID(), "Downloading")
|
||||
defer reader.Close()
|
||||
|
||||
verifier, err := digest.NewDigestVerifier(ld.digest)
|
||||
if err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
if ld.verifier == nil {
|
||||
ld.verifier, err = digest.NewDigestVerifier(ld.digest)
|
||||
if err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = io.Copy(tmpFile, io.TeeReader(reader, verifier))
|
||||
_, err = io.Copy(tmpFile, io.TeeReader(reader, ld.verifier))
|
||||
if err != nil {
|
||||
tmpFile.Close()
|
||||
if err := os.Remove(tmpFile.Name()); err != nil {
|
||||
logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name())
|
||||
if err == transport.ErrWrongCodeForByteRange {
|
||||
if err := ld.truncateDownloadFile(); err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
return nil, 0, err
|
||||
}
|
||||
ld.tmpFile = nil
|
||||
return nil, 0, retryOnError(err)
|
||||
}
|
||||
|
||||
progress.Update(progressOutput, ld.ID(), "Verifying Checksum")
|
||||
|
||||
if !verifier.Verified() {
|
||||
if !ld.verifier.Verified() {
|
||||
err = fmt.Errorf("filesystem layer verification failed for digest %s", ld.digest)
|
||||
logrus.Error(err)
|
||||
|
||||
tmpFile.Close()
|
||||
if err := os.Remove(tmpFile.Name()); err != nil {
|
||||
logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name())
|
||||
}
|
||||
ld.tmpFile = nil
|
||||
// Allow a retry if this digest verification error happened
|
||||
// after a resumed download.
|
||||
if offset != 0 {
|
||||
if err := ld.truncateDownloadFile(); err != nil {
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
|
||||
return nil, 0, err
|
||||
}
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
|
||||
|
@ -213,6 +258,7 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
|
|||
logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name())
|
||||
}
|
||||
ld.tmpFile = nil
|
||||
ld.verifier = nil
|
||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||
}
|
||||
return tmpFile, size, nil
|
||||
|
@ -227,6 +273,23 @@ func (ld *v2LayerDescriptor) Close() {
|
|||
}
|
||||
}
|
||||
|
||||
func (ld *v2LayerDescriptor) truncateDownloadFile() error {
|
||||
// Need a new hash context since we will be redoing the download
|
||||
ld.verifier = nil
|
||||
|
||||
if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil {
|
||||
logrus.Debugf("error seeking to beginning of download file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ld.tmpFile.Truncate(0); err != nil {
|
||||
logrus.Debugf("error truncating download file: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
|
||||
// Cache mapping from this layer's DiffID to the blobsum
|
||||
ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.FullName()})
|
||||
|
|
Loading…
Reference in a new issue