testserver.go 4.8 KB

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