github.com/rentongzhang/docker@v1.8.2-rc1/daemon/execdriver/windows/run.go (about)

     1  // +build windows
     2  
     3  package windows
     4  
     5  // Note this is alpha code for the bring up of containers on Windows.
     6  
     7  import (
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/daemon/execdriver"
    15  	"github.com/microsoft/hcsshim"
    16  	"github.com/natefinch/npipe"
    17  )
    18  
    19  type layer struct {
    20  	Id   string
    21  	Path string
    22  }
    23  
    24  type defConfig struct {
    25  	DefFile string
    26  }
    27  
    28  type networkConnection struct {
    29  	NetworkName string
    30  	EnableNat   bool
    31  }
    32  type networkSettings struct {
    33  	MacAddress string
    34  }
    35  
    36  type device struct {
    37  	DeviceType string
    38  	Connection interface{}
    39  	Settings   interface{}
    40  }
    41  
    42  type containerInit struct {
    43  	SystemType              string
    44  	Name                    string
    45  	IsDummy                 bool
    46  	VolumePath              string
    47  	Devices                 []device
    48  	IgnoreFlushesDuringBoot bool
    49  	LayerFolderPath         string
    50  	Layers                  []layer
    51  }
    52  
    53  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
    54  
    55  	var (
    56  		term                           execdriver.Terminal
    57  		err                            error
    58  		inListen, outListen, errListen *npipe.PipeListener
    59  	)
    60  
    61  	// Make sure the client isn't asking for options which aren't supported
    62  	err = checkSupportedOptions(c)
    63  	if err != nil {
    64  		return execdriver.ExitStatus{ExitCode: -1}, err
    65  	}
    66  
    67  	cu := &containerInit{
    68  		SystemType:              "Container",
    69  		Name:                    c.ID,
    70  		IsDummy:                 dummyMode,
    71  		VolumePath:              c.Rootfs,
    72  		IgnoreFlushesDuringBoot: c.FirstStart,
    73  		LayerFolderPath:         c.LayerFolder,
    74  	}
    75  
    76  	for i := 0; i < len(c.LayerPaths); i++ {
    77  		cu.Layers = append(cu.Layers, layer{
    78  			Id:   hcsshim.NewGUID(c.LayerPaths[i]).ToString(),
    79  			Path: c.LayerPaths[i],
    80  		})
    81  	}
    82  
    83  	if c.Network.Interface != nil {
    84  		dev := device{
    85  			DeviceType: "Network",
    86  			Connection: &networkConnection{
    87  				NetworkName: c.Network.Interface.Bridge,
    88  				EnableNat:   false,
    89  			},
    90  		}
    91  
    92  		if c.Network.Interface.MacAddress != "" {
    93  			windowsStyleMAC := strings.Replace(
    94  				c.Network.Interface.MacAddress, ":", "-", -1)
    95  			dev.Settings = networkSettings{
    96  				MacAddress: windowsStyleMAC,
    97  			}
    98  		}
    99  
   100  		logrus.Debugf("Virtual switch '%s', mac='%s'", c.Network.Interface.Bridge, c.Network.Interface.MacAddress)
   101  
   102  		cu.Devices = append(cu.Devices, dev)
   103  	} else {
   104  		logrus.Debugln("No network interface")
   105  	}
   106  
   107  	configurationb, err := json.Marshal(cu)
   108  	if err != nil {
   109  		return execdriver.ExitStatus{ExitCode: -1}, err
   110  	}
   111  
   112  	configuration := string(configurationb)
   113  
   114  	err = hcsshim.CreateComputeSystem(c.ID, configuration)
   115  	if err != nil {
   116  		logrus.Debugln("Failed to create temporary container ", err)
   117  		return execdriver.ExitStatus{ExitCode: -1}, err
   118  	}
   119  
   120  	// Start the container
   121  	logrus.Debugln("Starting container ", c.ID)
   122  	err = hcsshim.StartComputeSystem(c.ID)
   123  	if err != nil {
   124  		logrus.Errorf("Failed to start compute system: %s", err)
   125  		return execdriver.ExitStatus{ExitCode: -1}, err
   126  	}
   127  	defer func() {
   128  		// Stop the container
   129  
   130  		if terminateMode {
   131  			logrus.Debugf("Terminating container %s", c.ID)
   132  			if err := hcsshim.TerminateComputeSystem(c.ID); err != nil {
   133  				// IMPORTANT: Don't fail if fails to change state. It could already
   134  				// have been stopped through kill().
   135  				// Otherwise, the docker daemon will hang in job wait()
   136  				logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err)
   137  			}
   138  		} else {
   139  			logrus.Debugf("Shutting down container %s", c.ID)
   140  			if err := hcsshim.ShutdownComputeSystem(c.ID); err != nil {
   141  				// IMPORTANT: Don't fail if fails to change state. It could already
   142  				// have been stopped through kill().
   143  				// Otherwise, the docker daemon will hang in job wait()
   144  				logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
   145  			}
   146  		}
   147  	}()
   148  
   149  	// We use a different pipe name between real and dummy mode in the HCS
   150  	var serverPipeFormat, clientPipeFormat string
   151  	if dummyMode {
   152  		clientPipeFormat = `\\.\pipe\docker-run-%[1]s-%[2]s`
   153  		serverPipeFormat = clientPipeFormat
   154  	} else {
   155  		clientPipeFormat = `\\.\pipe\docker-run-%[2]s`
   156  		serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-run-%[2]s`
   157  	}
   158  
   159  	createProcessParms := hcsshim.CreateProcessParams{
   160  		EmulateConsole:   c.ProcessConfig.Tty,
   161  		WorkingDirectory: c.WorkingDir,
   162  		ConsoleSize:      c.ProcessConfig.ConsoleSize,
   163  	}
   164  
   165  	// Configure the environment for the process
   166  	createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
   167  
   168  	// Connect stdin
   169  	if pipes.Stdin != nil {
   170  		stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdin")
   171  		createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdin")
   172  
   173  		// Listen on the named pipe
   174  		inListen, err = npipe.Listen(stdInPipe)
   175  		if err != nil {
   176  			logrus.Errorf("stdin failed to listen on %s err=%s", stdInPipe, err)
   177  			return execdriver.ExitStatus{ExitCode: -1}, err
   178  		}
   179  		defer inListen.Close()
   180  
   181  		// Launch a goroutine to do the accept. We do this so that we can
   182  		// cause an otherwise blocking goroutine to gracefully close when
   183  		// the caller (us) closes the listener
   184  		go stdinAccept(inListen, stdInPipe, pipes.Stdin)
   185  	}
   186  
   187  	// Connect stdout
   188  	stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdout")
   189  	createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdout")
   190  
   191  	outListen, err = npipe.Listen(stdOutPipe)
   192  	if err != nil {
   193  		logrus.Errorf("stdout failed to listen on %s err=%s", stdOutPipe, err)
   194  		return execdriver.ExitStatus{ExitCode: -1}, err
   195  	}
   196  	defer outListen.Close()
   197  	go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
   198  
   199  	// No stderr on TTY.
   200  	if !c.ProcessConfig.Tty {
   201  		// Connect stderr
   202  		stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stderr")
   203  		createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stderr")
   204  		errListen, err = npipe.Listen(stdErrPipe)
   205  		if err != nil {
   206  			logrus.Errorf("stderr failed to listen on %s err=%s", stdErrPipe, err)
   207  			return execdriver.ExitStatus{ExitCode: -1}, err
   208  		}
   209  		defer errListen.Close()
   210  		go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
   211  	}
   212  
   213  	// This should get caught earlier, but just in case - validate that we
   214  	// have something to run
   215  	if c.ProcessConfig.Entrypoint == "" {
   216  		err = errors.New("No entrypoint specified")
   217  		logrus.Error(err)
   218  		return execdriver.ExitStatus{ExitCode: -1}, err
   219  	}
   220  
   221  	// Build the command line of the process
   222  	createProcessParms.CommandLine = c.ProcessConfig.Entrypoint
   223  	for _, arg := range c.ProcessConfig.Arguments {
   224  		logrus.Debugln("appending ", arg)
   225  		createProcessParms.CommandLine += " " + arg
   226  	}
   227  	logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine)
   228  
   229  	// Start the command running in the container.
   230  	var pid uint32
   231  	pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
   232  
   233  	if err != nil {
   234  		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
   235  		return execdriver.ExitStatus{ExitCode: -1}, err
   236  	}
   237  
   238  	//Save the PID as we'll need this in Kill()
   239  	logrus.Debugf("PID %d", pid)
   240  	c.ContainerPid = int(pid)
   241  
   242  	if c.ProcessConfig.Tty {
   243  		term = NewTtyConsole(c.ID, pid)
   244  	} else {
   245  		term = NewStdConsole()
   246  	}
   247  	c.ProcessConfig.Terminal = term
   248  
   249  	// Maintain our list of active containers. We'll need this later for exec
   250  	// and other commands.
   251  	d.Lock()
   252  	d.activeContainers[c.ID] = &activeContainer{
   253  		command: c,
   254  	}
   255  	d.Unlock()
   256  
   257  	// Invoke the start callback
   258  	if startCallback != nil {
   259  		startCallback(&c.ProcessConfig, int(pid))
   260  	}
   261  
   262  	var exitCode int32
   263  	exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid)
   264  	if err != nil {
   265  		logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
   266  		return execdriver.ExitStatus{ExitCode: -1}, err
   267  	}
   268  
   269  	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
   270  	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
   271  }