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