github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/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  	cu.HvPartition = c.HvPartition
   108  
   109  	if cu.HvPartition {
   110  		cu.SandboxPath = filepath.Dir(c.LayerFolder)
   111  	} else {
   112  		cu.VolumePath = c.Rootfs
   113  		cu.LayerFolderPath = c.LayerFolder
   114  	}
   115  
   116  	for _, layerPath := range c.LayerPaths {
   117  		_, filename := filepath.Split(layerPath)
   118  		g, err := hcsshim.NameToGuid(filename)
   119  		if err != nil {
   120  			return execdriver.ExitStatus{ExitCode: -1}, err
   121  		}
   122  		cu.Layers = append(cu.Layers, layer{
   123  			ID:   g.ToString(),
   124  			Path: layerPath,
   125  		})
   126  	}
   127  
   128  	// Add the mounts (volumes, bind mounts etc) to the structure
   129  	mds := make([]mappedDir, len(c.Mounts))
   130  	for i, mount := range c.Mounts {
   131  		mds[i] = mappedDir{
   132  			HostPath:      mount.Source,
   133  			ContainerPath: mount.Destination,
   134  			ReadOnly:      !mount.Writable}
   135  	}
   136  	cu.MappedDirectories = mds
   137  
   138  	// TODO Windows. At some point, when there is CLI on docker run to
   139  	// enable the IP Address of the container to be passed into docker run,
   140  	// the IP Address needs to be wired through to HCS in the JSON. It
   141  	// would be present in c.Network.Interface.IPAddress. See matching
   142  	// TODO in daemon\container_windows.go, function populateCommand.
   143  
   144  	if c.Network.Interface != nil {
   145  
   146  		var pbs []portBinding
   147  
   148  		// Enumerate through the port bindings specified by the user and convert
   149  		// them into the internal structure matching the JSON blob that can be
   150  		// understood by the HCS.
   151  		for i, v := range c.Network.Interface.PortBindings {
   152  			proto := strings.ToUpper(i.Proto())
   153  			if proto != "TCP" && proto != "UDP" {
   154  				return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto())
   155  			}
   156  
   157  			if len(v) > 1 {
   158  				return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings")
   159  			}
   160  
   161  			for _, v2 := range v {
   162  				var (
   163  					iPort, ePort int
   164  					err          error
   165  				)
   166  				if len(v2.HostIP) != 0 {
   167  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
   168  				}
   169  				if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
   170  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
   171  				}
   172  				if iPort, err = strconv.Atoi(i.Port()); err != nil {
   173  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
   174  				}
   175  				if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
   176  					return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range")
   177  				}
   178  				pbs = append(pbs,
   179  					portBinding{ExternalPort: ePort,
   180  						InternalPort: iPort,
   181  						Protocol:     proto})
   182  			}
   183  		}
   184  
   185  		// TODO Windows: TP3 workaround. Allow the user to override the name of
   186  		// the Container NAT device through an environment variable. This will
   187  		// ultimately be a global daemon parameter on Windows, similar to -b
   188  		// for the name of the virtual switch (aka bridge).
   189  		cn := os.Getenv("DOCKER_CONTAINER_NAT")
   190  		if len(cn) == 0 {
   191  			cn = defaultContainerNAT
   192  		}
   193  
   194  		dev := device{
   195  			DeviceType: "Network",
   196  			Connection: &networkConnection{
   197  				NetworkName: c.Network.Interface.Bridge,
   198  				// TODO Windows: Fixme, next line. Needs HCS fix.
   199  				EnableNat: false,
   200  				Nat: natSettings{
   201  					Name:         cn,
   202  					PortBindings: pbs,
   203  				},
   204  			},
   205  		}
   206  
   207  		if c.Network.Interface.MacAddress != "" {
   208  			windowsStyleMAC := strings.Replace(
   209  				c.Network.Interface.MacAddress, ":", "-", -1)
   210  			dev.Settings = networkSettings{
   211  				MacAddress: windowsStyleMAC,
   212  			}
   213  		}
   214  		cu.Devices = append(cu.Devices, dev)
   215  	} else {
   216  		logrus.Debugln("No network interface")
   217  	}
   218  
   219  	configurationb, err := json.Marshal(cu)
   220  	if err != nil {
   221  		return execdriver.ExitStatus{ExitCode: -1}, err
   222  	}
   223  
   224  	configuration := string(configurationb)
   225  
   226  	err = hcsshim.CreateComputeSystem(c.ID, configuration)
   227  	if err != nil {
   228  		logrus.Debugln("Failed to create temporary container ", err)
   229  		return execdriver.ExitStatus{ExitCode: -1}, err
   230  	}
   231  
   232  	// Start the container
   233  	logrus.Debugln("Starting container ", c.ID)
   234  	err = hcsshim.StartComputeSystem(c.ID)
   235  	if err != nil {
   236  		logrus.Errorf("Failed to start compute system: %s", err)
   237  		return execdriver.ExitStatus{ExitCode: -1}, err
   238  	}
   239  	defer func() {
   240  		// Stop the container
   241  		if forceKill {
   242  			logrus.Debugf("Forcibly terminating container %s", c.ID)
   243  			if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
   244  				logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err)
   245  			}
   246  		} else {
   247  			logrus.Debugf("Shutting down container %s", c.ID)
   248  			if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
   249  				if errno != hcsshim.Win32SystemShutdownIsInProgress &&
   250  					errno != hcsshim.Win32SpecifiedPathInvalid &&
   251  					errno != hcsshim.Win32SystemCannotFindThePathSpecified {
   252  					logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err)
   253  				}
   254  			}
   255  		}
   256  	}()
   257  
   258  	createProcessParms := hcsshim.CreateProcessParams{
   259  		EmulateConsole:   c.ProcessConfig.Tty,
   260  		WorkingDirectory: c.WorkingDir,
   261  		ConsoleSize:      c.ProcessConfig.ConsoleSize,
   262  	}
   263  
   264  	// Configure the environment for the process
   265  	createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
   266  
   267  	createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
   268  
   269  	if err != nil {
   270  		return execdriver.ExitStatus{ExitCode: -1}, err
   271  	}
   272  
   273  	// Start the command running in the container.
   274  	pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
   275  	if err != nil {
   276  		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
   277  		return execdriver.ExitStatus{ExitCode: -1}, err
   278  	}
   279  
   280  	// Now that the process has been launched, begin copying data to and from
   281  	// the named pipes for the std handles.
   282  	setupPipes(stdin, stdout, stderr, pipes)
   283  
   284  	//Save the PID as we'll need this in Kill()
   285  	logrus.Debugf("PID %d", pid)
   286  	c.ContainerPid = int(pid)
   287  
   288  	if c.ProcessConfig.Tty {
   289  		term = NewTtyConsole(c.ID, pid)
   290  	} else {
   291  		term = NewStdConsole()
   292  	}
   293  	c.ProcessConfig.Terminal = term
   294  
   295  	// Maintain our list of active containers. We'll need this later for exec
   296  	// and other commands.
   297  	d.Lock()
   298  	d.activeContainers[c.ID] = &activeContainer{
   299  		command: c,
   300  	}
   301  	d.Unlock()
   302  
   303  	if hooks.Start != nil {
   304  		// A closed channel for OOM is returned here as it will be
   305  		// non-blocking and return the correct result when read.
   306  		chOOM := make(chan struct{})
   307  		close(chOOM)
   308  		hooks.Start(&c.ProcessConfig, int(pid), chOOM)
   309  	}
   310  
   311  	var (
   312  		exitCode int32
   313  		errno    uint32
   314  	)
   315  	exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite)
   316  	if err != nil {
   317  		if errno != hcsshim.Win32PipeHasBeenEnded {
   318  			logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
   319  		}
   320  		// Do NOT return err here as the container would have
   321  		// started, otherwise docker will deadlock. It's perfectly legitimate
   322  		// for WaitForProcessInComputeSystem to fail in situations such
   323  		// as the container being killed on another thread.
   324  		return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil
   325  	}
   326  
   327  	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
   328  	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
   329  }
   330  
   331  // SupportsHooks implements the execdriver Driver interface.
   332  // The windows driver does not support the hook mechanism
   333  func (d *Driver) SupportsHooks() bool {
   334  	return false
   335  }