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.
230 lines
5.2 KiB
230 lines
5.2 KiB
package testutil
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// TestServer represents a running weed mini server for testing
|
|
type TestServer struct {
|
|
cmd *exec.Cmd
|
|
config ServerConfig
|
|
done chan error
|
|
}
|
|
|
|
// ServerConfig holds configuration for starting a weed mini server
|
|
type ServerConfig struct {
|
|
DataDir string
|
|
S3Port int
|
|
S3Config string
|
|
AccessKey string
|
|
SecretKey string
|
|
LogFile string
|
|
PIDFile string
|
|
StartupWait time.Duration
|
|
WeedBinary string
|
|
}
|
|
|
|
// DefaultServerConfig creates a default server configuration
|
|
func DefaultServerConfig(dataDir *string) ServerConfig {
|
|
if dataDir == nil {
|
|
dataDir = &[]string{filepath.Join(os.TempDir(), "weed-test-data")}[0]
|
|
}
|
|
return ServerConfig{
|
|
DataDir: *dataDir,
|
|
S3Port: 8333,
|
|
AccessKey: "test",
|
|
SecretKey: "test",
|
|
LogFile: "weed-test.log",
|
|
PIDFile: "weed-test.pid",
|
|
StartupWait: 30 * time.Second,
|
|
}
|
|
}
|
|
|
|
// StartServer starts a weed mini server with the given configuration
|
|
func StartServer(config ServerConfig) (*TestServer, error) {
|
|
// Find weed binary
|
|
weedBinary, err := findWeedBinary()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find weed binary: %w", err)
|
|
}
|
|
config.WeedBinary = weedBinary
|
|
|
|
// Create data directory
|
|
os.MkdirAll(config.DataDir, 0755)
|
|
|
|
// Build command arguments
|
|
args := []string{
|
|
"server",
|
|
"-debug",
|
|
"-s3",
|
|
fmt.Sprintf("-s3.port=%d", config.S3Port),
|
|
"-s3.allowDeleteBucketNotEmpty=true",
|
|
"-filer",
|
|
"-filer.maxMB=64",
|
|
"-master.volumeSizeLimitMB=50",
|
|
"-master.peers=none",
|
|
"-volume.max=100",
|
|
fmt.Sprintf("-dir=%s", config.DataDir),
|
|
"-volume.preStopSeconds=1",
|
|
}
|
|
|
|
// choose free ports for master, volume, filer and metrics to avoid collisions
|
|
pick := func() int {
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
p := l.Addr().(*net.TCPAddr).Port
|
|
l.Close()
|
|
return p
|
|
}
|
|
|
|
masterPort := pick()
|
|
volumePort := pick()
|
|
filerPort := pick()
|
|
metricsPort := pick()
|
|
|
|
if masterPort != 0 {
|
|
args = append(args, fmt.Sprintf("-master.port=%d", masterPort))
|
|
}
|
|
if volumePort != 0 {
|
|
args = append(args, fmt.Sprintf("-volume.port=%d", volumePort))
|
|
}
|
|
if filerPort != 0 {
|
|
args = append(args, fmt.Sprintf("-filer.port=%d", filerPort))
|
|
}
|
|
if metricsPort != 0 {
|
|
args = append(args, fmt.Sprintf("-metricsPort=%d", metricsPort))
|
|
}
|
|
|
|
if config.S3Config != "" {
|
|
args = append(args, fmt.Sprintf("-s3.config=%s", config.S3Config))
|
|
}
|
|
|
|
// Start server process
|
|
cmd := exec.Command(weedBinary, args...)
|
|
logFile, err := os.Create(config.LogFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create log file: %w", err)
|
|
}
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
logFile.Close()
|
|
return nil, fmt.Errorf("failed to start server: %w", err)
|
|
}
|
|
|
|
// Save PID
|
|
os.WriteFile(config.PIDFile, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644)
|
|
|
|
server := &TestServer{
|
|
cmd: cmd,
|
|
config: config,
|
|
done: make(chan error, 1),
|
|
}
|
|
|
|
// Wait for server to be ready
|
|
if err := server.WaitForReady(); err != nil {
|
|
server.Stop()
|
|
return nil, fmt.Errorf("server failed to start: %w", err)
|
|
}
|
|
|
|
return server, nil
|
|
}
|
|
|
|
// WaitForReady polls the server health endpoint until it responds
|
|
func (s *TestServer) WaitForReady() error {
|
|
deadline := time.Now().Add(s.config.StartupWait)
|
|
for {
|
|
if time.Now().After(deadline) {
|
|
return fmt.Errorf("server startup timeout after %v", s.config.StartupWait)
|
|
}
|
|
|
|
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", s.config.S3Port))
|
|
if err == nil {
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Stop stops the server and cleans up resources
|
|
func (s *TestServer) Stop() error {
|
|
if s.cmd.Process == nil {
|
|
return nil
|
|
}
|
|
|
|
// Try graceful shutdown
|
|
s.cmd.Process.Signal(os.Interrupt)
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- s.cmd.Wait()
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
// Force kill if graceful shutdown takes too long
|
|
s.cmd.Process.Kill()
|
|
<-done
|
|
case <-done:
|
|
}
|
|
|
|
// Clean up files
|
|
os.Remove(s.config.PIDFile)
|
|
os.RemoveAll(s.config.DataDir)
|
|
|
|
return nil
|
|
}
|
|
|
|
// findWeedBinary searches for the weed binary in common locations
|
|
func findWeedBinary() (string, error) {
|
|
// Try various locations for weed binary
|
|
paths := []string{
|
|
"../../../weed",
|
|
"../../../../weed",
|
|
"../weed",
|
|
"./weed",
|
|
"/usr/local/bin/weed",
|
|
"/usr/bin/weed",
|
|
}
|
|
|
|
// Add GOPATH/bin/weed
|
|
if gopath := os.Getenv("GOPATH"); gopath != "" {
|
|
paths = append(paths, filepath.Join(gopath, "bin", "weed"))
|
|
}
|
|
|
|
// Add GOROOT/bin/weed
|
|
if goroot := os.Getenv("GOROOT"); goroot != "" {
|
|
paths = append(paths, filepath.Join(goroot, "bin", "weed"))
|
|
}
|
|
|
|
// Add ~/go/bin/weed (common default GOPATH)
|
|
if home := os.Getenv("HOME"); home != "" {
|
|
paths = append(paths, filepath.Join(home, "go", "bin", "weed"))
|
|
}
|
|
|
|
for _, p := range paths {
|
|
if info, err := os.Stat(p); err == nil {
|
|
// Check if it's a regular file and executable
|
|
if info.Mode().IsRegular() && (info.Mode().Perm()&0111) != 0 {
|
|
return p, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try PATH
|
|
if path, err := exec.LookPath("weed"); err == nil {
|
|
return path, nil
|
|
}
|
|
|
|
return "", fmt.Errorf("weed binary not found in PATH or common locations")
|
|
}
|