snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-16 03:23:42 +05:00
commit 89f5be005d
3997 changed files with 961970 additions and 0 deletions

View File

@@ -0,0 +1,479 @@
package vacuum
import (
"bytes"
"context"
"fmt"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/operation"
"github.com/seaweedfs/seaweedfs/weed/pb"
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
"github.com/seaweedfs/seaweedfs/weed/shell"
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
type TestCluster struct {
masterCmd *exec.Cmd
volumeServers []*exec.Cmd
}
func (c *TestCluster) Stop() {
for _, cmd := range c.volumeServers {
if cmd != nil && cmd.Process != nil {
cmd.Process.Kill()
cmd.Wait()
}
}
if c.masterCmd != nil && c.masterCmd.Process != nil {
c.masterCmd.Process.Kill()
c.masterCmd.Wait()
}
}
func startCluster(ctx context.Context, dataDir string) (*TestCluster, error) {
weedBinary := findWeedBinary()
if weedBinary == "" {
return nil, fmt.Errorf("weed binary not found - build with 'cd weed && go build' first")
}
cluster := &TestCluster{}
masterDir := filepath.Join(dataDir, "master")
os.MkdirAll(masterDir, 0755)
// Empty security.toml to disable JWT in tests
os.WriteFile(filepath.Join(dataDir, "security.toml"), []byte("# test\n"), 0644)
// Start master
masterCmd := exec.CommandContext(ctx, weedBinary, "master",
"-port", "9333",
"-mdir", masterDir,
"-volumeSizeLimitMB", "10",
"-ip", "127.0.0.1",
)
masterCmd.Dir = dataDir
masterLog, _ := os.Create(filepath.Join(masterDir, "master.log"))
masterCmd.Stdout = masterLog
masterCmd.Stderr = masterLog
if err := masterCmd.Start(); err != nil {
return nil, fmt.Errorf("start master: %v", err)
}
cluster.masterCmd = masterCmd
time.Sleep(2 * time.Second)
// Start 2 volume servers (enough for vacuum testing)
for i := 0; i < 2; i++ {
volumeDir := filepath.Join(dataDir, fmt.Sprintf("volume%d", i))
os.MkdirAll(volumeDir, 0755)
port := fmt.Sprintf("808%d", i)
volumeCmd := exec.CommandContext(ctx, weedBinary, "volume",
"-port", port,
"-dir", volumeDir,
"-max", "10",
"-master", "127.0.0.1:9333",
"-ip", "127.0.0.1",
)
volumeCmd.Dir = dataDir
volumeLog, _ := os.Create(filepath.Join(volumeDir, "volume.log"))
volumeCmd.Stdout = volumeLog
volumeCmd.Stderr = volumeLog
if err := volumeCmd.Start(); err != nil {
cluster.Stop()
return nil, fmt.Errorf("start volume server %d: %v", i, err)
}
cluster.volumeServers = append(cluster.volumeServers, volumeCmd)
}
time.Sleep(5 * time.Second)
return cluster, nil
}
func findWeedBinary() string {
candidates := []string{
"../../weed/weed",
"../weed/weed",
"./weed",
}
for _, c := range candidates {
if _, err := os.Stat(c); err == nil {
if abs, err := filepath.Abs(c); err == nil {
return abs
}
return c
}
}
if path, err := exec.LookPath("weed"); err == nil {
return path
}
return ""
}
func waitForServer(address string, timeout time.Duration) error {
start := time.Now()
for time.Since(start) < timeout {
if conn, err := net.DialTimeout("tcp", address, 1*time.Second); err == nil {
conn.Close()
return nil
}
time.Sleep(500 * time.Millisecond)
}
return fmt.Errorf("timeout waiting for server %s", address)
}
func uploadData(masterAddr, collection string, data []byte) (string, needle.VolumeId, error) {
assignResult, err := operation.Assign(context.Background(), func(ctx context.Context) pb.ServerAddress {
return pb.ServerAddress(masterAddr)
}, grpc.WithInsecure(), &operation.VolumeAssignRequest{
Count: 1,
Collection: collection,
})
if err != nil {
return "", 0, fmt.Errorf("assign: %v", err)
}
uploader, err := operation.NewUploader()
if err != nil {
return "", 0, fmt.Errorf("new uploader: %v", err)
}
uploadResult, err, _ := uploader.Upload(context.Background(), bytes.NewReader(data), &operation.UploadOption{
UploadUrl: "http://" + assignResult.Url + "/" + assignResult.Fid,
Filename: "testfile.txt",
MimeType: "text/plain",
})
if err != nil {
return "", 0, fmt.Errorf("upload: %v", err)
}
if uploadResult.Error != "" {
return "", 0, fmt.Errorf("upload error: %s", uploadResult.Error)
}
fid, err := needle.ParseFileIdFromString(assignResult.Fid)
if err != nil {
return "", 0, err
}
return assignResult.Fid, fid.VolumeId, nil
}
func deleteFile(masterAddr string, fid string) error {
results := operation.DeleteFileIds(func(ctx context.Context) pb.ServerAddress {
return pb.ServerAddress(masterAddr)
}, false, grpc.WithInsecure(), []string{fid})
for _, r := range results {
if r.Error != "" {
return fmt.Errorf("delete %s: %s", fid, r.Error)
}
}
return nil
}
func getGarbageRatio(volumeServerAddr string, volumeId uint32) (float64, error) {
var ratio float64
err := operation.WithVolumeServerClient(false, pb.ServerAddress(volumeServerAddr), grpc.WithInsecure(),
func(client volume_server_pb.VolumeServerClient) error {
resp, err := client.VacuumVolumeCheck(context.Background(), &volume_server_pb.VacuumVolumeCheckRequest{
VolumeId: volumeId,
})
if err != nil {
return err
}
ratio = resp.GarbageRatio
return nil
})
return ratio, err
}
// TestVacuumIntegration tests the full vacuum flow:
// upload data → delete some → verify garbage → vacuum → verify cleanup
func TestVacuumIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
testDir := t.TempDir()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
cluster, err := startCluster(ctx, testDir)
require.NoError(t, err)
defer cluster.Stop()
require.NoError(t, waitForServer("127.0.0.1:9333", 30*time.Second))
require.NoError(t, waitForServer("127.0.0.1:8080", 30*time.Second))
require.NoError(t, waitForServer("127.0.0.1:8081", 30*time.Second))
masterAddr := "127.0.0.1:9333"
collection := "vactest"
// Upload files large enough that deleting most creates significant garbage.
// With volumeSizeLimitMB=10, we need several MB of garbage to exceed the
// 10% threshold passed to vacuum.
const fileSize = 500 * 1024 // 500 KB per file
const totalFiles = 16
const filesToDelete = 12 // delete 75% → ~6 MB garbage out of ~8 MB
var fids []string
var payloads [][]byte
var fileVolumes []needle.VolumeId
for i := 0; i < totalFiles; i++ {
data := bytes.Repeat([]byte{byte('A' + i%26)}, fileSize)
fid, vid, err := uploadData(masterAddr, collection, data)
require.NoError(t, err, "upload %d", i)
fids = append(fids, fid)
payloads = append(payloads, data)
fileVolumes = append(fileVolumes, vid)
}
// Collect the set of volumes that will contain garbage after the deletes below.
// The master may spread uploads across multiple volumes, so we cannot assume
// a single volume id holds all the garbage.
dirtyVolumesSet := map[needle.VolumeId]struct{}{}
for i := 0; i < filesToDelete; i++ {
dirtyVolumesSet[fileVolumes[i]] = struct{}{}
}
var dirtyVolumes []needle.VolumeId
for v := range dirtyVolumesSet {
dirtyVolumes = append(dirtyVolumes, v)
}
// Sort for deterministic log output and stable iteration order across runs.
sort.Slice(dirtyVolumes, func(i, j int) bool { return dirtyVolumes[i] < dirtyVolumes[j] })
t.Logf("Uploaded %d files (%d KB each) across volumes %v; will delete from volumes %v",
totalFiles, fileSize/1024, fileVolumes, dirtyVolumes)
// Wait for heartbeat to report sizes
time.Sleep(6 * time.Second)
// Delete most files to create garbage well above the threshold
for i := 0; i < filesToDelete; i++ {
err := deleteFile(masterAddr, fids[i])
require.NoError(t, err, "delete %s", fids[i])
}
t.Logf("Deleted %d of %d files to create garbage", filesToDelete, totalFiles)
// Wait for heartbeat to report deletions
time.Sleep(6 * time.Second)
// Verify garbage exists on every volume we deleted from.
// Retry briefly in case heartbeats / deletions have not fully settled.
// We require all dirty volumes to report garbage > threshold so that
// the subsequent vacuum + cleanup check has a well-defined expectation
// for every volume, not just the first one that happens to be ready.
t.Run("verify_garbage_before_vacuum", func(t *testing.T) {
deadline := time.Now().Add(20 * time.Second)
var lastMissing needle.VolumeId
for {
ready := true
for _, vid := range dirtyVolumes {
volumeReady := false
for _, addr := range []string{"127.0.0.1:8080", "127.0.0.1:8081"} {
ratio, err := getGarbageRatio(addr, uint32(vid))
if err != nil {
continue
}
t.Logf("Garbage ratio for volume %d on %s: %.2f%%", vid, addr, ratio*100)
if ratio > 0.1 {
volumeReady = true
break
}
}
if !volumeReady {
ready = false
lastMissing = vid
break
}
}
if ready {
return
}
if time.Now().After(deadline) {
break
}
time.Sleep(1 * time.Second)
}
t.Fatalf("volume %d did not report garbage > 10%% — test data setup failed", lastMissing)
})
// Execute vacuum via shell command
t.Run("run_vacuum", func(t *testing.T) {
options := &shell.ShellOptions{
Masters: stringPtr(masterAddr),
GrpcDialOption: grpc.WithInsecure(),
FilerGroup: stringPtr("default"),
}
commandEnv := shell.NewCommandEnv(options)
// The session context outlives the vacuum command: a deadline here
// closes the master connection mid-command on a slow runner, and the
// leaked lock renewal then spins until the test binary's timeout.
shellCtx, shellCancel := context.WithCancel(context.Background())
defer shellCancel()
go commandEnv.MasterClient.KeepConnectedToMaster(shellCtx)
connectCtx, connectCancel := context.WithTimeout(shellCtx, 30*time.Second)
commandEnv.MasterClient.WaitUntilConnected(connectCtx)
connectCancel()
time.Sleep(2 * time.Second)
// Acquire lock (required by shell commands)
locked, unlock := tryLock(t, commandEnv, 30*time.Second)
require.True(t, locked, "could not acquire shell lock")
defer unlock()
// Find and execute vacuum command
var output bytes.Buffer
var found bool
var err error
for _, cmd := range shell.Commands {
if cmd.Name() == "volume.vacuum" {
err = cmd.Do(
[]string{"-garbageThreshold", "0.1", "-collection", collection},
commandEnv, &output,
)
found = true
break
}
}
require.True(t, found, "volume.vacuum command not found")
t.Logf("Vacuum output: %s", output.String())
require.NoError(t, err, "vacuum command failed")
t.Log("Vacuum completed successfully")
})
// Verify garbage was cleaned on every volume we deleted from.
// Vacuum + heartbeat reporting is asynchronous, so retry until each
// volume reports a cleaned ratio or the deadline expires.
t.Run("verify_cleanup_after_vacuum", func(t *testing.T) {
deadline := time.Now().Add(30 * time.Second)
remaining := map[needle.VolumeId]struct{}{}
for _, vid := range dirtyVolumes {
remaining[vid] = struct{}{}
}
failureReasons := map[needle.VolumeId]string{}
for {
for vid := range remaining {
var volumeFound, cleanupVerified bool
for _, addr := range []string{"127.0.0.1:8080", "127.0.0.1:8081"} {
ratio, err := getGarbageRatio(addr, uint32(vid))
if err != nil {
continue
}
volumeFound = true
t.Logf("Garbage ratio for volume %d after vacuum on %s: %.2f%%", vid, addr, ratio*100)
if ratio < 0.05 {
cleanupVerified = true
break
}
}
switch {
case !volumeFound:
failureReasons[vid] = fmt.Sprintf("no server reported volume %d after vacuum", vid)
case !cleanupVerified:
failureReasons[vid] = fmt.Sprintf("garbage on volume %d was not cleaned up after vacuum", vid)
default:
delete(remaining, vid)
delete(failureReasons, vid)
}
}
if len(remaining) == 0 {
return
}
if time.Now().After(deadline) {
break
}
time.Sleep(1 * time.Second)
}
stillFailing := make([]needle.VolumeId, 0, len(remaining))
for vid := range remaining {
stillFailing = append(stillFailing, vid)
}
sort.Slice(stillFailing, func(i, j int) bool { return stillFailing[i] < stillFailing[j] })
msgs := make([]string, 0, len(stillFailing))
for _, vid := range stillFailing {
msgs = append(msgs, failureReasons[vid])
}
t.Fatalf("cleanup verification failed for %d volume(s): %s",
len(stillFailing), strings.Join(msgs, "; "))
})
// Verify remaining files are still readable with correct contents
t.Run("verify_remaining_data", func(t *testing.T) {
for i := filesToDelete; i < totalFiles; i++ {
fid := fids[i]
expected := payloads[i]
// Read file via HTTP from volume server
client := &http.Client{Timeout: 5 * time.Second}
url := fmt.Sprintf("http://127.0.0.1:8080/%s", fid)
resp, err := client.Get(url)
if err != nil || resp.StatusCode == http.StatusNotFound {
if resp != nil {
resp.Body.Close()
}
url = fmt.Sprintf("http://127.0.0.1:8081/%s", fid)
resp, err = client.Get(url)
}
require.NoError(t, err, "read fid %s", fid)
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
require.NoError(t, err, "read body of fid %s", fid)
require.Equal(t, http.StatusOK, resp.StatusCode, "fid %s returned %d", fid, resp.StatusCode)
require.Equal(t, len(expected), len(body), "fid %s size mismatch", fid)
require.True(t, bytes.Equal(expected, body), "fid %s content mismatch", fid)
t.Logf("File %s verified (%d bytes)", fid, len(body))
}
})
}
func stringPtr(s string) *string {
return &s
}
func tryLock(t *testing.T, commandEnv *shell.CommandEnv, timeout time.Duration) (locked bool, unlock func()) {
t.Helper()
type result struct {
err error
}
done := make(chan result, 1)
go func() {
for _, cmd := range shell.Commands {
if cmd.Name() == "lock" {
var out bytes.Buffer
done <- result{err: cmd.Do([]string{}, commandEnv, &out)}
return
}
}
done <- result{err: fmt.Errorf("lock command not found")}
}()
select {
case res := <-done:
if res.err != nil {
t.Logf("lock failed: %v", res.err)
return false, nil
}
return true, func() {
for _, cmd := range shell.Commands {
if cmd.Name() == "unlock" {
var out bytes.Buffer
cmd.Do([]string{}, commandEnv, &out)
return
}
}
}
case <-time.After(timeout):
t.Log("lock timed out")
return false, nil
}
}