Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
493 lines
15 KiB
Go
493 lines
15 KiB
Go
package remote_cache
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Test configuration
|
|
// Uses two SeaweedFS instances:
|
|
// - Primary: The one being tested (has remote caching)
|
|
// - Remote: Acts as the "remote" S3 storage
|
|
const (
|
|
// Primary SeaweedFS
|
|
primaryEndpoint = "http://localhost:8333"
|
|
primaryMasterPort = "9333"
|
|
|
|
// Remote SeaweedFS (acts as remote storage)
|
|
remoteEndpoint = "http://localhost:8334"
|
|
|
|
// Credentials (anonymous access for testing)
|
|
accessKey = "some_access_key1"
|
|
secretKey = "some_secret_key1"
|
|
|
|
// Bucket name - mounted on primary as remote storage
|
|
testBucket = "remotemounted"
|
|
|
|
// Bucket name on the remote that testBucket is mounted on
|
|
remoteBucket = "remotesourcebucket"
|
|
|
|
// Path to weed binary
|
|
weedBinary = "../../../weed/weed_binary"
|
|
)
|
|
|
|
var (
|
|
primaryClient *s3.S3
|
|
primaryClientOnce sync.Once
|
|
)
|
|
|
|
func getPrimaryClient() *s3.S3 {
|
|
primaryClientOnce.Do(func() {
|
|
primaryClient = createS3Client(primaryEndpoint)
|
|
})
|
|
return primaryClient
|
|
}
|
|
|
|
func createS3Client(endpoint string) *s3.S3 {
|
|
sess, err := session.NewSession(&aws.Config{
|
|
Region: aws.String("us-east-1"),
|
|
Endpoint: aws.String(endpoint),
|
|
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
|
|
DisableSSL: aws.Bool(!strings.HasPrefix(endpoint, "https")),
|
|
S3ForcePathStyle: aws.Bool(true),
|
|
})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("failed to create session: %v", err))
|
|
}
|
|
return s3.New(sess)
|
|
}
|
|
|
|
// checkServersRunning ensures the servers are running and fails if they aren't
|
|
func checkServersRunning(t *testing.T) {
|
|
resp, err := http.Get(primaryEndpoint)
|
|
require.NoErrorf(t, err, "Primary SeaweedFS not running at %s", primaryEndpoint)
|
|
resp.Body.Close()
|
|
|
|
resp, err = http.Get(remoteEndpoint)
|
|
require.NoErrorf(t, err, "Remote SeaweedFS not running at %s", remoteEndpoint)
|
|
resp.Body.Close()
|
|
}
|
|
|
|
// stripLogs removes SeaweedFS log lines from the output
|
|
func stripLogs(output string) string {
|
|
lines := strings.Split(output, "\n")
|
|
var filtered []string
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if len(trimmed) > 0 && (trimmed[0] == 'I' || trimmed[0] == 'W' || trimmed[0] == 'E' || trimmed[0] == 'F') && len(trimmed) > 5 && isDigit(trimmed[1]) {
|
|
continue
|
|
}
|
|
filtered = append(filtered, line)
|
|
}
|
|
return strings.Join(filtered, "\n")
|
|
}
|
|
|
|
func isDigit(b byte) bool {
|
|
return b >= '0' && b <= '9'
|
|
}
|
|
|
|
// runWeedShell executes a weed shell command
|
|
func runWeedShell(t *testing.T, command string) (string, error) {
|
|
cmd := exec.Command(weedBinary, "shell", "-master=localhost:"+primaryMasterPort)
|
|
cmd.Stdin = strings.NewReader(command + "\nexit\n")
|
|
output, err := cmd.CombinedOutput()
|
|
result := stripLogs(string(output))
|
|
if err != nil {
|
|
t.Logf("weed shell command '%s' failed: %v, output: %s", command, err, result)
|
|
return result, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// runWeedShellWithOutput executes a weed shell command and returns output even on error
|
|
func runWeedShellWithOutput(t *testing.T, command string) (output string, err error) {
|
|
cmd := exec.Command(weedBinary, "shell", "-master=localhost:"+primaryMasterPort)
|
|
cmd.Stdin = strings.NewReader(command + "\nexit\n")
|
|
outputBytes, err := cmd.CombinedOutput()
|
|
output = stripLogs(string(outputBytes))
|
|
if err != nil {
|
|
t.Logf("weed shell command '%s' output: %s", command, output)
|
|
}
|
|
return output, err
|
|
}
|
|
|
|
// createTestFile creates a test file with specific content via S3
|
|
func createTestFile(t *testing.T, key string, size int) []byte {
|
|
data := make([]byte, size)
|
|
for i := range data {
|
|
data[i] = byte(i % 256)
|
|
}
|
|
uploadToPrimary(t, key, data)
|
|
return data
|
|
}
|
|
|
|
// verifyFileContent verifies file content matches expected data
|
|
func verifyFileContent(t *testing.T, key string, expected []byte) {
|
|
actual := getFromPrimary(t, key)
|
|
assert.Equal(t, expected, actual, "file content mismatch for %s", key)
|
|
}
|
|
|
|
// waitForCondition waits for a condition to be true with timeout
|
|
func waitForCondition(t *testing.T, condition func() bool, timeout time.Duration, message string) bool {
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
if condition() {
|
|
return true
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
t.Logf("Timeout waiting for: %s", message)
|
|
return false
|
|
}
|
|
|
|
// fileExists checks if a file exists via S3
|
|
func fileExists(t *testing.T, key string) bool {
|
|
_, err := getPrimaryClient().HeadObject(&s3.HeadObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(key),
|
|
})
|
|
return err == nil
|
|
}
|
|
|
|
// uploadToPrimary uploads an object to the primary SeaweedFS (local write)
|
|
func uploadToPrimary(t *testing.T, key string, data []byte) {
|
|
_, err := getPrimaryClient().PutObject(&s3.PutObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(key),
|
|
Body: bytes.NewReader(data),
|
|
})
|
|
require.NoError(t, err, "failed to upload to primary SeaweedFS")
|
|
}
|
|
|
|
// getFromPrimary gets an object from primary SeaweedFS
|
|
func getFromPrimary(t *testing.T, key string) []byte {
|
|
resp, err := getPrimaryClient().GetObject(&s3.GetObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(key),
|
|
})
|
|
require.NoError(t, err, "failed to get from primary SeaweedFS")
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err, "failed to read response body")
|
|
return data
|
|
}
|
|
|
|
// uncacheLocal purges the local cache, forcing data to be fetched from remote
|
|
func uncacheLocal(t *testing.T, pattern string) {
|
|
t.Logf("Purging local cache for pattern: %s", pattern)
|
|
output, err := runWeedShell(t, fmt.Sprintf("remote.uncache -dir=/buckets/%s -include=%s", testBucket, pattern))
|
|
if err != nil {
|
|
t.Logf("uncacheLocal warning: %v", err)
|
|
}
|
|
t.Log(output)
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
// TestRemoteCacheBasic tests the basic caching workflow:
|
|
// 1. Write to local
|
|
// 2. Uncache (push to remote, remove local chunks)
|
|
// 3. Read (triggers caching from remote)
|
|
func TestRemoteCacheBasic(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("test-basic-%d.txt", time.Now().UnixNano())
|
|
testData := []byte("Hello, this is test data for remote caching!")
|
|
|
|
// Step 1: Write to local
|
|
t.Log("Step 1: Writing object to primary SeaweedFS (local)...")
|
|
uploadToPrimary(t, testKey, testData)
|
|
|
|
// Verify it's readable
|
|
result := getFromPrimary(t, testKey)
|
|
assert.Equal(t, testData, result, "initial read mismatch")
|
|
|
|
// Step 2: Uncache - push to remote and remove local chunks
|
|
t.Log("Step 2: Uncaching (pushing to remote, removing local chunks)...")
|
|
uncacheLocal(t, testKey)
|
|
|
|
// Step 3: Read - this should trigger caching from remote
|
|
t.Log("Step 3: Reading object (should trigger caching from remote)...")
|
|
start := time.Now()
|
|
result = getFromPrimary(t, testKey)
|
|
firstReadDuration := time.Since(start)
|
|
|
|
assert.Equal(t, testData, result, "data mismatch after cache")
|
|
t.Logf("First read (from remote) took %v", firstReadDuration)
|
|
|
|
// Step 4: Read again - should be from local cache
|
|
t.Log("Step 4: Reading again (should be from local cache)...")
|
|
start = time.Now()
|
|
result = getFromPrimary(t, testKey)
|
|
secondReadDuration := time.Since(start)
|
|
|
|
assert.Equal(t, testData, result, "data mismatch on cached read")
|
|
t.Logf("Second read (from cache) took %v", secondReadDuration)
|
|
|
|
t.Log("Basic caching test passed")
|
|
}
|
|
|
|
// TestRemoteCacheConcurrent tests that concurrent reads of the same
|
|
// remote object only trigger ONE caching operation (singleflight deduplication)
|
|
func TestRemoteCacheConcurrent(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("test-concurrent-%d.txt", time.Now().UnixNano())
|
|
// Use larger data to make caching take measurable time
|
|
testData := make([]byte, 1024*1024) // 1MB
|
|
for i := range testData {
|
|
testData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Step 1: Write to local
|
|
t.Log("Step 1: Writing 1MB object to primary SeaweedFS...")
|
|
uploadToPrimary(t, testKey, testData)
|
|
|
|
// Verify it's readable
|
|
result := getFromPrimary(t, testKey)
|
|
assert.Equal(t, len(testData), len(result), "initial size mismatch")
|
|
|
|
// Step 2: Uncache
|
|
t.Log("Step 2: Uncaching (pushing to remote)...")
|
|
uncacheLocal(t, testKey)
|
|
|
|
// Step 3: Launch many concurrent reads - singleflight should deduplicate
|
|
numRequests := 10
|
|
var wg sync.WaitGroup
|
|
var successCount atomic.Int32
|
|
var errorCount atomic.Int32
|
|
results := make(chan []byte, numRequests)
|
|
|
|
t.Logf("Step 3: Launching %d concurrent requests...", numRequests)
|
|
startTime := time.Now()
|
|
|
|
for i := 0; i < numRequests; i++ {
|
|
wg.Add(1)
|
|
go func(idx int) {
|
|
defer wg.Done()
|
|
resp, err := getPrimaryClient().GetObject(&s3.GetObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(testKey),
|
|
})
|
|
if err != nil {
|
|
t.Logf("Request %d failed: %v", idx, err)
|
|
errorCount.Add(1)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Logf("Request %d read failed: %v", idx, err)
|
|
errorCount.Add(1)
|
|
return
|
|
}
|
|
|
|
results <- data
|
|
successCount.Add(1)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(results)
|
|
totalDuration := time.Since(startTime)
|
|
|
|
t.Logf("All %d requests completed in %v", numRequests, totalDuration)
|
|
t.Logf("Successful: %d, Failed: %d", successCount.Load(), errorCount.Load())
|
|
|
|
// Verify all successful requests returned correct data
|
|
for data := range results {
|
|
assert.Equal(t, len(testData), len(data), "data length mismatch")
|
|
}
|
|
|
|
// All requests should succeed
|
|
assert.Equal(t, int32(numRequests), successCount.Load(), "some requests failed")
|
|
assert.Equal(t, int32(0), errorCount.Load(), "no requests should fail")
|
|
|
|
t.Log("Concurrent caching test passed")
|
|
}
|
|
|
|
// TestRemoteCacheLargeObject tests caching of larger objects
|
|
func TestRemoteCacheLargeObject(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("test-large-%d.bin", time.Now().UnixNano())
|
|
// 5MB object
|
|
testData := make([]byte, 5*1024*1024)
|
|
for i := range testData {
|
|
testData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Step 1: Write to local
|
|
t.Log("Step 1: Writing 5MB object to primary SeaweedFS...")
|
|
uploadToPrimary(t, testKey, testData)
|
|
|
|
// Verify it's readable
|
|
result := getFromPrimary(t, testKey)
|
|
assert.Equal(t, len(testData), len(result), "initial size mismatch")
|
|
|
|
// Step 2: Uncache
|
|
t.Log("Step 2: Uncaching...")
|
|
uncacheLocal(t, testKey)
|
|
|
|
// Step 3: Read from remote
|
|
t.Log("Step 3: Reading 5MB object (should cache from remote)...")
|
|
start := time.Now()
|
|
result = getFromPrimary(t, testKey)
|
|
duration := time.Since(start)
|
|
|
|
assert.Equal(t, len(testData), len(result), "size mismatch")
|
|
assert.Equal(t, testData, result, "data mismatch")
|
|
t.Logf("Large object cached in %v", duration)
|
|
|
|
t.Log("Large object caching test passed")
|
|
}
|
|
|
|
// TestRemoteCacheRangeRequest tests that range requests work after caching
|
|
func TestRemoteCacheRangeRequest(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("test-range-%d.txt", time.Now().UnixNano())
|
|
testData := []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
|
|
// Write, uncache, then test range request
|
|
t.Log("Writing and uncaching object...")
|
|
uploadToPrimary(t, testKey, testData)
|
|
uncacheLocal(t, testKey)
|
|
|
|
// Range request should work and trigger caching
|
|
t.Log("Testing range request (bytes 10-19)...")
|
|
resp, err := getPrimaryClient().GetObject(&s3.GetObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(testKey),
|
|
Range: aws.String("bytes=10-19"),
|
|
})
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
rangeData, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
expected := testData[10:20] // "ABCDEFGHIJ"
|
|
assert.Equal(t, expected, rangeData, "range data mismatch")
|
|
t.Logf("Range request returned: %s", string(rangeData))
|
|
|
|
t.Log("Range request test passed")
|
|
}
|
|
|
|
// mountRemote remounts the test bucket, keeping the metadata already pulled,
|
|
// with the given extra remote.mount flags.
|
|
func mountRemote(t *testing.T, extraFlags string) {
|
|
cmd := fmt.Sprintf("remote.mount -dir=/buckets/%s -remote=seaweedremote/%s -nonempty -metadataStrategy=lazy %s", testBucket, remoteBucket, extraFlags)
|
|
output, err := runWeedShell(t, cmd)
|
|
require.NoErrorf(t, err, "remount failed: %s", output)
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
// copyLocalToRemote pushes matching local objects to the remote, which is what
|
|
// lets remote.uncache drop their local chunks afterwards.
|
|
func copyLocalToRemote(t *testing.T, pattern string) {
|
|
output, err := runWeedShell(t, fmt.Sprintf("remote.copy.local -dir=/buckets/%s -include=%s", testBucket, pattern))
|
|
require.NoErrorf(t, err, "remote.copy.local failed: %s", output)
|
|
}
|
|
|
|
// localChunkCount reports how many local chunks an entry has, so a test can
|
|
// tell a cached read from one served straight out of the remote.
|
|
func localChunkCount(t *testing.T, key string) string {
|
|
meta, err := runWeedShell(t, fmt.Sprintf("fs.meta.cat /buckets/%s/%s", testBucket, key))
|
|
require.NoError(t, err)
|
|
// fs.meta.cat closes with "chunks N meta size: ..."
|
|
summary := strings.LastIndex(meta, "chunks ")
|
|
require.GreaterOrEqualf(t, summary, 0, "no chunk count in %s", meta)
|
|
return strings.Fields(meta[summary+len("chunks "):])[0]
|
|
}
|
|
|
|
// TestRemoteCacheWaitZero tests that a mount with -cacheWait=0 serves a read of
|
|
// an uncached object from the remote without caching it locally.
|
|
func TestRemoteCacheWaitZero(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("test-cachewait-%d.bin", time.Now().UnixNano())
|
|
testData := make([]byte, 1024*1024)
|
|
for i := range testData {
|
|
testData[i] = byte(i % 256)
|
|
}
|
|
|
|
t.Log("Step 1: Writing 1MB object, pushing it to the remote and dropping the local chunks...")
|
|
uploadToPrimary(t, testKey, testData)
|
|
copyLocalToRemote(t, testKey)
|
|
uncacheLocal(t, testKey)
|
|
require.Equal(t, "0", localChunkCount(t, testKey), "the object must start out remote-only")
|
|
|
|
t.Log("Step 2: Remounting with -cacheWait=0...")
|
|
mountRemote(t, "-cacheWait=0")
|
|
defer mountRemote(t, "")
|
|
|
|
t.Log("Step 3: Reading the object (should stream from remote)...")
|
|
assert.Equal(t, testData, getFromPrimary(t, testKey), "data mismatch reading from remote")
|
|
assert.Equal(t, "0", localChunkCount(t, testKey), "the read must not cache chunks locally")
|
|
|
|
t.Log("Step 4: Reading with the size based wait (should cache locally)...")
|
|
mountRemote(t, "")
|
|
assert.Equal(t, testData, getFromPrimary(t, testKey), "data mismatch reading through the cache")
|
|
assert.NotEqual(t, "0", localChunkCount(t, testKey), "the read must cache chunks locally")
|
|
|
|
t.Log("Zero cache wait test passed")
|
|
}
|
|
|
|
// TestRemoteCacheNotFound tests that non-existent objects return proper errors
|
|
func TestRemoteCacheNotFound(t *testing.T) {
|
|
checkServersRunning(t)
|
|
|
|
testKey := fmt.Sprintf("non-existent-object-%d", time.Now().UnixNano())
|
|
|
|
_, err := getPrimaryClient().GetObject(&s3.GetObjectInput{
|
|
Bucket: aws.String(testBucket),
|
|
Key: aws.String(testKey),
|
|
})
|
|
|
|
assert.Error(t, err, "should get error for non-existent object")
|
|
t.Logf("Got expected error: %v", err)
|
|
|
|
t.Log("Not found test passed")
|
|
}
|
|
|
|
// TestMain sets up and tears down the test environment
|
|
func TestMain(m *testing.M) {
|
|
if !isServerRunning(primaryEndpoint) {
|
|
fmt.Println("WARNING: Primary SeaweedFS not running at", primaryEndpoint)
|
|
fmt.Println(" Run 'make test-with-server' to start servers automatically")
|
|
}
|
|
if !isServerRunning(remoteEndpoint) {
|
|
fmt.Println("WARNING: Remote SeaweedFS not running at", remoteEndpoint)
|
|
fmt.Println(" Run 'make test-with-server' to start servers automatically")
|
|
}
|
|
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func isServerRunning(url string) bool {
|
|
resp, err := http.Get(url)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
resp.Body.Close()
|
|
return true
|
|
}
|