You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

273 lines
7.6 KiB

package shell
import (
"bytes"
"testing"
"github.com/seaweedfs/seaweedfs/weed/storage/needle_map"
"github.com/seaweedfs/seaweedfs/weed/storage/types"
)
func TestBuildUnionFromMultipleIndexDatabases(t *testing.T) {
// Test that we can correctly identify missing entries between replicas
// Create mock index databases representing different replicas
replicaA := needle_map.NewMemDb()
replicaB := needle_map.NewMemDb()
replicaC := needle_map.NewMemDb()
defer replicaA.Close()
defer replicaB.Close()
defer replicaC.Close()
// Replica A has entries 1, 2, 3, 4, 5
replicaA.Set(types.NeedleId(1), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(2), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(3), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(4), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(5), types.Offset{}, types.Size(100))
// Replica B has entries 1, 2, 3, 6, 7 (missing 4, 5 from A, has unique 6, 7)
replicaB.Set(types.NeedleId(1), types.Offset{}, types.Size(100))
replicaB.Set(types.NeedleId(2), types.Offset{}, types.Size(100))
replicaB.Set(types.NeedleId(3), types.Offset{}, types.Size(100))
replicaB.Set(types.NeedleId(6), types.Offset{}, types.Size(100))
replicaB.Set(types.NeedleId(7), types.Offset{}, types.Size(100))
// Replica C has entries 1, 2, 8 (minimal overlap, has unique 8)
replicaC.Set(types.NeedleId(1), types.Offset{}, types.Size(100))
replicaC.Set(types.NeedleId(2), types.Offset{}, types.Size(100))
replicaC.Set(types.NeedleId(8), types.Offset{}, types.Size(100))
// Test: Find entries in B that are missing from A
var missingFromA []types.NeedleId
replicaB.AscendingVisit(func(nv needle_map.NeedleValue) error {
if _, found := replicaA.Get(nv.Key); !found {
missingFromA = append(missingFromA, nv.Key)
}
return nil
})
if len(missingFromA) != 2 {
t.Errorf("Expected 2 entries missing from A (6, 7), got %d: %v", len(missingFromA), missingFromA)
}
// Test: Find entries in C that are missing from A
var missingFromAinC []types.NeedleId
replicaC.AscendingVisit(func(nv needle_map.NeedleValue) error {
if _, found := replicaA.Get(nv.Key); !found {
missingFromAinC = append(missingFromAinC, nv.Key)
}
return nil
})
if len(missingFromAinC) != 1 {
t.Errorf("Expected 1 entry missing from A in C (8), got %d: %v", len(missingFromAinC), missingFromAinC)
}
// Simulate building union: add missing entries to A
for _, id := range missingFromA {
replicaA.Set(id, types.Offset{}, types.Size(100))
}
for _, id := range missingFromAinC {
replicaA.Set(id, types.Offset{}, types.Size(100))
}
// Verify A now has all 8 unique entries
count := 0
replicaA.AscendingVisit(func(nv needle_map.NeedleValue) error {
count++
return nil
})
if count != 8 {
t.Errorf("Expected union to have 8 entries, got %d", count)
}
}
func TestFindLargestReplica(t *testing.T) {
// Test that we correctly identify the replica with the most entries
type replicaInfo struct {
url string
fileCount uint64
}
testCases := []struct {
name string
replicas []replicaInfo
expected string
}{
{
name: "single replica",
replicas: []replicaInfo{
{"server1:8080", 100},
},
expected: "server1:8080",
},
{
name: "first is largest",
replicas: []replicaInfo{
{"server1:8080", 100},
{"server2:8080", 50},
{"server3:8080", 75},
},
expected: "server1:8080",
},
{
name: "last is largest",
replicas: []replicaInfo{
{"server1:8080", 50},
{"server2:8080", 75},
{"server3:8080", 100},
},
expected: "server3:8080",
},
{
name: "middle is largest",
replicas: []replicaInfo{
{"server1:8080", 50},
{"server2:8080", 100},
{"server3:8080", 75},
},
expected: "server2:8080",
},
{
name: "all equal - pick first",
replicas: []replicaInfo{
{"server1:8080", 100},
{"server2:8080", 100},
{"server3:8080", 100},
},
expected: "server1:8080",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Find the largest
bestIdx := 0
var bestCount uint64 = 0
for i, r := range tc.replicas {
if i == 0 || r.fileCount > bestCount {
bestIdx = i
bestCount = r.fileCount
}
}
if tc.replicas[bestIdx].url != tc.expected {
t.Errorf("Expected %s, got %s", tc.expected, tc.replicas[bestIdx].url)
}
})
}
}
func TestDeletedEntriesAreSkipped(t *testing.T) {
// Test that deleted entries are not copied during sync
replicaA := needle_map.NewMemDb()
replicaB := needle_map.NewMemDb()
defer replicaA.Close()
defer replicaB.Close()
// Replica A has entries 1, 2, 3 (all valid)
replicaA.Set(types.NeedleId(1), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(2), types.Offset{}, types.Size(100))
replicaA.Set(types.NeedleId(3), types.Offset{}, types.Size(100))
// Replica B has entry 4 valid, entry 5 deleted
replicaB.Set(types.NeedleId(4), types.Offset{}, types.Size(100))
replicaB.Set(types.NeedleId(5), types.Offset{}, types.Size(-1)) // Deleted (negative size)
// Find non-deleted entries in B missing from A
var missingFromA []types.NeedleId
replicaB.AscendingVisit(func(nv needle_map.NeedleValue) error {
if nv.Size.IsDeleted() {
return nil // Skip deleted
}
if _, found := replicaA.Get(nv.Key); !found {
missingFromA = append(missingFromA, nv.Key)
}
return nil
})
if len(missingFromA) != 1 {
t.Errorf("Expected 1 non-deleted entry missing (4), got %d: %v", len(missingFromA), missingFromA)
}
if len(missingFromA) > 0 && missingFromA[0] != types.NeedleId(4) {
t.Errorf("Expected missing entry to be 4, got %d", missingFromA[0])
}
}
func TestReplicaUnionBuilder_EmptyLocations(t *testing.T) {
// Test handling of empty locations slice
builder := &replicaUnionBuilder{
writer: &bytes.Buffer{},
vid: 1,
}
_, count, err := builder.buildUnionReplica(nil, "")
if err == nil {
t.Error("Expected error for empty locations")
}
if count != 0 {
t.Errorf("Expected 0 synced, got %d", count)
}
}
func TestAvoidDuplicateCopies(t *testing.T) {
// Test that when building union, we don't copy the same entry multiple times
// by updating the best replica's in-memory index after each copy
bestDB := needle_map.NewMemDb()
defer bestDB.Close()
// Best replica has entries 1, 2
bestDB.Set(types.NeedleId(1), types.Offset{}, types.Size(100))
bestDB.Set(types.NeedleId(2), types.Offset{}, types.Size(100))
// Simulate two other replicas both having entry 3
otherReplicas := [][]types.NeedleId{
{3, 4}, // Replica B has 3, 4
{3, 5}, // Replica C has 3, 5
}
copiedEntries := make(map[types.NeedleId]int) // Track how many times each entry is "copied"
for _, otherEntries := range otherReplicas {
for _, id := range otherEntries {
if _, found := bestDB.Get(id); !found {
// Would copy this entry
copiedEntries[id]++
// Add to bestDB to prevent duplicate copies
bestDB.Set(id, types.Offset{}, types.Size(100))
}
}
}
// Entry 3 should only be copied once (from first replica that has it)
if copiedEntries[types.NeedleId(3)] != 1 {
t.Errorf("Entry 3 should be copied exactly once, got %d", copiedEntries[types.NeedleId(3)])
}
// Entry 4 should be copied once
if copiedEntries[types.NeedleId(4)] != 1 {
t.Errorf("Entry 4 should be copied exactly once, got %d", copiedEntries[types.NeedleId(4)])
}
// Entry 5 should be copied once
if copiedEntries[types.NeedleId(5)] != 1 {
t.Errorf("Entry 5 should be copied exactly once, got %d", copiedEntries[types.NeedleId(5)])
}
// Best should now have 5 entries total
count := 0
bestDB.AscendingVisit(func(nv needle_map.NeedleValue) error {
count++
return nil
})
if count != 5 {
t.Errorf("Expected 5 entries in union, got %d", count)
}
}