Signed-off-by: John Howard <jhoward@microsoft.com>
This is the first step in refactoring moby (dockerd) to use containerd on Windows.
Similar to the current model in Linux, this adds the option to enable it for runtime.
It does not switch the graphdriver to containerd snapshotters.
- Refactors libcontainerd to a series of subpackages so that either a
"local" containerd (1) or a "remote" (2) containerd can be loaded as opposed
to conditional compile as "local" for Windows and "remote" for Linux.
- Updates libcontainerd such that Windows has an option to allow the use of a
"remote" containerd. Here, it communicates over a named pipe using GRPC.
This is currently guarded behind the experimental flag, an environment variable,
and the providing of a pipename to connect to containerd.
- Infrastructure pieces such as under pkg/system to have helper functions for
determining whether containerd is being used.
(1) "local" containerd is what the daemon on Windows has used since inception.
It's not really containerd at all - it's simply local invocation of HCS APIs
directly in-process from the daemon through the Microsoft/hcsshim library.
(2) "remote" containerd is what docker on Linux uses for it's runtime. It means
that there is a separate containerd service running, and docker communicates over
GRPC to it.
To try this out, you will need to start with something like the following:
Window 1:
containerd --log-level debug
Window 2:
$env:DOCKER_WINDOWS_CONTAINERD=1
dockerd --experimental -D --containerd \\.\pipe\containerd-containerd
You will need the following binary from github.com/containerd/containerd in your path:
- containerd.exe
You will need the following binaries from github.com/Microsoft/hcsshim in your path:
- runhcs.exe
- containerd-shim-runhcs-v1.exe
For LCOW, it will require and initrd.img and kernel in `C:\Program Files\Linux Containers`.
This is no different to the current requirements. However, you may need updated binaries,
particularly initrd.img built from Microsoft/opengcs as (at the time of writing), Linuxkit
binaries are somewhat out of date.
Note that containerd and hcsshim for HCS v2 APIs do not yet support all the required
functionality needed for docker. This will come in time - this is a baby (although large)
step to migrating Docker on Windows to containerd.
Note that the HCS v2 APIs are only called on RS5+ builds. RS1..RS4 will still use
HCS v1 APIs as the v2 APIs were not fully developed enough on these builds to be usable.
This abstraction is done in HCSShim. (Referring specifically to runtime)
Note the LCOW graphdriver still uses HCS v1 APIs regardless.
Note also that this does not migrate docker to use containerd snapshotters
rather than graphdrivers. This needs to be done in conjunction with Linux also
doing the same switch.
| ... | ... |
@@ -163,31 +163,9 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
|
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 | 165 |
ctx, cancel := context.WithCancel(context.Background()) |
| 166 |
- if cli.Config.ContainerdAddr == "" && runtime.GOOS != "windows" {
|
|
| 167 |
- systemContainerdAddr, ok, err := systemContainerdRunning(cli.Config.IsRootless()) |
|
| 168 |
- if err != nil {
|
|
| 169 |
- cancel() |
|
| 170 |
- return errors.Wrap(err, "could not determine whether the system containerd is running") |
|
| 171 |
- } |
|
| 172 |
- if !ok {
|
|
| 173 |
- opts, err := cli.getContainerdDaemonOpts() |
|
| 174 |
- if err != nil {
|
|
| 175 |
- cancel() |
|
| 176 |
- return errors.Wrap(err, "failed to generate containerd options") |
|
| 177 |
- } |
|
| 178 |
- |
|
| 179 |
- r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...) |
|
| 180 |
- if err != nil {
|
|
| 181 |
- cancel() |
|
| 182 |
- return errors.Wrap(err, "failed to start containerd") |
|
| 183 |
- } |
|
| 184 |
- cli.Config.ContainerdAddr = r.Address() |
|
| 185 |
- |
|
| 186 |
- // Try to wait for containerd to shutdown |
|
| 187 |
- defer r.WaitTimeout(10 * time.Second) |
|
| 188 |
- } else {
|
|
| 189 |
- cli.Config.ContainerdAddr = systemContainerdAddr |
|
| 190 |
- } |
|
| 166 |
+ if err := cli.initContainerD(ctx); err != nil {
|
|
| 167 |
+ cancel() |
|
| 168 |
+ return err |
|
| 191 | 169 |
} |
| 192 | 170 |
defer cancel() |
| 193 | 171 |
|
| ... | ... |
@@ -3,12 +3,14 @@ |
| 3 | 3 |
package main |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 |
+ "context" |
|
| 6 | 7 |
"fmt" |
| 7 | 8 |
"net" |
| 8 | 9 |
"os" |
| 9 | 10 |
"os/signal" |
| 10 | 11 |
"path/filepath" |
| 11 | 12 |
"strconv" |
| 13 |
+ "time" |
|
| 12 | 14 |
|
| 13 | 15 |
"github.com/containerd/containerd/runtime/v1/linux" |
| 14 | 16 |
"github.com/docker/docker/cmd/dockerd/hack" |
| ... | ... |
@@ -18,6 +20,7 @@ import ( |
| 18 | 18 |
"github.com/docker/docker/pkg/homedir" |
| 19 | 19 |
"github.com/docker/docker/rootless" |
| 20 | 20 |
"github.com/docker/libnetwork/portallocator" |
| 21 |
+ "github.com/pkg/errors" |
|
| 21 | 22 |
"golang.org/x/sys/unix" |
| 22 | 23 |
) |
| 23 | 24 |
|
| ... | ... |
@@ -145,3 +148,31 @@ func newCgroupParent(config *config.Config) string {
|
| 145 | 145 |
} |
| 146 | 146 |
return cgroupParent |
| 147 | 147 |
} |
| 148 |
+ |
|
| 149 |
+func (cli *DaemonCli) initContainerD(ctx context.Context) error {
|
|
| 150 |
+ if cli.Config.ContainerdAddr == "" {
|
|
| 151 |
+ systemContainerdAddr, ok, err := systemContainerdRunning(cli.Config.IsRootless()) |
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ return errors.Wrap(err, "could not determine whether the system containerd is running") |
|
| 154 |
+ } |
|
| 155 |
+ if !ok {
|
|
| 156 |
+ opts, err := cli.getContainerdDaemonOpts() |
|
| 157 |
+ if err != nil {
|
|
| 158 |
+ return errors.Wrap(err, "failed to generate containerd options") |
|
| 159 |
+ } |
|
| 160 |
+ |
|
| 161 |
+ r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...) |
|
| 162 |
+ if err != nil {
|
|
| 163 |
+ return errors.Wrap(err, "failed to start containerd") |
|
| 164 |
+ } |
|
| 165 |
+ cli.Config.ContainerdAddr = r.Address() |
|
| 166 |
+ |
|
| 167 |
+ // Try to wait for containerd to shutdown |
|
| 168 |
+ defer r.WaitTimeout(10 * time.Second) |
|
| 169 |
+ } else {
|
|
| 170 |
+ cli.Config.ContainerdAddr = systemContainerdAddr |
|
| 171 |
+ } |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ return nil |
|
| 175 |
+} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "context" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"net" |
| 6 | 7 |
"os" |
| ... | ... |
@@ -8,6 +9,7 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
"github.com/docker/docker/daemon/config" |
| 10 | 10 |
"github.com/docker/docker/libcontainerd/supervisor" |
| 11 |
+ "github.com/docker/docker/pkg/system" |
|
| 11 | 12 |
"github.com/sirupsen/logrus" |
| 12 | 13 |
"golang.org/x/sys/windows" |
| 13 | 14 |
) |
| ... | ... |
@@ -90,3 +92,8 @@ func wrapListeners(proto string, ls []net.Listener) []net.Listener {
|
| 90 | 90 |
func newCgroupParent(config *config.Config) string {
|
| 91 | 91 |
return "" |
| 92 | 92 |
} |
| 93 |
+ |
|
| 94 |
+func (cli *DaemonCli) initContainerD(_ context.Context) error {
|
|
| 95 |
+ system.InitContainerdRuntime(cli.Config.Experimental, cli.Config.ContainerdAddr) |
|
| 96 |
+ return nil |
|
| 97 |
+} |
| ... | ... |
@@ -42,6 +42,7 @@ import ( |
| 42 | 42 |
"github.com/moby/buildkit/util/resolver" |
| 43 | 43 |
"github.com/moby/buildkit/util/tracing" |
| 44 | 44 |
"github.com/sirupsen/logrus" |
| 45 |
+ |
|
| 45 | 46 |
// register graph drivers |
| 46 | 47 |
_ "github.com/docker/docker/daemon/graphdriver/register" |
| 47 | 48 |
"github.com/docker/docker/daemon/stats" |
| ... | ... |
@@ -50,6 +51,7 @@ import ( |
| 50 | 50 |
"github.com/docker/docker/image" |
| 51 | 51 |
"github.com/docker/docker/layer" |
| 52 | 52 |
"github.com/docker/docker/libcontainerd" |
| 53 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 53 | 54 |
"github.com/docker/docker/pkg/idtools" |
| 54 | 55 |
"github.com/docker/docker/pkg/locker" |
| 55 | 56 |
"github.com/docker/docker/pkg/plugingetter" |
| ... | ... |
@@ -105,7 +107,7 @@ type Daemon struct {
|
| 105 | 105 |
pluginManager *plugin.Manager |
| 106 | 106 |
linkIndex *linkIndex |
| 107 | 107 |
containerdCli *containerd.Client |
| 108 |
- containerd libcontainerd.Client |
|
| 108 |
+ containerd libcontainerdtypes.Client |
|
| 109 | 109 |
defaultIsolation containertypes.Isolation // Default isolation mode on Windows |
| 110 | 110 |
clusterProvider cluster.Provider |
| 111 | 111 |
cluster Cluster |
| ... | ... |
@@ -351,11 +353,11 @@ func (daemon *Daemon) restore() error {
|
| 351 | 351 |
logrus.WithField("container", c.ID).WithField("state", s).
|
| 352 | 352 |
Info("restored container paused")
|
| 353 | 353 |
switch s {
|
| 354 |
- case libcontainerd.StatusPaused, libcontainerd.StatusPausing: |
|
| 354 |
+ case libcontainerdtypes.StatusPaused, libcontainerdtypes.StatusPausing: |
|
| 355 | 355 |
// nothing to do |
| 356 |
- case libcontainerd.StatusStopped: |
|
| 356 |
+ case libcontainerdtypes.StatusStopped: |
|
| 357 | 357 |
alive = false |
| 358 |
- case libcontainerd.StatusUnknown: |
|
| 358 |
+ case libcontainerdtypes.StatusUnknown: |
|
| 359 | 359 |
logrus.WithField("container", c.ID).
|
| 360 | 360 |
Error("Unknown status for container during restore")
|
| 361 | 361 |
default: |
| ... | ... |
@@ -502,6 +502,7 @@ func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig |
| 502 | 502 |
// conditionalMountOnStart is a platform specific helper function during the |
| 503 | 503 |
// container start to call mount. |
| 504 | 504 |
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
|
| 505 |
+ |
|
| 505 | 506 |
// Bail out now for Linux containers. We cannot mount the containers filesystem on the |
| 506 | 507 |
// host as it is a non-Windows filesystem. |
| 507 | 508 |
if system.LCOWSupported() && container.OS != "windows" {
|
| ... | ... |
@@ -519,6 +520,7 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er |
| 519 | 519 |
// conditionalUnmountOnCleanup is a platform specific helper function called |
| 520 | 520 |
// during the cleanup of a container to unmount. |
| 521 | 521 |
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
|
| 522 |
+ |
|
| 522 | 523 |
// Bail out now for Linux containers |
| 523 | 524 |
if system.LCOWSupported() && container.OS != "windows" {
|
| 524 | 525 |
return nil |
| ... | ... |
@@ -9,7 +9,7 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
containerpkg "github.com/docker/docker/container" |
| 11 | 11 |
"github.com/docker/docker/errdefs" |
| 12 |
- "github.com/docker/docker/libcontainerd" |
|
| 12 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 13 | 13 |
"github.com/docker/docker/pkg/signal" |
| 14 | 14 |
"github.com/pkg/errors" |
| 15 | 15 |
"github.com/sirupsen/logrus" |
| ... | ... |
@@ -177,5 +177,5 @@ func (daemon *Daemon) killPossiblyDeadProcess(container *containerpkg.Container, |
| 177 | 177 |
} |
| 178 | 178 |
|
| 179 | 179 |
func (daemon *Daemon) kill(c *containerpkg.Container, sig int) error {
|
| 180 |
- return daemon.containerd.SignalProcess(context.Background(), c.ID, libcontainerd.InitProcessName, sig) |
|
| 180 |
+ return daemon.containerd.SignalProcess(context.Background(), c.ID, libcontainerdtypes.InitProcessName, sig) |
|
| 181 | 181 |
} |
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 | 12 |
"github.com/docker/docker/container" |
| 13 |
- "github.com/docker/docker/libcontainerd" |
|
| 13 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 14 | 14 |
"github.com/docker/docker/restartmanager" |
| 15 | 15 |
"github.com/sirupsen/logrus" |
| 16 | 16 |
) |
| ... | ... |
@@ -27,14 +27,14 @@ func (daemon *Daemon) setStateCounter(c *container.Container) {
|
| 27 | 27 |
} |
| 28 | 28 |
|
| 29 | 29 |
// ProcessEvent is called by libcontainerd whenever an event occurs |
| 30 |
-func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libcontainerd.EventInfo) error {
|
|
| 30 |
+func (daemon *Daemon) ProcessEvent(id string, e libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
|
|
| 31 | 31 |
c, err := daemon.GetContainer(id) |
| 32 | 32 |
if c == nil || err != nil {
|
| 33 | 33 |
return fmt.Errorf("no such container: %s", id)
|
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 | 36 |
switch e {
|
| 37 |
- case libcontainerd.EventOOM: |
|
| 37 |
+ case libcontainerdtypes.EventOOM: |
|
| 38 | 38 |
// StateOOM is Linux specific and should never be hit on Windows |
| 39 | 39 |
if runtime.GOOS == "windows" {
|
| 40 | 40 |
return errors.New("received StateOOM from libcontainerd on Windows. This should never happen")
|
| ... | ... |
@@ -48,7 +48,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
daemon.LogContainerEvent(c, "oom") |
| 51 |
- case libcontainerd.EventExit: |
|
| 51 |
+ case libcontainerdtypes.EventExit: |
|
| 52 | 52 |
if int(ei.Pid) == c.Pid {
|
| 53 | 53 |
c.Lock() |
| 54 | 54 |
_, _, err := daemon.containerd.DeleteTask(context.Background(), c.ID) |
| ... | ... |
@@ -140,7 +140,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc |
| 140 | 140 |
"exec-pid": ei.Pid, |
| 141 | 141 |
}).Warn("Ignoring Exit Event, no such exec command found")
|
| 142 | 142 |
} |
| 143 |
- case libcontainerd.EventStart: |
|
| 143 |
+ case libcontainerdtypes.EventStart: |
|
| 144 | 144 |
c.Lock() |
| 145 | 145 |
defer c.Unlock() |
| 146 | 146 |
|
| ... | ... |
@@ -159,7 +159,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc |
| 159 | 159 |
daemon.LogContainerEvent(c, "start") |
| 160 | 160 |
} |
| 161 | 161 |
|
| 162 |
- case libcontainerd.EventPaused: |
|
| 162 |
+ case libcontainerdtypes.EventPaused: |
|
| 163 | 163 |
c.Lock() |
| 164 | 164 |
defer c.Unlock() |
| 165 | 165 |
|
| ... | ... |
@@ -172,7 +172,7 @@ func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libc |
| 172 | 172 |
} |
| 173 | 173 |
daemon.LogContainerEvent(c, "pause") |
| 174 | 174 |
} |
| 175 |
- case libcontainerd.EventResumed: |
|
| 175 |
+ case libcontainerdtypes.EventResumed: |
|
| 176 | 176 |
c.Lock() |
| 177 | 177 |
defer c.Unlock() |
| 178 | 178 |
|
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"io/ioutil" |
| 6 | 7 |
"path/filepath" |
| ... | ... |
@@ -15,7 +16,7 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/pkg/system" |
| 16 | 16 |
"github.com/opencontainers/runtime-spec/specs-go" |
| 17 | 17 |
"github.com/pkg/errors" |
| 18 |
- "golang.org/x/sys/windows" |
|
| 18 |
+ "github.com/sirupsen/logrus" |
|
| 19 | 19 |
"golang.org/x/sys/windows/registry" |
| 20 | 20 |
) |
| 21 | 21 |
|
| ... | ... |
@@ -25,6 +26,7 @@ const ( |
| 25 | 25 |
) |
| 26 | 26 |
|
| 27 | 27 |
func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 28 |
+ |
|
| 28 | 29 |
img, err := daemon.imageService.GetImage(string(c.ImageID)) |
| 29 | 30 |
if err != nil {
|
| 30 | 31 |
return nil, err |
| ... | ... |
@@ -219,11 +221,18 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 219 | 219 |
return nil, fmt.Errorf("Unsupported platform %q", img.OS)
|
| 220 | 220 |
} |
| 221 | 221 |
|
| 222 |
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
|
|
| 223 |
+ if b, err := json.Marshal(&s); err == nil {
|
|
| 224 |
+ logrus.Debugf("Generated spec: %s", string(b))
|
|
| 225 |
+ } |
|
| 226 |
+ } |
|
| 227 |
+ |
|
| 222 | 228 |
return (*specs.Spec)(&s), nil |
| 223 | 229 |
} |
| 224 | 230 |
|
| 225 | 231 |
// Sets the Windows-specific fields of the OCI spec |
| 226 | 232 |
func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
|
| 233 |
+ |
|
| 227 | 234 |
if len(s.Process.Cwd) == 0 {
|
| 228 | 235 |
// We default to C:\ to workaround the oddity of the case that the |
| 229 | 236 |
// default directory for cmd running as LocalSystem (or |
| ... | ... |
@@ -396,29 +405,37 @@ func setResourcesInSpec(c *container.Container, s *specs.Spec, isHyperV bool) {
|
| 396 | 396 |
} |
| 397 | 397 |
} |
| 398 | 398 |
} |
| 399 |
- memoryLimit := uint64(c.HostConfig.Memory) |
|
| 400 |
- s.Windows.Resources = &specs.WindowsResources{
|
|
| 401 |
- CPU: &specs.WindowsCPUResources{
|
|
| 399 |
+ |
|
| 400 |
+ if cpuMaximum != 0 || cpuShares != 0 || cpuCount != 0 {
|
|
| 401 |
+ if s.Windows.Resources == nil {
|
|
| 402 |
+ s.Windows.Resources = &specs.WindowsResources{}
|
|
| 403 |
+ } |
|
| 404 |
+ s.Windows.Resources.CPU = &specs.WindowsCPUResources{
|
|
| 402 | 405 |
Maximum: &cpuMaximum, |
| 403 | 406 |
Shares: &cpuShares, |
| 404 | 407 |
Count: &cpuCount, |
| 405 |
- }, |
|
| 406 |
- Memory: &specs.WindowsMemoryResources{
|
|
| 408 |
+ } |
|
| 409 |
+ } |
|
| 410 |
+ |
|
| 411 |
+ memoryLimit := uint64(c.HostConfig.Memory) |
|
| 412 |
+ if memoryLimit != 0 {
|
|
| 413 |
+ if s.Windows.Resources == nil {
|
|
| 414 |
+ s.Windows.Resources = &specs.WindowsResources{}
|
|
| 415 |
+ } |
|
| 416 |
+ s.Windows.Resources.Memory = &specs.WindowsMemoryResources{
|
|
| 407 | 417 |
Limit: &memoryLimit, |
| 408 |
- }, |
|
| 409 |
- Storage: &specs.WindowsStorageResources{
|
|
| 410 |
- Bps: &c.HostConfig.IOMaximumBandwidth, |
|
| 411 |
- Iops: &c.HostConfig.IOMaximumIOps, |
|
| 412 |
- }, |
|
| 418 |
+ } |
|
| 413 | 419 |
} |
| 414 |
-} |
|
| 415 | 420 |
|
| 416 |
-func escapeArgs(args []string) []string {
|
|
| 417 |
- escapedArgs := make([]string, len(args)) |
|
| 418 |
- for i, a := range args {
|
|
| 419 |
- escapedArgs[i] = windows.EscapeArg(a) |
|
| 421 |
+ if c.HostConfig.IOMaximumBandwidth != 0 || c.HostConfig.IOMaximumIOps != 0 {
|
|
| 422 |
+ if s.Windows.Resources == nil {
|
|
| 423 |
+ s.Windows.Resources = &specs.WindowsResources{}
|
|
| 424 |
+ } |
|
| 425 |
+ s.Windows.Resources.Storage = &specs.WindowsStorageResources{
|
|
| 426 |
+ Bps: &c.HostConfig.IOMaximumBandwidth, |
|
| 427 |
+ Iops: &c.HostConfig.IOMaximumIOps, |
|
| 428 |
+ } |
|
| 420 | 429 |
} |
| 421 |
- return escapedArgs |
|
| 422 | 430 |
} |
| 423 | 431 |
|
| 424 | 432 |
// mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig |
| ... | ... |
@@ -5,7 +5,7 @@ import ( |
| 5 | 5 |
"fmt" |
| 6 | 6 |
"time" |
| 7 | 7 |
|
| 8 |
- "github.com/docker/docker/libcontainerd" |
|
| 8 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 | 11 |
// ContainerResize changes the size of the TTY of the process running |
| ... | ... |
@@ -20,7 +20,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
|
| 20 | 20 |
return errNotRunning(container.ID) |
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 |
- if err = daemon.containerd.ResizeTerminal(context.Background(), container.ID, libcontainerd.InitProcessName, width, height); err == nil {
|
|
| 23 |
+ if err = daemon.containerd.ResizeTerminal(context.Background(), container.ID, libcontainerdtypes.InitProcessName, width, height); err == nil {
|
|
| 24 | 24 |
attributes := map[string]string{
|
| 25 | 25 |
"height": fmt.Sprintf("%d", height),
|
| 26 | 26 |
"width": fmt.Sprintf("%d", width),
|
| ... | ... |
@@ -1,11 +1,23 @@ |
| 1 | 1 |
package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" |
|
| 4 | 5 |
"github.com/Microsoft/opengcs/client" |
| 5 | 6 |
"github.com/docker/docker/container" |
| 7 |
+ "github.com/docker/docker/pkg/system" |
|
| 6 | 8 |
) |
| 7 | 9 |
|
| 8 | 10 |
func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Container) (interface{}, error) {
|
| 11 |
+ |
|
| 12 |
+ // Set the runtime options to debug regardless of current logging level. |
|
| 13 |
+ if system.ContainerdRuntimeSupported() {
|
|
| 14 |
+ opts := &options.Options{Debug: true}
|
|
| 15 |
+ return opts, nil |
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ // TODO @jhowardmsft (containerd) - Probably need to revisit LCOW options here |
|
| 19 |
+ // rather than blindly ignoring them. |
|
| 20 |
+ |
|
| 9 | 21 |
// LCOW options. |
| 10 | 22 |
if container.OS == "linux" {
|
| 11 | 23 |
config := &client.Config{}
|
| ... | ... |
@@ -4,12 +4,12 @@ import ( |
| 4 | 4 |
"time" |
| 5 | 5 |
|
| 6 | 6 |
"github.com/docker/docker/api/types/container" |
| 7 |
- "github.com/docker/docker/libcontainerd" |
|
| 7 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 8 | 8 |
"github.com/opencontainers/runtime-spec/specs-go" |
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 |
-func toContainerdResources(resources container.Resources) *libcontainerd.Resources {
|
|
| 12 |
- var r libcontainerd.Resources |
|
| 11 |
+func toContainerdResources(resources container.Resources) *libcontainerdtypes.Resources {
|
|
| 12 |
+ var r libcontainerdtypes.Resources |
|
| 13 | 13 |
|
| 14 | 14 |
r.BlockIO = &specs.LinuxBlockIO{
|
| 15 | 15 |
Weight: &resources.BlkioWeight, |
| ... | ... |
@@ -2,10 +2,10 @@ package daemon // import "github.com/docker/docker/daemon" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/docker/api/types/container" |
| 5 |
- "github.com/docker/docker/libcontainerd" |
|
| 5 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 6 | 6 |
) |
| 7 | 7 |
|
| 8 |
-func toContainerdResources(resources container.Resources) *libcontainerd.Resources {
|
|
| 8 |
+func toContainerdResources(resources container.Resources) *libcontainerdtypes.Resources {
|
|
| 9 | 9 |
// We don't support update, so do nothing |
| 10 | 10 |
return nil |
| 11 | 11 |
} |
| ... | ... |
@@ -7,7 +7,7 @@ import ( |
| 7 | 7 |
"time" |
| 8 | 8 |
|
| 9 | 9 |
"github.com/containerd/containerd" |
| 10 |
- "github.com/docker/docker/libcontainerd" |
|
| 10 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 11 | 11 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 12 | 12 |
) |
| 13 | 13 |
|
| ... | ... |
@@ -18,19 +18,19 @@ type MockContainerdClient struct {
|
| 18 | 18 |
func (c *MockContainerdClient) Version(ctx context.Context) (containerd.Version, error) {
|
| 19 | 19 |
return containerd.Version{}, nil
|
| 20 | 20 |
} |
| 21 |
-func (c *MockContainerdClient) Restore(ctx context.Context, containerID string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) {
|
|
| 21 |
+func (c *MockContainerdClient) Restore(ctx context.Context, containerID string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
|
|
| 22 | 22 |
return false, 0, nil |
| 23 | 23 |
} |
| 24 | 24 |
func (c *MockContainerdClient) Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error {
|
| 25 | 25 |
return nil |
| 26 | 26 |
} |
| 27 |
-func (c *MockContainerdClient) Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) {
|
|
| 27 |
+func (c *MockContainerdClient) Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error) {
|
|
| 28 | 28 |
return 0, nil |
| 29 | 29 |
} |
| 30 | 30 |
func (c *MockContainerdClient) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
|
| 31 | 31 |
return nil |
| 32 | 32 |
} |
| 33 |
-func (c *MockContainerdClient) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerd.StdioCallback) (int, error) {
|
|
| 33 |
+func (c *MockContainerdClient) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
|
|
| 34 | 34 |
return 0, nil |
| 35 | 35 |
} |
| 36 | 36 |
func (c *MockContainerdClient) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
|
| ... | ... |
@@ -41,23 +41,23 @@ func (c *MockContainerdClient) CloseStdin(ctx context.Context, containerID, proc |
| 41 | 41 |
} |
| 42 | 42 |
func (c *MockContainerdClient) Pause(ctx context.Context, containerID string) error { return nil }
|
| 43 | 43 |
func (c *MockContainerdClient) Resume(ctx context.Context, containerID string) error { return nil }
|
| 44 |
-func (c *MockContainerdClient) Stats(ctx context.Context, containerID string) (*libcontainerd.Stats, error) {
|
|
| 44 |
+func (c *MockContainerdClient) Stats(ctx context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
|
|
| 45 | 45 |
return nil, nil |
| 46 | 46 |
} |
| 47 | 47 |
func (c *MockContainerdClient) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
|
| 48 | 48 |
return nil, nil |
| 49 | 49 |
} |
| 50 |
-func (c *MockContainerdClient) Summary(ctx context.Context, containerID string) ([]libcontainerd.Summary, error) {
|
|
| 50 |
+func (c *MockContainerdClient) Summary(ctx context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
|
|
| 51 | 51 |
return nil, nil |
| 52 | 52 |
} |
| 53 | 53 |
func (c *MockContainerdClient) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
|
| 54 | 54 |
return 0, time.Time{}, nil
|
| 55 | 55 |
} |
| 56 | 56 |
func (c *MockContainerdClient) Delete(ctx context.Context, containerID string) error { return nil }
|
| 57 |
-func (c *MockContainerdClient) Status(ctx context.Context, containerID string) (libcontainerd.Status, error) {
|
|
| 57 |
+func (c *MockContainerdClient) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
|
|
| 58 | 58 |
return "null", nil |
| 59 | 59 |
} |
| 60 |
-func (c *MockContainerdClient) UpdateResources(ctx context.Context, containerID string, resources *libcontainerd.Resources) error {
|
|
| 60 |
+func (c *MockContainerdClient) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
|
|
| 61 | 61 |
return nil |
| 62 | 62 |
} |
| 63 | 63 |
func (c *MockContainerdClient) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
|
| 64 | 64 |
deleted file mode 100644 |
| ... | ... |
@@ -1,918 +0,0 @@ |
| 1 |
-// +build !windows |
|
| 2 |
- |
|
| 3 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "context" |
|
| 7 |
- "encoding/json" |
|
| 8 |
- "fmt" |
|
| 9 |
- "io" |
|
| 10 |
- "os" |
|
| 11 |
- "path/filepath" |
|
| 12 |
- "reflect" |
|
| 13 |
- "runtime" |
|
| 14 |
- "strings" |
|
| 15 |
- "sync" |
|
| 16 |
- "syscall" |
|
| 17 |
- "time" |
|
| 18 |
- |
|
| 19 |
- "github.com/containerd/containerd" |
|
| 20 |
- apievents "github.com/containerd/containerd/api/events" |
|
| 21 |
- "github.com/containerd/containerd/api/types" |
|
| 22 |
- "github.com/containerd/containerd/archive" |
|
| 23 |
- "github.com/containerd/containerd/cio" |
|
| 24 |
- "github.com/containerd/containerd/content" |
|
| 25 |
- containerderrors "github.com/containerd/containerd/errdefs" |
|
| 26 |
- "github.com/containerd/containerd/events" |
|
| 27 |
- "github.com/containerd/containerd/images" |
|
| 28 |
- "github.com/containerd/containerd/runtime/linux/runctypes" |
|
| 29 |
- "github.com/containerd/typeurl" |
|
| 30 |
- "github.com/docker/docker/errdefs" |
|
| 31 |
- "github.com/docker/docker/pkg/ioutils" |
|
| 32 |
- v1 "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 33 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 34 |
- "github.com/pkg/errors" |
|
| 35 |
- "github.com/sirupsen/logrus" |
|
| 36 |
- "google.golang.org/grpc/codes" |
|
| 37 |
- "google.golang.org/grpc/status" |
|
| 38 |
-) |
|
| 39 |
- |
|
| 40 |
-// InitProcessName is the name given to the first process of a |
|
| 41 |
-// container |
|
| 42 |
-const InitProcessName = "init" |
|
| 43 |
- |
|
| 44 |
-type container struct {
|
|
| 45 |
- mu sync.Mutex |
|
| 46 |
- |
|
| 47 |
- bundleDir string |
|
| 48 |
- ctr containerd.Container |
|
| 49 |
- task containerd.Task |
|
| 50 |
- execs map[string]containerd.Process |
|
| 51 |
- oomKilled bool |
|
| 52 |
-} |
|
| 53 |
- |
|
| 54 |
-func (c *container) setTask(t containerd.Task) {
|
|
| 55 |
- c.mu.Lock() |
|
| 56 |
- c.task = t |
|
| 57 |
- c.mu.Unlock() |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-func (c *container) getTask() containerd.Task {
|
|
| 61 |
- c.mu.Lock() |
|
| 62 |
- t := c.task |
|
| 63 |
- c.mu.Unlock() |
|
| 64 |
- return t |
|
| 65 |
-} |
|
| 66 |
- |
|
| 67 |
-func (c *container) addProcess(id string, p containerd.Process) {
|
|
| 68 |
- c.mu.Lock() |
|
| 69 |
- if c.execs == nil {
|
|
| 70 |
- c.execs = make(map[string]containerd.Process) |
|
| 71 |
- } |
|
| 72 |
- c.execs[id] = p |
|
| 73 |
- c.mu.Unlock() |
|
| 74 |
-} |
|
| 75 |
- |
|
| 76 |
-func (c *container) deleteProcess(id string) {
|
|
| 77 |
- c.mu.Lock() |
|
| 78 |
- delete(c.execs, id) |
|
| 79 |
- c.mu.Unlock() |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-func (c *container) getProcess(id string) containerd.Process {
|
|
| 83 |
- c.mu.Lock() |
|
| 84 |
- p := c.execs[id] |
|
| 85 |
- c.mu.Unlock() |
|
| 86 |
- return p |
|
| 87 |
-} |
|
| 88 |
- |
|
| 89 |
-func (c *container) setOOMKilled(killed bool) {
|
|
| 90 |
- c.mu.Lock() |
|
| 91 |
- c.oomKilled = killed |
|
| 92 |
- c.mu.Unlock() |
|
| 93 |
-} |
|
| 94 |
- |
|
| 95 |
-func (c *container) getOOMKilled() bool {
|
|
| 96 |
- c.mu.Lock() |
|
| 97 |
- killed := c.oomKilled |
|
| 98 |
- c.mu.Unlock() |
|
| 99 |
- return killed |
|
| 100 |
-} |
|
| 101 |
- |
|
| 102 |
-type client struct {
|
|
| 103 |
- sync.RWMutex // protects containers map |
|
| 104 |
- |
|
| 105 |
- client *containerd.Client |
|
| 106 |
- stateDir string |
|
| 107 |
- logger *logrus.Entry |
|
| 108 |
- ns string |
|
| 109 |
- |
|
| 110 |
- backend Backend |
|
| 111 |
- eventQ queue |
|
| 112 |
- containers map[string]*container |
|
| 113 |
-} |
|
| 114 |
- |
|
| 115 |
-// NewClient creates a new libcontainerd client from a containerd client |
|
| 116 |
-func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b Backend) (Client, error) {
|
|
| 117 |
- c := &client{
|
|
| 118 |
- client: cli, |
|
| 119 |
- stateDir: stateDir, |
|
| 120 |
- logger: logrus.WithField("module", "libcontainerd").WithField("namespace", ns),
|
|
| 121 |
- ns: ns, |
|
| 122 |
- backend: b, |
|
| 123 |
- containers: make(map[string]*container), |
|
| 124 |
- } |
|
| 125 |
- |
|
| 126 |
- go c.processEventStream(ctx, ns) |
|
| 127 |
- |
|
| 128 |
- return c, nil |
|
| 129 |
-} |
|
| 130 |
- |
|
| 131 |
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 132 |
- return c.client.Version(ctx) |
|
| 133 |
-} |
|
| 134 |
- |
|
| 135 |
-// Restore loads the containerd container. |
|
| 136 |
-// It should not be called concurrently with any other operation for the given ID. |
|
| 137 |
-func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (alive bool, pid int, err error) {
|
|
| 138 |
- c.Lock() |
|
| 139 |
- _, ok := c.containers[id] |
|
| 140 |
- if ok {
|
|
| 141 |
- c.Unlock() |
|
| 142 |
- return false, 0, errors.WithStack(newConflictError("id already in use"))
|
|
| 143 |
- } |
|
| 144 |
- |
|
| 145 |
- cntr := &container{}
|
|
| 146 |
- c.containers[id] = cntr |
|
| 147 |
- cntr.mu.Lock() |
|
| 148 |
- defer cntr.mu.Unlock() |
|
| 149 |
- |
|
| 150 |
- c.Unlock() |
|
| 151 |
- |
|
| 152 |
- defer func() {
|
|
| 153 |
- if err != nil {
|
|
| 154 |
- c.Lock() |
|
| 155 |
- delete(c.containers, id) |
|
| 156 |
- c.Unlock() |
|
| 157 |
- } |
|
| 158 |
- }() |
|
| 159 |
- |
|
| 160 |
- var dio *cio.DirectIO |
|
| 161 |
- defer func() {
|
|
| 162 |
- if err != nil && dio != nil {
|
|
| 163 |
- dio.Cancel() |
|
| 164 |
- dio.Close() |
|
| 165 |
- } |
|
| 166 |
- err = wrapError(err) |
|
| 167 |
- }() |
|
| 168 |
- |
|
| 169 |
- ctr, err := c.client.LoadContainer(ctx, id) |
|
| 170 |
- if err != nil {
|
|
| 171 |
- return false, -1, errors.WithStack(wrapError(err)) |
|
| 172 |
- } |
|
| 173 |
- |
|
| 174 |
- attachIO := func(fifos *cio.FIFOSet) (cio.IO, error) {
|
|
| 175 |
- // dio must be assigned to the previously defined dio for the defer above |
|
| 176 |
- // to handle cleanup |
|
| 177 |
- dio, err = cio.NewDirectIO(ctx, fifos) |
|
| 178 |
- if err != nil {
|
|
| 179 |
- return nil, err |
|
| 180 |
- } |
|
| 181 |
- return attachStdio(dio) |
|
| 182 |
- } |
|
| 183 |
- t, err := ctr.Task(ctx, attachIO) |
|
| 184 |
- if err != nil && !containerderrors.IsNotFound(err) {
|
|
| 185 |
- return false, -1, errors.Wrap(wrapError(err), "error getting containerd task for container") |
|
| 186 |
- } |
|
| 187 |
- |
|
| 188 |
- if t != nil {
|
|
| 189 |
- s, err := t.Status(ctx) |
|
| 190 |
- if err != nil {
|
|
| 191 |
- return false, -1, errors.Wrap(wrapError(err), "error getting task status") |
|
| 192 |
- } |
|
| 193 |
- |
|
| 194 |
- alive = s.Status != containerd.Stopped |
|
| 195 |
- pid = int(t.Pid()) |
|
| 196 |
- } |
|
| 197 |
- |
|
| 198 |
- cntr.bundleDir = filepath.Join(c.stateDir, id) |
|
| 199 |
- cntr.ctr = ctr |
|
| 200 |
- cntr.task = t |
|
| 201 |
- // TODO(mlaventure): load execs |
|
| 202 |
- |
|
| 203 |
- c.logger.WithFields(logrus.Fields{
|
|
| 204 |
- "container": id, |
|
| 205 |
- "alive": alive, |
|
| 206 |
- "pid": pid, |
|
| 207 |
- }).Debug("restored container")
|
|
| 208 |
- |
|
| 209 |
- return alive, pid, nil |
|
| 210 |
-} |
|
| 211 |
- |
|
| 212 |
-func (c *client) Create(ctx context.Context, id string, ociSpec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 213 |
- if ctr := c.getContainer(id); ctr != nil {
|
|
| 214 |
- return errors.WithStack(newConflictError("id already in use"))
|
|
| 215 |
- } |
|
| 216 |
- |
|
| 217 |
- bdir, err := prepareBundleDir(filepath.Join(c.stateDir, id), ociSpec) |
|
| 218 |
- if err != nil {
|
|
| 219 |
- return errdefs.System(errors.Wrap(err, "prepare bundle dir failed")) |
|
| 220 |
- } |
|
| 221 |
- |
|
| 222 |
- c.logger.WithField("bundle", bdir).WithField("root", ociSpec.Root.Path).Debug("bundle dir created")
|
|
| 223 |
- |
|
| 224 |
- cdCtr, err := c.client.NewContainer(ctx, id, |
|
| 225 |
- containerd.WithSpec(ociSpec), |
|
| 226 |
- // TODO(mlaventure): when containerd support lcow, revisit runtime value |
|
| 227 |
- containerd.WithRuntime(fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS), runtimeOptions))
|
|
| 228 |
- if err != nil {
|
|
| 229 |
- return wrapError(err) |
|
| 230 |
- } |
|
| 231 |
- |
|
| 232 |
- c.Lock() |
|
| 233 |
- c.containers[id] = &container{
|
|
| 234 |
- bundleDir: bdir, |
|
| 235 |
- ctr: cdCtr, |
|
| 236 |
- } |
|
| 237 |
- c.Unlock() |
|
| 238 |
- |
|
| 239 |
- return nil |
|
| 240 |
-} |
|
| 241 |
- |
|
| 242 |
-// Start create and start a task for the specified containerd id |
|
| 243 |
-func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio StdioCallback) (int, error) {
|
|
| 244 |
- ctr := c.getContainer(id) |
|
| 245 |
- if ctr == nil {
|
|
| 246 |
- return -1, errors.WithStack(newNotFoundError("no such container"))
|
|
| 247 |
- } |
|
| 248 |
- if t := ctr.getTask(); t != nil {
|
|
| 249 |
- return -1, errors.WithStack(newConflictError("container already started"))
|
|
| 250 |
- } |
|
| 251 |
- |
|
| 252 |
- var ( |
|
| 253 |
- cp *types.Descriptor |
|
| 254 |
- t containerd.Task |
|
| 255 |
- rio cio.IO |
|
| 256 |
- err error |
|
| 257 |
- stdinCloseSync = make(chan struct{})
|
|
| 258 |
- ) |
|
| 259 |
- |
|
| 260 |
- if checkpointDir != "" {
|
|
| 261 |
- // write checkpoint to the content store |
|
| 262 |
- tar := archive.Diff(ctx, "", checkpointDir) |
|
| 263 |
- cp, err = c.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, checkpointDir, tar) |
|
| 264 |
- // remove the checkpoint when we're done |
|
| 265 |
- defer func() {
|
|
| 266 |
- if cp != nil {
|
|
| 267 |
- err := c.client.ContentStore().Delete(context.Background(), cp.Digest) |
|
| 268 |
- if err != nil {
|
|
| 269 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 270 |
- "ref": checkpointDir, |
|
| 271 |
- "digest": cp.Digest, |
|
| 272 |
- }).Warnf("failed to delete temporary checkpoint entry")
|
|
| 273 |
- } |
|
| 274 |
- } |
|
| 275 |
- }() |
|
| 276 |
- if err := tar.Close(); err != nil {
|
|
| 277 |
- return -1, errors.Wrap(err, "failed to close checkpoint tar stream") |
|
| 278 |
- } |
|
| 279 |
- if err != nil {
|
|
| 280 |
- return -1, errors.Wrapf(err, "failed to upload checkpoint to containerd") |
|
| 281 |
- } |
|
| 282 |
- } |
|
| 283 |
- |
|
| 284 |
- spec, err := ctr.ctr.Spec(ctx) |
|
| 285 |
- if err != nil {
|
|
| 286 |
- return -1, errors.Wrap(err, "failed to retrieve spec") |
|
| 287 |
- } |
|
| 288 |
- uid, gid := getSpecUser(spec) |
|
| 289 |
- t, err = ctr.ctr.NewTask(ctx, |
|
| 290 |
- func(id string) (cio.IO, error) {
|
|
| 291 |
- fifos := newFIFOSet(ctr.bundleDir, InitProcessName, withStdin, spec.Process.Terminal) |
|
| 292 |
- |
|
| 293 |
- rio, err = c.createIO(fifos, id, InitProcessName, stdinCloseSync, attachStdio) |
|
| 294 |
- return rio, err |
|
| 295 |
- }, |
|
| 296 |
- func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
|
|
| 297 |
- info.Checkpoint = cp |
|
| 298 |
- info.Options = &runctypes.CreateOptions{
|
|
| 299 |
- IoUid: uint32(uid), |
|
| 300 |
- IoGid: uint32(gid), |
|
| 301 |
- NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
|
|
| 302 |
- } |
|
| 303 |
- return nil |
|
| 304 |
- }) |
|
| 305 |
- if err != nil {
|
|
| 306 |
- close(stdinCloseSync) |
|
| 307 |
- if rio != nil {
|
|
| 308 |
- rio.Cancel() |
|
| 309 |
- rio.Close() |
|
| 310 |
- } |
|
| 311 |
- return -1, wrapError(err) |
|
| 312 |
- } |
|
| 313 |
- |
|
| 314 |
- ctr.setTask(t) |
|
| 315 |
- |
|
| 316 |
- // Signal c.createIO that it can call CloseIO |
|
| 317 |
- close(stdinCloseSync) |
|
| 318 |
- |
|
| 319 |
- if err := t.Start(ctx); err != nil {
|
|
| 320 |
- if _, err := t.Delete(ctx); err != nil {
|
|
| 321 |
- c.logger.WithError(err).WithField("container", id).
|
|
| 322 |
- Error("failed to delete task after fail start")
|
|
| 323 |
- } |
|
| 324 |
- ctr.setTask(nil) |
|
| 325 |
- return -1, wrapError(err) |
|
| 326 |
- } |
|
| 327 |
- |
|
| 328 |
- return int(t.Pid()), nil |
|
| 329 |
-} |
|
| 330 |
- |
|
| 331 |
-// Exec creates exec process. |
|
| 332 |
-// |
|
| 333 |
-// The containerd client calls Exec to register the exec config in the shim side. |
|
| 334 |
-// When the client calls Start, the shim will create stdin fifo if needs. But |
|
| 335 |
-// for the container main process, the stdin fifo will be created in Create not |
|
| 336 |
-// the Start call. stdinCloseSync channel should be closed after Start exec |
|
| 337 |
-// process. |
|
| 338 |
-func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) {
|
|
| 339 |
- ctr := c.getContainer(containerID) |
|
| 340 |
- if ctr == nil {
|
|
| 341 |
- return -1, errors.WithStack(newNotFoundError("no such container"))
|
|
| 342 |
- } |
|
| 343 |
- t := ctr.getTask() |
|
| 344 |
- if t == nil {
|
|
| 345 |
- return -1, errors.WithStack(newInvalidParameterError("container is not running"))
|
|
| 346 |
- } |
|
| 347 |
- |
|
| 348 |
- if p := ctr.getProcess(processID); p != nil {
|
|
| 349 |
- return -1, errors.WithStack(newConflictError("id already in use"))
|
|
| 350 |
- } |
|
| 351 |
- |
|
| 352 |
- var ( |
|
| 353 |
- p containerd.Process |
|
| 354 |
- rio cio.IO |
|
| 355 |
- err error |
|
| 356 |
- stdinCloseSync = make(chan struct{})
|
|
| 357 |
- ) |
|
| 358 |
- |
|
| 359 |
- fifos := newFIFOSet(ctr.bundleDir, processID, withStdin, spec.Terminal) |
|
| 360 |
- |
|
| 361 |
- defer func() {
|
|
| 362 |
- if err != nil {
|
|
| 363 |
- if rio != nil {
|
|
| 364 |
- rio.Cancel() |
|
| 365 |
- rio.Close() |
|
| 366 |
- } |
|
| 367 |
- } |
|
| 368 |
- }() |
|
| 369 |
- |
|
| 370 |
- p, err = t.Exec(ctx, processID, spec, func(id string) (cio.IO, error) {
|
|
| 371 |
- rio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio) |
|
| 372 |
- return rio, err |
|
| 373 |
- }) |
|
| 374 |
- if err != nil {
|
|
| 375 |
- close(stdinCloseSync) |
|
| 376 |
- return -1, wrapError(err) |
|
| 377 |
- } |
|
| 378 |
- |
|
| 379 |
- ctr.addProcess(processID, p) |
|
| 380 |
- |
|
| 381 |
- // Signal c.createIO that it can call CloseIO |
|
| 382 |
- // |
|
| 383 |
- // the stdin of exec process will be created after p.Start in containerd |
|
| 384 |
- defer close(stdinCloseSync) |
|
| 385 |
- |
|
| 386 |
- if err = p.Start(ctx); err != nil {
|
|
| 387 |
- // use new context for cleanup because old one may be cancelled by user, but leave a timeout to make sure |
|
| 388 |
- // we are not waiting forever if containerd is unresponsive or to work around fifo cancelling issues in |
|
| 389 |
- // older containerd-shim |
|
| 390 |
- ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) |
|
| 391 |
- defer cancel() |
|
| 392 |
- p.Delete(ctx) |
|
| 393 |
- ctr.deleteProcess(processID) |
|
| 394 |
- return -1, wrapError(err) |
|
| 395 |
- } |
|
| 396 |
- |
|
| 397 |
- return int(p.Pid()), nil |
|
| 398 |
-} |
|
| 399 |
- |
|
| 400 |
-func (c *client) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
|
|
| 401 |
- p, err := c.getProcess(containerID, processID) |
|
| 402 |
- if err != nil {
|
|
| 403 |
- return err |
|
| 404 |
- } |
|
| 405 |
- return wrapError(p.Kill(ctx, syscall.Signal(signal))) |
|
| 406 |
-} |
|
| 407 |
- |
|
| 408 |
-func (c *client) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
|
|
| 409 |
- p, err := c.getProcess(containerID, processID) |
|
| 410 |
- if err != nil {
|
|
| 411 |
- return err |
|
| 412 |
- } |
|
| 413 |
- |
|
| 414 |
- return p.Resize(ctx, uint32(width), uint32(height)) |
|
| 415 |
-} |
|
| 416 |
- |
|
| 417 |
-func (c *client) CloseStdin(ctx context.Context, containerID, processID string) error {
|
|
| 418 |
- p, err := c.getProcess(containerID, processID) |
|
| 419 |
- if err != nil {
|
|
| 420 |
- return err |
|
| 421 |
- } |
|
| 422 |
- |
|
| 423 |
- return p.CloseIO(ctx, containerd.WithStdinCloser) |
|
| 424 |
-} |
|
| 425 |
- |
|
| 426 |
-func (c *client) Pause(ctx context.Context, containerID string) error {
|
|
| 427 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 428 |
- if err != nil {
|
|
| 429 |
- return err |
|
| 430 |
- } |
|
| 431 |
- |
|
| 432 |
- return wrapError(p.(containerd.Task).Pause(ctx)) |
|
| 433 |
-} |
|
| 434 |
- |
|
| 435 |
-func (c *client) Resume(ctx context.Context, containerID string) error {
|
|
| 436 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 437 |
- if err != nil {
|
|
| 438 |
- return err |
|
| 439 |
- } |
|
| 440 |
- |
|
| 441 |
- return p.(containerd.Task).Resume(ctx) |
|
| 442 |
-} |
|
| 443 |
- |
|
| 444 |
-func (c *client) Stats(ctx context.Context, containerID string) (*Stats, error) {
|
|
| 445 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 446 |
- if err != nil {
|
|
| 447 |
- return nil, err |
|
| 448 |
- } |
|
| 449 |
- |
|
| 450 |
- m, err := p.(containerd.Task).Metrics(ctx) |
|
| 451 |
- if err != nil {
|
|
| 452 |
- return nil, err |
|
| 453 |
- } |
|
| 454 |
- |
|
| 455 |
- v, err := typeurl.UnmarshalAny(m.Data) |
|
| 456 |
- if err != nil {
|
|
| 457 |
- return nil, err |
|
| 458 |
- } |
|
| 459 |
- return interfaceToStats(m.Timestamp, v), nil |
|
| 460 |
-} |
|
| 461 |
- |
|
| 462 |
-func (c *client) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
|
|
| 463 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 464 |
- if err != nil {
|
|
| 465 |
- return nil, err |
|
| 466 |
- } |
|
| 467 |
- |
|
| 468 |
- pis, err := p.(containerd.Task).Pids(ctx) |
|
| 469 |
- if err != nil {
|
|
| 470 |
- return nil, err |
|
| 471 |
- } |
|
| 472 |
- |
|
| 473 |
- var pids []uint32 |
|
| 474 |
- for _, i := range pis {
|
|
| 475 |
- pids = append(pids, i.Pid) |
|
| 476 |
- } |
|
| 477 |
- |
|
| 478 |
- return pids, nil |
|
| 479 |
-} |
|
| 480 |
- |
|
| 481 |
-func (c *client) Summary(ctx context.Context, containerID string) ([]Summary, error) {
|
|
| 482 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 483 |
- if err != nil {
|
|
| 484 |
- return nil, err |
|
| 485 |
- } |
|
| 486 |
- |
|
| 487 |
- pis, err := p.(containerd.Task).Pids(ctx) |
|
| 488 |
- if err != nil {
|
|
| 489 |
- return nil, err |
|
| 490 |
- } |
|
| 491 |
- |
|
| 492 |
- var infos []Summary |
|
| 493 |
- for _, pi := range pis {
|
|
| 494 |
- i, err := typeurl.UnmarshalAny(pi.Info) |
|
| 495 |
- if err != nil {
|
|
| 496 |
- return nil, errors.Wrap(err, "unable to decode process details") |
|
| 497 |
- } |
|
| 498 |
- s, err := summaryFromInterface(i) |
|
| 499 |
- if err != nil {
|
|
| 500 |
- return nil, err |
|
| 501 |
- } |
|
| 502 |
- infos = append(infos, *s) |
|
| 503 |
- } |
|
| 504 |
- |
|
| 505 |
- return infos, nil |
|
| 506 |
-} |
|
| 507 |
- |
|
| 508 |
-func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
|
|
| 509 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 510 |
- if err != nil {
|
|
| 511 |
- return 255, time.Now(), nil |
|
| 512 |
- } |
|
| 513 |
- |
|
| 514 |
- status, err := p.(containerd.Task).Delete(ctx) |
|
| 515 |
- if err != nil {
|
|
| 516 |
- return 255, time.Now(), nil |
|
| 517 |
- } |
|
| 518 |
- |
|
| 519 |
- if ctr := c.getContainer(containerID); ctr != nil {
|
|
| 520 |
- ctr.setTask(nil) |
|
| 521 |
- } |
|
| 522 |
- return status.ExitCode(), status.ExitTime(), nil |
|
| 523 |
-} |
|
| 524 |
- |
|
| 525 |
-func (c *client) Delete(ctx context.Context, containerID string) error {
|
|
| 526 |
- ctr := c.getContainer(containerID) |
|
| 527 |
- if ctr == nil {
|
|
| 528 |
- return errors.WithStack(newNotFoundError("no such container"))
|
|
| 529 |
- } |
|
| 530 |
- |
|
| 531 |
- if err := ctr.ctr.Delete(ctx); err != nil {
|
|
| 532 |
- return wrapError(err) |
|
| 533 |
- } |
|
| 534 |
- |
|
| 535 |
- if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" {
|
|
| 536 |
- if err := os.RemoveAll(ctr.bundleDir); err != nil {
|
|
| 537 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 538 |
- "container": containerID, |
|
| 539 |
- "bundle": ctr.bundleDir, |
|
| 540 |
- }).Error("failed to remove state dir")
|
|
| 541 |
- } |
|
| 542 |
- } |
|
| 543 |
- |
|
| 544 |
- c.removeContainer(containerID) |
|
| 545 |
- |
|
| 546 |
- return nil |
|
| 547 |
-} |
|
| 548 |
- |
|
| 549 |
-func (c *client) Status(ctx context.Context, containerID string) (Status, error) {
|
|
| 550 |
- ctr := c.getContainer(containerID) |
|
| 551 |
- if ctr == nil {
|
|
| 552 |
- return StatusUnknown, errors.WithStack(newNotFoundError("no such container"))
|
|
| 553 |
- } |
|
| 554 |
- |
|
| 555 |
- t := ctr.getTask() |
|
| 556 |
- if t == nil {
|
|
| 557 |
- return StatusUnknown, errors.WithStack(newNotFoundError("no such task"))
|
|
| 558 |
- } |
|
| 559 |
- |
|
| 560 |
- s, err := t.Status(ctx) |
|
| 561 |
- if err != nil {
|
|
| 562 |
- return StatusUnknown, wrapError(err) |
|
| 563 |
- } |
|
| 564 |
- |
|
| 565 |
- return Status(s.Status), nil |
|
| 566 |
-} |
|
| 567 |
- |
|
| 568 |
-func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
|
|
| 569 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 570 |
- if err != nil {
|
|
| 571 |
- return err |
|
| 572 |
- } |
|
| 573 |
- |
|
| 574 |
- opts := []containerd.CheckpointTaskOpts{}
|
|
| 575 |
- if exit {
|
|
| 576 |
- opts = append(opts, func(r *containerd.CheckpointTaskInfo) error {
|
|
| 577 |
- if r.Options == nil {
|
|
| 578 |
- r.Options = &runctypes.CheckpointOptions{
|
|
| 579 |
- Exit: true, |
|
| 580 |
- } |
|
| 581 |
- } else {
|
|
| 582 |
- opts, _ := r.Options.(*runctypes.CheckpointOptions) |
|
| 583 |
- opts.Exit = true |
|
| 584 |
- } |
|
| 585 |
- return nil |
|
| 586 |
- }) |
|
| 587 |
- } |
|
| 588 |
- img, err := p.(containerd.Task).Checkpoint(ctx, opts...) |
|
| 589 |
- if err != nil {
|
|
| 590 |
- return wrapError(err) |
|
| 591 |
- } |
|
| 592 |
- // Whatever happens, delete the checkpoint from containerd |
|
| 593 |
- defer func() {
|
|
| 594 |
- err := c.client.ImageService().Delete(context.Background(), img.Name()) |
|
| 595 |
- if err != nil {
|
|
| 596 |
- c.logger.WithError(err).WithField("digest", img.Target().Digest).
|
|
| 597 |
- Warnf("failed to delete checkpoint image")
|
|
| 598 |
- } |
|
| 599 |
- }() |
|
| 600 |
- |
|
| 601 |
- b, err := content.ReadBlob(ctx, c.client.ContentStore(), img.Target()) |
|
| 602 |
- if err != nil {
|
|
| 603 |
- return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data")) |
|
| 604 |
- } |
|
| 605 |
- var index v1.Index |
|
| 606 |
- if err := json.Unmarshal(b, &index); err != nil {
|
|
| 607 |
- return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data")) |
|
| 608 |
- } |
|
| 609 |
- |
|
| 610 |
- var cpDesc *v1.Descriptor |
|
| 611 |
- for _, m := range index.Manifests {
|
|
| 612 |
- if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
|
| 613 |
- cpDesc = &m |
|
| 614 |
- break |
|
| 615 |
- } |
|
| 616 |
- } |
|
| 617 |
- if cpDesc == nil {
|
|
| 618 |
- return errdefs.System(errors.Wrapf(err, "invalid checkpoint")) |
|
| 619 |
- } |
|
| 620 |
- |
|
| 621 |
- rat, err := c.client.ContentStore().ReaderAt(ctx, *cpDesc) |
|
| 622 |
- if err != nil {
|
|
| 623 |
- return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader")) |
|
| 624 |
- } |
|
| 625 |
- defer rat.Close() |
|
| 626 |
- _, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat)) |
|
| 627 |
- if err != nil {
|
|
| 628 |
- return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader")) |
|
| 629 |
- } |
|
| 630 |
- |
|
| 631 |
- return err |
|
| 632 |
-} |
|
| 633 |
- |
|
| 634 |
-func (c *client) getContainer(id string) *container {
|
|
| 635 |
- c.RLock() |
|
| 636 |
- ctr := c.containers[id] |
|
| 637 |
- c.RUnlock() |
|
| 638 |
- |
|
| 639 |
- return ctr |
|
| 640 |
-} |
|
| 641 |
- |
|
| 642 |
-func (c *client) removeContainer(id string) {
|
|
| 643 |
- c.Lock() |
|
| 644 |
- delete(c.containers, id) |
|
| 645 |
- c.Unlock() |
|
| 646 |
-} |
|
| 647 |
- |
|
| 648 |
-func (c *client) getProcess(containerID, processID string) (containerd.Process, error) {
|
|
| 649 |
- ctr := c.getContainer(containerID) |
|
| 650 |
- if ctr == nil {
|
|
| 651 |
- return nil, errors.WithStack(newNotFoundError("no such container"))
|
|
| 652 |
- } |
|
| 653 |
- |
|
| 654 |
- t := ctr.getTask() |
|
| 655 |
- if t == nil {
|
|
| 656 |
- return nil, errors.WithStack(newNotFoundError("container is not running"))
|
|
| 657 |
- } |
|
| 658 |
- if processID == InitProcessName {
|
|
| 659 |
- return t, nil |
|
| 660 |
- } |
|
| 661 |
- |
|
| 662 |
- p := ctr.getProcess(processID) |
|
| 663 |
- if p == nil {
|
|
| 664 |
- return nil, errors.WithStack(newNotFoundError("no such exec"))
|
|
| 665 |
- } |
|
| 666 |
- return p, nil |
|
| 667 |
-} |
|
| 668 |
- |
|
| 669 |
-// createIO creates the io to be used by a process |
|
| 670 |
-// This needs to get a pointer to interface as upon closure the process may not have yet been registered |
|
| 671 |
-func (c *client) createIO(fifos *cio.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio StdioCallback) (cio.IO, error) {
|
|
| 672 |
- var ( |
|
| 673 |
- io *cio.DirectIO |
|
| 674 |
- err error |
|
| 675 |
- ) |
|
| 676 |
- |
|
| 677 |
- io, err = cio.NewDirectIO(context.Background(), fifos) |
|
| 678 |
- if err != nil {
|
|
| 679 |
- return nil, err |
|
| 680 |
- } |
|
| 681 |
- |
|
| 682 |
- if io.Stdin != nil {
|
|
| 683 |
- var ( |
|
| 684 |
- err error |
|
| 685 |
- stdinOnce sync.Once |
|
| 686 |
- ) |
|
| 687 |
- pipe := io.Stdin |
|
| 688 |
- io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error {
|
|
| 689 |
- stdinOnce.Do(func() {
|
|
| 690 |
- err = pipe.Close() |
|
| 691 |
- // Do the rest in a new routine to avoid a deadlock if the |
|
| 692 |
- // Exec/Start call failed. |
|
| 693 |
- go func() {
|
|
| 694 |
- <-stdinCloseSync |
|
| 695 |
- p, err := c.getProcess(containerID, processID) |
|
| 696 |
- if err == nil {
|
|
| 697 |
- err = p.CloseIO(context.Background(), containerd.WithStdinCloser) |
|
| 698 |
- if err != nil && strings.Contains(err.Error(), "transport is closing") {
|
|
| 699 |
- err = nil |
|
| 700 |
- } |
|
| 701 |
- } |
|
| 702 |
- }() |
|
| 703 |
- }) |
|
| 704 |
- return err |
|
| 705 |
- }) |
|
| 706 |
- } |
|
| 707 |
- |
|
| 708 |
- rio, err := attachStdio(io) |
|
| 709 |
- if err != nil {
|
|
| 710 |
- io.Cancel() |
|
| 711 |
- io.Close() |
|
| 712 |
- } |
|
| 713 |
- return rio, err |
|
| 714 |
-} |
|
| 715 |
- |
|
| 716 |
-func (c *client) processEvent(ctr *container, et EventType, ei EventInfo) {
|
|
| 717 |
- c.eventQ.append(ei.ContainerID, func() {
|
|
| 718 |
- err := c.backend.ProcessEvent(ei.ContainerID, et, ei) |
|
| 719 |
- if err != nil {
|
|
| 720 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 721 |
- "container": ei.ContainerID, |
|
| 722 |
- "event": et, |
|
| 723 |
- "event-info": ei, |
|
| 724 |
- }).Error("failed to process event")
|
|
| 725 |
- } |
|
| 726 |
- |
|
| 727 |
- if et == EventExit && ei.ProcessID != ei.ContainerID {
|
|
| 728 |
- p := ctr.getProcess(ei.ProcessID) |
|
| 729 |
- if p == nil {
|
|
| 730 |
- c.logger.WithError(errors.New("no such process")).
|
|
| 731 |
- WithFields(logrus.Fields{
|
|
| 732 |
- "container": ei.ContainerID, |
|
| 733 |
- "process": ei.ProcessID, |
|
| 734 |
- }).Error("exit event")
|
|
| 735 |
- return |
|
| 736 |
- } |
|
| 737 |
- _, err = p.Delete(context.Background()) |
|
| 738 |
- if err != nil {
|
|
| 739 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 740 |
- "container": ei.ContainerID, |
|
| 741 |
- "process": ei.ProcessID, |
|
| 742 |
- }).Warn("failed to delete process")
|
|
| 743 |
- } |
|
| 744 |
- ctr.deleteProcess(ei.ProcessID) |
|
| 745 |
- |
|
| 746 |
- ctr := c.getContainer(ei.ContainerID) |
|
| 747 |
- if ctr == nil {
|
|
| 748 |
- c.logger.WithFields(logrus.Fields{
|
|
| 749 |
- "container": ei.ContainerID, |
|
| 750 |
- }).Error("failed to find container")
|
|
| 751 |
- } else {
|
|
| 752 |
- newFIFOSet(ctr.bundleDir, ei.ProcessID, true, false).Close() |
|
| 753 |
- } |
|
| 754 |
- } |
|
| 755 |
- }) |
|
| 756 |
-} |
|
| 757 |
- |
|
| 758 |
-func (c *client) processEventStream(ctx context.Context, ns string) {
|
|
| 759 |
- var ( |
|
| 760 |
- err error |
|
| 761 |
- ev *events.Envelope |
|
| 762 |
- et EventType |
|
| 763 |
- ei EventInfo |
|
| 764 |
- ctr *container |
|
| 765 |
- ) |
|
| 766 |
- |
|
| 767 |
- // Filter on both namespace *and* topic. To create an "and" filter, |
|
| 768 |
- // this must be a single, comma-separated string |
|
| 769 |
- eventStream, errC := c.client.EventService().Subscribe(ctx, "namespace=="+ns+",topic~=|^/tasks/|") |
|
| 770 |
- |
|
| 771 |
- c.logger.Debug("processing event stream")
|
|
| 772 |
- |
|
| 773 |
- var oomKilled bool |
|
| 774 |
- for {
|
|
| 775 |
- select {
|
|
| 776 |
- case err = <-errC: |
|
| 777 |
- if err != nil {
|
|
| 778 |
- errStatus, ok := status.FromError(err) |
|
| 779 |
- if !ok || errStatus.Code() != codes.Canceled {
|
|
| 780 |
- c.logger.WithError(err).Error("failed to get event")
|
|
| 781 |
- go c.processEventStream(ctx, ns) |
|
| 782 |
- } else {
|
|
| 783 |
- c.logger.WithError(ctx.Err()).Info("stopping event stream following graceful shutdown")
|
|
| 784 |
- } |
|
| 785 |
- } |
|
| 786 |
- return |
|
| 787 |
- case ev = <-eventStream: |
|
| 788 |
- if ev.Event == nil {
|
|
| 789 |
- c.logger.WithField("event", ev).Warn("invalid event")
|
|
| 790 |
- continue |
|
| 791 |
- } |
|
| 792 |
- |
|
| 793 |
- v, err := typeurl.UnmarshalAny(ev.Event) |
|
| 794 |
- if err != nil {
|
|
| 795 |
- c.logger.WithError(err).WithField("event", ev).Warn("failed to unmarshal event")
|
|
| 796 |
- continue |
|
| 797 |
- } |
|
| 798 |
- |
|
| 799 |
- c.logger.WithField("topic", ev.Topic).Debug("event")
|
|
| 800 |
- |
|
| 801 |
- switch t := v.(type) {
|
|
| 802 |
- case *apievents.TaskCreate: |
|
| 803 |
- et = EventCreate |
|
| 804 |
- ei = EventInfo{
|
|
| 805 |
- ContainerID: t.ContainerID, |
|
| 806 |
- ProcessID: t.ContainerID, |
|
| 807 |
- Pid: t.Pid, |
|
| 808 |
- } |
|
| 809 |
- case *apievents.TaskStart: |
|
| 810 |
- et = EventStart |
|
| 811 |
- ei = EventInfo{
|
|
| 812 |
- ContainerID: t.ContainerID, |
|
| 813 |
- ProcessID: t.ContainerID, |
|
| 814 |
- Pid: t.Pid, |
|
| 815 |
- } |
|
| 816 |
- case *apievents.TaskExit: |
|
| 817 |
- et = EventExit |
|
| 818 |
- ei = EventInfo{
|
|
| 819 |
- ContainerID: t.ContainerID, |
|
| 820 |
- ProcessID: t.ID, |
|
| 821 |
- Pid: t.Pid, |
|
| 822 |
- ExitCode: t.ExitStatus, |
|
| 823 |
- ExitedAt: t.ExitedAt, |
|
| 824 |
- } |
|
| 825 |
- case *apievents.TaskOOM: |
|
| 826 |
- et = EventOOM |
|
| 827 |
- ei = EventInfo{
|
|
| 828 |
- ContainerID: t.ContainerID, |
|
| 829 |
- OOMKilled: true, |
|
| 830 |
- } |
|
| 831 |
- oomKilled = true |
|
| 832 |
- case *apievents.TaskExecAdded: |
|
| 833 |
- et = EventExecAdded |
|
| 834 |
- ei = EventInfo{
|
|
| 835 |
- ContainerID: t.ContainerID, |
|
| 836 |
- ProcessID: t.ExecID, |
|
| 837 |
- } |
|
| 838 |
- case *apievents.TaskExecStarted: |
|
| 839 |
- et = EventExecStarted |
|
| 840 |
- ei = EventInfo{
|
|
| 841 |
- ContainerID: t.ContainerID, |
|
| 842 |
- ProcessID: t.ExecID, |
|
| 843 |
- Pid: t.Pid, |
|
| 844 |
- } |
|
| 845 |
- case *apievents.TaskPaused: |
|
| 846 |
- et = EventPaused |
|
| 847 |
- ei = EventInfo{
|
|
| 848 |
- ContainerID: t.ContainerID, |
|
| 849 |
- } |
|
| 850 |
- case *apievents.TaskResumed: |
|
| 851 |
- et = EventResumed |
|
| 852 |
- ei = EventInfo{
|
|
| 853 |
- ContainerID: t.ContainerID, |
|
| 854 |
- } |
|
| 855 |
- default: |
|
| 856 |
- c.logger.WithFields(logrus.Fields{
|
|
| 857 |
- "topic": ev.Topic, |
|
| 858 |
- "type": reflect.TypeOf(t)}, |
|
| 859 |
- ).Info("ignoring event")
|
|
| 860 |
- continue |
|
| 861 |
- } |
|
| 862 |
- |
|
| 863 |
- ctr = c.getContainer(ei.ContainerID) |
|
| 864 |
- if ctr == nil {
|
|
| 865 |
- c.logger.WithField("container", ei.ContainerID).Warn("unknown container")
|
|
| 866 |
- continue |
|
| 867 |
- } |
|
| 868 |
- |
|
| 869 |
- if oomKilled {
|
|
| 870 |
- ctr.setOOMKilled(true) |
|
| 871 |
- oomKilled = false |
|
| 872 |
- } |
|
| 873 |
- ei.OOMKilled = ctr.getOOMKilled() |
|
| 874 |
- |
|
| 875 |
- c.processEvent(ctr, et, ei) |
|
| 876 |
- } |
|
| 877 |
- } |
|
| 878 |
-} |
|
| 879 |
- |
|
| 880 |
-func (c *client) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
|
|
| 881 |
- writer, err := c.client.ContentStore().Writer(ctx, content.WithRef(ref)) |
|
| 882 |
- if err != nil {
|
|
| 883 |
- return nil, err |
|
| 884 |
- } |
|
| 885 |
- defer writer.Close() |
|
| 886 |
- size, err := io.Copy(writer, r) |
|
| 887 |
- if err != nil {
|
|
| 888 |
- return nil, err |
|
| 889 |
- } |
|
| 890 |
- labels := map[string]string{
|
|
| 891 |
- "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339), |
|
| 892 |
- } |
|
| 893 |
- if err := writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
|
|
| 894 |
- return nil, err |
|
| 895 |
- } |
|
| 896 |
- return &types.Descriptor{
|
|
| 897 |
- MediaType: mediaType, |
|
| 898 |
- Digest: writer.Digest(), |
|
| 899 |
- Size_: size, |
|
| 900 |
- }, nil |
|
| 901 |
-} |
|
| 902 |
- |
|
| 903 |
-func wrapError(err error) error {
|
|
| 904 |
- switch {
|
|
| 905 |
- case err == nil: |
|
| 906 |
- return nil |
|
| 907 |
- case containerderrors.IsNotFound(err): |
|
| 908 |
- return errdefs.NotFound(err) |
|
| 909 |
- } |
|
| 910 |
- |
|
| 911 |
- msg := err.Error() |
|
| 912 |
- for _, s := range []string{"container does not exist", "not found", "no such container"} {
|
|
| 913 |
- if strings.Contains(msg, s) {
|
|
| 914 |
- return errdefs.NotFound(err) |
|
| 915 |
- } |
|
| 916 |
- } |
|
| 917 |
- return err |
|
| 918 |
-} |
| 919 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,108 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "context" |
|
| 5 |
- "fmt" |
|
| 6 |
- "os" |
|
| 7 |
- "path/filepath" |
|
| 8 |
- "strings" |
|
| 9 |
- |
|
| 10 |
- "github.com/containerd/containerd" |
|
| 11 |
- "github.com/containerd/containerd/cio" |
|
| 12 |
- "github.com/docker/docker/pkg/idtools" |
|
| 13 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 14 |
- "github.com/sirupsen/logrus" |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-func summaryFromInterface(i interface{}) (*Summary, error) {
|
|
| 18 |
- return &Summary{}, nil
|
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-func (c *client) UpdateResources(ctx context.Context, containerID string, resources *Resources) error {
|
|
| 22 |
- p, err := c.getProcess(containerID, InitProcessName) |
|
| 23 |
- if err != nil {
|
|
| 24 |
- return err |
|
| 25 |
- } |
|
| 26 |
- |
|
| 27 |
- // go doesn't like the alias in 1.8, this means this need to be |
|
| 28 |
- // platform specific |
|
| 29 |
- return p.(containerd.Task).Update(ctx, containerd.WithResources((*specs.LinuxResources)(resources))) |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func hostIDFromMap(id uint32, mp []specs.LinuxIDMapping) int {
|
|
| 33 |
- for _, m := range mp {
|
|
| 34 |
- if id >= m.ContainerID && id <= m.ContainerID+m.Size-1 {
|
|
| 35 |
- return int(m.HostID + id - m.ContainerID) |
|
| 36 |
- } |
|
| 37 |
- } |
|
| 38 |
- return 0 |
|
| 39 |
-} |
|
| 40 |
- |
|
| 41 |
-func getSpecUser(ociSpec *specs.Spec) (int, int) {
|
|
| 42 |
- var ( |
|
| 43 |
- uid int |
|
| 44 |
- gid int |
|
| 45 |
- ) |
|
| 46 |
- |
|
| 47 |
- for _, ns := range ociSpec.Linux.Namespaces {
|
|
| 48 |
- if ns.Type == specs.UserNamespace {
|
|
| 49 |
- uid = hostIDFromMap(0, ociSpec.Linux.UIDMappings) |
|
| 50 |
- gid = hostIDFromMap(0, ociSpec.Linux.GIDMappings) |
|
| 51 |
- break |
|
| 52 |
- } |
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 |
- return uid, gid |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 |
-func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
|
|
| 59 |
- uid, gid := getSpecUser(ociSpec) |
|
| 60 |
- if uid == 0 && gid == 0 {
|
|
| 61 |
- return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.Identity{UID: 0, GID: 0})
|
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- p := string(filepath.Separator) |
|
| 65 |
- components := strings.Split(bundleDir, string(filepath.Separator)) |
|
| 66 |
- for _, d := range components[1:] {
|
|
| 67 |
- p = filepath.Join(p, d) |
|
| 68 |
- fi, err := os.Stat(p) |
|
| 69 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 70 |
- return "", err |
|
| 71 |
- } |
|
| 72 |
- if os.IsNotExist(err) || fi.Mode()&1 == 0 {
|
|
| 73 |
- p = fmt.Sprintf("%s.%d.%d", p, uid, gid)
|
|
| 74 |
- if err := idtools.MkdirAndChown(p, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) {
|
|
| 75 |
- return "", err |
|
| 76 |
- } |
|
| 77 |
- } |
|
| 78 |
- } |
|
| 79 |
- |
|
| 80 |
- return p, nil |
|
| 81 |
-} |
|
| 82 |
- |
|
| 83 |
-func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
|
|
| 84 |
- config := cio.Config{
|
|
| 85 |
- Terminal: withTerminal, |
|
| 86 |
- Stdout: filepath.Join(bundleDir, processID+"-stdout"), |
|
| 87 |
- } |
|
| 88 |
- paths := []string{config.Stdout}
|
|
| 89 |
- |
|
| 90 |
- if withStdin {
|
|
| 91 |
- config.Stdin = filepath.Join(bundleDir, processID+"-stdin") |
|
| 92 |
- paths = append(paths, config.Stdin) |
|
| 93 |
- } |
|
| 94 |
- if !withTerminal {
|
|
| 95 |
- config.Stderr = filepath.Join(bundleDir, processID+"-stderr") |
|
| 96 |
- paths = append(paths, config.Stderr) |
|
| 97 |
- } |
|
| 98 |
- closer := func() error {
|
|
| 99 |
- for _, path := range paths {
|
|
| 100 |
- if err := os.RemoveAll(path); err != nil {
|
|
| 101 |
- logrus.Warnf("libcontainerd: failed to remove fifo %v: %v", path, err)
|
|
| 102 |
- } |
|
| 103 |
- } |
|
| 104 |
- return nil |
|
| 105 |
- } |
|
| 106 |
- |
|
| 107 |
- return cio.NewFIFOSet(config, closer) |
|
| 108 |
-} |
| 109 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,55 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "path/filepath" |
|
| 6 |
- |
|
| 7 |
- "github.com/containerd/containerd/cio" |
|
| 8 |
- "github.com/containerd/containerd/windows/hcsshimtypes" |
|
| 9 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 10 |
- "github.com/pkg/errors" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-func summaryFromInterface(i interface{}) (*Summary, error) {
|
|
| 14 |
- switch pd := i.(type) {
|
|
| 15 |
- case *hcsshimtypes.ProcessDetails: |
|
| 16 |
- return &Summary{
|
|
| 17 |
- CreateTimestamp: pd.CreatedAt, |
|
| 18 |
- ImageName: pd.ImageName, |
|
| 19 |
- KernelTime100ns: pd.KernelTime_100Ns, |
|
| 20 |
- MemoryCommitBytes: pd.MemoryCommitBytes, |
|
| 21 |
- MemoryWorkingSetPrivateBytes: pd.MemoryWorkingSetPrivateBytes, |
|
| 22 |
- MemoryWorkingSetSharedBytes: pd.MemoryWorkingSetSharedBytes, |
|
| 23 |
- ProcessId: pd.ProcessID, |
|
| 24 |
- UserTime100ns: pd.UserTime_100Ns, |
|
| 25 |
- }, nil |
|
| 26 |
- default: |
|
| 27 |
- return nil, errors.Errorf("Unknown process details type %T", pd)
|
|
| 28 |
- } |
|
| 29 |
-} |
|
| 30 |
- |
|
| 31 |
-func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
|
|
| 32 |
- return bundleDir, nil |
|
| 33 |
-} |
|
| 34 |
- |
|
| 35 |
-func pipeName(containerID, processID, name string) string {
|
|
| 36 |
- return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name) |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 |
-func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
|
|
| 40 |
- containerID := filepath.Base(bundleDir) |
|
| 41 |
- config := cio.Config{
|
|
| 42 |
- Terminal: withTerminal, |
|
| 43 |
- Stdout: pipeName(containerID, processID, "stdout"), |
|
| 44 |
- } |
|
| 45 |
- |
|
| 46 |
- if withStdin {
|
|
| 47 |
- config.Stdin = pipeName(containerID, processID, "stdin") |
|
| 48 |
- } |
|
| 49 |
- |
|
| 50 |
- if !config.Terminal {
|
|
| 51 |
- config.Stderr = pipeName(containerID, processID, "stderr") |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- return cio.NewFIFOSet(config, nil) |
|
| 55 |
-} |
| 56 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,1376 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "context" |
|
| 5 |
- "encoding/json" |
|
| 6 |
- "fmt" |
|
| 7 |
- "io/ioutil" |
|
| 8 |
- "os" |
|
| 9 |
- "path" |
|
| 10 |
- "path/filepath" |
|
| 11 |
- "regexp" |
|
| 12 |
- "strings" |
|
| 13 |
- "sync" |
|
| 14 |
- "syscall" |
|
| 15 |
- "time" |
|
| 16 |
- |
|
| 17 |
- "github.com/Microsoft/hcsshim" |
|
| 18 |
- opengcs "github.com/Microsoft/opengcs/client" |
|
| 19 |
- "github.com/containerd/containerd" |
|
| 20 |
- "github.com/containerd/containerd/cio" |
|
| 21 |
- "github.com/docker/docker/pkg/sysinfo" |
|
| 22 |
- "github.com/docker/docker/pkg/system" |
|
| 23 |
- specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 24 |
- "github.com/pkg/errors" |
|
| 25 |
- "github.com/sirupsen/logrus" |
|
| 26 |
- "golang.org/x/sys/windows" |
|
| 27 |
-) |
|
| 28 |
- |
|
| 29 |
-const InitProcessName = "init" |
|
| 30 |
- |
|
| 31 |
-type process struct {
|
|
| 32 |
- id string |
|
| 33 |
- pid int |
|
| 34 |
- hcsProcess hcsshim.Process |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-type container struct {
|
|
| 38 |
- sync.Mutex |
|
| 39 |
- |
|
| 40 |
- // The ociSpec is required, as client.Create() needs a spec, but can |
|
| 41 |
- // be called from the RestartManager context which does not otherwise |
|
| 42 |
- // have access to the Spec |
|
| 43 |
- ociSpec *specs.Spec |
|
| 44 |
- |
|
| 45 |
- isWindows bool |
|
| 46 |
- hcsContainer hcsshim.Container |
|
| 47 |
- |
|
| 48 |
- id string |
|
| 49 |
- status Status |
|
| 50 |
- exitedAt time.Time |
|
| 51 |
- exitCode uint32 |
|
| 52 |
- waitCh chan struct{}
|
|
| 53 |
- init *process |
|
| 54 |
- execs map[string]*process |
|
| 55 |
- terminateInvoked bool |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 |
-// Win32 error codes that are used for various workarounds |
|
| 59 |
-// These really should be ALL_CAPS to match golangs syscall library and standard |
|
| 60 |
-// Win32 error conventions, but golint insists on CamelCase. |
|
| 61 |
-const ( |
|
| 62 |
- CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string |
|
| 63 |
- ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started |
|
| 64 |
- ErrorBadPathname = syscall.Errno(161) // The specified path is invalid |
|
| 65 |
- ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object |
|
| 66 |
-) |
|
| 67 |
- |
|
| 68 |
-// defaultOwner is a tag passed to HCS to allow it to differentiate between |
|
| 69 |
-// container creator management stacks. We hard code "docker" in the case |
|
| 70 |
-// of docker. |
|
| 71 |
-const defaultOwner = "docker" |
|
| 72 |
- |
|
| 73 |
-type client struct {
|
|
| 74 |
- sync.Mutex |
|
| 75 |
- |
|
| 76 |
- stateDir string |
|
| 77 |
- backend Backend |
|
| 78 |
- logger *logrus.Entry |
|
| 79 |
- eventQ queue |
|
| 80 |
- containers map[string]*container |
|
| 81 |
-} |
|
| 82 |
- |
|
| 83 |
-// NewClient creates a new local executor for windows |
|
| 84 |
-func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b Backend) (Client, error) {
|
|
| 85 |
- c := &client{
|
|
| 86 |
- stateDir: stateDir, |
|
| 87 |
- backend: b, |
|
| 88 |
- logger: logrus.WithField("module", "libcontainerd").WithField("module", "libcontainerd").WithField("namespace", ns),
|
|
| 89 |
- containers: make(map[string]*container), |
|
| 90 |
- } |
|
| 91 |
- |
|
| 92 |
- return c, nil |
|
| 93 |
-} |
|
| 94 |
- |
|
| 95 |
-func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 96 |
- return containerd.Version{}, errors.New("not implemented on Windows")
|
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 |
-// Create is the entrypoint to create a container from a spec. |
|
| 100 |
-// Table below shows the fields required for HCS JSON calling parameters, |
|
| 101 |
-// where if not populated, is omitted. |
|
| 102 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 103 |
-// | | Isolation=Process | Isolation=Hyper-V | |
|
| 104 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 105 |
-// | VolumePath | \\?\\Volume{GUIDa} | |
|
|
| 106 |
-// | LayerFolderPath | %root%\windowsfilter\containerID | | |
|
| 107 |
-// | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID | |
|
| 108 |
-// | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM | |
|
| 109 |
-// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 110 |
-// |
|
| 111 |
-// Isolation=Process example: |
|
| 112 |
-// |
|
| 113 |
-// {
|
|
| 114 |
-// "SystemType": "Container", |
|
| 115 |
-// "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 116 |
-// "Owner": "docker", |
|
| 117 |
-// "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
|
|
| 118 |
-// "IgnoreFlushesDuringBoot": true, |
|
| 119 |
-// "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 120 |
-// "Layers": [{
|
|
| 121 |
-// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 122 |
-// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 123 |
-// }], |
|
| 124 |
-// "HostName": "5e0055c814a6", |
|
| 125 |
-// "MappedDirectories": [], |
|
| 126 |
-// "HvPartition": false, |
|
| 127 |
-// "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"], |
|
| 128 |
-//} |
|
| 129 |
-// |
|
| 130 |
-// Isolation=Hyper-V example: |
|
| 131 |
-// |
|
| 132 |
-//{
|
|
| 133 |
-// "SystemType": "Container", |
|
| 134 |
-// "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d", |
|
| 135 |
-// "Owner": "docker", |
|
| 136 |
-// "IgnoreFlushesDuringBoot": true, |
|
| 137 |
-// "Layers": [{
|
|
| 138 |
-// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 139 |
-// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 140 |
-// }], |
|
| 141 |
-// "HostName": "475c2c58933b", |
|
| 142 |
-// "MappedDirectories": [], |
|
| 143 |
-// "HvPartition": true, |
|
| 144 |
-// "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"], |
|
| 145 |
-// "DNSSearchList": "a.com,b.com,c.com", |
|
| 146 |
-// "HvRuntime": {
|
|
| 147 |
-// "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM" |
|
| 148 |
-// }, |
|
| 149 |
-//} |
|
| 150 |
-func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 151 |
- if ctr := c.getContainer(id); ctr != nil {
|
|
| 152 |
- return errors.WithStack(newConflictError("id already in use"))
|
|
| 153 |
- } |
|
| 154 |
- |
|
| 155 |
- // spec.Linux must be nil for Windows containers, but spec.Windows |
|
| 156 |
- // will be filled in regardless of container platform. This is a |
|
| 157 |
- // temporary workaround due to LCOW requiring layer folder paths, |
|
| 158 |
- // which are stored under spec.Windows. |
|
| 159 |
- // |
|
| 160 |
- // TODO: @darrenstahlmsft fix this once the OCI spec is updated to |
|
| 161 |
- // support layer folder paths for LCOW |
|
| 162 |
- if spec.Linux == nil {
|
|
| 163 |
- return c.createWindows(id, spec, runtimeOptions) |
|
| 164 |
- } |
|
| 165 |
- return c.createLinux(id, spec, runtimeOptions) |
|
| 166 |
-} |
|
| 167 |
- |
|
| 168 |
-func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 169 |
- logger := c.logger.WithField("container", id)
|
|
| 170 |
- configuration := &hcsshim.ContainerConfig{
|
|
| 171 |
- SystemType: "Container", |
|
| 172 |
- Name: id, |
|
| 173 |
- Owner: defaultOwner, |
|
| 174 |
- IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
|
| 175 |
- HostName: spec.Hostname, |
|
| 176 |
- HvPartition: false, |
|
| 177 |
- } |
|
| 178 |
- |
|
| 179 |
- c.extractResourcesFromSpec(spec, configuration) |
|
| 180 |
- |
|
| 181 |
- if spec.Windows.Resources != nil {
|
|
| 182 |
- if spec.Windows.Resources.Storage != nil {
|
|
| 183 |
- if spec.Windows.Resources.Storage.Bps != nil {
|
|
| 184 |
- configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps |
|
| 185 |
- } |
|
| 186 |
- if spec.Windows.Resources.Storage.Iops != nil {
|
|
| 187 |
- configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops |
|
| 188 |
- } |
|
| 189 |
- } |
|
| 190 |
- } |
|
| 191 |
- |
|
| 192 |
- if spec.Windows.HyperV != nil {
|
|
| 193 |
- configuration.HvPartition = true |
|
| 194 |
- } |
|
| 195 |
- |
|
| 196 |
- if spec.Windows.Network != nil {
|
|
| 197 |
- configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 198 |
- configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 199 |
- if spec.Windows.Network.DNSSearchList != nil {
|
|
| 200 |
- configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 201 |
- } |
|
| 202 |
- configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 203 |
- } |
|
| 204 |
- |
|
| 205 |
- if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
|
| 206 |
- configuration.Credentials = cs |
|
| 207 |
- } |
|
| 208 |
- |
|
| 209 |
- // We must have least two layers in the spec, the bottom one being a |
|
| 210 |
- // base image, the top one being the RW layer. |
|
| 211 |
- if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
|
| 212 |
- return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
|
| 213 |
- } |
|
| 214 |
- |
|
| 215 |
- // Strip off the top-most layer as that's passed in separately to HCS |
|
| 216 |
- configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 217 |
- layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 218 |
- |
|
| 219 |
- if configuration.HvPartition {
|
|
| 220 |
- // We don't currently support setting the utility VM image explicitly. |
|
| 221 |
- // TODO @swernli/jhowardmsft circa RS5, this may be re-locatable. |
|
| 222 |
- if spec.Windows.HyperV.UtilityVMPath != "" {
|
|
| 223 |
- return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
|
| 224 |
- } |
|
| 225 |
- |
|
| 226 |
- // Find the upper-most utility VM image. |
|
| 227 |
- var uvmImagePath string |
|
| 228 |
- for _, path := range layerFolders {
|
|
| 229 |
- fullPath := filepath.Join(path, "UtilityVM") |
|
| 230 |
- _, err := os.Stat(fullPath) |
|
| 231 |
- if err == nil {
|
|
| 232 |
- uvmImagePath = fullPath |
|
| 233 |
- break |
|
| 234 |
- } |
|
| 235 |
- if !os.IsNotExist(err) {
|
|
| 236 |
- return err |
|
| 237 |
- } |
|
| 238 |
- } |
|
| 239 |
- if uvmImagePath == "" {
|
|
| 240 |
- return errors.New("utility VM image could not be found")
|
|
| 241 |
- } |
|
| 242 |
- configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
|
| 243 |
- |
|
| 244 |
- if spec.Root.Path != "" {
|
|
| 245 |
- return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
|
| 246 |
- } |
|
| 247 |
- } else {
|
|
| 248 |
- 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}\}\\$`
|
|
| 249 |
- if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
|
| 250 |
- return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
|
| 251 |
- } |
|
| 252 |
- // HCS API requires the trailing backslash to be removed |
|
| 253 |
- configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1] |
|
| 254 |
- } |
|
| 255 |
- |
|
| 256 |
- if spec.Root.Readonly {
|
|
| 257 |
- return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`) |
|
| 258 |
- } |
|
| 259 |
- |
|
| 260 |
- for _, layerPath := range layerFolders {
|
|
| 261 |
- _, filename := filepath.Split(layerPath) |
|
| 262 |
- g, err := hcsshim.NameToGuid(filename) |
|
| 263 |
- if err != nil {
|
|
| 264 |
- return err |
|
| 265 |
- } |
|
| 266 |
- configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 267 |
- ID: g.ToString(), |
|
| 268 |
- Path: layerPath, |
|
| 269 |
- }) |
|
| 270 |
- } |
|
| 271 |
- |
|
| 272 |
- // Add the mounts (volumes, bind mounts etc) to the structure |
|
| 273 |
- var mds []hcsshim.MappedDir |
|
| 274 |
- var mps []hcsshim.MappedPipe |
|
| 275 |
- for _, mount := range spec.Mounts {
|
|
| 276 |
- const pipePrefix = `\\.\pipe\` |
|
| 277 |
- if mount.Type != "" {
|
|
| 278 |
- return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
|
| 279 |
- } |
|
| 280 |
- if strings.HasPrefix(mount.Destination, pipePrefix) {
|
|
| 281 |
- mp := hcsshim.MappedPipe{
|
|
| 282 |
- HostPath: mount.Source, |
|
| 283 |
- ContainerPipeName: mount.Destination[len(pipePrefix):], |
|
| 284 |
- } |
|
| 285 |
- mps = append(mps, mp) |
|
| 286 |
- } else {
|
|
| 287 |
- md := hcsshim.MappedDir{
|
|
| 288 |
- HostPath: mount.Source, |
|
| 289 |
- ContainerPath: mount.Destination, |
|
| 290 |
- ReadOnly: false, |
|
| 291 |
- } |
|
| 292 |
- for _, o := range mount.Options {
|
|
| 293 |
- if strings.ToLower(o) == "ro" {
|
|
| 294 |
- md.ReadOnly = true |
|
| 295 |
- } |
|
| 296 |
- } |
|
| 297 |
- mds = append(mds, md) |
|
| 298 |
- } |
|
| 299 |
- } |
|
| 300 |
- configuration.MappedDirectories = mds |
|
| 301 |
- if len(mps) > 0 && system.GetOSVersion().Build < 16299 { // RS3
|
|
| 302 |
- return errors.New("named pipe mounts are not supported on this version of Windows")
|
|
| 303 |
- } |
|
| 304 |
- configuration.MappedPipes = mps |
|
| 305 |
- |
|
| 306 |
- if len(spec.Windows.Devices) > 0 {
|
|
| 307 |
- // Add any device assignments |
|
| 308 |
- if configuration.HvPartition {
|
|
| 309 |
- return errors.New("device assignment is not supported for HyperV containers")
|
|
| 310 |
- } |
|
| 311 |
- if system.GetOSVersion().Build < 17763 { // RS5
|
|
| 312 |
- return errors.New("device assignment requires Windows builds RS5 (17763+) or later")
|
|
| 313 |
- } |
|
| 314 |
- for _, d := range spec.Windows.Devices {
|
|
| 315 |
- configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
|
|
| 316 |
- } |
|
| 317 |
- } |
|
| 318 |
- |
|
| 319 |
- hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 320 |
- if err != nil {
|
|
| 321 |
- return err |
|
| 322 |
- } |
|
| 323 |
- |
|
| 324 |
- // Construct a container object for calling start on it. |
|
| 325 |
- ctr := &container{
|
|
| 326 |
- id: id, |
|
| 327 |
- execs: make(map[string]*process), |
|
| 328 |
- isWindows: true, |
|
| 329 |
- ociSpec: spec, |
|
| 330 |
- hcsContainer: hcsContainer, |
|
| 331 |
- status: StatusCreated, |
|
| 332 |
- waitCh: make(chan struct{}),
|
|
| 333 |
- } |
|
| 334 |
- |
|
| 335 |
- logger.Debug("starting container")
|
|
| 336 |
- if err = hcsContainer.Start(); err != nil {
|
|
| 337 |
- c.logger.WithError(err).Error("failed to start container")
|
|
| 338 |
- ctr.Lock() |
|
| 339 |
- if err := c.terminateContainer(ctr); err != nil {
|
|
| 340 |
- c.logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 341 |
- } else {
|
|
| 342 |
- c.logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 343 |
- } |
|
| 344 |
- ctr.Unlock() |
|
| 345 |
- return err |
|
| 346 |
- } |
|
| 347 |
- |
|
| 348 |
- c.Lock() |
|
| 349 |
- c.containers[id] = ctr |
|
| 350 |
- c.Unlock() |
|
| 351 |
- |
|
| 352 |
- logger.Debug("createWindows() completed successfully")
|
|
| 353 |
- return nil |
|
| 354 |
- |
|
| 355 |
-} |
|
| 356 |
- |
|
| 357 |
-func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 358 |
- logrus.Debugf("libcontainerd: createLinux(): containerId %s ", id)
|
|
| 359 |
- logger := c.logger.WithField("container", id)
|
|
| 360 |
- |
|
| 361 |
- if runtimeOptions == nil {
|
|
| 362 |
- return fmt.Errorf("lcow option must be supplied to the runtime")
|
|
| 363 |
- } |
|
| 364 |
- lcowConfig, ok := runtimeOptions.(*opengcs.Config) |
|
| 365 |
- if !ok {
|
|
| 366 |
- return fmt.Errorf("lcow option must be supplied to the runtime")
|
|
| 367 |
- } |
|
| 368 |
- |
|
| 369 |
- configuration := &hcsshim.ContainerConfig{
|
|
| 370 |
- HvPartition: true, |
|
| 371 |
- Name: id, |
|
| 372 |
- SystemType: "container", |
|
| 373 |
- ContainerType: "linux", |
|
| 374 |
- Owner: defaultOwner, |
|
| 375 |
- TerminateOnLastHandleClosed: true, |
|
| 376 |
- } |
|
| 377 |
- |
|
| 378 |
- if lcowConfig.ActualMode == opengcs.ModeActualVhdx {
|
|
| 379 |
- configuration.HvRuntime = &hcsshim.HvRuntime{
|
|
| 380 |
- ImagePath: lcowConfig.Vhdx, |
|
| 381 |
- BootSource: "Vhd", |
|
| 382 |
- WritableBootSource: false, |
|
| 383 |
- } |
|
| 384 |
- } else {
|
|
| 385 |
- configuration.HvRuntime = &hcsshim.HvRuntime{
|
|
| 386 |
- ImagePath: lcowConfig.KirdPath, |
|
| 387 |
- LinuxKernelFile: lcowConfig.KernelFile, |
|
| 388 |
- LinuxInitrdFile: lcowConfig.InitrdFile, |
|
| 389 |
- LinuxBootParameters: lcowConfig.BootParameters, |
|
| 390 |
- } |
|
| 391 |
- } |
|
| 392 |
- |
|
| 393 |
- if spec.Windows == nil {
|
|
| 394 |
- return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
|
|
| 395 |
- } |
|
| 396 |
- |
|
| 397 |
- c.extractResourcesFromSpec(spec, configuration) |
|
| 398 |
- |
|
| 399 |
- // We must have least one layer in the spec |
|
| 400 |
- if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
|
|
| 401 |
- return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
|
|
| 402 |
- } |
|
| 403 |
- |
|
| 404 |
- // Strip off the top-most layer as that's passed in separately to HCS |
|
| 405 |
- configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 406 |
- layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 407 |
- |
|
| 408 |
- for _, layerPath := range layerFolders {
|
|
| 409 |
- _, filename := filepath.Split(layerPath) |
|
| 410 |
- g, err := hcsshim.NameToGuid(filename) |
|
| 411 |
- if err != nil {
|
|
| 412 |
- return err |
|
| 413 |
- } |
|
| 414 |
- configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 415 |
- ID: g.ToString(), |
|
| 416 |
- Path: filepath.Join(layerPath, "layer.vhd"), |
|
| 417 |
- }) |
|
| 418 |
- } |
|
| 419 |
- |
|
| 420 |
- if spec.Windows.Network != nil {
|
|
| 421 |
- configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 422 |
- configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 423 |
- if spec.Windows.Network.DNSSearchList != nil {
|
|
| 424 |
- configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 425 |
- } |
|
| 426 |
- configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 427 |
- } |
|
| 428 |
- |
|
| 429 |
- // Add the mounts (volumes, bind mounts etc) to the structure. We have to do |
|
| 430 |
- // some translation for both the mapped directories passed into HCS and in |
|
| 431 |
- // the spec. |
|
| 432 |
- // |
|
| 433 |
- // For HCS, we only pass in the mounts from the spec which are type "bind". |
|
| 434 |
- // Further, the "ContainerPath" field (which is a little mis-leadingly |
|
| 435 |
- // named when it applies to the utility VM rather than the container in the |
|
| 436 |
- // utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed |
|
| 437 |
- // by the caller through a 'uvmpath' option. |
|
| 438 |
- // |
|
| 439 |
- // We do similar translation for the mounts in the spec by stripping out |
|
| 440 |
- // the uvmpath option, and translating the Source path to the location in the |
|
| 441 |
- // utility VM calculated above. |
|
| 442 |
- // |
|
| 443 |
- // From inside the utility VM, you would see a 9p mount such as in the following |
|
| 444 |
- // where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds |
|
| 445 |
- // specifically: |
|
| 446 |
- // |
|
| 447 |
- // / # mount |
|
| 448 |
- // rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934) |
|
| 449 |
- // proc on /proc type proc (rw,relatime) |
|
| 450 |
- // sysfs on /sys type sysfs (rw,relatime) |
|
| 451 |
- // udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755) |
|
| 452 |
- // tmpfs on /run type tmpfs (rw,relatime) |
|
| 453 |
- // cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma) |
|
| 454 |
- // mqueue on /dev/mqueue type mqueue (rw,relatime) |
|
| 455 |
- // devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000) |
|
| 456 |
- // /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6) |
|
| 457 |
- // /dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl) |
|
| 458 |
- // /dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl) |
|
| 459 |
- // overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work) |
|
| 460 |
- // |
|
| 461 |
- // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l |
|
| 462 |
- // total 16 |
|
| 463 |
- // drwx------ 3 0 0 60 Sep 7 18:54 binds |
|
| 464 |
- // -rw-r--r-- 1 0 0 3345 Sep 7 18:54 config.json |
|
| 465 |
- // drwxr-xr-x 10 0 0 4096 Sep 6 17:26 layer0 |
|
| 466 |
- // drwxr-xr-x 1 0 0 4096 Sep 7 18:54 rootfs |
|
| 467 |
- // drwxr-xr-x 5 0 0 4096 Sep 7 18:54 scratch |
|
| 468 |
- // |
|
| 469 |
- // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds |
|
| 470 |
- // total 0 |
|
| 471 |
- // drwxrwxrwt 2 0 0 4096 Sep 7 16:51 target |
|
| 472 |
- |
|
| 473 |
- mds := []hcsshim.MappedDir{}
|
|
| 474 |
- specMounts := []specs.Mount{}
|
|
| 475 |
- for _, mount := range spec.Mounts {
|
|
| 476 |
- specMount := mount |
|
| 477 |
- if mount.Type == "bind" {
|
|
| 478 |
- // Strip out the uvmpath from the options |
|
| 479 |
- updatedOptions := []string{}
|
|
| 480 |
- uvmPath := "" |
|
| 481 |
- readonly := false |
|
| 482 |
- for _, opt := range mount.Options {
|
|
| 483 |
- dropOption := false |
|
| 484 |
- elements := strings.SplitN(opt, "=", 2) |
|
| 485 |
- switch elements[0] {
|
|
| 486 |
- case "uvmpath": |
|
| 487 |
- uvmPath = elements[1] |
|
| 488 |
- dropOption = true |
|
| 489 |
- case "rw": |
|
| 490 |
- case "ro": |
|
| 491 |
- readonly = true |
|
| 492 |
- case "rbind": |
|
| 493 |
- default: |
|
| 494 |
- return fmt.Errorf("unsupported option %q", opt)
|
|
| 495 |
- } |
|
| 496 |
- if !dropOption {
|
|
| 497 |
- updatedOptions = append(updatedOptions, opt) |
|
| 498 |
- } |
|
| 499 |
- } |
|
| 500 |
- mount.Options = updatedOptions |
|
| 501 |
- if uvmPath == "" {
|
|
| 502 |
- return fmt.Errorf("no uvmpath for bind mount %+v", mount)
|
|
| 503 |
- } |
|
| 504 |
- md := hcsshim.MappedDir{
|
|
| 505 |
- HostPath: mount.Source, |
|
| 506 |
- ContainerPath: path.Join(uvmPath, mount.Destination), |
|
| 507 |
- CreateInUtilityVM: true, |
|
| 508 |
- ReadOnly: readonly, |
|
| 509 |
- } |
|
| 510 |
- // If we are 1803/RS4+ enable LinuxMetadata support by default |
|
| 511 |
- if system.GetOSVersion().Build >= 17134 {
|
|
| 512 |
- md.LinuxMetadata = true |
|
| 513 |
- } |
|
| 514 |
- mds = append(mds, md) |
|
| 515 |
- specMount.Source = path.Join(uvmPath, mount.Destination) |
|
| 516 |
- } |
|
| 517 |
- specMounts = append(specMounts, specMount) |
|
| 518 |
- } |
|
| 519 |
- configuration.MappedDirectories = mds |
|
| 520 |
- |
|
| 521 |
- hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 522 |
- if err != nil {
|
|
| 523 |
- return err |
|
| 524 |
- } |
|
| 525 |
- |
|
| 526 |
- spec.Mounts = specMounts |
|
| 527 |
- |
|
| 528 |
- // Construct a container object for calling start on it. |
|
| 529 |
- ctr := &container{
|
|
| 530 |
- id: id, |
|
| 531 |
- execs: make(map[string]*process), |
|
| 532 |
- isWindows: false, |
|
| 533 |
- ociSpec: spec, |
|
| 534 |
- hcsContainer: hcsContainer, |
|
| 535 |
- status: StatusCreated, |
|
| 536 |
- waitCh: make(chan struct{}),
|
|
| 537 |
- } |
|
| 538 |
- |
|
| 539 |
- // Start the container. |
|
| 540 |
- logger.Debug("starting container")
|
|
| 541 |
- if err = hcsContainer.Start(); err != nil {
|
|
| 542 |
- c.logger.WithError(err).Error("failed to start container")
|
|
| 543 |
- ctr.debugGCS() |
|
| 544 |
- ctr.Lock() |
|
| 545 |
- if err := c.terminateContainer(ctr); err != nil {
|
|
| 546 |
- c.logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 547 |
- } else {
|
|
| 548 |
- c.logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 549 |
- } |
|
| 550 |
- ctr.Unlock() |
|
| 551 |
- return err |
|
| 552 |
- } |
|
| 553 |
- ctr.debugGCS() |
|
| 554 |
- |
|
| 555 |
- c.Lock() |
|
| 556 |
- c.containers[id] = ctr |
|
| 557 |
- c.Unlock() |
|
| 558 |
- |
|
| 559 |
- c.eventQ.append(id, func() {
|
|
| 560 |
- ei := EventInfo{
|
|
| 561 |
- ContainerID: id, |
|
| 562 |
- } |
|
| 563 |
- c.logger.WithFields(logrus.Fields{
|
|
| 564 |
- "container": ctr.id, |
|
| 565 |
- "event": EventCreate, |
|
| 566 |
- }).Info("sending event")
|
|
| 567 |
- err := c.backend.ProcessEvent(id, EventCreate, ei) |
|
| 568 |
- if err != nil {
|
|
| 569 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 570 |
- "container": id, |
|
| 571 |
- "event": EventCreate, |
|
| 572 |
- }).Error("failed to process event")
|
|
| 573 |
- } |
|
| 574 |
- }) |
|
| 575 |
- |
|
| 576 |
- logger.Debug("createLinux() completed successfully")
|
|
| 577 |
- return nil |
|
| 578 |
-} |
|
| 579 |
- |
|
| 580 |
-func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
|
|
| 581 |
- if spec.Windows.Resources != nil {
|
|
| 582 |
- if spec.Windows.Resources.CPU != nil {
|
|
| 583 |
- if spec.Windows.Resources.CPU.Count != nil {
|
|
| 584 |
- // This check is being done here rather than in adaptContainerSettings |
|
| 585 |
- // because we don't want to update the HostConfig in case this container |
|
| 586 |
- // is moved to a host with more CPUs than this one. |
|
| 587 |
- cpuCount := *spec.Windows.Resources.CPU.Count |
|
| 588 |
- hostCPUCount := uint64(sysinfo.NumCPU()) |
|
| 589 |
- if cpuCount > hostCPUCount {
|
|
| 590 |
- c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
|
|
| 591 |
- cpuCount = hostCPUCount |
|
| 592 |
- } |
|
| 593 |
- configuration.ProcessorCount = uint32(cpuCount) |
|
| 594 |
- } |
|
| 595 |
- if spec.Windows.Resources.CPU.Shares != nil {
|
|
| 596 |
- configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares) |
|
| 597 |
- } |
|
| 598 |
- if spec.Windows.Resources.CPU.Maximum != nil {
|
|
| 599 |
- configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum) |
|
| 600 |
- } |
|
| 601 |
- } |
|
| 602 |
- if spec.Windows.Resources.Memory != nil {
|
|
| 603 |
- if spec.Windows.Resources.Memory.Limit != nil {
|
|
| 604 |
- configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024 |
|
| 605 |
- } |
|
| 606 |
- } |
|
| 607 |
- } |
|
| 608 |
-} |
|
| 609 |
- |
|
| 610 |
-func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachStdio StdioCallback) (int, error) {
|
|
| 611 |
- ctr := c.getContainer(id) |
|
| 612 |
- switch {
|
|
| 613 |
- case ctr == nil: |
|
| 614 |
- return -1, errors.WithStack(newNotFoundError("no such container"))
|
|
| 615 |
- case ctr.init != nil: |
|
| 616 |
- return -1, errors.WithStack(newConflictError("container already started"))
|
|
| 617 |
- } |
|
| 618 |
- |
|
| 619 |
- logger := c.logger.WithField("container", id)
|
|
| 620 |
- |
|
| 621 |
- // Note we always tell HCS to create stdout as it's required |
|
| 622 |
- // regardless of '-i' or '-t' options, so that docker can always grab |
|
| 623 |
- // the output through logs. We also tell HCS to always create stdin, |
|
| 624 |
- // even if it's not used - it will be closed shortly. Stderr is only |
|
| 625 |
- // created if it we're not -t. |
|
| 626 |
- var ( |
|
| 627 |
- emulateConsole bool |
|
| 628 |
- createStdErrPipe bool |
|
| 629 |
- ) |
|
| 630 |
- if ctr.ociSpec.Process != nil {
|
|
| 631 |
- emulateConsole = ctr.ociSpec.Process.Terminal |
|
| 632 |
- createStdErrPipe = !ctr.ociSpec.Process.Terminal |
|
| 633 |
- } |
|
| 634 |
- |
|
| 635 |
- createProcessParms := &hcsshim.ProcessConfig{
|
|
| 636 |
- EmulateConsole: emulateConsole, |
|
| 637 |
- WorkingDirectory: ctr.ociSpec.Process.Cwd, |
|
| 638 |
- CreateStdInPipe: true, |
|
| 639 |
- CreateStdOutPipe: true, |
|
| 640 |
- CreateStdErrPipe: createStdErrPipe, |
|
| 641 |
- } |
|
| 642 |
- |
|
| 643 |
- if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
|
| 644 |
- createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 645 |
- createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 646 |
- } |
|
| 647 |
- |
|
| 648 |
- // Configure the environment for the process |
|
| 649 |
- createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
|
| 650 |
- if ctr.isWindows {
|
|
| 651 |
- createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ") |
|
| 652 |
- } else {
|
|
| 653 |
- createProcessParms.CommandArgs = ctr.ociSpec.Process.Args |
|
| 654 |
- } |
|
| 655 |
- createProcessParms.User = ctr.ociSpec.Process.User.Username |
|
| 656 |
- |
|
| 657 |
- // LCOW requires the raw OCI spec passed through HCS and onwards to |
|
| 658 |
- // GCS for the utility VM. |
|
| 659 |
- if !ctr.isWindows {
|
|
| 660 |
- ociBuf, err := json.Marshal(ctr.ociSpec) |
|
| 661 |
- if err != nil {
|
|
| 662 |
- return -1, err |
|
| 663 |
- } |
|
| 664 |
- ociRaw := json.RawMessage(ociBuf) |
|
| 665 |
- createProcessParms.OCISpecification = &ociRaw |
|
| 666 |
- } |
|
| 667 |
- |
|
| 668 |
- ctr.Lock() |
|
| 669 |
- defer ctr.Unlock() |
|
| 670 |
- |
|
| 671 |
- // Start the command running in the container. |
|
| 672 |
- newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) |
|
| 673 |
- if err != nil {
|
|
| 674 |
- logger.WithError(err).Error("CreateProcess() failed")
|
|
| 675 |
- return -1, err |
|
| 676 |
- } |
|
| 677 |
- defer func() {
|
|
| 678 |
- if err != nil {
|
|
| 679 |
- if err := newProcess.Kill(); err != nil {
|
|
| 680 |
- logger.WithError(err).Error("failed to kill process")
|
|
| 681 |
- } |
|
| 682 |
- go func() {
|
|
| 683 |
- if err := newProcess.Wait(); err != nil {
|
|
| 684 |
- logger.WithError(err).Error("failed to wait for process")
|
|
| 685 |
- } |
|
| 686 |
- if err := newProcess.Close(); err != nil {
|
|
| 687 |
- logger.WithError(err).Error("failed to clean process resources")
|
|
| 688 |
- } |
|
| 689 |
- }() |
|
| 690 |
- } |
|
| 691 |
- }() |
|
| 692 |
- p := &process{
|
|
| 693 |
- hcsProcess: newProcess, |
|
| 694 |
- id: InitProcessName, |
|
| 695 |
- pid: newProcess.Pid(), |
|
| 696 |
- } |
|
| 697 |
- logger.WithField("pid", p.pid).Debug("init process started")
|
|
| 698 |
- |
|
| 699 |
- dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal) |
|
| 700 |
- if err != nil {
|
|
| 701 |
- logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 702 |
- return -1, err |
|
| 703 |
- } |
|
| 704 |
- _, err = attachStdio(dio) |
|
| 705 |
- if err != nil {
|
|
| 706 |
- logger.WithError(err).Error("failed to attache stdio")
|
|
| 707 |
- return -1, err |
|
| 708 |
- } |
|
| 709 |
- ctr.status = StatusRunning |
|
| 710 |
- ctr.init = p |
|
| 711 |
- |
|
| 712 |
- // Spin up a go routine waiting for exit to handle cleanup |
|
| 713 |
- go c.reapProcess(ctr, p) |
|
| 714 |
- |
|
| 715 |
- // Generate the associated event |
|
| 716 |
- c.eventQ.append(id, func() {
|
|
| 717 |
- ei := EventInfo{
|
|
| 718 |
- ContainerID: id, |
|
| 719 |
- ProcessID: InitProcessName, |
|
| 720 |
- Pid: uint32(p.pid), |
|
| 721 |
- } |
|
| 722 |
- c.logger.WithFields(logrus.Fields{
|
|
| 723 |
- "container": ctr.id, |
|
| 724 |
- "event": EventStart, |
|
| 725 |
- "event-info": ei, |
|
| 726 |
- }).Info("sending event")
|
|
| 727 |
- err := c.backend.ProcessEvent(ei.ContainerID, EventStart, ei) |
|
| 728 |
- if err != nil {
|
|
| 729 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 730 |
- "container": id, |
|
| 731 |
- "event": EventStart, |
|
| 732 |
- "event-info": ei, |
|
| 733 |
- }).Error("failed to process event")
|
|
| 734 |
- } |
|
| 735 |
- }) |
|
| 736 |
- logger.Debug("start() completed")
|
|
| 737 |
- return p.pid, nil |
|
| 738 |
-} |
|
| 739 |
- |
|
| 740 |
-func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
|
|
| 741 |
- stdin, stdout, stderr, err := newProcess.Stdio() |
|
| 742 |
- if err != nil {
|
|
| 743 |
- return nil, err |
|
| 744 |
- } |
|
| 745 |
- |
|
| 746 |
- dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal) |
|
| 747 |
- |
|
| 748 |
- // Convert io.ReadClosers to io.Readers |
|
| 749 |
- if stdout != nil {
|
|
| 750 |
- dio.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
|
|
| 751 |
- } |
|
| 752 |
- if stderr != nil {
|
|
| 753 |
- dio.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
|
|
| 754 |
- } |
|
| 755 |
- return dio, nil |
|
| 756 |
-} |
|
| 757 |
- |
|
| 758 |
-// Exec adds a process in an running container |
|
| 759 |
-func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) {
|
|
| 760 |
- ctr := c.getContainer(containerID) |
|
| 761 |
- switch {
|
|
| 762 |
- case ctr == nil: |
|
| 763 |
- return -1, errors.WithStack(newNotFoundError("no such container"))
|
|
| 764 |
- case ctr.hcsContainer == nil: |
|
| 765 |
- return -1, errors.WithStack(newInvalidParameterError("container is not running"))
|
|
| 766 |
- case ctr.execs != nil && ctr.execs[processID] != nil: |
|
| 767 |
- return -1, errors.WithStack(newConflictError("id already in use"))
|
|
| 768 |
- } |
|
| 769 |
- logger := c.logger.WithFields(logrus.Fields{
|
|
| 770 |
- "container": containerID, |
|
| 771 |
- "exec": processID, |
|
| 772 |
- }) |
|
| 773 |
- |
|
| 774 |
- // Note we always tell HCS to |
|
| 775 |
- // create stdout as it's required regardless of '-i' or '-t' options, so that |
|
| 776 |
- // docker can always grab the output through logs. We also tell HCS to always |
|
| 777 |
- // create stdin, even if it's not used - it will be closed shortly. Stderr |
|
| 778 |
- // is only created if it we're not -t. |
|
| 779 |
- createProcessParms := hcsshim.ProcessConfig{
|
|
| 780 |
- CreateStdInPipe: true, |
|
| 781 |
- CreateStdOutPipe: true, |
|
| 782 |
- CreateStdErrPipe: !spec.Terminal, |
|
| 783 |
- } |
|
| 784 |
- if spec.Terminal {
|
|
| 785 |
- createProcessParms.EmulateConsole = true |
|
| 786 |
- if spec.ConsoleSize != nil {
|
|
| 787 |
- createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height) |
|
| 788 |
- createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width) |
|
| 789 |
- } |
|
| 790 |
- } |
|
| 791 |
- |
|
| 792 |
- // Take working directory from the process to add if it is defined, |
|
| 793 |
- // otherwise take from the first process. |
|
| 794 |
- if spec.Cwd != "" {
|
|
| 795 |
- createProcessParms.WorkingDirectory = spec.Cwd |
|
| 796 |
- } else {
|
|
| 797 |
- createProcessParms.WorkingDirectory = ctr.ociSpec.Process.Cwd |
|
| 798 |
- } |
|
| 799 |
- |
|
| 800 |
- // Configure the environment for the process |
|
| 801 |
- createProcessParms.Environment = setupEnvironmentVariables(spec.Env) |
|
| 802 |
- if ctr.isWindows {
|
|
| 803 |
- createProcessParms.CommandLine = strings.Join(spec.Args, " ") |
|
| 804 |
- } else {
|
|
| 805 |
- createProcessParms.CommandArgs = spec.Args |
|
| 806 |
- } |
|
| 807 |
- createProcessParms.User = spec.User.Username |
|
| 808 |
- |
|
| 809 |
- logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
|
|
| 810 |
- |
|
| 811 |
- // Start the command running in the container. |
|
| 812 |
- newProcess, err := ctr.hcsContainer.CreateProcess(&createProcessParms) |
|
| 813 |
- if err != nil {
|
|
| 814 |
- logger.WithError(err).Errorf("exec's CreateProcess() failed")
|
|
| 815 |
- return -1, err |
|
| 816 |
- } |
|
| 817 |
- pid := newProcess.Pid() |
|
| 818 |
- defer func() {
|
|
| 819 |
- if err != nil {
|
|
| 820 |
- if err := newProcess.Kill(); err != nil {
|
|
| 821 |
- logger.WithError(err).Error("failed to kill process")
|
|
| 822 |
- } |
|
| 823 |
- go func() {
|
|
| 824 |
- if err := newProcess.Wait(); err != nil {
|
|
| 825 |
- logger.WithError(err).Error("failed to wait for process")
|
|
| 826 |
- } |
|
| 827 |
- if err := newProcess.Close(); err != nil {
|
|
| 828 |
- logger.WithError(err).Error("failed to clean process resources")
|
|
| 829 |
- } |
|
| 830 |
- }() |
|
| 831 |
- } |
|
| 832 |
- }() |
|
| 833 |
- |
|
| 834 |
- dio, err := newIOFromProcess(newProcess, spec.Terminal) |
|
| 835 |
- if err != nil {
|
|
| 836 |
- logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 837 |
- return -1, err |
|
| 838 |
- } |
|
| 839 |
- // Tell the engine to attach streams back to the client |
|
| 840 |
- _, err = attachStdio(dio) |
|
| 841 |
- if err != nil {
|
|
| 842 |
- return -1, err |
|
| 843 |
- } |
|
| 844 |
- |
|
| 845 |
- p := &process{
|
|
| 846 |
- id: processID, |
|
| 847 |
- pid: pid, |
|
| 848 |
- hcsProcess: newProcess, |
|
| 849 |
- } |
|
| 850 |
- |
|
| 851 |
- // Add the process to the container's list of processes |
|
| 852 |
- ctr.Lock() |
|
| 853 |
- ctr.execs[processID] = p |
|
| 854 |
- ctr.Unlock() |
|
| 855 |
- |
|
| 856 |
- // Spin up a go routine waiting for exit to handle cleanup |
|
| 857 |
- go c.reapProcess(ctr, p) |
|
| 858 |
- |
|
| 859 |
- c.eventQ.append(ctr.id, func() {
|
|
| 860 |
- ei := EventInfo{
|
|
| 861 |
- ContainerID: ctr.id, |
|
| 862 |
- ProcessID: p.id, |
|
| 863 |
- Pid: uint32(p.pid), |
|
| 864 |
- } |
|
| 865 |
- c.logger.WithFields(logrus.Fields{
|
|
| 866 |
- "container": ctr.id, |
|
| 867 |
- "event": EventExecAdded, |
|
| 868 |
- "event-info": ei, |
|
| 869 |
- }).Info("sending event")
|
|
| 870 |
- err := c.backend.ProcessEvent(ctr.id, EventExecAdded, ei) |
|
| 871 |
- if err != nil {
|
|
| 872 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 873 |
- "container": ctr.id, |
|
| 874 |
- "event": EventExecAdded, |
|
| 875 |
- "event-info": ei, |
|
| 876 |
- }).Error("failed to process event")
|
|
| 877 |
- } |
|
| 878 |
- err = c.backend.ProcessEvent(ctr.id, EventExecStarted, ei) |
|
| 879 |
- if err != nil {
|
|
| 880 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 881 |
- "container": ctr.id, |
|
| 882 |
- "event": EventExecStarted, |
|
| 883 |
- "event-info": ei, |
|
| 884 |
- }).Error("failed to process event")
|
|
| 885 |
- } |
|
| 886 |
- }) |
|
| 887 |
- |
|
| 888 |
- return pid, nil |
|
| 889 |
-} |
|
| 890 |
- |
|
| 891 |
-// Signal handles `docker stop` on Windows. While Linux has support for |
|
| 892 |
-// the full range of signals, signals aren't really implemented on Windows. |
|
| 893 |
-// We fake supporting regular stop and -9 to force kill. |
|
| 894 |
-func (c *client) SignalProcess(_ context.Context, containerID, processID string, signal int) error {
|
|
| 895 |
- ctr, p, err := c.getProcess(containerID, processID) |
|
| 896 |
- if err != nil {
|
|
| 897 |
- return err |
|
| 898 |
- } |
|
| 899 |
- |
|
| 900 |
- logger := c.logger.WithFields(logrus.Fields{
|
|
| 901 |
- "container": containerID, |
|
| 902 |
- "process": processID, |
|
| 903 |
- "pid": p.pid, |
|
| 904 |
- "signal": signal, |
|
| 905 |
- }) |
|
| 906 |
- logger.Debug("Signal()")
|
|
| 907 |
- |
|
| 908 |
- if processID == InitProcessName {
|
|
| 909 |
- if syscall.Signal(signal) == syscall.SIGKILL {
|
|
| 910 |
- // Terminate the compute system |
|
| 911 |
- ctr.Lock() |
|
| 912 |
- ctr.terminateInvoked = true |
|
| 913 |
- if err := ctr.hcsContainer.Terminate(); err != nil {
|
|
| 914 |
- if !hcsshim.IsPending(err) {
|
|
| 915 |
- logger.WithError(err).Error("failed to terminate hccshim container")
|
|
| 916 |
- } |
|
| 917 |
- } |
|
| 918 |
- ctr.Unlock() |
|
| 919 |
- } else {
|
|
| 920 |
- // Shut down the container |
|
| 921 |
- if err := ctr.hcsContainer.Shutdown(); err != nil {
|
|
| 922 |
- if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
|
|
| 923 |
- // ignore errors |
|
| 924 |
- logger.WithError(err).Error("failed to shutdown hccshim container")
|
|
| 925 |
- } |
|
| 926 |
- } |
|
| 927 |
- } |
|
| 928 |
- } else {
|
|
| 929 |
- return p.hcsProcess.Kill() |
|
| 930 |
- } |
|
| 931 |
- |
|
| 932 |
- return nil |
|
| 933 |
-} |
|
| 934 |
- |
|
| 935 |
-// Resize handles a CLI event to resize an interactive docker run or docker |
|
| 936 |
-// exec window. |
|
| 937 |
-func (c *client) ResizeTerminal(_ context.Context, containerID, processID string, width, height int) error {
|
|
| 938 |
- _, p, err := c.getProcess(containerID, processID) |
|
| 939 |
- if err != nil {
|
|
| 940 |
- return err |
|
| 941 |
- } |
|
| 942 |
- |
|
| 943 |
- c.logger.WithFields(logrus.Fields{
|
|
| 944 |
- "container": containerID, |
|
| 945 |
- "process": processID, |
|
| 946 |
- "height": height, |
|
| 947 |
- "width": width, |
|
| 948 |
- "pid": p.pid, |
|
| 949 |
- }).Debug("resizing")
|
|
| 950 |
- return p.hcsProcess.ResizeConsole(uint16(width), uint16(height)) |
|
| 951 |
-} |
|
| 952 |
- |
|
| 953 |
-func (c *client) CloseStdin(_ context.Context, containerID, processID string) error {
|
|
| 954 |
- _, p, err := c.getProcess(containerID, processID) |
|
| 955 |
- if err != nil {
|
|
| 956 |
- return err |
|
| 957 |
- } |
|
| 958 |
- |
|
| 959 |
- return p.hcsProcess.CloseStdin() |
|
| 960 |
-} |
|
| 961 |
- |
|
| 962 |
-// Pause handles pause requests for containers |
|
| 963 |
-func (c *client) Pause(_ context.Context, containerID string) error {
|
|
| 964 |
- ctr, _, err := c.getProcess(containerID, InitProcessName) |
|
| 965 |
- if err != nil {
|
|
| 966 |
- return err |
|
| 967 |
- } |
|
| 968 |
- |
|
| 969 |
- if ctr.ociSpec.Windows.HyperV == nil {
|
|
| 970 |
- return errors.New("cannot pause Windows Server Containers")
|
|
| 971 |
- } |
|
| 972 |
- |
|
| 973 |
- ctr.Lock() |
|
| 974 |
- defer ctr.Unlock() |
|
| 975 |
- |
|
| 976 |
- if err = ctr.hcsContainer.Pause(); err != nil {
|
|
| 977 |
- return err |
|
| 978 |
- } |
|
| 979 |
- |
|
| 980 |
- ctr.status = StatusPaused |
|
| 981 |
- |
|
| 982 |
- c.eventQ.append(containerID, func() {
|
|
| 983 |
- err := c.backend.ProcessEvent(containerID, EventPaused, EventInfo{
|
|
| 984 |
- ContainerID: containerID, |
|
| 985 |
- ProcessID: InitProcessName, |
|
| 986 |
- }) |
|
| 987 |
- c.logger.WithFields(logrus.Fields{
|
|
| 988 |
- "container": ctr.id, |
|
| 989 |
- "event": EventPaused, |
|
| 990 |
- }).Info("sending event")
|
|
| 991 |
- if err != nil {
|
|
| 992 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 993 |
- "container": containerID, |
|
| 994 |
- "event": EventPaused, |
|
| 995 |
- }).Error("failed to process event")
|
|
| 996 |
- } |
|
| 997 |
- }) |
|
| 998 |
- |
|
| 999 |
- return nil |
|
| 1000 |
-} |
|
| 1001 |
- |
|
| 1002 |
-// Resume handles resume requests for containers |
|
| 1003 |
-func (c *client) Resume(_ context.Context, containerID string) error {
|
|
| 1004 |
- ctr, _, err := c.getProcess(containerID, InitProcessName) |
|
| 1005 |
- if err != nil {
|
|
| 1006 |
- return err |
|
| 1007 |
- } |
|
| 1008 |
- |
|
| 1009 |
- if ctr.ociSpec.Windows.HyperV == nil {
|
|
| 1010 |
- return errors.New("cannot resume Windows Server Containers")
|
|
| 1011 |
- } |
|
| 1012 |
- |
|
| 1013 |
- ctr.Lock() |
|
| 1014 |
- defer ctr.Unlock() |
|
| 1015 |
- |
|
| 1016 |
- if err = ctr.hcsContainer.Resume(); err != nil {
|
|
| 1017 |
- return err |
|
| 1018 |
- } |
|
| 1019 |
- |
|
| 1020 |
- ctr.status = StatusRunning |
|
| 1021 |
- |
|
| 1022 |
- c.eventQ.append(containerID, func() {
|
|
| 1023 |
- err := c.backend.ProcessEvent(containerID, EventResumed, EventInfo{
|
|
| 1024 |
- ContainerID: containerID, |
|
| 1025 |
- ProcessID: InitProcessName, |
|
| 1026 |
- }) |
|
| 1027 |
- c.logger.WithFields(logrus.Fields{
|
|
| 1028 |
- "container": ctr.id, |
|
| 1029 |
- "event": EventResumed, |
|
| 1030 |
- }).Info("sending event")
|
|
| 1031 |
- if err != nil {
|
|
| 1032 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 1033 |
- "container": containerID, |
|
| 1034 |
- "event": EventResumed, |
|
| 1035 |
- }).Error("failed to process event")
|
|
| 1036 |
- } |
|
| 1037 |
- }) |
|
| 1038 |
- |
|
| 1039 |
- return nil |
|
| 1040 |
-} |
|
| 1041 |
- |
|
| 1042 |
-// Stats handles stats requests for containers |
|
| 1043 |
-func (c *client) Stats(_ context.Context, containerID string) (*Stats, error) {
|
|
| 1044 |
- ctr, _, err := c.getProcess(containerID, InitProcessName) |
|
| 1045 |
- if err != nil {
|
|
| 1046 |
- return nil, err |
|
| 1047 |
- } |
|
| 1048 |
- |
|
| 1049 |
- readAt := time.Now() |
|
| 1050 |
- s, err := ctr.hcsContainer.Statistics() |
|
| 1051 |
- if err != nil {
|
|
| 1052 |
- return nil, err |
|
| 1053 |
- } |
|
| 1054 |
- return &Stats{
|
|
| 1055 |
- Read: readAt, |
|
| 1056 |
- HCSStats: &s, |
|
| 1057 |
- }, nil |
|
| 1058 |
-} |
|
| 1059 |
- |
|
| 1060 |
-// Restore is the handler for restoring a container |
|
| 1061 |
-func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallback) (bool, int, error) {
|
|
| 1062 |
- c.logger.WithField("container", id).Debug("restore()")
|
|
| 1063 |
- |
|
| 1064 |
- // TODO Windows: On RS1, a re-attach isn't possible. |
|
| 1065 |
- // However, there is a scenario in which there is an issue. |
|
| 1066 |
- // Consider a background container. The daemon dies unexpectedly. |
|
| 1067 |
- // HCS will still have the compute service alive and running. |
|
| 1068 |
- // For consistence, we call in to shoot it regardless if HCS knows about it |
|
| 1069 |
- // We explicitly just log a warning if the terminate fails. |
|
| 1070 |
- // Then we tell the backend the container exited. |
|
| 1071 |
- if hc, err := hcsshim.OpenContainer(id); err == nil {
|
|
| 1072 |
- const terminateTimeout = time.Minute * 2 |
|
| 1073 |
- err := hc.Terminate() |
|
| 1074 |
- |
|
| 1075 |
- if hcsshim.IsPending(err) {
|
|
| 1076 |
- err = hc.WaitTimeout(terminateTimeout) |
|
| 1077 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1078 |
- err = nil |
|
| 1079 |
- } |
|
| 1080 |
- |
|
| 1081 |
- if err != nil {
|
|
| 1082 |
- c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
|
|
| 1083 |
- return false, -1, err |
|
| 1084 |
- } |
|
| 1085 |
- } |
|
| 1086 |
- return false, -1, nil |
|
| 1087 |
-} |
|
| 1088 |
- |
|
| 1089 |
-// GetPidsForContainer returns a list of process IDs running in a container. |
|
| 1090 |
-// Not used on Windows. |
|
| 1091 |
-func (c *client) ListPids(_ context.Context, _ string) ([]uint32, error) {
|
|
| 1092 |
- return nil, errors.New("not implemented on Windows")
|
|
| 1093 |
-} |
|
| 1094 |
- |
|
| 1095 |
-// Summary returns a summary of the processes running in a container. |
|
| 1096 |
-// This is present in Windows to support docker top. In linux, the |
|
| 1097 |
-// engine shells out to ps to get process information. On Windows, as |
|
| 1098 |
-// the containers could be Hyper-V containers, they would not be |
|
| 1099 |
-// visible on the container host. However, libcontainerd does have |
|
| 1100 |
-// that information. |
|
| 1101 |
-func (c *client) Summary(_ context.Context, containerID string) ([]Summary, error) {
|
|
| 1102 |
- ctr, _, err := c.getProcess(containerID, InitProcessName) |
|
| 1103 |
- if err != nil {
|
|
| 1104 |
- return nil, err |
|
| 1105 |
- } |
|
| 1106 |
- |
|
| 1107 |
- p, err := ctr.hcsContainer.ProcessList() |
|
| 1108 |
- if err != nil {
|
|
| 1109 |
- return nil, err |
|
| 1110 |
- } |
|
| 1111 |
- |
|
| 1112 |
- pl := make([]Summary, len(p)) |
|
| 1113 |
- for i := range p {
|
|
| 1114 |
- pl[i] = Summary(p[i]) |
|
| 1115 |
- } |
|
| 1116 |
- return pl, nil |
|
| 1117 |
-} |
|
| 1118 |
- |
|
| 1119 |
-func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
|
|
| 1120 |
- ec := -1 |
|
| 1121 |
- ctr := c.getContainer(containerID) |
|
| 1122 |
- if ctr == nil {
|
|
| 1123 |
- return uint32(ec), time.Now(), errors.WithStack(newNotFoundError("no such container"))
|
|
| 1124 |
- } |
|
| 1125 |
- |
|
| 1126 |
- select {
|
|
| 1127 |
- case <-ctx.Done(): |
|
| 1128 |
- return uint32(ec), time.Now(), errors.WithStack(ctx.Err()) |
|
| 1129 |
- case <-ctr.waitCh: |
|
| 1130 |
- default: |
|
| 1131 |
- return uint32(ec), time.Now(), errors.New("container is not stopped")
|
|
| 1132 |
- } |
|
| 1133 |
- |
|
| 1134 |
- ctr.Lock() |
|
| 1135 |
- defer ctr.Unlock() |
|
| 1136 |
- return ctr.exitCode, ctr.exitedAt, nil |
|
| 1137 |
-} |
|
| 1138 |
- |
|
| 1139 |
-func (c *client) Delete(_ context.Context, containerID string) error {
|
|
| 1140 |
- c.Lock() |
|
| 1141 |
- defer c.Unlock() |
|
| 1142 |
- ctr := c.containers[containerID] |
|
| 1143 |
- if ctr == nil {
|
|
| 1144 |
- return errors.WithStack(newNotFoundError("no such container"))
|
|
| 1145 |
- } |
|
| 1146 |
- |
|
| 1147 |
- ctr.Lock() |
|
| 1148 |
- defer ctr.Unlock() |
|
| 1149 |
- |
|
| 1150 |
- switch ctr.status {
|
|
| 1151 |
- case StatusCreated: |
|
| 1152 |
- if err := c.shutdownContainer(ctr); err != nil {
|
|
| 1153 |
- return err |
|
| 1154 |
- } |
|
| 1155 |
- fallthrough |
|
| 1156 |
- case StatusStopped: |
|
| 1157 |
- delete(c.containers, containerID) |
|
| 1158 |
- return nil |
|
| 1159 |
- } |
|
| 1160 |
- |
|
| 1161 |
- return errors.WithStack(newInvalidParameterError("container is not stopped"))
|
|
| 1162 |
-} |
|
| 1163 |
- |
|
| 1164 |
-func (c *client) Status(ctx context.Context, containerID string) (Status, error) {
|
|
| 1165 |
- c.Lock() |
|
| 1166 |
- defer c.Unlock() |
|
| 1167 |
- ctr := c.containers[containerID] |
|
| 1168 |
- if ctr == nil {
|
|
| 1169 |
- return StatusUnknown, errors.WithStack(newNotFoundError("no such container"))
|
|
| 1170 |
- } |
|
| 1171 |
- |
|
| 1172 |
- ctr.Lock() |
|
| 1173 |
- defer ctr.Unlock() |
|
| 1174 |
- return ctr.status, nil |
|
| 1175 |
-} |
|
| 1176 |
- |
|
| 1177 |
-func (c *client) UpdateResources(ctx context.Context, containerID string, resources *Resources) error {
|
|
| 1178 |
- // Updating resource isn't supported on Windows |
|
| 1179 |
- // but we should return nil for enabling updating container |
|
| 1180 |
- return nil |
|
| 1181 |
-} |
|
| 1182 |
- |
|
| 1183 |
-func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
|
|
| 1184 |
- return errors.New("Windows: Containers do not support checkpoints")
|
|
| 1185 |
-} |
|
| 1186 |
- |
|
| 1187 |
-func (c *client) getContainer(id string) *container {
|
|
| 1188 |
- c.Lock() |
|
| 1189 |
- ctr := c.containers[id] |
|
| 1190 |
- c.Unlock() |
|
| 1191 |
- |
|
| 1192 |
- return ctr |
|
| 1193 |
-} |
|
| 1194 |
- |
|
| 1195 |
-func (c *client) getProcess(containerID, processID string) (*container, *process, error) {
|
|
| 1196 |
- ctr := c.getContainer(containerID) |
|
| 1197 |
- switch {
|
|
| 1198 |
- case ctr == nil: |
|
| 1199 |
- return nil, nil, errors.WithStack(newNotFoundError("no such container"))
|
|
| 1200 |
- case ctr.init == nil: |
|
| 1201 |
- return nil, nil, errors.WithStack(newNotFoundError("container is not running"))
|
|
| 1202 |
- case processID == InitProcessName: |
|
| 1203 |
- return ctr, ctr.init, nil |
|
| 1204 |
- default: |
|
| 1205 |
- ctr.Lock() |
|
| 1206 |
- defer ctr.Unlock() |
|
| 1207 |
- if ctr.execs == nil {
|
|
| 1208 |
- return nil, nil, errors.WithStack(newNotFoundError("no execs"))
|
|
| 1209 |
- } |
|
| 1210 |
- } |
|
| 1211 |
- |
|
| 1212 |
- p := ctr.execs[processID] |
|
| 1213 |
- if p == nil {
|
|
| 1214 |
- return nil, nil, errors.WithStack(newNotFoundError("no such exec"))
|
|
| 1215 |
- } |
|
| 1216 |
- |
|
| 1217 |
- return ctr, p, nil |
|
| 1218 |
-} |
|
| 1219 |
- |
|
| 1220 |
-// ctr mutex must be held when calling this function. |
|
| 1221 |
-func (c *client) shutdownContainer(ctr *container) error {
|
|
| 1222 |
- var err error |
|
| 1223 |
- const waitTimeout = time.Minute * 5 |
|
| 1224 |
- |
|
| 1225 |
- if !ctr.terminateInvoked {
|
|
| 1226 |
- err = ctr.hcsContainer.Shutdown() |
|
| 1227 |
- } |
|
| 1228 |
- |
|
| 1229 |
- if hcsshim.IsPending(err) || ctr.terminateInvoked {
|
|
| 1230 |
- err = ctr.hcsContainer.WaitTimeout(waitTimeout) |
|
| 1231 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1232 |
- err = nil |
|
| 1233 |
- } |
|
| 1234 |
- |
|
| 1235 |
- if err != nil {
|
|
| 1236 |
- c.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1237 |
- Debug("failed to shutdown container, terminating it")
|
|
| 1238 |
- terminateErr := c.terminateContainer(ctr) |
|
| 1239 |
- if terminateErr != nil {
|
|
| 1240 |
- c.logger.WithError(terminateErr).WithField("container", ctr.id).
|
|
| 1241 |
- Error("failed to shutdown container, and subsequent terminate also failed")
|
|
| 1242 |
- return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
|
|
| 1243 |
- } |
|
| 1244 |
- return err |
|
| 1245 |
- } |
|
| 1246 |
- |
|
| 1247 |
- return nil |
|
| 1248 |
-} |
|
| 1249 |
- |
|
| 1250 |
-// ctr mutex must be held when calling this function. |
|
| 1251 |
-func (c *client) terminateContainer(ctr *container) error {
|
|
| 1252 |
- const terminateTimeout = time.Minute * 5 |
|
| 1253 |
- ctr.terminateInvoked = true |
|
| 1254 |
- err := ctr.hcsContainer.Terminate() |
|
| 1255 |
- |
|
| 1256 |
- if hcsshim.IsPending(err) {
|
|
| 1257 |
- err = ctr.hcsContainer.WaitTimeout(terminateTimeout) |
|
| 1258 |
- } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1259 |
- err = nil |
|
| 1260 |
- } |
|
| 1261 |
- |
|
| 1262 |
- if err != nil {
|
|
| 1263 |
- c.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1264 |
- Debug("failed to terminate container")
|
|
| 1265 |
- return err |
|
| 1266 |
- } |
|
| 1267 |
- |
|
| 1268 |
- return nil |
|
| 1269 |
-} |
|
| 1270 |
- |
|
| 1271 |
-func (c *client) reapProcess(ctr *container, p *process) int {
|
|
| 1272 |
- logger := c.logger.WithFields(logrus.Fields{
|
|
| 1273 |
- "container": ctr.id, |
|
| 1274 |
- "process": p.id, |
|
| 1275 |
- }) |
|
| 1276 |
- |
|
| 1277 |
- var eventErr error |
|
| 1278 |
- |
|
| 1279 |
- // Block indefinitely for the process to exit. |
|
| 1280 |
- if err := p.hcsProcess.Wait(); err != nil {
|
|
| 1281 |
- if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1282 |
- logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
|
|
| 1283 |
- } |
|
| 1284 |
- // Fall through here, do not return. This ensures we attempt to |
|
| 1285 |
- // continue the shutdown in HCS and tell the docker engine that the |
|
| 1286 |
- // process/container has exited to avoid a container being dropped on |
|
| 1287 |
- // the floor. |
|
| 1288 |
- } |
|
| 1289 |
- exitedAt := time.Now() |
|
| 1290 |
- |
|
| 1291 |
- exitCode, err := p.hcsProcess.ExitCode() |
|
| 1292 |
- if err != nil {
|
|
| 1293 |
- if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1294 |
- logger.WithError(err).Warnf("unable to get exit code for process")
|
|
| 1295 |
- } |
|
| 1296 |
- // Since we got an error retrieving the exit code, make sure that the |
|
| 1297 |
- // code we return doesn't incorrectly indicate success. |
|
| 1298 |
- exitCode = -1 |
|
| 1299 |
- |
|
| 1300 |
- // Fall through here, do not return. This ensures we attempt to |
|
| 1301 |
- // continue the shutdown in HCS and tell the docker engine that the |
|
| 1302 |
- // process/container has exited to avoid a container being dropped on |
|
| 1303 |
- // the floor. |
|
| 1304 |
- } |
|
| 1305 |
- |
|
| 1306 |
- if err := p.hcsProcess.Close(); err != nil {
|
|
| 1307 |
- logger.WithError(err).Warnf("failed to cleanup hcs process resources")
|
|
| 1308 |
- exitCode = -1 |
|
| 1309 |
- eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
|
|
| 1310 |
- } |
|
| 1311 |
- |
|
| 1312 |
- if p.id == InitProcessName {
|
|
| 1313 |
- // Update container status |
|
| 1314 |
- ctr.Lock() |
|
| 1315 |
- ctr.status = StatusStopped |
|
| 1316 |
- ctr.exitedAt = exitedAt |
|
| 1317 |
- ctr.exitCode = uint32(exitCode) |
|
| 1318 |
- close(ctr.waitCh) |
|
| 1319 |
- |
|
| 1320 |
- if err := c.shutdownContainer(ctr); err != nil {
|
|
| 1321 |
- exitCode = -1 |
|
| 1322 |
- logger.WithError(err).Warn("failed to shutdown container")
|
|
| 1323 |
- thisErr := fmt.Errorf("failed to shutdown container: %s", err)
|
|
| 1324 |
- if eventErr != nil {
|
|
| 1325 |
- eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
|
|
| 1326 |
- } else {
|
|
| 1327 |
- eventErr = thisErr |
|
| 1328 |
- } |
|
| 1329 |
- } else {
|
|
| 1330 |
- logger.Debug("completed container shutdown")
|
|
| 1331 |
- } |
|
| 1332 |
- ctr.Unlock() |
|
| 1333 |
- |
|
| 1334 |
- if err := ctr.hcsContainer.Close(); err != nil {
|
|
| 1335 |
- exitCode = -1 |
|
| 1336 |
- logger.WithError(err).Error("failed to clean hcs container resources")
|
|
| 1337 |
- thisErr := fmt.Errorf("failed to terminate container: %s", err)
|
|
| 1338 |
- if eventErr != nil {
|
|
| 1339 |
- eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
|
|
| 1340 |
- } else {
|
|
| 1341 |
- eventErr = thisErr |
|
| 1342 |
- } |
|
| 1343 |
- } |
|
| 1344 |
- } |
|
| 1345 |
- |
|
| 1346 |
- c.eventQ.append(ctr.id, func() {
|
|
| 1347 |
- ei := EventInfo{
|
|
| 1348 |
- ContainerID: ctr.id, |
|
| 1349 |
- ProcessID: p.id, |
|
| 1350 |
- Pid: uint32(p.pid), |
|
| 1351 |
- ExitCode: uint32(exitCode), |
|
| 1352 |
- ExitedAt: exitedAt, |
|
| 1353 |
- Error: eventErr, |
|
| 1354 |
- } |
|
| 1355 |
- c.logger.WithFields(logrus.Fields{
|
|
| 1356 |
- "container": ctr.id, |
|
| 1357 |
- "event": EventExit, |
|
| 1358 |
- "event-info": ei, |
|
| 1359 |
- }).Info("sending event")
|
|
| 1360 |
- err := c.backend.ProcessEvent(ctr.id, EventExit, ei) |
|
| 1361 |
- if err != nil {
|
|
| 1362 |
- c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 1363 |
- "container": ctr.id, |
|
| 1364 |
- "event": EventExit, |
|
| 1365 |
- "event-info": ei, |
|
| 1366 |
- }).Error("failed to process event")
|
|
| 1367 |
- } |
|
| 1368 |
- if p.id != InitProcessName {
|
|
| 1369 |
- ctr.Lock() |
|
| 1370 |
- delete(ctr.execs, p.id) |
|
| 1371 |
- ctr.Unlock() |
|
| 1372 |
- } |
|
| 1373 |
- }) |
|
| 1374 |
- |
|
| 1375 |
- return exitCode |
|
| 1376 |
-} |
| 1377 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,13 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/errdefs" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-func newNotFoundError(err string) error { return errdefs.NotFound(errors.New(err)) }
|
|
| 10 |
- |
|
| 11 |
-func newInvalidParameterError(err string) error { return errdefs.InvalidParameter(errors.New(err)) }
|
|
| 12 |
- |
|
| 13 |
-func newConflictError(err string) error { return errdefs.Conflict(errors.New(err)) }
|
| 14 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/containerd/containerd" |
|
| 6 |
+ "github.com/docker/docker/libcontainerd/remote" |
|
| 7 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// NewClient creates a new libcontainerd client from a containerd client |
|
| 11 |
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 12 |
+ return remote.NewClient(ctx, cli, stateDir, ns, b) |
|
| 13 |
+} |
| 0 | 14 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,19 @@ |
| 0 |
+package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/containerd/containerd" |
|
| 6 |
+ "github.com/docker/docker/libcontainerd/local" |
|
| 7 |
+ "github.com/docker/docker/libcontainerd/remote" |
|
| 8 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 9 |
+ "github.com/docker/docker/pkg/system" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+// NewClient creates a new libcontainerd client from a containerd client |
|
| 13 |
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 14 |
+ if !system.ContainerdRuntimeSupported() {
|
|
| 15 |
+ return local.NewClient(ctx, cli, stateDir, ns, b) |
|
| 16 |
+ } |
|
| 17 |
+ return remote.NewClient(ctx, cli, stateDir, ns, b) |
|
| 18 |
+} |
| 0 | 19 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package local // import "github.com/docker/docker/libcontainerd/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) (n int, err error) {
|
|
| 16 |
+ n, err = r.ReadCloser.Read(b) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ r.Once.Do(func() { r.ReadCloser.Close() })
|
|
| 19 |
+ } |
|
| 20 |
+ return |
|
| 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 |
+} |
|
| 40 |
+ |
|
| 41 |
+func (p *process) Cleanup() error {
|
|
| 42 |
+ return nil |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,43 @@ |
| 0 |
+package local // import "github.com/docker/docker/libcontainerd/local" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ |
|
| 5 |
+ opengcs "github.com/Microsoft/opengcs/client" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// setupEnvironmentVariables converts a string array of environment variables |
|
| 9 |
+// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc. |
|
| 10 |
+func setupEnvironmentVariables(a []string) map[string]string {
|
|
| 11 |
+ r := make(map[string]string) |
|
| 12 |
+ for _, s := range a {
|
|
| 13 |
+ arr := strings.SplitN(s, "=", 2) |
|
| 14 |
+ if len(arr) == 2 {
|
|
| 15 |
+ r[arr[0]] = arr[1] |
|
| 16 |
+ } |
|
| 17 |
+ } |
|
| 18 |
+ return r |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// LCOWOption is a CreateOption required for LCOW configuration |
|
| 22 |
+type LCOWOption struct {
|
|
| 23 |
+ Config *opengcs.Config |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Apply for the LCOW option is a no-op. |
|
| 27 |
+func (s *LCOWOption) Apply(interface{}) error {
|
|
| 28 |
+ return nil |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+// debugGCS is a dirty hack for debugging for Linux Utility VMs. It simply |
|
| 32 |
+// runs a bunch of commands inside the UVM, but seriously aides in advanced debugging. |
|
| 33 |
+func (c *container) debugGCS() {
|
|
| 34 |
+ if c == nil || c.isWindows || c.hcsContainer == nil {
|
|
| 35 |
+ return |
|
| 36 |
+ } |
|
| 37 |
+ cfg := opengcs.Config{
|
|
| 38 |
+ Uvm: c.hcsContainer, |
|
| 39 |
+ UvmTimeoutSeconds: 600, |
|
| 40 |
+ } |
|
| 41 |
+ cfg.DebugGCS() |
|
| 42 |
+} |
| 0 | 43 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+package local // import "github.com/docker/docker/libcontainerd/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 |
+} |
| 0 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,1381 @@ |
| 0 |
+package local // import "github.com/docker/docker/libcontainerd/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 |
+ "encoding/json" |
|
| 8 |
+ "fmt" |
|
| 9 |
+ "io/ioutil" |
|
| 10 |
+ "os" |
|
| 11 |
+ "path" |
|
| 12 |
+ "path/filepath" |
|
| 13 |
+ "regexp" |
|
| 14 |
+ "strings" |
|
| 15 |
+ "sync" |
|
| 16 |
+ "syscall" |
|
| 17 |
+ "time" |
|
| 18 |
+ |
|
| 19 |
+ "github.com/Microsoft/hcsshim" |
|
| 20 |
+ opengcs "github.com/Microsoft/opengcs/client" |
|
| 21 |
+ "github.com/containerd/containerd" |
|
| 22 |
+ "github.com/containerd/containerd/cio" |
|
| 23 |
+ |
|
| 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/sysinfo" |
|
| 28 |
+ "github.com/docker/docker/pkg/system" |
|
| 29 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 30 |
+ "github.com/pkg/errors" |
|
| 31 |
+ "github.com/sirupsen/logrus" |
|
| 32 |
+ "golang.org/x/sys/windows" |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+type process struct {
|
|
| 36 |
+ id string |
|
| 37 |
+ pid int |
|
| 38 |
+ hcsProcess hcsshim.Process |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 41 |
+type container struct {
|
|
| 42 |
+ sync.Mutex |
|
| 43 |
+ |
|
| 44 |
+ // The ociSpec is required, as client.Create() needs a spec, but can |
|
| 45 |
+ // be called from the RestartManager context which does not otherwise |
|
| 46 |
+ // have access to the Spec |
|
| 47 |
+ ociSpec *specs.Spec |
|
| 48 |
+ |
|
| 49 |
+ isWindows bool |
|
| 50 |
+ hcsContainer hcsshim.Container |
|
| 51 |
+ |
|
| 52 |
+ id string |
|
| 53 |
+ status libcontainerdtypes.Status |
|
| 54 |
+ exitedAt time.Time |
|
| 55 |
+ exitCode uint32 |
|
| 56 |
+ waitCh chan struct{}
|
|
| 57 |
+ init *process |
|
| 58 |
+ execs map[string]*process |
|
| 59 |
+ terminateInvoked bool |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+// Win32 error codes that are used for various workarounds |
|
| 63 |
+// These really should be ALL_CAPS to match golangs syscall library and standard |
|
| 64 |
+// Win32 error conventions, but golint insists on CamelCase. |
|
| 65 |
+const ( |
|
| 66 |
+ CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string |
|
| 67 |
+ ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started |
|
| 68 |
+ ErrorBadPathname = syscall.Errno(161) // The specified path is invalid |
|
| 69 |
+ ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object |
|
| 70 |
+) |
|
| 71 |
+ |
|
| 72 |
+// defaultOwner is a tag passed to HCS to allow it to differentiate between |
|
| 73 |
+// container creator management stacks. We hard code "docker" in the case |
|
| 74 |
+// of docker. |
|
| 75 |
+const defaultOwner = "docker" |
|
| 76 |
+ |
|
| 77 |
+type client struct {
|
|
| 78 |
+ sync.Mutex |
|
| 79 |
+ |
|
| 80 |
+ stateDir string |
|
| 81 |
+ backend libcontainerdtypes.Backend |
|
| 82 |
+ logger *logrus.Entry |
|
| 83 |
+ eventQ queue.Queue |
|
| 84 |
+ containers map[string]*container |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+// NewClient creates a new local executor for windows |
|
| 88 |
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 89 |
+ c := &client{
|
|
| 90 |
+ stateDir: stateDir, |
|
| 91 |
+ backend: b, |
|
| 92 |
+ logger: logrus.WithField("module", "libcontainerd").WithField("module", "libcontainerd").WithField("namespace", ns),
|
|
| 93 |
+ containers: make(map[string]*container), |
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 96 |
+ return c, nil |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 100 |
+ return containerd.Version{}, errors.New("not implemented on Windows")
|
|
| 101 |
+} |
|
| 102 |
+ |
|
| 103 |
+// Create is the entrypoint to create a container from a spec. |
|
| 104 |
+// Table below shows the fields required for HCS JSON calling parameters, |
|
| 105 |
+// where if not populated, is omitted. |
|
| 106 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 107 |
+// | | Isolation=Process | Isolation=Hyper-V | |
|
| 108 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 109 |
+// | VolumePath | \\?\\Volume{GUIDa} | |
|
|
| 110 |
+// | LayerFolderPath | %root%\windowsfilter\containerID | | |
|
| 111 |
+// | Layers[] | ID=GUIDb;Path=%root%\windowsfilter\layerID | ID=GUIDb;Path=%root%\windowsfilter\layerID | |
|
| 112 |
+// | HvRuntime | | ImagePath=%root%\BaseLayerID\UtilityVM | |
|
| 113 |
+// +-----------------+--------------------------------------------+---------------------------------------------------+ |
|
| 114 |
+// |
|
| 115 |
+// Isolation=Process example: |
|
| 116 |
+// |
|
| 117 |
+// {
|
|
| 118 |
+// "SystemType": "Container", |
|
| 119 |
+// "Name": "5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 120 |
+// "Owner": "docker", |
|
| 121 |
+// "VolumePath": "\\\\\\\\?\\\\Volume{66d1ef4c-7a00-11e6-8948-00155ddbef9d}",
|
|
| 122 |
+// "IgnoreFlushesDuringBoot": true, |
|
| 123 |
+// "LayerFolderPath": "C:\\\\control\\\\windowsfilter\\\\5e0055c814a6005b8e57ac59f9a522066e0af12b48b3c26a9416e23907698776", |
|
| 124 |
+// "Layers": [{
|
|
| 125 |
+// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 126 |
+// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 127 |
+// }], |
|
| 128 |
+// "HostName": "5e0055c814a6", |
|
| 129 |
+// "MappedDirectories": [], |
|
| 130 |
+// "HvPartition": false, |
|
| 131 |
+// "EndpointList": ["eef2649d-bb17-4d53-9937-295a8efe6f2c"], |
|
| 132 |
+//} |
|
| 133 |
+// |
|
| 134 |
+// Isolation=Hyper-V example: |
|
| 135 |
+// |
|
| 136 |
+//{
|
|
| 137 |
+// "SystemType": "Container", |
|
| 138 |
+// "Name": "475c2c58933b72687a88a441e7e0ca4bd72d76413c5f9d5031fee83b98f6045d", |
|
| 139 |
+// "Owner": "docker", |
|
| 140 |
+// "IgnoreFlushesDuringBoot": true, |
|
| 141 |
+// "Layers": [{
|
|
| 142 |
+// "ID": "18955d65-d45a-557b-bf1c-49d6dfefc526", |
|
| 143 |
+// "Path": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c" |
|
| 144 |
+// }], |
|
| 145 |
+// "HostName": "475c2c58933b", |
|
| 146 |
+// "MappedDirectories": [], |
|
| 147 |
+// "HvPartition": true, |
|
| 148 |
+// "EndpointList": ["e1bb1e61-d56f-405e-b75d-fd520cefa0cb"], |
|
| 149 |
+// "DNSSearchList": "a.com,b.com,c.com", |
|
| 150 |
+// "HvRuntime": {
|
|
| 151 |
+// "ImagePath": "C:\\\\control\\\\windowsfilter\\\\65bf96e5760a09edf1790cb229e2dfb2dbd0fcdc0bf7451bae099106bfbfea0c\\\\UtilityVM" |
|
| 152 |
+// }, |
|
| 153 |
+//} |
|
| 154 |
+func (c *client) Create(_ context.Context, id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 155 |
+ if ctr := c.getContainer(id); ctr != nil {
|
|
| 156 |
+ return errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
|
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ // spec.Linux must be nil for Windows containers, but spec.Windows |
|
| 160 |
+ // will be filled in regardless of container platform. This is a |
|
| 161 |
+ // temporary workaround due to LCOW requiring layer folder paths, |
|
| 162 |
+ // which are stored under spec.Windows. |
|
| 163 |
+ // |
|
| 164 |
+ // TODO: @darrenstahlmsft fix this once the OCI spec is updated to |
|
| 165 |
+ // support layer folder paths for LCOW |
|
| 166 |
+ if spec.Linux == nil {
|
|
| 167 |
+ return c.createWindows(id, spec, runtimeOptions) |
|
| 168 |
+ } |
|
| 169 |
+ return c.createLinux(id, spec, runtimeOptions) |
|
| 170 |
+} |
|
| 171 |
+ |
|
| 172 |
+func (c *client) createWindows(id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 173 |
+ logger := c.logger.WithField("container", id)
|
|
| 174 |
+ configuration := &hcsshim.ContainerConfig{
|
|
| 175 |
+ SystemType: "Container", |
|
| 176 |
+ Name: id, |
|
| 177 |
+ Owner: defaultOwner, |
|
| 178 |
+ IgnoreFlushesDuringBoot: spec.Windows.IgnoreFlushesDuringBoot, |
|
| 179 |
+ HostName: spec.Hostname, |
|
| 180 |
+ HvPartition: false, |
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ c.extractResourcesFromSpec(spec, configuration) |
|
| 184 |
+ |
|
| 185 |
+ if spec.Windows.Resources != nil {
|
|
| 186 |
+ if spec.Windows.Resources.Storage != nil {
|
|
| 187 |
+ if spec.Windows.Resources.Storage.Bps != nil {
|
|
| 188 |
+ configuration.StorageBandwidthMaximum = *spec.Windows.Resources.Storage.Bps |
|
| 189 |
+ } |
|
| 190 |
+ if spec.Windows.Resources.Storage.Iops != nil {
|
|
| 191 |
+ configuration.StorageIOPSMaximum = *spec.Windows.Resources.Storage.Iops |
|
| 192 |
+ } |
|
| 193 |
+ } |
|
| 194 |
+ } |
|
| 195 |
+ |
|
| 196 |
+ if spec.Windows.HyperV != nil {
|
|
| 197 |
+ configuration.HvPartition = true |
|
| 198 |
+ } |
|
| 199 |
+ |
|
| 200 |
+ if spec.Windows.Network != nil {
|
|
| 201 |
+ configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 202 |
+ configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 203 |
+ if spec.Windows.Network.DNSSearchList != nil {
|
|
| 204 |
+ configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 205 |
+ } |
|
| 206 |
+ configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ if cs, ok := spec.Windows.CredentialSpec.(string); ok {
|
|
| 210 |
+ configuration.Credentials = cs |
|
| 211 |
+ } |
|
| 212 |
+ |
|
| 213 |
+ // We must have least two layers in the spec, the bottom one being a |
|
| 214 |
+ // base image, the top one being the RW layer. |
|
| 215 |
+ if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) < 2 {
|
|
| 216 |
+ return fmt.Errorf("OCI spec is invalid - at least two LayerFolders must be supplied to the runtime")
|
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 219 |
+ // Strip off the top-most layer as that's passed in separately to HCS |
|
| 220 |
+ configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 221 |
+ layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 222 |
+ |
|
| 223 |
+ if configuration.HvPartition {
|
|
| 224 |
+ // We don't currently support setting the utility VM image explicitly. |
|
| 225 |
+ // TODO @swernli/jhowardmsft circa RS5, this may be re-locatable. |
|
| 226 |
+ if spec.Windows.HyperV.UtilityVMPath != "" {
|
|
| 227 |
+ return errors.New("runtime does not support an explicit utility VM path for Hyper-V containers")
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ // Find the upper-most utility VM image. |
|
| 231 |
+ var uvmImagePath string |
|
| 232 |
+ for _, path := range layerFolders {
|
|
| 233 |
+ fullPath := filepath.Join(path, "UtilityVM") |
|
| 234 |
+ _, err := os.Stat(fullPath) |
|
| 235 |
+ if err == nil {
|
|
| 236 |
+ uvmImagePath = fullPath |
|
| 237 |
+ break |
|
| 238 |
+ } |
|
| 239 |
+ if !os.IsNotExist(err) {
|
|
| 240 |
+ return err |
|
| 241 |
+ } |
|
| 242 |
+ } |
|
| 243 |
+ if uvmImagePath == "" {
|
|
| 244 |
+ return errors.New("utility VM image could not be found")
|
|
| 245 |
+ } |
|
| 246 |
+ configuration.HvRuntime = &hcsshim.HvRuntime{ImagePath: uvmImagePath}
|
|
| 247 |
+ |
|
| 248 |
+ if spec.Root.Path != "" {
|
|
| 249 |
+ return errors.New("OCI spec is invalid - Root.Path must be omitted for a Hyper-V container")
|
|
| 250 |
+ } |
|
| 251 |
+ } else {
|
|
| 252 |
+ 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}\}\\$`
|
|
| 253 |
+ if _, err := regexp.MatchString(volumeGUIDRegex, spec.Root.Path); err != nil {
|
|
| 254 |
+ return fmt.Errorf(`OCI spec is invalid - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, spec.Root.Path)
|
|
| 255 |
+ } |
|
| 256 |
+ // HCS API requires the trailing backslash to be removed |
|
| 257 |
+ configuration.VolumePath = spec.Root.Path[:len(spec.Root.Path)-1] |
|
| 258 |
+ } |
|
| 259 |
+ |
|
| 260 |
+ if spec.Root.Readonly {
|
|
| 261 |
+ return errors.New(`OCI spec is invalid - Root.Readonly must not be set on Windows`) |
|
| 262 |
+ } |
|
| 263 |
+ |
|
| 264 |
+ for _, layerPath := range layerFolders {
|
|
| 265 |
+ _, filename := filepath.Split(layerPath) |
|
| 266 |
+ g, err := hcsshim.NameToGuid(filename) |
|
| 267 |
+ if err != nil {
|
|
| 268 |
+ return err |
|
| 269 |
+ } |
|
| 270 |
+ configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 271 |
+ ID: g.ToString(), |
|
| 272 |
+ Path: layerPath, |
|
| 273 |
+ }) |
|
| 274 |
+ } |
|
| 275 |
+ |
|
| 276 |
+ // Add the mounts (volumes, bind mounts etc) to the structure |
|
| 277 |
+ var mds []hcsshim.MappedDir |
|
| 278 |
+ var mps []hcsshim.MappedPipe |
|
| 279 |
+ for _, mount := range spec.Mounts {
|
|
| 280 |
+ const pipePrefix = `\\.\pipe\` |
|
| 281 |
+ if mount.Type != "" {
|
|
| 282 |
+ return fmt.Errorf("OCI spec is invalid - Mount.Type '%s' must not be set", mount.Type)
|
|
| 283 |
+ } |
|
| 284 |
+ if strings.HasPrefix(mount.Destination, pipePrefix) {
|
|
| 285 |
+ mp := hcsshim.MappedPipe{
|
|
| 286 |
+ HostPath: mount.Source, |
|
| 287 |
+ ContainerPipeName: mount.Destination[len(pipePrefix):], |
|
| 288 |
+ } |
|
| 289 |
+ mps = append(mps, mp) |
|
| 290 |
+ } else {
|
|
| 291 |
+ md := hcsshim.MappedDir{
|
|
| 292 |
+ HostPath: mount.Source, |
|
| 293 |
+ ContainerPath: mount.Destination, |
|
| 294 |
+ ReadOnly: false, |
|
| 295 |
+ } |
|
| 296 |
+ for _, o := range mount.Options {
|
|
| 297 |
+ if strings.ToLower(o) == "ro" {
|
|
| 298 |
+ md.ReadOnly = true |
|
| 299 |
+ } |
|
| 300 |
+ } |
|
| 301 |
+ mds = append(mds, md) |
|
| 302 |
+ } |
|
| 303 |
+ } |
|
| 304 |
+ configuration.MappedDirectories = mds |
|
| 305 |
+ if len(mps) > 0 && system.GetOSVersion().Build < 16299 { // RS3
|
|
| 306 |
+ return errors.New("named pipe mounts are not supported on this version of Windows")
|
|
| 307 |
+ } |
|
| 308 |
+ configuration.MappedPipes = mps |
|
| 309 |
+ |
|
| 310 |
+ if len(spec.Windows.Devices) > 0 {
|
|
| 311 |
+ // Add any device assignments |
|
| 312 |
+ if configuration.HvPartition {
|
|
| 313 |
+ return errors.New("device assignment is not supported for HyperV containers")
|
|
| 314 |
+ } |
|
| 315 |
+ if system.GetOSVersion().Build < 17763 { // RS5
|
|
| 316 |
+ return errors.New("device assignment requires Windows builds RS5 (17763+) or later")
|
|
| 317 |
+ } |
|
| 318 |
+ for _, d := range spec.Windows.Devices {
|
|
| 319 |
+ configuration.AssignedDevices = append(configuration.AssignedDevices, hcsshim.AssignedDevice{InterfaceClassGUID: d.ID})
|
|
| 320 |
+ } |
|
| 321 |
+ } |
|
| 322 |
+ |
|
| 323 |
+ hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 324 |
+ if err != nil {
|
|
| 325 |
+ return err |
|
| 326 |
+ } |
|
| 327 |
+ |
|
| 328 |
+ // Construct a container object for calling start on it. |
|
| 329 |
+ ctr := &container{
|
|
| 330 |
+ id: id, |
|
| 331 |
+ execs: make(map[string]*process), |
|
| 332 |
+ isWindows: true, |
|
| 333 |
+ ociSpec: spec, |
|
| 334 |
+ hcsContainer: hcsContainer, |
|
| 335 |
+ status: libcontainerdtypes.StatusCreated, |
|
| 336 |
+ waitCh: make(chan struct{}),
|
|
| 337 |
+ } |
|
| 338 |
+ |
|
| 339 |
+ logger.Debug("starting container")
|
|
| 340 |
+ if err = hcsContainer.Start(); err != nil {
|
|
| 341 |
+ c.logger.WithError(err).Error("failed to start container")
|
|
| 342 |
+ ctr.Lock() |
|
| 343 |
+ if err := c.terminateContainer(ctr); err != nil {
|
|
| 344 |
+ c.logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 345 |
+ } else {
|
|
| 346 |
+ c.logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 347 |
+ } |
|
| 348 |
+ ctr.Unlock() |
|
| 349 |
+ return err |
|
| 350 |
+ } |
|
| 351 |
+ |
|
| 352 |
+ c.Lock() |
|
| 353 |
+ c.containers[id] = ctr |
|
| 354 |
+ c.Unlock() |
|
| 355 |
+ |
|
| 356 |
+ logger.Debug("createWindows() completed successfully")
|
|
| 357 |
+ return nil |
|
| 358 |
+ |
|
| 359 |
+} |
|
| 360 |
+ |
|
| 361 |
+func (c *client) createLinux(id string, spec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 362 |
+ logrus.Debugf("libcontainerd: createLinux(): containerId %s ", id)
|
|
| 363 |
+ logger := c.logger.WithField("container", id)
|
|
| 364 |
+ |
|
| 365 |
+ if runtimeOptions == nil {
|
|
| 366 |
+ return fmt.Errorf("lcow option must be supplied to the runtime")
|
|
| 367 |
+ } |
|
| 368 |
+ lcowConfig, ok := runtimeOptions.(*opengcs.Config) |
|
| 369 |
+ if !ok {
|
|
| 370 |
+ return fmt.Errorf("lcow option must be supplied to the runtime")
|
|
| 371 |
+ } |
|
| 372 |
+ |
|
| 373 |
+ configuration := &hcsshim.ContainerConfig{
|
|
| 374 |
+ HvPartition: true, |
|
| 375 |
+ Name: id, |
|
| 376 |
+ SystemType: "container", |
|
| 377 |
+ ContainerType: "linux", |
|
| 378 |
+ Owner: defaultOwner, |
|
| 379 |
+ TerminateOnLastHandleClosed: true, |
|
| 380 |
+ } |
|
| 381 |
+ |
|
| 382 |
+ if lcowConfig.ActualMode == opengcs.ModeActualVhdx {
|
|
| 383 |
+ configuration.HvRuntime = &hcsshim.HvRuntime{
|
|
| 384 |
+ ImagePath: lcowConfig.Vhdx, |
|
| 385 |
+ BootSource: "Vhd", |
|
| 386 |
+ WritableBootSource: false, |
|
| 387 |
+ } |
|
| 388 |
+ } else {
|
|
| 389 |
+ configuration.HvRuntime = &hcsshim.HvRuntime{
|
|
| 390 |
+ ImagePath: lcowConfig.KirdPath, |
|
| 391 |
+ LinuxKernelFile: lcowConfig.KernelFile, |
|
| 392 |
+ LinuxInitrdFile: lcowConfig.InitrdFile, |
|
| 393 |
+ LinuxBootParameters: lcowConfig.BootParameters, |
|
| 394 |
+ } |
|
| 395 |
+ } |
|
| 396 |
+ |
|
| 397 |
+ if spec.Windows == nil {
|
|
| 398 |
+ return fmt.Errorf("spec.Windows must not be nil for LCOW containers")
|
|
| 399 |
+ } |
|
| 400 |
+ |
|
| 401 |
+ c.extractResourcesFromSpec(spec, configuration) |
|
| 402 |
+ |
|
| 403 |
+ // We must have least one layer in the spec |
|
| 404 |
+ if spec.Windows.LayerFolders == nil || len(spec.Windows.LayerFolders) == 0 {
|
|
| 405 |
+ return fmt.Errorf("OCI spec is invalid - at least one LayerFolders must be supplied to the runtime")
|
|
| 406 |
+ } |
|
| 407 |
+ |
|
| 408 |
+ // Strip off the top-most layer as that's passed in separately to HCS |
|
| 409 |
+ configuration.LayerFolderPath = spec.Windows.LayerFolders[len(spec.Windows.LayerFolders)-1] |
|
| 410 |
+ layerFolders := spec.Windows.LayerFolders[:len(spec.Windows.LayerFolders)-1] |
|
| 411 |
+ |
|
| 412 |
+ for _, layerPath := range layerFolders {
|
|
| 413 |
+ _, filename := filepath.Split(layerPath) |
|
| 414 |
+ g, err := hcsshim.NameToGuid(filename) |
|
| 415 |
+ if err != nil {
|
|
| 416 |
+ return err |
|
| 417 |
+ } |
|
| 418 |
+ configuration.Layers = append(configuration.Layers, hcsshim.Layer{
|
|
| 419 |
+ ID: g.ToString(), |
|
| 420 |
+ Path: filepath.Join(layerPath, "layer.vhd"), |
|
| 421 |
+ }) |
|
| 422 |
+ } |
|
| 423 |
+ |
|
| 424 |
+ if spec.Windows.Network != nil {
|
|
| 425 |
+ configuration.EndpointList = spec.Windows.Network.EndpointList |
|
| 426 |
+ configuration.AllowUnqualifiedDNSQuery = spec.Windows.Network.AllowUnqualifiedDNSQuery |
|
| 427 |
+ if spec.Windows.Network.DNSSearchList != nil {
|
|
| 428 |
+ configuration.DNSSearchList = strings.Join(spec.Windows.Network.DNSSearchList, ",") |
|
| 429 |
+ } |
|
| 430 |
+ configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
|
| 431 |
+ } |
|
| 432 |
+ |
|
| 433 |
+ // Add the mounts (volumes, bind mounts etc) to the structure. We have to do |
|
| 434 |
+ // some translation for both the mapped directories passed into HCS and in |
|
| 435 |
+ // the spec. |
|
| 436 |
+ // |
|
| 437 |
+ // For HCS, we only pass in the mounts from the spec which are type "bind". |
|
| 438 |
+ // Further, the "ContainerPath" field (which is a little mis-leadingly |
|
| 439 |
+ // named when it applies to the utility VM rather than the container in the |
|
| 440 |
+ // utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed |
|
| 441 |
+ // by the caller through a 'uvmpath' option. |
|
| 442 |
+ // |
|
| 443 |
+ // We do similar translation for the mounts in the spec by stripping out |
|
| 444 |
+ // the uvmpath option, and translating the Source path to the location in the |
|
| 445 |
+ // utility VM calculated above. |
|
| 446 |
+ // |
|
| 447 |
+ // From inside the utility VM, you would see a 9p mount such as in the following |
|
| 448 |
+ // where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds |
|
| 449 |
+ // specifically: |
|
| 450 |
+ // |
|
| 451 |
+ // / # mount |
|
| 452 |
+ // rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934) |
|
| 453 |
+ // proc on /proc type proc (rw,relatime) |
|
| 454 |
+ // sysfs on /sys type sysfs (rw,relatime) |
|
| 455 |
+ // udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755) |
|
| 456 |
+ // tmpfs on /run type tmpfs (rw,relatime) |
|
| 457 |
+ // cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma) |
|
| 458 |
+ // mqueue on /dev/mqueue type mqueue (rw,relatime) |
|
| 459 |
+ // devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000) |
|
| 460 |
+ // /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6) |
|
| 461 |
+ // /dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl) |
|
| 462 |
+ // /dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl) |
|
| 463 |
+ // overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work) |
|
| 464 |
+ // |
|
| 465 |
+ // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l |
|
| 466 |
+ // total 16 |
|
| 467 |
+ // drwx------ 3 0 0 60 Sep 7 18:54 binds |
|
| 468 |
+ // -rw-r--r-- 1 0 0 3345 Sep 7 18:54 config.json |
|
| 469 |
+ // drwxr-xr-x 10 0 0 4096 Sep 6 17:26 layer0 |
|
| 470 |
+ // drwxr-xr-x 1 0 0 4096 Sep 7 18:54 rootfs |
|
| 471 |
+ // drwxr-xr-x 5 0 0 4096 Sep 7 18:54 scratch |
|
| 472 |
+ // |
|
| 473 |
+ // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds |
|
| 474 |
+ // total 0 |
|
| 475 |
+ // drwxrwxrwt 2 0 0 4096 Sep 7 16:51 target |
|
| 476 |
+ |
|
| 477 |
+ mds := []hcsshim.MappedDir{}
|
|
| 478 |
+ specMounts := []specs.Mount{}
|
|
| 479 |
+ for _, mount := range spec.Mounts {
|
|
| 480 |
+ specMount := mount |
|
| 481 |
+ if mount.Type == "bind" {
|
|
| 482 |
+ // Strip out the uvmpath from the options |
|
| 483 |
+ updatedOptions := []string{}
|
|
| 484 |
+ uvmPath := "" |
|
| 485 |
+ readonly := false |
|
| 486 |
+ for _, opt := range mount.Options {
|
|
| 487 |
+ dropOption := false |
|
| 488 |
+ elements := strings.SplitN(opt, "=", 2) |
|
| 489 |
+ switch elements[0] {
|
|
| 490 |
+ case "uvmpath": |
|
| 491 |
+ uvmPath = elements[1] |
|
| 492 |
+ dropOption = true |
|
| 493 |
+ case "rw": |
|
| 494 |
+ case "ro": |
|
| 495 |
+ readonly = true |
|
| 496 |
+ case "rbind": |
|
| 497 |
+ default: |
|
| 498 |
+ return fmt.Errorf("unsupported option %q", opt)
|
|
| 499 |
+ } |
|
| 500 |
+ if !dropOption {
|
|
| 501 |
+ updatedOptions = append(updatedOptions, opt) |
|
| 502 |
+ } |
|
| 503 |
+ } |
|
| 504 |
+ mount.Options = updatedOptions |
|
| 505 |
+ if uvmPath == "" {
|
|
| 506 |
+ return fmt.Errorf("no uvmpath for bind mount %+v", mount)
|
|
| 507 |
+ } |
|
| 508 |
+ md := hcsshim.MappedDir{
|
|
| 509 |
+ HostPath: mount.Source, |
|
| 510 |
+ ContainerPath: path.Join(uvmPath, mount.Destination), |
|
| 511 |
+ CreateInUtilityVM: true, |
|
| 512 |
+ ReadOnly: readonly, |
|
| 513 |
+ } |
|
| 514 |
+ // If we are 1803/RS4+ enable LinuxMetadata support by default |
|
| 515 |
+ if system.GetOSVersion().Build >= 17134 {
|
|
| 516 |
+ md.LinuxMetadata = true |
|
| 517 |
+ } |
|
| 518 |
+ mds = append(mds, md) |
|
| 519 |
+ specMount.Source = path.Join(uvmPath, mount.Destination) |
|
| 520 |
+ } |
|
| 521 |
+ specMounts = append(specMounts, specMount) |
|
| 522 |
+ } |
|
| 523 |
+ configuration.MappedDirectories = mds |
|
| 524 |
+ |
|
| 525 |
+ hcsContainer, err := hcsshim.CreateContainer(id, configuration) |
|
| 526 |
+ if err != nil {
|
|
| 527 |
+ return err |
|
| 528 |
+ } |
|
| 529 |
+ |
|
| 530 |
+ spec.Mounts = specMounts |
|
| 531 |
+ |
|
| 532 |
+ // Construct a container object for calling start on it. |
|
| 533 |
+ ctr := &container{
|
|
| 534 |
+ id: id, |
|
| 535 |
+ execs: make(map[string]*process), |
|
| 536 |
+ isWindows: false, |
|
| 537 |
+ ociSpec: spec, |
|
| 538 |
+ hcsContainer: hcsContainer, |
|
| 539 |
+ status: libcontainerdtypes.StatusCreated, |
|
| 540 |
+ waitCh: make(chan struct{}),
|
|
| 541 |
+ } |
|
| 542 |
+ |
|
| 543 |
+ // Start the container. |
|
| 544 |
+ logger.Debug("starting container")
|
|
| 545 |
+ if err = hcsContainer.Start(); err != nil {
|
|
| 546 |
+ c.logger.WithError(err).Error("failed to start container")
|
|
| 547 |
+ ctr.debugGCS() |
|
| 548 |
+ ctr.Lock() |
|
| 549 |
+ if err := c.terminateContainer(ctr); err != nil {
|
|
| 550 |
+ c.logger.WithError(err).Error("failed to cleanup after a failed Start")
|
|
| 551 |
+ } else {
|
|
| 552 |
+ c.logger.Debug("cleaned up after failed Start by calling Terminate")
|
|
| 553 |
+ } |
|
| 554 |
+ ctr.Unlock() |
|
| 555 |
+ return err |
|
| 556 |
+ } |
|
| 557 |
+ ctr.debugGCS() |
|
| 558 |
+ |
|
| 559 |
+ c.Lock() |
|
| 560 |
+ c.containers[id] = ctr |
|
| 561 |
+ c.Unlock() |
|
| 562 |
+ |
|
| 563 |
+ c.eventQ.Append(id, func() {
|
|
| 564 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 565 |
+ ContainerID: id, |
|
| 566 |
+ } |
|
| 567 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 568 |
+ "container": ctr.id, |
|
| 569 |
+ "event": libcontainerdtypes.EventCreate, |
|
| 570 |
+ }).Info("sending event")
|
|
| 571 |
+ err := c.backend.ProcessEvent(id, libcontainerdtypes.EventCreate, ei) |
|
| 572 |
+ if err != nil {
|
|
| 573 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 574 |
+ "container": id, |
|
| 575 |
+ "event": libcontainerdtypes.EventCreate, |
|
| 576 |
+ }).Error("failed to process event")
|
|
| 577 |
+ } |
|
| 578 |
+ }) |
|
| 579 |
+ |
|
| 580 |
+ logger.Debug("createLinux() completed successfully")
|
|
| 581 |
+ return nil |
|
| 582 |
+} |
|
| 583 |
+ |
|
| 584 |
+func (c *client) extractResourcesFromSpec(spec *specs.Spec, configuration *hcsshim.ContainerConfig) {
|
|
| 585 |
+ if spec.Windows.Resources != nil {
|
|
| 586 |
+ if spec.Windows.Resources.CPU != nil {
|
|
| 587 |
+ if spec.Windows.Resources.CPU.Count != nil {
|
|
| 588 |
+ // This check is being done here rather than in adaptContainerSettings |
|
| 589 |
+ // because we don't want to update the HostConfig in case this container |
|
| 590 |
+ // is moved to a host with more CPUs than this one. |
|
| 591 |
+ cpuCount := *spec.Windows.Resources.CPU.Count |
|
| 592 |
+ hostCPUCount := uint64(sysinfo.NumCPU()) |
|
| 593 |
+ if cpuCount > hostCPUCount {
|
|
| 594 |
+ c.logger.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
|
|
| 595 |
+ cpuCount = hostCPUCount |
|
| 596 |
+ } |
|
| 597 |
+ configuration.ProcessorCount = uint32(cpuCount) |
|
| 598 |
+ } |
|
| 599 |
+ if spec.Windows.Resources.CPU.Shares != nil {
|
|
| 600 |
+ configuration.ProcessorWeight = uint64(*spec.Windows.Resources.CPU.Shares) |
|
| 601 |
+ } |
|
| 602 |
+ if spec.Windows.Resources.CPU.Maximum != nil {
|
|
| 603 |
+ configuration.ProcessorMaximum = int64(*spec.Windows.Resources.CPU.Maximum) |
|
| 604 |
+ } |
|
| 605 |
+ } |
|
| 606 |
+ if spec.Windows.Resources.Memory != nil {
|
|
| 607 |
+ if spec.Windows.Resources.Memory.Limit != nil {
|
|
| 608 |
+ configuration.MemoryMaximumInMB = int64(*spec.Windows.Resources.Memory.Limit) / 1024 / 1024 |
|
| 609 |
+ } |
|
| 610 |
+ } |
|
| 611 |
+ } |
|
| 612 |
+} |
|
| 613 |
+ |
|
| 614 |
+func (c *client) Start(_ context.Context, id, _ string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
|
|
| 615 |
+ ctr := c.getContainer(id) |
|
| 616 |
+ switch {
|
|
| 617 |
+ case ctr == nil: |
|
| 618 |
+ return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 619 |
+ case ctr.init != nil: |
|
| 620 |
+ return -1, errors.WithStack(errdefs.Conflict(errors.New("container already started")))
|
|
| 621 |
+ } |
|
| 622 |
+ |
|
| 623 |
+ logger := c.logger.WithField("container", id)
|
|
| 624 |
+ |
|
| 625 |
+ // Note we always tell HCS to create stdout as it's required |
|
| 626 |
+ // regardless of '-i' or '-t' options, so that docker can always grab |
|
| 627 |
+ // the output through logs. We also tell HCS to always create stdin, |
|
| 628 |
+ // even if it's not used - it will be closed shortly. Stderr is only |
|
| 629 |
+ // created if it we're not -t. |
|
| 630 |
+ var ( |
|
| 631 |
+ emulateConsole bool |
|
| 632 |
+ createStdErrPipe bool |
|
| 633 |
+ ) |
|
| 634 |
+ if ctr.ociSpec.Process != nil {
|
|
| 635 |
+ emulateConsole = ctr.ociSpec.Process.Terminal |
|
| 636 |
+ createStdErrPipe = !ctr.ociSpec.Process.Terminal |
|
| 637 |
+ } |
|
| 638 |
+ |
|
| 639 |
+ createProcessParms := &hcsshim.ProcessConfig{
|
|
| 640 |
+ EmulateConsole: emulateConsole, |
|
| 641 |
+ WorkingDirectory: ctr.ociSpec.Process.Cwd, |
|
| 642 |
+ CreateStdInPipe: true, |
|
| 643 |
+ CreateStdOutPipe: true, |
|
| 644 |
+ CreateStdErrPipe: createStdErrPipe, |
|
| 645 |
+ } |
|
| 646 |
+ |
|
| 647 |
+ if ctr.ociSpec.Process != nil && ctr.ociSpec.Process.ConsoleSize != nil {
|
|
| 648 |
+ createProcessParms.ConsoleSize[0] = uint(ctr.ociSpec.Process.ConsoleSize.Height) |
|
| 649 |
+ createProcessParms.ConsoleSize[1] = uint(ctr.ociSpec.Process.ConsoleSize.Width) |
|
| 650 |
+ } |
|
| 651 |
+ |
|
| 652 |
+ // Configure the environment for the process |
|
| 653 |
+ createProcessParms.Environment = setupEnvironmentVariables(ctr.ociSpec.Process.Env) |
|
| 654 |
+ if ctr.isWindows {
|
|
| 655 |
+ createProcessParms.CommandLine = strings.Join(ctr.ociSpec.Process.Args, " ") |
|
| 656 |
+ } else {
|
|
| 657 |
+ createProcessParms.CommandArgs = ctr.ociSpec.Process.Args |
|
| 658 |
+ } |
|
| 659 |
+ createProcessParms.User = ctr.ociSpec.Process.User.Username |
|
| 660 |
+ |
|
| 661 |
+ // LCOW requires the raw OCI spec passed through HCS and onwards to |
|
| 662 |
+ // GCS for the utility VM. |
|
| 663 |
+ if !ctr.isWindows {
|
|
| 664 |
+ ociBuf, err := json.Marshal(ctr.ociSpec) |
|
| 665 |
+ if err != nil {
|
|
| 666 |
+ return -1, err |
|
| 667 |
+ } |
|
| 668 |
+ ociRaw := json.RawMessage(ociBuf) |
|
| 669 |
+ createProcessParms.OCISpecification = &ociRaw |
|
| 670 |
+ } |
|
| 671 |
+ |
|
| 672 |
+ ctr.Lock() |
|
| 673 |
+ defer ctr.Unlock() |
|
| 674 |
+ |
|
| 675 |
+ // Start the command running in the container. |
|
| 676 |
+ newProcess, err := ctr.hcsContainer.CreateProcess(createProcessParms) |
|
| 677 |
+ if err != nil {
|
|
| 678 |
+ logger.WithError(err).Error("CreateProcess() failed")
|
|
| 679 |
+ return -1, err |
|
| 680 |
+ } |
|
| 681 |
+ defer func() {
|
|
| 682 |
+ if err != nil {
|
|
| 683 |
+ if err := newProcess.Kill(); err != nil {
|
|
| 684 |
+ logger.WithError(err).Error("failed to kill process")
|
|
| 685 |
+ } |
|
| 686 |
+ go func() {
|
|
| 687 |
+ if err := newProcess.Wait(); err != nil {
|
|
| 688 |
+ logger.WithError(err).Error("failed to wait for process")
|
|
| 689 |
+ } |
|
| 690 |
+ if err := newProcess.Close(); err != nil {
|
|
| 691 |
+ logger.WithError(err).Error("failed to clean process resources")
|
|
| 692 |
+ } |
|
| 693 |
+ }() |
|
| 694 |
+ } |
|
| 695 |
+ }() |
|
| 696 |
+ p := &process{
|
|
| 697 |
+ hcsProcess: newProcess, |
|
| 698 |
+ id: libcontainerdtypes.InitProcessName, |
|
| 699 |
+ pid: newProcess.Pid(), |
|
| 700 |
+ } |
|
| 701 |
+ logger.WithField("pid", p.pid).Debug("init process started")
|
|
| 702 |
+ |
|
| 703 |
+ dio, err := newIOFromProcess(newProcess, ctr.ociSpec.Process.Terminal) |
|
| 704 |
+ if err != nil {
|
|
| 705 |
+ logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 706 |
+ return -1, err |
|
| 707 |
+ } |
|
| 708 |
+ _, err = attachStdio(dio) |
|
| 709 |
+ if err != nil {
|
|
| 710 |
+ logger.WithError(err).Error("failed to attache stdio")
|
|
| 711 |
+ return -1, err |
|
| 712 |
+ } |
|
| 713 |
+ ctr.status = libcontainerdtypes.StatusRunning |
|
| 714 |
+ ctr.init = p |
|
| 715 |
+ |
|
| 716 |
+ // Spin up a go routine waiting for exit to handle cleanup |
|
| 717 |
+ go c.reapProcess(ctr, p) |
|
| 718 |
+ |
|
| 719 |
+ // Generate the associated event |
|
| 720 |
+ c.eventQ.Append(id, func() {
|
|
| 721 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 722 |
+ ContainerID: id, |
|
| 723 |
+ ProcessID: libcontainerdtypes.InitProcessName, |
|
| 724 |
+ Pid: uint32(p.pid), |
|
| 725 |
+ } |
|
| 726 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 727 |
+ "container": ctr.id, |
|
| 728 |
+ "event": libcontainerdtypes.EventStart, |
|
| 729 |
+ "event-info": ei, |
|
| 730 |
+ }).Info("sending event")
|
|
| 731 |
+ err := c.backend.ProcessEvent(ei.ContainerID, libcontainerdtypes.EventStart, ei) |
|
| 732 |
+ if err != nil {
|
|
| 733 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 734 |
+ "container": id, |
|
| 735 |
+ "event": libcontainerdtypes.EventStart, |
|
| 736 |
+ "event-info": ei, |
|
| 737 |
+ }).Error("failed to process event")
|
|
| 738 |
+ } |
|
| 739 |
+ }) |
|
| 740 |
+ logger.Debug("start() completed")
|
|
| 741 |
+ return p.pid, nil |
|
| 742 |
+} |
|
| 743 |
+ |
|
| 744 |
+func newIOFromProcess(newProcess hcsshim.Process, terminal bool) (*cio.DirectIO, error) {
|
|
| 745 |
+ stdin, stdout, stderr, err := newProcess.Stdio() |
|
| 746 |
+ if err != nil {
|
|
| 747 |
+ return nil, err |
|
| 748 |
+ } |
|
| 749 |
+ |
|
| 750 |
+ dio := cio.NewDirectIO(createStdInCloser(stdin, newProcess), nil, nil, terminal) |
|
| 751 |
+ |
|
| 752 |
+ // Convert io.ReadClosers to io.Readers |
|
| 753 |
+ if stdout != nil {
|
|
| 754 |
+ dio.Stdout = ioutil.NopCloser(&autoClosingReader{ReadCloser: stdout})
|
|
| 755 |
+ } |
|
| 756 |
+ if stderr != nil {
|
|
| 757 |
+ dio.Stderr = ioutil.NopCloser(&autoClosingReader{ReadCloser: stderr})
|
|
| 758 |
+ } |
|
| 759 |
+ return dio, nil |
|
| 760 |
+} |
|
| 761 |
+ |
|
| 762 |
+// Exec adds a process in an running container |
|
| 763 |
+func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
|
|
| 764 |
+ ctr := c.getContainer(containerID) |
|
| 765 |
+ switch {
|
|
| 766 |
+ case ctr == nil: |
|
| 767 |
+ return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 768 |
+ case ctr.hcsContainer == nil: |
|
| 769 |
+ return -1, errors.WithStack(errdefs.InvalidParameter(errors.New("container is not running")))
|
|
| 770 |
+ case ctr.execs != nil && ctr.execs[processID] != nil: |
|
| 771 |
+ return -1, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
|
|
| 772 |
+ } |
|
| 773 |
+ logger := c.logger.WithFields(logrus.Fields{
|
|
| 774 |
+ "container": containerID, |
|
| 775 |
+ "exec": processID, |
|
| 776 |
+ }) |
|
| 777 |
+ |
|
| 778 |
+ // Note we always tell HCS to |
|
| 779 |
+ // create stdout as it's required regardless of '-i' or '-t' options, so that |
|
| 780 |
+ // docker can always grab the output through logs. We also tell HCS to always |
|
| 781 |
+ // create stdin, even if it's not used - it will be closed shortly. Stderr |
|
| 782 |
+ // is only created if it we're not -t. |
|
| 783 |
+ createProcessParms := hcsshim.ProcessConfig{
|
|
| 784 |
+ CreateStdInPipe: true, |
|
| 785 |
+ CreateStdOutPipe: true, |
|
| 786 |
+ CreateStdErrPipe: !spec.Terminal, |
|
| 787 |
+ } |
|
| 788 |
+ if spec.Terminal {
|
|
| 789 |
+ createProcessParms.EmulateConsole = true |
|
| 790 |
+ if spec.ConsoleSize != nil {
|
|
| 791 |
+ createProcessParms.ConsoleSize[0] = uint(spec.ConsoleSize.Height) |
|
| 792 |
+ createProcessParms.ConsoleSize[1] = uint(spec.ConsoleSize.Width) |
|
| 793 |
+ } |
|
| 794 |
+ } |
|
| 795 |
+ |
|
| 796 |
+ // Take working directory from the process to add if it is defined, |
|
| 797 |
+ // otherwise take from the first process. |
|
| 798 |
+ if spec.Cwd != "" {
|
|
| 799 |
+ createProcessParms.WorkingDirectory = spec.Cwd |
|
| 800 |
+ } else {
|
|
| 801 |
+ createProcessParms.WorkingDirectory = ctr.ociSpec.Process.Cwd |
|
| 802 |
+ } |
|
| 803 |
+ |
|
| 804 |
+ // Configure the environment for the process |
|
| 805 |
+ createProcessParms.Environment = setupEnvironmentVariables(spec.Env) |
|
| 806 |
+ if ctr.isWindows {
|
|
| 807 |
+ createProcessParms.CommandLine = strings.Join(spec.Args, " ") |
|
| 808 |
+ } else {
|
|
| 809 |
+ createProcessParms.CommandArgs = spec.Args |
|
| 810 |
+ } |
|
| 811 |
+ createProcessParms.User = spec.User.Username |
|
| 812 |
+ |
|
| 813 |
+ logger.Debugf("exec commandLine: %s", createProcessParms.CommandLine)
|
|
| 814 |
+ |
|
| 815 |
+ // Start the command running in the container. |
|
| 816 |
+ newProcess, err := ctr.hcsContainer.CreateProcess(&createProcessParms) |
|
| 817 |
+ if err != nil {
|
|
| 818 |
+ logger.WithError(err).Errorf("exec's CreateProcess() failed")
|
|
| 819 |
+ return -1, err |
|
| 820 |
+ } |
|
| 821 |
+ pid := newProcess.Pid() |
|
| 822 |
+ defer func() {
|
|
| 823 |
+ if err != nil {
|
|
| 824 |
+ if err := newProcess.Kill(); err != nil {
|
|
| 825 |
+ logger.WithError(err).Error("failed to kill process")
|
|
| 826 |
+ } |
|
| 827 |
+ go func() {
|
|
| 828 |
+ if err := newProcess.Wait(); err != nil {
|
|
| 829 |
+ logger.WithError(err).Error("failed to wait for process")
|
|
| 830 |
+ } |
|
| 831 |
+ if err := newProcess.Close(); err != nil {
|
|
| 832 |
+ logger.WithError(err).Error("failed to clean process resources")
|
|
| 833 |
+ } |
|
| 834 |
+ }() |
|
| 835 |
+ } |
|
| 836 |
+ }() |
|
| 837 |
+ |
|
| 838 |
+ dio, err := newIOFromProcess(newProcess, spec.Terminal) |
|
| 839 |
+ if err != nil {
|
|
| 840 |
+ logger.WithError(err).Error("failed to get stdio pipes")
|
|
| 841 |
+ return -1, err |
|
| 842 |
+ } |
|
| 843 |
+ // Tell the engine to attach streams back to the client |
|
| 844 |
+ _, err = attachStdio(dio) |
|
| 845 |
+ if err != nil {
|
|
| 846 |
+ return -1, err |
|
| 847 |
+ } |
|
| 848 |
+ |
|
| 849 |
+ p := &process{
|
|
| 850 |
+ id: processID, |
|
| 851 |
+ pid: pid, |
|
| 852 |
+ hcsProcess: newProcess, |
|
| 853 |
+ } |
|
| 854 |
+ |
|
| 855 |
+ // Add the process to the container's list of processes |
|
| 856 |
+ ctr.Lock() |
|
| 857 |
+ ctr.execs[processID] = p |
|
| 858 |
+ ctr.Unlock() |
|
| 859 |
+ |
|
| 860 |
+ // Spin up a go routine waiting for exit to handle cleanup |
|
| 861 |
+ go c.reapProcess(ctr, p) |
|
| 862 |
+ |
|
| 863 |
+ c.eventQ.Append(ctr.id, func() {
|
|
| 864 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 865 |
+ ContainerID: ctr.id, |
|
| 866 |
+ ProcessID: p.id, |
|
| 867 |
+ Pid: uint32(p.pid), |
|
| 868 |
+ } |
|
| 869 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 870 |
+ "container": ctr.id, |
|
| 871 |
+ "event": libcontainerdtypes.EventExecAdded, |
|
| 872 |
+ "event-info": ei, |
|
| 873 |
+ }).Info("sending event")
|
|
| 874 |
+ err := c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExecAdded, ei) |
|
| 875 |
+ if err != nil {
|
|
| 876 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 877 |
+ "container": ctr.id, |
|
| 878 |
+ "event": libcontainerdtypes.EventExecAdded, |
|
| 879 |
+ "event-info": ei, |
|
| 880 |
+ }).Error("failed to process event")
|
|
| 881 |
+ } |
|
| 882 |
+ err = c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExecStarted, ei) |
|
| 883 |
+ if err != nil {
|
|
| 884 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 885 |
+ "container": ctr.id, |
|
| 886 |
+ "event": libcontainerdtypes.EventExecStarted, |
|
| 887 |
+ "event-info": ei, |
|
| 888 |
+ }).Error("failed to process event")
|
|
| 889 |
+ } |
|
| 890 |
+ }) |
|
| 891 |
+ |
|
| 892 |
+ return pid, nil |
|
| 893 |
+} |
|
| 894 |
+ |
|
| 895 |
+// Signal handles `docker stop` on Windows. While Linux has support for |
|
| 896 |
+// the full range of signals, signals aren't really implemented on Windows. |
|
| 897 |
+// We fake supporting regular stop and -9 to force kill. |
|
| 898 |
+func (c *client) SignalProcess(_ context.Context, containerID, processID string, signal int) error {
|
|
| 899 |
+ ctr, p, err := c.getProcess(containerID, processID) |
|
| 900 |
+ if err != nil {
|
|
| 901 |
+ return err |
|
| 902 |
+ } |
|
| 903 |
+ |
|
| 904 |
+ logger := c.logger.WithFields(logrus.Fields{
|
|
| 905 |
+ "container": containerID, |
|
| 906 |
+ "process": processID, |
|
| 907 |
+ "pid": p.pid, |
|
| 908 |
+ "signal": signal, |
|
| 909 |
+ }) |
|
| 910 |
+ logger.Debug("Signal()")
|
|
| 911 |
+ |
|
| 912 |
+ if processID == libcontainerdtypes.InitProcessName {
|
|
| 913 |
+ if syscall.Signal(signal) == syscall.SIGKILL {
|
|
| 914 |
+ // Terminate the compute system |
|
| 915 |
+ ctr.Lock() |
|
| 916 |
+ ctr.terminateInvoked = true |
|
| 917 |
+ if err := ctr.hcsContainer.Terminate(); err != nil {
|
|
| 918 |
+ if !hcsshim.IsPending(err) {
|
|
| 919 |
+ logger.WithError(err).Error("failed to terminate hccshim container")
|
|
| 920 |
+ } |
|
| 921 |
+ } |
|
| 922 |
+ ctr.Unlock() |
|
| 923 |
+ } else {
|
|
| 924 |
+ // Shut down the container |
|
| 925 |
+ if err := ctr.hcsContainer.Shutdown(); err != nil {
|
|
| 926 |
+ if !hcsshim.IsPending(err) && !hcsshim.IsAlreadyStopped(err) {
|
|
| 927 |
+ // ignore errors |
|
| 928 |
+ logger.WithError(err).Error("failed to shutdown hccshim container")
|
|
| 929 |
+ } |
|
| 930 |
+ } |
|
| 931 |
+ } |
|
| 932 |
+ } else {
|
|
| 933 |
+ return p.hcsProcess.Kill() |
|
| 934 |
+ } |
|
| 935 |
+ |
|
| 936 |
+ return nil |
|
| 937 |
+} |
|
| 938 |
+ |
|
| 939 |
+// Resize handles a CLI event to resize an interactive docker run or docker |
|
| 940 |
+// exec window. |
|
| 941 |
+func (c *client) ResizeTerminal(_ context.Context, containerID, processID string, width, height int) error {
|
|
| 942 |
+ _, p, err := c.getProcess(containerID, processID) |
|
| 943 |
+ if err != nil {
|
|
| 944 |
+ return err |
|
| 945 |
+ } |
|
| 946 |
+ |
|
| 947 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 948 |
+ "container": containerID, |
|
| 949 |
+ "process": processID, |
|
| 950 |
+ "height": height, |
|
| 951 |
+ "width": width, |
|
| 952 |
+ "pid": p.pid, |
|
| 953 |
+ }).Debug("resizing")
|
|
| 954 |
+ return p.hcsProcess.ResizeConsole(uint16(width), uint16(height)) |
|
| 955 |
+} |
|
| 956 |
+ |
|
| 957 |
+func (c *client) CloseStdin(_ context.Context, containerID, processID string) error {
|
|
| 958 |
+ _, p, err := c.getProcess(containerID, processID) |
|
| 959 |
+ if err != nil {
|
|
| 960 |
+ return err |
|
| 961 |
+ } |
|
| 962 |
+ |
|
| 963 |
+ return p.hcsProcess.CloseStdin() |
|
| 964 |
+} |
|
| 965 |
+ |
|
| 966 |
+// Pause handles pause requests for containers |
|
| 967 |
+func (c *client) Pause(_ context.Context, containerID string) error {
|
|
| 968 |
+ ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 969 |
+ if err != nil {
|
|
| 970 |
+ return err |
|
| 971 |
+ } |
|
| 972 |
+ |
|
| 973 |
+ if ctr.ociSpec.Windows.HyperV == nil {
|
|
| 974 |
+ return errors.New("cannot pause Windows Server Containers")
|
|
| 975 |
+ } |
|
| 976 |
+ |
|
| 977 |
+ ctr.Lock() |
|
| 978 |
+ defer ctr.Unlock() |
|
| 979 |
+ |
|
| 980 |
+ if err = ctr.hcsContainer.Pause(); err != nil {
|
|
| 981 |
+ return err |
|
| 982 |
+ } |
|
| 983 |
+ |
|
| 984 |
+ ctr.status = libcontainerdtypes.StatusPaused |
|
| 985 |
+ |
|
| 986 |
+ c.eventQ.Append(containerID, func() {
|
|
| 987 |
+ err := c.backend.ProcessEvent(containerID, libcontainerdtypes.EventPaused, libcontainerdtypes.EventInfo{
|
|
| 988 |
+ ContainerID: containerID, |
|
| 989 |
+ ProcessID: libcontainerdtypes.InitProcessName, |
|
| 990 |
+ }) |
|
| 991 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 992 |
+ "container": ctr.id, |
|
| 993 |
+ "event": libcontainerdtypes.EventPaused, |
|
| 994 |
+ }).Info("sending event")
|
|
| 995 |
+ if err != nil {
|
|
| 996 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 997 |
+ "container": containerID, |
|
| 998 |
+ "event": libcontainerdtypes.EventPaused, |
|
| 999 |
+ }).Error("failed to process event")
|
|
| 1000 |
+ } |
|
| 1001 |
+ }) |
|
| 1002 |
+ |
|
| 1003 |
+ return nil |
|
| 1004 |
+} |
|
| 1005 |
+ |
|
| 1006 |
+// Resume handles resume requests for containers |
|
| 1007 |
+func (c *client) Resume(_ context.Context, containerID string) error {
|
|
| 1008 |
+ ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 1009 |
+ if err != nil {
|
|
| 1010 |
+ return err |
|
| 1011 |
+ } |
|
| 1012 |
+ |
|
| 1013 |
+ if ctr.ociSpec.Windows.HyperV == nil {
|
|
| 1014 |
+ return errors.New("cannot resume Windows Server Containers")
|
|
| 1015 |
+ } |
|
| 1016 |
+ |
|
| 1017 |
+ ctr.Lock() |
|
| 1018 |
+ defer ctr.Unlock() |
|
| 1019 |
+ |
|
| 1020 |
+ if err = ctr.hcsContainer.Resume(); err != nil {
|
|
| 1021 |
+ return err |
|
| 1022 |
+ } |
|
| 1023 |
+ |
|
| 1024 |
+ ctr.status = libcontainerdtypes.StatusRunning |
|
| 1025 |
+ |
|
| 1026 |
+ c.eventQ.Append(containerID, func() {
|
|
| 1027 |
+ err := c.backend.ProcessEvent(containerID, libcontainerdtypes.EventResumed, libcontainerdtypes.EventInfo{
|
|
| 1028 |
+ ContainerID: containerID, |
|
| 1029 |
+ ProcessID: libcontainerdtypes.InitProcessName, |
|
| 1030 |
+ }) |
|
| 1031 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 1032 |
+ "container": ctr.id, |
|
| 1033 |
+ "event": libcontainerdtypes.EventResumed, |
|
| 1034 |
+ }).Info("sending event")
|
|
| 1035 |
+ if err != nil {
|
|
| 1036 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 1037 |
+ "container": containerID, |
|
| 1038 |
+ "event": libcontainerdtypes.EventResumed, |
|
| 1039 |
+ }).Error("failed to process event")
|
|
| 1040 |
+ } |
|
| 1041 |
+ }) |
|
| 1042 |
+ |
|
| 1043 |
+ return nil |
|
| 1044 |
+} |
|
| 1045 |
+ |
|
| 1046 |
+// Stats handles stats requests for containers |
|
| 1047 |
+func (c *client) Stats(_ context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
|
|
| 1048 |
+ ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 1049 |
+ if err != nil {
|
|
| 1050 |
+ return nil, err |
|
| 1051 |
+ } |
|
| 1052 |
+ |
|
| 1053 |
+ readAt := time.Now() |
|
| 1054 |
+ s, err := ctr.hcsContainer.Statistics() |
|
| 1055 |
+ if err != nil {
|
|
| 1056 |
+ return nil, err |
|
| 1057 |
+ } |
|
| 1058 |
+ return &libcontainerdtypes.Stats{
|
|
| 1059 |
+ Read: readAt, |
|
| 1060 |
+ HCSStats: &s, |
|
| 1061 |
+ }, nil |
|
| 1062 |
+} |
|
| 1063 |
+ |
|
| 1064 |
+// Restore is the handler for restoring a container |
|
| 1065 |
+func (c *client) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (bool, int, error) {
|
|
| 1066 |
+ c.logger.WithField("container", id).Debug("restore()")
|
|
| 1067 |
+ |
|
| 1068 |
+ // TODO Windows: On RS1, a re-attach isn't possible. |
|
| 1069 |
+ // However, there is a scenario in which there is an issue. |
|
| 1070 |
+ // Consider a background container. The daemon dies unexpectedly. |
|
| 1071 |
+ // HCS will still have the compute service alive and running. |
|
| 1072 |
+ // For consistence, we call in to shoot it regardless if HCS knows about it |
|
| 1073 |
+ // We explicitly just log a warning if the terminate fails. |
|
| 1074 |
+ // Then we tell the backend the container exited. |
|
| 1075 |
+ if hc, err := hcsshim.OpenContainer(id); err == nil {
|
|
| 1076 |
+ const terminateTimeout = time.Minute * 2 |
|
| 1077 |
+ err := hc.Terminate() |
|
| 1078 |
+ |
|
| 1079 |
+ if hcsshim.IsPending(err) {
|
|
| 1080 |
+ err = hc.WaitTimeout(terminateTimeout) |
|
| 1081 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1082 |
+ err = nil |
|
| 1083 |
+ } |
|
| 1084 |
+ |
|
| 1085 |
+ if err != nil {
|
|
| 1086 |
+ c.logger.WithField("container", id).WithError(err).Debug("terminate failed on restore")
|
|
| 1087 |
+ return false, -1, err |
|
| 1088 |
+ } |
|
| 1089 |
+ } |
|
| 1090 |
+ return false, -1, nil |
|
| 1091 |
+} |
|
| 1092 |
+ |
|
| 1093 |
+// GetPidsForContainer returns a list of process IDs running in a container. |
|
| 1094 |
+// Not used on Windows. |
|
| 1095 |
+func (c *client) ListPids(_ context.Context, _ string) ([]uint32, error) {
|
|
| 1096 |
+ return nil, errors.New("not implemented on Windows")
|
|
| 1097 |
+} |
|
| 1098 |
+ |
|
| 1099 |
+// Summary returns a summary of the processes running in a container. |
|
| 1100 |
+// This is present in Windows to support docker top. In linux, the |
|
| 1101 |
+// engine shells out to ps to get process information. On Windows, as |
|
| 1102 |
+// the containers could be Hyper-V containers, they would not be |
|
| 1103 |
+// visible on the container host. However, libcontainerd does have |
|
| 1104 |
+// that information. |
|
| 1105 |
+func (c *client) Summary(_ context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
|
|
| 1106 |
+ ctr, _, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 1107 |
+ if err != nil {
|
|
| 1108 |
+ return nil, err |
|
| 1109 |
+ } |
|
| 1110 |
+ |
|
| 1111 |
+ p, err := ctr.hcsContainer.ProcessList() |
|
| 1112 |
+ if err != nil {
|
|
| 1113 |
+ return nil, err |
|
| 1114 |
+ } |
|
| 1115 |
+ |
|
| 1116 |
+ pl := make([]libcontainerdtypes.Summary, len(p)) |
|
| 1117 |
+ for i := range p {
|
|
| 1118 |
+ pl[i] = libcontainerdtypes.Summary(p[i]) |
|
| 1119 |
+ } |
|
| 1120 |
+ return pl, nil |
|
| 1121 |
+} |
|
| 1122 |
+ |
|
| 1123 |
+func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
|
|
| 1124 |
+ ec := -1 |
|
| 1125 |
+ ctr := c.getContainer(containerID) |
|
| 1126 |
+ if ctr == nil {
|
|
| 1127 |
+ return uint32(ec), time.Now(), errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 1128 |
+ } |
|
| 1129 |
+ |
|
| 1130 |
+ select {
|
|
| 1131 |
+ case <-ctx.Done(): |
|
| 1132 |
+ return uint32(ec), time.Now(), errors.WithStack(ctx.Err()) |
|
| 1133 |
+ case <-ctr.waitCh: |
|
| 1134 |
+ default: |
|
| 1135 |
+ return uint32(ec), time.Now(), errors.New("container is not stopped")
|
|
| 1136 |
+ } |
|
| 1137 |
+ |
|
| 1138 |
+ ctr.Lock() |
|
| 1139 |
+ defer ctr.Unlock() |
|
| 1140 |
+ return ctr.exitCode, ctr.exitedAt, nil |
|
| 1141 |
+} |
|
| 1142 |
+ |
|
| 1143 |
+func (c *client) Delete(_ context.Context, containerID string) error {
|
|
| 1144 |
+ c.Lock() |
|
| 1145 |
+ defer c.Unlock() |
|
| 1146 |
+ ctr := c.containers[containerID] |
|
| 1147 |
+ if ctr == nil {
|
|
| 1148 |
+ return errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 1149 |
+ } |
|
| 1150 |
+ |
|
| 1151 |
+ ctr.Lock() |
|
| 1152 |
+ defer ctr.Unlock() |
|
| 1153 |
+ |
|
| 1154 |
+ switch ctr.status {
|
|
| 1155 |
+ case libcontainerdtypes.StatusCreated: |
|
| 1156 |
+ if err := c.shutdownContainer(ctr); err != nil {
|
|
| 1157 |
+ return err |
|
| 1158 |
+ } |
|
| 1159 |
+ fallthrough |
|
| 1160 |
+ case libcontainerdtypes.StatusStopped: |
|
| 1161 |
+ delete(c.containers, containerID) |
|
| 1162 |
+ return nil |
|
| 1163 |
+ } |
|
| 1164 |
+ |
|
| 1165 |
+ return errors.WithStack(errdefs.InvalidParameter(errors.New("container is not stopped")))
|
|
| 1166 |
+} |
|
| 1167 |
+ |
|
| 1168 |
+func (c *client) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
|
|
| 1169 |
+ c.Lock() |
|
| 1170 |
+ defer c.Unlock() |
|
| 1171 |
+ ctr := c.containers[containerID] |
|
| 1172 |
+ if ctr == nil {
|
|
| 1173 |
+ return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 1174 |
+ } |
|
| 1175 |
+ |
|
| 1176 |
+ ctr.Lock() |
|
| 1177 |
+ defer ctr.Unlock() |
|
| 1178 |
+ return ctr.status, nil |
|
| 1179 |
+} |
|
| 1180 |
+ |
|
| 1181 |
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
|
|
| 1182 |
+ // Updating resource isn't supported on Windows |
|
| 1183 |
+ // but we should return nil for enabling updating container |
|
| 1184 |
+ return nil |
|
| 1185 |
+} |
|
| 1186 |
+ |
|
| 1187 |
+func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
|
|
| 1188 |
+ return errors.New("Windows: Containers do not support checkpoints")
|
|
| 1189 |
+} |
|
| 1190 |
+ |
|
| 1191 |
+func (c *client) getContainer(id string) *container {
|
|
| 1192 |
+ c.Lock() |
|
| 1193 |
+ ctr := c.containers[id] |
|
| 1194 |
+ c.Unlock() |
|
| 1195 |
+ |
|
| 1196 |
+ return ctr |
|
| 1197 |
+} |
|
| 1198 |
+ |
|
| 1199 |
+func (c *client) getProcess(containerID, processID string) (*container, *process, error) {
|
|
| 1200 |
+ ctr := c.getContainer(containerID) |
|
| 1201 |
+ switch {
|
|
| 1202 |
+ case ctr == nil: |
|
| 1203 |
+ return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 1204 |
+ case ctr.init == nil: |
|
| 1205 |
+ return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("container is not running")))
|
|
| 1206 |
+ case processID == libcontainerdtypes.InitProcessName: |
|
| 1207 |
+ return ctr, ctr.init, nil |
|
| 1208 |
+ default: |
|
| 1209 |
+ ctr.Lock() |
|
| 1210 |
+ defer ctr.Unlock() |
|
| 1211 |
+ if ctr.execs == nil {
|
|
| 1212 |
+ return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no execs")))
|
|
| 1213 |
+ } |
|
| 1214 |
+ } |
|
| 1215 |
+ |
|
| 1216 |
+ p := ctr.execs[processID] |
|
| 1217 |
+ if p == nil {
|
|
| 1218 |
+ return nil, nil, errors.WithStack(errdefs.NotFound(errors.New("no such exec")))
|
|
| 1219 |
+ } |
|
| 1220 |
+ |
|
| 1221 |
+ return ctr, p, nil |
|
| 1222 |
+} |
|
| 1223 |
+ |
|
| 1224 |
+// ctr mutex must be held when calling this function. |
|
| 1225 |
+func (c *client) shutdownContainer(ctr *container) error {
|
|
| 1226 |
+ var err error |
|
| 1227 |
+ const waitTimeout = time.Minute * 5 |
|
| 1228 |
+ |
|
| 1229 |
+ if !ctr.terminateInvoked {
|
|
| 1230 |
+ err = ctr.hcsContainer.Shutdown() |
|
| 1231 |
+ } |
|
| 1232 |
+ |
|
| 1233 |
+ if hcsshim.IsPending(err) || ctr.terminateInvoked {
|
|
| 1234 |
+ err = ctr.hcsContainer.WaitTimeout(waitTimeout) |
|
| 1235 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1236 |
+ err = nil |
|
| 1237 |
+ } |
|
| 1238 |
+ |
|
| 1239 |
+ if err != nil {
|
|
| 1240 |
+ c.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1241 |
+ Debug("failed to shutdown container, terminating it")
|
|
| 1242 |
+ terminateErr := c.terminateContainer(ctr) |
|
| 1243 |
+ if terminateErr != nil {
|
|
| 1244 |
+ c.logger.WithError(terminateErr).WithField("container", ctr.id).
|
|
| 1245 |
+ Error("failed to shutdown container, and subsequent terminate also failed")
|
|
| 1246 |
+ return fmt.Errorf("%s: subsequent terminate failed %s", err, terminateErr)
|
|
| 1247 |
+ } |
|
| 1248 |
+ return err |
|
| 1249 |
+ } |
|
| 1250 |
+ |
|
| 1251 |
+ return nil |
|
| 1252 |
+} |
|
| 1253 |
+ |
|
| 1254 |
+// ctr mutex must be held when calling this function. |
|
| 1255 |
+func (c *client) terminateContainer(ctr *container) error {
|
|
| 1256 |
+ const terminateTimeout = time.Minute * 5 |
|
| 1257 |
+ ctr.terminateInvoked = true |
|
| 1258 |
+ err := ctr.hcsContainer.Terminate() |
|
| 1259 |
+ |
|
| 1260 |
+ if hcsshim.IsPending(err) {
|
|
| 1261 |
+ err = ctr.hcsContainer.WaitTimeout(terminateTimeout) |
|
| 1262 |
+ } else if hcsshim.IsAlreadyStopped(err) {
|
|
| 1263 |
+ err = nil |
|
| 1264 |
+ } |
|
| 1265 |
+ |
|
| 1266 |
+ if err != nil {
|
|
| 1267 |
+ c.logger.WithError(err).WithField("container", ctr.id).
|
|
| 1268 |
+ Debug("failed to terminate container")
|
|
| 1269 |
+ return err |
|
| 1270 |
+ } |
|
| 1271 |
+ |
|
| 1272 |
+ return nil |
|
| 1273 |
+} |
|
| 1274 |
+ |
|
| 1275 |
+func (c *client) reapProcess(ctr *container, p *process) int {
|
|
| 1276 |
+ logger := c.logger.WithFields(logrus.Fields{
|
|
| 1277 |
+ "container": ctr.id, |
|
| 1278 |
+ "process": p.id, |
|
| 1279 |
+ }) |
|
| 1280 |
+ |
|
| 1281 |
+ var eventErr error |
|
| 1282 |
+ |
|
| 1283 |
+ // Block indefinitely for the process to exit. |
|
| 1284 |
+ if err := p.hcsProcess.Wait(); err != nil {
|
|
| 1285 |
+ if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1286 |
+ logger.WithError(err).Warnf("Wait() failed (container may have been killed)")
|
|
| 1287 |
+ } |
|
| 1288 |
+ // Fall through here, do not return. This ensures we attempt to |
|
| 1289 |
+ // continue the shutdown in HCS and tell the docker engine that the |
|
| 1290 |
+ // process/container has exited to avoid a container being dropped on |
|
| 1291 |
+ // the floor. |
|
| 1292 |
+ } |
|
| 1293 |
+ exitedAt := time.Now() |
|
| 1294 |
+ |
|
| 1295 |
+ exitCode, err := p.hcsProcess.ExitCode() |
|
| 1296 |
+ if err != nil {
|
|
| 1297 |
+ if herr, ok := err.(*hcsshim.ProcessError); ok && herr.Err != windows.ERROR_BROKEN_PIPE {
|
|
| 1298 |
+ logger.WithError(err).Warnf("unable to get exit code for process")
|
|
| 1299 |
+ } |
|
| 1300 |
+ // Since we got an error retrieving the exit code, make sure that the |
|
| 1301 |
+ // code we return doesn't incorrectly indicate success. |
|
| 1302 |
+ exitCode = -1 |
|
| 1303 |
+ |
|
| 1304 |
+ // Fall through here, do not return. This ensures we attempt to |
|
| 1305 |
+ // continue the shutdown in HCS and tell the docker engine that the |
|
| 1306 |
+ // process/container has exited to avoid a container being dropped on |
|
| 1307 |
+ // the floor. |
|
| 1308 |
+ } |
|
| 1309 |
+ |
|
| 1310 |
+ if err := p.hcsProcess.Close(); err != nil {
|
|
| 1311 |
+ logger.WithError(err).Warnf("failed to cleanup hcs process resources")
|
|
| 1312 |
+ exitCode = -1 |
|
| 1313 |
+ eventErr = fmt.Errorf("hcsProcess.Close() failed %s", err)
|
|
| 1314 |
+ } |
|
| 1315 |
+ |
|
| 1316 |
+ if p.id == libcontainerdtypes.InitProcessName {
|
|
| 1317 |
+ // Update container status |
|
| 1318 |
+ ctr.Lock() |
|
| 1319 |
+ ctr.status = libcontainerdtypes.StatusStopped |
|
| 1320 |
+ ctr.exitedAt = exitedAt |
|
| 1321 |
+ ctr.exitCode = uint32(exitCode) |
|
| 1322 |
+ close(ctr.waitCh) |
|
| 1323 |
+ |
|
| 1324 |
+ if err := c.shutdownContainer(ctr); err != nil {
|
|
| 1325 |
+ exitCode = -1 |
|
| 1326 |
+ logger.WithError(err).Warn("failed to shutdown container")
|
|
| 1327 |
+ thisErr := fmt.Errorf("failed to shutdown container: %s", err)
|
|
| 1328 |
+ if eventErr != nil {
|
|
| 1329 |
+ eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
|
|
| 1330 |
+ } else {
|
|
| 1331 |
+ eventErr = thisErr |
|
| 1332 |
+ } |
|
| 1333 |
+ } else {
|
|
| 1334 |
+ logger.Debug("completed container shutdown")
|
|
| 1335 |
+ } |
|
| 1336 |
+ ctr.Unlock() |
|
| 1337 |
+ |
|
| 1338 |
+ if err := ctr.hcsContainer.Close(); err != nil {
|
|
| 1339 |
+ exitCode = -1 |
|
| 1340 |
+ logger.WithError(err).Error("failed to clean hcs container resources")
|
|
| 1341 |
+ thisErr := fmt.Errorf("failed to terminate container: %s", err)
|
|
| 1342 |
+ if eventErr != nil {
|
|
| 1343 |
+ eventErr = fmt.Errorf("%s: %s", eventErr, thisErr)
|
|
| 1344 |
+ } else {
|
|
| 1345 |
+ eventErr = thisErr |
|
| 1346 |
+ } |
|
| 1347 |
+ } |
|
| 1348 |
+ } |
|
| 1349 |
+ |
|
| 1350 |
+ c.eventQ.Append(ctr.id, func() {
|
|
| 1351 |
+ ei := libcontainerdtypes.EventInfo{
|
|
| 1352 |
+ ContainerID: ctr.id, |
|
| 1353 |
+ ProcessID: p.id, |
|
| 1354 |
+ Pid: uint32(p.pid), |
|
| 1355 |
+ ExitCode: uint32(exitCode), |
|
| 1356 |
+ ExitedAt: exitedAt, |
|
| 1357 |
+ Error: eventErr, |
|
| 1358 |
+ } |
|
| 1359 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 1360 |
+ "container": ctr.id, |
|
| 1361 |
+ "event": libcontainerdtypes.EventExit, |
|
| 1362 |
+ "event-info": ei, |
|
| 1363 |
+ }).Info("sending event")
|
|
| 1364 |
+ err := c.backend.ProcessEvent(ctr.id, libcontainerdtypes.EventExit, ei) |
|
| 1365 |
+ if err != nil {
|
|
| 1366 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 1367 |
+ "container": ctr.id, |
|
| 1368 |
+ "event": libcontainerdtypes.EventExit, |
|
| 1369 |
+ "event-info": ei, |
|
| 1370 |
+ }).Error("failed to process event")
|
|
| 1371 |
+ } |
|
| 1372 |
+ if p.id != libcontainerdtypes.InitProcessName {
|
|
| 1373 |
+ ctr.Lock() |
|
| 1374 |
+ delete(ctr.execs, p.id) |
|
| 1375 |
+ ctr.Unlock() |
|
| 1376 |
+ } |
|
| 1377 |
+ }) |
|
| 1378 |
+ |
|
| 1379 |
+ return exitCode |
|
| 1380 |
+} |
| 0 | 1381 |
deleted file mode 100644 |
| ... | ... |
@@ -1,44 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 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) (n int, err error) {
|
|
| 17 |
- n, err = r.ReadCloser.Read(b) |
|
| 18 |
- if err != nil {
|
|
| 19 |
- r.Once.Do(func() { r.ReadCloser.Close() })
|
|
| 20 |
- } |
|
| 21 |
- return |
|
| 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 |
- |
|
| 42 |
-func (p *process) Cleanup() error {
|
|
| 43 |
- return nil |
|
| 44 |
-} |
| 45 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,35 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import "sync" |
|
| 4 |
- |
|
| 5 |
-type queue struct {
|
|
| 6 |
- sync.Mutex |
|
| 7 |
- fns map[string]chan struct{}
|
|
| 8 |
-} |
|
| 9 |
- |
|
| 10 |
-func (q *queue) append(id string, f func()) {
|
|
| 11 |
- q.Lock() |
|
| 12 |
- defer q.Unlock() |
|
| 13 |
- |
|
| 14 |
- if q.fns == nil {
|
|
| 15 |
- q.fns = make(map[string]chan struct{})
|
|
| 16 |
- } |
|
| 17 |
- |
|
| 18 |
- done := make(chan struct{})
|
|
| 19 |
- |
|
| 20 |
- fn, ok := q.fns[id] |
|
| 21 |
- q.fns[id] = done |
|
| 22 |
- go func() {
|
|
| 23 |
- if ok {
|
|
| 24 |
- <-fn |
|
| 25 |
- } |
|
| 26 |
- f() |
|
| 27 |
- close(done) |
|
| 28 |
- |
|
| 29 |
- q.Lock() |
|
| 30 |
- if q.fns[id] == done {
|
|
| 31 |
- delete(q.fns, id) |
|
| 32 |
- } |
|
| 33 |
- q.Unlock() |
|
| 34 |
- }() |
|
| 35 |
-} |
| 36 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package queue // import "github.com/docker/docker/libcontainerd/queue" |
|
| 1 |
+ |
|
| 2 |
+import "sync" |
|
| 3 |
+ |
|
| 4 |
+// Queue is the structure used for holding functions in a queue. |
|
| 5 |
+type Queue struct {
|
|
| 6 |
+ sync.Mutex |
|
| 7 |
+ fns map[string]chan struct{}
|
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+// Append adds an item to a queue. |
|
| 11 |
+func (q *Queue) Append(id string, f func()) {
|
|
| 12 |
+ q.Lock() |
|
| 13 |
+ defer q.Unlock() |
|
| 14 |
+ |
|
| 15 |
+ if q.fns == nil {
|
|
| 16 |
+ q.fns = make(map[string]chan struct{})
|
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ done := make(chan struct{})
|
|
| 20 |
+ |
|
| 21 |
+ fn, ok := q.fns[id] |
|
| 22 |
+ q.fns[id] = done |
|
| 23 |
+ go func() {
|
|
| 24 |
+ if ok {
|
|
| 25 |
+ <-fn |
|
| 26 |
+ } |
|
| 27 |
+ f() |
|
| 28 |
+ close(done) |
|
| 29 |
+ |
|
| 30 |
+ q.Lock() |
|
| 31 |
+ if q.fns[id] == done {
|
|
| 32 |
+ delete(q.fns, id) |
|
| 33 |
+ } |
|
| 34 |
+ q.Unlock() |
|
| 35 |
+ }() |
|
| 36 |
+} |
| 0 | 37 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,31 @@ |
| 0 |
+package queue // import "github.com/docker/docker/libcontainerd/queue" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ "time" |
|
| 5 |
+ |
|
| 6 |
+ "gotest.tools/assert" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestSerialization(t *testing.T) {
|
|
| 10 |
+ var ( |
|
| 11 |
+ q Queue |
|
| 12 |
+ serialization = 1 |
|
| 13 |
+ ) |
|
| 14 |
+ |
|
| 15 |
+ q.Append("aaa", func() {
|
|
| 16 |
+ //simulate a long time task |
|
| 17 |
+ time.Sleep(10 * time.Millisecond) |
|
| 18 |
+ assert.Equal(t, serialization, 1) |
|
| 19 |
+ serialization = 2 |
|
| 20 |
+ }) |
|
| 21 |
+ q.Append("aaa", func() {
|
|
| 22 |
+ assert.Equal(t, serialization, 2) |
|
| 23 |
+ serialization = 3 |
|
| 24 |
+ }) |
|
| 25 |
+ q.Append("aaa", func() {
|
|
| 26 |
+ assert.Equal(t, serialization, 3) |
|
| 27 |
+ serialization = 4 |
|
| 28 |
+ }) |
|
| 29 |
+ time.Sleep(20 * time.Millisecond) |
|
| 30 |
+} |
| 0 | 31 |
deleted file mode 100644 |
| ... | ... |
@@ -1,31 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- "time" |
|
| 6 |
- |
|
| 7 |
- "gotest.tools/assert" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func TestSerialization(t *testing.T) {
|
|
| 11 |
- var ( |
|
| 12 |
- q queue |
|
| 13 |
- serialization = 1 |
|
| 14 |
- ) |
|
| 15 |
- |
|
| 16 |
- q.append("aaa", func() {
|
|
| 17 |
- //simulate a long time task |
|
| 18 |
- time.Sleep(10 * time.Millisecond) |
|
| 19 |
- assert.Equal(t, serialization, 1) |
|
| 20 |
- serialization = 2 |
|
| 21 |
- }) |
|
| 22 |
- q.append("aaa", func() {
|
|
| 23 |
- assert.Equal(t, serialization, 2) |
|
| 24 |
- serialization = 3 |
|
| 25 |
- }) |
|
| 26 |
- q.append("aaa", func() {
|
|
| 27 |
- assert.Equal(t, serialization, 3) |
|
| 28 |
- serialization = 4 |
|
| 29 |
- }) |
|
| 30 |
- time.Sleep(20 * time.Millisecond) |
|
| 31 |
-} |
| 32 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,921 @@ |
| 0 |
+package remote // import "github.com/docker/docker/libcontainerd/remote" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "io" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path/filepath" |
|
| 8 |
+ "reflect" |
|
| 9 |
+ "runtime" |
|
| 10 |
+ "strings" |
|
| 11 |
+ "sync" |
|
| 12 |
+ "syscall" |
|
| 13 |
+ "time" |
|
| 14 |
+ |
|
| 15 |
+ "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" |
|
| 16 |
+ "github.com/containerd/containerd" |
|
| 17 |
+ apievents "github.com/containerd/containerd/api/events" |
|
| 18 |
+ "github.com/containerd/containerd/api/types" |
|
| 19 |
+ "github.com/containerd/containerd/archive" |
|
| 20 |
+ "github.com/containerd/containerd/cio" |
|
| 21 |
+ "github.com/containerd/containerd/content" |
|
| 22 |
+ containerderrors "github.com/containerd/containerd/errdefs" |
|
| 23 |
+ "github.com/containerd/containerd/events" |
|
| 24 |
+ "github.com/containerd/containerd/images" |
|
| 25 |
+ "github.com/containerd/containerd/runtime/linux/runctypes" |
|
| 26 |
+ "github.com/containerd/typeurl" |
|
| 27 |
+ "github.com/docker/docker/errdefs" |
|
| 28 |
+ "github.com/docker/docker/libcontainerd/queue" |
|
| 29 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 30 |
+ |
|
| 31 |
+ "github.com/docker/docker/pkg/ioutils" |
|
| 32 |
+ v1 "github.com/opencontainers/image-spec/specs-go/v1" |
|
| 33 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 34 |
+ "github.com/pkg/errors" |
|
| 35 |
+ "github.com/sirupsen/logrus" |
|
| 36 |
+ "google.golang.org/grpc/codes" |
|
| 37 |
+ "google.golang.org/grpc/status" |
|
| 38 |
+) |
|
| 39 |
+ |
|
| 40 |
+type container struct {
|
|
| 41 |
+ mu sync.Mutex |
|
| 42 |
+ |
|
| 43 |
+ bundleDir string |
|
| 44 |
+ ctr containerd.Container |
|
| 45 |
+ task containerd.Task |
|
| 46 |
+ execs map[string]containerd.Process |
|
| 47 |
+ oomKilled bool |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func (c *container) setTask(t containerd.Task) {
|
|
| 51 |
+ c.mu.Lock() |
|
| 52 |
+ c.task = t |
|
| 53 |
+ c.mu.Unlock() |
|
| 54 |
+} |
|
| 55 |
+ |
|
| 56 |
+func (c *container) getTask() containerd.Task {
|
|
| 57 |
+ c.mu.Lock() |
|
| 58 |
+ t := c.task |
|
| 59 |
+ c.mu.Unlock() |
|
| 60 |
+ return t |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func (c *container) addProcess(id string, p containerd.Process) {
|
|
| 64 |
+ c.mu.Lock() |
|
| 65 |
+ if c.execs == nil {
|
|
| 66 |
+ c.execs = make(map[string]containerd.Process) |
|
| 67 |
+ } |
|
| 68 |
+ c.execs[id] = p |
|
| 69 |
+ c.mu.Unlock() |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func (c *container) deleteProcess(id string) {
|
|
| 73 |
+ c.mu.Lock() |
|
| 74 |
+ delete(c.execs, id) |
|
| 75 |
+ c.mu.Unlock() |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+func (c *container) getProcess(id string) containerd.Process {
|
|
| 79 |
+ c.mu.Lock() |
|
| 80 |
+ p := c.execs[id] |
|
| 81 |
+ c.mu.Unlock() |
|
| 82 |
+ return p |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+func (c *container) setOOMKilled(killed bool) {
|
|
| 86 |
+ c.mu.Lock() |
|
| 87 |
+ c.oomKilled = killed |
|
| 88 |
+ c.mu.Unlock() |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+func (c *container) getOOMKilled() bool {
|
|
| 92 |
+ c.mu.Lock() |
|
| 93 |
+ killed := c.oomKilled |
|
| 94 |
+ c.mu.Unlock() |
|
| 95 |
+ return killed |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+type client struct {
|
|
| 99 |
+ sync.RWMutex // protects containers map |
|
| 100 |
+ |
|
| 101 |
+ client *containerd.Client |
|
| 102 |
+ stateDir string |
|
| 103 |
+ logger *logrus.Entry |
|
| 104 |
+ ns string |
|
| 105 |
+ |
|
| 106 |
+ backend libcontainerdtypes.Backend |
|
| 107 |
+ eventQ queue.Queue |
|
| 108 |
+ containers map[string]*container |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+// NewClient creates a new libcontainerd client from a containerd client |
|
| 112 |
+func NewClient(ctx context.Context, cli *containerd.Client, stateDir, ns string, b libcontainerdtypes.Backend) (libcontainerdtypes.Client, error) {
|
|
| 113 |
+ c := &client{
|
|
| 114 |
+ client: cli, |
|
| 115 |
+ stateDir: stateDir, |
|
| 116 |
+ logger: logrus.WithField("module", "libcontainerd").WithField("namespace", ns),
|
|
| 117 |
+ ns: ns, |
|
| 118 |
+ backend: b, |
|
| 119 |
+ containers: make(map[string]*container), |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ go c.processEventStream(ctx, ns) |
|
| 123 |
+ |
|
| 124 |
+ return c, nil |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func (c *client) Version(ctx context.Context) (containerd.Version, error) {
|
|
| 128 |
+ return c.client.Version(ctx) |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+// Restore loads the containerd container. |
|
| 132 |
+// It should not be called concurrently with any other operation for the given ID. |
|
| 133 |
+func (c *client) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
|
|
| 134 |
+ c.Lock() |
|
| 135 |
+ _, ok := c.containers[id] |
|
| 136 |
+ if ok {
|
|
| 137 |
+ c.Unlock() |
|
| 138 |
+ return false, 0, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
|
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ cntr := &container{}
|
|
| 142 |
+ c.containers[id] = cntr |
|
| 143 |
+ cntr.mu.Lock() |
|
| 144 |
+ defer cntr.mu.Unlock() |
|
| 145 |
+ |
|
| 146 |
+ c.Unlock() |
|
| 147 |
+ |
|
| 148 |
+ defer func() {
|
|
| 149 |
+ if err != nil {
|
|
| 150 |
+ c.Lock() |
|
| 151 |
+ delete(c.containers, id) |
|
| 152 |
+ c.Unlock() |
|
| 153 |
+ } |
|
| 154 |
+ }() |
|
| 155 |
+ |
|
| 156 |
+ var dio *cio.DirectIO |
|
| 157 |
+ defer func() {
|
|
| 158 |
+ if err != nil && dio != nil {
|
|
| 159 |
+ dio.Cancel() |
|
| 160 |
+ dio.Close() |
|
| 161 |
+ } |
|
| 162 |
+ err = wrapError(err) |
|
| 163 |
+ }() |
|
| 164 |
+ |
|
| 165 |
+ ctr, err := c.client.LoadContainer(ctx, id) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ return false, -1, errors.WithStack(wrapError(err)) |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ attachIO := func(fifos *cio.FIFOSet) (cio.IO, error) {
|
|
| 171 |
+ // dio must be assigned to the previously defined dio for the defer above |
|
| 172 |
+ // to handle cleanup |
|
| 173 |
+ |
|
| 174 |
+ dio, err = c.newDirectIO(ctx, fifos) |
|
| 175 |
+ if err != nil {
|
|
| 176 |
+ return nil, err |
|
| 177 |
+ } |
|
| 178 |
+ return attachStdio(dio) |
|
| 179 |
+ } |
|
| 180 |
+ t, err := ctr.Task(ctx, attachIO) |
|
| 181 |
+ if err != nil && !containerderrors.IsNotFound(err) {
|
|
| 182 |
+ return false, -1, errors.Wrap(wrapError(err), "error getting containerd task for container") |
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+ if t != nil {
|
|
| 186 |
+ s, err := t.Status(ctx) |
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ return false, -1, errors.Wrap(wrapError(err), "error getting task status") |
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 191 |
+ alive = s.Status != containerd.Stopped |
|
| 192 |
+ pid = int(t.Pid()) |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ cntr.bundleDir = filepath.Join(c.stateDir, id) |
|
| 196 |
+ cntr.ctr = ctr |
|
| 197 |
+ cntr.task = t |
|
| 198 |
+ // TODO(mlaventure): load execs |
|
| 199 |
+ |
|
| 200 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 201 |
+ "container": id, |
|
| 202 |
+ "alive": alive, |
|
| 203 |
+ "pid": pid, |
|
| 204 |
+ }).Debug("restored container")
|
|
| 205 |
+ |
|
| 206 |
+ return alive, pid, nil |
|
| 207 |
+} |
|
| 208 |
+ |
|
| 209 |
+func (c *client) Create(ctx context.Context, id string, ociSpec *specs.Spec, runtimeOptions interface{}) error {
|
|
| 210 |
+ if ctr := c.getContainer(id); ctr != nil {
|
|
| 211 |
+ return errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
|
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ bdir, err := prepareBundleDir(filepath.Join(c.stateDir, id), ociSpec) |
|
| 215 |
+ if err != nil {
|
|
| 216 |
+ return errdefs.System(errors.Wrap(err, "prepare bundle dir failed")) |
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 219 |
+ c.logger.WithField("bundle", bdir).WithField("root", ociSpec.Root.Path).Debug("bundle dir created")
|
|
| 220 |
+ |
|
| 221 |
+ cdCtr, err := c.client.NewContainer(ctx, id, |
|
| 222 |
+ containerd.WithSpec(ociSpec), |
|
| 223 |
+ containerd.WithRuntime(runtimeName, runtimeOptions)) |
|
| 224 |
+ if err != nil {
|
|
| 225 |
+ return wrapError(err) |
|
| 226 |
+ } |
|
| 227 |
+ |
|
| 228 |
+ c.Lock() |
|
| 229 |
+ c.containers[id] = &container{
|
|
| 230 |
+ bundleDir: bdir, |
|
| 231 |
+ ctr: cdCtr, |
|
| 232 |
+ } |
|
| 233 |
+ c.Unlock() |
|
| 234 |
+ |
|
| 235 |
+ return nil |
|
| 236 |
+} |
|
| 237 |
+ |
|
| 238 |
+// Start create and start a task for the specified containerd id |
|
| 239 |
+func (c *client) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
|
|
| 240 |
+ ctr := c.getContainer(id) |
|
| 241 |
+ if ctr == nil {
|
|
| 242 |
+ return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 243 |
+ } |
|
| 244 |
+ if t := ctr.getTask(); t != nil {
|
|
| 245 |
+ return -1, errors.WithStack(errdefs.Conflict(errors.New("container already started")))
|
|
| 246 |
+ } |
|
| 247 |
+ |
|
| 248 |
+ var ( |
|
| 249 |
+ cp *types.Descriptor |
|
| 250 |
+ t containerd.Task |
|
| 251 |
+ rio cio.IO |
|
| 252 |
+ err error |
|
| 253 |
+ stdinCloseSync = make(chan struct{})
|
|
| 254 |
+ ) |
|
| 255 |
+ |
|
| 256 |
+ if checkpointDir != "" {
|
|
| 257 |
+ // write checkpoint to the content store |
|
| 258 |
+ tar := archive.Diff(ctx, "", checkpointDir) |
|
| 259 |
+ cp, err = c.writeContent(ctx, images.MediaTypeContainerd1Checkpoint, checkpointDir, tar) |
|
| 260 |
+ // remove the checkpoint when we're done |
|
| 261 |
+ defer func() {
|
|
| 262 |
+ if cp != nil {
|
|
| 263 |
+ err := c.client.ContentStore().Delete(context.Background(), cp.Digest) |
|
| 264 |
+ if err != nil {
|
|
| 265 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 266 |
+ "ref": checkpointDir, |
|
| 267 |
+ "digest": cp.Digest, |
|
| 268 |
+ }).Warnf("failed to delete temporary checkpoint entry")
|
|
| 269 |
+ } |
|
| 270 |
+ } |
|
| 271 |
+ }() |
|
| 272 |
+ if err := tar.Close(); err != nil {
|
|
| 273 |
+ return -1, errors.Wrap(err, "failed to close checkpoint tar stream") |
|
| 274 |
+ } |
|
| 275 |
+ if err != nil {
|
|
| 276 |
+ return -1, errors.Wrapf(err, "failed to upload checkpoint to containerd") |
|
| 277 |
+ } |
|
| 278 |
+ } |
|
| 279 |
+ |
|
| 280 |
+ spec, err := ctr.ctr.Spec(ctx) |
|
| 281 |
+ if err != nil {
|
|
| 282 |
+ return -1, errors.Wrap(err, "failed to retrieve spec") |
|
| 283 |
+ } |
|
| 284 |
+ uid, gid := getSpecUser(spec) |
|
| 285 |
+ t, err = ctr.ctr.NewTask(ctx, |
|
| 286 |
+ func(id string) (cio.IO, error) {
|
|
| 287 |
+ fifos := newFIFOSet(ctr.bundleDir, libcontainerdtypes.InitProcessName, withStdin, spec.Process.Terminal) |
|
| 288 |
+ |
|
| 289 |
+ rio, err = c.createIO(fifos, id, libcontainerdtypes.InitProcessName, stdinCloseSync, attachStdio) |
|
| 290 |
+ return rio, err |
|
| 291 |
+ }, |
|
| 292 |
+ func(_ context.Context, _ *containerd.Client, info *containerd.TaskInfo) error {
|
|
| 293 |
+ info.Checkpoint = cp |
|
| 294 |
+ if runtime.GOOS != "windows" {
|
|
| 295 |
+ info.Options = &runctypes.CreateOptions{
|
|
| 296 |
+ IoUid: uint32(uid), |
|
| 297 |
+ IoGid: uint32(gid), |
|
| 298 |
+ NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
|
|
| 299 |
+ } |
|
| 300 |
+ } else {
|
|
| 301 |
+ // Make sure we set the runhcs options to debug if we are at debug level. |
|
| 302 |
+ if c.logger.Level == logrus.DebugLevel {
|
|
| 303 |
+ info.Options = &options.Options{Debug: true}
|
|
| 304 |
+ } |
|
| 305 |
+ } |
|
| 306 |
+ return nil |
|
| 307 |
+ }) |
|
| 308 |
+ if err != nil {
|
|
| 309 |
+ close(stdinCloseSync) |
|
| 310 |
+ if rio != nil {
|
|
| 311 |
+ rio.Cancel() |
|
| 312 |
+ rio.Close() |
|
| 313 |
+ } |
|
| 314 |
+ return -1, wrapError(err) |
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ ctr.setTask(t) |
|
| 318 |
+ |
|
| 319 |
+ // Signal c.createIO that it can call CloseIO |
|
| 320 |
+ close(stdinCloseSync) |
|
| 321 |
+ |
|
| 322 |
+ if err := t.Start(ctx); err != nil {
|
|
| 323 |
+ if _, err := t.Delete(ctx); err != nil {
|
|
| 324 |
+ c.logger.WithError(err).WithField("container", id).
|
|
| 325 |
+ Error("failed to delete task after fail start")
|
|
| 326 |
+ } |
|
| 327 |
+ ctr.setTask(nil) |
|
| 328 |
+ return -1, wrapError(err) |
|
| 329 |
+ } |
|
| 330 |
+ |
|
| 331 |
+ return int(t.Pid()), nil |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+// Exec creates exec process. |
|
| 335 |
+// |
|
| 336 |
+// The containerd client calls Exec to register the exec config in the shim side. |
|
| 337 |
+// When the client calls Start, the shim will create stdin fifo if needs. But |
|
| 338 |
+// for the container main process, the stdin fifo will be created in Create not |
|
| 339 |
+// the Start call. stdinCloseSync channel should be closed after Start exec |
|
| 340 |
+// process. |
|
| 341 |
+func (c *client) Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (int, error) {
|
|
| 342 |
+ ctr := c.getContainer(containerID) |
|
| 343 |
+ if ctr == nil {
|
|
| 344 |
+ return -1, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 345 |
+ } |
|
| 346 |
+ t := ctr.getTask() |
|
| 347 |
+ if t == nil {
|
|
| 348 |
+ return -1, errors.WithStack(errdefs.InvalidParameter(errors.New("container is not running")))
|
|
| 349 |
+ } |
|
| 350 |
+ |
|
| 351 |
+ if p := ctr.getProcess(processID); p != nil {
|
|
| 352 |
+ return -1, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
|
|
| 353 |
+ } |
|
| 354 |
+ |
|
| 355 |
+ var ( |
|
| 356 |
+ p containerd.Process |
|
| 357 |
+ rio cio.IO |
|
| 358 |
+ err error |
|
| 359 |
+ stdinCloseSync = make(chan struct{})
|
|
| 360 |
+ ) |
|
| 361 |
+ |
|
| 362 |
+ fifos := newFIFOSet(ctr.bundleDir, processID, withStdin, spec.Terminal) |
|
| 363 |
+ |
|
| 364 |
+ defer func() {
|
|
| 365 |
+ if err != nil {
|
|
| 366 |
+ if rio != nil {
|
|
| 367 |
+ rio.Cancel() |
|
| 368 |
+ rio.Close() |
|
| 369 |
+ } |
|
| 370 |
+ } |
|
| 371 |
+ }() |
|
| 372 |
+ |
|
| 373 |
+ p, err = t.Exec(ctx, processID, spec, func(id string) (cio.IO, error) {
|
|
| 374 |
+ rio, err = c.createIO(fifos, containerID, processID, stdinCloseSync, attachStdio) |
|
| 375 |
+ return rio, err |
|
| 376 |
+ }) |
|
| 377 |
+ if err != nil {
|
|
| 378 |
+ close(stdinCloseSync) |
|
| 379 |
+ return -1, wrapError(err) |
|
| 380 |
+ } |
|
| 381 |
+ |
|
| 382 |
+ ctr.addProcess(processID, p) |
|
| 383 |
+ |
|
| 384 |
+ // Signal c.createIO that it can call CloseIO |
|
| 385 |
+ // |
|
| 386 |
+ // the stdin of exec process will be created after p.Start in containerd |
|
| 387 |
+ defer close(stdinCloseSync) |
|
| 388 |
+ |
|
| 389 |
+ if err = p.Start(ctx); err != nil {
|
|
| 390 |
+ // use new context for cleanup because old one may be cancelled by user, but leave a timeout to make sure |
|
| 391 |
+ // we are not waiting forever if containerd is unresponsive or to work around fifo cancelling issues in |
|
| 392 |
+ // older containerd-shim |
|
| 393 |
+ ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) |
|
| 394 |
+ defer cancel() |
|
| 395 |
+ p.Delete(ctx) |
|
| 396 |
+ ctr.deleteProcess(processID) |
|
| 397 |
+ return -1, wrapError(err) |
|
| 398 |
+ } |
|
| 399 |
+ |
|
| 400 |
+ return int(p.Pid()), nil |
|
| 401 |
+} |
|
| 402 |
+ |
|
| 403 |
+func (c *client) SignalProcess(ctx context.Context, containerID, processID string, signal int) error {
|
|
| 404 |
+ p, err := c.getProcess(containerID, processID) |
|
| 405 |
+ if err != nil {
|
|
| 406 |
+ return err |
|
| 407 |
+ } |
|
| 408 |
+ return wrapError(p.Kill(ctx, syscall.Signal(signal))) |
|
| 409 |
+} |
|
| 410 |
+ |
|
| 411 |
+func (c *client) ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error {
|
|
| 412 |
+ p, err := c.getProcess(containerID, processID) |
|
| 413 |
+ if err != nil {
|
|
| 414 |
+ return err |
|
| 415 |
+ } |
|
| 416 |
+ |
|
| 417 |
+ return p.Resize(ctx, uint32(width), uint32(height)) |
|
| 418 |
+} |
|
| 419 |
+ |
|
| 420 |
+func (c *client) CloseStdin(ctx context.Context, containerID, processID string) error {
|
|
| 421 |
+ p, err := c.getProcess(containerID, processID) |
|
| 422 |
+ if err != nil {
|
|
| 423 |
+ return err |
|
| 424 |
+ } |
|
| 425 |
+ |
|
| 426 |
+ return p.CloseIO(ctx, containerd.WithStdinCloser) |
|
| 427 |
+} |
|
| 428 |
+ |
|
| 429 |
+func (c *client) Pause(ctx context.Context, containerID string) error {
|
|
| 430 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 431 |
+ if err != nil {
|
|
| 432 |
+ return err |
|
| 433 |
+ } |
|
| 434 |
+ |
|
| 435 |
+ return wrapError(p.(containerd.Task).Pause(ctx)) |
|
| 436 |
+} |
|
| 437 |
+ |
|
| 438 |
+func (c *client) Resume(ctx context.Context, containerID string) error {
|
|
| 439 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 440 |
+ if err != nil {
|
|
| 441 |
+ return err |
|
| 442 |
+ } |
|
| 443 |
+ |
|
| 444 |
+ return p.(containerd.Task).Resume(ctx) |
|
| 445 |
+} |
|
| 446 |
+ |
|
| 447 |
+func (c *client) Stats(ctx context.Context, containerID string) (*libcontainerdtypes.Stats, error) {
|
|
| 448 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 449 |
+ if err != nil {
|
|
| 450 |
+ return nil, err |
|
| 451 |
+ } |
|
| 452 |
+ |
|
| 453 |
+ m, err := p.(containerd.Task).Metrics(ctx) |
|
| 454 |
+ if err != nil {
|
|
| 455 |
+ return nil, err |
|
| 456 |
+ } |
|
| 457 |
+ |
|
| 458 |
+ v, err := typeurl.UnmarshalAny(m.Data) |
|
| 459 |
+ if err != nil {
|
|
| 460 |
+ return nil, err |
|
| 461 |
+ } |
|
| 462 |
+ return libcontainerdtypes.InterfaceToStats(m.Timestamp, v), nil |
|
| 463 |
+} |
|
| 464 |
+ |
|
| 465 |
+func (c *client) ListPids(ctx context.Context, containerID string) ([]uint32, error) {
|
|
| 466 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 467 |
+ if err != nil {
|
|
| 468 |
+ return nil, err |
|
| 469 |
+ } |
|
| 470 |
+ |
|
| 471 |
+ pis, err := p.(containerd.Task).Pids(ctx) |
|
| 472 |
+ if err != nil {
|
|
| 473 |
+ return nil, err |
|
| 474 |
+ } |
|
| 475 |
+ |
|
| 476 |
+ var pids []uint32 |
|
| 477 |
+ for _, i := range pis {
|
|
| 478 |
+ pids = append(pids, i.Pid) |
|
| 479 |
+ } |
|
| 480 |
+ |
|
| 481 |
+ return pids, nil |
|
| 482 |
+} |
|
| 483 |
+ |
|
| 484 |
+func (c *client) Summary(ctx context.Context, containerID string) ([]libcontainerdtypes.Summary, error) {
|
|
| 485 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 486 |
+ if err != nil {
|
|
| 487 |
+ return nil, err |
|
| 488 |
+ } |
|
| 489 |
+ |
|
| 490 |
+ pis, err := p.(containerd.Task).Pids(ctx) |
|
| 491 |
+ if err != nil {
|
|
| 492 |
+ return nil, err |
|
| 493 |
+ } |
|
| 494 |
+ |
|
| 495 |
+ var infos []libcontainerdtypes.Summary |
|
| 496 |
+ for _, pi := range pis {
|
|
| 497 |
+ i, err := typeurl.UnmarshalAny(pi.Info) |
|
| 498 |
+ if err != nil {
|
|
| 499 |
+ return nil, errors.Wrap(err, "unable to decode process details") |
|
| 500 |
+ } |
|
| 501 |
+ s, err := summaryFromInterface(i) |
|
| 502 |
+ if err != nil {
|
|
| 503 |
+ return nil, err |
|
| 504 |
+ } |
|
| 505 |
+ infos = append(infos, *s) |
|
| 506 |
+ } |
|
| 507 |
+ |
|
| 508 |
+ return infos, nil |
|
| 509 |
+} |
|
| 510 |
+ |
|
| 511 |
+func (c *client) DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) {
|
|
| 512 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 513 |
+ if err != nil {
|
|
| 514 |
+ return 255, time.Now(), nil |
|
| 515 |
+ } |
|
| 516 |
+ |
|
| 517 |
+ status, err := p.(containerd.Task).Delete(ctx) |
|
| 518 |
+ if err != nil {
|
|
| 519 |
+ return 255, time.Now(), nil |
|
| 520 |
+ } |
|
| 521 |
+ |
|
| 522 |
+ if ctr := c.getContainer(containerID); ctr != nil {
|
|
| 523 |
+ ctr.setTask(nil) |
|
| 524 |
+ } |
|
| 525 |
+ return status.ExitCode(), status.ExitTime(), nil |
|
| 526 |
+} |
|
| 527 |
+ |
|
| 528 |
+func (c *client) Delete(ctx context.Context, containerID string) error {
|
|
| 529 |
+ ctr := c.getContainer(containerID) |
|
| 530 |
+ if ctr == nil {
|
|
| 531 |
+ return errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 532 |
+ } |
|
| 533 |
+ |
|
| 534 |
+ if err := ctr.ctr.Delete(ctx); err != nil {
|
|
| 535 |
+ return wrapError(err) |
|
| 536 |
+ } |
|
| 537 |
+ |
|
| 538 |
+ if os.Getenv("LIBCONTAINERD_NOCLEAN") != "1" {
|
|
| 539 |
+ if err := os.RemoveAll(ctr.bundleDir); err != nil {
|
|
| 540 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 541 |
+ "container": containerID, |
|
| 542 |
+ "bundle": ctr.bundleDir, |
|
| 543 |
+ }).Error("failed to remove state dir")
|
|
| 544 |
+ } |
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ c.removeContainer(containerID) |
|
| 548 |
+ |
|
| 549 |
+ return nil |
|
| 550 |
+} |
|
| 551 |
+ |
|
| 552 |
+func (c *client) Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) {
|
|
| 553 |
+ ctr := c.getContainer(containerID) |
|
| 554 |
+ if ctr == nil {
|
|
| 555 |
+ return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 556 |
+ } |
|
| 557 |
+ |
|
| 558 |
+ t := ctr.getTask() |
|
| 559 |
+ if t == nil {
|
|
| 560 |
+ return libcontainerdtypes.StatusUnknown, errors.WithStack(errdefs.NotFound(errors.New("no such task")))
|
|
| 561 |
+ } |
|
| 562 |
+ |
|
| 563 |
+ s, err := t.Status(ctx) |
|
| 564 |
+ if err != nil {
|
|
| 565 |
+ return libcontainerdtypes.StatusUnknown, wrapError(err) |
|
| 566 |
+ } |
|
| 567 |
+ |
|
| 568 |
+ return libcontainerdtypes.Status(s.Status), nil |
|
| 569 |
+} |
|
| 570 |
+ |
|
| 571 |
+func (c *client) CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error {
|
|
| 572 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 573 |
+ if err != nil {
|
|
| 574 |
+ return err |
|
| 575 |
+ } |
|
| 576 |
+ |
|
| 577 |
+ opts := []containerd.CheckpointTaskOpts{}
|
|
| 578 |
+ if exit {
|
|
| 579 |
+ opts = append(opts, func(r *containerd.CheckpointTaskInfo) error {
|
|
| 580 |
+ if r.Options == nil {
|
|
| 581 |
+ r.Options = &runctypes.CheckpointOptions{
|
|
| 582 |
+ Exit: true, |
|
| 583 |
+ } |
|
| 584 |
+ } else {
|
|
| 585 |
+ opts, _ := r.Options.(*runctypes.CheckpointOptions) |
|
| 586 |
+ opts.Exit = true |
|
| 587 |
+ } |
|
| 588 |
+ return nil |
|
| 589 |
+ }) |
|
| 590 |
+ } |
|
| 591 |
+ img, err := p.(containerd.Task).Checkpoint(ctx, opts...) |
|
| 592 |
+ if err != nil {
|
|
| 593 |
+ return wrapError(err) |
|
| 594 |
+ } |
|
| 595 |
+ // Whatever happens, delete the checkpoint from containerd |
|
| 596 |
+ defer func() {
|
|
| 597 |
+ err := c.client.ImageService().Delete(context.Background(), img.Name()) |
|
| 598 |
+ if err != nil {
|
|
| 599 |
+ c.logger.WithError(err).WithField("digest", img.Target().Digest).
|
|
| 600 |
+ Warnf("failed to delete checkpoint image")
|
|
| 601 |
+ } |
|
| 602 |
+ }() |
|
| 603 |
+ |
|
| 604 |
+ b, err := content.ReadBlob(ctx, c.client.ContentStore(), img.Target()) |
|
| 605 |
+ if err != nil {
|
|
| 606 |
+ return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data")) |
|
| 607 |
+ } |
|
| 608 |
+ var index v1.Index |
|
| 609 |
+ if err := json.Unmarshal(b, &index); err != nil {
|
|
| 610 |
+ return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data")) |
|
| 611 |
+ } |
|
| 612 |
+ |
|
| 613 |
+ var cpDesc *v1.Descriptor |
|
| 614 |
+ for _, m := range index.Manifests {
|
|
| 615 |
+ if m.MediaType == images.MediaTypeContainerd1Checkpoint {
|
|
| 616 |
+ cpDesc = &m |
|
| 617 |
+ break |
|
| 618 |
+ } |
|
| 619 |
+ } |
|
| 620 |
+ if cpDesc == nil {
|
|
| 621 |
+ return errdefs.System(errors.Wrapf(err, "invalid checkpoint")) |
|
| 622 |
+ } |
|
| 623 |
+ |
|
| 624 |
+ rat, err := c.client.ContentStore().ReaderAt(ctx, *cpDesc) |
|
| 625 |
+ if err != nil {
|
|
| 626 |
+ return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader")) |
|
| 627 |
+ } |
|
| 628 |
+ defer rat.Close() |
|
| 629 |
+ _, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat)) |
|
| 630 |
+ if err != nil {
|
|
| 631 |
+ return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader")) |
|
| 632 |
+ } |
|
| 633 |
+ |
|
| 634 |
+ return err |
|
| 635 |
+} |
|
| 636 |
+ |
|
| 637 |
+func (c *client) getContainer(id string) *container {
|
|
| 638 |
+ c.RLock() |
|
| 639 |
+ ctr := c.containers[id] |
|
| 640 |
+ c.RUnlock() |
|
| 641 |
+ |
|
| 642 |
+ return ctr |
|
| 643 |
+} |
|
| 644 |
+ |
|
| 645 |
+func (c *client) removeContainer(id string) {
|
|
| 646 |
+ c.Lock() |
|
| 647 |
+ delete(c.containers, id) |
|
| 648 |
+ c.Unlock() |
|
| 649 |
+} |
|
| 650 |
+ |
|
| 651 |
+func (c *client) getProcess(containerID, processID string) (containerd.Process, error) {
|
|
| 652 |
+ ctr := c.getContainer(containerID) |
|
| 653 |
+ if ctr == nil {
|
|
| 654 |
+ return nil, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
|
|
| 655 |
+ } |
|
| 656 |
+ |
|
| 657 |
+ t := ctr.getTask() |
|
| 658 |
+ if t == nil {
|
|
| 659 |
+ return nil, errors.WithStack(errdefs.NotFound(errors.New("container is not running")))
|
|
| 660 |
+ } |
|
| 661 |
+ if processID == libcontainerdtypes.InitProcessName {
|
|
| 662 |
+ return t, nil |
|
| 663 |
+ } |
|
| 664 |
+ |
|
| 665 |
+ p := ctr.getProcess(processID) |
|
| 666 |
+ if p == nil {
|
|
| 667 |
+ return nil, errors.WithStack(errdefs.NotFound(errors.New("no such exec")))
|
|
| 668 |
+ } |
|
| 669 |
+ return p, nil |
|
| 670 |
+} |
|
| 671 |
+ |
|
| 672 |
+// createIO creates the io to be used by a process |
|
| 673 |
+// This needs to get a pointer to interface as upon closure the process may not have yet been registered |
|
| 674 |
+func (c *client) createIO(fifos *cio.FIFOSet, containerID, processID string, stdinCloseSync chan struct{}, attachStdio libcontainerdtypes.StdioCallback) (cio.IO, error) {
|
|
| 675 |
+ var ( |
|
| 676 |
+ io *cio.DirectIO |
|
| 677 |
+ err error |
|
| 678 |
+ ) |
|
| 679 |
+ io, err = c.newDirectIO(context.Background(), fifos) |
|
| 680 |
+ if err != nil {
|
|
| 681 |
+ return nil, err |
|
| 682 |
+ } |
|
| 683 |
+ |
|
| 684 |
+ if io.Stdin != nil {
|
|
| 685 |
+ var ( |
|
| 686 |
+ err error |
|
| 687 |
+ stdinOnce sync.Once |
|
| 688 |
+ ) |
|
| 689 |
+ pipe := io.Stdin |
|
| 690 |
+ io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error {
|
|
| 691 |
+ stdinOnce.Do(func() {
|
|
| 692 |
+ err = pipe.Close() |
|
| 693 |
+ // Do the rest in a new routine to avoid a deadlock if the |
|
| 694 |
+ // Exec/Start call failed. |
|
| 695 |
+ go func() {
|
|
| 696 |
+ <-stdinCloseSync |
|
| 697 |
+ p, err := c.getProcess(containerID, processID) |
|
| 698 |
+ if err == nil {
|
|
| 699 |
+ err = p.CloseIO(context.Background(), containerd.WithStdinCloser) |
|
| 700 |
+ if err != nil && strings.Contains(err.Error(), "transport is closing") {
|
|
| 701 |
+ err = nil |
|
| 702 |
+ } |
|
| 703 |
+ } |
|
| 704 |
+ }() |
|
| 705 |
+ }) |
|
| 706 |
+ return err |
|
| 707 |
+ }) |
|
| 708 |
+ } |
|
| 709 |
+ |
|
| 710 |
+ rio, err := attachStdio(io) |
|
| 711 |
+ if err != nil {
|
|
| 712 |
+ io.Cancel() |
|
| 713 |
+ io.Close() |
|
| 714 |
+ } |
|
| 715 |
+ return rio, err |
|
| 716 |
+} |
|
| 717 |
+ |
|
| 718 |
+func (c *client) processEvent(ctr *container, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) {
|
|
| 719 |
+ c.eventQ.Append(ei.ContainerID, func() {
|
|
| 720 |
+ err := c.backend.ProcessEvent(ei.ContainerID, et, ei) |
|
| 721 |
+ if err != nil {
|
|
| 722 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 723 |
+ "container": ei.ContainerID, |
|
| 724 |
+ "event": et, |
|
| 725 |
+ "event-info": ei, |
|
| 726 |
+ }).Error("failed to process event")
|
|
| 727 |
+ } |
|
| 728 |
+ |
|
| 729 |
+ if et == libcontainerdtypes.EventExit && ei.ProcessID != ei.ContainerID {
|
|
| 730 |
+ p := ctr.getProcess(ei.ProcessID) |
|
| 731 |
+ if p == nil {
|
|
| 732 |
+ c.logger.WithError(errors.New("no such process")).
|
|
| 733 |
+ WithFields(logrus.Fields{
|
|
| 734 |
+ "container": ei.ContainerID, |
|
| 735 |
+ "process": ei.ProcessID, |
|
| 736 |
+ }).Error("exit event")
|
|
| 737 |
+ return |
|
| 738 |
+ } |
|
| 739 |
+ _, err = p.Delete(context.Background()) |
|
| 740 |
+ if err != nil {
|
|
| 741 |
+ c.logger.WithError(err).WithFields(logrus.Fields{
|
|
| 742 |
+ "container": ei.ContainerID, |
|
| 743 |
+ "process": ei.ProcessID, |
|
| 744 |
+ }).Warn("failed to delete process")
|
|
| 745 |
+ } |
|
| 746 |
+ ctr.deleteProcess(ei.ProcessID) |
|
| 747 |
+ |
|
| 748 |
+ ctr := c.getContainer(ei.ContainerID) |
|
| 749 |
+ if ctr == nil {
|
|
| 750 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 751 |
+ "container": ei.ContainerID, |
|
| 752 |
+ }).Error("failed to find container")
|
|
| 753 |
+ } else {
|
|
| 754 |
+ newFIFOSet(ctr.bundleDir, ei.ProcessID, true, false).Close() |
|
| 755 |
+ } |
|
| 756 |
+ } |
|
| 757 |
+ }) |
|
| 758 |
+} |
|
| 759 |
+ |
|
| 760 |
+func (c *client) processEventStream(ctx context.Context, ns string) {
|
|
| 761 |
+ var ( |
|
| 762 |
+ err error |
|
| 763 |
+ ev *events.Envelope |
|
| 764 |
+ et libcontainerdtypes.EventType |
|
| 765 |
+ ei libcontainerdtypes.EventInfo |
|
| 766 |
+ ctr *container |
|
| 767 |
+ ) |
|
| 768 |
+ |
|
| 769 |
+ // Filter on both namespace *and* topic. To create an "and" filter, |
|
| 770 |
+ // this must be a single, comma-separated string |
|
| 771 |
+ eventStream, errC := c.client.EventService().Subscribe(ctx, "namespace=="+ns+",topic~=|^/tasks/|") |
|
| 772 |
+ |
|
| 773 |
+ c.logger.Debug("processing event stream")
|
|
| 774 |
+ |
|
| 775 |
+ var oomKilled bool |
|
| 776 |
+ for {
|
|
| 777 |
+ select {
|
|
| 778 |
+ case err = <-errC: |
|
| 779 |
+ if err != nil {
|
|
| 780 |
+ errStatus, ok := status.FromError(err) |
|
| 781 |
+ if !ok || errStatus.Code() != codes.Canceled {
|
|
| 782 |
+ c.logger.WithError(err).Error("failed to get event")
|
|
| 783 |
+ go c.processEventStream(ctx, ns) |
|
| 784 |
+ } else {
|
|
| 785 |
+ c.logger.WithError(ctx.Err()).Info("stopping event stream following graceful shutdown")
|
|
| 786 |
+ } |
|
| 787 |
+ } |
|
| 788 |
+ return |
|
| 789 |
+ case ev = <-eventStream: |
|
| 790 |
+ if ev.Event == nil {
|
|
| 791 |
+ c.logger.WithField("event", ev).Warn("invalid event")
|
|
| 792 |
+ continue |
|
| 793 |
+ } |
|
| 794 |
+ |
|
| 795 |
+ v, err := typeurl.UnmarshalAny(ev.Event) |
|
| 796 |
+ if err != nil {
|
|
| 797 |
+ c.logger.WithError(err).WithField("event", ev).Warn("failed to unmarshal event")
|
|
| 798 |
+ continue |
|
| 799 |
+ } |
|
| 800 |
+ |
|
| 801 |
+ c.logger.WithField("topic", ev.Topic).Debug("event")
|
|
| 802 |
+ |
|
| 803 |
+ switch t := v.(type) {
|
|
| 804 |
+ case *apievents.TaskCreate: |
|
| 805 |
+ et = libcontainerdtypes.EventCreate |
|
| 806 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 807 |
+ ContainerID: t.ContainerID, |
|
| 808 |
+ ProcessID: t.ContainerID, |
|
| 809 |
+ Pid: t.Pid, |
|
| 810 |
+ } |
|
| 811 |
+ case *apievents.TaskStart: |
|
| 812 |
+ et = libcontainerdtypes.EventStart |
|
| 813 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 814 |
+ ContainerID: t.ContainerID, |
|
| 815 |
+ ProcessID: t.ContainerID, |
|
| 816 |
+ Pid: t.Pid, |
|
| 817 |
+ } |
|
| 818 |
+ case *apievents.TaskExit: |
|
| 819 |
+ et = libcontainerdtypes.EventExit |
|
| 820 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 821 |
+ ContainerID: t.ContainerID, |
|
| 822 |
+ ProcessID: t.ID, |
|
| 823 |
+ Pid: t.Pid, |
|
| 824 |
+ ExitCode: t.ExitStatus, |
|
| 825 |
+ ExitedAt: t.ExitedAt, |
|
| 826 |
+ } |
|
| 827 |
+ case *apievents.TaskOOM: |
|
| 828 |
+ et = libcontainerdtypes.EventOOM |
|
| 829 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 830 |
+ ContainerID: t.ContainerID, |
|
| 831 |
+ OOMKilled: true, |
|
| 832 |
+ } |
|
| 833 |
+ oomKilled = true |
|
| 834 |
+ case *apievents.TaskExecAdded: |
|
| 835 |
+ et = libcontainerdtypes.EventExecAdded |
|
| 836 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 837 |
+ ContainerID: t.ContainerID, |
|
| 838 |
+ ProcessID: t.ExecID, |
|
| 839 |
+ } |
|
| 840 |
+ case *apievents.TaskExecStarted: |
|
| 841 |
+ et = libcontainerdtypes.EventExecStarted |
|
| 842 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 843 |
+ ContainerID: t.ContainerID, |
|
| 844 |
+ ProcessID: t.ExecID, |
|
| 845 |
+ Pid: t.Pid, |
|
| 846 |
+ } |
|
| 847 |
+ case *apievents.TaskPaused: |
|
| 848 |
+ et = libcontainerdtypes.EventPaused |
|
| 849 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 850 |
+ ContainerID: t.ContainerID, |
|
| 851 |
+ } |
|
| 852 |
+ case *apievents.TaskResumed: |
|
| 853 |
+ et = libcontainerdtypes.EventResumed |
|
| 854 |
+ ei = libcontainerdtypes.EventInfo{
|
|
| 855 |
+ ContainerID: t.ContainerID, |
|
| 856 |
+ } |
|
| 857 |
+ default: |
|
| 858 |
+ c.logger.WithFields(logrus.Fields{
|
|
| 859 |
+ "topic": ev.Topic, |
|
| 860 |
+ "type": reflect.TypeOf(t)}, |
|
| 861 |
+ ).Info("ignoring event")
|
|
| 862 |
+ continue |
|
| 863 |
+ } |
|
| 864 |
+ |
|
| 865 |
+ ctr = c.getContainer(ei.ContainerID) |
|
| 866 |
+ if ctr == nil {
|
|
| 867 |
+ c.logger.WithField("container", ei.ContainerID).Warn("unknown container")
|
|
| 868 |
+ continue |
|
| 869 |
+ } |
|
| 870 |
+ |
|
| 871 |
+ if oomKilled {
|
|
| 872 |
+ ctr.setOOMKilled(true) |
|
| 873 |
+ oomKilled = false |
|
| 874 |
+ } |
|
| 875 |
+ ei.OOMKilled = ctr.getOOMKilled() |
|
| 876 |
+ |
|
| 877 |
+ c.processEvent(ctr, et, ei) |
|
| 878 |
+ } |
|
| 879 |
+ } |
|
| 880 |
+} |
|
| 881 |
+ |
|
| 882 |
+func (c *client) writeContent(ctx context.Context, mediaType, ref string, r io.Reader) (*types.Descriptor, error) {
|
|
| 883 |
+ writer, err := c.client.ContentStore().Writer(ctx, content.WithRef(ref)) |
|
| 884 |
+ if err != nil {
|
|
| 885 |
+ return nil, err |
|
| 886 |
+ } |
|
| 887 |
+ defer writer.Close() |
|
| 888 |
+ size, err := io.Copy(writer, r) |
|
| 889 |
+ if err != nil {
|
|
| 890 |
+ return nil, err |
|
| 891 |
+ } |
|
| 892 |
+ labels := map[string]string{
|
|
| 893 |
+ "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339), |
|
| 894 |
+ } |
|
| 895 |
+ if err := writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
|
|
| 896 |
+ return nil, err |
|
| 897 |
+ } |
|
| 898 |
+ return &types.Descriptor{
|
|
| 899 |
+ MediaType: mediaType, |
|
| 900 |
+ Digest: writer.Digest(), |
|
| 901 |
+ Size_: size, |
|
| 902 |
+ }, nil |
|
| 903 |
+} |
|
| 904 |
+ |
|
| 905 |
+func wrapError(err error) error {
|
|
| 906 |
+ switch {
|
|
| 907 |
+ case err == nil: |
|
| 908 |
+ return nil |
|
| 909 |
+ case containerderrors.IsNotFound(err): |
|
| 910 |
+ return errdefs.NotFound(err) |
|
| 911 |
+ } |
|
| 912 |
+ |
|
| 913 |
+ msg := err.Error() |
|
| 914 |
+ for _, s := range []string{"container does not exist", "not found", "no such container"} {
|
|
| 915 |
+ if strings.Contains(msg, s) {
|
|
| 916 |
+ return errdefs.NotFound(err) |
|
| 917 |
+ } |
|
| 918 |
+ } |
|
| 919 |
+ return err |
|
| 920 |
+} |
| 0 | 921 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,161 @@ |
| 0 |
+package remote // import "github.com/docker/docker/libcontainerd/remote" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "net" |
|
| 5 |
+ "sync" |
|
| 6 |
+ |
|
| 7 |
+ winio "github.com/Microsoft/go-winio" |
|
| 8 |
+ "github.com/containerd/containerd/cio" |
|
| 9 |
+ "github.com/pkg/errors" |
|
| 10 |
+ "github.com/sirupsen/logrus" |
|
| 11 |
+ // "golang.org/x/net/context" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+type delayedConnection struct {
|
|
| 15 |
+ l net.Listener |
|
| 16 |
+ con net.Conn |
|
| 17 |
+ wg sync.WaitGroup |
|
| 18 |
+ once sync.Once |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func (dc *delayedConnection) Write(p []byte) (int, error) {
|
|
| 22 |
+ dc.wg.Wait() |
|
| 23 |
+ if dc.con != nil {
|
|
| 24 |
+ return dc.con.Write(p) |
|
| 25 |
+ } |
|
| 26 |
+ return 0, errors.New("use of closed network connection")
|
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (dc *delayedConnection) Read(p []byte) (int, error) {
|
|
| 30 |
+ dc.wg.Wait() |
|
| 31 |
+ if dc.con != nil {
|
|
| 32 |
+ return dc.con.Read(p) |
|
| 33 |
+ } |
|
| 34 |
+ return 0, errors.New("use of closed network connection")
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func (dc *delayedConnection) unblockConnectionWaiters() {
|
|
| 38 |
+ defer dc.once.Do(func() {
|
|
| 39 |
+ dc.wg.Done() |
|
| 40 |
+ }) |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+func (dc *delayedConnection) Close() error {
|
|
| 44 |
+ dc.l.Close() |
|
| 45 |
+ if dc.con != nil {
|
|
| 46 |
+ return dc.con.Close() |
|
| 47 |
+ } |
|
| 48 |
+ dc.unblockConnectionWaiters() |
|
| 49 |
+ return nil |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+type stdioPipes struct {
|
|
| 53 |
+ stdin io.WriteCloser |
|
| 54 |
+ stdout io.ReadCloser |
|
| 55 |
+ stderr io.ReadCloser |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+// newStdioPipes creates actual fifos for stdio. |
|
| 59 |
+func (c *client) newStdioPipes(fifos *cio.FIFOSet) (_ *stdioPipes, err error) {
|
|
| 60 |
+ p := &stdioPipes{}
|
|
| 61 |
+ if fifos.Stdin != "" {
|
|
| 62 |
+ c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("listen")
|
|
| 63 |
+ l, err := winio.ListenPipe(fifos.Stdin, nil) |
|
| 64 |
+ if err != nil {
|
|
| 65 |
+ return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin) |
|
| 66 |
+ } |
|
| 67 |
+ dc := &delayedConnection{
|
|
| 68 |
+ l: l, |
|
| 69 |
+ } |
|
| 70 |
+ dc.wg.Add(1) |
|
| 71 |
+ defer func() {
|
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ dc.Close() |
|
| 74 |
+ } |
|
| 75 |
+ }() |
|
| 76 |
+ p.stdin = dc |
|
| 77 |
+ |
|
| 78 |
+ go func() {
|
|
| 79 |
+ c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("accept")
|
|
| 80 |
+ conn, err := l.Accept() |
|
| 81 |
+ if err != nil {
|
|
| 82 |
+ dc.Close() |
|
| 83 |
+ if err != winio.ErrPipeListenerClosed {
|
|
| 84 |
+ c.logger.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.Stdin)
|
|
| 85 |
+ } |
|
| 86 |
+ return |
|
| 87 |
+ } |
|
| 88 |
+ c.logger.WithFields(logrus.Fields{"stdin": fifos.Stdin}).Debug("connected")
|
|
| 89 |
+ dc.con = conn |
|
| 90 |
+ dc.unblockConnectionWaiters() |
|
| 91 |
+ }() |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ if fifos.Stdout != "" {
|
|
| 95 |
+ c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("listen")
|
|
| 96 |
+ l, err := winio.ListenPipe(fifos.Stdout, nil) |
|
| 97 |
+ if err != nil {
|
|
| 98 |
+ return nil, errors.Wrapf(err, "failed to create stdout pipe %s", fifos.Stdout) |
|
| 99 |
+ } |
|
| 100 |
+ dc := &delayedConnection{
|
|
| 101 |
+ l: l, |
|
| 102 |
+ } |
|
| 103 |
+ dc.wg.Add(1) |
|
| 104 |
+ defer func() {
|
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ dc.Close() |
|
| 107 |
+ } |
|
| 108 |
+ }() |
|
| 109 |
+ p.stdout = dc |
|
| 110 |
+ |
|
| 111 |
+ go func() {
|
|
| 112 |
+ c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("accept")
|
|
| 113 |
+ conn, err := l.Accept() |
|
| 114 |
+ if err != nil {
|
|
| 115 |
+ dc.Close() |
|
| 116 |
+ if err != winio.ErrPipeListenerClosed {
|
|
| 117 |
+ c.logger.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Stdout)
|
|
| 118 |
+ } |
|
| 119 |
+ return |
|
| 120 |
+ } |
|
| 121 |
+ c.logger.WithFields(logrus.Fields{"stdout": fifos.Stdout}).Debug("connected")
|
|
| 122 |
+ dc.con = conn |
|
| 123 |
+ dc.unblockConnectionWaiters() |
|
| 124 |
+ }() |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ if fifos.Stderr != "" {
|
|
| 128 |
+ c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("listen")
|
|
| 129 |
+ l, err := winio.ListenPipe(fifos.Stderr, nil) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr) |
|
| 132 |
+ } |
|
| 133 |
+ dc := &delayedConnection{
|
|
| 134 |
+ l: l, |
|
| 135 |
+ } |
|
| 136 |
+ dc.wg.Add(1) |
|
| 137 |
+ defer func() {
|
|
| 138 |
+ if err != nil {
|
|
| 139 |
+ dc.Close() |
|
| 140 |
+ } |
|
| 141 |
+ }() |
|
| 142 |
+ p.stderr = dc |
|
| 143 |
+ |
|
| 144 |
+ go func() {
|
|
| 145 |
+ c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("accept")
|
|
| 146 |
+ conn, err := l.Accept() |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ dc.Close() |
|
| 149 |
+ if err != winio.ErrPipeListenerClosed {
|
|
| 150 |
+ c.logger.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Stderr)
|
|
| 151 |
+ } |
|
| 152 |
+ return |
|
| 153 |
+ } |
|
| 154 |
+ c.logger.WithFields(logrus.Fields{"stderr": fifos.Stderr}).Debug("connected")
|
|
| 155 |
+ dc.con = conn |
|
| 156 |
+ dc.unblockConnectionWaiters() |
|
| 157 |
+ }() |
|
| 158 |
+ } |
|
| 159 |
+ return p, nil |
|
| 160 |
+} |
| 0 | 161 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,115 @@ |
| 0 |
+package remote // import "github.com/docker/docker/libcontainerd/remote" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/containerd/containerd" |
|
| 10 |
+ "github.com/containerd/containerd/cio" |
|
| 11 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 12 |
+ "github.com/docker/docker/pkg/idtools" |
|
| 13 |
+ "github.com/opencontainers/runtime-spec/specs-go" |
|
| 14 |
+ "github.com/sirupsen/logrus" |
|
| 15 |
+) |
|
| 16 |
+ |
|
| 17 |
+const runtimeName = "io.containerd.runtime.v1.linux" |
|
| 18 |
+ |
|
| 19 |
+func summaryFromInterface(i interface{}) (*libcontainerdtypes.Summary, error) {
|
|
| 20 |
+ return &libcontainerdtypes.Summary{}, nil
|
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
|
|
| 24 |
+ p, err := c.getProcess(containerID, libcontainerdtypes.InitProcessName) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return err |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 29 |
+ // go doesn't like the alias in 1.8, this means this need to be |
|
| 30 |
+ // platform specific |
|
| 31 |
+ return p.(containerd.Task).Update(ctx, containerd.WithResources((*specs.LinuxResources)(resources))) |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+func hostIDFromMap(id uint32, mp []specs.LinuxIDMapping) int {
|
|
| 35 |
+ for _, m := range mp {
|
|
| 36 |
+ if id >= m.ContainerID && id <= m.ContainerID+m.Size-1 {
|
|
| 37 |
+ return int(m.HostID + id - m.ContainerID) |
|
| 38 |
+ } |
|
| 39 |
+ } |
|
| 40 |
+ return 0 |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+func getSpecUser(ociSpec *specs.Spec) (int, int) {
|
|
| 44 |
+ var ( |
|
| 45 |
+ uid int |
|
| 46 |
+ gid int |
|
| 47 |
+ ) |
|
| 48 |
+ |
|
| 49 |
+ for _, ns := range ociSpec.Linux.Namespaces {
|
|
| 50 |
+ if ns.Type == specs.UserNamespace {
|
|
| 51 |
+ uid = hostIDFromMap(0, ociSpec.Linux.UIDMappings) |
|
| 52 |
+ gid = hostIDFromMap(0, ociSpec.Linux.GIDMappings) |
|
| 53 |
+ break |
|
| 54 |
+ } |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ return uid, gid |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
|
|
| 61 |
+ uid, gid := getSpecUser(ociSpec) |
|
| 62 |
+ if uid == 0 && gid == 0 {
|
|
| 63 |
+ return bundleDir, idtools.MkdirAllAndChownNew(bundleDir, 0755, idtools.Identity{UID: 0, GID: 0})
|
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ p := string(filepath.Separator) |
|
| 67 |
+ components := strings.Split(bundleDir, string(filepath.Separator)) |
|
| 68 |
+ for _, d := range components[1:] {
|
|
| 69 |
+ p = filepath.Join(p, d) |
|
| 70 |
+ fi, err := os.Stat(p) |
|
| 71 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 72 |
+ return "", err |
|
| 73 |
+ } |
|
| 74 |
+ if os.IsNotExist(err) || fi.Mode()&1 == 0 {
|
|
| 75 |
+ p = fmt.Sprintf("%s.%d.%d", p, uid, gid)
|
|
| 76 |
+ if err := idtools.MkdirAndChown(p, 0700, idtools.Identity{UID: uid, GID: gid}); err != nil && !os.IsExist(err) {
|
|
| 77 |
+ return "", err |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ return p, nil |
|
| 83 |
+} |
|
| 84 |
+ |
|
| 85 |
+func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
|
|
| 86 |
+ config := cio.Config{
|
|
| 87 |
+ Terminal: withTerminal, |
|
| 88 |
+ Stdout: filepath.Join(bundleDir, processID+"-stdout"), |
|
| 89 |
+ } |
|
| 90 |
+ paths := []string{config.Stdout}
|
|
| 91 |
+ |
|
| 92 |
+ if withStdin {
|
|
| 93 |
+ config.Stdin = filepath.Join(bundleDir, processID+"-stdin") |
|
| 94 |
+ paths = append(paths, config.Stdin) |
|
| 95 |
+ } |
|
| 96 |
+ if !withTerminal {
|
|
| 97 |
+ config.Stderr = filepath.Join(bundleDir, processID+"-stderr") |
|
| 98 |
+ paths = append(paths, config.Stderr) |
|
| 99 |
+ } |
|
| 100 |
+ closer := func() error {
|
|
| 101 |
+ for _, path := range paths {
|
|
| 102 |
+ if err := os.RemoveAll(path); err != nil {
|
|
| 103 |
+ logrus.Warnf("libcontainerd: failed to remove fifo %v: %v", path, err)
|
|
| 104 |
+ } |
|
| 105 |
+ } |
|
| 106 |
+ return nil |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ return cio.NewFIFOSet(config, closer) |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 112 |
+func (c *client) newDirectIO(ctx context.Context, fifos *cio.FIFOSet) (*cio.DirectIO, error) {
|
|
| 113 |
+ return cio.NewDirectIO(ctx, fifos) |
|
| 114 |
+} |
| 0 | 115 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,80 @@ |
| 0 |
+package remote // import "github.com/docker/docker/libcontainerd/remote" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/containerd/containerd/cio" |
|
| 9 |
+ "github.com/containerd/containerd/windows/hcsshimtypes" |
|
| 10 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 11 |
+ specs "github.com/opencontainers/runtime-spec/specs-go" |
|
| 12 |
+ "github.com/pkg/errors" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+const runtimeName = "io.containerd.runhcs.v1" |
|
| 16 |
+ |
|
| 17 |
+func summaryFromInterface(i interface{}) (*libcontainerdtypes.Summary, error) {
|
|
| 18 |
+ switch pd := i.(type) {
|
|
| 19 |
+ case *hcsshimtypes.ProcessDetails: |
|
| 20 |
+ return &libcontainerdtypes.Summary{
|
|
| 21 |
+ CreateTimestamp: pd.CreatedAt, |
|
| 22 |
+ ImageName: pd.ImageName, |
|
| 23 |
+ KernelTime100ns: pd.KernelTime_100Ns, |
|
| 24 |
+ MemoryCommitBytes: pd.MemoryCommitBytes, |
|
| 25 |
+ MemoryWorkingSetPrivateBytes: pd.MemoryWorkingSetPrivateBytes, |
|
| 26 |
+ MemoryWorkingSetSharedBytes: pd.MemoryWorkingSetSharedBytes, |
|
| 27 |
+ ProcessId: pd.ProcessID, |
|
| 28 |
+ UserTime100ns: pd.UserTime_100Ns, |
|
| 29 |
+ }, nil |
|
| 30 |
+ default: |
|
| 31 |
+ return nil, errors.Errorf("Unknown process details type %T", pd)
|
|
| 32 |
+ } |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func prepareBundleDir(bundleDir string, ociSpec *specs.Spec) (string, error) {
|
|
| 36 |
+ // TODO: (containerd) Determine if we need to use system.MkdirAllWithACL here |
|
| 37 |
+ return bundleDir, os.MkdirAll(bundleDir, 0755) |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func pipeName(containerID, processID, name string) string {
|
|
| 41 |
+ return fmt.Sprintf(`\\.\pipe\containerd-%s-%s-%s`, containerID, processID, name) |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func newFIFOSet(bundleDir, processID string, withStdin, withTerminal bool) *cio.FIFOSet {
|
|
| 45 |
+ containerID := filepath.Base(bundleDir) |
|
| 46 |
+ config := cio.Config{
|
|
| 47 |
+ Terminal: withTerminal, |
|
| 48 |
+ Stdout: pipeName(containerID, processID, "stdout"), |
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ if withStdin {
|
|
| 52 |
+ config.Stdin = pipeName(containerID, processID, "stdin") |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if !config.Terminal {
|
|
| 56 |
+ config.Stderr = pipeName(containerID, processID, "stderr") |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ return cio.NewFIFOSet(config, nil) |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+func (c *client) newDirectIO(ctx context.Context, fifos *cio.FIFOSet) (*cio.DirectIO, error) {
|
|
| 63 |
+ pipes, err := c.newStdioPipes(fifos) |
|
| 64 |
+ if err != nil {
|
|
| 65 |
+ return nil, err |
|
| 66 |
+ } |
|
| 67 |
+ return cio.NewDirectIOFromFIFOSet(ctx, pipes.stdin, pipes.stdout, pipes.stderr, fifos), nil |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func (c *client) UpdateResources(ctx context.Context, containerID string, resources *libcontainerdtypes.Resources) error {
|
|
| 71 |
+ // TODO: (containerd): Not implemented, but don't error. |
|
| 72 |
+ return nil |
|
| 73 |
+} |
|
| 74 |
+ |
|
| 75 |
+func getSpecUser(ociSpec *specs.Spec) (int, int) {
|
|
| 76 |
+ // TODO: (containerd): Not implemented, but don't error. |
|
| 77 |
+ // Not clear if we can even do this for LCOW. |
|
| 78 |
+ return 0, 0 |
|
| 79 |
+} |
| 0 | 80 |
deleted file mode 100644 |
| ... | ... |
@@ -1,91 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "context" |
|
| 5 |
- "time" |
|
| 6 |
- |
|
| 7 |
- "github.com/containerd/containerd" |
|
| 8 |
- "github.com/containerd/containerd/cio" |
|
| 9 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 10 |
-) |
|
| 11 |
- |
|
| 12 |
-// EventType represents a possible event from libcontainerd |
|
| 13 |
-type EventType string |
|
| 14 |
- |
|
| 15 |
-// Event constants used when reporting events |
|
| 16 |
-const ( |
|
| 17 |
- EventUnknown EventType = "unknown" |
|
| 18 |
- EventExit EventType = "exit" |
|
| 19 |
- EventOOM EventType = "oom" |
|
| 20 |
- EventCreate EventType = "create" |
|
| 21 |
- EventStart EventType = "start" |
|
| 22 |
- EventExecAdded EventType = "exec-added" |
|
| 23 |
- EventExecStarted EventType = "exec-started" |
|
| 24 |
- EventPaused EventType = "paused" |
|
| 25 |
- EventResumed EventType = "resumed" |
|
| 26 |
-) |
|
| 27 |
- |
|
| 28 |
-// Status represents the current status of a container |
|
| 29 |
-type Status string |
|
| 30 |
- |
|
| 31 |
-// Possible container statuses |
|
| 32 |
-const ( |
|
| 33 |
- // Running indicates the process is currently executing |
|
| 34 |
- StatusRunning Status = "running" |
|
| 35 |
- // Created indicates the process has been created within containerd but the |
|
| 36 |
- // user's defined process has not started |
|
| 37 |
- StatusCreated Status = "created" |
|
| 38 |
- // Stopped indicates that the process has ran and exited |
|
| 39 |
- StatusStopped Status = "stopped" |
|
| 40 |
- // Paused indicates that the process is currently paused |
|
| 41 |
- StatusPaused Status = "paused" |
|
| 42 |
- // Pausing indicates that the process is currently switching from a |
|
| 43 |
- // running state into a paused state |
|
| 44 |
- StatusPausing Status = "pausing" |
|
| 45 |
- // Unknown indicates that we could not determine the status from the runtime |
|
| 46 |
- StatusUnknown Status = "unknown" |
|
| 47 |
-) |
|
| 48 |
- |
|
| 49 |
-// EventInfo contains the event info |
|
| 50 |
-type EventInfo struct {
|
|
| 51 |
- ContainerID string |
|
| 52 |
- ProcessID string |
|
| 53 |
- Pid uint32 |
|
| 54 |
- ExitCode uint32 |
|
| 55 |
- ExitedAt time.Time |
|
| 56 |
- OOMKilled bool |
|
| 57 |
- Error error |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-// Backend defines callbacks that the client of the library needs to implement. |
|
| 61 |
-type Backend interface {
|
|
| 62 |
- ProcessEvent(containerID string, event EventType, ei EventInfo) error |
|
| 63 |
-} |
|
| 64 |
- |
|
| 65 |
-// Client provides access to containerd features. |
|
| 66 |
-type Client interface {
|
|
| 67 |
- Version(ctx context.Context) (containerd.Version, error) |
|
| 68 |
- |
|
| 69 |
- Restore(ctx context.Context, containerID string, attachStdio StdioCallback) (alive bool, pid int, err error) |
|
| 70 |
- |
|
| 71 |
- Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
|
|
| 72 |
- Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio StdioCallback) (pid int, err error) |
|
| 73 |
- SignalProcess(ctx context.Context, containerID, processID string, signal int) error |
|
| 74 |
- Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) |
|
| 75 |
- ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error |
|
| 76 |
- CloseStdin(ctx context.Context, containerID, processID string) error |
|
| 77 |
- Pause(ctx context.Context, containerID string) error |
|
| 78 |
- Resume(ctx context.Context, containerID string) error |
|
| 79 |
- Stats(ctx context.Context, containerID string) (*Stats, error) |
|
| 80 |
- ListPids(ctx context.Context, containerID string) ([]uint32, error) |
|
| 81 |
- Summary(ctx context.Context, containerID string) ([]Summary, error) |
|
| 82 |
- DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) |
|
| 83 |
- Delete(ctx context.Context, containerID string) error |
|
| 84 |
- Status(ctx context.Context, containerID string) (Status, error) |
|
| 85 |
- |
|
| 86 |
- UpdateResources(ctx context.Context, containerID string, resources *Resources) error |
|
| 87 |
- CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error |
|
| 88 |
-} |
|
| 89 |
- |
|
| 90 |
-// StdioCallback is called to connect a container or process stdio. |
|
| 91 |
-type StdioCallback func(io *cio.DirectIO) (cio.IO, error) |
| 92 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,94 @@ |
| 0 |
+package types // import "github.com/docker/docker/libcontainerd/types" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "time" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/containerd/containerd" |
|
| 7 |
+ "github.com/containerd/containerd/cio" |
|
| 8 |
+ "github.com/opencontainers/runtime-spec/specs-go" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// EventType represents a possible event from libcontainerd |
|
| 12 |
+type EventType string |
|
| 13 |
+ |
|
| 14 |
+// Event constants used when reporting events |
|
| 15 |
+const ( |
|
| 16 |
+ EventUnknown EventType = "unknown" |
|
| 17 |
+ EventExit EventType = "exit" |
|
| 18 |
+ EventOOM EventType = "oom" |
|
| 19 |
+ EventCreate EventType = "create" |
|
| 20 |
+ EventStart EventType = "start" |
|
| 21 |
+ EventExecAdded EventType = "exec-added" |
|
| 22 |
+ EventExecStarted EventType = "exec-started" |
|
| 23 |
+ EventPaused EventType = "paused" |
|
| 24 |
+ EventResumed EventType = "resumed" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// Status represents the current status of a container |
|
| 28 |
+type Status string |
|
| 29 |
+ |
|
| 30 |
+// Possible container statuses |
|
| 31 |
+const ( |
|
| 32 |
+ // Running indicates the process is currently executing |
|
| 33 |
+ StatusRunning Status = "running" |
|
| 34 |
+ // Created indicates the process has been created within containerd but the |
|
| 35 |
+ // user's defined process has not started |
|
| 36 |
+ StatusCreated Status = "created" |
|
| 37 |
+ // Stopped indicates that the process has ran and exited |
|
| 38 |
+ StatusStopped Status = "stopped" |
|
| 39 |
+ // Paused indicates that the process is currently paused |
|
| 40 |
+ StatusPaused Status = "paused" |
|
| 41 |
+ // Pausing indicates that the process is currently switching from a |
|
| 42 |
+ // running state into a paused state |
|
| 43 |
+ StatusPausing Status = "pausing" |
|
| 44 |
+ // Unknown indicates that we could not determine the status from the runtime |
|
| 45 |
+ StatusUnknown Status = "unknown" |
|
| 46 |
+) |
|
| 47 |
+ |
|
| 48 |
+// EventInfo contains the event info |
|
| 49 |
+type EventInfo struct {
|
|
| 50 |
+ ContainerID string |
|
| 51 |
+ ProcessID string |
|
| 52 |
+ Pid uint32 |
|
| 53 |
+ ExitCode uint32 |
|
| 54 |
+ ExitedAt time.Time |
|
| 55 |
+ OOMKilled bool |
|
| 56 |
+ Error error |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// Backend defines callbacks that the client of the library needs to implement. |
|
| 60 |
+type Backend interface {
|
|
| 61 |
+ ProcessEvent(containerID string, event EventType, ei EventInfo) error |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+// Client provides access to containerd features. |
|
| 65 |
+type Client interface {
|
|
| 66 |
+ Version(ctx context.Context) (containerd.Version, error) |
|
| 67 |
+ |
|
| 68 |
+ Restore(ctx context.Context, containerID string, attachStdio StdioCallback) (alive bool, pid int, err error) |
|
| 69 |
+ |
|
| 70 |
+ Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
|
|
| 71 |
+ Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio StdioCallback) (pid int, err error) |
|
| 72 |
+ SignalProcess(ctx context.Context, containerID, processID string, signal int) error |
|
| 73 |
+ Exec(ctx context.Context, containerID, processID string, spec *specs.Process, withStdin bool, attachStdio StdioCallback) (int, error) |
|
| 74 |
+ ResizeTerminal(ctx context.Context, containerID, processID string, width, height int) error |
|
| 75 |
+ CloseStdin(ctx context.Context, containerID, processID string) error |
|
| 76 |
+ Pause(ctx context.Context, containerID string) error |
|
| 77 |
+ Resume(ctx context.Context, containerID string) error |
|
| 78 |
+ Stats(ctx context.Context, containerID string) (*Stats, error) |
|
| 79 |
+ ListPids(ctx context.Context, containerID string) ([]uint32, error) |
|
| 80 |
+ Summary(ctx context.Context, containerID string) ([]Summary, error) |
|
| 81 |
+ DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) |
|
| 82 |
+ Delete(ctx context.Context, containerID string) error |
|
| 83 |
+ Status(ctx context.Context, containerID string) (Status, error) |
|
| 84 |
+ |
|
| 85 |
+ UpdateResources(ctx context.Context, containerID string, resources *Resources) error |
|
| 86 |
+ CreateCheckpoint(ctx context.Context, containerID, checkpointDir string, exit bool) error |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+// StdioCallback is called to connect a container or process stdio. |
|
| 90 |
+type StdioCallback func(io *cio.DirectIO) (cio.IO, error) |
|
| 91 |
+ |
|
| 92 |
+// InitProcessName is the name given to the first process of a container |
|
| 93 |
+const InitProcessName = "init" |
| 0 | 94 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,31 @@ |
| 0 |
+package types // import "github.com/docker/docker/libcontainerd/types" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "time" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/containerd/cgroups" |
|
| 6 |
+ "github.com/opencontainers/runtime-spec/specs-go" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// Summary is not used on linux |
|
| 10 |
+type Summary struct{}
|
|
| 11 |
+ |
|
| 12 |
+// Stats holds metrics properties as returned by containerd |
|
| 13 |
+type Stats struct {
|
|
| 14 |
+ Read time.Time |
|
| 15 |
+ Metrics *cgroups.Metrics |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+// InterfaceToStats returns a stats object from the platform-specific interface. |
|
| 19 |
+func InterfaceToStats(read time.Time, v interface{}) *Stats {
|
|
| 20 |
+ return &Stats{
|
|
| 21 |
+ Metrics: v.(*cgroups.Metrics), |
|
| 22 |
+ Read: read, |
|
| 23 |
+ } |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Resources defines updatable container resource values. TODO: it must match containerd upcoming API |
|
| 27 |
+type Resources specs.LinuxResources |
|
| 28 |
+ |
|
| 29 |
+// Checkpoints contains the details of a checkpoint |
|
| 30 |
+type Checkpoints struct{}
|
| 0 | 31 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package types // import "github.com/docker/docker/libcontainerd/types" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "time" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/Microsoft/hcsshim" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// Summary contains a ProcessList item from HCS to support `top` |
|
| 9 |
+type Summary hcsshim.ProcessListItem |
|
| 10 |
+ |
|
| 11 |
+// Stats contains statistics from HCS |
|
| 12 |
+type Stats struct {
|
|
| 13 |
+ Read time.Time |
|
| 14 |
+ HCSStats *hcsshim.Statistics |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+// InterfaceToStats returns a stats object from the platform-specific interface. |
|
| 18 |
+func InterfaceToStats(read time.Time, v interface{}) *Stats {
|
|
| 19 |
+ return &Stats{
|
|
| 20 |
+ HCSStats: v.(*hcsshim.Statistics), |
|
| 21 |
+ Read: read, |
|
| 22 |
+ } |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// Resources defines updatable container resource values. |
|
| 26 |
+type Resources struct{}
|
|
| 27 |
+ |
|
| 28 |
+// Checkpoint holds the details of a checkpoint (not supported in windows) |
|
| 29 |
+type Checkpoint struct {
|
|
| 30 |
+ Name string |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// Checkpoints contains the details of a checkpoint |
|
| 34 |
+type Checkpoints struct {
|
|
| 35 |
+ Checkpoints []*Checkpoint |
|
| 36 |
+} |
| 0 | 37 |
deleted file mode 100644 |
| ... | ... |
@@ -1,30 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "time" |
|
| 5 |
- |
|
| 6 |
- "github.com/containerd/cgroups" |
|
| 7 |
- "github.com/opencontainers/runtime-spec/specs-go" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-// Summary is not used on linux |
|
| 11 |
-type Summary struct{}
|
|
| 12 |
- |
|
| 13 |
-// Stats holds metrics properties as returned by containerd |
|
| 14 |
-type Stats struct {
|
|
| 15 |
- Read time.Time |
|
| 16 |
- Metrics *cgroups.Metrics |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func interfaceToStats(read time.Time, v interface{}) *Stats {
|
|
| 20 |
- return &Stats{
|
|
| 21 |
- Metrics: v.(*cgroups.Metrics), |
|
| 22 |
- Read: read, |
|
| 23 |
- } |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// Resources defines updatable container resource values. TODO: it must match containerd upcoming API |
|
| 27 |
-type Resources specs.LinuxResources |
|
| 28 |
- |
|
| 29 |
-// Checkpoints contains the details of a checkpoint |
|
| 30 |
-type Checkpoints struct{}
|
| 31 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,42 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "time" |
|
| 5 |
- |
|
| 6 |
- "github.com/Microsoft/hcsshim" |
|
| 7 |
- opengcs "github.com/Microsoft/opengcs/client" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-// Summary contains a ProcessList item from HCS to support `top` |
|
| 11 |
-type Summary hcsshim.ProcessListItem |
|
| 12 |
- |
|
| 13 |
-// Stats contains statistics from HCS |
|
| 14 |
-type Stats struct {
|
|
| 15 |
- Read time.Time |
|
| 16 |
- HCSStats *hcsshim.Statistics |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func interfaceToStats(read time.Time, v interface{}) *Stats {
|
|
| 20 |
- return &Stats{
|
|
| 21 |
- HCSStats: v.(*hcsshim.Statistics), |
|
| 22 |
- Read: read, |
|
| 23 |
- } |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-// Resources defines updatable container resource values. |
|
| 27 |
-type Resources struct{}
|
|
| 28 |
- |
|
| 29 |
-// LCOWOption is a CreateOption required for LCOW configuration |
|
| 30 |
-type LCOWOption struct {
|
|
| 31 |
- Config *opengcs.Config |
|
| 32 |
-} |
|
| 33 |
- |
|
| 34 |
-// Checkpoint holds the details of a checkpoint (not supported in windows) |
|
| 35 |
-type Checkpoint struct {
|
|
| 36 |
- Name string |
|
| 37 |
-} |
|
| 38 |
- |
|
| 39 |
-// Checkpoints contains the details of a checkpoint |
|
| 40 |
-type Checkpoints struct {
|
|
| 41 |
- Checkpoints []*Checkpoint |
|
| 42 |
-} |
| 43 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,38 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "strings" |
|
| 5 |
- |
|
| 6 |
- opengcs "github.com/Microsoft/opengcs/client" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// setupEnvironmentVariables converts a string array of environment variables |
|
| 10 |
-// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc. |
|
| 11 |
-func setupEnvironmentVariables(a []string) map[string]string {
|
|
| 12 |
- r := make(map[string]string) |
|
| 13 |
- for _, s := range a {
|
|
| 14 |
- arr := strings.SplitN(s, "=", 2) |
|
| 15 |
- if len(arr) == 2 {
|
|
| 16 |
- r[arr[0]] = arr[1] |
|
| 17 |
- } |
|
| 18 |
- } |
|
| 19 |
- return r |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-// Apply for the LCOW option is a no-op. |
|
| 23 |
-func (s *LCOWOption) Apply(interface{}) error {
|
|
| 24 |
- return nil |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-// debugGCS is a dirty hack for debugging for Linux Utility VMs. It simply |
|
| 28 |
-// runs a bunch of commands inside the UVM, but seriously aides in advanced debugging. |
|
| 29 |
-func (c *container) debugGCS() {
|
|
| 30 |
- if c == nil || c.isWindows || c.hcsContainer == nil {
|
|
| 31 |
- return |
|
| 32 |
- } |
|
| 33 |
- cfg := opengcs.Config{
|
|
| 34 |
- Uvm: c.hcsContainer, |
|
| 35 |
- UvmTimeoutSeconds: 600, |
|
| 36 |
- } |
|
| 37 |
- cfg.DebugGCS() |
|
| 38 |
-} |
| 39 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,13 +0,0 @@ |
| 1 |
-package libcontainerd // import "github.com/docker/docker/libcontainerd" |
|
| 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 |
-} |
| ... | ... |
@@ -5,3 +5,8 @@ package system // import "github.com/docker/docker/pkg/system" |
| 5 | 5 |
// InitLCOW does nothing since LCOW is a windows only feature |
| 6 | 6 |
func InitLCOW(experimental bool) {
|
| 7 | 7 |
} |
| 8 |
+ |
|
| 9 |
+// ContainerdRuntimeSupported returns true if the use of ContainerD runtime is supported. |
|
| 10 |
+func ContainerdRuntimeSupported(_ bool, _ string) bool {
|
|
| 11 |
+ return true |
|
| 12 |
+} |
| ... | ... |
@@ -1,7 +1,19 @@ |
| 1 | 1 |
package system // import "github.com/docker/docker/pkg/system" |
| 2 | 2 |
|
| 3 |
-// lcowSupported determines if Linux Containers on Windows are supported. |
|
| 4 |
-var lcowSupported = false |
|
| 3 |
+import ( |
|
| 4 |
+ "os" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/sirupsen/logrus" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+var ( |
|
| 10 |
+ // lcowSupported determines if Linux Containers on Windows are supported. |
|
| 11 |
+ lcowSupported = false |
|
| 12 |
+ |
|
| 13 |
+ // containerdRuntimeSupported determines if ContainerD should be the runtime. |
|
| 14 |
+ // As of March 2019, this is an experimental feature. |
|
| 15 |
+ containerdRuntimeSupported = false |
|
| 16 |
+) |
|
| 5 | 17 |
|
| 6 | 18 |
// InitLCOW sets whether LCOW is supported or not |
| 7 | 19 |
func InitLCOW(experimental bool) {
|
| ... | ... |
@@ -10,3 +22,19 @@ func InitLCOW(experimental bool) {
|
| 10 | 10 |
lcowSupported = true |
| 11 | 11 |
} |
| 12 | 12 |
} |
| 13 |
+ |
|
| 14 |
+// InitContainerdRuntime sets whether to use ContainerD for runtime |
|
| 15 |
+// on Windows. This is an experimental feature still in development, and |
|
| 16 |
+// also requires an environment variable to be set (so as not to turn the |
|
| 17 |
+// feature on from simply experimental which would also mean LCOW. |
|
| 18 |
+func InitContainerdRuntime(experimental bool, cdPath string) {
|
|
| 19 |
+ if experimental && len(cdPath) > 0 && len(os.Getenv("DOCKER_WINDOWS_CONTAINERD_RUNTIME")) > 0 {
|
|
| 20 |
+ logrus.Warnf("Using ContainerD runtime. This feature is experimental")
|
|
| 21 |
+ containerdRuntimeSupported = true |
|
| 22 |
+ } |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// ContainerdRuntimeSupported returns true if the use of ContainerD runtime is supported. |
|
| 26 |
+func ContainerdRuntimeSupported() bool {
|
|
| 27 |
+ return containerdRuntimeSupported |
|
| 28 |
+} |
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
"github.com/containerd/containerd/runtime/linux/runctypes" |
| 13 | 13 |
"github.com/docker/docker/errdefs" |
| 14 | 14 |
"github.com/docker/docker/libcontainerd" |
| 15 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 15 | 16 |
"github.com/opencontainers/runtime-spec/specs-go" |
| 16 | 17 |
"github.com/pkg/errors" |
| 17 | 18 |
"github.com/sirupsen/logrus" |
| ... | ... |
@@ -30,11 +31,11 @@ type ExitHandler interface {
|
| 30 | 30 |
// However right now this whole package is tied to github.com/docker/docker/libcontainerd |
| 31 | 31 |
type Client interface {
|
| 32 | 32 |
Create(ctx context.Context, containerID string, spec *specs.Spec, runtimeOptions interface{}) error
|
| 33 |
- Restore(ctx context.Context, containerID string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) |
|
| 34 |
- Status(ctx context.Context, containerID string) (libcontainerd.Status, error) |
|
| 33 |
+ Restore(ctx context.Context, containerID string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) |
|
| 34 |
+ Status(ctx context.Context, containerID string) (libcontainerdtypes.Status, error) |
|
| 35 | 35 |
Delete(ctx context.Context, containerID string) error |
| 36 | 36 |
DeleteTask(ctx context.Context, containerID string) (uint32, time.Time, error) |
| 37 |
- Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) |
|
| 37 |
+ Start(ctx context.Context, containerID, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error) |
|
| 38 | 38 |
SignalProcess(ctx context.Context, containerID, processID string, signal int) error |
| 39 | 39 |
} |
| 40 | 40 |
|
| ... | ... |
@@ -87,7 +88,7 @@ func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteClo |
| 87 | 87 |
logrus.WithError(err2).WithField("id", id).Warn("Received an error while attempting to read plugin status")
|
| 88 | 88 |
} |
| 89 | 89 |
} else {
|
| 90 |
- if status != libcontainerd.StatusRunning && status != libcontainerd.StatusUnknown {
|
|
| 90 |
+ if status != libcontainerdtypes.StatusRunning && status != libcontainerdtypes.StatusUnknown {
|
|
| 91 | 91 |
if err2 := e.client.Delete(ctx, id); err2 != nil && !errdefs.IsNotFound(err2) {
|
| 92 | 92 |
logrus.WithError(err2).WithField("plugin", id).Error("Error cleaning up containerd container")
|
| 93 | 93 |
} |
| ... | ... |
@@ -122,19 +123,19 @@ func (e *Executor) Restore(id string, stdout, stderr io.WriteCloser) (bool, erro |
| 122 | 122 |
// IsRunning returns if the container with the given id is running |
| 123 | 123 |
func (e *Executor) IsRunning(id string) (bool, error) {
|
| 124 | 124 |
status, err := e.client.Status(context.Background(), id) |
| 125 |
- return status == libcontainerd.StatusRunning, err |
|
| 125 |
+ return status == libcontainerdtypes.StatusRunning, err |
|
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 | 128 |
// Signal sends the specified signal to the container |
| 129 | 129 |
func (e *Executor) Signal(id string, signal int) error {
|
| 130 |
- return e.client.SignalProcess(context.Background(), id, libcontainerd.InitProcessName, signal) |
|
| 130 |
+ return e.client.SignalProcess(context.Background(), id, libcontainerdtypes.InitProcessName, signal) |
|
| 131 | 131 |
} |
| 132 | 132 |
|
| 133 | 133 |
// ProcessEvent handles events from containerd |
| 134 | 134 |
// All events are ignored except the exit event, which is sent of to the stored handler |
| 135 |
-func (e *Executor) ProcessEvent(id string, et libcontainerd.EventType, ei libcontainerd.EventInfo) error {
|
|
| 135 |
+func (e *Executor) ProcessEvent(id string, et libcontainerdtypes.EventType, ei libcontainerdtypes.EventInfo) error {
|
|
| 136 | 136 |
switch et {
|
| 137 |
- case libcontainerd.EventExit: |
|
| 137 |
+ case libcontainerdtypes.EventExit: |
|
| 138 | 138 |
deleteTaskAndContainer(context.Background(), e.client, id) |
| 139 | 139 |
return e.exitHandler.HandleExitEvent(ei.ContainerID) |
| 140 | 140 |
} |
| ... | ... |
@@ -152,7 +153,7 @@ func (c *rio) Wait() {
|
| 152 | 152 |
c.IO.Wait() |
| 153 | 153 |
} |
| 154 | 154 |
|
| 155 |
-func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerd.StdioCallback {
|
|
| 155 |
+func attachStreamsFunc(stdout, stderr io.WriteCloser) libcontainerdtypes.StdioCallback {
|
|
| 156 | 156 |
return func(iop *cio.DirectIO) (cio.IO, error) {
|
| 157 | 157 |
if iop.Stdin != nil {
|
| 158 | 158 |
iop.Stdin.Close() |
| ... | ... |
@@ -8,7 +8,7 @@ import ( |
| 8 | 8 |
"testing" |
| 9 | 9 |
"time" |
| 10 | 10 |
|
| 11 |
- "github.com/docker/docker/libcontainerd" |
|
| 11 |
+ libcontainerdtypes "github.com/docker/docker/libcontainerd/types" |
|
| 12 | 12 |
"github.com/opencontainers/runtime-spec/specs-go" |
| 13 | 13 |
"github.com/pkg/errors" |
| 14 | 14 |
"gotest.tools/assert" |
| ... | ... |
@@ -82,22 +82,22 @@ func (c *mockClient) Create(ctx context.Context, id string, _ *specs.Spec, _ int |
| 82 | 82 |
return nil |
| 83 | 83 |
} |
| 84 | 84 |
|
| 85 |
-func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerd.StdioCallback) (alive bool, pid int, err error) {
|
|
| 85 |
+func (c *mockClient) Restore(ctx context.Context, id string, attachStdio libcontainerdtypes.StdioCallback) (alive bool, pid int, err error) {
|
|
| 86 | 86 |
return false, 0, nil |
| 87 | 87 |
} |
| 88 | 88 |
|
| 89 |
-func (c *mockClient) Status(ctx context.Context, id string) (libcontainerd.Status, error) {
|
|
| 89 |
+func (c *mockClient) Status(ctx context.Context, id string) (libcontainerdtypes.Status, error) {
|
|
| 90 | 90 |
c.mu.Lock() |
| 91 | 91 |
defer c.mu.Unlock() |
| 92 | 92 |
|
| 93 | 93 |
running, ok := c.containers[id] |
| 94 | 94 |
if !ok {
|
| 95 |
- return libcontainerd.StatusUnknown, errors.New("not found")
|
|
| 95 |
+ return libcontainerdtypes.StatusUnknown, errors.New("not found")
|
|
| 96 | 96 |
} |
| 97 | 97 |
if running {
|
| 98 |
- return libcontainerd.StatusRunning, nil |
|
| 98 |
+ return libcontainerdtypes.StatusRunning, nil |
|
| 99 | 99 |
} |
| 100 |
- return libcontainerd.StatusStopped, nil |
|
| 100 |
+ return libcontainerdtypes.StatusStopped, nil |
|
| 101 | 101 |
} |
| 102 | 102 |
|
| 103 | 103 |
func (c *mockClient) Delete(ctx context.Context, id string) error {
|
| ... | ... |
@@ -111,7 +111,7 @@ func (c *mockClient) DeleteTask(ctx context.Context, id string) (uint32, time.Ti |
| 111 | 111 |
return 0, time.Time{}, nil
|
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
-func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerd.StdioCallback) (pid int, err error) {
|
|
| 114 |
+func (c *mockClient) Start(ctx context.Context, id, checkpointDir string, withStdin bool, attachStdio libcontainerdtypes.StdioCallback) (pid int, err error) {
|
|
| 115 | 115 |
c.mu.Lock() |
| 116 | 116 |
defer c.mu.Unlock() |
| 117 | 117 |
|