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
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:
519
test/multi_master/cluster.go
Normal file
519
test/multi_master/cluster.go
Normal file
@@ -0,0 +1,519 @@
|
||||
package multi_master
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/test/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
waitTimeout = 30 * time.Second
|
||||
waitTick = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
// masterNode represents a single master process in the cluster.
|
||||
type masterNode struct {
|
||||
port int
|
||||
grpcPort int
|
||||
dataDir string
|
||||
cmd *exec.Cmd
|
||||
logFile string
|
||||
stopped bool
|
||||
// peersStr overrides the cluster-wide peer list for this node, so a test
|
||||
// can start a master that only knows about a subset of the cluster.
|
||||
peersStr string
|
||||
// raftBootstrap starts the master with -raftBootstrap, the way the helm
|
||||
// chart renders master.raftBootstrap on every master, every restart.
|
||||
raftBootstrap bool
|
||||
}
|
||||
|
||||
// MasterCluster manages a 3-node master raft cluster for integration tests.
|
||||
type MasterCluster struct {
|
||||
t testing.TB
|
||||
weedBinary string
|
||||
baseDir string
|
||||
logsDir string
|
||||
keepLogs bool
|
||||
|
||||
nodes [3]*masterNode
|
||||
mu sync.Mutex
|
||||
|
||||
// peers string shared by all nodes, e.g. "127.0.0.1:9333,127.0.0.1:9334,127.0.0.1:9335"
|
||||
peersStr string
|
||||
|
||||
// raftHashicorp starts the masters with -raftHashicorp
|
||||
raftHashicorp bool
|
||||
}
|
||||
|
||||
// clusterStatus is the JSON returned by /cluster/status.
|
||||
type clusterStatus struct {
|
||||
IsLeader bool `json:"IsLeader"`
|
||||
Leader string `json:"Leader"`
|
||||
Peers []string `json:"Peers"`
|
||||
}
|
||||
|
||||
// StartMasterCluster boots a 3-node master raft cluster and waits for a leader.
|
||||
func StartMasterCluster(t testing.TB) *MasterCluster {
|
||||
t.Helper()
|
||||
|
||||
mc := NewMasterCluster(t, false)
|
||||
for i := range 3 {
|
||||
mc.StartNode(i)
|
||||
}
|
||||
|
||||
if err := mc.WaitForLeader(waitTimeout); err != nil {
|
||||
mc.DumpLogs()
|
||||
mc.StopAll()
|
||||
t.Fatalf("cluster did not elect a leader: %v", err)
|
||||
}
|
||||
|
||||
// Wait for TopologyId to be generated and propagated. This is async
|
||||
// after leader election, and we need it committed before tests can
|
||||
// reliably stop/restart nodes.
|
||||
if _, err := mc.WaitForTopologyId(waitTimeout); err != nil {
|
||||
mc.DumpLogs()
|
||||
mc.StopAll()
|
||||
t.Fatalf("TopologyId not generated: %v", err)
|
||||
}
|
||||
|
||||
return mc
|
||||
}
|
||||
|
||||
// NewMasterCluster allocates ports and data directories for a 3-node master
|
||||
// cluster without starting anything, so a test can choose what each node comes
|
||||
// up with.
|
||||
func NewMasterCluster(t testing.TB, raftHashicorp bool) *MasterCluster {
|
||||
t.Helper()
|
||||
|
||||
weedBinary, err := findOrBuildWeedBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("resolve weed binary: %v", err)
|
||||
}
|
||||
|
||||
keepLogs := os.Getenv("MULTI_MASTER_IT_KEEP_LOGS") == "1"
|
||||
baseDir, err := os.MkdirTemp("", "seaweedfs_multi_master_it_")
|
||||
if err != nil {
|
||||
t.Fatalf("create temp dir: %v", err)
|
||||
}
|
||||
logsDir := filepath.Join(baseDir, "logs")
|
||||
os.MkdirAll(logsDir, 0o755)
|
||||
|
||||
// Allocate 3 mini-safe ports (each guarantees port+10000 is also free).
|
||||
httpPorts, err := testutil.AllocateMiniPorts(3)
|
||||
if err != nil {
|
||||
t.Fatalf("allocate ports: %v", err)
|
||||
}
|
||||
var nodes [3]*masterNode
|
||||
var peerParts []string
|
||||
for i, hp := range httpPorts {
|
||||
dataDir := filepath.Join(baseDir, fmt.Sprintf("m%d", i))
|
||||
os.MkdirAll(dataDir, 0o755)
|
||||
nodes[i] = &masterNode{
|
||||
port: hp,
|
||||
grpcPort: hp + testutil.GrpcPortOffset,
|
||||
dataDir: dataDir,
|
||||
logFile: filepath.Join(logsDir, fmt.Sprintf("master%d.log", i)),
|
||||
}
|
||||
peerParts = append(peerParts, fmt.Sprintf("127.0.0.1:%d", hp))
|
||||
}
|
||||
|
||||
mc := &MasterCluster{
|
||||
t: t,
|
||||
weedBinary: weedBinary,
|
||||
baseDir: baseDir,
|
||||
logsDir: logsDir,
|
||||
keepLogs: keepLogs,
|
||||
nodes: nodes,
|
||||
peersStr: strings.Join(peerParts, ","),
|
||||
raftHashicorp: raftHashicorp,
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
mc.StopAll()
|
||||
})
|
||||
return mc
|
||||
}
|
||||
|
||||
// SetNodePeers narrows the peer list node i starts with, mirroring a
|
||||
// StatefulSet whose replica count changed under a running master.
|
||||
func (mc *MasterCluster) SetNodePeers(i int, peers string) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.nodes[i].peersStr = peers
|
||||
}
|
||||
|
||||
// SetRaftBootstrap makes node i start with -raftBootstrap.
|
||||
func (mc *MasterCluster) SetRaftBootstrap(i int) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.nodes[i].raftBootstrap = true
|
||||
}
|
||||
|
||||
// StartNode starts the master process at the given index (0–2).
|
||||
func (mc *MasterCluster) StartNode(i int) {
|
||||
mc.t.Helper()
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
n := mc.nodes[i]
|
||||
if n.cmd != nil && !n.stopped {
|
||||
return // already running
|
||||
}
|
||||
|
||||
logFile, err := os.OpenFile(n.logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
mc.t.Fatalf("create log for node %d: %v", i, err)
|
||||
}
|
||||
|
||||
peersStr := n.peersStr
|
||||
if peersStr == "" {
|
||||
peersStr = mc.peersStr
|
||||
}
|
||||
args := []string{
|
||||
"master",
|
||||
"-ip=127.0.0.1",
|
||||
"-port=" + strconv.Itoa(n.port),
|
||||
"-port.grpc=" + strconv.Itoa(n.grpcPort),
|
||||
"-mdir=" + n.dataDir,
|
||||
"-peers=" + peersStr,
|
||||
"-electionTimeout=3s",
|
||||
"-volumeSizeLimitMB=32",
|
||||
"-defaultReplication=000",
|
||||
}
|
||||
if mc.raftHashicorp {
|
||||
args = append(args, "-raftHashicorp")
|
||||
}
|
||||
if n.raftBootstrap {
|
||||
args = append(args, "-raftBootstrap")
|
||||
}
|
||||
|
||||
n.cmd = exec.Command(mc.weedBinary, args...)
|
||||
n.cmd.Dir = mc.baseDir
|
||||
n.cmd.Stdout = logFile
|
||||
n.cmd.Stderr = logFile
|
||||
n.stopped = false
|
||||
if err := n.cmd.Start(); err != nil {
|
||||
mc.t.Fatalf("start node %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// StopNode gracefully stops the master at the given index.
|
||||
func (mc *MasterCluster) StopNode(i int) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
mc.stopNodeLocked(i)
|
||||
}
|
||||
|
||||
func (mc *MasterCluster) stopNodeLocked(i int) {
|
||||
n := mc.nodes[i]
|
||||
if n.cmd == nil || n.stopped {
|
||||
return
|
||||
}
|
||||
n.stopped = true
|
||||
_ = n.cmd.Process.Signal(os.Interrupt)
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- n.cmd.Wait() }()
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
_ = n.cmd.Process.Kill()
|
||||
<-done
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
|
||||
// StopAll stops all running master nodes.
|
||||
func (mc *MasterCluster) StopAll() {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
for i := range 3 {
|
||||
mc.stopNodeLocked(i)
|
||||
}
|
||||
if !mc.keepLogs && !mc.t.Failed() {
|
||||
os.RemoveAll(mc.baseDir)
|
||||
} else if mc.baseDir != "" {
|
||||
mc.t.Logf("multi-master logs kept at %s", mc.baseDir)
|
||||
}
|
||||
}
|
||||
|
||||
// NodeURL returns the HTTP URL for node i.
|
||||
func (mc *MasterCluster) NodeURL(i int) string {
|
||||
return fmt.Sprintf("http://127.0.0.1:%d", mc.nodes[i].port)
|
||||
}
|
||||
|
||||
// NodeAddress returns "127.0.0.1:port" for node i.
|
||||
func (mc *MasterCluster) NodeAddress(i int) string {
|
||||
return fmt.Sprintf("127.0.0.1:%d", mc.nodes[i].port)
|
||||
}
|
||||
|
||||
// NodeGRPCAddress returns "127.0.0.1:grpcPort" for node i.
|
||||
func (mc *MasterCluster) NodeGRPCAddress(i int) string {
|
||||
return fmt.Sprintf("127.0.0.1:%d", mc.nodes[i].grpcPort)
|
||||
}
|
||||
|
||||
// IsNodeRunning returns true if the node at index i has a live process.
|
||||
func (mc *MasterCluster) IsNodeRunning(i int) bool {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
n := mc.nodes[i]
|
||||
return n.cmd != nil && !n.stopped
|
||||
}
|
||||
|
||||
// GetClusterStatus fetches /cluster/status from node i.
|
||||
func (mc *MasterCluster) GetClusterStatus(i int) (*clusterStatus, error) {
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
resp, err := client.Get(mc.NodeURL(i) + "/cluster/status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var cs clusterStatus
|
||||
if err := json.Unmarshal(body, &cs); err != nil {
|
||||
return nil, fmt.Errorf("parse cluster/status: %w (body: %s)", err, string(body))
|
||||
}
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
// GetTopologyId fetches the TopologyId from /dir/status on node i.
|
||||
func (mc *MasterCluster) GetTopologyId(i int) (string, error) {
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
resp, err := client.Get(mc.NodeURL(i) + "/dir/status")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var raw map[string]any
|
||||
if err := json.Unmarshal(body, &raw); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if id, ok := raw["TopologyId"].(string); ok {
|
||||
return id, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// FindLeader returns the index of the leader node and its address.
|
||||
// Returns -1 if no leader is found.
|
||||
func (mc *MasterCluster) FindLeader() (int, string) {
|
||||
for i := range 3 {
|
||||
if !mc.IsNodeRunning(i) {
|
||||
continue
|
||||
}
|
||||
cs, err := mc.GetClusterStatus(i)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if cs.IsLeader {
|
||||
return i, mc.NodeAddress(i)
|
||||
}
|
||||
}
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
// WaitForLeader polls until a leader is elected or timeout.
|
||||
func (mc *MasterCluster) WaitForLeader(timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if idx, _ := mc.FindLeader(); idx >= 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
return fmt.Errorf("no leader elected within %v", timeout)
|
||||
}
|
||||
|
||||
// WaitForNoLeader waits until no running master claims leadership. goraft only
|
||||
// checks whether it still has a quorum on an election-timeout ticker, and needs
|
||||
// its peers to go quiet for a full timeout first, so a master that has lost its
|
||||
// quorum can keep claiming leadership for tens of seconds. It cannot commit
|
||||
// anything in that window.
|
||||
func (mc *MasterCluster) WaitForNoLeader(timeout time.Duration) error {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
idx, _ := mc.FindLeader()
|
||||
if idx < 0 {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
idx, addr := mc.FindLeader()
|
||||
return fmt.Errorf("master %d at %s still claims leadership after %v", idx, addr, timeout)
|
||||
}
|
||||
|
||||
// WaitForNewLeader waits for a leader that is different from the given address.
|
||||
func (mc *MasterCluster) WaitForNewLeader(oldLeaderAddr string, timeout time.Duration) (int, string, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
idx, addr := mc.FindLeader()
|
||||
if idx >= 0 && addr != oldLeaderAddr {
|
||||
return idx, addr, nil
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
return -1, "", fmt.Errorf("no new leader (different from %s) within %v", oldLeaderAddr, timeout)
|
||||
}
|
||||
|
||||
// WaitForTopologyId waits until the leader reports a non-empty TopologyId, and
|
||||
// returns it. It is only readable once the leader has applied the raft entry
|
||||
// carrying it, which lands after the election it won.
|
||||
func (mc *MasterCluster) WaitForTopologyId(timeout time.Duration) (string, error) {
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
if idx, _ := mc.FindLeader(); idx >= 0 {
|
||||
if id, err := mc.GetTopologyId(idx); err == nil && id != "" {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
return "", fmt.Errorf("TopologyId not available within %v", timeout)
|
||||
}
|
||||
|
||||
// WaitForNodeReady waits for node i to respond to HTTP.
|
||||
func (mc *MasterCluster) WaitForNodeReady(i int, timeout time.Duration) error {
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
deadline := time.Now().Add(timeout)
|
||||
for time.Now().Before(deadline) {
|
||||
resp, err := client.Get(mc.NodeURL(i) + "/cluster/status")
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
return fmt.Errorf("node %d not ready within %v", i, timeout)
|
||||
}
|
||||
|
||||
// LogContains reports whether node i's log holds the given text.
|
||||
func (mc *MasterCluster) LogContains(i int, text string) bool {
|
||||
b, err := os.ReadFile(mc.nodes[i].logFile)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(b), text)
|
||||
}
|
||||
|
||||
// topologyIdLine matches every line a master logs when it learns a TopologyId,
|
||||
// whichever raft implementation applied it.
|
||||
var topologyIdLine = regexp.MustCompile(`TopologyId[^:]*: ([0-9a-f-]{36})`)
|
||||
|
||||
// NodeTopologyIds returns the TopologyIds node i has logged. /dir/status is
|
||||
// proxied to the leader, so a master's own view of the cluster identity is only
|
||||
// visible in its log, and that is where a fork shows up.
|
||||
func (mc *MasterCluster) NodeTopologyIds(i int) []string {
|
||||
b, err := os.ReadFile(mc.nodes[i].logFile)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var ids []string
|
||||
for _, m := range topologyIdLine.FindAllStringSubmatch(string(b), -1) {
|
||||
ids = append(ids, m[1])
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// WaitForNodeTopologyIds waits until every master has logged a TopologyId and
|
||||
// returns what each one saw.
|
||||
func (mc *MasterCluster) WaitForNodeTopologyIds(timeout time.Duration) ([3][]string, error) {
|
||||
var ids [3][]string
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
missing := -1
|
||||
for i := range 3 {
|
||||
ids[i] = mc.NodeTopologyIds(i)
|
||||
if len(ids[i]) == 0 {
|
||||
missing = i
|
||||
}
|
||||
}
|
||||
if missing < 0 {
|
||||
return ids, nil
|
||||
}
|
||||
if !time.Now().Before(deadline) {
|
||||
return ids, fmt.Errorf("master %d logged no TopologyId within %v", missing, timeout)
|
||||
}
|
||||
time.Sleep(waitTick)
|
||||
}
|
||||
}
|
||||
|
||||
// DumpLogs prints the tail of all master logs.
|
||||
func (mc *MasterCluster) DumpLogs() {
|
||||
for i := range 3 {
|
||||
mc.t.Logf("=== master%d log tail ===\n%s", i, mc.tailLog(i))
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *MasterCluster) tailLog(i int) string {
|
||||
f, err := os.Open(mc.nodes[i].logFile)
|
||||
if err != nil {
|
||||
return "(no log)"
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
lines := make([]string, 0, 50)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
if len(lines) > 50 {
|
||||
lines = lines[1:]
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func findOrBuildWeedBinary() (string, error) {
|
||||
if fromEnv := os.Getenv("WEED_BINARY"); fromEnv != "" {
|
||||
if isExecutableFile(fromEnv) {
|
||||
return fromEnv, nil
|
||||
}
|
||||
return "", fmt.Errorf("WEED_BINARY not executable: %s", fromEnv)
|
||||
}
|
||||
|
||||
repoRoot := ""
|
||||
if _, file, _, ok := runtime.Caller(0); ok {
|
||||
repoRoot = filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
|
||||
}
|
||||
if repoRoot == "" {
|
||||
return "", fmt.Errorf("unable to detect repository root")
|
||||
}
|
||||
|
||||
// Check if already built
|
||||
binDir := filepath.Join(os.TempDir(), "seaweedfs_multi_master_it_bin")
|
||||
os.MkdirAll(binDir, 0o755)
|
||||
binPath := filepath.Join(binDir, "weed")
|
||||
if isExecutableFile(binPath) {
|
||||
return binPath, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", binPath, ".")
|
||||
cmd.Dir = filepath.Join(repoRoot, "weed")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("build weed binary: %w\n%s", err, out.String())
|
||||
}
|
||||
return binPath, nil
|
||||
}
|
||||
|
||||
func isExecutableFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || info.IsDir() {
|
||||
return false
|
||||
}
|
||||
return info.Mode().Perm()&0o111 != 0
|
||||
}
|
||||
Reference in New Issue
Block a user