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