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  }