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