libcontainerd/container_windows.go
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
 }