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 }