github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/daemon/execdriver/windows/run.go (about)

     1  // +build windows
     2  
     3  package windows
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/daemon/execdriver"
    15  	"github.com/microsoft/hcsshim"
    16  )
    17  
    18  // defaultContainerNAT is the default name of the container NAT device that is
    19  // preconfigured on the server.
    20  const defaultContainerNAT = "ContainerNAT"
    21  
    22  type layer struct {
    23  	ID   string
    24  	Path string
    25  }
    26  
    27  type defConfig struct {
    28  	DefFile string
    29  }
    30  
    31  type portBinding struct {
    32  	Protocol     string
    33  	InternalPort int
    34  	ExternalPort int
    35  }
    36  
    37  type natSettings struct {
    38  	Name         string
    39  	PortBindings []portBinding
    40  }
    41  
    42  type networkConnection struct {
    43  	NetworkName string
    44  	// TODO Windows: Add Ip4Address string to this structure when hooked up in
    45  	// docker CLI. This is present in the HCS JSON handler.
    46  	EnableNat bool
    47  	Nat       natSettings
    48  }
    49  type networkSettings struct {
    50  	MacAddress string
    51  }
    52  
    53  type device struct {
    54  	DeviceType string
    55  	Connection interface{}
    56  	Settings   interface{}
    57  }
    58  
    59  type mappedDir struct {
    60  	HostPath      string
    61  	ContainerPath string
    62  	ReadOnly      bool
    63  }
    64  
    65  type containerInit struct {
    66  	SystemType              string      // HCS requires this to be hard-coded to "Container"
    67  	Name                    string      // Name of the container. We use the docker ID.
    68  	Owner                   string      // The management platform that created this container
    69  	IsDummy                 bool        // Used for development purposes.
    70  	VolumePath              string      // Windows volume path for scratch space
    71  	Devices                 []device    // Devices used by the container
    72  	IgnoreFlushesDuringBoot bool        // Optimisation hint for container startup in Windows
    73  	LayerFolderPath         string      // Where the layer folders are located
    74  	Layers                  []layer     // List of storage layers
    75  	ProcessorWeight         int64       `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be ommited and HCS will default.
    76  	HostName                string      // Hostname
    77  	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
    78  	SandboxPath             string      // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
    79  	HvPartition             bool        // True if it a Hyper-V Container
    80  }
    81  
    82  // defaultOwner is a tag passed to HCS to allow it to differentiate between
    83  // container creator management stacks. We hard code "docker" in the case
    84  // of docker.
    85  const defaultOwner = "docker"
    86  
    87  // Run implements the exec driver Driver interface
    88  func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
    89  
    90  	var (
    91  		term execdriver.Terminal
    92  		err  error
    93  	)
    94  
    95  	cu := &containerInit{
    96  		SystemType:              "Container",
    97  		Name:                    c.ID,
    98  		Owner:                   defaultOwner,
    99  		IsDummy:                 dummyMode,
   100  		VolumePath:              c.Rootfs,
   101  		IgnoreFlushesDuringBoot: c.FirstStart,
   102  		LayerFolderPath:         c.LayerFolder,
   103  		ProcessorWeight:         c.Resources.CPUShares,
   104  		HostName:                c.Hostname,
   105  	}
   106  
   107  	// Work out the isolation (whether it is a hypervisor partition)
   108  	if c.Isolation.IsDefault() {
   109  		// Not specified by caller. Take daemon default
   110  		cu.HvPartition = defaultIsolation.IsHyperV()
   111  	} else {
   112  		// Take value specified by caller
   113  		cu.HvPartition = c.Isolation.IsHyperV()
   114  	}
   115  
   116  	if cu.HvPartition {
   117  		cu.SandboxPath = filepath.Dir(c.LayerFolder)
   118  	} else {
   119  		cu.VolumePath = c.Rootfs
   120  		cu.LayerFolderPath = c.LayerFolder
   121  	}
   122  
   123  	for _, layerPath := range c.LayerPaths {
   124  		_, filename := filepath.Split(layerPath)
   125  		g, err := hcsshim.NameToGuid(filename)
   126  		if err != nil {
   127  			return execdriver.ExitStatus{ExitCode: -1}, err
   128  		}
   129  		cu.Layers = append(cu.Layers, layer{
   130  			ID:   g.ToString(),
   131  			Path: layerPath,
   132  		})
   133  	}
   134  
   135  	// Add the mounts (volumes, bind mounts etc) to the structure
   136  	mds := make([]mappedDir, len(c.Mounts))
   137  	for i, mount := range c.Mounts {
   138  		mds[i] = mappedDir{
   139  			HostPath:      mount.Source,
   140  			ContainerPath: mount.Destination,
   141  			ReadOnly:      !mount.Writable}
   142  	}
   143  	cu.MappedDirectories = mds
   144  
   145  	// TODO Windows. At some point, when there is CLI on docker run to
   146  	// enable the IP Address of the container to be passed into docker run,
   147  	// the IP Address needs to be wired through to HCS in the JSON. It
   148  	// would be present in c.Network.Interface.IPAddress. See matching
   149  	// TODO in daemon\container_windows.go, function populateCommand.
   150  
   151  	if c.Network.Interface != nil {
   152  
   153  		var pbs []portBinding
   154  
   155  		// Enumerate through the port bindings specified by the user and convert
   156  		// them into the internal structure matching the JSON blob that can be
   157  		// understood by the HCS.
   158  		for i, v := range c.Network.Interface.PortBindings {
   159  			proto := strings.ToUpper(i.Proto())
   160  			if proto != "TCP" && proto != "UDP" {
   161  				return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto())
   162  			}
   163  
   164  			if len(v) > 1 {
   165  				return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings")
   166  			}
   167  
   168  			for _, v2 := range v {
   169  				var (
   170  					iPort, ePort int
   171  					err          error
   172  				)
   173  				if len(v2.HostIP) != 0 {
   174  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
   175  				}
   176  				if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
   177  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
   178  				}
   179  				if iPort, err = strconv.Atoi(i.Port()); err != nil {
   180  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
   181  				}
   182  				if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
   183  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range")
   184  				}
   185  				pbs = append(pbs,
   186  					portBinding{ExternalPort: ePort,
   187  						InternalPort: iPort,
   188  						Protocol:     proto})
   189  			}
   190  		}
   191  
   192  		// TODO Windows: TP3 workaround. Allow the user to override the name of
   193  		// the Container NAT device through an environment variable. This will
   194  		// ultimately be a global daemon parameter on Windows, similar to -b
   195  		// for the name of the virtual switch (aka bridge).
   196  		cn := os.Getenv("DOCKER_CONTAINER_NAT")
   197  		if len(cn) == 0 {
   198  			cn = defaultContainerNAT
   199  		}
   200  
   201  		dev := device{
   202  			DeviceType: "Network",
   203  			Connection: &networkConnection{
   204  				NetworkName: c.Network.Interface.Bridge,
   205  				// TODO Windows: Fixme, next line. Needs HCS fix.
   206  				EnableNat: false,
   207  				Nat: natSettings{
   208  					Name:         cn,
   209  					PortBindings: pbs,
   210  				},
   211  			},
   212  		}
   213  
   214  		if c.Network.Interface.MacAddress != "" {
   215  			windowsStyleMAC := strings.Replace(
   216  				c.Network.Interface.MacAddress, ":", "-", -1)
   217  			dev.Settings = networkSettings{
   218  				MacAddress: windowsStyleMAC,
   219  			}
   220  		}
   221  		cu.Devices = append(cu.Devices, dev)
   222  	} else {
   223  		logrus.Debugln("No network interface")
   224  	}
   225  
   226  	configurationb, err := json.Marshal(cu)
   227  	if err != nil {
   228  		return execdriver.ExitStatus{ExitCode: -1}, err
   229  	}
   230  
   231  	configuration := string(configurationb)
   232  
   233  	err = hcsshim.CreateComputeSystem(c.ID, configuration)
   234  	if err != nil {
   235  		logrus.Debugln("Failed to create temporary container ", err)
   236  		return execdriver.ExitStatus{ExitCode: -1}, err
   237  	}
   238  
   239  	// Start the container
   240  	logrus.Debugln("Starting container ", c.ID)
   241  	err = hcsshim.StartComputeSystem(c.ID)
   242  	if err != nil {
   243  		logrus.Errorf("Failed to start compute system: %s", err)
   244  		return execdriver.ExitStatus{ExitCode: -1}, err
   245  	}
   246  	defer func() {
   247  		// Stop the container
   248  		if forceKill {
   249  			logrus.Debugf("Forcibly terminating container %s", c.ID)
   250  			if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
   251  				logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err)
   252  			}
   253  		} else {
   254  			logrus.Debugf("Shutting down container %s", c.ID)
   255  			if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
   256  				if errno != hcsshim.Win32SystemShutdownIsInProgress &&
   257  					errno != hcsshim.Win32SpecifiedPathInvalid &&
   258  					errno != hcsshim.Win32SystemCannotFindThePathSpecified {
   259  					logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err)
   260  				}
   261  			}
   262  		}
   263  	}()
   264  
   265  	createProcessParms := hcsshim.CreateProcessParams{
   266  		EmulateConsole:   c.ProcessConfig.Tty,
   267  		WorkingDirectory: c.WorkingDir,
   268  		ConsoleSize:      c.ProcessConfig.ConsoleSize,
   269  	}
   270  
   271  	// Configure the environment for the process
   272  	createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
   273  
   274  	createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
   275  
   276  	if err != nil {
   277  		return execdriver.ExitStatus{ExitCode: -1}, err
   278  	}
   279  
   280  	// Start the command running in the container.
   281  	pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
   282  	if err != nil {
   283  		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
   284  		return execdriver.ExitStatus{ExitCode: -1}, err
   285  	}
   286  
   287  	// Now that the process has been launched, begin copying data to and from
   288  	// the named pipes for the std handles.
   289  	setupPipes(stdin, stdout, stderr, pipes)
   290  
   291  	//Save the PID as we'll need this in Kill()
   292  	logrus.Debugf("PID %d", pid)
   293  	c.ContainerPid = int(pid)
   294  
   295  	if c.ProcessConfig.Tty {
   296  		term = NewTtyConsole(c.ID, pid)
   297  	} else {
   298  		term = NewStdConsole()
   299  	}
   300  	c.ProcessConfig.Terminal = term
   301  
   302  	// Maintain our list of active containers. We'll need this later for exec
   303  	// and other commands.
   304  	d.Lock()
   305  	d.activeContainers[c.ID] = &activeContainer{
   306  		command: c,
   307  	}
   308  	d.Unlock()
   309  
   310  	if hooks.Start != nil {
   311  		// A closed channel for OOM is returned here as it will be
   312  		// non-blocking and return the correct result when read.
   313  		chOOM := make(chan struct{})
   314  		close(chOOM)
   315  		hooks.Start(&c.ProcessConfig, int(pid), chOOM)
   316  	}
   317  
   318  	var (
   319  		exitCode int32
   320  		errno    uint32
   321  	)
   322  	exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite)
   323  	if err != nil {
   324  		if errno != hcsshim.Win32PipeHasBeenEnded {
   325  			logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
   326  		}
   327  		// Do NOT return err here as the container would have
   328  		// started, otherwise docker will deadlock. It's perfectly legitimate
   329  		// for WaitForProcessInComputeSystem to fail in situations such
   330  		// as the container being killed on another thread.
   331  		return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil
   332  	}
   333  
   334  	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
   335  	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
   336  }
   337  
   338  // SupportsHooks implements the execdriver Driver interface.
   339  // The windows driver does not support the hook mechanism
   340  func (d *Driver) SupportsHooks() bool {
   341  	return false
   342  }