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 }