taildrop: add logic for resuming partial files (#9785)
We add the following API: * type FileChecksums * type Checksum * func Manager.PartialFiles * func Manager.HashPartialFile * func ResumeReader The Manager methods provide the ability to query for partial files and retrieve a list of checksums for a given partial file. The ResumeReader function is a helper that wraps an io.Reader to discard content that is identical locally and remotely. The FileChecksums type represents the checksums of a file and is safe to JSON marshal and send over the wire. Updates tailscale/corp#14772 Signed-off-by: Joe Tsai <joetsai@digital-static.net> Co-authored-by: Rhea Ghosh <rhea@tailscale.com>
This commit is contained in:
63
taildrop/resume_test.go
Normal file
63
taildrop/resume_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package taildrop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
"tailscale.com/util/must"
|
||||
)
|
||||
|
||||
func TestResume(t *testing.T) {
|
||||
oldBlockSize := blockSize
|
||||
defer func() { blockSize = oldBlockSize }()
|
||||
blockSize = 256
|
||||
|
||||
m := Manager{Logf: t.Logf, Dir: t.TempDir()}
|
||||
|
||||
rn := rand.New(rand.NewSource(0))
|
||||
want := make([]byte, 12345)
|
||||
must.Get(io.ReadFull(rn, want))
|
||||
|
||||
t.Run("resume-noop", func(t *testing.T) {
|
||||
r := io.Reader(bytes.NewReader(want))
|
||||
offset, r, err := ResumeReader(r, func(offset, length int64) (FileChecksums, error) {
|
||||
return m.HashPartialFile("", "foo", offset, length)
|
||||
})
|
||||
must.Do(err)
|
||||
must.Get(m.PutFile("", "foo", r, offset, -1))
|
||||
got := must.Get(os.ReadFile(must.Get(m.joinDir("foo"))))
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("content mismatches")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("resume-retry", func(t *testing.T) {
|
||||
rn := rand.New(rand.NewSource(0))
|
||||
for {
|
||||
r := io.Reader(bytes.NewReader(want))
|
||||
offset, r, err := ResumeReader(r, func(offset, length int64) (FileChecksums, error) {
|
||||
return m.HashPartialFile("", "foo", offset, length)
|
||||
})
|
||||
must.Do(err)
|
||||
numWant := rn.Int63n(min(int64(len(want))-offset, 1000) + 1)
|
||||
if offset < int64(len(want)) {
|
||||
r = io.MultiReader(io.LimitReader(r, numWant), iotest.ErrReader(io.ErrClosedPipe))
|
||||
}
|
||||
if _, err := m.PutFile("", "foo", r, offset, -1); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
got := must.Get(os.ReadFile(must.Get(m.joinDir("foo"))))
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("content mismatches")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
Reference in New Issue
Block a user