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