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