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.
386 lines
9.8 KiB
386 lines
9.8 KiB
//go:build foundationdb
|
|
// +build foundationdb
|
|
|
|
package foundationdb
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
func TestFoundationDBStore_Initialize(t *testing.T) {
|
|
// Test with default configuration
|
|
config := util.NewViper()
|
|
config.Set("foundationdb.cluster_file", getTestClusterFile())
|
|
config.Set("foundationdb.api_version", 630)
|
|
|
|
store := &FoundationDBStore{}
|
|
err := store.Initialize(config, "foundationdb.")
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
|
|
defer store.Shutdown()
|
|
|
|
if store.GetName() != "foundationdb" {
|
|
t.Errorf("Expected store name 'foundationdb', got '%s'", store.GetName())
|
|
}
|
|
|
|
if store.directoryPrefix != "seaweedfs" {
|
|
t.Errorf("Expected default directory prefix 'seaweedfs', got '%s'", store.directoryPrefix)
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_InitializeWithCustomConfig(t *testing.T) {
|
|
config := util.NewViper()
|
|
config.Set("foundationdb.cluster_file", getTestClusterFile())
|
|
config.Set("foundationdb.api_version", 630)
|
|
config.Set("foundationdb.timeout", "10s")
|
|
config.Set("foundationdb.max_retry_delay", "2s")
|
|
config.Set("foundationdb.directory_prefix", "custom_prefix")
|
|
|
|
store := &FoundationDBStore{}
|
|
err := store.Initialize(config, "foundationdb.")
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
|
|
defer store.Shutdown()
|
|
|
|
if store.directoryPrefix != "custom_prefix" {
|
|
t.Errorf("Expected custom directory prefix 'custom_prefix', got '%s'", store.directoryPrefix)
|
|
}
|
|
|
|
if store.timeout != 10*time.Second {
|
|
t.Errorf("Expected timeout 10s, got %v", store.timeout)
|
|
}
|
|
|
|
if store.maxRetryDelay != 2*time.Second {
|
|
t.Errorf("Expected max retry delay 2s, got %v", store.maxRetryDelay)
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_InitializeInvalidConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config map[string]interface{}
|
|
errorMsg string
|
|
}{
|
|
{
|
|
name: "invalid timeout",
|
|
config: map[string]interface{}{
|
|
"foundationdb.cluster_file": getTestClusterFile(),
|
|
"foundationdb.api_version": 720,
|
|
"foundationdb.timeout": "invalid",
|
|
"foundationdb.directory_prefix": "test",
|
|
},
|
|
errorMsg: "invalid timeout duration",
|
|
},
|
|
{
|
|
name: "invalid max_retry_delay",
|
|
config: map[string]interface{}{
|
|
"foundationdb.cluster_file": getTestClusterFile(),
|
|
"foundationdb.api_version": 720,
|
|
"foundationdb.timeout": "5s",
|
|
"foundationdb.max_retry_delay": "invalid",
|
|
"foundationdb.directory_prefix": "test",
|
|
},
|
|
errorMsg: "invalid max_retry_delay duration",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
config := util.NewViper()
|
|
for key, value := range tt.config {
|
|
config.Set(key, value)
|
|
}
|
|
|
|
store := &FoundationDBStore{}
|
|
err := store.Initialize(config, "foundationdb.")
|
|
if err == nil {
|
|
store.Shutdown()
|
|
t.Errorf("Expected initialization to fail, but it succeeded")
|
|
} else if !containsString(err.Error(), tt.errorMsg) {
|
|
t.Errorf("Expected error message to contain '%s', got '%s'", tt.errorMsg, err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_KeyGeneration(t *testing.T) {
|
|
store := &FoundationDBStore{}
|
|
err := store.initialize(getTestClusterFile(), 720)
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
defer store.Shutdown()
|
|
|
|
// Test key generation for different paths
|
|
testCases := []struct {
|
|
dirPath string
|
|
fileName string
|
|
desc string
|
|
}{
|
|
{"/", "file.txt", "root directory file"},
|
|
{"/dir", "file.txt", "subdirectory file"},
|
|
{"/deep/nested/dir", "file.txt", "deep nested file"},
|
|
{"/dir with spaces", "file with spaces.txt", "paths with spaces"},
|
|
{"/unicode/测试", "文件.txt", "unicode paths"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
key := store.genKey(tc.dirPath, tc.fileName)
|
|
if len(key) == 0 {
|
|
t.Error("Generated key should not be empty")
|
|
}
|
|
|
|
// Test that we can extract filename back
|
|
// Note: This tests internal consistency
|
|
if tc.fileName != "" {
|
|
extractedName := store.extractFileName(key)
|
|
if extractedName != tc.fileName {
|
|
t.Errorf("Expected extracted filename '%s', got '%s'", tc.fileName, extractedName)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_DirectoryKeyPrefix(t *testing.T) {
|
|
store := &FoundationDBStore{}
|
|
err := store.initialize(getTestClusterFile(), 720)
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
defer store.Shutdown()
|
|
|
|
testCases := []struct {
|
|
dirPath string
|
|
prefix string
|
|
desc string
|
|
}{
|
|
{"/", "", "root directory, no prefix"},
|
|
{"/dir", "", "subdirectory, no prefix"},
|
|
{"/dir", "test", "subdirectory with prefix"},
|
|
{"/deep/nested", "pre", "nested directory with prefix"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
key := store.genDirectoryKeyPrefix(tc.dirPath, tc.prefix)
|
|
if len(key) == 0 {
|
|
t.Error("Generated directory key prefix should not be empty")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_ErrorHandling(t *testing.T) {
|
|
store := &FoundationDBStore{}
|
|
err := store.initialize(getTestClusterFile(), 720)
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
defer store.Shutdown()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Test FindEntry with non-existent path
|
|
_, err = store.FindEntry(ctx, "/non/existent/file.txt")
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent file")
|
|
}
|
|
if err != filer_pb.ErrNotFound {
|
|
t.Errorf("Expected ErrNotFound, got %v", err)
|
|
}
|
|
|
|
// Test KvGet with non-existent key
|
|
_, err = store.KvGet(ctx, []byte("non_existent_key"))
|
|
if err == nil {
|
|
t.Error("Expected error for non-existent key")
|
|
}
|
|
if err != filer.ErrKvNotFound {
|
|
t.Errorf("Expected ErrKvNotFound, got %v", err)
|
|
}
|
|
|
|
// Test transaction state errors
|
|
err = store.CommitTransaction(ctx)
|
|
if err == nil {
|
|
t.Error("Expected error when committing without active transaction")
|
|
}
|
|
|
|
err = store.RollbackTransaction(ctx)
|
|
if err == nil {
|
|
t.Error("Expected error when rolling back without active transaction")
|
|
}
|
|
}
|
|
|
|
func TestFoundationDBStore_TransactionState(t *testing.T) {
|
|
store := &FoundationDBStore{}
|
|
err := store.initialize(getTestClusterFile(), 720)
|
|
if err != nil {
|
|
t.Skip("FoundationDB not available for testing, skipping")
|
|
}
|
|
defer store.Shutdown()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Test double transaction begin
|
|
txCtx, err := store.BeginTransaction(ctx)
|
|
if err != nil {
|
|
t.Fatalf("BeginTransaction failed: %v", err)
|
|
}
|
|
|
|
// Try to begin another transaction
|
|
_, err = store.BeginTransaction(ctx)
|
|
if err == nil {
|
|
t.Error("Expected error when beginning transaction while one is active")
|
|
}
|
|
|
|
// Commit the transaction
|
|
err = store.CommitTransaction(txCtx)
|
|
if err != nil {
|
|
t.Fatalf("CommitTransaction failed: %v", err)
|
|
}
|
|
|
|
// Now should be able to begin a new transaction
|
|
txCtx2, err := store.BeginTransaction(ctx)
|
|
if err != nil {
|
|
t.Fatalf("BeginTransaction after commit failed: %v", err)
|
|
}
|
|
|
|
// Rollback this time
|
|
err = store.RollbackTransaction(txCtx2)
|
|
if err != nil {
|
|
t.Fatalf("RollbackTransaction failed: %v", err)
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkFoundationDBStore_InsertEntry(b *testing.B) {
|
|
store := createBenchmarkStore(b)
|
|
defer store.Shutdown()
|
|
|
|
ctx := context.Background()
|
|
entry := &filer.Entry{
|
|
FullPath: "/benchmark/file.txt",
|
|
Attr: filer.Attr{
|
|
Mode: 0644,
|
|
Uid: 1000,
|
|
Gid: 1000,
|
|
Mtime: time.Now(),
|
|
},
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
entry.FullPath = util.NewFullPath("/benchmark", util.Uint64toHex(uint64(i))+".txt")
|
|
err := store.InsertEntry(ctx, entry)
|
|
if err != nil {
|
|
b.Fatalf("InsertEntry failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkFoundationDBStore_FindEntry(b *testing.B) {
|
|
store := createBenchmarkStore(b)
|
|
defer store.Shutdown()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Pre-populate with test entries
|
|
numEntries := 1000
|
|
for i := 0; i < numEntries; i++ {
|
|
entry := &filer.Entry{
|
|
FullPath: util.NewFullPath("/benchmark", util.Uint64toHex(uint64(i))+".txt"),
|
|
Attr: filer.Attr{
|
|
Mode: 0644,
|
|
Uid: 1000,
|
|
Gid: 1000,
|
|
Mtime: time.Now(),
|
|
},
|
|
}
|
|
err := store.InsertEntry(ctx, entry)
|
|
if err != nil {
|
|
b.Fatalf("Pre-population InsertEntry failed: %v", err)
|
|
}
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
path := util.NewFullPath("/benchmark", util.Uint64toHex(uint64(i%numEntries))+".txt")
|
|
_, err := store.FindEntry(ctx, path)
|
|
if err != nil {
|
|
b.Fatalf("FindEntry failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkFoundationDBStore_KvOperations(b *testing.B) {
|
|
store := createBenchmarkStore(b)
|
|
defer store.Shutdown()
|
|
|
|
ctx := context.Background()
|
|
key := []byte("benchmark_key")
|
|
value := []byte("benchmark_value")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
// Put
|
|
err := store.KvPut(ctx, key, value)
|
|
if err != nil {
|
|
b.Fatalf("KvPut failed: %v", err)
|
|
}
|
|
|
|
// Get
|
|
_, err = store.KvGet(ctx, key)
|
|
if err != nil {
|
|
b.Fatalf("KvGet failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
func getTestClusterFile() string {
|
|
clusterFile := os.Getenv("FDB_CLUSTER_FILE")
|
|
if clusterFile == "" {
|
|
clusterFile = "/var/fdb/config/fdb.cluster"
|
|
}
|
|
return clusterFile
|
|
}
|
|
|
|
func createBenchmarkStore(b *testing.B) *FoundationDBStore {
|
|
clusterFile := getTestClusterFile()
|
|
if _, err := os.Stat(clusterFile); os.IsNotExist(err) {
|
|
b.Skip("FoundationDB cluster file not found, skipping benchmark")
|
|
}
|
|
|
|
store := &FoundationDBStore{}
|
|
err := store.initialize(clusterFile, 720)
|
|
if err != nil {
|
|
b.Skipf("Failed to initialize FoundationDB store: %v", err)
|
|
}
|
|
|
|
return store
|
|
}
|
|
|
|
func containsString(s, substr string) bool {
|
|
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
|
|
(len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
|
|
func() bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}())))
|
|
}
|