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 }