github.com/olljanat/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  }