github.com/ojongerius/docker@v1.11.2/libcontainerd/client_windows.go (about) 1 package libcontainerd 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "syscall" 13 "time" 14 15 "github.com/Microsoft/hcsshim" 16 "github.com/Sirupsen/logrus" 17 ) 18 19 type client struct { 20 clientCommon 21 22 // Platform specific properties below here (none presently on Windows) 23 } 24 25 // defaultContainerNAT is the default name of the container NAT device that is 26 // preconfigured on the server. TODO Windows - Remove for TP5 support as not needed. 27 const defaultContainerNAT = "ContainerNAT" 28 29 // Win32 error codes that are used for various workarounds 30 // These really should be ALL_CAPS to match golangs syscall library and standard 31 // Win32 error conventions, but golint insists on CamelCase. 32 const ( 33 CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string 34 ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started 35 ErrorBadPathname = syscall.Errno(161) // The specified path is invalid 36 ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object 37 ) 38 39 type layer struct { 40 ID string 41 Path string 42 } 43 44 type defConfig struct { 45 DefFile string 46 } 47 48 type portBinding struct { 49 Protocol string 50 InternalPort int 51 ExternalPort int 52 } 53 54 type natSettings struct { 55 Name string 56 PortBindings []portBinding 57 } 58 59 type networkConnection struct { 60 NetworkName string 61 Nat natSettings 62 } 63 type networkSettings struct { 64 MacAddress string 65 } 66 67 type device struct { 68 DeviceType string 69 Connection interface{} 70 Settings interface{} 71 } 72 73 type mappedDir struct { 74 HostPath string 75 ContainerPath string 76 ReadOnly bool 77 } 78 79 // TODO Windows: @darrenstahlmsft Add ProcessorCount 80 type containerInit struct { 81 SystemType string // HCS requires this to be hard-coded to "Container" 82 Name string // Name of the container. We use the docker ID. 83 Owner string // The management platform that created this container 84 IsDummy bool // Used for development purposes. 85 VolumePath string // Windows volume path for scratch space 86 Devices []device // Devices used by the container 87 IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows 88 LayerFolderPath string // Where the layer folders are located 89 Layers []layer // List of storage layers 90 ProcessorWeight uint64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default. 91 ProcessorMaximum int64 `json:",omitempty"` // CPU maximum usage percent 1..100 92 StorageIOPSMaximum uint64 `json:",omitempty"` // Maximum Storage IOPS 93 StorageBandwidthMaximum uint64 `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second 94 StorageSandboxSize uint64 `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller 95 MemoryMaximumInMB int64 `json:",omitempty"` // Maximum memory available to the container in Megabytes 96 HostName string // Hostname 97 MappedDirectories []mappedDir // List of mapped directories (volumes/mounts) 98 SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers) 99 HvPartition bool // True if it a Hyper-V Container 100 EndpointList []string // List of networking endpoints to be attached to container 101 } 102 103 // defaultOwner is a tag passed to HCS to allow it to differentiate between 104 // container creator management stacks. We hard code "docker" in the case 105 // of docker. 106 const defaultOwner = "docker" 107 108 // Create is the entrypoint to create a container from a spec, and if successfully 109 // created, start it too. 110 func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) error { 111 logrus.Debugln("LCD client.Create() with spec", spec) 112 113 cu := &containerInit{ 114 SystemType: "Container", 115 Name: containerID, 116 Owner: defaultOwner, 117 118 VolumePath: spec.Root.Path, 119 IgnoreFlushesDuringBoot: spec.Windows.FirstStart, 120 LayerFolderPath: spec.Windows.LayerFolder, 121 HostName: spec.Hostname, 122 } 123 124 if spec.Windows.Networking != nil { 125 cu.EndpointList = spec.Windows.Networking.EndpointList 126 } 127 128 if spec.Windows.Resources != nil { 129 if spec.Windows.Resources.CPU != nil { 130 if spec.Windows.Resources.CPU.Shares != nil { 131 cu.ProcessorWeight = *spec.Windows.Resources.CPU.Shares 132 } 133 if spec.Windows.Resources.CPU.Percent != nil { 134 cu.ProcessorMaximum = *spec.Windows.Resources.CPU.Percent * 100 // ProcessorMaximum is a value between 1 and 10000 135 } 136 } 137 if spec.Windows.Resources.Memory != nil { 138 if spec.Windows.Resources.Memory.Limit != nil { 139 cu.MemoryMaximumInMB = *spec.Windows.Resources.Memory.Limit / 1024 / 1024 140 } 141 } 142 if spec.Windows.Resources.Storage != nil { 143 if spec.Windows.Resources.Storage.Bps != nil { 144 cu.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps 145 } 146 if spec.Windows.Resources.Storage.Iops != nil { 147 cu.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops 148 } 149 if spec.Windows.Resources.Storage.SandboxSize != nil { 150 cu.StorageSandboxSize = *spec.Windows.Resources.Storage.SandboxSize 151 } 152 } 153 } 154 155 cu.HvPartition = (spec.Windows.HvRuntime != nil) 156 157 // TODO Windows @jhowardmsft. FIXME post TP5. 158 // if spec.Windows.HvRuntime != nil { 159 // if spec.WIndows.HVRuntime.ImagePath != "" { 160 // cu.TBD = spec.Windows.HvRuntime.ImagePath 161 // } 162 // } 163 164 if cu.HvPartition { 165 cu.SandboxPath = filepath.Dir(spec.Windows.LayerFolder) 166 } else { 167 cu.VolumePath = spec.Root.Path 168 cu.LayerFolderPath = spec.Windows.LayerFolder 169 } 170 171 for _, layerPath := range spec.Windows.LayerPaths { 172 _, filename := filepath.Split(layerPath) 173 g, err := hcsshim.NameToGuid(filename) 174 if err != nil { 175 return err 176 } 177 cu.Layers = append(cu.Layers, layer{ 178 ID: g.ToString(), 179 Path: layerPath, 180 }) 181 } 182 183 // Add the mounts (volumes, bind mounts etc) to the structure 184 mds := make([]mappedDir, len(spec.Mounts)) 185 for i, mount := range spec.Mounts { 186 mds[i] = mappedDir{ 187 HostPath: mount.Source, 188 ContainerPath: mount.Destination, 189 ReadOnly: mount.Readonly} 190 } 191 cu.MappedDirectories = mds 192 193 // TODO Windows: vv START OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED 194 if hcsshim.IsTP4() && 195 spec.Windows.Networking != nil && 196 spec.Windows.Networking.Bridge != "" { 197 // Enumerate through the port bindings specified by the user and convert 198 // them into the internal structure matching the JSON blob that can be 199 // understood by the HCS. 200 var pbs []portBinding 201 for i, v := range spec.Windows.Networking.PortBindings { 202 proto := strings.ToUpper(i.Proto()) 203 if proto != "TCP" && proto != "UDP" { 204 return fmt.Errorf("invalid protocol %s", i.Proto()) 205 } 206 207 if len(v) > 1 { 208 return fmt.Errorf("Windows does not support more than one host port in NAT settings") 209 } 210 211 for _, v2 := range v { 212 var ( 213 iPort, ePort int 214 err error 215 ) 216 if len(v2.HostIP) != 0 { 217 return fmt.Errorf("Windows does not support host IP addresses in NAT settings") 218 } 219 if ePort, err = strconv.Atoi(v2.HostPort); err != nil { 220 return fmt.Errorf("invalid container port %s: %s", v2.HostPort, err) 221 } 222 if iPort, err = strconv.Atoi(i.Port()); err != nil { 223 return fmt.Errorf("invalid internal port %s: %s", i.Port(), err) 224 } 225 if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 { 226 return fmt.Errorf("specified NAT port is not in allowed range") 227 } 228 pbs = append(pbs, 229 portBinding{ExternalPort: ePort, 230 InternalPort: iPort, 231 Protocol: proto}) 232 } 233 } 234 235 dev := device{ 236 DeviceType: "Network", 237 Connection: &networkConnection{ 238 NetworkName: spec.Windows.Networking.Bridge, 239 Nat: natSettings{ 240 Name: defaultContainerNAT, 241 PortBindings: pbs, 242 }, 243 }, 244 } 245 246 if spec.Windows.Networking.MacAddress != "" { 247 windowsStyleMAC := strings.Replace( 248 spec.Windows.Networking.MacAddress, ":", "-", -1) 249 dev.Settings = networkSettings{ 250 MacAddress: windowsStyleMAC, 251 } 252 } 253 cu.Devices = append(cu.Devices, dev) 254 } else { 255 logrus.Debugln("No network interface") 256 } 257 // TODO Windows: ^^ END OF TP4 BLOCK OF CODE. REMOVE ONCE TP4 IS NO LONGER SUPPORTED 258 259 configurationb, err := json.Marshal(cu) 260 if err != nil { 261 return err 262 } 263 264 configuration := string(configurationb) 265 266 // TODO Windows TP5 timeframe. Remove when TP4 is no longer supported. 267 // The following a workaround for Windows TP4 which has a networking 268 // bug which fairly frequently returns an error. Back off and retry. 269 if !hcsshim.IsTP4() { 270 if err := hcsshim.CreateComputeSystem(containerID, configuration); err != nil { 271 return err 272 } 273 } else { 274 maxAttempts := 5 275 for i := 1; i <= maxAttempts; i++ { 276 err = hcsshim.CreateComputeSystem(containerID, configuration) 277 if err == nil { 278 break 279 } 280 281 if herr, ok := err.(*hcsshim.HcsError); ok { 282 if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found 283 herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified 284 herr.Err != ErrorNoNetwork && // The network is not present or not started 285 herr.Err != ErrorBadPathname && // The specified path is invalid 286 herr.Err != CoEClassstring && // Invalid class string 287 herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object 288 logrus.Debugln("Failed to create temporary container ", err) 289 return err 290 } 291 logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1) 292 time.Sleep(50 * time.Millisecond) 293 } 294 } 295 } 296 297 // Construct a container object for calling start on it. 298 container := &container{ 299 containerCommon: containerCommon{ 300 process: process{ 301 processCommon: processCommon{ 302 containerID: containerID, 303 client: clnt, 304 friendlyName: InitFriendlyName, 305 }, 306 commandLine: strings.Join(spec.Process.Args, " "), 307 }, 308 processes: make(map[string]*process), 309 }, 310 ociSpec: spec, 311 } 312 313 container.options = options 314 for _, option := range options { 315 if err := option.Apply(container); err != nil { 316 logrus.Error(err) 317 } 318 } 319 320 // Call start, and if it fails, delete the container from our 321 // internal structure, and also keep HCS in sync by deleting the 322 // container there. 323 logrus.Debugf("Create() id=%s, Calling start()", containerID) 324 if err := container.start(); err != nil { 325 clnt.deleteContainer(containerID) 326 return err 327 } 328 329 logrus.Debugf("Create() id=%s completed successfully", containerID) 330 return nil 331 332 } 333 334 // AddProcess is the handler for adding a process to an already running 335 // container. It's called through docker exec. 336 func (clnt *client) AddProcess(containerID, processFriendlyName string, procToAdd Process) error { 337 338 clnt.lock(containerID) 339 defer clnt.unlock(containerID) 340 container, err := clnt.getContainer(containerID) 341 if err != nil { 342 return err 343 } 344 345 createProcessParms := hcsshim.CreateProcessParams{ 346 EmulateConsole: procToAdd.Terminal, 347 ConsoleSize: procToAdd.InitialConsoleSize, 348 } 349 350 // Take working directory from the process to add if it is defined, 351 // otherwise take from the first process. 352 if procToAdd.Cwd != "" { 353 createProcessParms.WorkingDirectory = procToAdd.Cwd 354 } else { 355 createProcessParms.WorkingDirectory = container.ociSpec.Process.Cwd 356 } 357 358 // Configure the environment for the process 359 createProcessParms.Environment = setupEnvironmentVariables(procToAdd.Env) 360 createProcessParms.CommandLine = strings.Join(procToAdd.Args, " ") 361 362 logrus.Debugf("commandLine: %s", createProcessParms.CommandLine) 363 364 // Start the command running in the container. Note we always tell HCS to 365 // create stdout as it's required regardless of '-i' or '-t' options, so that 366 // docker can always grab the output through logs. We also tell HCS to always 367 // create stdin, even if it's not used - it will be closed shortly. Stderr 368 // is only created if it we're not -t. 369 var stdout, stderr io.ReadCloser 370 var pid uint32 371 iopipe := &IOPipe{Terminal: procToAdd.Terminal} 372 pid, iopipe.Stdin, stdout, stderr, err = hcsshim.CreateProcessInComputeSystem( 373 containerID, 374 true, 375 true, 376 !procToAdd.Terminal, 377 createProcessParms) 378 if err != nil { 379 logrus.Errorf("AddProcess %s CreateProcessInComputeSystem() failed %s", containerID, err) 380 return err 381 } 382 383 // Convert io.ReadClosers to io.Readers 384 if stdout != nil { 385 iopipe.Stdout = openReaderFromPipe(stdout) 386 } 387 if stderr != nil { 388 iopipe.Stderr = openReaderFromPipe(stderr) 389 } 390 391 // Add the process to the containers list of processes 392 container.processes[processFriendlyName] = 393 &process{ 394 processCommon: processCommon{ 395 containerID: containerID, 396 friendlyName: processFriendlyName, 397 client: clnt, 398 systemPid: pid, 399 }, 400 commandLine: createProcessParms.CommandLine, 401 } 402 403 // Make sure the lock is not held while calling back into the daemon 404 clnt.unlock(containerID) 405 406 // Tell the engine to attach streams back to the client 407 if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { 408 return err 409 } 410 411 // Lock again so that the defer unlock doesn't fail. (I really don't like this code) 412 clnt.lock(containerID) 413 414 // Spin up a go routine waiting for exit to handle cleanup 415 go container.waitExit(pid, processFriendlyName, false) 416 417 return nil 418 } 419 420 // Signal handles `docker stop` on Windows. While Linux has support for 421 // the full range of signals, signals aren't really implemented on Windows. 422 // We fake supporting regular stop and -9 to force kill. 423 func (clnt *client) Signal(containerID string, sig int) error { 424 var ( 425 cont *container 426 err error 427 ) 428 429 // Get the container as we need it to find the pid of the process. 430 clnt.lock(containerID) 431 defer clnt.unlock(containerID) 432 if cont, err = clnt.getContainer(containerID); err != nil { 433 return err 434 } 435 436 logrus.Debugf("lcd: Signal() containerID=%s sig=%d pid=%d", containerID, sig, cont.systemPid) 437 context := fmt.Sprintf("Signal: sig=%d pid=%d", sig, cont.systemPid) 438 439 if syscall.Signal(sig) == syscall.SIGKILL { 440 // Terminate the compute system 441 if err := hcsshim.TerminateComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil { 442 logrus.Errorf("Failed to terminate %s - %q", containerID, err) 443 } 444 445 } else { 446 // Terminate Process 447 if err = hcsshim.TerminateProcessInComputeSystem(containerID, cont.systemPid); err != nil { 448 logrus.Warnf("Failed to terminate pid %d in %s: %q", cont.systemPid, containerID, err) 449 // Ignore errors 450 err = nil 451 } 452 453 // Shutdown the compute system 454 if err := hcsshim.ShutdownComputeSystem(containerID, hcsshim.TimeoutInfinite, context); err != nil { 455 logrus.Errorf("Failed to shutdown %s - %q", containerID, err) 456 } 457 } 458 return nil 459 } 460 461 // Resize handles a CLI event to resize an interactive docker run or docker exec 462 // window. 463 func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { 464 // Get the libcontainerd container object 465 clnt.lock(containerID) 466 defer clnt.unlock(containerID) 467 cont, err := clnt.getContainer(containerID) 468 if err != nil { 469 return err 470 } 471 472 if processFriendlyName == InitFriendlyName { 473 logrus.Debugln("Resizing systemPID in", containerID, cont.process.systemPid) 474 return hcsshim.ResizeConsoleInComputeSystem(containerID, cont.process.systemPid, height, width) 475 } 476 477 for _, p := range cont.processes { 478 if p.friendlyName == processFriendlyName { 479 logrus.Debugln("Resizing exec'd process", containerID, p.systemPid) 480 return hcsshim.ResizeConsoleInComputeSystem(containerID, p.systemPid, height, width) 481 } 482 } 483 484 return fmt.Errorf("Resize could not find containerID %s to resize", containerID) 485 486 } 487 488 // Pause handles pause requests for containers 489 func (clnt *client) Pause(containerID string) error { 490 return errors.New("Windows: Containers cannot be paused") 491 } 492 493 // Resume handles resume requests for containers 494 func (clnt *client) Resume(containerID string) error { 495 return errors.New("Windows: Containers cannot be paused") 496 } 497 498 // Stats handles stats requests for containers 499 func (clnt *client) Stats(containerID string) (*Stats, error) { 500 return nil, errors.New("Windows: Stats not implemented") 501 } 502 503 // Restore is the handler for restoring a container 504 func (clnt *client) Restore(containerID string, unusedOnWindows ...CreateOption) error { 505 // TODO Windows: Implement this. For now, just tell the backend the container exited. 506 logrus.Debugf("lcd Restore %s", containerID) 507 return clnt.backend.StateChanged(containerID, StateInfo{ 508 State: StateExit, 509 ExitCode: 1 << 31, 510 }) 511 } 512 513 // GetPidsForContainer returns a list of process IDs running in a container. 514 // Although implemented, this is not used in Windows. 515 func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { 516 var pids []int 517 clnt.lock(containerID) 518 defer clnt.unlock(containerID) 519 cont, err := clnt.getContainer(containerID) 520 if err != nil { 521 return nil, err 522 } 523 524 // Add the first process 525 pids = append(pids, int(cont.containerCommon.systemPid)) 526 // And add all the exec'd processes 527 for _, p := range cont.processes { 528 pids = append(pids, int(p.processCommon.systemPid)) 529 } 530 return pids, nil 531 } 532 533 // Summary returns a summary of the processes running in a container. 534 // This is present in Windows to support docker top. In linux, the 535 // engine shells out to ps to get process information. On Windows, as 536 // the containers could be Hyper-V containers, they would not be 537 // visible on the container host. However, libcontainerd does have 538 // that information. 539 func (clnt *client) Summary(containerID string) ([]Summary, error) { 540 var s []Summary 541 clnt.lock(containerID) 542 defer clnt.unlock(containerID) 543 cont, err := clnt.getContainer(containerID) 544 if err != nil { 545 return nil, err 546 } 547 548 // Add the first process 549 s = append(s, Summary{ 550 Pid: cont.containerCommon.systemPid, 551 Command: cont.ociSpec.Process.Args[0]}) 552 // And add all the exec'd processes 553 for _, p := range cont.processes { 554 s = append(s, Summary{ 555 Pid: p.processCommon.systemPid, 556 Command: p.commandLine}) 557 } 558 return s, nil 559 560 } 561 562 // UpdateResources updates resources for a running container. 563 func (clnt *client) UpdateResources(containerID string, resources Resources) error { 564 // Updating resource isn't supported on Windows 565 // but we should return nil for enabling updating container 566 return nil 567 }