github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fstest/testserver/testserver.go (about)

     1  // Package testserver starts and stops test servers if required
     2  package testserver
     3  
     4  import (
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"net"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/rclone/rclone/fs"
    18  	"github.com/rclone/rclone/fs/fspath"
    19  )
    20  
    21  var (
    22  	once      sync.Once
    23  	configDir string // where the config is stored
    24  	// Note of running servers
    25  	runningMu   sync.Mutex
    26  	running     = map[string]int{}
    27  	errNotFound = errors.New("command not found")
    28  )
    29  
    30  // Assume we are run somewhere within the rclone root
    31  func findConfig() (string, error) {
    32  	dir := filepath.Join("fstest", "testserver", "init.d")
    33  	for i := 0; i < 5; i++ {
    34  		fi, err := os.Stat(dir)
    35  		if err == nil && fi.IsDir() {
    36  			return filepath.Abs(dir)
    37  		} else if !os.IsNotExist(err) {
    38  			return "", err
    39  		}
    40  		dir = filepath.Join("..", dir)
    41  	}
    42  	return "", errors.New("couldn't find testserver config files - run from within rclone source")
    43  }
    44  
    45  // run the command returning the output and an error
    46  func run(name, command string) (out []byte, err error) {
    47  	cmdPath := filepath.Join(configDir, name)
    48  	fi, err := os.Stat(cmdPath)
    49  	if err != nil || fi.IsDir() {
    50  		return nil, errNotFound
    51  	}
    52  	cmd := exec.Command(cmdPath, command)
    53  	out, err = cmd.CombinedOutput()
    54  	if err != nil {
    55  		err = fmt.Errorf("failed to run %s %s\n%s: %w", cmdPath, command, string(out), err)
    56  	}
    57  	return out, err
    58  }
    59  
    60  // Check to see if the server is running
    61  func isRunning(name string) bool {
    62  	_, err := run(name, "status")
    63  	return err == nil
    64  }
    65  
    66  // envKey returns the environment variable name to set name, key
    67  func envKey(name, key string) string {
    68  	return fmt.Sprintf("RCLONE_CONFIG_%s_%s", strings.ToUpper(name), strings.ToUpper(key))
    69  }
    70  
    71  // match a line of config var=value
    72  var matchLine = regexp.MustCompile(`^([a-zA-Z_]+)=(.*)$`)
    73  
    74  // Start the server and set its env vars
    75  // Call with the mutex held
    76  func start(name string) error {
    77  	out, err := run(name, "start")
    78  	if err != nil {
    79  		return err
    80  	}
    81  	fs.Logf(name, "Starting server")
    82  	// parse the output and set environment vars from it
    83  	var connect string
    84  	for _, line := range bytes.Split(out, []byte("\n")) {
    85  		line = bytes.TrimSpace(line)
    86  		part := matchLine.FindSubmatch(line)
    87  		if part != nil {
    88  			key, value := part[1], part[2]
    89  			if string(key) == "_connect" {
    90  				connect = string(value)
    91  				continue
    92  			}
    93  
    94  			// fs.Debugf(name, "key = %q, envKey = %q, value = %q", key, envKey(name, string(key)), value)
    95  			err = os.Setenv(envKey(name, string(key)), string(value))
    96  			if err != nil {
    97  				return err
    98  			}
    99  		}
   100  	}
   101  	if connect == "" {
   102  		return nil
   103  	}
   104  	// If we got a _connect value then try to connect to it
   105  	const maxTries = 30
   106  	var rdBuf = make([]byte, 1)
   107  	for i := 1; i <= maxTries; i++ {
   108  		if i != 0 {
   109  			time.Sleep(time.Second)
   110  		}
   111  		fs.Debugf(name, "Attempting to connect to %q try %d/%d", connect, i, maxTries)
   112  		conn, err := net.DialTimeout("tcp", connect, time.Second)
   113  		if err != nil {
   114  			fs.Debugf(name, "Connection to %q failed try %d/%d: %v", connect, i, maxTries, err)
   115  			continue
   116  		}
   117  
   118  		err = conn.SetReadDeadline(time.Now().Add(time.Second))
   119  		if err != nil {
   120  			return fmt.Errorf("failed to set deadline: %w", err)
   121  		}
   122  		n, err := conn.Read(rdBuf)
   123  		_ = conn.Close()
   124  		fs.Debugf(name, "Read %d, error: %v", n, err)
   125  		if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {
   126  			// Try again
   127  			continue
   128  		}
   129  		//time.Sleep(30 * time.Second)
   130  		return nil
   131  	}
   132  	return fmt.Errorf("failed to connect to %q on %q", name, connect)
   133  }
   134  
   135  // Start starts the named test server which can be stopped by the
   136  // function returned.
   137  func Start(remoteName string) (fn func(), err error) {
   138  	if remoteName == "" {
   139  		// don't start the local backend
   140  		return func() {}, nil
   141  	}
   142  	parsed, err := fspath.Parse(remoteName)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	name := parsed.ConfigString
   147  	if name == "" {
   148  		// don't start the local backend
   149  		return func() {}, nil
   150  	}
   151  
   152  	// Make sure we know where the config is
   153  	once.Do(func() {
   154  		configDir, err = findConfig()
   155  	})
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	runningMu.Lock()
   161  	defer runningMu.Unlock()
   162  
   163  	if running[name] <= 0 {
   164  		// if server isn't running check to see if this server has
   165  		// been started already but not by us and stop it if so
   166  		if os.Getenv(envKey(name, "type")) == "" && isRunning(name) {
   167  			stop(name)
   168  		}
   169  		if !isRunning(name) {
   170  			err = start(name)
   171  			if err == errNotFound {
   172  				// if no file found then don't start or stop
   173  				return func() {}, nil
   174  			} else if err != nil {
   175  				return nil, err
   176  			}
   177  			running[name] = 0
   178  		} else {
   179  			running[name] = 1
   180  		}
   181  	}
   182  	running[name]++
   183  
   184  	return func() {
   185  		runningMu.Lock()
   186  		defer runningMu.Unlock()
   187  		stop(name)
   188  	}, nil
   189  
   190  }
   191  
   192  // Stops the named test server
   193  // Call with the mutex held
   194  func stop(name string) {
   195  	running[name]--
   196  	if running[name] <= 0 {
   197  		_, err := run(name, "stop")
   198  		if err != nil {
   199  			fs.Errorf(name, "Failed to stop server: %v", err)
   200  		}
   201  		running[name] = 0
   202  		fs.Logf(name, "Stopped server")
   203  	}
   204  }