container_windows.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. // start starts a created container.
  31. // Caller needs to lock container ID before calling this method.
  32. func (ctr *container) start() error {
  33. var err error
  34. isServicing := false
  35. for _, option := range ctr.options {
  36. if s, ok := option.(*ServicingOption); ok && s.IsServicing {
  37. isServicing = true
  38. }
  39. }
  40. // Start the container. If this is a servicing container, this call will block
  41. // until the container is done with the servicing execution.
  42. logrus.Debugln("libcontainerd: starting container ", ctr.containerID)
  43. if err = ctr.hcsContainer.Start(); err != nil {
  44. logrus.Errorf("libcontainerd: failed to start container: %s", err)
  45. if err := ctr.terminate(); err != nil {
  46. logrus.Errorf("libcontainerd: failed to cleanup after a failed Start. %s", err)
  47. } else {
  48. logrus.Debugln("libcontainerd: cleaned up after failed Start by calling Terminate")
  49. }
  50. return err
  51. }
  52. // Note we always tell HCS to
  53. // create stdout as it's required regardless of '-i' or '-t' options, so that
  54. // docker can always grab the output through logs. We also tell HCS to always
  55. // create stdin, even if it's not used - it will be closed shortly. Stderr
  56. // is only created if it we're not -t.
  57. createProcessParms := &hcsshim.ProcessConfig{
  58. EmulateConsole: ctr.ociSpec.Process.Terminal,
  59. WorkingDirectory: ctr.ociSpec.Process.Cwd,
  60. ConsoleSize: ctr.ociSpec.Process.InitialConsoleSize,
  61. CreateStdInPipe: !isServicing,
  62. CreateStdOutPipe: !isServicing,
  63. CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing,
  64. }
  65. // Configure the environment for the process
  66. createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env)
  67. createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
  68. // Start the command running in the container.
  69. newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms)
  70. if err != nil {
  71. logrus.Errorf("libcontainerd: CreateProcess() failed %s", err)
  72. if err := ctr.terminate(); err != nil {
  73. logrus.Errorf("libcontainerd: failed to cleanup after a failed CreateProcess. %s", err)
  74. } else {
  75. logrus.Debugln("libcontainerd: cleaned up after failed CreateProcess by calling Terminate")
  76. }
  77. return err
  78. }
  79. ctr.startedAt = time.Now()
  80. pid := newProcess.Pid()
  81. openedProcess, err := ctr.hcsContainer.OpenProcess(pid)
  82. if err != nil {
  83. logrus.Errorf("OpenProcess() failed %s", err)
  84. if err := ctr.terminate(); err != nil {
  85. logrus.Errorf("Failed to cleanup after a failed OpenProcess. %s", err)
  86. } else {
  87. logrus.Debugln("Cleaned up after failed OpenProcess by calling Terminate")
  88. }
  89. return err
  90. }
  91. // Save the hcs Process and PID
  92. ctr.process.friendlyName = InitFriendlyName
  93. ctr.process.hcsProcess = openedProcess
  94. // If this is a servicing container, wait on the process synchronously here and
  95. // if it succeeds, wait for it cleanly shutdown and merge into the parent container.
  96. if isServicing {
  97. exitCode := ctr.waitProcessExitCode(&ctr.process)
  98. if exitCode != 0 {
  99. logrus.Warnf("libcontainerd: servicing container %s returned non-zero exit code %d", ctr.containerID, exitCode)
  100. return ctr.terminate()
  101. }
  102. return ctr.hcsContainer.WaitTimeout(time.Minute * 5)
  103. }
  104. var stdout, stderr io.ReadCloser
  105. var stdin io.WriteCloser
  106. stdin, stdout, stderr, err = newProcess.Stdio()
  107. if err != nil {
  108. logrus.Errorf("libcontainerd: failed to get stdio pipes: %s", err)
  109. if err := ctr.terminate(); err != nil {
  110. logrus.Errorf("libcontainerd: failed to cleanup after a failed Stdio. %s", err)
  111. }
  112. return err
  113. }
  114. iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal}
  115. iopipe.Stdin = createStdInCloser(stdin, newProcess)
  116. // Convert io.ReadClosers to io.Readers
  117. if stdout != nil {
  118. iopipe.Stdout = openReaderFromPipe(stdout)
  119. }
  120. if stderr != nil {
  121. iopipe.Stderr = openReaderFromPipe(stderr)
  122. }
  123. // Save the PID
  124. logrus.Debugf("libcontainerd: process started - PID %d", pid)
  125. ctr.systemPid = uint32(pid)
  126. // Spin up a go routine waiting for exit to handle cleanup
  127. go ctr.waitExit(&ctr.process, true)
  128. ctr.client.appendContainer(ctr)
  129. if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
  130. // OK to return the error here, as waitExit will handle tear-down in HCS
  131. return err
  132. }
  133. // Tell the docker engine that the container has started.
  134. si := StateInfo{
  135. CommonStateInfo: CommonStateInfo{
  136. State: StateStart,
  137. Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft
  138. }}
  139. logrus.Debugf("libcontainerd: start() completed OK, %+v", si)
  140. return ctr.client.backend.StateChanged(ctr.containerID, si)
  141. }
  142. // waitProcessExitCode will wait for the given process to exit and return its error code.
  143. func (ctr *container) waitProcessExitCode(process *process) int {
  144. // Block indefinitely for the process to exit.
  145. err := process.hcsProcess.Wait()
  146. if err != nil {
  147. if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
  148. logrus.Warnf("libcontainerd: Wait() failed (container may have been killed): %s", err)
  149. }
  150. // Fall through here, do not return. This ensures we attempt to continue the
  151. // shutdown in HCS and tell the docker engine that the process/container
  152. // has exited to avoid a container being dropped on the floor.
  153. }
  154. exitCode, err := process.hcsProcess.ExitCode()
  155. if err != nil {
  156. if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
  157. logrus.Warnf("libcontainerd: unable to get exit code from container %s", ctr.containerID)
  158. }
  159. // Since we got an error retrieving the exit code, make sure that the code we return
  160. // doesn't incorrectly indicate success.
  161. exitCode = -1
  162. // Fall through here, do not return. This ensures we attempt to continue the
  163. // shutdown in HCS and tell the docker engine that the process/container
  164. // has exited to avoid a container being dropped on the floor.
  165. }
  166. return exitCode
  167. }
  168. // waitExit runs as a goroutine waiting for the process to exit. It's
  169. // equivalent to (in the linux containerd world) where events come in for
  170. // state change notifications from containerd.
  171. func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) error {
  172. var waitRestart chan error
  173. logrus.Debugln("libcontainerd: waitExit() on pid", process.systemPid)
  174. exitCode := ctr.waitProcessExitCode(process)
  175. // Lock the container while shutting down
  176. ctr.client.lock(ctr.containerID)
  177. // Assume the container has exited
  178. si := StateInfo{
  179. CommonStateInfo: CommonStateInfo{
  180. State: StateExit,
  181. ExitCode: uint32(exitCode),
  182. Pid: process.systemPid,
  183. ProcessID: process.friendlyName,
  184. },
  185. UpdatePending: false,
  186. }
  187. // But it could have been an exec'd process which exited
  188. if !isFirstProcessToStart {
  189. si.State = StateExitProcess
  190. ctr.cleanProcess(process.friendlyName)
  191. } else {
  192. updatePending, err := ctr.hcsContainer.HasPendingUpdates()
  193. if err != nil {
  194. logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
  195. } else {
  196. si.UpdatePending = updatePending
  197. }
  198. logrus.Debugf("libcontainerd: shutting down container %s", ctr.containerID)
  199. if err := ctr.shutdown(); err != nil {
  200. logrus.Debugf("libcontainerd: failed to shutdown container %s", ctr.containerID)
  201. } else {
  202. logrus.Debugf("libcontainerd: completed shutting down container %s", ctr.containerID)
  203. }
  204. if err := ctr.hcsContainer.Close(); err != nil {
  205. logrus.Error(err)
  206. }
  207. if !ctr.manualStopRequested && ctr.restartManager != nil {
  208. restart, wait, err := ctr.restartManager.ShouldRestart(uint32(exitCode), false, time.Since(ctr.startedAt))
  209. if err != nil {
  210. logrus.Error(err)
  211. } else if restart {
  212. si.State = StateRestart
  213. ctr.restarting = true
  214. ctr.client.deleteContainer(ctr.containerID)
  215. waitRestart = wait
  216. }
  217. }
  218. // Remove process from list if we have exited
  219. // We need to do so here in case the Message Handler decides to restart it.
  220. if si.State == StateExit {
  221. ctr.client.deleteContainer(ctr.containerID)
  222. }
  223. }
  224. if err := process.hcsProcess.Close(); err != nil {
  225. logrus.Errorf("libcontainerd: hcsProcess.Close(): %v", err)
  226. }
  227. // Unlock here before we call back into the daemon to update state
  228. ctr.client.unlock(ctr.containerID)
  229. // Call into the backend to notify it of the state change.
  230. logrus.Debugf("libcontainerd: waitExit() calling backend.StateChanged %+v", si)
  231. if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
  232. logrus.Error(err)
  233. }
  234. if si.State == StateRestart {
  235. go func() {
  236. err := <-waitRestart
  237. ctr.restarting = false
  238. if err == nil {
  239. if err = ctr.client.Create(ctr.containerID, "", "", ctr.ociSpec, ctr.options...); err != nil {
  240. logrus.Errorf("libcontainerd: error restarting %v", err)
  241. }
  242. }
  243. if err != nil {
  244. si.State = StateExit
  245. if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
  246. logrus.Error(err)
  247. }
  248. }
  249. }()
  250. }
  251. logrus.Debugf("libcontainerd: waitExit() completed OK, %+v", si)
  252. return nil
  253. }
  254. // cleanProcess removes process from the map.
  255. // Caller needs to lock container ID before calling this method.
  256. func (ctr *container) cleanProcess(id string) {
  257. delete(ctr.processes, id)
  258. }
  259. // shutdown shuts down the container in HCS
  260. // Caller needs to lock container ID before calling this method.
  261. func (ctr *container) shutdown() error {
  262. const shutdownTimeout = time.Minute * 5
  263. err := ctr.hcsContainer.Shutdown()
  264. if hcsshim.IsPending(err) {
  265. // Explicit timeout to avoid a (remote) possibility that shutdown hangs indefinitely.
  266. err = ctr.hcsContainer.WaitTimeout(shutdownTimeout)
  267. } else if hcsshim.IsAlreadyStopped(err) {
  268. err = nil
  269. }
  270. if err != nil {
  271. logrus.Debugf("libcontainerd: error shutting down container %s %v calling terminate", ctr.containerID, err)
  272. if err := ctr.terminate(); err != nil {
  273. return err
  274. }
  275. return err
  276. }
  277. return nil
  278. }
  279. // terminate terminates the container in HCS
  280. // Caller needs to lock container ID before calling this method.
  281. func (ctr *container) terminate() error {
  282. const terminateTimeout = time.Minute * 5
  283. err := ctr.hcsContainer.Terminate()
  284. if hcsshim.IsPending(err) {
  285. err = ctr.hcsContainer.WaitTimeout(terminateTimeout)
  286. } else if hcsshim.IsAlreadyStopped(err) {
  287. err = nil
  288. }
  289. if err != nil {
  290. logrus.Debugf("libcontainerd: error terminating container %s %v", ctr.containerID, err)
  291. return err
  292. }
  293. return nil
  294. }