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
209 lines
6.0 KiB
Go
209 lines
6.0 KiB
Go
package lock_manager
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestHashRing_BasicOperations(t *testing.T) {
|
|
hr := NewHashRing(50)
|
|
|
|
// Empty ring
|
|
p, b := hr.GetPrimaryAndBackup("key1")
|
|
assert.Equal(t, pb.ServerAddress(""), p)
|
|
assert.Equal(t, pb.ServerAddress(""), b)
|
|
|
|
// Single server — no backup
|
|
hr.AddServer("filer1:8888")
|
|
p, b = hr.GetPrimaryAndBackup("key1")
|
|
assert.Equal(t, pb.ServerAddress("filer1:8888"), p)
|
|
assert.Equal(t, pb.ServerAddress(""), b)
|
|
|
|
// Two servers — backup is the other server
|
|
hr.AddServer("filer2:8888")
|
|
p, b = hr.GetPrimaryAndBackup("key1")
|
|
assert.NotEqual(t, p, b)
|
|
assert.NotEmpty(t, b)
|
|
|
|
// Three servers
|
|
hr.AddServer("filer3:8888")
|
|
p, b = hr.GetPrimaryAndBackup("key1")
|
|
assert.NotEqual(t, p, b)
|
|
assert.NotEmpty(t, b)
|
|
|
|
// Remove server
|
|
hr.RemoveServer("filer2:8888")
|
|
assert.Equal(t, 2, hr.ServerCount())
|
|
}
|
|
|
|
func TestHashRing_DuplicateAddRemove(t *testing.T) {
|
|
hr := NewHashRing(50)
|
|
|
|
hr.AddServer("filer1:8888")
|
|
hr.AddServer("filer1:8888") // duplicate
|
|
assert.Equal(t, 1, hr.ServerCount())
|
|
|
|
hr.RemoveServer("filer1:8888")
|
|
assert.Equal(t, 0, hr.ServerCount())
|
|
|
|
hr.RemoveServer("filer1:8888") // remove non-existent
|
|
assert.Equal(t, 0, hr.ServerCount())
|
|
}
|
|
|
|
func TestHashRing_SetServers(t *testing.T) {
|
|
hr := NewHashRing(50)
|
|
|
|
hr.SetServers([]pb.ServerAddress{"a:1", "b:2", "c:3"})
|
|
assert.Equal(t, 3, hr.ServerCount())
|
|
|
|
servers := hr.GetServers()
|
|
assert.Equal(t, 3, len(servers))
|
|
|
|
// SetServers replaces
|
|
hr.SetServers([]pb.ServerAddress{"x:1", "y:2"})
|
|
assert.Equal(t, 2, hr.ServerCount())
|
|
}
|
|
|
|
func TestHashRing_ConsistencyOnRemoval(t *testing.T) {
|
|
// The key property of consistent hashing: when a server is removed,
|
|
// only keys that mapped to the removed server change.
|
|
hr := NewHashRing(50)
|
|
servers := []pb.ServerAddress{"filer1:8888", "filer2:8888", "filer3:8888"}
|
|
hr.SetServers(servers)
|
|
|
|
numKeys := 1000
|
|
// Record where each key maps before removal
|
|
before := make(map[string]pb.ServerAddress, numKeys)
|
|
for i := 0; i < numKeys; i++ {
|
|
key := fmt.Sprintf("lock-key-%d", i)
|
|
before[key] = hr.GetPrimary(key)
|
|
}
|
|
|
|
// Remove filer2
|
|
hr.RemoveServer("filer2:8888")
|
|
|
|
moved := 0
|
|
for i := 0; i < numKeys; i++ {
|
|
key := fmt.Sprintf("lock-key-%d", i)
|
|
after := hr.GetPrimary(key)
|
|
if before[key] != after {
|
|
// Only keys from filer2 should move
|
|
assert.Equal(t, pb.ServerAddress("filer2:8888"), before[key],
|
|
"key %s moved from %s to %s, but it wasn't on the removed server", key, before[key], after)
|
|
moved++
|
|
}
|
|
}
|
|
// Roughly 1/3 of keys should move (those that were on filer2)
|
|
t.Logf("Keys that moved: %d / %d", moved, numKeys)
|
|
assert.Greater(t, moved, 0, "some keys should have moved")
|
|
assert.Less(t, moved, numKeys, "not all keys should move")
|
|
}
|
|
|
|
func TestHashRing_BackupIsSuccessor(t *testing.T) {
|
|
// After removing primary, the backup should become the new primary
|
|
hr := NewHashRing(50)
|
|
servers := []pb.ServerAddress{"filer1:8888", "filer2:8888", "filer3:8888"}
|
|
hr.SetServers(servers)
|
|
|
|
// For each key, verify that removing the primary makes the backup the new primary
|
|
promoted := 0
|
|
total := 500
|
|
for i := 0; i < total; i++ {
|
|
key := fmt.Sprintf("test-lock-%d", i)
|
|
primary, backup := hr.GetPrimaryAndBackup(key)
|
|
assert.NotEqual(t, primary, backup)
|
|
|
|
// Temporarily remove primary
|
|
hr.RemoveServer(primary)
|
|
newPrimary := hr.GetPrimary(key)
|
|
if newPrimary == backup {
|
|
promoted++
|
|
}
|
|
// Restore
|
|
hr.AddServer(primary)
|
|
}
|
|
// The backup should become new primary for all keys
|
|
assert.Equal(t, total, promoted,
|
|
"backup should become new primary for all keys when primary is removed")
|
|
}
|
|
|
|
func TestHashRing_Distribution(t *testing.T) {
|
|
hr := NewHashRing(50)
|
|
servers := []pb.ServerAddress{"filer1:8888", "filer2:8888", "filer3:8888"}
|
|
hr.SetServers(servers)
|
|
|
|
counts := make(map[pb.ServerAddress]int)
|
|
numKeys := 3000
|
|
for i := 0; i < numKeys; i++ {
|
|
key := fmt.Sprintf("dist-key-%d", i)
|
|
p := hr.GetPrimary(key)
|
|
counts[p]++
|
|
}
|
|
|
|
expected := float64(numKeys) / float64(len(servers))
|
|
for server, count := range counts {
|
|
deviation := math.Abs(float64(count)-expected) / expected
|
|
t.Logf("Server %s: %d keys (%.1f%% deviation)", server, count, deviation*100)
|
|
// Allow up to 40% deviation with 50 vnodes and 3 servers
|
|
assert.Less(t, deviation, 0.40,
|
|
"server %s has too many or too few keys: %d (expected ~%d)", server, count, int(expected))
|
|
}
|
|
}
|
|
|
|
func TestHashRing_GetPrimary(t *testing.T) {
|
|
hr := NewHashRing(50)
|
|
|
|
// Empty ring
|
|
assert.Equal(t, pb.ServerAddress(""), hr.GetPrimary("key"))
|
|
|
|
hr.SetServers([]pb.ServerAddress{"a:1", "b:2"})
|
|
|
|
// Deterministic: same key always maps to same server
|
|
p1 := hr.GetPrimary("mykey")
|
|
p2 := hr.GetPrimary("mykey")
|
|
assert.Equal(t, p1, p2)
|
|
|
|
// GetPrimary matches the primary from GetPrimaryAndBackup
|
|
primary, _ := hr.GetPrimaryAndBackup("mykey")
|
|
assert.Equal(t, primary, hr.GetPrimary("mykey"))
|
|
}
|
|
|
|
// The ring must be identical on every node holding the same server set,
|
|
// regardless of the order servers were added or supplied. Build rings several
|
|
// ways and assert they agree on the primary for a wide range of keys.
|
|
func TestHashRing_OrderIndependent(t *testing.T) {
|
|
servers := []pb.ServerAddress{
|
|
"filer-a:8888", "filer-b:8888", "filer-c:8888", "filer-d:8888", "filer-e:8888",
|
|
}
|
|
|
|
bySet := NewHashRing(50)
|
|
bySet.SetServers(servers)
|
|
|
|
byReverse := NewHashRing(50)
|
|
rev := append([]pb.ServerAddress(nil), servers...)
|
|
for i, j := 0, len(rev)-1; i < j; i, j = i+1, j-1 {
|
|
rev[i], rev[j] = rev[j], rev[i]
|
|
}
|
|
byReverse.SetServers(rev)
|
|
|
|
byAdd := NewHashRing(50)
|
|
for _, s := range []pb.ServerAddress{"filer-c:8888", "filer-e:8888", "filer-a:8888", "filer-d:8888", "filer-b:8888"} {
|
|
byAdd.AddServer(s)
|
|
}
|
|
|
|
for i := 0; i < 5000; i++ {
|
|
key := fmt.Sprintf("s3.object.write:/buckets/b/obj-%d", i)
|
|
p := bySet.GetPrimary(key)
|
|
if got := byReverse.GetPrimary(key); got != p {
|
|
t.Fatalf("reverse-order ring disagrees on %q: %s vs %s", key, got, p)
|
|
}
|
|
if got := byAdd.GetPrimary(key); got != p {
|
|
t.Fatalf("add-order ring disagrees on %q: %s vs %s", key, got, p)
|
|
}
|
|
}
|
|
}
|