Files
seaweedfs/weed/storage/blockvol/blockapi/client_test.go
T
Ping QiuandClaude Opus 4.6 979a9b496c feat: Phase 8 CP8-1/2/3/4 -- ops control plane, multi-replica, CSI snapshots, observability
CP8-1: HTTP REST API (create/delete/lookup/list/assign/servers), blockapi Go
client with multi-master failover, 5 shell commands, HTML dashboard at /block/.

CP8-2: RF=2/RF=3 multi-replica support -- ShipperGroup fan-out, distributed
sync, health scoring, segment-based scrub, gated promotion (heartbeat
freshness + WAL LSN + role checks), failover/rebuild for N>2 replicas.

CP8-3: CSI snapshot + expansion -- CreateSnapshot/DeleteSnapshot/ListSnapshots
RPCs, NodeExpandVolume with iSCSI rescan, snapshot ID helpers, 20 adversarial
tests covering concurrent ops, edge cases, and error injection.

CP8-4: Observability -- EngineMetrics atomic counters for flusher/group-commit/
WAL-shipper/scrub, 10 new Prometheus metrics, barrier_lag_lsn SLO gauge,
failover/promotion/rebuild counters, request ID correlation in master gRPC
logs, baseline regression framework with 7 hard-fail conditions.

Total: 63 files, ~11.2K LOC, 160+ new tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:05:17 -08:00

104 lines
2.8 KiB
Go

package blockapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestClientCreateVolume(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" || r.URL.Path != "/block/volume" {
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
var req CreateVolumeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Errorf("decode request: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if req.Name != "test-vol" {
t.Errorf("expected name test-vol, got %s", req.Name)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(VolumeInfo{
Name: req.Name,
VolumeServer: "vs1:9333",
SizeBytes: req.SizeBytes,
Epoch: 1,
Role: "primary",
Status: "active",
})
}))
defer ts.Close()
client := NewClient(ts.URL)
info, err := client.CreateVolume(context.Background(), CreateVolumeRequest{
Name: "test-vol",
SizeBytes: 1 << 30,
})
if err != nil {
t.Fatal(err)
}
if info.Name != "test-vol" {
t.Errorf("expected name test-vol, got %s", info.Name)
}
if info.VolumeServer != "vs1:9333" {
t.Errorf("expected vs1:9333, got %s", info.VolumeServer)
}
}
func TestClientListVolumes(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/block/volumes" {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode([]VolumeInfo{
{Name: "alpha", VolumeServer: "vs1:9333"},
{Name: "beta", VolumeServer: "vs2:9333"},
})
}))
defer ts.Close()
client := NewClient(ts.URL)
vols, err := client.ListVolumes(context.Background())
if err != nil {
t.Fatal(err)
}
if len(vols) != 2 {
t.Fatalf("expected 2 volumes, got %d", len(vols))
}
if vols[0].Name != "alpha" || vols[1].Name != "beta" {
t.Errorf("unexpected volumes: %+v", vols)
}
}
func TestClientMultiMasterFallback(t *testing.T) {
// First server immediately rejects connections (closed listener).
dead := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
deadURL := dead.URL
dead.Close() // close it so connections are refused
// Second server responds normally.
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode([]VolumeInfo{})
}))
defer ts.Close()
client := NewClient(deadURL + "," + ts.URL)
vols, err := client.ListVolumes(context.Background())
if err != nil {
t.Fatal(err)
}
if vols == nil {
t.Error("expected non-nil result")
}
}