container_windows.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package libcontainerd
  2. import (
  3. "io"
  4. "strings"
  5. "syscall"
  6. "time"
  7. "github.com/Microsoft/hcsshim"
  8. "github.com/Sirupsen/logrus"
  9. )
  10. type container struct {
  11. containerCommon
  12. // Platform specific fields are below here. There are none presently on Windows.
  13. options []CreateOption
  14. // The ociSpec is required, as client.Create() needs a spec,
  15. // but can be called from the RestartManager context which does not
  16. // otherwise have access to the Spec
  17. ociSpec Spec
  18. manualStopRequested bool
  19. hcsContainer hcsshim.Container
  20. }
  21. func (ctr *container) newProcess(friendlyName string) *process {
  22. return &process{
  23. processCommon: processCommon{
  24. containerID: ctr.containerID,
  25. friendlyName: friendlyName,
  26. client: ctr.client,
  27. },
  28. }
  29. }
  30. func (ctr *container) start() error {
  31. var err error
  32. // Start the container. If this is a servicing container, this call will block
  33. // until the container is done with the servicing execution.
  34. logrus.Debugln("Starting container ", ctr.containerID)
  35. if err = ctr.hcsContainer.Start(); err != nil {
  36. logrus.Errorf("Failed to start container: %s", err)
  37. if err := ctr.terminate(); err != nil {
  38. logrus.Errorf("Failed to cleanup after a failed Start. %s", err)
  39. } else {
  40. logrus.Debugln("Cleaned up after failed Start by calling Terminate")
  41. }
  42. return err
  43. }
  44. for _, option := range ctr.options {
  45. if s, ok := option.(*ServicingOption); ok && s.IsServicing {
  46. // Since the servicing operation is complete when Start returns without error,
  47. // we can shutdown (which triggers merge) and exit early.
  48. return ctr.shutdown()
  49. }
  50. }
  51. // Note we always tell HCS to
  52. // create stdout as it's required regardless of '-i' or '-t' options, so that
  53. // docker can always grab the output through logs. We also tell HCS to always
  54. // create stdin, even if it's not used - it will be closed shortly. Stderr
  55. // is only created if it we're not -t.
  56. createProcessParms := &hcsshim.ProcessConfig{
  57. EmulateConsole: ctr.ociSpec.Process.Terminal,
  58. WorkingDirectory: ctr.ociSpec.Process.Cwd,
  59. ConsoleSize: ctr.ociSpec.Process.InitialConsoleSize,
  60. CreateStdInPipe: true,
  61. CreateStdOutPipe: true,
  62. CreateStdErrPipe: !ctr.ociSpec.Process.Terminal,
  63. }
  64. // Configure the environment for the process
  65. createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
  66. createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
  67. // Start the command running in the container.
  68. hcsProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
  69. if err != nil {
  70. logrus.Errorf("CreateProcess() failed %s", err)
  71. if err := ctr.terminate(); err != nil {
  72. logrus.Errorf("Failed to cleanup after a failed CreateProcess. %s", err)
  73. } else {
  74. logrus.Debugln("Cleaned up after failed CreateProcess by calling Terminate")
  75. }
  76. return err
  77. }
  78. ctr.startedAt = time.Now()
  79. // Save the hcs Process and PID
  80. ctr.process.friendlyName = InitFriendlyName
  81. pid := hcsProcess.Pid()
  82. ctr.process.hcsProcess = hcsProcess
  83. var stdout, stderr io.ReadCloser
  84. var stdin io.WriteCloser
  85. stdin, stdout, stderr, err = hcsProcess.Stdio()
  86. if err != nil {
  87. logrus.Errorf("failed to get stdio pipes: %s", err)
  88. if err := ctr.terminate(); err != nil {
  89. logrus.Errorf("Failed to cleanup after a failed Stdio. %s", err)
  90. }
  91. return err
  92. }
  93. iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal}
  94. iopipe.Stdin = createStdInCloser(stdin, hcsProcess)
  95. // TEMP: Work around Windows BS/DEL behavior.
  96. iopipe.Stdin = fixStdinBackspaceBehavior(iopipe.Stdin, ctr.ociSpec.Platform.OSVersion, ctr.ociSpec.Process.Terminal)
  97. // Convert io.ReadClosers to io.Readers
  98. if stdout != nil {
  99. iopipe.Stdout = openReaderFromPipe(stdout)
  100. }
  101. if stderr != nil {
  102. iopipe.Stderr = openReaderFromPipe(stderr)
  103. }
  104. // Save the PID
  105. logrus.Debugf("Process started - PID %d", pid)
  106. ctr.systemPid = uint32(pid)
  107. // Spin up a go routine waiting for exit to handle cleanup
  108. go ctr.waitExit(&ctr.process, true)
  109. ctr.client.appendContainer(ctr)
  110. if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
  111. // OK to return the error here, as waitExit will handle tear-down in HCS
  112. return err
  113. }
  114. // Tell the docker engine that the container has started.
  115. si := StateInfo{
  116. CommonStateInfo: CommonStateInfo{
  117. State: StateStart,
  118. Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft
  119. }}
  120. return ctr.client.backend.StateChanged(ctr.containerID, si)
  121. }
  122. // waitExit runs as a goroutine waiting for the process to exit. It's
  123. // equivalent to (in the linux containerd world) where events come in for
  124. // state change notifications from containerd.
  125. func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) error {
  126. logrus.Debugln("waitExit on pid", process.systemPid)
  127. // Block indefinitely for the process to exit.
  128. err := process.hcsProcess.Wait()
  129. if err != nil {
  130. if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
  131. logrus.Warnf("Wait failed (container may have been killed): %s", err)
  132. }
  133. // Fall through here, do not return. This ensures we attempt to continue the
  134. // shutdown in HCS and tell the docker engine that the process/container
  135. // has exited to avoid a container being dropped on the floor.
  136. }
  137. exitCode, err := process.hcsProcess.ExitCode()
  138. if err != nil {
  139. if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
  140. logrus.Warnf("Unable to get exit code from container %s", ctr.containerID)
  141. }
  142. // Fall through here, do not return. This ensures we attempt to continue the
  143. // shutdown in HCS and tell the docker engine that the process/container
  144. // has exited to avoid a container being dropped on the floor.
  145. }
  146. if err := process.hcsProcess.Close(); err != nil {
  147. logrus.Error(err)
  148. }
  149. // Assume the container has exited
  150. si := StateInfo{
  151. CommonStateInfo: CommonStateInfo{
  152. State: StateExit,
  153. ExitCode: uint32(exitCode),
  154. Pid: process.systemPid,
  155. ProcessID: process.friendlyName,
  156. },
  157. UpdatePending: false,
  158. }
  159. // But it could have been an exec'd process which exited
  160. if !isFirstProcessToStart {
  161. si.State = StateExitProcess
  162. } else {
  163. updatePending, err := ctr.hcsContainer.HasPendingUpdates()
  164. if err != nil {
  165. logrus.Warnf("HasPendingUpdates failed (container may have been killed): %s", err)
  166. } else {
  167. si.UpdatePending = updatePending
  168. }
  169. logrus.Debugf("Shutting down container %s", ctr.containerID)
  170. if err := ctr.shutdown(); err != nil {
  171. logrus.Debugf("Failed to shutdown container %s", ctr.containerID)
  172. } else {
  173. logrus.Debugf("Completed shutting down container %s", ctr.containerID)
  174. }
  175. if err := ctr.hcsContainer.Close(); err != nil {
  176. logrus.Error(err)
  177. }
  178. if !ctr.manualStopRequested && ctr.restartManager != nil {
  179. restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode), false, time.Since(ctr.startedAt))
  180. if err != nil {
  181. logrus.Error(err)
  182. } else if restart {
  183. si.State = StateRestart
  184. ctr.restarting = true
  185. go func() {
  186. err := <-wait
  187. ctr.restarting = false
  188. ctr.client.deleteContainer(ctr.friendlyName)
  189. if err != nil {
  190. si.State = StateExit
  191. if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
  192. logrus.Error(err)
  193. }
  194. logrus.Error(err)
  195. } else {
  196. ctr.client.Create(ctr.containerID, ctr.ociSpec, ctr.options...)
  197. }
  198. }()
  199. }
  200. }
  201. // Remove process from list if we have exited
  202. // We need to do so here in case the Message Handler decides to restart it.
  203. if si.State == StateExit {
  204. if err := ctr.hcsContainer.Close(); err != nil {
  205. logrus.Error(err)
  206. }
  207. ctr.client.deleteContainer(ctr.friendlyName)
  208. }
  209. }
  210. // Call into the backend to notify it of the state change.
  211. logrus.Debugf("waitExit() calling backend.StateChanged %v", si)
  212. if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
  213. logrus.Error(err)
  214. }
  215. logrus.Debugln("waitExit() completed OK")
  216. return nil
  217. }
  218. func (ctr *container) shutdown() error {
  219. const shutdownTimeout = time.Minute * 5
  220. err := ctr.hcsContainer.Shutdown()
  221. if err == hcsshim.ErrVmcomputeOperationPending {
  222. // Explicit timeout to avoid a (remote) possibility that shutdown hangs indefinitely.
  223. err = ctr.hcsContainer.WaitTimeout(shutdownTimeout)
  224. }
  225. if err != nil {
  226. logrus.Debugf("error shutting down container %s %v calling terminate", ctr.containerID, err)
  227. if err := ctr.terminate(); err != nil {
  228. return err
  229. }
  230. return err
  231. }
  232. return nil
  233. }
  234. func (ctr *container) terminate() error {
  235. const terminateTimeout = time.Minute * 5
  236. err := ctr.hcsContainer.Terminate()
  237. if err == hcsshim.ErrVmcomputeOperationPending {
  238. err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
  239. }
  240. if err != nil {
  241. return err
  242. }
  243. return nil
  244. }