forked from TrueCloudLab/distribution
0270bec916
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>
193 lines
5 KiB
Go
193 lines
5 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"io"
|
|
mrand "math/rand"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/docker/distribution/digest"
|
|
|
|
"github.com/docker/distribution/storagedriver/inmemory"
|
|
)
|
|
|
|
func TestSimpleRead(t *testing.T) {
|
|
content := make([]byte, 1<<20)
|
|
n, err := rand.Read(content)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error building random data: %v", err)
|
|
}
|
|
|
|
if n != len(content) {
|
|
t.Fatalf("random read did't fill buffer")
|
|
}
|
|
|
|
dgst, err := digest.FromReader(bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error digesting random content: %v", err)
|
|
}
|
|
|
|
driver := inmemory.New()
|
|
path := "/random"
|
|
|
|
if err := driver.PutContent(path, content); err != nil {
|
|
t.Fatalf("error putting patterned content: %v", err)
|
|
}
|
|
|
|
fr, err := newFileReader(driver, path)
|
|
if err != nil {
|
|
t.Fatalf("error allocating file reader: %v", err)
|
|
}
|
|
|
|
verifier := digest.NewDigestVerifier(dgst)
|
|
io.Copy(verifier, fr)
|
|
|
|
if !verifier.Verified() {
|
|
t.Fatalf("unable to verify read data")
|
|
}
|
|
}
|
|
|
|
func TestFileReaderSeek(t *testing.T) {
|
|
driver := inmemory.New()
|
|
pattern := "01234567890ab" // prime length block
|
|
repititions := 1024
|
|
path := "/patterned"
|
|
content := bytes.Repeat([]byte(pattern), repititions)
|
|
|
|
if err := driver.PutContent(path, content); err != nil {
|
|
t.Fatalf("error putting patterned content: %v", err)
|
|
}
|
|
|
|
fr, err := newFileReader(driver, path)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error creating file reader: %v", err)
|
|
}
|
|
|
|
// Seek all over the place, in blocks of pattern size and make sure we get
|
|
// the right data.
|
|
for _, repitition := range mrand.Perm(repititions - 1) {
|
|
targetOffset := int64(len(pattern) * repitition)
|
|
// Seek to a multiple of pattern size and read pattern size bytes
|
|
offset, err := fr.Seek(targetOffset, os.SEEK_SET)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error seeking: %v", err)
|
|
}
|
|
|
|
if offset != targetOffset {
|
|
t.Fatalf("did not seek to correct offset: %d != %d", offset, targetOffset)
|
|
}
|
|
|
|
p := make([]byte, len(pattern))
|
|
|
|
n, err := fr.Read(p)
|
|
if err != nil {
|
|
t.Fatalf("error reading pattern: %v", err)
|
|
}
|
|
|
|
if n != len(pattern) {
|
|
t.Fatalf("incorrect read length: %d != %d", n, len(pattern))
|
|
}
|
|
|
|
if string(p) != pattern {
|
|
t.Fatalf("incorrect read content: %q != %q", p, pattern)
|
|
}
|
|
|
|
// Check offset
|
|
current, err := fr.Seek(0, os.SEEK_CUR)
|
|
if err != nil {
|
|
t.Fatalf("error checking current offset: %v", err)
|
|
}
|
|
|
|
if current != targetOffset+int64(len(pattern)) {
|
|
t.Fatalf("unexpected offset after read: %v", err)
|
|
}
|
|
}
|
|
|
|
start, err := fr.Seek(0, os.SEEK_SET)
|
|
if err != nil {
|
|
t.Fatalf("error seeking to start: %v", err)
|
|
}
|
|
|
|
if start != 0 {
|
|
t.Fatalf("expected to seek to start: %v != 0", start)
|
|
}
|
|
|
|
end, err := fr.Seek(0, os.SEEK_END)
|
|
if err != nil {
|
|
t.Fatalf("error checking current offset: %v", err)
|
|
}
|
|
|
|
if end != int64(len(content)) {
|
|
t.Fatalf("expected to seek to end: %v != %v", end, len(content))
|
|
}
|
|
|
|
// 4. Seek before start, ensure error.
|
|
|
|
// seek before start
|
|
before, err := fr.Seek(-1, os.SEEK_SET)
|
|
if err == nil {
|
|
t.Fatalf("error expected, returned offset=%v", before)
|
|
}
|
|
|
|
// 5. Seek after end,
|
|
after, err := fr.Seek(1, os.SEEK_END)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error expected, returned offset=%v", after)
|
|
}
|
|
|
|
p := make([]byte, 16)
|
|
n, err := fr.Read(p)
|
|
|
|
if n != 0 {
|
|
t.Fatalf("bytes reads %d != %d", n, 0)
|
|
}
|
|
|
|
if err != io.EOF {
|
|
t.Fatalf("expected io.EOF, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestFileReaderNonExistentFile ensures the reader behaves as expected with a
|
|
// missing or zero-length remote file. While the file may not exist, the
|
|
// reader should not error out on creation and should return 0-bytes from the
|
|
// read method, with an io.EOF error.
|
|
func TestFileReaderNonExistentFile(t *testing.T) {
|
|
driver := inmemory.New()
|
|
fr, err := newFileReader(driver, "/doesnotexist")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error initializing reader: %v", err)
|
|
}
|
|
|
|
var buf [1024]byte
|
|
|
|
n, err := fr.Read(buf[:])
|
|
if n != 0 {
|
|
t.Fatalf("non-zero byte read reported: %d != 0", n)
|
|
}
|
|
|
|
if err != io.EOF {
|
|
t.Fatalf("read on missing file should return io.EOF, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestLayerReadErrors covers the various error return type for different
|
|
// conditions that can arise when reading a layer.
|
|
func TestFileReaderErrors(t *testing.T) {
|
|
// TODO(stevvooe): We need to cover error return types, driven by the
|
|
// errors returned via the HTTP API. For now, here is a incomplete list:
|
|
//
|
|
// 1. Layer Not Found: returned when layer is not found or access is
|
|
// denied.
|
|
// 2. Layer Unavailable: returned when link references are unresolved,
|
|
// but layer is known to the registry.
|
|
// 3. Layer Invalid: This may more split into more errors, but should be
|
|
// returned when name or tarsum does not reference a valid error. We
|
|
// may also need something to communication layer verification errors
|
|
// for the inline tarsum check.
|
|
// 4. Timeout: timeouts to backend. Need to better understand these
|
|
// failure cases and how the storage driver propagates these errors
|
|
// up the stack.
|
|
}
|