| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- package fuse_test
- import (
- "fmt"
- "io/fs"
- "os"
- "os/exec"
- "path/filepath"
- "syscall"
- "testing"
- "time"
- "github.com/stretchr/testify/require"
- )
- // FuseTestFramework provides utilities for FUSE integration testing
- type FuseTestFramework struct {
- t *testing.T
- tempDir string
- mountPoint string
- dataDir string
- masterProcess *os.Process
- volumeProcess *os.Process
- filerProcess *os.Process
- mountProcess *os.Process
- masterAddr string
- volumeAddr string
- filerAddr string
- weedBinary string
- isSetup bool
- }
- // TestConfig holds configuration for FUSE tests
- type TestConfig struct {
- Collection string
- Replication string
- ChunkSizeMB int
- CacheSizeMB int
- NumVolumes int
- EnableDebug bool
- MountOptions []string
- SkipCleanup bool // for debugging failed tests
- }
- // DefaultTestConfig returns a default configuration for FUSE tests
- func DefaultTestConfig() *TestConfig {
- return &TestConfig{
- Collection: "",
- Replication: "000",
- ChunkSizeMB: 4,
- CacheSizeMB: 100,
- NumVolumes: 3,
- EnableDebug: false,
- MountOptions: []string{},
- SkipCleanup: false,
- }
- }
- // NewFuseTestFramework creates a new FUSE testing framework
- func NewFuseTestFramework(t *testing.T, config *TestConfig) *FuseTestFramework {
- if config == nil {
- config = DefaultTestConfig()
- }
- tempDir, err := os.MkdirTemp("", "seaweedfs_fuse_test_")
- require.NoError(t, err)
- return &FuseTestFramework{
- t: t,
- tempDir: tempDir,
- mountPoint: filepath.Join(tempDir, "mount"),
- dataDir: filepath.Join(tempDir, "data"),
- masterAddr: "127.0.0.1:19333",
- volumeAddr: "127.0.0.1:18080",
- filerAddr: "127.0.0.1:18888",
- weedBinary: findWeedBinary(),
- isSetup: false,
- }
- }
- // Setup starts SeaweedFS cluster and mounts FUSE filesystem
- func (f *FuseTestFramework) Setup(config *TestConfig) error {
- if f.isSetup {
- return fmt.Errorf("framework already setup")
- }
- // Create directories
- dirs := []string{f.mountPoint, f.dataDir}
- for _, dir := range dirs {
- if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("failed to create directory %s: %v", dir, err)
- }
- }
- // Start master
- if err := f.startMaster(config); err != nil {
- return fmt.Errorf("failed to start master: %v", err)
- }
- // Wait for master to be ready
- if err := f.waitForService(f.masterAddr, 30*time.Second); err != nil {
- return fmt.Errorf("master not ready: %v", err)
- }
- // Start volume servers
- if err := f.startVolumeServers(config); err != nil {
- return fmt.Errorf("failed to start volume servers: %v", err)
- }
- // Wait for volume server to be ready
- if err := f.waitForService(f.volumeAddr, 30*time.Second); err != nil {
- return fmt.Errorf("volume server not ready: %v", err)
- }
- // Start filer
- if err := f.startFiler(config); err != nil {
- return fmt.Errorf("failed to start filer: %v", err)
- }
- // Wait for filer to be ready
- if err := f.waitForService(f.filerAddr, 30*time.Second); err != nil {
- return fmt.Errorf("filer not ready: %v", err)
- }
- // Mount FUSE filesystem
- if err := f.mountFuse(config); err != nil {
- return fmt.Errorf("failed to mount FUSE: %v", err)
- }
- // Wait for mount to be ready
- if err := f.waitForMount(30 * time.Second); err != nil {
- return fmt.Errorf("FUSE mount not ready: %v", err)
- }
- f.isSetup = true
- return nil
- }
- // Cleanup stops all processes and removes temporary files
- func (f *FuseTestFramework) Cleanup() {
- if f.mountProcess != nil {
- f.unmountFuse()
- }
- // Stop processes in reverse order
- processes := []*os.Process{f.mountProcess, f.filerProcess, f.volumeProcess, f.masterProcess}
- for _, proc := range processes {
- if proc != nil {
- proc.Signal(syscall.SIGTERM)
- proc.Wait()
- }
- }
- // Remove temp directory
- if !DefaultTestConfig().SkipCleanup {
- os.RemoveAll(f.tempDir)
- }
- }
- // GetMountPoint returns the FUSE mount point path
- func (f *FuseTestFramework) GetMountPoint() string {
- return f.mountPoint
- }
- // GetFilerAddr returns the filer address
- func (f *FuseTestFramework) GetFilerAddr() string {
- return f.filerAddr
- }
- // startMaster starts the SeaweedFS master server
- func (f *FuseTestFramework) startMaster(config *TestConfig) error {
- args := []string{
- "master",
- "-ip=127.0.0.1",
- "-port=19333",
- "-mdir=" + filepath.Join(f.dataDir, "master"),
- "-raftBootstrap",
- }
- if config.EnableDebug {
- args = append(args, "-v=4")
- }
- cmd := exec.Command(f.weedBinary, args...)
- cmd.Dir = f.tempDir
- if err := cmd.Start(); err != nil {
- return err
- }
- f.masterProcess = cmd.Process
- return nil
- }
- // startVolumeServers starts SeaweedFS volume servers
- func (f *FuseTestFramework) startVolumeServers(config *TestConfig) error {
- args := []string{
- "volume",
- "-mserver=" + f.masterAddr,
- "-ip=127.0.0.1",
- "-port=18080",
- "-dir=" + filepath.Join(f.dataDir, "volume"),
- fmt.Sprintf("-max=%d", config.NumVolumes),
- }
- if config.EnableDebug {
- args = append(args, "-v=4")
- }
- cmd := exec.Command(f.weedBinary, args...)
- cmd.Dir = f.tempDir
- if err := cmd.Start(); err != nil {
- return err
- }
- f.volumeProcess = cmd.Process
- return nil
- }
- // startFiler starts the SeaweedFS filer server
- func (f *FuseTestFramework) startFiler(config *TestConfig) error {
- args := []string{
- "filer",
- "-master=" + f.masterAddr,
- "-ip=127.0.0.1",
- "-port=18888",
- }
- if config.EnableDebug {
- args = append(args, "-v=4")
- }
- cmd := exec.Command(f.weedBinary, args...)
- cmd.Dir = f.tempDir
- if err := cmd.Start(); err != nil {
- return err
- }
- f.filerProcess = cmd.Process
- return nil
- }
- // mountFuse mounts the SeaweedFS FUSE filesystem
- func (f *FuseTestFramework) mountFuse(config *TestConfig) error {
- args := []string{
- "mount",
- "-filer=" + f.filerAddr,
- "-dir=" + f.mountPoint,
- "-filer.path=/",
- "-dirAutoCreate",
- }
- if config.Collection != "" {
- args = append(args, "-collection="+config.Collection)
- }
- if config.Replication != "" {
- args = append(args, "-replication="+config.Replication)
- }
- if config.ChunkSizeMB > 0 {
- args = append(args, fmt.Sprintf("-chunkSizeLimitMB=%d", config.ChunkSizeMB))
- }
- if config.CacheSizeMB > 0 {
- args = append(args, fmt.Sprintf("-cacheSizeMB=%d", config.CacheSizeMB))
- }
- if config.EnableDebug {
- args = append(args, "-v=4")
- }
- args = append(args, config.MountOptions...)
- cmd := exec.Command(f.weedBinary, args...)
- cmd.Dir = f.tempDir
- if err := cmd.Start(); err != nil {
- return err
- }
- f.mountProcess = cmd.Process
- return nil
- }
- // unmountFuse unmounts the FUSE filesystem
- func (f *FuseTestFramework) unmountFuse() error {
- if f.mountProcess != nil {
- f.mountProcess.Signal(syscall.SIGTERM)
- f.mountProcess.Wait()
- f.mountProcess = nil
- }
- // Also try system unmount as backup
- exec.Command("umount", f.mountPoint).Run()
- return nil
- }
- // waitForService waits for a service to be available
- func (f *FuseTestFramework) waitForService(addr string, timeout time.Duration) error {
- deadline := time.Now().Add(timeout)
- for time.Now().Before(deadline) {
- conn, err := net.DialTimeout("tcp", addr, 1*time.Second)
- if err == nil {
- conn.Close()
- return nil
- }
- time.Sleep(100 * time.Millisecond)
- }
- return fmt.Errorf("service at %s not ready within timeout", addr)
- }
- // waitForMount waits for the FUSE mount to be ready
- func (f *FuseTestFramework) waitForMount(timeout time.Duration) error {
- deadline := time.Now().Add(timeout)
- for time.Now().Before(deadline) {
- // Check if mount point is accessible
- if _, err := os.Stat(f.mountPoint); err == nil {
- // Try to list directory
- if _, err := os.ReadDir(f.mountPoint); err == nil {
- return nil
- }
- }
- time.Sleep(100 * time.Millisecond)
- }
- return fmt.Errorf("mount point not ready within timeout")
- }
- // findWeedBinary locates the weed binary
- func findWeedBinary() string {
- // Try different possible locations
- candidates := []string{
- "./weed",
- "../weed",
- "../../weed",
- "weed", // in PATH
- }
- for _, candidate := range candidates {
- if _, err := exec.LookPath(candidate); err == nil {
- return candidate
- }
- if _, err := os.Stat(candidate); err == nil {
- abs, _ := filepath.Abs(candidate)
- return abs
- }
- }
- // Default fallback
- return "weed"
- }
- // Helper functions for test assertions
- // AssertFileExists checks if a file exists in the mount point
- func (f *FuseTestFramework) AssertFileExists(relativePath string) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- _, err := os.Stat(fullPath)
- require.NoError(f.t, err, "file should exist: %s", relativePath)
- }
- // AssertFileNotExists checks if a file does not exist in the mount point
- func (f *FuseTestFramework) AssertFileNotExists(relativePath string) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- _, err := os.Stat(fullPath)
- require.True(f.t, os.IsNotExist(err), "file should not exist: %s", relativePath)
- }
- // AssertFileContent checks if a file has expected content
- func (f *FuseTestFramework) AssertFileContent(relativePath string, expectedContent []byte) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- actualContent, err := os.ReadFile(fullPath)
- require.NoError(f.t, err, "failed to read file: %s", relativePath)
- require.Equal(f.t, expectedContent, actualContent, "file content mismatch: %s", relativePath)
- }
- // AssertFileMode checks if a file has expected permissions
- func (f *FuseTestFramework) AssertFileMode(relativePath string, expectedMode fs.FileMode) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- info, err := os.Stat(fullPath)
- require.NoError(f.t, err, "failed to stat file: %s", relativePath)
- require.Equal(f.t, expectedMode, info.Mode(), "file mode mismatch: %s", relativePath)
- }
- // CreateTestFile creates a test file with specified content
- func (f *FuseTestFramework) CreateTestFile(relativePath string, content []byte) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- dir := filepath.Dir(fullPath)
- require.NoError(f.t, os.MkdirAll(dir, 0755), "failed to create directory: %s", dir)
- require.NoError(f.t, os.WriteFile(fullPath, content, 0644), "failed to create file: %s", relativePath)
- }
- // CreateTestDir creates a test directory
- func (f *FuseTestFramework) CreateTestDir(relativePath string) {
- fullPath := filepath.Join(f.mountPoint, relativePath)
- require.NoError(f.t, os.MkdirAll(fullPath, 0755), "failed to create directory: %s", relativePath)
- }
|