container_windows.go 10 KB

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