process.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // +build windows
  2. package client
  3. import (
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strings"
  9. "time"
  10. "github.com/Microsoft/hcsshim"
  11. "github.com/sirupsen/logrus"
  12. )
  13. // Process is the structure pertaining to a process running in a utility VM.
  14. type process struct {
  15. Process hcsshim.Process
  16. Stdin io.WriteCloser
  17. Stdout io.ReadCloser
  18. Stderr io.ReadCloser
  19. }
  20. // createUtilsProcess is a convenient wrapper for hcsshim.createUtilsProcess to use when
  21. // communicating with a utility VM.
  22. func (config *Config) createUtilsProcess(commandLine string) (process, error) {
  23. logrus.Debugf("opengcs: createUtilsProcess")
  24. if config.Uvm == nil {
  25. return process{}, fmt.Errorf("cannot create utils process as no utility VM is in configuration")
  26. }
  27. var (
  28. err error
  29. proc process
  30. )
  31. env := make(map[string]string)
  32. env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:"
  33. processConfig := &hcsshim.ProcessConfig{
  34. EmulateConsole: false,
  35. CreateStdInPipe: true,
  36. CreateStdOutPipe: true,
  37. CreateStdErrPipe: true,
  38. CreateInUtilityVm: true,
  39. WorkingDirectory: "/bin",
  40. Environment: env,
  41. CommandLine: commandLine,
  42. }
  43. proc.Process, err = config.Uvm.CreateProcess(processConfig)
  44. if err != nil {
  45. return process{}, fmt.Errorf("failed to create process (%+v) in utility VM: %s", config, err)
  46. }
  47. if proc.Stdin, proc.Stdout, proc.Stderr, err = proc.Process.Stdio(); err != nil {
  48. proc.Process.Kill() // Should this have a timeout?
  49. proc.Process.Close()
  50. return process{}, fmt.Errorf("failed to get stdio pipes for process %+v: %s", config, err)
  51. }
  52. logrus.Debugf("opengcs: createUtilsProcess success: pid %d", proc.Process.Pid())
  53. return proc, nil
  54. }
  55. // RunProcess runs the given command line program in the utilityVM. It takes in
  56. // an input to the reader to feed into stdin and returns stdout to output.
  57. // IMPORTANT: It is the responsibility of the caller to call Close() on the returned process.
  58. func (config *Config) RunProcess(commandLine string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (hcsshim.Process, error) {
  59. logrus.Debugf("opengcs: RunProcess: %s", commandLine)
  60. process, err := config.createUtilsProcess(commandLine)
  61. if err != nil {
  62. return nil, err
  63. }
  64. // Send the data into the process's stdin
  65. if stdin != nil {
  66. if _, err = copyWithTimeout(process.Stdin,
  67. stdin,
  68. 0,
  69. config.UvmTimeoutSeconds,
  70. fmt.Sprintf("send to stdin of %s", commandLine)); err != nil {
  71. return nil, err
  72. }
  73. // Don't need stdin now we've sent everything. This signals GCS that we are finished sending data.
  74. if err := process.Process.CloseStdin(); err != nil && !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyClosed(err) {
  75. // This error will occur if the compute system is currently shutting down
  76. if perr, ok := err.(*hcsshim.ProcessError); ok && perr.Err != hcsshim.ErrVmcomputeOperationInvalidState {
  77. return nil, err
  78. }
  79. }
  80. }
  81. if stdout != nil {
  82. // Copy the data over to the writer.
  83. if _, err := copyWithTimeout(stdout,
  84. process.Stdout,
  85. 0,
  86. config.UvmTimeoutSeconds,
  87. fmt.Sprintf("RunProcess: copy back from %s", commandLine)); err != nil {
  88. return nil, err
  89. }
  90. }
  91. if stderr != nil {
  92. // Copy the data over to the writer.
  93. if _, err := copyWithTimeout(stderr,
  94. process.Stderr,
  95. 0,
  96. config.UvmTimeoutSeconds,
  97. fmt.Sprintf("RunProcess: copy back from %s", commandLine)); err != nil {
  98. return nil, err
  99. }
  100. }
  101. logrus.Debugf("opengcs: runProcess success: %s", commandLine)
  102. return process.Process, nil
  103. }
  104. func debugCommand(s string) string {
  105. return fmt.Sprintf(`echo -e 'DEBUG COMMAND: %s\\n--------------\\n';%s;echo -e '\\n\\n';`, s, s)
  106. }
  107. // DebugGCS extracts logs from the GCS. It's a useful hack for debugging,
  108. // but not necessarily optimal, but all that is available to us in RS3.
  109. func (config *Config) DebugGCS() {
  110. if logrus.GetLevel() < logrus.DebugLevel || len(os.Getenv("OPENGCS_DEBUG_ENABLE")) == 0 {
  111. return
  112. }
  113. var out bytes.Buffer
  114. cmd := os.Getenv("OPENGCS_DEBUG_COMMAND")
  115. if cmd == "" {
  116. cmd = `sh -c "`
  117. cmd += debugCommand("kill -10 `pidof gcs`") // SIGUSR1 for stackdump
  118. cmd += debugCommand("ls -l /tmp")
  119. cmd += debugCommand("cat /tmp/gcs.log")
  120. cmd += debugCommand("cat /tmp/gcs/gcs-stacks*")
  121. cmd += debugCommand("cat /tmp/gcs/paniclog*")
  122. cmd += debugCommand("ls -l /tmp/gcs")
  123. cmd += debugCommand("ls -l /tmp/gcs/*")
  124. cmd += debugCommand("cat /tmp/gcs/*/config.json")
  125. cmd += debugCommand("ls -lR /var/run/gcsrunc")
  126. cmd += debugCommand("cat /tmp/gcs/global-runc.log")
  127. cmd += debugCommand("cat /tmp/gcs/*/runc.log")
  128. cmd += debugCommand("ps -ef")
  129. cmd += `"`
  130. }
  131. proc, err := config.RunProcess(cmd, nil, &out, nil)
  132. defer func() {
  133. if proc != nil {
  134. proc.Kill()
  135. proc.Close()
  136. }
  137. }()
  138. if err != nil {
  139. logrus.Debugln("benign failure getting gcs logs: ", err)
  140. }
  141. if proc != nil {
  142. proc.WaitTimeout(time.Second * 30)
  143. }
  144. logrus.Debugf("GCS Debugging:\n%s\n\nEnd GCS Debugging", strings.TrimSpace(out.String()))
  145. }