github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/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 if remoteName == "" { 122 // don't start the local backend 123 return func() {}, nil 124 } 125 var name string 126 name, _, err = fspath.Parse(remoteName) 127 if err != nil { 128 return nil, err 129 } 130 if name == "" { 131 // don't start the local backend 132 return func() {}, nil 133 } 134 135 // Make sure we know where the config is 136 once.Do(func() { 137 configDir, err = findConfig() 138 }) 139 if err != nil { 140 return nil, err 141 } 142 143 runningMu.Lock() 144 defer runningMu.Unlock() 145 146 if running[name] <= 0 { 147 // if server isn't running check to see if this server has 148 // been started already but not by us and stop it if so 149 if os.Getenv(envKey(name, "type")) == "" && isRunning(name) { 150 stop(name) 151 } 152 if !isRunning(name) { 153 err = start(name) 154 if err == errNotFound { 155 // if no file found then don't start or stop 156 return func() {}, nil 157 } else if err != nil { 158 return nil, err 159 } 160 running[name] = 0 161 } else { 162 running[name] = 1 163 } 164 } 165 running[name]++ 166 167 return func() { 168 runningMu.Lock() 169 defer runningMu.Unlock() 170 stop(name) 171 }, nil 172 173 } 174 175 // Stops the named test server 176 // Call with the mutex held 177 func stop(name string) { 178 running[name]-- 179 if running[name] <= 0 { 180 _, err := run(name, "stop") 181 if err != nil { 182 fs.Errorf(name, "Failed to stop server: %v", err) 183 } 184 running[name] = 0 185 fs.Logf(name, "Stopped server") 186 } 187 }