94d70d83 |
package libcontainerd
import ( |
f1545882 |
"encoding/json" |
f6564746 |
"fmt" |
94d70d83 |
"io" |
6d264645 |
"io/ioutil" |
94d70d83 |
"strings" |
b6db56b5 |
"time" |
94d70d83 |
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus" |
f1545882 |
"github.com/docker/docker/pkg/system" |
02309170 |
"github.com/opencontainers/runtime-spec/specs-go" |
069fdc8a |
"golang.org/x/sys/windows" |
94d70d83 |
)
type container struct {
containerCommon
// Platform specific fields are below here. There are none presently on Windows.
options []CreateOption
// The ociSpec is required, as client.Create() needs a spec,
// but can be called from the RestartManager context which does not
// otherwise have access to the Spec |
02309170 |
ociSpec specs.Spec |
a5b64f28 |
manualStopRequested bool |
959c1a52 |
hcsContainer hcsshim.Container |
94d70d83 |
}
func (ctr *container) newProcess(friendlyName string) *process {
return &process{
processCommon: processCommon{
containerID: ctr.containerID,
friendlyName: friendlyName,
client: ctr.client,
},
}
}
|
740e26f3 |
// start starts a created container.
// Caller needs to lock container ID before calling this method. |
37a3be24 |
func (ctr *container) start(attachStdio StdioCallback) error { |
94d70d83 |
var err error |
f2ad7be2 |
isServicing := false
for _, option := range ctr.options {
if s, ok := option.(*ServicingOption); ok && s.IsServicing {
isServicing = true
}
} |
94d70d83 |
|
da92dad5 |
// Start the container. If this is a servicing container, this call will block
// until the container is done with the servicing execution. |
5231c553 |
logrus.Debugln("libcontainerd: starting container ", ctr.containerID) |
959c1a52 |
if err = ctr.hcsContainer.Start(); err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: failed to start container: %s", err) |
054992e2 |
if err := ctr.terminate(); err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: failed to cleanup after a failed Start. %s", err) |
054992e2 |
} else { |
5231c553 |
logrus.Debugln("libcontainerd: cleaned up after failed Start by calling Terminate") |
054992e2 |
} |
94d70d83 |
return err
}
|
959c1a52 |
// Note we always tell HCS to
// create stdout as it's required regardless of '-i' or '-t' options, so that
// docker can always grab the output through logs. We also tell HCS to always
// create stdin, even if it's not used - it will be closed shortly. Stderr
// is only created if it we're not -t.
createProcessParms := &hcsshim.ProcessConfig{ |
94d70d83 |
EmulateConsole: ctr.ociSpec.Process.Terminal,
WorkingDirectory: ctr.ociSpec.Process.Cwd, |
f2ad7be2 |
CreateStdInPipe: !isServicing,
CreateStdOutPipe: !isServicing,
CreateStdErrPipe: !ctr.ociSpec.Process.Terminal && !isServicing, |
94d70d83 |
} |
267c04aa |
createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height)
createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
94d70d83 |
// Configure the environment for the process
createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
19645521 |
if ctr.ociSpec.Platform.OS == "windows" {
createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ")
} else {
createProcessParms.CommandArgs = ctr.ociSpec.Process.Args
} |
5207ff72 |
createProcessParms.User = ctr.ociSpec.Process.User.Username |
94d70d83 |
|
f1545882 |
// LCOW requires the raw OCI spec passed through HCS and onwards to GCS for the utility VM.
if system.LCOWSupported() && ctr.ociSpec.Platform.OS == "linux" {
ociBuf, err := json.Marshal(ctr.ociSpec)
if err != nil {
return err
}
ociRaw := json.RawMessage(ociBuf)
createProcessParms.OCISpecification = &ociRaw
}
|
959c1a52 |
// Start the command running in the container. |
740e26f3 |
newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) |
94d70d83 |
if err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: CreateProcess() failed %s", err) |
054992e2 |
if err := ctr.terminate(); err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: failed to cleanup after a failed CreateProcess. %s", err) |
94d70d83 |
} else { |
5231c553 |
logrus.Debugln("libcontainerd: cleaned up after failed CreateProcess by calling Terminate") |
94d70d83 |
}
return err
}
|
740e26f3 |
pid := newProcess.Pid()
|
959c1a52 |
// Save the hcs Process and PID
ctr.process.friendlyName = InitFriendlyName |
f6d5f7b9 |
ctr.process.hcsProcess = newProcess |
959c1a52 |
|
f2ad7be2 |
// If this is a servicing container, wait on the process synchronously here and |
6591a37a |
// if it succeeds, wait for it cleanly shutdown and merge into the parent container. |
f2ad7be2 |
if isServicing {
exitCode := ctr.waitProcessExitCode(&ctr.process)
if exitCode != 0 { |
f6564746 |
if err := ctr.terminate(); err != nil {
logrus.Warnf("libcontainerd: terminating servicing container %s failed: %s", ctr.containerID, err)
}
return fmt.Errorf("libcontainerd: servicing container %s returned non-zero exit code %d", ctr.containerID, exitCode) |
f2ad7be2 |
}
|
6591a37a |
return ctr.hcsContainer.WaitTimeout(time.Minute * 5) |
f2ad7be2 |
}
|
959c1a52 |
var stdout, stderr io.ReadCloser
var stdin io.WriteCloser |
740e26f3 |
stdin, stdout, stderr, err = newProcess.Stdio() |
959c1a52 |
if err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: failed to get stdio pipes: %s", err) |
959c1a52 |
if err := ctr.terminate(); err != nil { |
5231c553 |
logrus.Errorf("libcontainerd: failed to cleanup after a failed Stdio. %s", err) |
959c1a52 |
}
return err
}
iopipe := &IOPipe{Terminal: ctr.ociSpec.Process.Terminal}
|
740e26f3 |
iopipe.Stdin = createStdInCloser(stdin, newProcess) |
959c1a52 |
|
94d70d83 |
// Convert io.ReadClosers to io.Readers
if stdout != nil { |
6d264645 |
iopipe.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout}) |
94d70d83 |
}
if stderr != nil { |
6d264645 |
iopipe.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr}) |
94d70d83 |
}
// Save the PID |
5231c553 |
logrus.Debugf("libcontainerd: process started - PID %d", pid) |
94d70d83 |
ctr.systemPid = uint32(pid)
// Spin up a go routine waiting for exit to handle cleanup |
959c1a52 |
go ctr.waitExit(&ctr.process, true) |
94d70d83 |
ctr.client.appendContainer(ctr)
|
37a3be24 |
if err := attachStdio(*iopipe); err != nil { |
94d70d83 |
// OK to return the error here, as waitExit will handle tear-down in HCS
return err
}
// Tell the docker engine that the container has started.
si := StateInfo{ |
818a5198 |
CommonStateInfo: CommonStateInfo{
State: StateStart,
Pid: ctr.systemPid, // Not sure this is needed? Double-check monitor.go in daemon BUGBUG @jhowardmsft
}} |
740e26f3 |
logrus.Debugf("libcontainerd: start() completed OK, %+v", si) |
94d70d83 |
return ctr.client.backend.StateChanged(ctr.containerID, si)
}
|
f2ad7be2 |
// waitProcessExitCode will wait for the given process to exit and return its error code.
func (ctr *container) waitProcessExitCode(process *process) int { |
94d70d83 |
// Block indefinitely for the process to exit. |
959c1a52 |
err := process.hcsProcess.Wait() |
94d70d83 |
if err != nil { |
069fdc8a |
if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE { |
5231c553 |
logrus.Warnf("libcontainerd: Wait() failed (container may have been killed): %s", err) |
94d70d83 |
}
// Fall through here, do not return. This ensures we attempt to continue the |
959c1a52 |
// shutdown in HCS and tell the docker engine that the process/container
// has exited to avoid a container being dropped on the floor.
}
exitCode, err := process.hcsProcess.ExitCode()
if err != nil { |
069fdc8a |
if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE { |
5231c553 |
logrus.Warnf("libcontainerd: unable to get exit code from container %s", ctr.containerID) |
959c1a52 |
} |
17c1b9c0 |
// Since we got an error retrieving the exit code, make sure that the code we return
// doesn't incorrectly indicate success.
exitCode = -1
|
959c1a52 |
// Fall through here, do not return. This ensures we attempt to continue the
// shutdown in HCS and tell the docker engine that the process/container |
94d70d83 |
// has exited to avoid a container being dropped on the floor.
}
|
f2ad7be2 |
return exitCode
}
// waitExit runs as a goroutine waiting for the process to exit. It's
// equivalent to (in the linux containerd world) where events come in for
// state change notifications from containerd.
func (ctr *container) waitExit(process *process, isFirstProcessToStart bool) error { |
5231c553 |
logrus.Debugln("libcontainerd: waitExit() on pid", process.systemPid) |
f2ad7be2 |
exitCode := ctr.waitProcessExitCode(process) |
b819ffdb |
// Lock the container while removing the process/container from the list |
740e26f3 |
ctr.client.lock(ctr.containerID) |
f2ad7be2 |
|
b819ffdb |
if !isFirstProcessToStart {
ctr.cleanProcess(process.friendlyName)
} else {
ctr.client.deleteContainer(ctr.containerID)
}
// Unlock here so other threads are unblocked
ctr.client.unlock(ctr.containerID)
|
94d70d83 |
// Assume the container has exited
si := StateInfo{ |
818a5198 |
CommonStateInfo: CommonStateInfo{
State: StateExit,
ExitCode: uint32(exitCode), |
959c1a52 |
Pid: process.systemPid,
ProcessID: process.friendlyName, |
818a5198 |
},
UpdatePending: false, |
94d70d83 |
}
// But it could have been an exec'd process which exited
if !isFirstProcessToStart {
si.State = StateExitProcess |
a5b64f28 |
} else { |
f1545882 |
// Pending updates is only applicable for WCOW
if ctr.ociSpec.Platform.OS == "windows" {
updatePending, err := ctr.hcsContainer.HasPendingUpdates()
if err != nil {
logrus.Warnf("libcontainerd: HasPendingUpdates() failed (container may have been killed): %s", err)
} else {
si.UpdatePending = updatePending
} |
da92dad5 |
} |
818a5198 |
|
5231c553 |
logrus.Debugf("libcontainerd: shutting down container %s", ctr.containerID) |
959c1a52 |
if err := ctr.shutdown(); err != nil { |
5231c553 |
logrus.Debugf("libcontainerd: failed to shutdown container %s", ctr.containerID) |
94d70d83 |
} else { |
5231c553 |
logrus.Debugf("libcontainerd: completed shutting down container %s", ctr.containerID) |
94d70d83 |
} |
959c1a52 |
if err := ctr.hcsContainer.Close(); err != nil {
logrus.Error(err)
} |
94d70d83 |
}
|
740e26f3 |
if err := process.hcsProcess.Close(); err != nil {
logrus.Errorf("libcontainerd: hcsProcess.Close(): %v", err)
}
|
94d70d83 |
// Call into the backend to notify it of the state change. |
5231c553 |
logrus.Debugf("libcontainerd: waitExit() calling backend.StateChanged %+v", si) |
94d70d83 |
if err := ctr.client.backend.StateChanged(ctr.containerID, si); err != nil {
logrus.Error(err)
} |
c2499dff |
logrus.Debugf("libcontainerd: waitExit() completed OK, %+v", si)
|
94d70d83 |
return nil
} |
959c1a52 |
|
740e26f3 |
// cleanProcess removes process from the map.
// Caller needs to lock container ID before calling this method.
func (ctr *container) cleanProcess(id string) {
delete(ctr.processes, id)
}
// shutdown shuts down the container in HCS
// Caller needs to lock container ID before calling this method. |
959c1a52 |
func (ctr *container) shutdown() error {
const shutdownTimeout = time.Minute * 5
err := ctr.hcsContainer.Shutdown() |
c58d0358 |
if hcsshim.IsPending(err) { |
959c1a52 |
// Explicit timeout to avoid a (remote) possibility that shutdown hangs indefinitely.
err = ctr.hcsContainer.WaitTimeout(shutdownTimeout) |
c58d0358 |
} else if hcsshim.IsAlreadyStopped(err) { |
79060e82 |
err = nil |
959c1a52 |
}
if err != nil { |
5231c553 |
logrus.Debugf("libcontainerd: error shutting down container %s %v calling terminate", ctr.containerID, err) |
959c1a52 |
if err := ctr.terminate(); err != nil {
return err
}
return err
}
return nil
}
|
740e26f3 |
// terminate terminates the container in HCS
// Caller needs to lock container ID before calling this method. |
959c1a52 |
func (ctr *container) terminate() error {
const terminateTimeout = time.Minute * 5
err := ctr.hcsContainer.Terminate()
|
c58d0358 |
if hcsshim.IsPending(err) { |
959c1a52 |
err = ctr.hcsContainer.WaitTimeout(terminateTimeout) |
c58d0358 |
} else if hcsshim.IsAlreadyStopped(err) { |
79060e82 |
err = nil |
959c1a52 |
}
if err != nil { |
5231c553 |
logrus.Debugf("libcontainerd: error terminating container %s %v", ctr.containerID, err) |
959c1a52 |
return err
}
return nil
} |