Signed-off-by: Derek McGowan <derek@mcg.dev>
| ... | ... |
@@ -16,8 +16,8 @@ import ( |
| 16 | 16 |
networktypes "github.com/docker/docker/api/types/network" |
| 17 | 17 |
"github.com/docker/docker/daemon/config" |
| 18 | 18 |
"github.com/docker/docker/daemon/container" |
| 19 |
+ "github.com/docker/docker/daemon/internal/libcontainerd/local" |
|
| 19 | 20 |
"github.com/docker/docker/daemon/network" |
| 20 |
- "github.com/docker/docker/libcontainerd/local" |
|
| 21 | 21 |
"github.com/docker/docker/libcontainerd/remote" |
| 22 | 22 |
"github.com/docker/docker/libnetwork" |
| 23 | 23 |
nwconfig "github.com/docker/docker/libnetwork/config" |
| 24 | 24 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1225 @@ |
| 0 |
+package local |
|
| 1 |
+ |
|
| 2 |
+// This package contains the legacy in-proc calls in HCS using the v1 schema |
|
| 3 |
+// for Windows runtime purposes. |
|
| 4 |
+ |
|
| 5 |
+import ( |
|
| 6 |
+ "context" |
|
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 9 |
+ "os" |
|
| 10 |
+ "path/filepath" |
|
| 11 |
+ "regexp" |
|
| 12 |
+ "runtime" |
|
| 13 |
+ "strings" |
|
| 14 |
+ "sync" |
|
| 15 |
+ "syscall" |
|
| 16 |
+ "time" |
|
| 17 |
+ |
|
| 18 |
+ "github.com/Microsoft/hcsshim" |
|
| 19 |
+ containerd "github.com/containerd/containerd/v2/client" |
|
| 20 |
+ "github.com/containerd/containerd/v2/pkg/cio" |
|
| 21 |
+ cerrdefs "github.com/containerd/errdefs" |
|
| 22 |
+ "github.com/containerd/log" |
|
| 23 |
+ "github.com/docker/docker/errdefs" |
|
| 24 |
+ "github.com/docker/docker/libcontainerd/queue" |
|
| 25 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 26 |
+ "github.com/docker/docker/pkg/system" |
|
| 27 |
+ "github.com/opencontainers/runtime-spec/specs-go" |
|
| 28 |
+ "github.com/pkg/errors" |
|
| 29 |
+ "golang.org/x/sys/windows" |
|
| 30 |
+ "google.golang.org/protobuf/types/known/timestamppb" |
|
| 31 |
+) |
|
| 32 |
+ |
|
| 33 |
+type process struct {
|
|
| 34 |
+ // mu guards the mutable fields of this struct. |
|
| 35 |
+ // |
|
| 36 |
+ // Always lock mu before ctr's mutex to prevent deadlocks. |
|
| 37 |
+ mu sync.Mutex |
|
| 38 |
+ id string // Invariants: immutable |
|
| 39 |
+ ctr *container // Invariants: immutable, ctr != nil |
|
| 40 |
+ hcsProcess hcsshim.Process // Is set to nil on process exit |
|
| 41 |
+ exited *containerd.ExitStatus // Valid iff waitCh is closed |
|
| 42 |
+ waitCh chan struct{}
|
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+type task struct {
|
|
| 46 |
+ process |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+type container struct {
|
|
| 50 |
+ mu sync.Mutex |
|
| 51 |
+ |
|
| 52 |
+ // The ociSpec is required, as client.Create() needs a spec, but can |
|
| 53 |
+ // be called from the RestartManager context which does not otherwise |
|
| 54 |
+ // have access to the Spec |
|
| 55 |
+ // |
|
| 56 |
+ // A container value with ociSpec == nil represents a container which |
|
| 57 |
+ // has been loaded with (*client).LoadContainer, and is ineligible to |
|
| 58 |
+ // be Start()ed. |
|
| 59 |
+ ociSpec *specs.Spec |
|
| 60 |
+ |
|
| 61 |
+ hcsContainer hcsshim.Container // Is set to nil on container delete |
|
| 62 |
+ isPaused bool |
|
| 63 |
+ |
|
| 64 |
+ client *client |
|
| 65 |
+ id string |
|
| 66 |
+ terminateInvoked bool |
|
| 67 |
+ |
|
| 68 |
+ // task is a reference to the current task for the container. As a |
|
| 69 |
+ // corollary, when task == nil the container has no current task: the |
|
| 70 |
+ // container was never Start()ed or the task was Delete()d. |
|
| 71 |
+ task *task |
|
| 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 |
+type client struct {
|
|
| 80 |
+ backend libcontainerdtypes.Backend |
|
| 81 |
+ logger *log.Entry |
|
| 82 |
+ eventQ queue.Queue |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+// NewClient creates a new local executor for windows |
|
| 86 |
+func NewClient(ctx context.Context, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 87 |
+ return &client{
|
|
| 88 |
+ backend: b, |
|
| 89 |
+ logger: log.G(ctx).WithField("module", "libcontainerd"),
|
|
| 90 |
+ }, nil |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 94 |
+ return containerd.Version{}, errors.New("not implemented on Windows")
|
|
| 95 |
+} |
|
| 96 |
+ |
|
| 97 |
+// NewContainer is the entrypoint to create a container from a spec. |
|
| 98 |
+// Table below shows the fields required for HCS JSON calling parameters, |
|
| 99 |
+// where if not populated, is omitted. |
|
| 100 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 101 |
+// | | Isolation=Process | Isolation=Hyper-V | |
|
| 102 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 103 |
+// | VolumePath | \\?\\Volume{GUIDa} | |
|
|
| 104 |
+// | LayerFolderPath | %root%\windowsfilter\containerID | | |
|
| 105 |
+// | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID | |
|
| 106 |
+// | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM | |
|
| 107 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 108 |
+// |
|
| 109 |
+// Isolation=Process example: |
|
| 110 |
+// |
|
| 111 |
+// {
|
|
| 112 |
+// "SystemType": "Container", |
|
| 113 |
+// "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 114 |
+// "Owner": "docker", |
|
| 115 |
+// "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
|
|
| 116 |
+// "IgnoreFlushesDuringBoot": true, |
|
| 117 |
+// "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 118 |
+// "Layers": [{
|
|
| 119 |
+// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 120 |
+// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 121 |
+// }], |
|
| 122 |
+// "HostName": "5e0055c814a6", |
|
| 123 |
+// "MappedDirectories": [], |
|
| 124 |
+// "HvPartition": false, |
|
| 125 |
+// "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"], |
|
| 126 |
+// } |
|
| 127 |
+// |
|
| 128 |
+// Isolation=Hyper-V example: |
|
| 129 |
+// |
|
| 130 |
+// {
|
|
| 131 |
+// "SystemType": "Container", |
|
| 132 |
+// "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d", |
|
| 133 |
+// "Owner": "docker", |
|
| 134 |
+// "IgnoreFlushesDuringBoot": true, |
|
| 135 |
+// "Layers": [{
|
|
| 136 |
+// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 137 |
+// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 138 |
+// }], |
|
| 139 |
+// "HostName": "475c2c58933b", |
|
| 140 |
+// "MappedDirectories": [], |
|
| 141 |
+// "HvPartition": true, |
|
| 142 |
+// "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"], |
|
| 143 |
+// "DNSSearchList": "a.com,b.com,c.com", |
|
| 144 |
+// "HvRuntime": {
|
|
| 145 |
+// "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM" |
|
| 146 |
+// }, |
|
| 147 |
+// } |
|
| 148 |
+func (c *client) NewContainer(_ context.Context, id string, spec *specs.Spec, _ string, _ interface{}, _ ...containerd.NewContainerOpts) (libcontainerdtypes.Container, error) {
|
|
| 149 |
+ if spec.Linux != nil {
|
|
| 150 |
+ return nil, errors.New("linux containers are not supported on this platform")
|
|
| 151 |
+ } |
|
| 152 |
+ ctr, err := c.createWindows(id, spec) |
|
| 153 |
+ if err != nil {
|
|
| 154 |
+ return nil, err |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 157 |
+ c.eventQ.Append(id, func() {
|
|
| 158 |
+ c.logger.WithFields(log.Fields{
|
|
| 159 |
+ "container": id, |
|
| 160 |
+ "event": libcontainerdtypes.EventCreate, |
|
| 161 |
+ }).Info("sending event")
|
|
| 162 |
+ |
|
| 163 |
+ err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, libcontainerdtypes.EventInfo{
|
|
| 164 |
+ ContainerID: id, |
|
| 165 |
+ }) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ c.logger.WithFields(log.Fields{
|
|
| 168 |
+ "container": id, |
|
| 169 |
+ "event": libcontainerdtypes.EventCreate, |
|
| 170 |
+ "error": err, |
|
| 171 |
+ }).Error("failed to process event")
|
|
| 172 |
+ } |
|
| 173 |
+ }) |
|
| 174 |
+ return ctr, nil |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+func (c *client) createWindows(id string, spec *specs.Spec) (*container, error) {
|
|
| 178 |
+ logger := c.logger.WithField("container", id)
|
|
| 179 |
+ configuration := &hcsshim.ContainerConfig{
|
|
| 180 |
+ SystemType: "Container", |
|
| 181 |
+ Name: id, |
|
| 182 |
+ Owner: defaultOwner, |
|
| 183 |
+ IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
|
| 184 |
+ HostName: spec.Hostname, |
|
| 185 |
+ HvPartition: false, |
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ c.extractResourcesFromSpec(spec, configuration) |
|
| 189 |
+ |
|
| 190 |
+ if spec.Windows.Resources != nil {
|
|
| 191 |
+ if spec.Windows.Resources.Storage != nil {
|
|
| 192 |
+ if spec.Windows.Resources.Storage.Bps != nil {
|
|
| 193 |
+ configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps |
|
| 194 |
+ } |
|
| 195 |
+ if spec.Windows.Resources.Storage.Iops != nil {
|
|
| 196 |
+ configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops |
|
| 197 |
+ } |
|
| 198 |
+ } |
|
| 199 |
+ } |
|
| 200 |
+ |
|
| 201 |
+ if spec.Windows.HyperV != nil {
|
|
| 202 |
+ configuration.HvPartition = true |
|
| 203 |
+ } |
|
| 204 |
+ |
|
| 205 |
+ if spec.Windows.Network != nil {
|
|
| 206 |
+ configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 207 |
+ configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 208 |
+ if spec.Windows.Network.DNSSearchList != nil {
|
|
| 209 |
+ configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 210 |
+ } |
|
| 211 |
+ configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
|
| 215 |
+ configuration.Credentials = cs |
|
| 216 |
+ } |
|
| 217 |
+ |
|
| 218 |
+ // We must have least two layers in the spec, the bottom one being a |
|
| 219 |
+ // base image, the top one being the RW layer. |
|
| 220 |
+ if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
|
| 221 |
+ return nil, fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
|
| 222 |
+ } |
|
| 223 |
+ |
|
| 224 |
+ // Strip off the top-most layer as that's passed in separately to HCS |
|
| 225 |
+ configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 226 |
+ layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 227 |
+ |
|
| 228 |
+ if configuration.HvPartition {
|
|
| 229 |
+ // We don't currently support setting the utility VM image explicitly. |
|
| 230 |
+ // TODO circa RS5, this may be re-locatable. |
|
| 231 |
+ if spec.Windows.HyperV.UtilityVMPath != "" {
|
|
| 232 |
+ return nil, errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
|
| 233 |
+ } |
|
| 234 |
+ |
|
| 235 |
+ // Find the upper-most utility VM image. |
|
| 236 |
+ var uvmImagePath string |
|
| 237 |
+ for _, path := range layerFolders {
|
|
| 238 |
+ fullPath := filepath.Join(path, "UtilityVM") |
|
| 239 |
+ _, err := os.Stat(fullPath) |
|
| 240 |
+ if err == nil {
|
|
| 241 |
+ uvmImagePath = fullPath |
|
| 242 |
+ break |
|
| 243 |
+ } |
|
| 244 |
+ if !os.IsNotExist(err) {
|
|
| 245 |
+ return nil, err |
|
| 246 |
+ } |
|
| 247 |
+ } |
|
| 248 |
+ if uvmImagePath == "" {
|
|
| 249 |
+ return nil, errors.New("utility VM image could not be found")
|
|
| 250 |
+ } |
|
| 251 |
+ configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
|
| 252 |
+ |
|
| 253 |
+ if spec.Root.Path != "" {
|
|
| 254 |
+ return nil, errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
|
| 255 |
+ } |
|
| 256 |
+ } else {
|
|
| 257 |
+ const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
|
|
| 258 |
+ if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
|
| 259 |
+ return nil, fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
|
| 260 |
+ } |
|
| 261 |
+ // HCS API requires the trailing backslash to be removed |
|
| 262 |
+ configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1] |
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ if spec.Root.Readonly {
|
|
| 266 |
+ return nil, errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`) |
|
| 267 |
+ } |
|
| 268 |
+ |
|
| 269 |
+ for _, layerPath := range layerFolders {
|
|
| 270 |
+ _, filename := filepath.Split(layerPath) |
|
| 271 |
+ g, err := hcsshim.NameToGuid(filename) |
|
| 272 |
+ if err != nil {
|
|
| 273 |
+ return nil, err |
|
| 274 |
+ } |
|
| 275 |
+ configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 276 |
+ ID: g.ToString(), |
|
| 277 |
+ Path: layerPath, |
|
| 278 |
+ }) |
|
| 279 |
+ } |
|
| 280 |
+ |
|
| 281 |
+ // Add the mounts (volumes, bind mounts etc) to the structure |
|
| 282 |
+ var mds []hcsshim.MappedDir |
|
| 283 |
+ var mps []hcsshim.MappedPipe |
|
| 284 |
+ for _, mount := range spec.Mounts {
|
|
| 285 |
+ const pipePrefix = `\\.\pipe\` |
|
| 286 |
+ if mount.Type != "" {
|
|
| 287 |
+ return nil, fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
|
| 288 |
+ } |
|
| 289 |
+ if strings.HasPrefix(mount.Destination, pipePrefix) {
|
|
| 290 |
+ mp := hcsshim.MappedPipe{
|
|
| 291 |
+ HostPath: mount.Source, |
|
| 292 |
+ ContainerPipeName: mount.Destination[len(pipePrefix):], |
|
| 293 |
+ } |
|
| 294 |
+ mps = append(mps, mp) |
|
| 295 |
+ } else {
|
|
| 296 |
+ md := hcsshim.MappedDir{
|
|
| 297 |
+ HostPath: mount.Source, |
|
| 298 |
+ ContainerPath: mount.Destination, |
|
| 299 |
+ ReadOnly: false, |
|
| 300 |
+ } |
|
| 301 |
+ for _, o := range mount.Options {
|
|
| 302 |
+ if strings.ToLower(o) == "ro" {
|
|
| 303 |
+ md.ReadOnly = true |
|
| 304 |
+ } |
|
| 305 |
+ } |
|
| 306 |
+ mds = append(mds, md) |
|
| 307 |
+ } |
|
| 308 |
+ } |
|
| 309 |
+ configuration.MappedDirectories = mds |
|
| 310 |
+ configuration.MappedPipes = mps |
|
| 311 |
+ |
|
| 312 |
+ if len(spec.Windows.Devices) > 0 {
|
|
| 313 |
+ // Add any device assignments |
|
| 314 |
+ if configuration.HvPartition {
|
|
| 315 |
+ return nil, errors.New("device assignment is not supported for HyperV containers")
|
|
| 316 |
+ } |
|
| 317 |
+ for _, d := range spec.Windows.Devices {
|
|
| 318 |
+ // Per https://github.com/microsoft/hcsshim/blob/v0.9.2/internal/uvm/virtual_device.go#L17-L18, |
|
| 319 |
+ // these represent an Interface Class GUID. |
|
| 320 |
+ if d.IDType != "class" && d.IDType != "vpci-class-guid" {
|
|
| 321 |
+ return nil, errors.Errorf("device assignment of type '%s' is not supported", d.IDType)
|
|
| 322 |
+ } |
|
| 323 |
+ configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
|
|
| 324 |
+ } |
|
| 325 |
+ } |
|
| 326 |
+ |
|
| 327 |
+ hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 328 |
+ if err != nil {
|
|
| 329 |
+ return nil, err |
|
| 330 |
+ } |
|
| 331 |
+ |
|
| 332 |
+ // Construct a container object for calling start on it. |
|
| 333 |
+ ctr := &container{
|
|
| 334 |
+ client: c, |
|
| 335 |
+ id: id, |
|
| 336 |
+ ociSpec: spec, |
|
| 337 |
+ hcsContainer: hcsContainer, |
|
| 338 |
+ } |
|
| 339 |
+ |
|
| 340 |
+ logger.Debug("starting container")
|
|
| 341 |
+ if err := ctr.hcsContainer.Start(); err != nil {
|
|
| 342 |
+ logger.WithError(err).Error("failed to start container")
|
|
| 343 |
+ ctr.mu.Lock() |
|
| 344 |
+ if err := ctr.terminateContainer(); err != nil {
|
|
| 345 |
+ logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 346 |
+ } else {
|
|
| 347 |
+ logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 348 |
+ } |
|
| 349 |
+ ctr.mu.Unlock() |
|
| 350 |
+ return nil, err |
|
| 351 |
+ } |
|
| 352 |
+ |
|
| 353 |
+ logger.Debug("createWindows() completed successfully")
|
|
| 354 |
+ return ctr, nil |
|
| 355 |
+} |
|
| 356 |
+ |
|
| 357 |
+func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
|
|
| 358 |
+ if spec.Windows.Resources == nil {
|
|
| 359 |
+ return |
|
| 360 |
+ } |
|
| 361 |
+ if spec.Windows.Resources.CPU != nil {
|
|
| 362 |
+ if spec.Windows.Resources.CPU.Count != nil {
|
|
| 363 |
+ // This check is being done here rather than in adaptContainerSettings |
|
| 364 |
+ // because we don't want to update the HostConfig in case this container |
|
| 365 |
+ // is moved to a host with more CPUs than this one. |
|
| 366 |
+ cpuCount := *spec.Windows.Resources.CPU.Count |
|
| 367 |
+ hostCPUCount := uint64(runtime.NumCPU()) |
|
| 368 |
+ if cpuCount > hostCPUCount {
|
|
| 369 |
+ c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
|
|
| 370 |
+ cpuCount = hostCPUCount |
|
| 371 |
+ } |
|
| 372 |
+ configuration.ProcessorCount = uint32(cpuCount) |
|
| 373 |
+ } |
|
| 374 |
+ if spec.Windows.Resources.CPU.Shares != nil {
|
|
| 375 |
+ configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares) |
|
| 376 |
+ } |
|
| 377 |
+ if spec.Windows.Resources.CPU.Maximum != nil {
|
|
| 378 |
+ configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum) |
|
| 379 |
+ } |
|
| 380 |
+ } |
|
| 381 |
+ if spec.Windows.Resources.Memory != nil {
|
|
| 382 |
+ if spec.Windows.Resources.Memory.Limit != nil {
|
|
| 383 |
+ configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024 |
|
| 384 |
+ } |
|
| 385 |
+ } |
|
| 386 |
+} |
|
| 387 |
+ |
|
| 388 |
+func (ctr *container) NewTask(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
|
|
| 389 |
+ ctr.mu.Lock() |
|
| 390 |
+ defer ctr.mu.Unlock() |
|
| 391 |
+ |
|
| 392 |
+ switch {
|
|
| 393 |
+ case ctr.ociSpec == nil: |
|
| 394 |
+ return nil, errors.WithStack(errdefs.NotImplemented(errors.New("a restored container cannot be started")))
|
|
| 395 |
+ case ctr.task != nil: |
|
| 396 |
+ return nil, errors.WithStack(errdefs.NotModified(cerrdefs.ErrAlreadyExists)) |
|
| 397 |
+ } |
|
| 398 |
+ |
|
| 399 |
+ logger := ctr.client.logger.WithField("container", ctr.id)
|
|
| 400 |
+ |
|
| 401 |
+ // Note we always tell HCS to create stdout as it's required |
|
| 402 |
+ // regardless of '-i' or '-t' options, so that docker can always grab |
|
| 403 |
+ // the output through logs. We also tell HCS to always create stdin, |
|
| 404 |
+ // even if it's not used - it will be closed shortly. Stderr is only |
|
| 405 |
+ // created if it we're not -t. |
|
| 406 |
+ var ( |
|
| 407 |
+ emulateConsole bool |
|
| 408 |
+ createStdErrPipe bool |
|
| 409 |
+ ) |
|
| 410 |
+ if ctr.ociSpec.Process != nil {
|
|
| 411 |
+ emulateConsole = ctr.ociSpec.Process.Terminal |
|
| 412 |
+ createStdErrPipe = !ctr.ociSpec.Process.Terminal |
|
| 413 |
+ } |
|
| 414 |
+ |
|
| 415 |
+ createProcessParms := &hcsshim.ProcessConfig{
|
|
| 416 |
+ EmulateConsole: emulateConsole, |
|
| 417 |
+ WorkingDirectory: ctr.ociSpec.Process.Cwd, |
|
| 418 |
+ CreateStdInPipe: true, |
|
| 419 |
+ CreateStdOutPipe: true, |
|
| 420 |
+ CreateStdErrPipe: createStdErrPipe, |
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 423 |
+ if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
|
| 424 |
+ createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 425 |
+ createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 426 |
+ } |
|
| 427 |
+ |
|
| 428 |
+ // Configure the environment for the process |
|
| 429 |
+ createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
|
| 430 |
+ |
|
| 431 |
+ // Configure the CommandLine/CommandArgs |
|
| 432 |
+ setCommandLineAndArgs(ctr.ociSpec.Process, createProcessParms) |
|
| 433 |
+ logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
|
|
| 434 |
+ |
|
| 435 |
+ createProcessParms.User = ctr.ociSpec.Process.User.Username |
|
| 436 |
+ |
|
| 437 |
+ // Start the command running in the container. |
|
| 438 |
+ newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) |
|
| 439 |
+ if err != nil {
|
|
| 440 |
+ logger.WithError(err).Error("CreateProcess() failed")
|
|
| 441 |
+ return nil, err |
|
| 442 |
+ } |
|
| 443 |
+ |
|
| 444 |
+ defer func() {
|
|
| 445 |
+ if retErr != nil {
|
|
| 446 |
+ if err := newProcess.Kill(); err != nil {
|
|
| 447 |
+ logger.WithError(err).Error("failed to kill process")
|
|
| 448 |
+ } |
|
| 449 |
+ go func() {
|
|
| 450 |
+ if err := newProcess.Wait(); err != nil {
|
|
| 451 |
+ logger.WithError(err).Error("failed to wait for process")
|
|
| 452 |
+ } |
|
| 453 |
+ if err := newProcess.Close(); err != nil {
|
|
| 454 |
+ logger.WithError(err).Error("failed to clean process resources")
|
|
| 455 |
+ } |
|
| 456 |
+ }() |
|
| 457 |
+ } |
|
| 458 |
+ }() |
|
| 459 |
+ |
|
| 460 |
+ pid := newProcess.Pid() |
|
| 461 |
+ logger.WithField("pid", pid).Debug("init process started")
|
|
| 462 |
+ |
|
| 463 |
+ dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal) |
|
| 464 |
+ if err != nil {
|
|
| 465 |
+ logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 466 |
+ return nil, err |
|
| 467 |
+ } |
|
| 468 |
+ _, err = attachStdio(dio) |
|
| 469 |
+ if err != nil {
|
|
| 470 |
+ logger.WithError(err).Error("failed to attach stdio")
|
|
| 471 |
+ return nil, err |
|
| 472 |
+ } |
|
| 473 |
+ |
|
| 474 |
+ t := &task{process{
|
|
| 475 |
+ id: ctr.id, |
|
| 476 |
+ ctr: ctr, |
|
| 477 |
+ hcsProcess: newProcess, |
|
| 478 |
+ waitCh: make(chan struct{}),
|
|
| 479 |
+ }} |
|
| 480 |
+ |
|
| 481 |
+ // All fallible operations have succeeded so it is now safe to set the |
|
| 482 |
+ // container's current task. |
|
| 483 |
+ ctr.task = t |
|
| 484 |
+ |
|
| 485 |
+ // Spin up a goroutine to notify the backend and clean up resources when |
|
| 486 |
+ // the task exits. Defer until after the start event is sent so that the |
|
| 487 |
+ // exit event is not sent out-of-order. |
|
| 488 |
+ defer func() { go t.reap() }()
|
|
| 489 |
+ |
|
| 490 |
+ // Generate the associated event |
|
| 491 |
+ ctr.client.eventQ.Append(ctr.id, func() {
|
|
| 492 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 493 |
+ ContainerID: ctr.id, |
|
| 494 |
+ ProcessID: t.id, |
|
| 495 |
+ Pid: uint32(pid), |
|
| 496 |
+ } |
|
| 497 |
+ ctr.client.logger.WithFields(log.Fields{
|
|
| 498 |
+ "container": ctr.id, |
|
| 499 |
+ "event": libcontainerdtypes.EventStart, |
|
| 500 |
+ "event-info": ei, |
|
| 501 |
+ }).Info("sending event")
|
|
| 502 |
+ err := ctr.client.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei) |
|
| 503 |
+ if err != nil {
|
|
| 504 |
+ ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 505 |
+ "container": ei.ContainerID, |
|
| 506 |
+ "event": libcontainerdtypes.EventStart, |
|
| 507 |
+ "event-info": ei, |
|
| 508 |
+ }).Error("failed to process event")
|
|
| 509 |
+ } |
|
| 510 |
+ }) |
|
| 511 |
+ logger.Debug("start() completed")
|
|
| 512 |
+ return t, nil |
|
| 513 |
+} |
|
| 514 |
+ |
|
| 515 |
+func (*task) Start(context.Context) error {
|
|
| 516 |
+ // No-op on Windows. |
|
| 517 |
+ return nil |
|
| 518 |
+} |
|
| 519 |
+ |
|
| 520 |
+func (ctr *container) Task(context.Context) (libcontainerdtypes.Task, error) {
|
|
| 521 |
+ ctr.mu.Lock() |
|
| 522 |
+ defer ctr.mu.Unlock() |
|
| 523 |
+ if ctr.task == nil {
|
|
| 524 |
+ return nil, errdefs.NotFound(cerrdefs.ErrNotFound) |
|
| 525 |
+ } |
|
| 526 |
+ return ctr.task, nil |
|
| 527 |
+} |
|
| 528 |
+ |
|
| 529 |
+// setCommandLineAndArgs configures the HCS ProcessConfig based on an OCI process spec |
|
| 530 |
+func setCommandLineAndArgs(process *specs.Process, createProcessParms *hcsshim.ProcessConfig) {
|
|
| 531 |
+ if process.CommandLine != "" {
|
|
| 532 |
+ createProcessParms.CommandLine = process.CommandLine |
|
| 533 |
+ } else {
|
|
| 534 |
+ createProcessParms.CommandLine = system.EscapeArgs(process.Args) |
|
| 535 |
+ } |
|
| 536 |
+} |
|
| 537 |
+ |
|
| 538 |
+func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
|
|
| 539 |
+ stdin, stdout, stderr, err := newProcess.Stdio() |
|
| 540 |
+ if err != nil {
|
|
| 541 |
+ return nil, err |
|
| 542 |
+ } |
|
| 543 |
+ |
|
| 544 |
+ dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal) |
|
| 545 |
+ |
|
| 546 |
+ // Convert io.ReadClosers to io.Readers |
|
| 547 |
+ if stdout != nil {
|
|
| 548 |
+ dio.Stdout = io.NopCloser(&autoClosingReader{ReadCloser: stdout})
|
|
| 549 |
+ } |
|
| 550 |
+ if stderr != nil {
|
|
| 551 |
+ dio.Stderr = io.NopCloser(&autoClosingReader{ReadCloser: stderr})
|
|
| 552 |
+ } |
|
| 553 |
+ return dio, nil |
|
| 554 |
+} |
|
| 555 |
+ |
|
| 556 |
+// Exec launches a process in a running container. |
|
| 557 |
+// |
|
| 558 |
+// The processID argument is entirely informational. As there is no mechanism |
|
| 559 |
+// (exposed through the libcontainerd interfaces) to enumerate or reference an |
|
| 560 |
+// exec'd process by ID, uniqueness is not currently enforced. |
|
| 561 |
+func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Process, retErr error) {
|
|
| 562 |
+ hcsContainer, err := t.getHCSContainer() |
|
| 563 |
+ if err != nil {
|
|
| 564 |
+ return nil, err |
|
| 565 |
+ } |
|
| 566 |
+ logger := t.ctr.client.logger.WithFields(log.Fields{
|
|
| 567 |
+ "container": t.ctr.id, |
|
| 568 |
+ "exec": processID, |
|
| 569 |
+ }) |
|
| 570 |
+ |
|
| 571 |
+ // Note we always tell HCS to |
|
| 572 |
+ // create stdout as it's required regardless of '-i' or '-t' options, so that |
|
| 573 |
+ // docker can always grab the output through logs. We also tell HCS to always |
|
| 574 |
+ // create stdin, even if it's not used - it will be closed shortly. Stderr |
|
| 575 |
+ // is only created if it we're not -t. |
|
| 576 |
+ createProcessParms := &hcsshim.ProcessConfig{
|
|
| 577 |
+ CreateStdInPipe: true, |
|
| 578 |
+ CreateStdOutPipe: true, |
|
| 579 |
+ CreateStdErrPipe: !spec.Terminal, |
|
| 580 |
+ } |
|
| 581 |
+ if spec.Terminal {
|
|
| 582 |
+ createProcessParms.EmulateConsole = true |
|
| 583 |
+ if spec.ConsoleSize != nil {
|
|
| 584 |
+ createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height) |
|
| 585 |
+ createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width) |
|
| 586 |
+ } |
|
| 587 |
+ } |
|
| 588 |
+ |
|
| 589 |
+ // Take working directory from the process to add if it is defined, |
|
| 590 |
+ // otherwise take from the first process. |
|
| 591 |
+ if spec.Cwd != "" {
|
|
| 592 |
+ createProcessParms.WorkingDirectory = spec.Cwd |
|
| 593 |
+ } else {
|
|
| 594 |
+ createProcessParms.WorkingDirectory = t.ctr.ociSpec.Process.Cwd |
|
| 595 |
+ } |
|
| 596 |
+ |
|
| 597 |
+ // Configure the environment for the process |
|
| 598 |
+ createProcessParms.Environment = setupEnvironmentVariables(spec.Env) |
|
| 599 |
+ |
|
| 600 |
+ // Configure the CommandLine/CommandArgs |
|
| 601 |
+ setCommandLineAndArgs(spec, createProcessParms) |
|
| 602 |
+ logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
|
|
| 603 |
+ |
|
| 604 |
+ createProcessParms.User = spec.User.Username |
|
| 605 |
+ |
|
| 606 |
+ // Start the command running in the container. |
|
| 607 |
+ newProcess, err := hcsContainer.CreateProcess(createProcessParms) |
|
| 608 |
+ if err != nil {
|
|
| 609 |
+ logger.WithError(err).Errorf("exec's CreateProcess() failed")
|
|
| 610 |
+ return nil, err |
|
| 611 |
+ } |
|
| 612 |
+ defer func() {
|
|
| 613 |
+ if retErr != nil {
|
|
| 614 |
+ if err := newProcess.Kill(); err != nil {
|
|
| 615 |
+ logger.WithError(err).Error("failed to kill process")
|
|
| 616 |
+ } |
|
| 617 |
+ go func() {
|
|
| 618 |
+ if err := newProcess.Wait(); err != nil {
|
|
| 619 |
+ logger.WithError(err).Error("failed to wait for process")
|
|
| 620 |
+ } |
|
| 621 |
+ if err := newProcess.Close(); err != nil {
|
|
| 622 |
+ logger.WithError(err).Error("failed to clean process resources")
|
|
| 623 |
+ } |
|
| 624 |
+ }() |
|
| 625 |
+ } |
|
| 626 |
+ }() |
|
| 627 |
+ |
|
| 628 |
+ dio, err := newIOFromProcess(newProcess, spec.Terminal) |
|
| 629 |
+ if err != nil {
|
|
| 630 |
+ logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 631 |
+ return nil, err |
|
| 632 |
+ } |
|
| 633 |
+ // Tell the engine to attach streams back to the client |
|
| 634 |
+ _, err = attachStdio(dio) |
|
| 635 |
+ if err != nil {
|
|
| 636 |
+ return nil, err |
|
| 637 |
+ } |
|
| 638 |
+ |
|
| 639 |
+ p := &process{
|
|
| 640 |
+ id: processID, |
|
| 641 |
+ ctr: t.ctr, |
|
| 642 |
+ hcsProcess: newProcess, |
|
| 643 |
+ waitCh: make(chan struct{}),
|
|
| 644 |
+ } |
|
| 645 |
+ |
|
| 646 |
+ // Spin up a goroutine to notify the backend and clean up resources when |
|
| 647 |
+ // the process exits. Defer until after the start event is sent so that |
|
| 648 |
+ // the exit event is not sent out-of-order. |
|
| 649 |
+ defer func() { go p.reap() }()
|
|
| 650 |
+ |
|
| 651 |
+ pid := newProcess.Pid() |
|
| 652 |
+ t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 653 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 654 |
+ ContainerID: t.ctr.id, |
|
| 655 |
+ ProcessID: p.id, |
|
| 656 |
+ Pid: uint32(pid), |
|
| 657 |
+ } |
|
| 658 |
+ t.ctr.client.logger.WithFields(log.Fields{
|
|
| 659 |
+ "container": t.ctr.id, |
|
| 660 |
+ "event": libcontainerdtypes.EventExecAdded, |
|
| 661 |
+ "event-info": ei, |
|
| 662 |
+ }).Info("sending event")
|
|
| 663 |
+ err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecAdded, ei) |
|
| 664 |
+ if err != nil {
|
|
| 665 |
+ t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 666 |
+ "container": t.ctr.id, |
|
| 667 |
+ "event": libcontainerdtypes.EventExecAdded, |
|
| 668 |
+ "event-info": ei, |
|
| 669 |
+ }).Error("failed to process event")
|
|
| 670 |
+ } |
|
| 671 |
+ err = t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecStarted, ei) |
|
| 672 |
+ if err != nil {
|
|
| 673 |
+ t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 674 |
+ "container": t.ctr.id, |
|
| 675 |
+ "event": libcontainerdtypes.EventExecStarted, |
|
| 676 |
+ "event-info": ei, |
|
| 677 |
+ }).Error("failed to process event")
|
|
| 678 |
+ } |
|
| 679 |
+ }) |
|
| 680 |
+ |
|
| 681 |
+ return p, nil |
|
| 682 |
+} |
|
| 683 |
+ |
|
| 684 |
+func (p *process) Pid() uint32 {
|
|
| 685 |
+ p.mu.Lock() |
|
| 686 |
+ hcsProcess := p.hcsProcess |
|
| 687 |
+ p.mu.Unlock() |
|
| 688 |
+ if hcsProcess == nil {
|
|
| 689 |
+ return 0 |
|
| 690 |
+ } |
|
| 691 |
+ return uint32(hcsProcess.Pid()) |
|
| 692 |
+} |
|
| 693 |
+ |
|
| 694 |
+func (p *process) Kill(_ context.Context, signal syscall.Signal) error {
|
|
| 695 |
+ p.mu.Lock() |
|
| 696 |
+ hcsProcess := p.hcsProcess |
|
| 697 |
+ p.mu.Unlock() |
|
| 698 |
+ if hcsProcess == nil {
|
|
| 699 |
+ return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 700 |
+ } |
|
| 701 |
+ return hcsProcess.Kill() |
|
| 702 |
+} |
|
| 703 |
+ |
|
| 704 |
+// Kill handles `docker stop` on Windows. While Linux has support for |
|
| 705 |
+// the full range of signals, signals aren't really implemented on Windows. |
|
| 706 |
+// We fake supporting regular stop and -9 to force kill. |
|
| 707 |
+func (t *task) Kill(_ context.Context, signal syscall.Signal) error {
|
|
| 708 |
+ hcsContainer, err := t.getHCSContainer() |
|
| 709 |
+ if err != nil {
|
|
| 710 |
+ return err |
|
| 711 |
+ } |
|
| 712 |
+ |
|
| 713 |
+ logger := t.ctr.client.logger.WithFields(log.Fields{
|
|
| 714 |
+ "container": t.ctr.id, |
|
| 715 |
+ "process": t.id, |
|
| 716 |
+ "pid": t.Pid(), |
|
| 717 |
+ "signal": signal, |
|
| 718 |
+ }) |
|
| 719 |
+ logger.Debug("Signal()")
|
|
| 720 |
+ |
|
| 721 |
+ var op string |
|
| 722 |
+ if signal == syscall.SIGKILL {
|
|
| 723 |
+ // Terminate the compute system |
|
| 724 |
+ t.ctr.mu.Lock() |
|
| 725 |
+ t.ctr.terminateInvoked = true |
|
| 726 |
+ t.ctr.mu.Unlock() |
|
| 727 |
+ op, err = "terminate", hcsContainer.Terminate() |
|
| 728 |
+ } else {
|
|
| 729 |
+ // Shut down the container |
|
| 730 |
+ op, err = "shutdown", hcsContainer.Shutdown() |
|
| 731 |
+ } |
|
| 732 |
+ if err != nil {
|
|
| 733 |
+ if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
|
|
| 734 |
+ // ignore errors |
|
| 735 |
+ logger.WithError(err).Errorf("failed to %s hccshim container", op)
|
|
| 736 |
+ } |
|
| 737 |
+ } |
|
| 738 |
+ |
|
| 739 |
+ return nil |
|
| 740 |
+} |
|
| 741 |
+ |
|
| 742 |
+// Resize handles a CLI event to resize an interactive docker run or docker |
|
| 743 |
+// exec window. |
|
| 744 |
+func (p *process) Resize(_ context.Context, width, height uint32) error {
|
|
| 745 |
+ p.mu.Lock() |
|
| 746 |
+ hcsProcess := p.hcsProcess |
|
| 747 |
+ p.mu.Unlock() |
|
| 748 |
+ if hcsProcess == nil {
|
|
| 749 |
+ return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 750 |
+ } |
|
| 751 |
+ |
|
| 752 |
+ p.ctr.client.logger.WithFields(log.Fields{
|
|
| 753 |
+ "container": p.ctr.id, |
|
| 754 |
+ "process": p.id, |
|
| 755 |
+ "height": height, |
|
| 756 |
+ "width": width, |
|
| 757 |
+ "pid": hcsProcess.Pid(), |
|
| 758 |
+ }).Debug("resizing")
|
|
| 759 |
+ return hcsProcess.ResizeConsole(uint16(width), uint16(height)) |
|
| 760 |
+} |
|
| 761 |
+ |
|
| 762 |
+func (p *process) CloseStdin(context.Context) error {
|
|
| 763 |
+ p.mu.Lock() |
|
| 764 |
+ hcsProcess := p.hcsProcess |
|
| 765 |
+ p.mu.Unlock() |
|
| 766 |
+ if hcsProcess == nil {
|
|
| 767 |
+ return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 768 |
+ } |
|
| 769 |
+ |
|
| 770 |
+ return hcsProcess.CloseStdin() |
|
| 771 |
+} |
|
| 772 |
+ |
|
| 773 |
+// Pause handles pause requests for containers |
|
| 774 |
+func (t *task) Pause(_ context.Context) error {
|
|
| 775 |
+ if t.ctr.ociSpec.Windows.HyperV == nil {
|
|
| 776 |
+ return errdefs.NotImplemented(errors.WithStack(errors.New("not implemented for containers using process isolation")))
|
|
| 777 |
+ } |
|
| 778 |
+ |
|
| 779 |
+ t.ctr.mu.Lock() |
|
| 780 |
+ defer t.ctr.mu.Unlock() |
|
| 781 |
+ |
|
| 782 |
+ if err := t.assertIsCurrentTask(); err != nil {
|
|
| 783 |
+ return err |
|
| 784 |
+ } |
|
| 785 |
+ if t.ctr.hcsContainer == nil {
|
|
| 786 |
+ return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 787 |
+ } |
|
| 788 |
+ if err := t.ctr.hcsContainer.Pause(); err != nil {
|
|
| 789 |
+ return err |
|
| 790 |
+ } |
|
| 791 |
+ |
|
| 792 |
+ t.ctr.isPaused = true |
|
| 793 |
+ |
|
| 794 |
+ t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 795 |
+ err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
|
|
| 796 |
+ ContainerID: t.ctr.id, |
|
| 797 |
+ ProcessID: t.id, |
|
| 798 |
+ }) |
|
| 799 |
+ t.ctr.client.logger.WithFields(log.Fields{
|
|
| 800 |
+ "container": t.ctr.id, |
|
| 801 |
+ "event": libcontainerdtypes.EventPaused, |
|
| 802 |
+ }).Info("sending event")
|
|
| 803 |
+ if err != nil {
|
|
| 804 |
+ t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 805 |
+ "container": t.ctr.id, |
|
| 806 |
+ "event": libcontainerdtypes.EventPaused, |
|
| 807 |
+ }).Error("failed to process event")
|
|
| 808 |
+ } |
|
| 809 |
+ }) |
|
| 810 |
+ |
|
| 811 |
+ return nil |
|
| 812 |
+} |
|
| 813 |
+ |
|
| 814 |
+// Resume handles resume requests for containers |
|
| 815 |
+func (t *task) Resume(ctx context.Context) error {
|
|
| 816 |
+ if t.ctr.ociSpec.Windows.HyperV == nil {
|
|
| 817 |
+ return errors.New("cannot resume Windows Server Containers")
|
|
| 818 |
+ } |
|
| 819 |
+ |
|
| 820 |
+ t.ctr.mu.Lock() |
|
| 821 |
+ defer t.ctr.mu.Unlock() |
|
| 822 |
+ |
|
| 823 |
+ if err := t.assertIsCurrentTask(); err != nil {
|
|
| 824 |
+ return err |
|
| 825 |
+ } |
|
| 826 |
+ if t.ctr.hcsContainer == nil {
|
|
| 827 |
+ return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 828 |
+ } |
|
| 829 |
+ if err := t.ctr.hcsContainer.Resume(); err != nil {
|
|
| 830 |
+ return err |
|
| 831 |
+ } |
|
| 832 |
+ |
|
| 833 |
+ t.ctr.isPaused = false |
|
| 834 |
+ |
|
| 835 |
+ t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 836 |
+ err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
|
|
| 837 |
+ ContainerID: t.ctr.id, |
|
| 838 |
+ ProcessID: t.id, |
|
| 839 |
+ }) |
|
| 840 |
+ t.ctr.client.logger.WithFields(log.Fields{
|
|
| 841 |
+ "container": t.ctr.id, |
|
| 842 |
+ "event": libcontainerdtypes.EventResumed, |
|
| 843 |
+ }).Info("sending event")
|
|
| 844 |
+ if err != nil {
|
|
| 845 |
+ t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 846 |
+ "container": t.ctr.id, |
|
| 847 |
+ "event": libcontainerdtypes.EventResumed, |
|
| 848 |
+ }).Error("failed to process event")
|
|
| 849 |
+ } |
|
| 850 |
+ }) |
|
| 851 |
+ |
|
| 852 |
+ return nil |
|
| 853 |
+} |
|
| 854 |
+ |
|
| 855 |
+// Stats handles stats requests for containers |
|
| 856 |
+func (t *task) Stats(_ context.Context) (*libcontainerdtypes.Stats, error) {
|
|
| 857 |
+ hc, err := t.getHCSContainer() |
|
| 858 |
+ if err != nil {
|
|
| 859 |
+ return nil, err |
|
| 860 |
+ } |
|
| 861 |
+ |
|
| 862 |
+ readAt := time.Now() |
|
| 863 |
+ s, err := hc.Statistics() |
|
| 864 |
+ if err != nil {
|
|
| 865 |
+ return nil, err |
|
| 866 |
+ } |
|
| 867 |
+ return &libcontainerdtypes.Stats{
|
|
| 868 |
+ Read: readAt, |
|
| 869 |
+ HCSStats: &s, |
|
| 870 |
+ }, nil |
|
| 871 |
+} |
|
| 872 |
+ |
|
| 873 |
+// LoadContainer is the handler for restoring a container |
|
| 874 |
+func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtypes.Container, error) {
|
|
| 875 |
+ c.logger.WithField("container", id).Debug("LoadContainer()")
|
|
| 876 |
+ |
|
| 877 |
+ // TODO Windows: On RS1, a re-attach isn't possible. |
|
| 878 |
+ // However, there is a scenario in which there is an issue. |
|
| 879 |
+ // Consider a background container. The daemon dies unexpectedly. |
|
| 880 |
+ // HCS will still have the compute service alive and running. |
|
| 881 |
+ // For consistence, we call in to shoot it regardless if HCS knows about it |
|
| 882 |
+ // We explicitly just log a warning if the terminate fails. |
|
| 883 |
+ // Then we tell the backend the container exited. |
|
| 884 |
+ hc, err := hcsshim.OpenContainer(id) |
|
| 885 |
+ if err != nil {
|
|
| 886 |
+ return nil, errdefs.NotFound(errors.New("container not found"))
|
|
| 887 |
+ } |
|
| 888 |
+ const terminateTimeout = time.Minute * 2 |
|
| 889 |
+ err = hc.Terminate() |
|
| 890 |
+ |
|
| 891 |
+ if hcsshim.IsPending(err) {
|
|
| 892 |
+ err = hc.WaitTimeout(terminateTimeout) |
|
| 893 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 894 |
+ err = nil |
|
| 895 |
+ } |
|
| 896 |
+ |
|
| 897 |
+ if err != nil {
|
|
| 898 |
+ c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
|
|
| 899 |
+ return nil, err |
|
| 900 |
+ } |
|
| 901 |
+ return &container{
|
|
| 902 |
+ client: c, |
|
| 903 |
+ hcsContainer: hc, |
|
| 904 |
+ id: id, |
|
| 905 |
+ }, nil |
|
| 906 |
+} |
|
| 907 |
+ |
|
| 908 |
+// AttachTask is only called by the daemon when restoring containers. As |
|
| 909 |
+// re-attach isn't possible (see LoadContainer), a NotFound error is |
|
| 910 |
+// unconditionally returned to allow restore to make progress. |
|
| 911 |
+func (*container) AttachTask(context.Context, libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
|
|
| 912 |
+ return nil, errdefs.NotFound(cerrdefs.ErrNotImplemented) |
|
| 913 |
+} |
|
| 914 |
+ |
|
| 915 |
+// Pids returns a list of process IDs running in a container. It is not |
|
| 916 |
+// implemented on Windows. |
|
| 917 |
+func (t *task) Pids(context.Context) ([]containerd.ProcessInfo, error) {
|
|
| 918 |
+ return nil, errors.New("not implemented on Windows")
|
|
| 919 |
+} |
|
| 920 |
+ |
|
| 921 |
+// Summary returns a summary of the processes running in a container. |
|
| 922 |
+// This is present in Windows to support docker top. In linux, the |
|
| 923 |
+// engine shells out to ps to get process information. On Windows, as |
|
| 924 |
+// the containers could be Hyper-V containers, they would not be |
|
| 925 |
+// visible on the container host. However, libcontainerd does have |
|
| 926 |
+// that information. |
|
| 927 |
+func (t *task) Summary(_ context.Context) ([]libcontainerdtypes.Summary, error) {
|
|
| 928 |
+ hc, err := t.getHCSContainer() |
|
| 929 |
+ if err != nil {
|
|
| 930 |
+ return nil, err |
|
| 931 |
+ } |
|
| 932 |
+ |
|
| 933 |
+ p, err := hc.ProcessList() |
|
| 934 |
+ if err != nil {
|
|
| 935 |
+ return nil, err |
|
| 936 |
+ } |
|
| 937 |
+ |
|
| 938 |
+ pl := make([]libcontainerdtypes.Summary, len(p)) |
|
| 939 |
+ for i := range p {
|
|
| 940 |
+ pl[i] = libcontainerdtypes.Summary{
|
|
| 941 |
+ ImageName: p[i].ImageName, |
|
| 942 |
+ CreatedAt: timestamppb.New(p[i].CreateTimestamp), |
|
| 943 |
+ KernelTime_100Ns: p[i].KernelTime100ns, |
|
| 944 |
+ MemoryCommitBytes: p[i].MemoryCommitBytes, |
|
| 945 |
+ MemoryWorkingSetPrivateBytes: p[i].MemoryWorkingSetPrivateBytes, |
|
| 946 |
+ MemoryWorkingSetSharedBytes: p[i].MemoryWorkingSetSharedBytes, |
|
| 947 |
+ ProcessID: p[i].ProcessId, |
|
| 948 |
+ UserTime_100Ns: p[i].UserTime100ns, |
|
| 949 |
+ ExecID: "", |
|
| 950 |
+ } |
|
| 951 |
+ } |
|
| 952 |
+ return pl, nil |
|
| 953 |
+} |
|
| 954 |
+ |
|
| 955 |
+func (p *process) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
|
|
| 956 |
+ select {
|
|
| 957 |
+ case <-ctx.Done(): |
|
| 958 |
+ return nil, errors.WithStack(ctx.Err()) |
|
| 959 |
+ case <-p.waitCh: |
|
| 960 |
+ default: |
|
| 961 |
+ return nil, errdefs.Conflict(errors.New("process is running"))
|
|
| 962 |
+ } |
|
| 963 |
+ return p.exited, nil |
|
| 964 |
+} |
|
| 965 |
+ |
|
| 966 |
+func (t *task) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
|
|
| 967 |
+ select {
|
|
| 968 |
+ case <-ctx.Done(): |
|
| 969 |
+ return nil, errors.WithStack(ctx.Err()) |
|
| 970 |
+ case <-t.waitCh: |
|
| 971 |
+ default: |
|
| 972 |
+ return nil, errdefs.Conflict(errors.New("container is not stopped"))
|
|
| 973 |
+ } |
|
| 974 |
+ |
|
| 975 |
+ t.ctr.mu.Lock() |
|
| 976 |
+ defer t.ctr.mu.Unlock() |
|
| 977 |
+ if err := t.assertIsCurrentTask(); err != nil {
|
|
| 978 |
+ return nil, err |
|
| 979 |
+ } |
|
| 980 |
+ t.ctr.task = nil |
|
| 981 |
+ return t.exited, nil |
|
| 982 |
+} |
|
| 983 |
+ |
|
| 984 |
+func (t *task) ForceDelete(ctx context.Context) error {
|
|
| 985 |
+ select {
|
|
| 986 |
+ case <-t.waitCh: // Task is already stopped. |
|
| 987 |
+ _, err := t.Delete(ctx) |
|
| 988 |
+ return err |
|
| 989 |
+ default: |
|
| 990 |
+ } |
|
| 991 |
+ |
|
| 992 |
+ if err := t.Kill(ctx, syscall.SIGKILL); err != nil {
|
|
| 993 |
+ return errors.Wrap(err, "could not force-kill task") |
|
| 994 |
+ } |
|
| 995 |
+ |
|
| 996 |
+ select {
|
|
| 997 |
+ case <-ctx.Done(): |
|
| 998 |
+ return ctx.Err() |
|
| 999 |
+ case <-t.waitCh: |
|
| 1000 |
+ _, err := t.Delete(ctx) |
|
| 1001 |
+ return err |
|
| 1002 |
+ } |
|
| 1003 |
+} |
|
| 1004 |
+ |
|
| 1005 |
+func (t *task) Status(ctx context.Context) (containerd.Status, error) {
|
|
| 1006 |
+ select {
|
|
| 1007 |
+ case <-t.waitCh: |
|
| 1008 |
+ return containerd.Status{
|
|
| 1009 |
+ Status: containerd.Stopped, |
|
| 1010 |
+ ExitStatus: t.exited.ExitCode(), |
|
| 1011 |
+ ExitTime: t.exited.ExitTime(), |
|
| 1012 |
+ }, nil |
|
| 1013 |
+ default: |
|
| 1014 |
+ } |
|
| 1015 |
+ |
|
| 1016 |
+ t.ctr.mu.Lock() |
|
| 1017 |
+ defer t.ctr.mu.Unlock() |
|
| 1018 |
+ s := containerd.Running |
|
| 1019 |
+ if t.ctr.isPaused {
|
|
| 1020 |
+ s = containerd.Paused |
|
| 1021 |
+ } |
|
| 1022 |
+ return containerd.Status{Status: s}, nil
|
|
| 1023 |
+} |
|
| 1024 |
+ |
|
| 1025 |
+func (*task) UpdateResources(context.Context, *libcontainerdtypes.Resources) error {
|
|
| 1026 |
+ // Updating resource isn't supported on Windows |
|
| 1027 |
+ // but we should return nil for enabling updating container |
|
| 1028 |
+ return nil |
|
| 1029 |
+} |
|
| 1030 |
+ |
|
| 1031 |
+func (*task) CreateCheckpoint(context.Context, string, bool) error {
|
|
| 1032 |
+ return errors.New("Windows: Containers do not support checkpoints")
|
|
| 1033 |
+} |
|
| 1034 |
+ |
|
| 1035 |
+// assertIsCurrentTask returns a non-nil error if the task has been deleted. |
|
| 1036 |
+func (t *task) assertIsCurrentTask() error {
|
|
| 1037 |
+ if t.ctr.task != t {
|
|
| 1038 |
+ return errors.WithStack(errdefs.NotFound(fmt.Errorf("task %q not found", t.id)))
|
|
| 1039 |
+ } |
|
| 1040 |
+ return nil |
|
| 1041 |
+} |
|
| 1042 |
+ |
|
| 1043 |
+// getHCSContainer returns a reference to the hcsshim Container for the task's |
|
| 1044 |
+// container if neither the task nor container have been deleted. |
|
| 1045 |
+// |
|
| 1046 |
+// t.ctr.mu must not be locked by the calling goroutine when calling this |
|
| 1047 |
+// function. |
|
| 1048 |
+func (t *task) getHCSContainer() (hcsshim.Container, error) {
|
|
| 1049 |
+ t.ctr.mu.Lock() |
|
| 1050 |
+ defer t.ctr.mu.Unlock() |
|
| 1051 |
+ if err := t.assertIsCurrentTask(); err != nil {
|
|
| 1052 |
+ return nil, err |
|
| 1053 |
+ } |
|
| 1054 |
+ hc := t.ctr.hcsContainer |
|
| 1055 |
+ if hc == nil {
|
|
| 1056 |
+ return nil, errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 1057 |
+ } |
|
| 1058 |
+ return hc, nil |
|
| 1059 |
+} |
|
| 1060 |
+ |
|
| 1061 |
+// ctr mutex must be held when calling this function. |
|
| 1062 |
+func (ctr *container) shutdownContainer() error {
|
|
| 1063 |
+ var err error |
|
| 1064 |
+ const waitTimeout = time.Minute * 5 |
|
| 1065 |
+ |
|
| 1066 |
+ if !ctr.terminateInvoked {
|
|
| 1067 |
+ err = ctr.hcsContainer.Shutdown() |
|
| 1068 |
+ } |
|
| 1069 |
+ |
|
| 1070 |
+ if hcsshim.IsPending(err) || ctr.terminateInvoked {
|
|
| 1071 |
+ err = ctr.hcsContainer.WaitTimeout(waitTimeout) |
|
| 1072 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1073 |
+ err = nil |
|
| 1074 |
+ } |
|
| 1075 |
+ |
|
| 1076 |
+ if err != nil {
|
|
| 1077 |
+ ctr.client.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1078 |
+ Debug("failed to shutdown container, terminating it")
|
|
| 1079 |
+ terminateErr := ctr.terminateContainer() |
|
| 1080 |
+ if terminateErr != nil {
|
|
| 1081 |
+ ctr.client.logger.WithError(terminateErr).WithField("container", ctr.id).
|
|
| 1082 |
+ Error("failed to shutdown container, and subsequent terminate also failed")
|
|
| 1083 |
+ return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
|
|
| 1084 |
+ } |
|
| 1085 |
+ return err |
|
| 1086 |
+ } |
|
| 1087 |
+ |
|
| 1088 |
+ return nil |
|
| 1089 |
+} |
|
| 1090 |
+ |
|
| 1091 |
+// ctr mutex must be held when calling this function. |
|
| 1092 |
+func (ctr *container) terminateContainer() error {
|
|
| 1093 |
+ const terminateTimeout = time.Minute * 5 |
|
| 1094 |
+ ctr.terminateInvoked = true |
|
| 1095 |
+ err := ctr.hcsContainer.Terminate() |
|
| 1096 |
+ |
|
| 1097 |
+ if hcsshim.IsPending(err) {
|
|
| 1098 |
+ err = ctr.hcsContainer.WaitTimeout(terminateTimeout) |
|
| 1099 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1100 |
+ err = nil |
|
| 1101 |
+ } |
|
| 1102 |
+ |
|
| 1103 |
+ if err != nil {
|
|
| 1104 |
+ ctr.client.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1105 |
+ Debug("failed to terminate container")
|
|
| 1106 |
+ return err |
|
| 1107 |
+ } |
|
| 1108 |
+ |
|
| 1109 |
+ return nil |
|
| 1110 |
+} |
|
| 1111 |
+ |
|
| 1112 |
+func (p *process) reap() {
|
|
| 1113 |
+ logger := p.ctr.client.logger.WithFields(log.Fields{
|
|
| 1114 |
+ "container": p.ctr.id, |
|
| 1115 |
+ "process": p.id, |
|
| 1116 |
+ }) |
|
| 1117 |
+ |
|
| 1118 |
+ var eventErr error |
|
| 1119 |
+ |
|
| 1120 |
+ // Block indefinitely for the process to exit. |
|
| 1121 |
+ if err := p.hcsProcess.Wait(); err != nil {
|
|
| 1122 |
+ if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1123 |
+ logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
|
|
| 1124 |
+ } |
|
| 1125 |
+ // Fall through here, do not return. This ensures we tell the |
|
| 1126 |
+ // docker engine that the process/container has exited to avoid |
|
| 1127 |
+ // a container being dropped on the floor. |
|
| 1128 |
+ } |
|
| 1129 |
+ exitedAt := time.Now() |
|
| 1130 |
+ |
|
| 1131 |
+ exitCode, err := p.hcsProcess.ExitCode() |
|
| 1132 |
+ if err != nil {
|
|
| 1133 |
+ if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1134 |
+ logger.WithError(err).Warnf("unable to get exit code for process")
|
|
| 1135 |
+ } |
|
| 1136 |
+ // Since we got an error retrieving the exit code, make sure that the |
|
| 1137 |
+ // code we return doesn't incorrectly indicate success. |
|
| 1138 |
+ exitCode = -1 |
|
| 1139 |
+ |
|
| 1140 |
+ // Fall through here, do not return. This ensures we tell the |
|
| 1141 |
+ // docker engine that the process/container has exited to avoid |
|
| 1142 |
+ // a container being dropped on the floor. |
|
| 1143 |
+ } |
|
| 1144 |
+ |
|
| 1145 |
+ p.mu.Lock() |
|
| 1146 |
+ hcsProcess := p.hcsProcess |
|
| 1147 |
+ p.hcsProcess = nil |
|
| 1148 |
+ p.mu.Unlock() |
|
| 1149 |
+ |
|
| 1150 |
+ if err := hcsProcess.Close(); err != nil {
|
|
| 1151 |
+ logger.WithError(err).Warnf("failed to cleanup hcs process resources")
|
|
| 1152 |
+ exitCode = -1 |
|
| 1153 |
+ eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
|
|
| 1154 |
+ } |
|
| 1155 |
+ |
|
| 1156 |
+ // Explicit locking is not required as reads from exited are |
|
| 1157 |
+ // synchronized using waitCh. |
|
| 1158 |
+ p.exited = containerd.NewExitStatus(uint32(exitCode), exitedAt, nil) |
|
| 1159 |
+ close(p.waitCh) |
|
| 1160 |
+ |
|
| 1161 |
+ p.ctr.client.eventQ.Append(p.ctr.id, func() {
|
|
| 1162 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 1163 |
+ ContainerID: p.ctr.id, |
|
| 1164 |
+ ProcessID: p.id, |
|
| 1165 |
+ Pid: uint32(hcsProcess.Pid()), |
|
| 1166 |
+ ExitCode: uint32(exitCode), |
|
| 1167 |
+ ExitedAt: exitedAt, |
|
| 1168 |
+ Error: eventErr, |
|
| 1169 |
+ } |
|
| 1170 |
+ p.ctr.client.logger.WithFields(log.Fields{
|
|
| 1171 |
+ "container": p.ctr.id, |
|
| 1172 |
+ "event": libcontainerdtypes.EventExit, |
|
| 1173 |
+ "event-info": ei, |
|
| 1174 |
+ }).Info("sending event")
|
|
| 1175 |
+ err := p.ctr.client.backend.ProcessEvent(p.ctr.id, libcontainerdtypes.EventExit, ei) |
|
| 1176 |
+ if err != nil {
|
|
| 1177 |
+ p.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 1178 |
+ "container": p.ctr.id, |
|
| 1179 |
+ "event": libcontainerdtypes.EventExit, |
|
| 1180 |
+ "event-info": ei, |
|
| 1181 |
+ }).Error("failed to process event")
|
|
| 1182 |
+ } |
|
| 1183 |
+ }) |
|
| 1184 |
+} |
|
| 1185 |
+ |
|
| 1186 |
+func (ctr *container) Delete(context.Context) error {
|
|
| 1187 |
+ ctr.mu.Lock() |
|
| 1188 |
+ defer ctr.mu.Unlock() |
|
| 1189 |
+ |
|
| 1190 |
+ if ctr.hcsContainer == nil {
|
|
| 1191 |
+ return errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", ctr.id)))
|
|
| 1192 |
+ } |
|
| 1193 |
+ |
|
| 1194 |
+ // Check that there is no task currently running. |
|
| 1195 |
+ if ctr.task != nil {
|
|
| 1196 |
+ select {
|
|
| 1197 |
+ case <-ctr.task.waitCh: |
|
| 1198 |
+ default: |
|
| 1199 |
+ return errors.WithStack(errdefs.Conflict(errors.New("container is not stopped")))
|
|
| 1200 |
+ } |
|
| 1201 |
+ } |
|
| 1202 |
+ |
|
| 1203 |
+ var ( |
|
| 1204 |
+ logger = ctr.client.logger.WithFields(log.Fields{
|
|
| 1205 |
+ "container": ctr.id, |
|
| 1206 |
+ }) |
|
| 1207 |
+ thisErr error |
|
| 1208 |
+ ) |
|
| 1209 |
+ |
|
| 1210 |
+ if err := ctr.shutdownContainer(); err != nil {
|
|
| 1211 |
+ logger.WithError(err).Warn("failed to shutdown container")
|
|
| 1212 |
+ thisErr = errors.Wrap(err, "failed to shutdown container") |
|
| 1213 |
+ } else {
|
|
| 1214 |
+ logger.Debug("completed container shutdown")
|
|
| 1215 |
+ } |
|
| 1216 |
+ |
|
| 1217 |
+ if err := ctr.hcsContainer.Close(); err != nil {
|
|
| 1218 |
+ logger.WithError(err).Error("failed to clean hcs container resources")
|
|
| 1219 |
+ thisErr = errors.Wrap(err, "failed to terminate container") |
|
| 1220 |
+ } |
|
| 1221 |
+ |
|
| 1222 |
+ ctr.hcsContainer = nil |
|
| 1223 |
+ return thisErr |
|
| 1224 |
+} |
| 0 | 1225 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,40 @@ |
| 0 |
+package local |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "sync" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/Microsoft/hcsshim" |
|
| 7 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type autoClosingReader struct {
|
|
| 11 |
+ io.ReadCloser |
|
| 12 |
+ sync.Once |
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func (r *autoClosingReader) Read(b []byte) (int, error) {
|
|
| 16 |
+ n, err := r.ReadCloser.Read(b) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ r.Once.Do(func() { r.ReadCloser.Close() })
|
|
| 19 |
+ } |
|
| 20 |
+ return n, err |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func createStdInCloser(pipe io.WriteCloser, process hcsshim.Process) io.WriteCloser {
|
|
| 24 |
+ return ioutils.NewWriteCloserWrapper(pipe, func() error {
|
|
| 25 |
+ if err := pipe.Close(); err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ err := process.CloseStdin() |
|
| 30 |
+ if err != nil && !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyClosed(err) {
|
|
| 31 |
+ // This error will occur if the compute system is currently shutting down |
|
| 32 |
+ if perr, ok := err.(*hcsshim.ProcessError); ok && perr.Err != hcsshim.ErrVmcomputeOperationInvalidState {
|
|
| 33 |
+ return err |
|
| 34 |
+ } |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ return nil |
|
| 38 |
+ }) |
|
| 39 |
+} |
| 0 | 40 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,15 @@ |
| 0 |
+package local |
|
| 1 |
+ |
|
| 2 |
+import "strings" |
|
| 3 |
+ |
|
| 4 |
+// setupEnvironmentVariables converts a string array of environment variables |
|
| 5 |
+// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc. |
|
| 6 |
+func setupEnvironmentVariables(a []string) map[string]string {
|
|
| 7 |
+ r := make(map[string]string) |
|
| 8 |
+ for _, s := range a {
|
|
| 9 |
+ if k, v, ok := strings.Cut(s, "="); ok {
|
|
| 10 |
+ r[k] = v |
|
| 11 |
+ } |
|
| 12 |
+ } |
|
| 13 |
+ return r |
|
| 14 |
+} |
| 0 | 15 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+package local |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestEnvironmentParsing(t *testing.T) {
|
|
| 7 |
+ env := []string{"foo=bar", "car=hat", "a=b=c"}
|
|
| 8 |
+ result := setupEnvironmentVariables(env) |
|
| 9 |
+ if len(result) != 3 || result["foo"] != "bar" || result["car"] != "hat" || result["a"] != "b=c" {
|
|
| 10 |
+ t.Fatalf("Expected map[foo:bar car:hat a:b=c], got %v", result)
|
|
| 11 |
+ } |
|
| 12 |
+} |
| ... | ... |
@@ -4,7 +4,7 @@ import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
|
| 6 | 6 |
containerd "github.com/containerd/containerd/v2/client" |
| 7 |
- "github.com/docker/docker/libcontainerd/local" |
|
| 7 |
+ "github.com/docker/docker/daemon/internal/libcontainerd/local" |
|
| 8 | 8 |
"github.com/docker/docker/libcontainerd/remote" |
| 9 | 9 |
libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
| 10 | 10 |
"github.com/docker/docker/pkg/system" |
| 11 | 11 |
deleted file mode 100644 |
| ... | ... |
@@ -1,1225 +0,0 @@ |
| 1 |
-package local |
|
| 2 |
- |
|
| 3 |
-// This package contains the legacy in-proc calls in HCS using the v1 schema |
|
| 4 |
-// for Windows runtime purposes. |
|
| 5 |
- |
|
| 6 |
-import ( |
|
| 7 |
- "context" |
|
| 8 |
- "fmt" |
|
| 9 |
- "io" |
|
| 10 |
- "os" |
|
| 11 |
- "path/filepath" |
|
| 12 |
- "regexp" |
|
| 13 |
- "runtime" |
|
| 14 |
- "strings" |
|
| 15 |
- "sync" |
|
| 16 |
- "syscall" |
|
| 17 |
- "time" |
|
| 18 |
- |
|
| 19 |
- "github.com/Microsoft/hcsshim" |
|
| 20 |
- containerd "github.com/containerd/containerd/v2/client" |
|
| 21 |
- "github.com/containerd/containerd/v2/pkg/cio" |
|
| 22 |
- cerrdefs "github.com/containerd/errdefs" |
|
| 23 |
- "github.com/containerd/log" |
|
| 24 |
- "github.com/docker/docker/errdefs" |
|
| 25 |
- "github.com/docker/docker/libcontainerd/queue" |
|
| 26 |
- libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 27 |
- "github.com/docker/docker/pkg/system" |
|
| 28 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 29 |
- "github.com/pkg/errors" |
|
| 30 |
- "golang.org/x/sys/windows" |
|
| 31 |
- "google.golang.org/protobuf/types/known/timestamppb" |
|
| 32 |
-) |
|
| 33 |
- |
|
| 34 |
-type process struct {
|
|
| 35 |
- // mu guards the mutable fields of this struct. |
|
| 36 |
- // |
|
| 37 |
- // Always lock mu before ctr's mutex to prevent deadlocks. |
|
| 38 |
- mu sync.Mutex |
|
| 39 |
- id string // Invariants: immutable |
|
| 40 |
- ctr *container // Invariants: immutable, ctr != nil |
|
| 41 |
- hcsProcess hcsshim.Process // Is set to nil on process exit |
|
| 42 |
- exited *containerd.ExitStatus // Valid iff waitCh is closed |
|
| 43 |
- waitCh chan struct{}
|
|
| 44 |
-} |
|
| 45 |
- |
|
| 46 |
-type task struct {
|
|
| 47 |
- process |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-type container struct {
|
|
| 51 |
- mu sync.Mutex |
|
| 52 |
- |
|
| 53 |
- // The ociSpec is required, as client.Create() needs a spec, but can |
|
| 54 |
- // be called from the RestartManager context which does not otherwise |
|
| 55 |
- // have access to the Spec |
|
| 56 |
- // |
|
| 57 |
- // A container value with ociSpec == nil represents a container which |
|
| 58 |
- // has been loaded with (*client).LoadContainer, and is ineligible to |
|
| 59 |
- // be Start()ed. |
|
| 60 |
- ociSpec *specs.Spec |
|
| 61 |
- |
|
| 62 |
- hcsContainer hcsshim.Container // Is set to nil on container delete |
|
| 63 |
- isPaused bool |
|
| 64 |
- |
|
| 65 |
- client *client |
|
| 66 |
- id string |
|
| 67 |
- terminateInvoked bool |
|
| 68 |
- |
|
| 69 |
- // task is a reference to the current task for the container. As a |
|
| 70 |
- // corollary, when task == nil the container has no current task: the |
|
| 71 |
- // container was never Start()ed or the task was Delete()d. |
|
| 72 |
- task *task |
|
| 73 |
-} |
|
| 74 |
- |
|
| 75 |
-// defaultOwner is a tag passed to HCS to allow it to differentiate between |
|
| 76 |
-// container creator management stacks. We hard code "docker" in the case |
|
| 77 |
-// of docker. |
|
| 78 |
-const defaultOwner = "docker" |
|
| 79 |
- |
|
| 80 |
-type client struct {
|
|
| 81 |
- backend libcontainerdtypes.Backend |
|
| 82 |
- logger *log.Entry |
|
| 83 |
- eventQ queue.Queue |
|
| 84 |
-} |
|
| 85 |
- |
|
| 86 |
-// NewClient creates a new local executor for windows |
|
| 87 |
-func NewClient(ctx context.Context, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 88 |
- return &client{
|
|
| 89 |
- backend: b, |
|
| 90 |
- logger: log.G(ctx).WithField("module", "libcontainerd"),
|
|
| 91 |
- }, nil |
|
| 92 |
-} |
|
| 93 |
- |
|
| 94 |
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 95 |
- return containerd.Version{}, errors.New("not implemented on Windows")
|
|
| 96 |
-} |
|
| 97 |
- |
|
| 98 |
-// NewContainer is the entrypoint to create a container from a spec. |
|
| 99 |
-// Table below shows the fields required for HCS JSON calling parameters, |
|
| 100 |
-// where if not populated, is omitted. |
|
| 101 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 102 |
-// | | Isolation=Process | Isolation=Hyper-V | |
|
| 103 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 104 |
-// | VolumePath | \\?\\Volume{GUIDa} | |
|
|
| 105 |
-// | LayerFolderPath | %root%\windowsfilter\containerID | | |
|
| 106 |
-// | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID | |
|
| 107 |
-// | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM | |
|
| 108 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 109 |
-// |
|
| 110 |
-// Isolation=Process example: |
|
| 111 |
-// |
|
| 112 |
-// {
|
|
| 113 |
-// "SystemType": "Container", |
|
| 114 |
-// "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 115 |
-// "Owner": "docker", |
|
| 116 |
-// "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
|
|
| 117 |
-// "IgnoreFlushesDuringBoot": true, |
|
| 118 |
-// "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 119 |
-// "Layers": [{
|
|
| 120 |
-// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 121 |
-// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 122 |
-// }], |
|
| 123 |
-// "HostName": "5e0055c814a6", |
|
| 124 |
-// "MappedDirectories": [], |
|
| 125 |
-// "HvPartition": false, |
|
| 126 |
-// "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"], |
|
| 127 |
-// } |
|
| 128 |
-// |
|
| 129 |
-// Isolation=Hyper-V example: |
|
| 130 |
-// |
|
| 131 |
-// {
|
|
| 132 |
-// "SystemType": "Container", |
|
| 133 |
-// "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d", |
|
| 134 |
-// "Owner": "docker", |
|
| 135 |
-// "IgnoreFlushesDuringBoot": true, |
|
| 136 |
-// "Layers": [{
|
|
| 137 |
-// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 138 |
-// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 139 |
-// }], |
|
| 140 |
-// "HostName": "475c2c58933b", |
|
| 141 |
-// "MappedDirectories": [], |
|
| 142 |
-// "HvPartition": true, |
|
| 143 |
-// "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"], |
|
| 144 |
-// "DNSSearchList": "a.com,b.com,c.com", |
|
| 145 |
-// "HvRuntime": {
|
|
| 146 |
-// "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM" |
|
| 147 |
-// }, |
|
| 148 |
-// } |
|
| 149 |
-func (c *client) NewContainer(_ context.Context, id string, spec *specs.Spec, _ string, _ interface{}, _ ...containerd.NewContainerOpts) (libcontainerdtypes.Container, error) {
|
|
| 150 |
- if spec.Linux != nil {
|
|
| 151 |
- return nil, errors.New("linux containers are not supported on this platform")
|
|
| 152 |
- } |
|
| 153 |
- ctr, err := c.createWindows(id, spec) |
|
| 154 |
- if err != nil {
|
|
| 155 |
- return nil, err |
|
| 156 |
- } |
|
| 157 |
- |
|
| 158 |
- c.eventQ.Append(id, func() {
|
|
| 159 |
- c.logger.WithFields(log.Fields{
|
|
| 160 |
- "container": id, |
|
| 161 |
- "event": libcontainerdtypes.EventCreate, |
|
| 162 |
- }).Info("sending event")
|
|
| 163 |
- |
|
| 164 |
- err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, libcontainerdtypes.EventInfo{
|
|
| 165 |
- ContainerID: id, |
|
| 166 |
- }) |
|
| 167 |
- if err != nil {
|
|
| 168 |
- c.logger.WithFields(log.Fields{
|
|
| 169 |
- "container": id, |
|
| 170 |
- "event": libcontainerdtypes.EventCreate, |
|
| 171 |
- "error": err, |
|
| 172 |
- }).Error("failed to process event")
|
|
| 173 |
- } |
|
| 174 |
- }) |
|
| 175 |
- return ctr, nil |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 |
-func (c *client) createWindows(id string, spec *specs.Spec) (*container, error) {
|
|
| 179 |
- logger := c.logger.WithField("container", id)
|
|
| 180 |
- configuration := &hcsshim.ContainerConfig{
|
|
| 181 |
- SystemType: "Container", |
|
| 182 |
- Name: id, |
|
| 183 |
- Owner: defaultOwner, |
|
| 184 |
- IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
|
| 185 |
- HostName: spec.Hostname, |
|
| 186 |
- HvPartition: false, |
|
| 187 |
- } |
|
| 188 |
- |
|
| 189 |
- c.extractResourcesFromSpec(spec, configuration) |
|
| 190 |
- |
|
| 191 |
- if spec.Windows.Resources != nil {
|
|
| 192 |
- if spec.Windows.Resources.Storage != nil {
|
|
| 193 |
- if spec.Windows.Resources.Storage.Bps != nil {
|
|
| 194 |
- configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps |
|
| 195 |
- } |
|
| 196 |
- if spec.Windows.Resources.Storage.Iops != nil {
|
|
| 197 |
- configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops |
|
| 198 |
- } |
|
| 199 |
- } |
|
| 200 |
- } |
|
| 201 |
- |
|
| 202 |
- if spec.Windows.HyperV != nil {
|
|
| 203 |
- configuration.HvPartition = true |
|
| 204 |
- } |
|
| 205 |
- |
|
| 206 |
- if spec.Windows.Network != nil {
|
|
| 207 |
- configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 208 |
- configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 209 |
- if spec.Windows.Network.DNSSearchList != nil {
|
|
| 210 |
- configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 211 |
- } |
|
| 212 |
- configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 213 |
- } |
|
| 214 |
- |
|
| 215 |
- if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
|
| 216 |
- configuration.Credentials = cs |
|
| 217 |
- } |
|
| 218 |
- |
|
| 219 |
- // We must have least two layers in the spec, the bottom one being a |
|
| 220 |
- // base image, the top one being the RW layer. |
|
| 221 |
- if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
|
| 222 |
- return nil, fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- // Strip off the top-most layer as that's passed in separately to HCS |
|
| 226 |
- configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 227 |
- layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 228 |
- |
|
| 229 |
- if configuration.HvPartition {
|
|
| 230 |
- // We don't currently support setting the utility VM image explicitly. |
|
| 231 |
- // TODO circa RS5, this may be re-locatable. |
|
| 232 |
- if spec.Windows.HyperV.UtilityVMPath != "" {
|
|
| 233 |
- return nil, errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
|
| 234 |
- } |
|
| 235 |
- |
|
| 236 |
- // Find the upper-most utility VM image. |
|
| 237 |
- var uvmImagePath string |
|
| 238 |
- for _, path := range layerFolders {
|
|
| 239 |
- fullPath := filepath.Join(path, "UtilityVM") |
|
| 240 |
- _, err := os.Stat(fullPath) |
|
| 241 |
- if err == nil {
|
|
| 242 |
- uvmImagePath = fullPath |
|
| 243 |
- break |
|
| 244 |
- } |
|
| 245 |
- if !os.IsNotExist(err) {
|
|
| 246 |
- return nil, err |
|
| 247 |
- } |
|
| 248 |
- } |
|
| 249 |
- if uvmImagePath == "" {
|
|
| 250 |
- return nil, errors.New("utility VM image could not be found")
|
|
| 251 |
- } |
|
| 252 |
- configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
|
| 253 |
- |
|
| 254 |
- if spec.Root.Path != "" {
|
|
| 255 |
- return nil, errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
|
| 256 |
- } |
|
| 257 |
- } else {
|
|
| 258 |
- const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}\\$`
|
|
| 259 |
- if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
|
| 260 |
- return nil, fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
|
| 261 |
- } |
|
| 262 |
- // HCS API requires the trailing backslash to be removed |
|
| 263 |
- configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1] |
|
| 264 |
- } |
|
| 265 |
- |
|
| 266 |
- if spec.Root.Readonly {
|
|
| 267 |
- return nil, errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`) |
|
| 268 |
- } |
|
| 269 |
- |
|
| 270 |
- for _, layerPath := range layerFolders {
|
|
| 271 |
- _, filename := filepath.Split(layerPath) |
|
| 272 |
- g, err := hcsshim.NameToGuid(filename) |
|
| 273 |
- if err != nil {
|
|
| 274 |
- return nil, err |
|
| 275 |
- } |
|
| 276 |
- configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 277 |
- ID: g.ToString(), |
|
| 278 |
- Path: layerPath, |
|
| 279 |
- }) |
|
| 280 |
- } |
|
| 281 |
- |
|
| 282 |
- // Add the mounts (volumes, bind mounts etc) to the structure |
|
| 283 |
- var mds []hcsshim.MappedDir |
|
| 284 |
- var mps []hcsshim.MappedPipe |
|
| 285 |
- for _, mount := range spec.Mounts {
|
|
| 286 |
- const pipePrefix = `\\.\pipe\` |
|
| 287 |
- if mount.Type != "" {
|
|
| 288 |
- return nil, fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
|
| 289 |
- } |
|
| 290 |
- if strings.HasPrefix(mount.Destination, pipePrefix) {
|
|
| 291 |
- mp := hcsshim.MappedPipe{
|
|
| 292 |
- HostPath: mount.Source, |
|
| 293 |
- ContainerPipeName: mount.Destination[len(pipePrefix):], |
|
| 294 |
- } |
|
| 295 |
- mps = append(mps, mp) |
|
| 296 |
- } else {
|
|
| 297 |
- md := hcsshim.MappedDir{
|
|
| 298 |
- HostPath: mount.Source, |
|
| 299 |
- ContainerPath: mount.Destination, |
|
| 300 |
- ReadOnly: false, |
|
| 301 |
- } |
|
| 302 |
- for _, o := range mount.Options {
|
|
| 303 |
- if strings.ToLower(o) == "ro" {
|
|
| 304 |
- md.ReadOnly = true |
|
| 305 |
- } |
|
| 306 |
- } |
|
| 307 |
- mds = append(mds, md) |
|
| 308 |
- } |
|
| 309 |
- } |
|
| 310 |
- configuration.MappedDirectories = mds |
|
| 311 |
- configuration.MappedPipes = mps |
|
| 312 |
- |
|
| 313 |
- if len(spec.Windows.Devices) > 0 {
|
|
| 314 |
- // Add any device assignments |
|
| 315 |
- if configuration.HvPartition {
|
|
| 316 |
- return nil, errors.New("device assignment is not supported for HyperV containers")
|
|
| 317 |
- } |
|
| 318 |
- for _, d := range spec.Windows.Devices {
|
|
| 319 |
- // Per https://github.com/microsoft/hcsshim/blob/v0.9.2/internal/uvm/virtual_device.go#L17-L18, |
|
| 320 |
- // these represent an Interface Class GUID. |
|
| 321 |
- if d.IDType != "class" && d.IDType != "vpci-class-guid" {
|
|
| 322 |
- return nil, errors.Errorf("device assignment of type '%s' is not supported", d.IDType)
|
|
| 323 |
- } |
|
| 324 |
- configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
|
|
| 325 |
- } |
|
| 326 |
- } |
|
| 327 |
- |
|
| 328 |
- hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 329 |
- if err != nil {
|
|
| 330 |
- return nil, err |
|
| 331 |
- } |
|
| 332 |
- |
|
| 333 |
- // Construct a container object for calling start on it. |
|
| 334 |
- ctr := &container{
|
|
| 335 |
- client: c, |
|
| 336 |
- id: id, |
|
| 337 |
- ociSpec: spec, |
|
| 338 |
- hcsContainer: hcsContainer, |
|
| 339 |
- } |
|
| 340 |
- |
|
| 341 |
- logger.Debug("starting container")
|
|
| 342 |
- if err := ctr.hcsContainer.Start(); err != nil {
|
|
| 343 |
- logger.WithError(err).Error("failed to start container")
|
|
| 344 |
- ctr.mu.Lock() |
|
| 345 |
- if err := ctr.terminateContainer(); err != nil {
|
|
| 346 |
- logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 347 |
- } else {
|
|
| 348 |
- logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 349 |
- } |
|
| 350 |
- ctr.mu.Unlock() |
|
| 351 |
- return nil, err |
|
| 352 |
- } |
|
| 353 |
- |
|
| 354 |
- logger.Debug("createWindows() completed successfully")
|
|
| 355 |
- return ctr, nil |
|
| 356 |
-} |
|
| 357 |
- |
|
| 358 |
-func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
|
|
| 359 |
- if spec.Windows.Resources == nil {
|
|
| 360 |
- return |
|
| 361 |
- } |
|
| 362 |
- if spec.Windows.Resources.CPU != nil {
|
|
| 363 |
- if spec.Windows.Resources.CPU.Count != nil {
|
|
| 364 |
- // This check is being done here rather than in adaptContainerSettings |
|
| 365 |
- // because we don't want to update the HostConfig in case this container |
|
| 366 |
- // is moved to a host with more CPUs than this one. |
|
| 367 |
- cpuCount := *spec.Windows.Resources.CPU.Count |
|
| 368 |
- hostCPUCount := uint64(runtime.NumCPU()) |
|
| 369 |
- if cpuCount > hostCPUCount {
|
|
| 370 |
- c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
|
|
| 371 |
- cpuCount = hostCPUCount |
|
| 372 |
- } |
|
| 373 |
- configuration.ProcessorCount = uint32(cpuCount) |
|
| 374 |
- } |
|
| 375 |
- if spec.Windows.Resources.CPU.Shares != nil {
|
|
| 376 |
- configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares) |
|
| 377 |
- } |
|
| 378 |
- if spec.Windows.Resources.CPU.Maximum != nil {
|
|
| 379 |
- configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum) |
|
| 380 |
- } |
|
| 381 |
- } |
|
| 382 |
- if spec.Windows.Resources.Memory != nil {
|
|
| 383 |
- if spec.Windows.Resources.Memory.Limit != nil {
|
|
| 384 |
- configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024 |
|
| 385 |
- } |
|
| 386 |
- } |
|
| 387 |
-} |
|
| 388 |
- |
|
| 389 |
-func (ctr *container) NewTask(_ context.Context, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Task, retErr error) {
|
|
| 390 |
- ctr.mu.Lock() |
|
| 391 |
- defer ctr.mu.Unlock() |
|
| 392 |
- |
|
| 393 |
- switch {
|
|
| 394 |
- case ctr.ociSpec == nil: |
|
| 395 |
- return nil, errors.WithStack(errdefs.NotImplemented(errors.New("a restored container cannot be started")))
|
|
| 396 |
- case ctr.task != nil: |
|
| 397 |
- return nil, errors.WithStack(errdefs.NotModified(cerrdefs.ErrAlreadyExists)) |
|
| 398 |
- } |
|
| 399 |
- |
|
| 400 |
- logger := ctr.client.logger.WithField("container", ctr.id)
|
|
| 401 |
- |
|
| 402 |
- // Note we always tell HCS to create stdout as it's required |
|
| 403 |
- // regardless of '-i' or '-t' options, so that docker can always grab |
|
| 404 |
- // the output through logs. We also tell HCS to always create stdin, |
|
| 405 |
- // even if it's not used - it will be closed shortly. Stderr is only |
|
| 406 |
- // created if it we're not -t. |
|
| 407 |
- var ( |
|
| 408 |
- emulateConsole bool |
|
| 409 |
- createStdErrPipe bool |
|
| 410 |
- ) |
|
| 411 |
- if ctr.ociSpec.Process != nil {
|
|
| 412 |
- emulateConsole = ctr.ociSpec.Process.Terminal |
|
| 413 |
- createStdErrPipe = !ctr.ociSpec.Process.Terminal |
|
| 414 |
- } |
|
| 415 |
- |
|
| 416 |
- createProcessParms := &hcsshim.ProcessConfig{
|
|
| 417 |
- EmulateConsole: emulateConsole, |
|
| 418 |
- WorkingDirectory: ctr.ociSpec.Process.Cwd, |
|
| 419 |
- CreateStdInPipe: true, |
|
| 420 |
- CreateStdOutPipe: true, |
|
| 421 |
- CreateStdErrPipe: createStdErrPipe, |
|
| 422 |
- } |
|
| 423 |
- |
|
| 424 |
- if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
|
| 425 |
- createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 426 |
- createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 427 |
- } |
|
| 428 |
- |
|
| 429 |
- // Configure the environment for the process |
|
| 430 |
- createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
|
| 431 |
- |
|
| 432 |
- // Configure the CommandLine/CommandArgs |
|
| 433 |
- setCommandLineAndArgs(ctr.ociSpec.Process, createProcessParms) |
|
| 434 |
- logger.Debugf("start commandLine: %s", createProcessParms.CommandLine)
|
|
| 435 |
- |
|
| 436 |
- createProcessParms.User = ctr.ociSpec.Process.User.Username |
|
| 437 |
- |
|
| 438 |
- // Start the command running in the container. |
|
| 439 |
- newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) |
|
| 440 |
- if err != nil {
|
|
| 441 |
- logger.WithError(err).Error("CreateProcess() failed")
|
|
| 442 |
- return nil, err |
|
| 443 |
- } |
|
| 444 |
- |
|
| 445 |
- defer func() {
|
|
| 446 |
- if retErr != nil {
|
|
| 447 |
- if err := newProcess.Kill(); err != nil {
|
|
| 448 |
- logger.WithError(err).Error("failed to kill process")
|
|
| 449 |
- } |
|
| 450 |
- go func() {
|
|
| 451 |
- if err := newProcess.Wait(); err != nil {
|
|
| 452 |
- logger.WithError(err).Error("failed to wait for process")
|
|
| 453 |
- } |
|
| 454 |
- if err := newProcess.Close(); err != nil {
|
|
| 455 |
- logger.WithError(err).Error("failed to clean process resources")
|
|
| 456 |
- } |
|
| 457 |
- }() |
|
| 458 |
- } |
|
| 459 |
- }() |
|
| 460 |
- |
|
| 461 |
- pid := newProcess.Pid() |
|
| 462 |
- logger.WithField("pid", pid).Debug("init process started")
|
|
| 463 |
- |
|
| 464 |
- dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal) |
|
| 465 |
- if err != nil {
|
|
| 466 |
- logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 467 |
- return nil, err |
|
| 468 |
- } |
|
| 469 |
- _, err = attachStdio(dio) |
|
| 470 |
- if err != nil {
|
|
| 471 |
- logger.WithError(err).Error("failed to attach stdio")
|
|
| 472 |
- return nil, err |
|
| 473 |
- } |
|
| 474 |
- |
|
| 475 |
- t := &task{process{
|
|
| 476 |
- id: ctr.id, |
|
| 477 |
- ctr: ctr, |
|
| 478 |
- hcsProcess: newProcess, |
|
| 479 |
- waitCh: make(chan struct{}),
|
|
| 480 |
- }} |
|
| 481 |
- |
|
| 482 |
- // All fallible operations have succeeded so it is now safe to set the |
|
| 483 |
- // container's current task. |
|
| 484 |
- ctr.task = t |
|
| 485 |
- |
|
| 486 |
- // Spin up a goroutine to notify the backend and clean up resources when |
|
| 487 |
- // the task exits. Defer until after the start event is sent so that the |
|
| 488 |
- // exit event is not sent out-of-order. |
|
| 489 |
- defer func() { go t.reap() }()
|
|
| 490 |
- |
|
| 491 |
- // Generate the associated event |
|
| 492 |
- ctr.client.eventQ.Append(ctr.id, func() {
|
|
| 493 |
- ei := libcontainerdtypes.EventInfo{
|
|
| 494 |
- ContainerID: ctr.id, |
|
| 495 |
- ProcessID: t.id, |
|
| 496 |
- Pid: uint32(pid), |
|
| 497 |
- } |
|
| 498 |
- ctr.client.logger.WithFields(log.Fields{
|
|
| 499 |
- "container": ctr.id, |
|
| 500 |
- "event": libcontainerdtypes.EventStart, |
|
| 501 |
- "event-info": ei, |
|
| 502 |
- }).Info("sending event")
|
|
| 503 |
- err := ctr.client.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei) |
|
| 504 |
- if err != nil {
|
|
| 505 |
- ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 506 |
- "container": ei.ContainerID, |
|
| 507 |
- "event": libcontainerdtypes.EventStart, |
|
| 508 |
- "event-info": ei, |
|
| 509 |
- }).Error("failed to process event")
|
|
| 510 |
- } |
|
| 511 |
- }) |
|
| 512 |
- logger.Debug("start() completed")
|
|
| 513 |
- return t, nil |
|
| 514 |
-} |
|
| 515 |
- |
|
| 516 |
-func (*task) Start(context.Context) error {
|
|
| 517 |
- // No-op on Windows. |
|
| 518 |
- return nil |
|
| 519 |
-} |
|
| 520 |
- |
|
| 521 |
-func (ctr *container) Task(context.Context) (libcontainerdtypes.Task, error) {
|
|
| 522 |
- ctr.mu.Lock() |
|
| 523 |
- defer ctr.mu.Unlock() |
|
| 524 |
- if ctr.task == nil {
|
|
| 525 |
- return nil, errdefs.NotFound(cerrdefs.ErrNotFound) |
|
| 526 |
- } |
|
| 527 |
- return ctr.task, nil |
|
| 528 |
-} |
|
| 529 |
- |
|
| 530 |
-// setCommandLineAndArgs configures the HCS ProcessConfig based on an OCI process spec |
|
| 531 |
-func setCommandLineAndArgs(process *specs.Process, createProcessParms *hcsshim.ProcessConfig) {
|
|
| 532 |
- if process.CommandLine != "" {
|
|
| 533 |
- createProcessParms.CommandLine = process.CommandLine |
|
| 534 |
- } else {
|
|
| 535 |
- createProcessParms.CommandLine = system.EscapeArgs(process.Args) |
|
| 536 |
- } |
|
| 537 |
-} |
|
| 538 |
- |
|
| 539 |
-func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
|
|
| 540 |
- stdin, stdout, stderr, err := newProcess.Stdio() |
|
| 541 |
- if err != nil {
|
|
| 542 |
- return nil, err |
|
| 543 |
- } |
|
| 544 |
- |
|
| 545 |
- dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal) |
|
| 546 |
- |
|
| 547 |
- // Convert io.ReadClosers to io.Readers |
|
| 548 |
- if stdout != nil {
|
|
| 549 |
- dio.Stdout = io.NopCloser(&autoClosingReader{ReadCloser: stdout})
|
|
| 550 |
- } |
|
| 551 |
- if stderr != nil {
|
|
| 552 |
- dio.Stderr = io.NopCloser(&autoClosingReader{ReadCloser: stderr})
|
|
| 553 |
- } |
|
| 554 |
- return dio, nil |
|
| 555 |
-} |
|
| 556 |
- |
|
| 557 |
-// Exec launches a process in a running container. |
|
| 558 |
-// |
|
| 559 |
-// The processID argument is entirely informational. As there is no mechanism |
|
| 560 |
-// (exposed through the libcontainerd interfaces) to enumerate or reference an |
|
| 561 |
-// exec'd process by ID, uniqueness is not currently enforced. |
|
| 562 |
-func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (_ libcontainerdtypes.Process, retErr error) {
|
|
| 563 |
- hcsContainer, err := t.getHCSContainer() |
|
| 564 |
- if err != nil {
|
|
| 565 |
- return nil, err |
|
| 566 |
- } |
|
| 567 |
- logger := t.ctr.client.logger.WithFields(log.Fields{
|
|
| 568 |
- "container": t.ctr.id, |
|
| 569 |
- "exec": processID, |
|
| 570 |
- }) |
|
| 571 |
- |
|
| 572 |
- // Note we always tell HCS to |
|
| 573 |
- // create stdout as it's required regardless of '-i' or '-t' options, so that |
|
| 574 |
- // docker can always grab the output through logs. We also tell HCS to always |
|
| 575 |
- // create stdin, even if it's not used - it will be closed shortly. Stderr |
|
| 576 |
- // is only created if it we're not -t. |
|
| 577 |
- createProcessParms := &hcsshim.ProcessConfig{
|
|
| 578 |
- CreateStdInPipe: true, |
|
| 579 |
- CreateStdOutPipe: true, |
|
| 580 |
- CreateStdErrPipe: !spec.Terminal, |
|
| 581 |
- } |
|
| 582 |
- if spec.Terminal {
|
|
| 583 |
- createProcessParms.EmulateConsole = true |
|
| 584 |
- if spec.ConsoleSize != nil {
|
|
| 585 |
- createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height) |
|
| 586 |
- createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width) |
|
| 587 |
- } |
|
| 588 |
- } |
|
| 589 |
- |
|
| 590 |
- // Take working directory from the process to add if it is defined, |
|
| 591 |
- // otherwise take from the first process. |
|
| 592 |
- if spec.Cwd != "" {
|
|
| 593 |
- createProcessParms.WorkingDirectory = spec.Cwd |
|
| 594 |
- } else {
|
|
| 595 |
- createProcessParms.WorkingDirectory = t.ctr.ociSpec.Process.Cwd |
|
| 596 |
- } |
|
| 597 |
- |
|
| 598 |
- // Configure the environment for the process |
|
| 599 |
- createProcessParms.Environment = setupEnvironmentVariables(spec.Env) |
|
| 600 |
- |
|
| 601 |
- // Configure the CommandLine/CommandArgs |
|
| 602 |
- setCommandLineAndArgs(spec, createProcessParms) |
|
| 603 |
- logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
|
|
| 604 |
- |
|
| 605 |
- createProcessParms.User = spec.User.Username |
|
| 606 |
- |
|
| 607 |
- // Start the command running in the container. |
|
| 608 |
- newProcess, err := hcsContainer.CreateProcess(createProcessParms) |
|
| 609 |
- if err != nil {
|
|
| 610 |
- logger.WithError(err).Errorf("exec's CreateProcess() failed")
|
|
| 611 |
- return nil, err |
|
| 612 |
- } |
|
| 613 |
- defer func() {
|
|
| 614 |
- if retErr != nil {
|
|
| 615 |
- if err := newProcess.Kill(); err != nil {
|
|
| 616 |
- logger.WithError(err).Error("failed to kill process")
|
|
| 617 |
- } |
|
| 618 |
- go func() {
|
|
| 619 |
- if err := newProcess.Wait(); err != nil {
|
|
| 620 |
- logger.WithError(err).Error("failed to wait for process")
|
|
| 621 |
- } |
|
| 622 |
- if err := newProcess.Close(); err != nil {
|
|
| 623 |
- logger.WithError(err).Error("failed to clean process resources")
|
|
| 624 |
- } |
|
| 625 |
- }() |
|
| 626 |
- } |
|
| 627 |
- }() |
|
| 628 |
- |
|
| 629 |
- dio, err := newIOFromProcess(newProcess, spec.Terminal) |
|
| 630 |
- if err != nil {
|
|
| 631 |
- logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 632 |
- return nil, err |
|
| 633 |
- } |
|
| 634 |
- // Tell the engine to attach streams back to the client |
|
| 635 |
- _, err = attachStdio(dio) |
|
| 636 |
- if err != nil {
|
|
| 637 |
- return nil, err |
|
| 638 |
- } |
|
| 639 |
- |
|
| 640 |
- p := &process{
|
|
| 641 |
- id: processID, |
|
| 642 |
- ctr: t.ctr, |
|
| 643 |
- hcsProcess: newProcess, |
|
| 644 |
- waitCh: make(chan struct{}),
|
|
| 645 |
- } |
|
| 646 |
- |
|
| 647 |
- // Spin up a goroutine to notify the backend and clean up resources when |
|
| 648 |
- // the process exits. Defer until after the start event is sent so that |
|
| 649 |
- // the exit event is not sent out-of-order. |
|
| 650 |
- defer func() { go p.reap() }()
|
|
| 651 |
- |
|
| 652 |
- pid := newProcess.Pid() |
|
| 653 |
- t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 654 |
- ei := libcontainerdtypes.EventInfo{
|
|
| 655 |
- ContainerID: t.ctr.id, |
|
| 656 |
- ProcessID: p.id, |
|
| 657 |
- Pid: uint32(pid), |
|
| 658 |
- } |
|
| 659 |
- t.ctr.client.logger.WithFields(log.Fields{
|
|
| 660 |
- "container": t.ctr.id, |
|
| 661 |
- "event": libcontainerdtypes.EventExecAdded, |
|
| 662 |
- "event-info": ei, |
|
| 663 |
- }).Info("sending event")
|
|
| 664 |
- err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecAdded, ei) |
|
| 665 |
- if err != nil {
|
|
| 666 |
- t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 667 |
- "container": t.ctr.id, |
|
| 668 |
- "event": libcontainerdtypes.EventExecAdded, |
|
| 669 |
- "event-info": ei, |
|
| 670 |
- }).Error("failed to process event")
|
|
| 671 |
- } |
|
| 672 |
- err = t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventExecStarted, ei) |
|
| 673 |
- if err != nil {
|
|
| 674 |
- t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 675 |
- "container": t.ctr.id, |
|
| 676 |
- "event": libcontainerdtypes.EventExecStarted, |
|
| 677 |
- "event-info": ei, |
|
| 678 |
- }).Error("failed to process event")
|
|
| 679 |
- } |
|
| 680 |
- }) |
|
| 681 |
- |
|
| 682 |
- return p, nil |
|
| 683 |
-} |
|
| 684 |
- |
|
| 685 |
-func (p *process) Pid() uint32 {
|
|
| 686 |
- p.mu.Lock() |
|
| 687 |
- hcsProcess := p.hcsProcess |
|
| 688 |
- p.mu.Unlock() |
|
| 689 |
- if hcsProcess == nil {
|
|
| 690 |
- return 0 |
|
| 691 |
- } |
|
| 692 |
- return uint32(hcsProcess.Pid()) |
|
| 693 |
-} |
|
| 694 |
- |
|
| 695 |
-func (p *process) Kill(_ context.Context, signal syscall.Signal) error {
|
|
| 696 |
- p.mu.Lock() |
|
| 697 |
- hcsProcess := p.hcsProcess |
|
| 698 |
- p.mu.Unlock() |
|
| 699 |
- if hcsProcess == nil {
|
|
| 700 |
- return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 701 |
- } |
|
| 702 |
- return hcsProcess.Kill() |
|
| 703 |
-} |
|
| 704 |
- |
|
| 705 |
-// Kill handles `docker stop` on Windows. While Linux has support for |
|
| 706 |
-// the full range of signals, signals aren't really implemented on Windows. |
|
| 707 |
-// We fake supporting regular stop and -9 to force kill. |
|
| 708 |
-func (t *task) Kill(_ context.Context, signal syscall.Signal) error {
|
|
| 709 |
- hcsContainer, err := t.getHCSContainer() |
|
| 710 |
- if err != nil {
|
|
| 711 |
- return err |
|
| 712 |
- } |
|
| 713 |
- |
|
| 714 |
- logger := t.ctr.client.logger.WithFields(log.Fields{
|
|
| 715 |
- "container": t.ctr.id, |
|
| 716 |
- "process": t.id, |
|
| 717 |
- "pid": t.Pid(), |
|
| 718 |
- "signal": signal, |
|
| 719 |
- }) |
|
| 720 |
- logger.Debug("Signal()")
|
|
| 721 |
- |
|
| 722 |
- var op string |
|
| 723 |
- if signal == syscall.SIGKILL {
|
|
| 724 |
- // Terminate the compute system |
|
| 725 |
- t.ctr.mu.Lock() |
|
| 726 |
- t.ctr.terminateInvoked = true |
|
| 727 |
- t.ctr.mu.Unlock() |
|
| 728 |
- op, err = "terminate", hcsContainer.Terminate() |
|
| 729 |
- } else {
|
|
| 730 |
- // Shut down the container |
|
| 731 |
- op, err = "shutdown", hcsContainer.Shutdown() |
|
| 732 |
- } |
|
| 733 |
- if err != nil {
|
|
| 734 |
- if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
|
|
| 735 |
- // ignore errors |
|
| 736 |
- logger.WithError(err).Errorf("failed to %s hccshim container", op)
|
|
| 737 |
- } |
|
| 738 |
- } |
|
| 739 |
- |
|
| 740 |
- return nil |
|
| 741 |
-} |
|
| 742 |
- |
|
| 743 |
-// Resize handles a CLI event to resize an interactive docker run or docker |
|
| 744 |
-// exec window. |
|
| 745 |
-func (p *process) Resize(_ context.Context, width, height uint32) error {
|
|
| 746 |
- p.mu.Lock() |
|
| 747 |
- hcsProcess := p.hcsProcess |
|
| 748 |
- p.mu.Unlock() |
|
| 749 |
- if hcsProcess == nil {
|
|
| 750 |
- return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 751 |
- } |
|
| 752 |
- |
|
| 753 |
- p.ctr.client.logger.WithFields(log.Fields{
|
|
| 754 |
- "container": p.ctr.id, |
|
| 755 |
- "process": p.id, |
|
| 756 |
- "height": height, |
|
| 757 |
- "width": width, |
|
| 758 |
- "pid": hcsProcess.Pid(), |
|
| 759 |
- }).Debug("resizing")
|
|
| 760 |
- return hcsProcess.ResizeConsole(uint16(width), uint16(height)) |
|
| 761 |
-} |
|
| 762 |
- |
|
| 763 |
-func (p *process) CloseStdin(context.Context) error {
|
|
| 764 |
- p.mu.Lock() |
|
| 765 |
- hcsProcess := p.hcsProcess |
|
| 766 |
- p.mu.Unlock() |
|
| 767 |
- if hcsProcess == nil {
|
|
| 768 |
- return errors.WithStack(errdefs.NotFound(errors.New("process not found")))
|
|
| 769 |
- } |
|
| 770 |
- |
|
| 771 |
- return hcsProcess.CloseStdin() |
|
| 772 |
-} |
|
| 773 |
- |
|
| 774 |
-// Pause handles pause requests for containers |
|
| 775 |
-func (t *task) Pause(_ context.Context) error {
|
|
| 776 |
- if t.ctr.ociSpec.Windows.HyperV == nil {
|
|
| 777 |
- return errdefs.NotImplemented(errors.WithStack(errors.New("not implemented for containers using process isolation")))
|
|
| 778 |
- } |
|
| 779 |
- |
|
| 780 |
- t.ctr.mu.Lock() |
|
| 781 |
- defer t.ctr.mu.Unlock() |
|
| 782 |
- |
|
| 783 |
- if err := t.assertIsCurrentTask(); err != nil {
|
|
| 784 |
- return err |
|
| 785 |
- } |
|
| 786 |
- if t.ctr.hcsContainer == nil {
|
|
| 787 |
- return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 788 |
- } |
|
| 789 |
- if err := t.ctr.hcsContainer.Pause(); err != nil {
|
|
| 790 |
- return err |
|
| 791 |
- } |
|
| 792 |
- |
|
| 793 |
- t.ctr.isPaused = true |
|
| 794 |
- |
|
| 795 |
- t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 796 |
- err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
|
|
| 797 |
- ContainerID: t.ctr.id, |
|
| 798 |
- ProcessID: t.id, |
|
| 799 |
- }) |
|
| 800 |
- t.ctr.client.logger.WithFields(log.Fields{
|
|
| 801 |
- "container": t.ctr.id, |
|
| 802 |
- "event": libcontainerdtypes.EventPaused, |
|
| 803 |
- }).Info("sending event")
|
|
| 804 |
- if err != nil {
|
|
| 805 |
- t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 806 |
- "container": t.ctr.id, |
|
| 807 |
- "event": libcontainerdtypes.EventPaused, |
|
| 808 |
- }).Error("failed to process event")
|
|
| 809 |
- } |
|
| 810 |
- }) |
|
| 811 |
- |
|
| 812 |
- return nil |
|
| 813 |
-} |
|
| 814 |
- |
|
| 815 |
-// Resume handles resume requests for containers |
|
| 816 |
-func (t *task) Resume(ctx context.Context) error {
|
|
| 817 |
- if t.ctr.ociSpec.Windows.HyperV == nil {
|
|
| 818 |
- return errors.New("cannot resume Windows Server Containers")
|
|
| 819 |
- } |
|
| 820 |
- |
|
| 821 |
- t.ctr.mu.Lock() |
|
| 822 |
- defer t.ctr.mu.Unlock() |
|
| 823 |
- |
|
| 824 |
- if err := t.assertIsCurrentTask(); err != nil {
|
|
| 825 |
- return err |
|
| 826 |
- } |
|
| 827 |
- if t.ctr.hcsContainer == nil {
|
|
| 828 |
- return errdefs.NotFound(errors.WithStack(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 829 |
- } |
|
| 830 |
- if err := t.ctr.hcsContainer.Resume(); err != nil {
|
|
| 831 |
- return err |
|
| 832 |
- } |
|
| 833 |
- |
|
| 834 |
- t.ctr.isPaused = false |
|
| 835 |
- |
|
| 836 |
- t.ctr.client.eventQ.Append(t.ctr.id, func() {
|
|
| 837 |
- err := t.ctr.client.backend.ProcessEvent(t.ctr.id, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
|
|
| 838 |
- ContainerID: t.ctr.id, |
|
| 839 |
- ProcessID: t.id, |
|
| 840 |
- }) |
|
| 841 |
- t.ctr.client.logger.WithFields(log.Fields{
|
|
| 842 |
- "container": t.ctr.id, |
|
| 843 |
- "event": libcontainerdtypes.EventResumed, |
|
| 844 |
- }).Info("sending event")
|
|
| 845 |
- if err != nil {
|
|
| 846 |
- t.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 847 |
- "container": t.ctr.id, |
|
| 848 |
- "event": libcontainerdtypes.EventResumed, |
|
| 849 |
- }).Error("failed to process event")
|
|
| 850 |
- } |
|
| 851 |
- }) |
|
| 852 |
- |
|
| 853 |
- return nil |
|
| 854 |
-} |
|
| 855 |
- |
|
| 856 |
-// Stats handles stats requests for containers |
|
| 857 |
-func (t *task) Stats(_ context.Context) (*libcontainerdtypes.Stats, error) {
|
|
| 858 |
- hc, err := t.getHCSContainer() |
|
| 859 |
- if err != nil {
|
|
| 860 |
- return nil, err |
|
| 861 |
- } |
|
| 862 |
- |
|
| 863 |
- readAt := time.Now() |
|
| 864 |
- s, err := hc.Statistics() |
|
| 865 |
- if err != nil {
|
|
| 866 |
- return nil, err |
|
| 867 |
- } |
|
| 868 |
- return &libcontainerdtypes.Stats{
|
|
| 869 |
- Read: readAt, |
|
| 870 |
- HCSStats: &s, |
|
| 871 |
- }, nil |
|
| 872 |
-} |
|
| 873 |
- |
|
| 874 |
-// LoadContainer is the handler for restoring a container |
|
| 875 |
-func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtypes.Container, error) {
|
|
| 876 |
- c.logger.WithField("container", id).Debug("LoadContainer()")
|
|
| 877 |
- |
|
| 878 |
- // TODO Windows: On RS1, a re-attach isn't possible. |
|
| 879 |
- // However, there is a scenario in which there is an issue. |
|
| 880 |
- // Consider a background container. The daemon dies unexpectedly. |
|
| 881 |
- // HCS will still have the compute service alive and running. |
|
| 882 |
- // For consistence, we call in to shoot it regardless if HCS knows about it |
|
| 883 |
- // We explicitly just log a warning if the terminate fails. |
|
| 884 |
- // Then we tell the backend the container exited. |
|
| 885 |
- hc, err := hcsshim.OpenContainer(id) |
|
| 886 |
- if err != nil {
|
|
| 887 |
- return nil, errdefs.NotFound(errors.New("container not found"))
|
|
| 888 |
- } |
|
| 889 |
- const terminateTimeout = time.Minute * 2 |
|
| 890 |
- err = hc.Terminate() |
|
| 891 |
- |
|
| 892 |
- if hcsshim.IsPending(err) {
|
|
| 893 |
- err = hc.WaitTimeout(terminateTimeout) |
|
| 894 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 895 |
- err = nil |
|
| 896 |
- } |
|
| 897 |
- |
|
| 898 |
- if err != nil {
|
|
| 899 |
- c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
|
|
| 900 |
- return nil, err |
|
| 901 |
- } |
|
| 902 |
- return &container{
|
|
| 903 |
- client: c, |
|
| 904 |
- hcsContainer: hc, |
|
| 905 |
- id: id, |
|
| 906 |
- }, nil |
|
| 907 |
-} |
|
| 908 |
- |
|
| 909 |
-// AttachTask is only called by the daemon when restoring containers. As |
|
| 910 |
-// re-attach isn't possible (see LoadContainer), a NotFound error is |
|
| 911 |
-// unconditionally returned to allow restore to make progress. |
|
| 912 |
-func (*container) AttachTask(context.Context, libcontainerdtypes.StdioCallback) (libcontainerdtypes.Task, error) {
|
|
| 913 |
- return nil, errdefs.NotFound(cerrdefs.ErrNotImplemented) |
|
| 914 |
-} |
|
| 915 |
- |
|
| 916 |
-// Pids returns a list of process IDs running in a container. It is not |
|
| 917 |
-// implemented on Windows. |
|
| 918 |
-func (t *task) Pids(context.Context) ([]containerd.ProcessInfo, error) {
|
|
| 919 |
- return nil, errors.New("not implemented on Windows")
|
|
| 920 |
-} |
|
| 921 |
- |
|
| 922 |
-// Summary returns a summary of the processes running in a container. |
|
| 923 |
-// This is present in Windows to support docker top. In linux, the |
|
| 924 |
-// engine shells out to ps to get process information. On Windows, as |
|
| 925 |
-// the containers could be Hyper-V containers, they would not be |
|
| 926 |
-// visible on the container host. However, libcontainerd does have |
|
| 927 |
-// that information. |
|
| 928 |
-func (t *task) Summary(_ context.Context) ([]libcontainerdtypes.Summary, error) {
|
|
| 929 |
- hc, err := t.getHCSContainer() |
|
| 930 |
- if err != nil {
|
|
| 931 |
- return nil, err |
|
| 932 |
- } |
|
| 933 |
- |
|
| 934 |
- p, err := hc.ProcessList() |
|
| 935 |
- if err != nil {
|
|
| 936 |
- return nil, err |
|
| 937 |
- } |
|
| 938 |
- |
|
| 939 |
- pl := make([]libcontainerdtypes.Summary, len(p)) |
|
| 940 |
- for i := range p {
|
|
| 941 |
- pl[i] = libcontainerdtypes.Summary{
|
|
| 942 |
- ImageName: p[i].ImageName, |
|
| 943 |
- CreatedAt: timestamppb.New(p[i].CreateTimestamp), |
|
| 944 |
- KernelTime_100Ns: p[i].KernelTime100ns, |
|
| 945 |
- MemoryCommitBytes: p[i].MemoryCommitBytes, |
|
| 946 |
- MemoryWorkingSetPrivateBytes: p[i].MemoryWorkingSetPrivateBytes, |
|
| 947 |
- MemoryWorkingSetSharedBytes: p[i].MemoryWorkingSetSharedBytes, |
|
| 948 |
- ProcessID: p[i].ProcessId, |
|
| 949 |
- UserTime_100Ns: p[i].UserTime100ns, |
|
| 950 |
- ExecID: "", |
|
| 951 |
- } |
|
| 952 |
- } |
|
| 953 |
- return pl, nil |
|
| 954 |
-} |
|
| 955 |
- |
|
| 956 |
-func (p *process) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
|
|
| 957 |
- select {
|
|
| 958 |
- case <-ctx.Done(): |
|
| 959 |
- return nil, errors.WithStack(ctx.Err()) |
|
| 960 |
- case <-p.waitCh: |
|
| 961 |
- default: |
|
| 962 |
- return nil, errdefs.Conflict(errors.New("process is running"))
|
|
| 963 |
- } |
|
| 964 |
- return p.exited, nil |
|
| 965 |
-} |
|
| 966 |
- |
|
| 967 |
-func (t *task) Delete(ctx context.Context) (*containerd.ExitStatus, error) {
|
|
| 968 |
- select {
|
|
| 969 |
- case <-ctx.Done(): |
|
| 970 |
- return nil, errors.WithStack(ctx.Err()) |
|
| 971 |
- case <-t.waitCh: |
|
| 972 |
- default: |
|
| 973 |
- return nil, errdefs.Conflict(errors.New("container is not stopped"))
|
|
| 974 |
- } |
|
| 975 |
- |
|
| 976 |
- t.ctr.mu.Lock() |
|
| 977 |
- defer t.ctr.mu.Unlock() |
|
| 978 |
- if err := t.assertIsCurrentTask(); err != nil {
|
|
| 979 |
- return nil, err |
|
| 980 |
- } |
|
| 981 |
- t.ctr.task = nil |
|
| 982 |
- return t.exited, nil |
|
| 983 |
-} |
|
| 984 |
- |
|
| 985 |
-func (t *task) ForceDelete(ctx context.Context) error {
|
|
| 986 |
- select {
|
|
| 987 |
- case <-t.waitCh: // Task is already stopped. |
|
| 988 |
- _, err := t.Delete(ctx) |
|
| 989 |
- return err |
|
| 990 |
- default: |
|
| 991 |
- } |
|
| 992 |
- |
|
| 993 |
- if err := t.Kill(ctx, syscall.SIGKILL); err != nil {
|
|
| 994 |
- return errors.Wrap(err, "could not force-kill task") |
|
| 995 |
- } |
|
| 996 |
- |
|
| 997 |
- select {
|
|
| 998 |
- case <-ctx.Done(): |
|
| 999 |
- return ctx.Err() |
|
| 1000 |
- case <-t.waitCh: |
|
| 1001 |
- _, err := t.Delete(ctx) |
|
| 1002 |
- return err |
|
| 1003 |
- } |
|
| 1004 |
-} |
|
| 1005 |
- |
|
| 1006 |
-func (t *task) Status(ctx context.Context) (containerd.Status, error) {
|
|
| 1007 |
- select {
|
|
| 1008 |
- case <-t.waitCh: |
|
| 1009 |
- return containerd.Status{
|
|
| 1010 |
- Status: containerd.Stopped, |
|
| 1011 |
- ExitStatus: t.exited.ExitCode(), |
|
| 1012 |
- ExitTime: t.exited.ExitTime(), |
|
| 1013 |
- }, nil |
|
| 1014 |
- default: |
|
| 1015 |
- } |
|
| 1016 |
- |
|
| 1017 |
- t.ctr.mu.Lock() |
|
| 1018 |
- defer t.ctr.mu.Unlock() |
|
| 1019 |
- s := containerd.Running |
|
| 1020 |
- if t.ctr.isPaused {
|
|
| 1021 |
- s = containerd.Paused |
|
| 1022 |
- } |
|
| 1023 |
- return containerd.Status{Status: s}, nil
|
|
| 1024 |
-} |
|
| 1025 |
- |
|
| 1026 |
-func (*task) UpdateResources(context.Context, *libcontainerdtypes.Resources) error {
|
|
| 1027 |
- // Updating resource isn't supported on Windows |
|
| 1028 |
- // but we should return nil for enabling updating container |
|
| 1029 |
- return nil |
|
| 1030 |
-} |
|
| 1031 |
- |
|
| 1032 |
-func (*task) CreateCheckpoint(context.Context, string, bool) error {
|
|
| 1033 |
- return errors.New("Windows: Containers do not support checkpoints")
|
|
| 1034 |
-} |
|
| 1035 |
- |
|
| 1036 |
-// assertIsCurrentTask returns a non-nil error if the task has been deleted. |
|
| 1037 |
-func (t *task) assertIsCurrentTask() error {
|
|
| 1038 |
- if t.ctr.task != t {
|
|
| 1039 |
- return errors.WithStack(errdefs.NotFound(fmt.Errorf("task %q not found", t.id)))
|
|
| 1040 |
- } |
|
| 1041 |
- return nil |
|
| 1042 |
-} |
|
| 1043 |
- |
|
| 1044 |
-// getHCSContainer returns a reference to the hcsshim Container for the task's |
|
| 1045 |
-// container if neither the task nor container have been deleted. |
|
| 1046 |
-// |
|
| 1047 |
-// t.ctr.mu must not be locked by the calling goroutine when calling this |
|
| 1048 |
-// function. |
|
| 1049 |
-func (t *task) getHCSContainer() (hcsshim.Container, error) {
|
|
| 1050 |
- t.ctr.mu.Lock() |
|
| 1051 |
- defer t.ctr.mu.Unlock() |
|
| 1052 |
- if err := t.assertIsCurrentTask(); err != nil {
|
|
| 1053 |
- return nil, err |
|
| 1054 |
- } |
|
| 1055 |
- hc := t.ctr.hcsContainer |
|
| 1056 |
- if hc == nil {
|
|
| 1057 |
- return nil, errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", t.ctr.id)))
|
|
| 1058 |
- } |
|
| 1059 |
- return hc, nil |
|
| 1060 |
-} |
|
| 1061 |
- |
|
| 1062 |
-// ctr mutex must be held when calling this function. |
|
| 1063 |
-func (ctr *container) shutdownContainer() error {
|
|
| 1064 |
- var err error |
|
| 1065 |
- const waitTimeout = time.Minute * 5 |
|
| 1066 |
- |
|
| 1067 |
- if !ctr.terminateInvoked {
|
|
| 1068 |
- err = ctr.hcsContainer.Shutdown() |
|
| 1069 |
- } |
|
| 1070 |
- |
|
| 1071 |
- if hcsshim.IsPending(err) || ctr.terminateInvoked {
|
|
| 1072 |
- err = ctr.hcsContainer.WaitTimeout(waitTimeout) |
|
| 1073 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1074 |
- err = nil |
|
| 1075 |
- } |
|
| 1076 |
- |
|
| 1077 |
- if err != nil {
|
|
| 1078 |
- ctr.client.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1079 |
- Debug("failed to shutdown container, terminating it")
|
|
| 1080 |
- terminateErr := ctr.terminateContainer() |
|
| 1081 |
- if terminateErr != nil {
|
|
| 1082 |
- ctr.client.logger.WithError(terminateErr).WithField("container", ctr.id).
|
|
| 1083 |
- Error("failed to shutdown container, and subsequent terminate also failed")
|
|
| 1084 |
- return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
|
|
| 1085 |
- } |
|
| 1086 |
- return err |
|
| 1087 |
- } |
|
| 1088 |
- |
|
| 1089 |
- return nil |
|
| 1090 |
-} |
|
| 1091 |
- |
|
| 1092 |
-// ctr mutex must be held when calling this function. |
|
| 1093 |
-func (ctr *container) terminateContainer() error {
|
|
| 1094 |
- const terminateTimeout = time.Minute * 5 |
|
| 1095 |
- ctr.terminateInvoked = true |
|
| 1096 |
- err := ctr.hcsContainer.Terminate() |
|
| 1097 |
- |
|
| 1098 |
- if hcsshim.IsPending(err) {
|
|
| 1099 |
- err = ctr.hcsContainer.WaitTimeout(terminateTimeout) |
|
| 1100 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1101 |
- err = nil |
|
| 1102 |
- } |
|
| 1103 |
- |
|
| 1104 |
- if err != nil {
|
|
| 1105 |
- ctr.client.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1106 |
- Debug("failed to terminate container")
|
|
| 1107 |
- return err |
|
| 1108 |
- } |
|
| 1109 |
- |
|
| 1110 |
- return nil |
|
| 1111 |
-} |
|
| 1112 |
- |
|
| 1113 |
-func (p *process) reap() {
|
|
| 1114 |
- logger := p.ctr.client.logger.WithFields(log.Fields{
|
|
| 1115 |
- "container": p.ctr.id, |
|
| 1116 |
- "process": p.id, |
|
| 1117 |
- }) |
|
| 1118 |
- |
|
| 1119 |
- var eventErr error |
|
| 1120 |
- |
|
| 1121 |
- // Block indefinitely for the process to exit. |
|
| 1122 |
- if err := p.hcsProcess.Wait(); err != nil {
|
|
| 1123 |
- if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1124 |
- logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
|
|
| 1125 |
- } |
|
| 1126 |
- // Fall through here, do not return. This ensures we tell the |
|
| 1127 |
- // docker engine that the process/container has exited to avoid |
|
| 1128 |
- // a container being dropped on the floor. |
|
| 1129 |
- } |
|
| 1130 |
- exitedAt := time.Now() |
|
| 1131 |
- |
|
| 1132 |
- exitCode, err := p.hcsProcess.ExitCode() |
|
| 1133 |
- if err != nil {
|
|
| 1134 |
- if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1135 |
- logger.WithError(err).Warnf("unable to get exit code for process")
|
|
| 1136 |
- } |
|
| 1137 |
- // Since we got an error retrieving the exit code, make sure that the |
|
| 1138 |
- // code we return doesn't incorrectly indicate success. |
|
| 1139 |
- exitCode = -1 |
|
| 1140 |
- |
|
| 1141 |
- // Fall through here, do not return. This ensures we tell the |
|
| 1142 |
- // docker engine that the process/container has exited to avoid |
|
| 1143 |
- // a container being dropped on the floor. |
|
| 1144 |
- } |
|
| 1145 |
- |
|
| 1146 |
- p.mu.Lock() |
|
| 1147 |
- hcsProcess := p.hcsProcess |
|
| 1148 |
- p.hcsProcess = nil |
|
| 1149 |
- p.mu.Unlock() |
|
| 1150 |
- |
|
| 1151 |
- if err := hcsProcess.Close(); err != nil {
|
|
| 1152 |
- logger.WithError(err).Warnf("failed to cleanup hcs process resources")
|
|
| 1153 |
- exitCode = -1 |
|
| 1154 |
- eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
|
|
| 1155 |
- } |
|
| 1156 |
- |
|
| 1157 |
- // Explicit locking is not required as reads from exited are |
|
| 1158 |
- // synchronized using waitCh. |
|
| 1159 |
- p.exited = containerd.NewExitStatus(uint32(exitCode), exitedAt, nil) |
|
| 1160 |
- close(p.waitCh) |
|
| 1161 |
- |
|
| 1162 |
- p.ctr.client.eventQ.Append(p.ctr.id, func() {
|
|
| 1163 |
- ei := libcontainerdtypes.EventInfo{
|
|
| 1164 |
- ContainerID: p.ctr.id, |
|
| 1165 |
- ProcessID: p.id, |
|
| 1166 |
- Pid: uint32(hcsProcess.Pid()), |
|
| 1167 |
- ExitCode: uint32(exitCode), |
|
| 1168 |
- ExitedAt: exitedAt, |
|
| 1169 |
- Error: eventErr, |
|
| 1170 |
- } |
|
| 1171 |
- p.ctr.client.logger.WithFields(log.Fields{
|
|
| 1172 |
- "container": p.ctr.id, |
|
| 1173 |
- "event": libcontainerdtypes.EventExit, |
|
| 1174 |
- "event-info": ei, |
|
| 1175 |
- }).Info("sending event")
|
|
| 1176 |
- err := p.ctr.client.backend.ProcessEvent(p.ctr.id, libcontainerdtypes.EventExit, ei) |
|
| 1177 |
- if err != nil {
|
|
| 1178 |
- p.ctr.client.logger.WithError(err).WithFields(log.Fields{
|
|
| 1179 |
- "container": p.ctr.id, |
|
| 1180 |
- "event": libcontainerdtypes.EventExit, |
|
| 1181 |
- "event-info": ei, |
|
| 1182 |
- }).Error("failed to process event")
|
|
| 1183 |
- } |
|
| 1184 |
- }) |
|
| 1185 |
-} |
|
| 1186 |
- |
|
| 1187 |
-func (ctr *container) Delete(context.Context) error {
|
|
| 1188 |
- ctr.mu.Lock() |
|
| 1189 |
- defer ctr.mu.Unlock() |
|
| 1190 |
- |
|
| 1191 |
- if ctr.hcsContainer == nil {
|
|
| 1192 |
- return errors.WithStack(errdefs.NotFound(fmt.Errorf("container %q not found", ctr.id)))
|
|
| 1193 |
- } |
|
| 1194 |
- |
|
| 1195 |
- // Check that there is no task currently running. |
|
| 1196 |
- if ctr.task != nil {
|
|
| 1197 |
- select {
|
|
| 1198 |
- case <-ctr.task.waitCh: |
|
| 1199 |
- default: |
|
| 1200 |
- return errors.WithStack(errdefs.Conflict(errors.New("container is not stopped")))
|
|
| 1201 |
- } |
|
| 1202 |
- } |
|
| 1203 |
- |
|
| 1204 |
- var ( |
|
| 1205 |
- logger = ctr.client.logger.WithFields(log.Fields{
|
|
| 1206 |
- "container": ctr.id, |
|
| 1207 |
- }) |
|
| 1208 |
- thisErr error |
|
| 1209 |
- ) |
|
| 1210 |
- |
|
| 1211 |
- if err := ctr.shutdownContainer(); err != nil {
|
|
| 1212 |
- logger.WithError(err).Warn("failed to shutdown container")
|
|
| 1213 |
- thisErr = errors.Wrap(err, "failed to shutdown container") |
|
| 1214 |
- } else {
|
|
| 1215 |
- logger.Debug("completed container shutdown")
|
|
| 1216 |
- } |
|
| 1217 |
- |
|
| 1218 |
- if err := ctr.hcsContainer.Close(); err != nil {
|
|
| 1219 |
- logger.WithError(err).Error("failed to clean hcs container resources")
|
|
| 1220 |
- thisErr = errors.Wrap(err, "failed to terminate container") |
|
| 1221 |
- } |
|
| 1222 |
- |
|
| 1223 |
- ctr.hcsContainer = nil |
|
| 1224 |
- return thisErr |
|
| 1225 |
-} |
| 1226 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,40 +0,0 @@ |
| 1 |
-package local |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io" |
|
| 5 |
- "sync" |
|
| 6 |
- |
|
| 7 |
- "github.com/Microsoft/hcsshim" |
|
| 8 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-type autoClosingReader struct {
|
|
| 12 |
- io.ReadCloser |
|
| 13 |
- sync.Once |
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func (r *autoClosingReader) Read(b []byte) (int, error) {
|
|
| 17 |
- n, err := r.ReadCloser.Read(b) |
|
| 18 |
- if err != nil {
|
|
| 19 |
- r.Once.Do(func() { r.ReadCloser.Close() })
|
|
| 20 |
- } |
|
| 21 |
- return n, err |
|
| 22 |
-} |
|
| 23 |
- |
|
| 24 |
-func createStdInCloser(pipe io.WriteCloser, process hcsshim.Process) io.WriteCloser {
|
|
| 25 |
- return ioutils.NewWriteCloserWrapper(pipe, func() error {
|
|
| 26 |
- if err := pipe.Close(); err != nil {
|
|
| 27 |
- return err |
|
| 28 |
- } |
|
| 29 |
- |
|
| 30 |
- err := process.CloseStdin() |
|
| 31 |
- if err != nil && !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyClosed(err) {
|
|
| 32 |
- // This error will occur if the compute system is currently shutting down |
|
| 33 |
- if perr, ok := err.(*hcsshim.ProcessError); ok && perr.Err != hcsshim.ErrVmcomputeOperationInvalidState {
|
|
| 34 |
- return err |
|
| 35 |
- } |
|
| 36 |
- } |
|
| 37 |
- |
|
| 38 |
- return nil |
|
| 39 |
- }) |
|
| 40 |
-} |
| 41 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,15 +0,0 @@ |
| 1 |
-package local |
|
| 2 |
- |
|
| 3 |
-import "strings" |
|
| 4 |
- |
|
| 5 |
-// setupEnvironmentVariables converts a string array of environment variables |
|
| 6 |
-// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc. |
|
| 7 |
-func setupEnvironmentVariables(a []string) map[string]string {
|
|
| 8 |
- r := make(map[string]string) |
|
| 9 |
- for _, s := range a {
|
|
| 10 |
- if k, v, ok := strings.Cut(s, "="); ok {
|
|
| 11 |
- r[k] = v |
|
| 12 |
- } |
|
| 13 |
- } |
|
| 14 |
- return r |
|
| 15 |
-} |
| 16 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,13 +0,0 @@ |
| 1 |
-package local |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-func TestEnvironmentParsing(t *testing.T) {
|
|
| 8 |
- env := []string{"foo=bar", "car=hat", "a=b=c"}
|
|
| 9 |
- result := setupEnvironmentVariables(env) |
|
| 10 |
- if len(result) != 3 || result["foo"] != "bar" || result["car"] != "hat" || result["a"] != "b=c" {
|
|
| 11 |
- t.Fatalf("Expected map[foo:bar car:hat a:b=c], got %v", result)
|
|
| 12 |
- } |
|
| 13 |
-} |