utils.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. package integration
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "reflect"
  12. "strings"
  13. "syscall"
  14. "time"
  15. "github.com/docker/docker/pkg/stringutils"
  16. )
  17. // GetExitCode returns the ExitStatus of the specified error if its type is
  18. // exec.ExitError, returns 0 and an error otherwise.
  19. func GetExitCode(err error) (int, error) {
  20. exitCode := 0
  21. if exiterr, ok := err.(*exec.ExitError); ok {
  22. if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  23. return procExit.ExitStatus(), nil
  24. }
  25. }
  26. return exitCode, fmt.Errorf("failed to get exit code")
  27. }
  28. // ProcessExitCode process the specified error and returns the exit status code
  29. // if the error was of type exec.ExitError, returns nothing otherwise.
  30. func ProcessExitCode(err error) (exitCode int) {
  31. if err != nil {
  32. var exiterr error
  33. if exitCode, exiterr = GetExitCode(err); exiterr != nil {
  34. // TODO: Fix this so we check the error's text.
  35. // we've failed to retrieve exit code, so we set it to 127
  36. exitCode = 127
  37. }
  38. }
  39. return
  40. }
  41. // IsKilled process the specified error and returns whether the process was killed or not.
  42. func IsKilled(err error) bool {
  43. if exitErr, ok := err.(*exec.ExitError); ok {
  44. status, ok := exitErr.Sys().(syscall.WaitStatus)
  45. if !ok {
  46. return false
  47. }
  48. // status.ExitStatus() is required on Windows because it does not
  49. // implement Signal() nor Signaled(). Just check it had a bad exit
  50. // status could mean it was killed (and in tests we do kill)
  51. return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0
  52. }
  53. return false
  54. }
  55. // RunCommandWithOutput runs the specified command and returns the combined output (stdout/stderr)
  56. // with the exitCode different from 0 and the error if something bad happened
  57. func RunCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) {
  58. exitCode = 0
  59. out, err := cmd.CombinedOutput()
  60. exitCode = ProcessExitCode(err)
  61. output = string(out)
  62. return
  63. }
  64. // RunCommandWithStdoutStderr runs the specified command and returns stdout and stderr separately
  65. // with the exitCode different from 0 and the error if something bad happened
  66. func RunCommandWithStdoutStderr(cmd *exec.Cmd) (stdout string, stderr string, exitCode int, err error) {
  67. var (
  68. stderrBuffer, stdoutBuffer bytes.Buffer
  69. )
  70. exitCode = 0
  71. cmd.Stderr = &stderrBuffer
  72. cmd.Stdout = &stdoutBuffer
  73. err = cmd.Run()
  74. exitCode = ProcessExitCode(err)
  75. stdout = stdoutBuffer.String()
  76. stderr = stderrBuffer.String()
  77. return
  78. }
  79. // RunCommandWithOutputForDuration runs the specified command "timeboxed" by the specified duration.
  80. // If the process is still running when the timebox is finished, the process will be killed and .
  81. // It will returns the output with the exitCode different from 0 and the error if something bad happened
  82. // and a boolean whether it has been killed or not.
  83. func RunCommandWithOutputForDuration(cmd *exec.Cmd, duration time.Duration) (output string, exitCode int, timedOut bool, err error) {
  84. var outputBuffer bytes.Buffer
  85. if cmd.Stdout != nil {
  86. err = errors.New("cmd.Stdout already set")
  87. return
  88. }
  89. cmd.Stdout = &outputBuffer
  90. if cmd.Stderr != nil {
  91. err = errors.New("cmd.Stderr already set")
  92. return
  93. }
  94. cmd.Stderr = &outputBuffer
  95. // Start the command in the main thread..
  96. err = cmd.Start()
  97. if err != nil {
  98. err = fmt.Errorf("Fail to start command %v : %v", cmd, err)
  99. }
  100. type exitInfo struct {
  101. exitErr error
  102. exitCode int
  103. }
  104. done := make(chan exitInfo, 1)
  105. go func() {
  106. // And wait for it to exit in the goroutine :)
  107. info := exitInfo{}
  108. info.exitErr = cmd.Wait()
  109. info.exitCode = ProcessExitCode(info.exitErr)
  110. done <- info
  111. }()
  112. select {
  113. case <-time.After(duration):
  114. killErr := cmd.Process.Kill()
  115. if killErr != nil {
  116. fmt.Printf("failed to kill (pid=%d): %v\n", cmd.Process.Pid, killErr)
  117. }
  118. timedOut = true
  119. case info := <-done:
  120. err = info.exitErr
  121. exitCode = info.exitCode
  122. }
  123. output = outputBuffer.String()
  124. return
  125. }
  126. var errCmdTimeout = fmt.Errorf("command timed out")
  127. // RunCommandWithOutputAndTimeout runs the specified command "timeboxed" by the specified duration.
  128. // It returns the output with the exitCode different from 0 and the error if something bad happened or
  129. // if the process timed out (and has been killed).
  130. func RunCommandWithOutputAndTimeout(cmd *exec.Cmd, timeout time.Duration) (output string, exitCode int, err error) {
  131. var timedOut bool
  132. output, exitCode, timedOut, err = RunCommandWithOutputForDuration(cmd, timeout)
  133. if timedOut {
  134. err = errCmdTimeout
  135. }
  136. return
  137. }
  138. // RunCommand runs the specified command and returns the exitCode different from 0
  139. // and the error if something bad happened.
  140. func RunCommand(cmd *exec.Cmd) (exitCode int, err error) {
  141. exitCode = 0
  142. err = cmd.Run()
  143. exitCode = ProcessExitCode(err)
  144. return
  145. }
  146. // RunCommandPipelineWithOutput runs the array of commands with the output
  147. // of each pipelined with the following (like cmd1 | cmd2 | cmd3 would do).
  148. // It returns the final output, the exitCode different from 0 and the error
  149. // if something bad happened.
  150. func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) {
  151. if len(cmds) < 2 {
  152. return "", 0, errors.New("pipeline does not have multiple cmds")
  153. }
  154. // connect stdin of each cmd to stdout pipe of previous cmd
  155. for i, cmd := range cmds {
  156. if i > 0 {
  157. prevCmd := cmds[i-1]
  158. cmd.Stdin, err = prevCmd.StdoutPipe()
  159. if err != nil {
  160. return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err)
  161. }
  162. }
  163. }
  164. // start all cmds except the last
  165. for _, cmd := range cmds[:len(cmds)-1] {
  166. if err = cmd.Start(); err != nil {
  167. return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err)
  168. }
  169. }
  170. var pipelineError error
  171. defer func() {
  172. // wait all cmds except the last to release their resources
  173. for _, cmd := range cmds[:len(cmds)-1] {
  174. if err := cmd.Wait(); err != nil {
  175. pipelineError = fmt.Errorf("command %s failed with error: %v", cmd.Path, err)
  176. break
  177. }
  178. }
  179. }()
  180. if pipelineError != nil {
  181. return "", 0, pipelineError
  182. }
  183. // wait on last cmd
  184. return RunCommandWithOutput(cmds[len(cmds)-1])
  185. }
  186. // ConvertSliceOfStringsToMap converts a slices of string in a map
  187. // with the strings as key and an empty string as values.
  188. func ConvertSliceOfStringsToMap(input []string) map[string]struct{} {
  189. output := make(map[string]struct{})
  190. for _, v := range input {
  191. output[v] = struct{}{}
  192. }
  193. return output
  194. }
  195. // CompareDirectoryEntries compares two sets of FileInfo (usually taken from a directory)
  196. // and returns an error if different.
  197. func CompareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error {
  198. var (
  199. e1Entries = make(map[string]struct{})
  200. e2Entries = make(map[string]struct{})
  201. )
  202. for _, e := range e1 {
  203. e1Entries[e.Name()] = struct{}{}
  204. }
  205. for _, e := range e2 {
  206. e2Entries[e.Name()] = struct{}{}
  207. }
  208. if !reflect.DeepEqual(e1Entries, e2Entries) {
  209. return fmt.Errorf("entries differ")
  210. }
  211. return nil
  212. }
  213. // ListTar lists the entries of a tar.
  214. func ListTar(f io.Reader) ([]string, error) {
  215. tr := tar.NewReader(f)
  216. var entries []string
  217. for {
  218. th, err := tr.Next()
  219. if err == io.EOF {
  220. // end of tar archive
  221. return entries, nil
  222. }
  223. if err != nil {
  224. return entries, err
  225. }
  226. entries = append(entries, th.Name)
  227. }
  228. }
  229. // RandomTmpDirPath provides a temporary path with rand string appended.
  230. // does not create or checks if it exists.
  231. func RandomTmpDirPath(s string, platform string) string {
  232. tmp := "/tmp"
  233. if platform == "windows" {
  234. tmp = os.Getenv("TEMP")
  235. }
  236. path := filepath.Join(tmp, fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10)))
  237. if platform == "windows" {
  238. return filepath.FromSlash(path) // Using \
  239. }
  240. return filepath.ToSlash(path) // Using /
  241. }
  242. // ConsumeWithSpeed reads chunkSize bytes from reader before sleeping
  243. // for interval duration. Returns total read bytes. Send true to the
  244. // stop channel to return before reading to EOF on the reader.
  245. func ConsumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) {
  246. buffer := make([]byte, chunkSize)
  247. for {
  248. var readBytes int
  249. readBytes, err = reader.Read(buffer)
  250. n += readBytes
  251. if err != nil {
  252. if err == io.EOF {
  253. err = nil
  254. }
  255. return
  256. }
  257. select {
  258. case <-stop:
  259. return
  260. case <-time.After(interval):
  261. }
  262. }
  263. }
  264. // ParseCgroupPaths parses 'procCgroupData', which is output of '/proc/<pid>/cgroup', and returns
  265. // a map which cgroup name as key and path as value.
  266. func ParseCgroupPaths(procCgroupData string) map[string]string {
  267. cgroupPaths := map[string]string{}
  268. for _, line := range strings.Split(procCgroupData, "\n") {
  269. parts := strings.Split(line, ":")
  270. if len(parts) != 3 {
  271. continue
  272. }
  273. cgroupPaths[parts[1]] = parts[2]
  274. }
  275. return cgroupPaths
  276. }
  277. // ChannelBuffer holds a chan of byte array that can be populate in a goroutine.
  278. type ChannelBuffer struct {
  279. C chan []byte
  280. }
  281. // Write implements Writer.
  282. func (c *ChannelBuffer) Write(b []byte) (int, error) {
  283. c.C <- b
  284. return len(b), nil
  285. }
  286. // Close closes the go channel.
  287. func (c *ChannelBuffer) Close() error {
  288. close(c.C)
  289. return nil
  290. }
  291. // ReadTimeout reads the content of the channel in the specified byte array with
  292. // the specified duration as timeout.
  293. func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
  294. select {
  295. case b := <-c.C:
  296. return copy(p[0:], b), nil
  297. case <-time.After(n):
  298. return -1, fmt.Errorf("timeout reading from channel")
  299. }
  300. }
  301. // RunAtDifferentDate runs the specified function with the given time.
  302. // It changes the date of the system, which can led to weird behaviors.
  303. func RunAtDifferentDate(date time.Time, block func()) {
  304. // Layout for date. MMDDhhmmYYYY
  305. const timeLayout = "010203042006"
  306. // Ensure we bring time back to now
  307. now := time.Now().Format(timeLayout)
  308. dateReset := exec.Command("date", now)
  309. defer RunCommand(dateReset)
  310. dateChange := exec.Command("date", date.Format(timeLayout))
  311. RunCommand(dateChange)
  312. block()
  313. return
  314. }