Skip to content

Commit

Permalink
Test S3 gateway multipart copy in Esti
Browse files Browse the repository at this point in the history
  • Loading branch information
arielshaqed committed Feb 15, 2024
1 parent 754398d commit 37b3281
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
82 changes: 82 additions & 0 deletions esti/s3_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package esti

import (
"bytes"
"hash/crc32"
"io"
"math/rand"
"net/http"
Expand Down Expand Up @@ -328,6 +329,87 @@ func TestS3HeadBucket(t *testing.T) {
})
}

func TestS3CopyObjectMultipart(t *testing.T) {
const minDataContentLengthForMultipart = 5 << 20
ctx, _, repo := setupTest(t)
defer tearDownTest(repo)

// additional repository for copy between repos
const destRepoName = "tests3copyobjectdest"
destRepo := createRepositoryByName(ctx, t, destRepoName)
defer deleteRepositoryIfAskedTo(ctx, destRepoName)

// content
r := rand.New(rand.NewSource(17))
objContent := testutil.RandomReader(r, largeDataContentLength)
srcPath := gatewayTestPrefix + "source-file"
destPath := gatewayTestPrefix + "dest-file"

// upload data
s3lakefsClient := newMinioClient(t, credentials.NewStaticV2)
_, err := s3lakefsClient.PutObject(ctx, repo, srcPath, objContent, largeDataContentLength,
minio.PutObjectOptions{})
require.NoError(t, err)

dest := minio.CopyDestOptions{
Bucket: destRepo,
Object: destPath,
}

srcs := []minio.CopySrcOptions{
{
Bucket: srcRepo,
Object: srcPath,
Start: 0,
End: minDataContentLengthForMultipart,
}, {
Bucket: srcRepo,
Object: srcPath,
Start: minDataContentLengthForMultipart,
End: largeDataContentLength,
},
}

ui, err := s3lakefsClient.ComposeObject(ctx, dst, srcs...)
if err != nil {
t.Fatalf("minio.Client.ComposeObject from(%+v) to(%+v): %s", srcs, dest, err)
}

if ui.Size != largeDataContentLength {
t.Errorf("Copied %d bytes when expecting %d", ui.Size, largeDataContentLength)
}

// Comparing 2 readers is too much work. Instead just hash them.
// This will fail for malicious bad S3 gateways, but otherwise is
// fine.
uploadedReader, err := s3lakefsClient.GetObject(ctx, repo, srcPath, minio.GetObjectOptions{})
if err != nil {
t.Fatalf("Get uploaded object: %s", err)
}
defer uploadedReader.Close()
uploadedCRC, err := testutil.ChecksumReader(uploadedReader)
if err != nil {
t.Fatalf("Read uploaded object: %s", err)
}
if uploadedCRC == 0 {
t.Fatal("Impossibly bad luck: uploaded data with CRC64 == 0!")
}

copiedReader, err := s3lakefsClient.GetObject(ctx, repo, srcPath, minio.GetObjectOptions{})
if err != nil {
t.Fatalf("Get copied object: %s", err)
}
defer copiedReader.Close()
copiedCRC, err := testutil.ChecksumReader(copiedReader)
if err != nil {
t.Fatalf("Read copied object: %s", err)
}

if uploadedCRC != copiedCRC {
t.Fatal("Copy not equal")
}
}

