aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/container_api.go38
-rw-r--r--libpod/container_easyjson.go13
-rw-r--r--libpod/container_internal.go4
-rw-r--r--libpod/container_internal_linux.go6
-rw-r--r--libpod/oci.go16
-rw-r--r--libpod/oci_linux.go6
-rw-r--r--libpod/oci_unsupported.go2
-rw-r--r--libpod/options.go4
-rw-r--r--libpod/runtime_pod_infra_linux.go6
-rw-r--r--libpod/util.go44
11 files changed, 108 insertions, 34 deletions
diff --git a/libpod/container.go b/libpod/container.go
index 16f61d021..a8a58f4d8 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -9,6 +9,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -296,6 +297,8 @@ type ContainerConfig struct {
HostAdd []string `json:"hostsAdd,omitempty"`
// Network names (CNI) to add container to. Empty to use default network.
Networks []string `json:"networks,omitempty"`
+ // Network mode specified for the default network.
+ NetMode namespaces.NetworkMode `json:"networkMode,omitempty"`
// Image Config
diff --git a/libpod/container_api.go b/libpod/container_api.go
index df6b6e962..4789c0cd2 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -328,6 +328,11 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
if err != nil {
return errors.Wrapf(err, "error exec %s", c.ID())
}
+ chWait := make(chan error)
+ go func() {
+ chWait <- execCmd.Wait()
+ }()
+ defer close(chWait)
pidFile := c.execPidPath(sessionID)
// 60 second seems a reasonable time to wait
@@ -336,18 +341,12 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
const pidWaitTimeout = 60000
// Wait until the runtime makes the pidfile
- // TODO: If runtime errors before the PID file is created, we have to
- // wait for timeout here
- if err := WaitForFile(pidFile, pidWaitTimeout*time.Millisecond); err != nil {
- logrus.Debugf("Timed out waiting for pidfile from runtime for container %s exec", c.ID())
-
- // Check if an error occurred in the process before we made a pidfile
- // TODO: Wait() here is a poor choice - is there a way to see if
- // a process has finished, instead of waiting for it to finish?
- if err := execCmd.Wait(); err != nil {
+ exited, err := WaitForFile(pidFile, chWait, pidWaitTimeout*time.Millisecond)
+ if err != nil {
+ if exited {
+ // If the runtime exited, propagate the error we got from the process.
return err
}
-
return errors.Wrapf(err, "timed out waiting for runtime to create pidfile for exec session in container %s", c.ID())
}
@@ -389,7 +388,10 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e
locked = false
}
- waitErr := execCmd.Wait()
+ var waitErr error
+ if !exited {
+ waitErr = <-chWait
+ }
// Lock again
if !c.batched {
@@ -831,10 +833,16 @@ func (c *Container) Refresh(ctx context.Context) error {
}
// ContainerCheckpointOptions is a struct used to pass the parameters
-// for checkpointing to corresponding functions
+// for checkpointing (and restoring) to the corresponding functions
type ContainerCheckpointOptions struct {
- Keep bool
+ // Keep tells the API to not delete checkpoint artifacts
+ Keep bool
+ // KeepRunning tells the API to keep the container running
+ // after writing the checkpoint to disk
KeepRunning bool
+ // TCPEstablished tells the API to checkpoint a container
+ // even if it contains established TCP connections
+ TCPEstablished bool
}
// Checkpoint checkpoints a container
@@ -853,7 +861,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO
}
// Restore restores a container
-func (c *Container) Restore(ctx context.Context, keep bool) (err error) {
+func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) {
logrus.Debugf("Trying to restore container %s", c)
if !c.batched {
c.lock.Lock()
@@ -864,5 +872,5 @@ func (c *Container) Restore(ctx context.Context, keep bool) (err error) {
}
}
- return c.restore(ctx, keep)
+ return c.restore(ctx, options)
}
diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go
index 041cc08ac..8bf5cb64f 100644
--- a/libpod/container_easyjson.go
+++ b/libpod/container_easyjson.go
@@ -8,6 +8,7 @@ import (
json "encoding/json"
types "github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/current"
+ namespaces "github.com/containers/libpod/pkg/namespaces"
storage "github.com/containers/storage"
idtools "github.com/containers/storage/pkg/idtools"
ocicni "github.com/cri-o/ocicni/pkg/ocicni"
@@ -1550,6 +1551,8 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou
}
in.Delim(']')
}
+ case "networkMode":
+ out.NetMode = namespaces.NetworkMode(in.String())
case "userVolumes":
if in.IsNull() {
in.Skip()
@@ -2177,6 +2180,16 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer,
out.RawByte(']')
}
}
+ if in.NetMode != "" {
+ const prefix string = ",\"networkMode\":"
+ if first {
+ first = false
+ out.RawString(prefix[1:])
+ } else {
+ out.RawString(prefix)
+ }
+ out.String(string(in.NetMode))
+ }
if len(in.UserVolumes) != 0 {
const prefix string = ",\"userVolumes\":"
if first {
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 051e0aeb7..700773e7f 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -586,7 +586,7 @@ func (c *Container) completeNetworkSetup() error {
if err := c.syncContainer(); err != nil {
return err
}
- if rootless.IsRootless() {
+ if c.config.NetMode == "slirp4netns" {
return c.runtime.setupRootlessNetNS(c)
}
return c.runtime.setupNetNS(c)
@@ -606,7 +606,7 @@ func (c *Container) init(ctx context.Context) error {
}
// With the spec complete, do an OCI create
- if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, false); err != nil {
+ if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, nil); err != nil {
return err
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 677645e7d..6b21bc3d6 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -514,7 +514,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
return c.save()
}
-func (c *Container) restore(ctx context.Context, keep bool) (err error) {
+func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (err error) {
if !criu.CheckForCriu() {
return errors.Errorf("restoring a container requires at least CRIU %d", criu.MinCriuVersion)
@@ -602,7 +602,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) {
// Cleanup for a working restore.
c.removeConmonFiles()
- if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, true); err != nil {
+ if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, &options); err != nil {
return err
}
@@ -610,7 +610,7 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) {
c.state.State = ContainerStateRunning
- if !keep {
+ if !options.Keep {
// Delete all checkpoint related files. At this point, in theory, all files
// should exist. Still ignoring errors for now as the container should be
// restored and running. Not erroring out just because some cleanup operation
diff --git a/libpod/oci.go b/libpod/oci.go
index a7aec06e5..a8013aa47 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -227,7 +227,7 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
return files, nil
}
-func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
+func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
var stderrBuf bytes.Buffer
runtimeDir, err := util.GetRootlessRuntimeDir()
@@ -289,8 +289,11 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
args = append(args, "--syslog")
}
- if restoreContainer {
+ if restoreOptions != nil {
args = append(args, "--restore", ctr.CheckpointPath())
+ if restoreOptions.TCPEstablished {
+ args = append(args, "--restore-arg", "--tcp-established")
+ }
}
logrus.WithFields(logrus.Fields{
@@ -316,6 +319,10 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_USERNS_CONFIGURED=%s", os.Getenv("_LIBPOD_USERNS_CONFIGURED")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_ROOTLESS_UID=%s", os.Getenv("_LIBPOD_ROOTLESS_UID")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME")))
+ cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
if r.reservePorts {
ports, err := bindPorts(ctr.config.PortMappings)
@@ -329,7 +336,7 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res
cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
}
- if rootless.IsRootless() {
+ if ctr.config.NetMode.IsSlirp4netns() {
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to create rootless network sync pipe")
@@ -862,6 +869,9 @@ func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckp
if options.KeepRunning {
args = append(args, "--leave-running")
}
+ if options.TCPEstablished {
+ args = append(args, "--tcp-established")
+ }
args = append(args, ctr.ID())
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...)
}
diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go
index b159eae78..2737a641e 100644
--- a/libpod/oci_linux.go
+++ b/libpod/oci_linux.go
@@ -65,10 +65,10 @@ func newPipe() (parent *os.File, child *os.File, err error) {
// CreateContainer creates a container in the OCI runtime
// TODO terminal support for container
// Presently just ignoring conmon opts related to it
-func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
+func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
if ctr.state.UserNSRoot == "" {
// no need of an intermediate mount ns
- return r.createOCIContainer(ctr, cgroupParent, restoreContainer)
+ return r.createOCIContainer(ctr, cgroupParent, restoreOptions)
}
var wg sync.WaitGroup
wg.Add(1)
@@ -106,7 +106,7 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor
if err != nil {
return
}
- err = r.createOCIContainer(ctr, cgroupParent, restoreContainer)
+ err = r.createOCIContainer(ctr, cgroupParent, restoreOptions)
}()
wg.Wait()
diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go
index b133eb402..8c084d1e2 100644
--- a/libpod/oci_unsupported.go
+++ b/libpod/oci_unsupported.go
@@ -15,7 +15,7 @@ func newPipe() (parent *os.File, child *os.File, err error) {
return nil, nil, ErrNotImplemented
}
-func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
+func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreOptions *ContainerCheckpointOptions) (err error) {
return ErrNotImplemented
}
diff --git a/libpod/options.go b/libpod/options.go
index 507847d65..7f4e3ac6b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -7,6 +7,7 @@ import (
"regexp"
"syscall"
+ "github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
@@ -817,7 +818,7 @@ func WithDependencyCtrs(ctrs []*Container) CtrCreateOption {
// namespace with a minimal configuration.
// An optional array of port mappings can be provided.
// Conflicts with WithNetNSFrom().
-func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, networks []string) CtrCreateOption {
+func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmode string, networks []string) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return ErrCtrFinalized
@@ -831,6 +832,7 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netwo
ctr.config.CreateNetNS = true
ctr.config.PortMappings = portMappings
ctr.config.Networks = networks
+ ctr.config.NetMode = namespaces.NetworkMode(netmode)
return nil
}
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index 450a2fb32..8a5dbef56 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -50,7 +50,11 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
// Since user namespace sharing is not implemented, we only need to check if it's rootless
networks := make([]string, 0)
- options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks))
+ netmode := "bridge"
+ if isRootless {
+ netmode = "slirp4netns"
+ }
+ options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, netmode, networks))
return r.newContainer(ctx, g.Config, options...)
}
diff --git a/libpod/util.go b/libpod/util.go
index 7007b29cd..aa3494529 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -13,6 +13,7 @@ import (
"github.com/containers/image/signature"
"github.com/containers/image/types"
"github.com/containers/libpod/pkg/util"
+ "github.com/fsnotify/fsnotify"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@@ -90,31 +91,64 @@ func MountExists(specMounts []spec.Mount, dest string) bool {
}
// WaitForFile waits until a file has been created or the given timeout has occurred
-func WaitForFile(path string, timeout time.Duration) error {
+func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, error) {
done := make(chan struct{})
chControl := make(chan struct{})
+
+ var inotifyEvents chan fsnotify.Event
+ var timer chan struct{}
+ watcher, err := fsnotify.NewWatcher()
+ if err == nil {
+ if err := watcher.Add(filepath.Dir(path)); err == nil {
+ inotifyEvents = watcher.Events
+ }
+ defer watcher.Close()
+ }
+ if inotifyEvents == nil {
+ // If for any reason we fail to create the inotify
+ // watcher, fallback to polling the file
+ timer = make(chan struct{})
+ go func() {
+ select {
+ case <-chControl:
+ close(timer)
+ return
+ default:
+ time.Sleep(25 * time.Millisecond)
+ timer <- struct{}{}
+ }
+ }()
+ }
+
go func() {
for {
select {
case <-chControl:
return
- default:
+ case <-timer:
+ _, err := os.Stat(path)
+ if err == nil {
+ close(done)
+ return
+ }
+ case <-inotifyEvents:
_, err := os.Stat(path)
if err == nil {
close(done)
return
}
- time.Sleep(25 * time.Millisecond)
}
}
}()
select {
+ case e := <-chWait:
+ return true, e
case <-done:
- return nil
+ return false, nil
case <-time.After(timeout):
close(chControl)
- return errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
+ return false, errors.Wrapf(ErrInternal, "timed out waiting for file %s", path)
}
}