-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Introduce sparseio.Copy() and write the disk layers sparsely * vetu create: no need to use temporary.AtomicallyCopyThrough() ...since we're dealing with a temporary VM directory anyways. * Put temporary.AtomicallyCopyThrough() on the sparse rails * sparseio_test.go: add TestCopySmall test * Fix sparseio.Copy() argument order and add TestAtomicallyCopyThrough
- Loading branch information
Showing
6 changed files
with
242 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package sparseio | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
) | ||
|
||
const blockSize = 64 * 1024 | ||
|
||
func Copy(dst io.WriterAt, src io.Reader) error { | ||
chunk := make([]byte, blockSize) | ||
zeroedChunk := make([]byte, blockSize) | ||
|
||
var offset int64 | ||
|
||
for { | ||
n, err := src.Read(chunk) | ||
if err != nil { | ||
if errors.Is(err, io.EOF) { | ||
return nil | ||
} | ||
|
||
return err | ||
} | ||
|
||
// Only write non-zero chunks | ||
if !bytes.Equal(chunk[:n], zeroedChunk[:n]) { | ||
if _, err := dst.WriteAt(chunk[:n], offset); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
offset += int64(n) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package sparseio_test | ||
|
||
import ( | ||
"github.com/cirruslabs/vetu/internal/sparseio" | ||
"github.com/dustin/go-humanize" | ||
"github.com/opencontainers/go-digest" | ||
"github.com/stretchr/testify/require" | ||
"math/rand" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func TestCopySmall(t *testing.T) { | ||
// Create a small file | ||
originalFilePath := filepath.Join(t.TempDir(), "original.txt") | ||
err := os.WriteFile(originalFilePath, []byte("Hello, World!\n"), 0600) | ||
require.NoError(t, err) | ||
|
||
// Sparsely copy it | ||
sparseFilePath := filepath.Join(t.TempDir(), "sparse.txt") | ||
copySparse(t, originalFilePath, sparseFilePath) | ||
|
||
// Ensure that both files have identical contents | ||
require.Equal(t, fileDigest(t, originalFilePath), fileDigest(t, sparseFilePath)) | ||
} | ||
|
||
//nolint:gosec // we don't need cryptographically secure randomness here | ||
func TestCopyRandomized(t *testing.T) { | ||
// Create a sufficiently large file that contains | ||
// interleaved random-filled and zero-filled parts | ||
originalFilePath := filepath.Join(t.TempDir(), "original.bin") | ||
originalFile, err := os.Create(originalFilePath) | ||
require.NoError(t, err) | ||
|
||
var wroteBytes int64 | ||
|
||
for wroteBytes < 1*humanize.GByte { | ||
chunk := randomlySizedChunk(1*humanize.KByte, 4*humanize.MByte) | ||
|
||
// Randomize the contents of some chunks | ||
if rand.Intn(2) == 1 { | ||
//nolint:staticcheck // what's the alternative to the deprecated rand.Read() anyways? | ||
_, err = rand.Read(chunk) | ||
require.NoError(t, err) | ||
} | ||
|
||
n, err := originalFile.Write(chunk) | ||
require.NoError(t, err) | ||
|
||
wroteBytes += int64(n) | ||
} | ||
|
||
require.NoError(t, originalFile.Close()) | ||
|
||
// Sparsely copy the original file | ||
sparseFilePath := filepath.Join(t.TempDir(), "sparse.bin") | ||
copySparse(t, originalFilePath, sparseFilePath) | ||
|
||
// Ensure that the copied file has the same contents as the original file | ||
require.Equal(t, fileDigest(t, originalFilePath), fileDigest(t, sparseFilePath)) | ||
} | ||
|
||
func copySparse(t *testing.T, originalFilePath string, sparseFilePath string) { | ||
originalFile, err := os.Open(originalFilePath) | ||
require.NoError(t, err) | ||
|
||
originalFileInfo, err := originalFile.Stat() | ||
require.NoError(t, err) | ||
|
||
sparseFile, err := os.Create(sparseFilePath) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, sparseFile.Truncate(originalFileInfo.Size())) | ||
require.NoError(t, sparseio.Copy(sparseFile, originalFile)) | ||
|
||
require.NoError(t, originalFile.Close()) | ||
require.NoError(t, sparseFile.Close()) | ||
} | ||
|
||
//nolint:gosec // we don't need cryptographically secure randomness here | ||
func randomlySizedChunk(minBytes int, maxBytes int) []byte { | ||
return make([]byte, rand.Intn(maxBytes-minBytes+1)+minBytes) | ||
} | ||
|
||
func fileDigest(t *testing.T, path string) digest.Digest { | ||
file, err := os.Open(path) | ||
require.NoError(t, err) | ||
|
||
digest, err := digest.FromReader(file) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, file.Close()) | ||
|
||
return digest | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package temporary_test | ||
|
||
import ( | ||
cryptorand "crypto/rand" | ||
"github.com/cirruslabs/vetu/internal/storage/temporary" | ||
"github.com/dustin/go-humanize" | ||
"github.com/opencontainers/go-digest" | ||
"github.com/stretchr/testify/require" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func TestAtomicallyCopyThrough(t *testing.T) { | ||
t.Setenv("VETU_HOME", filepath.Join(t.TempDir(), ".vetu")) | ||
|
||
tmpDir := t.TempDir() | ||
|
||
// Create a source directory | ||
srcDir := filepath.Join(tmpDir, "src") | ||
require.NoError(t, os.Mkdir(srcDir, 0700)) | ||
|
||
// Add a small-sized text file to it | ||
err := os.WriteFile(filepath.Join(srcDir, "text.txt"), []byte("Hello, World!\n"), 0600) | ||
require.NoError(t, err) | ||
|
||
// Add a medium-sized binary file to it | ||
buf := make([]byte, 64*humanize.MByte) | ||
_, err = cryptorand.Read(buf) | ||
require.NoError(t, err) | ||
|
||
err = os.WriteFile(filepath.Join(srcDir, "binary.bin"), buf, 0600) | ||
require.NoError(t, err) | ||
|
||
// Copy source directory contents to destination directory | ||
dstDir := filepath.Join(tmpDir, "dst") | ||
require.NoError(t, temporary.AtomicallyCopyThrough(srcDir, dstDir)) | ||
|
||
// Ensure that the files copied are identical | ||
// to the ones in the source directory | ||
require.Equal(t, fileDigest(t, filepath.Join(dstDir, "text.txt")), digest.FromString("Hello, World!\n")) | ||
require.Equal(t, fileDigest(t, filepath.Join(dstDir, "binary.bin")), digest.FromBytes(buf)) | ||
} | ||
|
||
func fileDigest(t *testing.T, path string) digest.Digest { | ||
file, err := os.Open(path) | ||
require.NoError(t, err) | ||
|
||
digest, err := digest.FromReader(file) | ||
require.NoError(t, err) | ||
|
||
require.NoError(t, file.Close()) | ||
|
||
return digest | ||
} |