github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/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 // Work out the isolation (whether it is a hypervisor partition) 108 if c.Isolation.IsDefault() { 109 // Not specified by caller. Take daemon default 110 cu.HvPartition = defaultIsolation.IsHyperV() 111 } else { 112 // Take value specified by caller 113 cu.HvPartition = c.Isolation.IsHyperV() 114 } 115 116 if cu.HvPartition { 117 cu.SandboxPath = filepath.Dir(c.LayerFolder) 118 } else { 119 cu.VolumePath = c.Rootfs 120 cu.LayerFolderPath = c.LayerFolder 121 } 122 123 for _, layerPath := range c.LayerPaths { 124 _, filename := filepath.Split(layerPath) 125 g, err := hcsshim.NameToGuid(filename) 126 if err != nil { 127 return execdriver.ExitStatus{ExitCode: -1}, err 128 } 129 cu.Layers = append(cu.Layers, layer{ 130 ID: g.ToString(), 131 Path: layerPath, 132 }) 133 } 134 135 // Add the mounts (volumes, bind mounts etc) to the structure 136 mds := make([]mappedDir, len(c.Mounts)) 137 for i, mount := range c.Mounts { 138 mds[i] = mappedDir{ 139 HostPath: mount.Source, 140 ContainerPath: mount.Destination, 141 ReadOnly: !mount.Writable} 142 } 143 cu.MappedDirectories = mds 144 145 // TODO Windows. At some point, when there is CLI on docker run to 146 // enable the IP Address of the container to be passed into docker run, 147 // the IP Address needs to be wired through to HCS in the JSON. It 148 // would be present in c.Network.Interface.IPAddress. See matching 149 // TODO in daemon\container_windows.go, function populateCommand. 150 151 if c.Network.Interface != nil { 152 153 var pbs []portBinding 154 155 // Enumerate through the port bindings specified by the user and convert 156 // them into the internal structure matching the JSON blob that can be 157 // understood by the HCS. 158 for i, v := range c.Network.Interface.PortBindings { 159 proto := strings.ToUpper(i.Proto()) 160 if proto != "TCP" && proto != "UDP" { 161 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto()) 162 } 163 164 if len(v) > 1 { 165 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings") 166 } 167 168 for _, v2 := range v { 169 var ( 170 iPort, ePort int 171 err error 172 ) 173 if len(v2.HostIP) != 0 { 174 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings") 175 } 176 if ePort, err = strconv.Atoi(v2.HostPort); err != nil { 177 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) 178 } 179 if iPort, err = strconv.Atoi(i.Port()); err != nil { 180 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err) 181 } 182 if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { 183 return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range") 184 } 185 pbs = append(pbs, 186 portBinding{ExternalPort: ePort, 187 InternalPort: iPort, 188 Protocol: proto}) 189 } 190 } 191 192 // TODO Windows: TP3 workaround. Allow the user to override the name of 193 // the Container NAT device through an environment variable. This will 194 // ultimately be a global daemon parameter on Windows, similar to -b 195 // for the name of the virtual switch (aka bridge). 196 cn := os.Getenv("DOCKER_CONTAINER_NAT") 197 if len(cn) == 0 { 198 cn = defaultContainerNAT 199 } 200 201 dev := device{ 202 DeviceType: "Network", 203 Connection: &networkConnection{ 204 NetworkName: c.Network.Interface.Bridge, 205 // TODO Windows: Fixme, next line. Needs HCS fix. 206 EnableNat: false, 207 Nat: natSettings{ 208 Name: cn, 209 PortBindings: pbs, 210 }, 211 }, 212 } 213 214 if c.Network.Interface.MacAddress != "" { 215 windowsStyleMAC := strings.Replace( 216 c.Network.Interface.MacAddress, ":", "-", -1) 217 dev.Settings = networkSettings{ 218 MacAddress: windowsStyleMAC, 219 } 220 } 221 cu.Devices = append(cu.Devices, dev) 222 } else { 223 logrus.Debugln("No network interface") 224 } 225 226 configurationb, err := json.Marshal(cu) 227 if err != nil { 228 return execdriver.ExitStatus{ExitCode: -1}, err 229 } 230 231 configuration := string(configurationb) 232 233 err = hcsshim.CreateComputeSystem(c.ID, configuration) 234 if err != nil { 235 logrus.Debugln("Failed to create temporary container ", err) 236 return execdriver.ExitStatus{ExitCode: -1}, err 237 } 238 239 // Start the container 240 logrus.Debugln("Starting container ", c.ID) 241 err = hcsshim.StartComputeSystem(c.ID) 242 if err != nil { 243 logrus.Errorf("Failed to start compute system: %s", err) 244 return execdriver.ExitStatus{ExitCode: -1}, err 245 } 246 defer func() { 247 // Stop the container 248 if forceKill { 249 logrus.Debugf("Forcibly terminating container %s", c.ID) 250 if errno, err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { 251 logrus.Warnf("Ignoring error from TerminateComputeSystem 0x%X %s", errno, err) 252 } 253 } else { 254 logrus.Debugf("Shutting down container %s", c.ID) 255 if errno, err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil { 256 if errno != hcsshim.Win32SystemShutdownIsInProgress && 257 errno != hcsshim.Win32SpecifiedPathInvalid && 258 errno != hcsshim.Win32SystemCannotFindThePathSpecified { 259 logrus.Warnf("Ignoring error from ShutdownComputeSystem 0x%X %s", errno, err) 260 } 261 } 262 } 263 }() 264 265 createProcessParms := hcsshim.CreateProcessParams{ 266 EmulateConsole: c.ProcessConfig.Tty, 267 WorkingDirectory: c.WorkingDir, 268 ConsoleSize: c.ProcessConfig.ConsoleSize, 269 } 270 271 // Configure the environment for the process 272 createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env) 273 274 createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped) 275 276 if err != nil { 277 return execdriver.ExitStatus{ExitCode: -1}, err 278 } 279 280 // Start the command running in the container. 281 pid, stdin, stdout, stderr, _, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms) 282 if err != nil { 283 logrus.Errorf("CreateProcessInComputeSystem() failed %s", err) 284 return execdriver.ExitStatus{ExitCode: -1}, err 285 } 286 287 // Now that the process has been launched, begin copying data to and from 288 // the named pipes for the std handles. 289 setupPipes(stdin, stdout, stderr, pipes) 290 291 //Save the PID as we'll need this in Kill() 292 logrus.Debugf("PID %d", pid) 293 c.ContainerPid = int(pid) 294 295 if c.ProcessConfig.Tty { 296 term = NewTtyConsole(c.ID, pid) 297 } else { 298 term = NewStdConsole() 299 } 300 c.ProcessConfig.Terminal = term 301 302 // Maintain our list of active containers. We'll need this later for exec 303 // and other commands. 304 d.Lock() 305 d.activeContainers[c.ID] = &activeContainer{ 306 command: c, 307 } 308 d.Unlock() 309 310 if hooks.Start != nil { 311 // A closed channel for OOM is returned here as it will be 312 // non-blocking and return the correct result when read. 313 chOOM := make(chan struct{}) 314 close(chOOM) 315 hooks.Start(&c.ProcessConfig, int(pid), chOOM) 316 } 317 318 var ( 319 exitCode int32 320 errno uint32 321 ) 322 exitCode, errno, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite) 323 if err != nil { 324 if errno != hcsshim.Win32PipeHasBeenEnded { 325 logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err) 326 } 327 // Do NOT return err here as the container would have 328 // started, otherwise docker will deadlock. It's perfectly legitimate 329 // for WaitForProcessInComputeSystem to fail in situations such 330 // as the container being killed on another thread. 331 return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil 332 } 333 334 logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID) 335 return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil 336 } 337 338 // SupportsHooks implements the execdriver Driver interface. 339 // The windows driver does not support the hook mechanism 340 func (d *Driver) SupportsHooks() bool { 341 return false 342 }