mirror of
https://github.com/hanzoai/s3.git
synced 2026-10-30 14:59:09 +00:00
Full internal rename — build-verified, every module green (root + s3-rdma-sidecar
+ telemetry/server + all test modules):
- Dirs: weed/→s3/ (main tree); cmd/weed-{db,sql}→cmd/s3-{db,sql};
seaweed-volume→s3-volume; seaweedfs-rdma-sidecar→s3-rdma-sidecar;
s3/mount/weedfs_*.go→s3fs_*.go (~113 path renames).
- Imports: github.com/hanzoai/s3/weed/ → /s3/ across 1700+ files.
- gRPC proto services Seaweed/SeaweedFiler/SeaweedMessaging/... → Hanzo*
(8 services) via full protoc regeneration (correct descriptors, no sed-corruption).
- Brand SeaweedFS/Seaweed/seaweed → Hanzo/hanzo; internal X-Seaweedfs-* headers
→ X-Hanzo-*; .seaweedfs config dir → .s3.
- CLI is `s3`: go install github.com/hanzoai/s3/s3@latest | make install | Docker.
install.sh rewritten as a Hanzo S3 installer.
- External forks github.com/seaweedfs/{raft,go-fuse,goexif,cockroachdb-parser}
kept verbatim — immutable third-party module paths; NOTICE retained.
Residual (cosmetic, non-build): kafka loadtest test-proto package com.seaweedfs.loadtest.
103 lines
3.7 KiB
Go
103 lines
3.7 KiB
Go
package volume_server_http_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzoai/s3/test/volume_server/framework"
|
|
"github.com/hanzoai/s3/test/volume_server/matrix"
|
|
"github.com/hanzoai/s3/s3/pb/volume_server_pb"
|
|
"github.com/hanzoai/s3/s3/storage/needle"
|
|
)
|
|
|
|
func TestReplicatedUploadSucceedsImmediatelyAfterAllocate(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in short mode")
|
|
}
|
|
|
|
clusterHarness := framework.StartDualVolumeCluster(t, matrix.P1())
|
|
|
|
conn0, grpc0 := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress(0))
|
|
defer conn0.Close()
|
|
conn1, grpc1 := framework.DialVolumeServer(t, clusterHarness.VolumeGRPCAddress(1))
|
|
defer conn1.Close()
|
|
|
|
const volumeID = uint32(115)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
req := &volume_server_pb.AllocateVolumeRequest{
|
|
VolumeId: volumeID,
|
|
Replication: "001",
|
|
Version: uint32(needle.GetCurrentVersion()),
|
|
}
|
|
if _, err := grpc0.AllocateVolume(ctx, req); err != nil {
|
|
t.Fatalf("allocate replicated volume on node0: %v", err)
|
|
}
|
|
if _, err := grpc1.AllocateVolume(ctx, req); err != nil {
|
|
t.Fatalf("allocate replicated volume on node1: %v", err)
|
|
}
|
|
|
|
client := framework.NewHTTPClient()
|
|
fid := framework.NewFileID(volumeID, 881001, 0x0B0C0D0E)
|
|
payload := []byte("replicated-upload-after-allocate")
|
|
|
|
// The master only learns about replica locations through volume-server
|
|
// heartbeats, which lag behind the direct AllocateVolume gRPC calls above.
|
|
// In production a client obtains its fid from the master assign flow, which
|
|
// guarantees the master already knows every replica; this test crafts the
|
|
// fid by hand, so the replicated write would otherwise look up the master
|
|
// before the second replica is registered and fail with a 500. Wait until
|
|
// the master reports both replicas before uploading.
|
|
if !waitForMasterReplicaCount(t, client, clusterHarness.MasterURL(), volumeID, 2, 10*time.Second) {
|
|
t.Fatalf("master did not report 2 replica locations for volume %d within deadline", volumeID)
|
|
}
|
|
|
|
uploadResp := framework.UploadBytes(t, client, clusterHarness.VolumeAdminURL(0), fid, payload)
|
|
_ = framework.ReadAllAndClose(t, uploadResp)
|
|
if uploadResp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("replicated upload expected 201, got %d", uploadResp.StatusCode)
|
|
}
|
|
|
|
replicaReadURL := clusterHarness.VolumeAdminURL(1) + "/" + fid
|
|
var replicaBody []byte
|
|
if !waitForHTTPStatus(t, client, replicaReadURL, http.StatusOK, 10*time.Second, func(resp *http.Response) {
|
|
replicaBody = framework.ReadAllAndClose(t, resp)
|
|
}) {
|
|
t.Fatalf("replica did not become readable within deadline")
|
|
}
|
|
if string(replicaBody) != string(payload) {
|
|
t.Fatalf("replica body mismatch: got %q want %q", string(replicaBody), string(payload))
|
|
}
|
|
}
|
|
|
|
// waitForMasterReplicaCount polls the master volume lookup until it reports at
|
|
// least want locations for volumeID, or the timeout elapses.
|
|
func waitForMasterReplicaCount(t testing.TB, client *http.Client, masterURL string, volumeID uint32, want int, timeout time.Duration) bool {
|
|
t.Helper()
|
|
|
|
lookupURL := fmt.Sprintf("%s/dir/lookup?volumeId=%d", masterURL, volumeID)
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
resp := framework.DoRequest(t, client, mustNewRequest(t, http.MethodGet, lookupURL))
|
|
body := framework.ReadAllAndClose(t, resp)
|
|
if resp.StatusCode == http.StatusOK {
|
|
var result struct {
|
|
Locations []struct {
|
|
Url string `json:"url"`
|
|
} `json:"locations"`
|
|
}
|
|
if err := json.Unmarshal(body, &result); err == nil && len(result.Locations) >= want {
|
|
return true
|
|
}
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
|
|
return false
|
|
}
|