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