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 }