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.
		
		
		
		
		
			
		
			
				
					
					
						
							384 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							384 lines
						
					
					
						
							10 KiB
						
					
					
				
								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)
							 | 
						|
								}
							 |