github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/fstest/testserver/testserver.go (about)

     1  // Package testserver starts and stops test servers if required
     2  package testserver
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/pkg/errors"
    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 = errors.Wrapf(err, "failed to run %s %s\n%s", cmdPath, command, string(out))
    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, 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  	for i := 1; i <= maxTries; i++ {
   107  		fs.Debugf(name, "Attempting to connect to %q try %d/%d", connect, i, maxTries)
   108  		conn, err := net.Dial("tcp", connect)
   109  		if err == nil {
   110  			_ = conn.Close()
   111  			return nil
   112  		}
   113  		time.Sleep(time.Second)
   114  	}
   115  	return errors.Errorf("failed to connect to %q on %q", name, connect)
   116  }
   117  
   118  // Start starts the named test server which can be stopped by the
   119  // function returned.
   120  func Start(remoteName string) (fn func(), err error) {
   121  	var name string
   122  	name, _, err = fspath.Parse(remoteName)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	if name == "" {
   127  		// don't start the local backend
   128  		return func() {}, nil
   129  	}
   130  
   131  	// Make sure we know where the config is
   132  	once.Do(func() {
   133  		configDir, err = findConfig()
   134  	})
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	runningMu.Lock()
   140  	defer runningMu.Unlock()
   141  
   142  	if running[name] <= 0 {
   143  		// if server isn't running check to see if this server has
   144  		// been started already but not by us and stop it if so
   145  		if os.Getenv(envKey(name, "type")) == "" && isRunning(name) {
   146  			stop(name)
   147  		}
   148  		if !isRunning(name) {
   149  			err = start(name)
   150  			if err == errNotFound {
   151  				// if no file found then don't start or stop
   152  				return func() {}, nil
   153  			} else if err != nil {
   154  				return nil, err
   155  			}
   156  			running[name] = 0
   157  		} else {
   158  			running[name] = 1
   159  		}
   160  	}
   161  	running[name]++
   162  
   163  	return func() {
   164  		runningMu.Lock()
   165  		defer runningMu.Unlock()
   166  		stop(name)
   167  	}, nil
   168  
   169  }
   170  
   171  // Stops the named test server
   172  // Call with the mutex held
   173  func stop(name string) {
   174  	running[name]--
   175  	if running[name] <= 0 {
   176  		_, err := run(name, "stop")
   177  		if err != nil {
   178  			fs.Errorf(name, "Failed to stop server: %v", err)
   179  		}
   180  		running[name] = 0
   181  		fs.Logf(name, "Stopped server")
   182  	}
   183  }