func TestS3CopyObject(t *testing.T) {
ctx, _, repo := setupTest(t)
defer tearDownTest(repo)
Expand Down
4 changes: 4 additions & 0 deletions esti/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ func deleteRepositoryIfAskedTo(ctx context.Context, repositoryName string) {

const randomDataContentLength = 16

// largeDataContentLength is >5 MiB, which is large enough to require
// multipart operations.
const largeDataContentLength = 6 << 20

func uploadFileRandomDataAndReport(ctx context.Context, repo, branch, objPath string, direct bool) (checksum, content string, err error) {
objContent := randstr.String(randomDataContentLength)
checksum, err = uploadFileAndReport(ctx, repo, branch, objPath, objContent, direct)
Expand Down
29 changes: 29 additions & 0 deletions pkg/testutil/checksum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package testutil

Check failure on line 1 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

: # github.com/treeverse/lakefs/pkg/testutil

import (
"hash/crc64"
"io"
)

const bufSize = 4 << 16

var polynomial = crc64.ECMA

Check failure on line 10 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

cannot use crc64.ECMA (untyped int constant 14514072000185962306) as int value in variable declaration (overflows)

Check failure on line 10 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

cannot use crc64.ECMA (untyped int constant 14514072000185962306) as int value in variable declaration (overflows)

Check failure on line 10 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

cannot use crc64.ECMA (untyped int constant 14514072000185962306) as int value in variable declaration (overflows)

// ChecksumReader returns the checksum (CRC-64) of the contents of reader.
func ChecksumReader(reader io.Reader) (uint64, error) {
buf := make([]byte, bufSize)

Check failure on line 14 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

buf declared and not used

Check failure on line 14 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

buf declared and not used

Check failure on line 14 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

buf declared and not used
var val uint64
for {
n, err := r.Read(b)

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

undefined: r

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

undefined: b

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

undefined: r

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

undefined: b

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

undefined: r

Check failure on line 17 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

undefined: b
if err != nil {
if err == io.EOF {
return val, nil
}
return val, err
}
if n == 0 {
return val, nil
}
val = crc64.Update(val, polynomial, b[:n])

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

cannot use polynomial (variable of type int) as *crc64.Table value in argument to crc64.Update

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Go tests

undefined: b

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

cannot use polynomial (variable of type int) as *crc64.Table value in argument to crc64.Update

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

undefined: b

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

cannot use polynomial (variable of type int) as *crc64.Table value in argument to crc64.Update

Check failure on line 27 in pkg/testutil/checksum.go

View workflow job for this annotation

GitHub Actions / Test unified gc

undefined: b
}
}
21 changes: 21 additions & 0 deletions pkg/testutil/random.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testutil

import (
"io"
"math/rand"
"strings"
"unicode/utf8"
Expand Down Expand Up @@ -33,3 +34,23 @@ func RandomString(rand *rand.Rand, size int) string {
_, lastRuneSize := utf8.DecodeLastRuneInString(ret)
return ret[0 : len(ret)-lastRuneSize]
}

type randomReader struct {
rand *rand.Rand
remaining int64
}

func (r *randomReader) Read(p []byte) (n int, err error) {
n := len(p)

Check failure on line 44 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Go tests

no new variables on left side of :=

Check failure on line 44 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

no new variables on left side of :=

Check failure on line 44 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Test unified gc

no new variables on left side of :=
if n > r.remaining {

Check failure on line 45 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Go tests

invalid operation: n > r.remaining (mismatched types int and int64)

Check failure on line 45 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

invalid operation: n > r.remaining (mismatched types int and int64)

Check failure on line 45 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Test unified gc

invalid operation: n > r.remaining (mismatched types int and int64)
n = r.remaining

Check failure on line 46 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Go tests

cannot use r.remaining (variable of type int64) as int value in assignment

Check failure on line 46 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Linters and Checkers

cannot use r.remaining (variable of type int64) as int value in assignment

Check failure on line 46 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Test unified gc

cannot use r.remaining (variable of type int64) as int value in assignment
}
r.rand.Read(p[:n])
r.remaining -= n

Check failure on line 49 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Run Go tests

invalid operation: r.remaining -= n (mismatched types int64 and int)

Check failure on line 49 in pkg/testutil/random.go

View workflow job for this annotation

GitHub Actions / Test unified gc

invalid operation: r.remaining -= n (mismatched types int64 and int)
return n, nil
}

// RandomReader returns a reader that will return size bytes from rand.
func RandomReader(rand *rand.Rand, size int64) io.Reader {
return &randomReader{rand: rand, remaining: size}
}

0 comments on commit 37b3281

Please sign in to comment.