Handle empty blob files more appropriately
Several API tests were added to ensure correct acceptance of zero-size and empty tar files. This led to several changes in the storage backend around the guarantees of remote file reading, which backs the layer and layer upload type. In support of these changes, zero-length and empty checks have been added to the digest package. These provide a sanity check against upstream tarsum changes. The fileReader has been modified to be more robust when reading and seeking on zero-length or non-existent files. The file no longer needs to exist for the reader to be created. Seeks can now move beyond the end of the file, causing reads to issue an io.EOF. This eliminates errors during certain race conditions for reading files which should be detected by stat calls. As a part of this, a few error types were factored out and the read buffer size was increased to something more reasonable. Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
097fce3bb2
commit
0270bec916
12 changed files with 218 additions and 91 deletions
|
@ -1,6 +1,7 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"time"
|
||||
|
@ -89,7 +90,10 @@ func (luc *layerUploadController) validateLayer(dgst digest.Digest) (digest.Dige
|
|||
case tarsum.Version1:
|
||||
default:
|
||||
// version 0 and dev, for now.
|
||||
return "", ErrLayerTarSumVersionUnsupported
|
||||
return "", ErrLayerInvalidDigest{
|
||||
Digest: dgst,
|
||||
Reason: ErrLayerTarSumVersionUnsupported,
|
||||
}
|
||||
}
|
||||
|
||||
digestVerifier := digest.NewDigestVerifier(dgst)
|
||||
|
@ -102,22 +106,7 @@ func (luc *layerUploadController) validateLayer(dgst digest.Digest) (digest.Dige
|
|||
// Read the file from the backend driver and validate it.
|
||||
fr, err := newFileReader(luc.fileWriter.driver, luc.path)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case storagedriver.PathNotFoundError:
|
||||
// NOTE(stevvooe): Path not found can mean several things by we
|
||||
// should report the upload is not available. This can happen if
|
||||
// the following happens:
|
||||
//
|
||||
// 1. If not data was received for the upload instance.
|
||||
// 2. Backend storage driver has not convereged after receiving latest data.
|
||||
//
|
||||
// This *does not* mean that the upload does not exist, since we
|
||||
// can't even get a LayerUpload object without having the
|
||||
// directory exist.
|
||||
return "", ErrLayerUploadUnavailable{Err: err}
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
tr := io.TeeReader(fr, digestVerifier)
|
||||
|
@ -132,7 +121,10 @@ func (luc *layerUploadController) validateLayer(dgst digest.Digest) (digest.Dige
|
|||
}
|
||||
|
||||
if !digestVerifier.Verified() {
|
||||
return "", ErrLayerInvalidDigest{Digest: dgst}
|
||||
return "", ErrLayerInvalidDigest{
|
||||
Digest: dgst,
|
||||
Reason: fmt.Errorf("content does not match digest"),
|
||||
}
|
||||
}
|
||||
|
||||
return canonical, nil
|
||||
|
@ -151,7 +143,7 @@ func (luc *layerUploadController) moveLayer(dgst digest.Digest) error {
|
|||
}
|
||||
|
||||
// Check for existence
|
||||
if _, err := luc.layerStore.repository.registry.driver.Stat(blobPath); err != nil {
|
||||
if _, err := luc.driver.Stat(blobPath); err != nil {
|
||||
switch err := err.(type) {
|
||||
case storagedriver.PathNotFoundError:
|
||||
break // ensure that it doesn't exist.
|
||||
|
@ -166,6 +158,31 @@ func (luc *layerUploadController) moveLayer(dgst digest.Digest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// If no data was received, we may not actually have a file on disk. Check
|
||||
// the size here and write a zero-length file to blobPath if this is the
|
||||
// case. For the most part, this should only ever happen with zero-length
|
||||
// tars.
|
||||
if _, err := luc.driver.Stat(luc.path); err != nil {
|
||||
switch err := err.(type) {
|
||||
case storagedriver.PathNotFoundError:
|
||||
// HACK(stevvooe): This is slightly dangerous: if we verify above,
|
||||
// get a hash, then the underlying file is deleted, we risk moving
|
||||
// a zero-length blob into a nonzero-length blob location. To
|
||||
// prevent this horrid thing, we employ the hack of only allowing
|
||||
// to this happen for the zero tarsum.
|
||||
if dgst == digest.DigestTarSumV1EmptyTar {
|
||||
return luc.driver.PutContent(blobPath, []byte{})
|
||||
}
|
||||
|
||||
// We let this fail during the move below.
|
||||
logrus.
|
||||
WithField("upload.uuid", luc.UUID()).
|
||||
WithField("digest", dgst).Warnf("attempted to move zero-length content with non-zero digest")
|
||||
default:
|
||||
return err // unrelated error
|
||||
}
|
||||
}
|
||||
|
||||
return luc.driver.Move(luc.path, blobPath)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue