github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/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" 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 go func() { 109 exitErr := cmd.Run() 110 exitCode = ProcessExitCode(exitErr) 111 done <- exitErr 112 }() 113 114 select { 115 case <-time.After(duration): 116 killErr := cmd.Process.Kill() 117 if killErr != nil { 118 fmt.Printf("failed to kill (pid=%d): %v\n", cmd.Process.Pid, killErr) 119 } 120 timedOut = true 121 break 122 case err = <-done: 123 break 124 } 125 output = outputBuffer.String() 126 return 127 } 128 129 var errCmdTimeout = fmt.Errorf("command timed out") 130 131 // RunCommandWithOutputAndTimeout runs the specified command "timeboxed" by the specified duration. 132 // It returns the output with the exitCode different from 0 and the error if something bad happened or 133 // if the process timed out (and has been killed). 134 func RunCommandWithOutputAndTimeout(cmd *exec.Cmd, timeout time.Duration) (output string, exitCode int, err error) { 135 var timedOut bool 136 output, exitCode, timedOut, err = RunCommandWithOutputForDuration(cmd, timeout) 137 if timedOut { 138 err = errCmdTimeout 139 } 140 return 141 } 142 143 // RunCommand runs the specified command and returns the exitCode different from 0 144 // and the error if something bad happened. 145 func RunCommand(cmd *exec.Cmd) (exitCode int, err error) { 146 exitCode = 0 147 err = cmd.Run() 148 exitCode = ProcessExitCode(err) 149 return 150 } 151 152 // RunCommandPipelineWithOutput runs the array of commands with the output 153 // of each pipelined with the following (like cmd1 | cmd2 | cmd3 would do). 154 // It returns the final output, the exitCode different from 0 and the error 155 // if something bad happened. 156 func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) { 157 if len(cmds) < 2 { 158 return "", 0, errors.New("pipeline does not have multiple cmds") 159 } 160 161 // connect stdin of each cmd to stdout pipe of previous cmd 162 for i, cmd := range cmds { 163 if i > 0 { 164 prevCmd := cmds[i-1] 165 cmd.Stdin, err = prevCmd.StdoutPipe() 166 167 if err != nil { 168 return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err) 169 } 170 } 171 } 172 173 // start all cmds except the last 174 for _, cmd := range cmds[:len(cmds)-1] { 175 if err = cmd.Start(); err != nil { 176 return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err) 177 } 178 } 179 180 defer func() { 181 // wait all cmds except the last to release their resources 182 for _, cmd := range cmds[:len(cmds)-1] { 183 cmd.Wait() 184 } 185 }() 186 187 // wait on last cmd 188 return RunCommandWithOutput(cmds[len(cmds)-1]) 189 } 190 191 // UnmarshalJSON deserialize a JSON in the given interface. 192 func UnmarshalJSON(data []byte, result interface{}) error { 193 if err := json.Unmarshal(data, result); err != nil { 194 return err 195 } 196 197 return nil 198 } 199 200 // ConvertSliceOfStringsToMap converts a slices of string in a map 201 // with the strings as key and an empty string as values. 202 func ConvertSliceOfStringsToMap(input []string) map[string]struct{} { 203 output := make(map[string]struct{}) 204 for _, v := range input { 205 output[v] = struct{}{} 206 } 207 return output 208 } 209 210 // CompareDirectoryEntries compares two sets of FileInfo (usually taken from a directory) 211 // and returns an error if different. 212 func CompareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error { 213 var ( 214 e1Entries = make(map[string]struct{}) 215 e2Entries = make(map[string]struct{}) 216 ) 217 for _, e := range e1 { 218 e1Entries[e.Name()] = struct{}{} 219 } 220 for _, e := range e2 { 221 e2Entries[e.Name()] = struct{}{} 222 } 223 if !reflect.DeepEqual(e1Entries, e2Entries) { 224 return fmt.Errorf("entries differ") 225 } 226 return nil 227 } 228 229 // ListTar lists the entries of a tar. 230 func ListTar(f io.Reader) ([]string, error) { 231 tr := tar.NewReader(f) 232 var entries []string 233 234 for { 235 th, err := tr.Next() 236 if err == io.EOF { 237 // end of tar archive 238 return entries, nil 239 } 240 if err != nil { 241 return entries, err 242 } 243 entries = append(entries, th.Name) 244 } 245 } 246 247 // RandomUnixTmpDirPath provides a temporary unix path with rand string appended. 248 // does not create or checks if it exists. 249 func RandomUnixTmpDirPath(s string) string { 250 return path.Join("/tmp", fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10))) 251 } 252 253 // ConsumeWithSpeed reads chunkSize bytes from reader after every interval. 254 // Returns total read bytes. 255 func ConsumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) { 256 buffer := make([]byte, chunkSize) 257 for { 258 select { 259 case <-stop: 260 return 261 default: 262 var readBytes int 263 readBytes, err = reader.Read(buffer) 264 n += readBytes 265 if err != nil { 266 if err == io.EOF { 267 err = nil 268 } 269 return 270 } 271 time.Sleep(interval) 272 } 273 } 274 } 275 276 // ParseCgroupPaths arses 'procCgroupData', which is output of '/proc/<pid>/cgroup', and returns 277 // a map which cgroup name as key and path as value. 278 func ParseCgroupPaths(procCgroupData string) map[string]string { 279 cgroupPaths := map[string]string{} 280 for _, line := range strings.Split(procCgroupData, "\n") { 281 parts := strings.Split(line, ":") 282 if len(parts) != 3 { 283 continue 284 } 285 cgroupPaths[parts[1]] = parts[2] 286 } 287 return cgroupPaths 288 } 289 290 // ChannelBuffer holds a chan of byte array that can be populate in a goroutine. 291 type ChannelBuffer struct { 292 C chan []byte 293 } 294 295 // Write implements Writer. 296 func (c *ChannelBuffer) Write(b []byte) (int, error) { 297 c.C <- b 298 return len(b), nil 299 } 300 301 // Close closes the go channel. 302 func (c *ChannelBuffer) Close() error { 303 close(c.C) 304 return nil 305 } 306 307 // ReadTimeout reads the content of the channel in the specified byte array with 308 // the specified duration as timeout. 309 func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) { 310 select { 311 case b := <-c.C: 312 return copy(p[0:], b), nil 313 case <-time.After(n): 314 return -1, fmt.Errorf("timeout reading from channel") 315 } 316 } 317 318 // RunAtDifferentDate runs the specifed function with the given time. 319 // It changes the date of the system, which can led to weird behaviors. 320 func RunAtDifferentDate(date time.Time, block func()) { 321 // Layout for date. MMDDhhmmYYYY 322 const timeLayout = "010203042006" 323 // Ensure we bring time back to now 324 now := time.Now().Format(timeLayout) 325 dateReset := exec.Command("date", now) 326 defer RunCommand(dateReset) 327 328 dateChange := exec.Command("date", date.Format(timeLayout)) 329 RunCommand(dateChange) 330 block() 331 return 332 }