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