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 }