summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml4
-rw-r--r--Dockerfile5
-rw-r--r--Dockerfile.CentOS2
-rw-r--r--Dockerfile.Fedora2
-rw-r--r--cmd/podman/checkpoint.go9
-rw-r--r--cmd/podman/commit.go2
-rw-r--r--cmd/podman/common.go10
-rw-r--r--cmd/podman/images.go4
-rw-r--r--cmd/podman/import.go2
-rw-r--r--cmd/podman/inspect.go6
-rw-r--r--cmd/podman/restore.go13
-rw-r--r--cmd/podman/stop.go7
-rw-r--r--completions/bash/podman30
-rw-r--r--contrib/python/podman/podman/libs/containers.py35
-rw-r--r--contrib/python/podman/test/test_containers.py2
-rw-r--r--contrib/python/pypodman/pypodman/lib/actions/commit_action.py18
-rw-r--r--docs/podman-container-checkpoint.1.md7
-rw-r--r--docs/podman-container-restore.1.md8
-rw-r--r--docs/podman-create.1.md3
-rw-r--r--docs/podman-run.1.md3
-rw-r--r--libpod/container.go7
-rw-r--r--libpod/container_api.go52
-rw-r--r--libpod/container_easyjson.go13
-rw-r--r--libpod/container_internal.go6
-rw-r--r--libpod/container_internal_linux.go13
-rw-r--r--libpod/kube.go2
-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/pod_internal.go2
-rw-r--r--libpod/runtime_pod_infra_linux.go6
-rw-r--r--libpod/util.go44
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/resolvconf/resolvconf.go12
-rw-r--r--pkg/spec/createconfig.go4
-rw-r--r--pkg/spec/spec.go3
-rw-r--r--pkg/util/utils.go32
-rw-r--r--pkg/varlinkapi/containers_create.go7
-rw-r--r--test/e2e/checkpoint_test.go161
-rw-r--r--test/e2e/libpod_suite_test.go6
-rw-r--r--test/e2e/rootless_test.go12
-rw-r--r--test/e2e/stop_test.go14
-rw-r--r--test/utils/podmantest_test.go4
-rw-r--r--test/utils/utils.go6
-rw-r--r--vendor.conf2
-rw-r--r--vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go6
47 files changed, 502 insertions, 119 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index c5b73fdc9..983ee1237 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -18,8 +18,8 @@ gce_instance:
env:
FEDORA_CNI_COMMIT: "412b6d31280682bb4fab4446f113c22ff1886554"
CNI_COMMIT: "7480240de9749f9a0a5c8614b17f1f03e0c06ab9"
- CRIO_COMMIT: "662dbb31b5d4f5ed54511a47cde7190c61c28677"
- CRIU_COMMIT: "584cbe4643c3fc7dc901ff08bf923ca0fe7326f9"
+ CRIO_COMMIT: "7a283c391abb7bd25086a8ff91dbb36ebdd24466"
+ CRIU_COMMIT: "c74b83cd49c00589c0c0468ba5fe685b67fdbd0a"
RUNC_COMMIT: "78ef28e63bec2ee4c139b5e3e0d691eb9bdc748d"
# File to update in home-dir with task-specific env. var values
ENVLIB: ".bash_profile"
diff --git a/Dockerfile b/Dockerfile
index 70d1a7629..3eb7b0a07 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -64,7 +64,7 @@ RUN set -x \
&& rm -rf "$GOPATH"
# Install conmon
-ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677
+ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \
@@ -112,8 +112,7 @@ RUN set -x \
&& go get -u github.com/mailru/easyjson/... \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
-# Install criu
-ENV CRIU_COMMIT 584cbe4643c3fc7dc901ff08bf923ca0fe7326f9
+# Install latest stable criu version
RUN set -x \
&& cd /tmp \
&& git clone https://github.com/checkpoint-restore/criu.git \
diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS
index 67b7ddce1..3e14e59db 100644
--- a/Dockerfile.CentOS
+++ b/Dockerfile.CentOS
@@ -68,7 +68,7 @@ RUN set -x \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
# Install conmon
-ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677
+ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \
diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora
index 38cd599d4..51bcaeaf0 100644
--- a/Dockerfile.Fedora
+++ b/Dockerfile.Fedora
@@ -72,7 +72,7 @@ RUN set -x \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
# Install conmon
-ENV CRIO_COMMIT 662dbb31b5d4f5ed54511a47cde7190c61c28677
+ENV CRIO_COMMIT 7a283c391abb7bd25086a8ff91dbb36ebdd24466
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/kubernetes-sigs/cri-o.git "$GOPATH/src/github.com/kubernetes-sigs/cri-o.git" \
diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go
index ddfd12bc3..824c97662 100644
--- a/cmd/podman/checkpoint.go
+++ b/cmd/podman/checkpoint.go
@@ -28,6 +28,10 @@ var (
Usage: "leave the container running after writing checkpoint to disk",
},
cli.BoolFlag{
+ Name: "tcp-established",
+ Usage: "checkpoint a container with established TCP connections",
+ },
+ cli.BoolFlag{
Name: "all, a",
Usage: "checkpoint all running containers",
},
@@ -55,8 +59,9 @@ func checkpointCmd(c *cli.Context) error {
defer runtime.Shutdown(false)
options := libpod.ContainerCheckpointOptions{
- Keep: c.Bool("keep"),
- KeepRunning: c.Bool("leave-running"),
+ Keep: c.Bool("keep"),
+ KeepRunning: c.Bool("leave-running"),
+ TCPEstablished: c.Bool("tcp-established"),
}
if err := checkAllAndLatest(c); err != nil {
diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go
index b09c6b0d9..02ede4f73 100644
--- a/cmd/podman/commit.go
+++ b/cmd/podman/commit.go
@@ -95,7 +95,7 @@ func commitCmd(c *cli.Context) error {
for _, change := range c.StringSlice("change") {
splitChange := strings.Split(strings.ToUpper(change), "=")
if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) {
- return errors.Errorf("invalid syntax for --change ", change)
+ return errors.Errorf("invalid syntax for --change: %s", change)
}
}
}
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index f9e746b28..c4016698a 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/buildah"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/fatih/camelcase"
"github.com/pkg/errors"
@@ -161,6 +162,13 @@ func getContext() context.Context {
return context.TODO()
}
+func getDefaultNetwork() string {
+ if rootless.IsRootless() {
+ return "slirp4netns"
+ }
+ return "bridge"
+}
+
// Common flags shared between commands
var createFlags = []cli.Flag{
cli.StringSliceFlag{
@@ -372,7 +380,7 @@ var createFlags = []cli.Flag{
cli.StringFlag{
Name: "net, network",
Usage: "Connect a container to a network",
- Value: "bridge",
+ Value: getDefaultNetwork(),
},
cli.BoolFlag{
Name: "oom-kill-disable",
diff --git a/cmd/podman/images.go b/cmd/podman/images.go
index c52b26260..3351123ed 100644
--- a/cmd/podman/images.go
+++ b/cmd/podman/images.go
@@ -376,13 +376,13 @@ func CreateFilterFuncs(ctx context.Context, r *libpod.Runtime, c *cli.Context, i
case "before":
before, err := r.ImageRuntime().NewFromLocal(splitFilter[1])
if err != nil {
- return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1])
+ return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
}
filterFuncs = append(filterFuncs, image.CreatedBeforeFilter(before.Created()))
case "after":
after, err := r.ImageRuntime().NewFromLocal(splitFilter[1])
if err != nil {
- return nil, errors.Wrapf(err, "unable to find image % in local stores", splitFilter[1])
+ return nil, errors.Wrapf(err, "unable to find image %s in local stores", splitFilter[1])
}
filterFuncs = append(filterFuncs, image.CreatedAfterFilter(after.Created()))
case "dangling":
diff --git a/cmd/podman/import.go b/cmd/podman/import.go
index be516e4fa..144354fa6 100644
--- a/cmd/podman/import.go
+++ b/cmd/podman/import.go
@@ -139,7 +139,7 @@ func downloadFromURL(source string) (string, error) {
_, err = io.Copy(outFile, response.Body)
if err != nil {
- return "", errors.Wrapf(err, "error saving %q to %q", source, outFile)
+ return "", errors.Wrapf(err, "error saving %s to %s", source, outFile.Name())
}
return outFile.Name(), nil
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index bd9e8c13c..6ffcde55f 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -119,7 +119,7 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l
}
libpodInspectData, err := ctr.Inspect(c.Bool("size"))
if err != nil {
- inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID)
+ inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID())
break
}
data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData)
@@ -154,12 +154,12 @@ func iterateInput(ctx context.Context, c *cli.Context, args []string, runtime *l
} else {
libpodInspectData, err := ctr.Inspect(c.Bool("size"))
if err != nil {
- inspectError = errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID)
+ inspectError = errors.Wrapf(err, "error getting libpod container inspect data %s", ctr.ID())
break
}
data, err = shared.GetCtrInspectInfo(ctr, libpodInspectData)
if err != nil {
- inspectError = errors.Wrapf(err, "error parsing container data %q", ctr.ID)
+ inspectError = errors.Wrapf(err, "error parsing container data %s", ctr.ID())
break
}
}
diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go
index 067a2b5d4..bc2a71ba0 100644
--- a/cmd/podman/restore.go
+++ b/cmd/podman/restore.go
@@ -27,6 +27,10 @@ var (
// dedicated state for container which are checkpointed.
// TODO: add ContainerStateCheckpointed
cli.BoolFlag{
+ Name: "tcp-established",
+ Usage: "checkpoint a container with established TCP connections",
+ },
+ cli.BoolFlag{
Name: "all, a",
Usage: "restore all checkpointed containers",
},
@@ -53,16 +57,19 @@ func restoreCmd(c *cli.Context) error {
}
defer runtime.Shutdown(false)
- keep := c.Bool("keep")
+ options := libpod.ContainerCheckpointOptions{
+ Keep: c.Bool("keep"),
+ TCPEstablished: c.Bool("tcp-established"),
+ }
if err := checkAllAndLatest(c); err != nil {
return err
}
- containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "checkpointed")
+ containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateExited, "checkpointed")
for _, ctr := range containers {
- if err = ctr.Restore(context.TODO(), keep); err != nil {
+ if err = ctr.Restore(context.TODO(), options); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go
index 04022839a..ade51705e 100644
--- a/cmd/podman/stop.go
+++ b/cmd/podman/stop.go
@@ -2,6 +2,7 @@ package main
import (
"fmt"
+
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
@@ -77,7 +78,11 @@ func stopCmd(c *cli.Context) error {
stopTimeout = ctr.StopTimeout()
}
f := func() error {
- return con.StopWithTimeout(stopTimeout)
+ if err := con.StopWithTimeout(stopTimeout); err != nil && errors.Cause(err) != libpod.ErrCtrStopped {
+ return err
+ }
+ return nil
+
}
stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{
ContainerID: con.ID(),
diff --git a/completions/bash/podman b/completions/bash/podman
index 3c6b6ec50..c34381e2b 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -716,16 +716,22 @@ _podman_container_attach() {
}
_podman_container_checkpoint() {
- local options_with_args="
- --help -h
- "
local boolean_options="
- --keep
+ -a
+ --all
+ -h
+ --help
-k
+ --keep
+ -l
+ --latest
+ -R
+ --leave-running
+ --tcp-established
"
case "$cur" in
-*)
- COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ COMPREPLY=($(compgen -W "$boolean_options" -- "$cur"))
;;
*)
__podman_complete_containers_running
@@ -794,16 +800,20 @@ _podman_container_restart() {
}
_podman_container_restore() {
- local options_with_args="
- --help -h
- "
local boolean_options="
- --keep
+ -a
+ --all
+ -h
+ --help
-k
+ --keep
+ -l
+ --latest
+ --tcp-established
"
case "$cur" in
-*)
- COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ COMPREPLY=($(compgen -W "$boolean_options" -- "$cur"))
;;
*)
__podman_complete_containers_created
diff --git a/contrib/python/podman/podman/libs/containers.py b/contrib/python/podman/podman/libs/containers.py
index 21a94557a..7adecea8f 100644
--- a/contrib/python/podman/podman/libs/containers.py
+++ b/contrib/python/podman/podman/libs/containers.py
@@ -108,19 +108,16 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
results = podman.ExportContainer(self._id, target)
return results['tarfile']
- def commit(self,
- image_name,
- *args,
- changes=[],
- message='',
- pause=True,
- **kwargs): # pylint: disable=unused-argument
+ def commit(self, image_name, **kwargs):
"""Create image from container.
- All changes overwrite existing values.
- See inspect() to obtain current settings.
+ Keyword arguments:
+ author -- change image's author
+ message -- change image's message, docker format only.
+ pause -- pause container during commit
+ change -- Additional properties to change
- Changes:
+ Change examples:
CMD=/usr/bin/zsh
ENTRYPOINT=/bin/sh date
ENV=TEST=test_containers.TestContainers.test_commit
@@ -129,21 +126,23 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
USER=bozo:circus
VOLUME=/data
WORKDIR=/data/application
+
+ All changes overwrite existing values.
+ See inspect() to obtain current settings.
"""
- # TODO: Clean up *args, **kwargs after Commit() is complete
- try:
- author = kwargs.get('author', getpass.getuser())
- except Exception: # pylint: disable=broad-except
- author = ''
+ author = kwargs.get('author', None) or getpass.getuser()
+ change = kwargs.get('change', None) or []
+ message = kwargs.get('message', None) or ''
+ pause = kwargs.get('pause', None) or True
- for c in changes:
+ for c in change:
if c.startswith('LABEL=') and c.count('=') < 2:
raise ValueError(
'LABEL should have the format: LABEL=label=value, not {}'.
format(c))
with self._client() as podman:
- results = podman.Commit(self._id, image_name, changes, author,
+ results = podman.Commit(self._id, image_name, change, author,
message, pause)
return results['image']
@@ -194,7 +193,7 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
podman.UnpauseContainer(self._id)
return self._refresh(podman)
- def update_container(self, *args, **kwargs): \
+ def update_container(self, *args, **kwargs): \
# pylint: disable=unused-argument
"""TODO: Update container..., return id on success."""
with self._client() as podman:
diff --git a/contrib/python/podman/test/test_containers.py b/contrib/python/podman/test/test_containers.py
index 3de1e54bc..a7a6ac304 100644
--- a/contrib/python/podman/test/test_containers.py
+++ b/contrib/python/podman/test/test_containers.py
@@ -152,7 +152,7 @@ class TestContainers(PodmanTestCase):
changes.append('WORKDIR=/data/application')
id = self.alpine_ctnr.commit(
- 'alpine3', author='Bozo the clown', changes=changes, pause=True)
+ 'alpine3', author='Bozo the clown', change=changes, pause=True)
img = self.pclient.images.get(id)
self.assertIsNotNone(img)
diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py
index 21665ad0b..21924e938 100644
--- a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py
+++ b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py
@@ -30,7 +30,8 @@ class Commit(AbstractActionBase):
choices=('oci', 'docker'),
default='oci',
type=str.lower,
- help='Set the format of the image manifest and metadata',
+ help='Set the format of the image manifest and metadata.'
+ ' (Ignored.)',
)
parser.add_argument(
'--iidfile',
@@ -40,7 +41,8 @@ class Commit(AbstractActionBase):
parser.add_argument(
'--message',
'-m',
- help='Set commit message for committed image',
+ help='Set commit message for committed image'
+ ' (Only on docker images.)',
)
parser.add_argument(
'--pause',
@@ -80,8 +82,16 @@ class Commit(AbstractActionBase):
flush=True)
return 1
else:
- ident = ctnr.commit(self.opts['image'][0], **self.opts)
- print(ident)
+ ident = ctnr.commit(
+ self.opts['image'][0],
+ change=self.opts.get('change', None),
+ message=self.opts.get('message', None),
+ pause=self.opts['pause'],
+ author=self.opts.get('author', None),
+ )
+
+ if not self.opts['quiet']:
+ print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md
index 6f454dfd1..94e52dc78 100644
--- a/docs/podman-container-checkpoint.1.md
+++ b/docs/podman-container-checkpoint.1.md
@@ -29,6 +29,13 @@ Instead of providing the container name or ID, checkpoint the last created conta
Leave the container running after checkpointing instead of stopping it.
+**--tcp-established**
+
+Checkpoint a container with established TCP connections. If the checkpoint
+image contains established TCP connections, this options is required during
+restore. Defaults to not checkpointing containers with established TCP
+connections.
+
## EXAMPLE
podman container checkpoint mywebserver
diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md
index 4dd5ea7c7..44219f3ef 100644
--- a/docs/podman-container-restore.1.md
+++ b/docs/podman-container-restore.1.md
@@ -32,6 +32,14 @@ Restore all checkpointed containers.
Instead of providing the container name or ID, restore the last created container.
+**--tcp-established**
+
+Restore a container with established TCP connections. If the checkpoint image
+contains established TCP connections, this option is required during restore.
+If the checkpoint image does not contain established TCP connections this
+option is ignored. Defaults to not restoring containers with established TCP
+connections.
+
## EXAMPLE
podman container restore mywebserver
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index 68c00685b..474796a35 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -426,7 +426,8 @@ Set the Network mode for the container
'container:<name|id>': reuse another container's network stack
'host': use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
'<network-name>|<network-id>': connect to a user-defined network
- 'ns:<path>' path to a network namespace to join
+ 'ns:<path>': path to a network namespace to join
+ 'slirp4netns': use slirp4netns to create a user network stack. This is the default for rootless containers
**--network-alias**=[]
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index 912026a55..202091b07 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -408,7 +408,8 @@ Set the Network mode for the container:
- `container:<name|id>`: reuse another container's network stack
- `host`: use the podman host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
- `<network-name>|<network-id>`: connect to a user-defined network
-- `ns:<path>` path to a network namespace to join
+- `ns:<path>`: path to a network namespace to join
+- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers
**--network-alias**=[]
diff --git a/libpod/container.go b/libpod/container.go
index 16f61d021..b6155a809 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
@@ -826,7 +829,7 @@ func (c *Container) IPs() ([]net.IPNet, error) {
}
if !c.config.CreateNetNS {
- return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod")
+ return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID())
}
ips := make([]net.IPNet, 0)
@@ -854,7 +857,7 @@ func (c *Container) Routes() ([]types.Route, error) {
}
if !c.config.CreateNetNS {
- return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod")
+ return nil, errors.Wrapf(ErrInvalidArg, "container %s network namespace is not managed by libpod", c.ID())
}
routes := make([]types.Route, 0)
diff --git a/libpod/container_api.go b/libpod/container_api.go
index df6b6e962..bc92cae69 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -39,7 +39,7 @@ func (c *Container) Init(ctx context.Context) (err error) {
notRunning, err := c.checkDependenciesRunning()
if err != nil {
- return errors.Wrapf(err, "error checking dependencies for container %s")
+ return errors.Wrapf(err, "error checking dependencies for container %s", c.ID())
}
if len(notRunning) > 0 {
depString := strings.Join(notRunning, ",")
@@ -93,7 +93,7 @@ func (c *Container) Start(ctx context.Context) (err error) {
notRunning, err := c.checkDependenciesRunning()
if err != nil {
- return errors.Wrapf(err, "error checking dependencies for container %s")
+ return errors.Wrapf(err, "error checking dependencies for container %s", c.ID())
}
if len(notRunning) > 0 {
depString := strings.Join(notRunning, ",")
@@ -159,7 +159,7 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams,
notRunning, err := c.checkDependenciesRunning()
if err != nil {
- return nil, errors.Wrapf(err, "error checking dependencies for container %s")
+ return nil, errors.Wrapf(err, "error checking dependencies for container %s", c.ID())
}
if len(notRunning) > 0 {
depString := strings.Join(notRunning, ",")
@@ -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 {
@@ -716,7 +718,7 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e
notRunning, err := c.checkDependenciesRunning()
if err != nil {
- return errors.Wrapf(err, "error checking dependencies for container %s")
+ return errors.Wrapf(err, "error checking dependencies for container %s", c.ID())
}
if len(notRunning) > 0 {
depString := strings.Join(notRunning, ",")
@@ -801,7 +803,7 @@ func (c *Container) Refresh(ctx context.Context) error {
return err
}
- logrus.Debugf("Successfully refresh container %s state")
+ logrus.Debugf("Successfully refresh container %s state", c.ID())
// Initialize the container if it was created in runc
if wasCreated || wasRunning || wasPaused {
@@ -831,15 +833,21 @@ 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
func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
- logrus.Debugf("Trying to checkpoint container %s", c)
+ logrus.Debugf("Trying to checkpoint container %s", c.ID())
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -853,8 +861,8 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO
}
// Restore restores a container
-func (c *Container) Restore(ctx context.Context, keep bool) (err error) {
- logrus.Debugf("Trying to restore container %s", c)
+func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (err error) {
+ logrus.Debugf("Trying to restore container %s", c.ID())
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@@ -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..b616e0a07 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
}
@@ -1191,7 +1191,7 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten
if c.runtime.config.HooksDirNotExistFatal || !os.IsNotExist(err) {
return nil, err
}
- logrus.Warnf("failed to load hooks: {}", err)
+ logrus.Warnf("failed to load hooks: %q", err)
return nil, nil
}
hooks, err := manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0)
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index ffb82cc94..8861d7728 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
@@ -729,9 +729,10 @@ func (c *Container) generateResolvConf() (string, error) {
return "", errors.Wrapf(err, "unable to read %s", resolvPath)
}
- // Process the file to remove localhost nameservers
+ // Ensure that the container's /etc/resolv.conf is compatible with its
+ // network configuration.
// TODO: set ipv6 enable bool more sanely
- resolv, err := resolvconf.FilterResolvDNS(contents, true)
+ resolv, err := resolvconf.FilterResolvDNS(contents, true, c.config.CreateNetNS)
if err != nil {
return "", errors.Wrapf(err, "error parsing host resolv.conf")
}
@@ -764,7 +765,7 @@ func (c *Container) generateResolvConf() (string, error) {
// Build resolv.conf
if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
- return "", errors.Wrapf(err, "error building resolv.conf for container %s")
+ return "", errors.Wrapf(err, "error building resolv.conf for container %s", c.ID())
}
// Relabel resolv.conf for the container
diff --git a/libpod/kube.go b/libpod/kube.go
index 00db0033b..1a5f80878 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -240,7 +240,7 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) {
if c.User() != "" {
// It is *possible* that
- logrus.Debug("Looking in container for user: %s", c.User())
+ logrus.Debugf("Looking in container for user: %s", c.User())
u, err := lookup.GetUser(c.state.Mountpoint, c.User())
if err != nil {
return nil, err
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/pod_internal.go b/libpod/pod_internal.go
index 46162c7ef..39a25c004 100644
--- a/libpod/pod_internal.go
+++ b/libpod/pod_internal.go
@@ -48,7 +48,7 @@ func (p *Pod) updatePod() error {
// Save pod state to database
func (p *Pod) save() error {
if err := p.runtime.state.SavePod(p); err != nil {
- return errors.Wrapf(err, "error saving pod %s state")
+ return errors.Wrapf(err, "error saving pod %s state", p.ID())
}
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)
}
}
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index bee833fa9..832efd554 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -223,7 +223,12 @@ func (n NetworkMode) IsBridge() bool {
return n == "bridge"
}
+// IsSlirp4netns indicates if we are running a rootless network stack
+func (n NetworkMode) IsSlirp4netns() bool {
+ return n == "slirp4netns"
+}
+
// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
- return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
+ return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns()
}
diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go
index fccd60093..e85bcb377 100644
--- a/pkg/resolvconf/resolvconf.go
+++ b/pkg/resolvconf/resolvconf.go
@@ -103,13 +103,21 @@ func GetLastModified() *File {
}
// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs:
-// 1. It looks for localhost (127.*|::1) entries in the provided
+// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided
// resolv.conf, removing local nameserver entries, and, if the resulting
// cleaned config has no defined nameservers left, adds default DNS entries
// 2. Given the caller provides the enable/disable state of IPv6, the filter
// code will remove all IPv6 nameservers if it is not enabled for containers
//
-func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
+func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) {
+ // If we're using the host netns, we have nothing to do besides hash the file.
+ if !netnsEnabled {
+ hash, err := ioutils.HashData(bytes.NewReader(resolvConf))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: resolvConf, Hash: hash}, nil
+ }
cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
// if IPv6 is not enabled, also clean out any IPv6 address nameserver
if !ipv6Enabled {
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 6a0642ee7..a0fd40318 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -391,11 +391,11 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
options = append(options, libpod.WithNetNSFrom(connectedCtr))
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
isRootless := rootless.IsRootless()
- postConfigureNetNS := isRootless || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
+ postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
if isRootless && len(portBindings) > 0 {
return nil, errors.New("port bindings are not yet supported by rootless containers")
}
- options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, networks))
+ options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
}
if c.PidMode.IsContainer() {
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index b1cca2c9e..05be00864 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -453,6 +453,9 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error {
} else if IsPod(string(netMode)) {
logrus.Debug("Using pod netmode, unless pod is not sharing")
return nil
+ } else if netMode.IsSlirp4netns() {
+ logrus.Debug("Using slirp4netns netmode")
+ return nil
} else if netMode.IsUserDefined() {
logrus.Debug("Using user defined netmode")
return nil
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index c5ba38b9f..de29bc5d8 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -284,6 +284,35 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) {
return opts, nil
}
+type tomlOptionsConfig struct {
+ MountProgram string `toml:"mount_program"`
+}
+
+type tomlConfig struct {
+ Storage struct {
+ Driver string `toml:"driver"`
+ RunRoot string `toml:"runroot"`
+ GraphRoot string `toml:"graphroot"`
+ Options struct{ tomlOptionsConfig } `toml:"options"`
+ } `toml:"storage"`
+}
+
+func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig {
+ config := new(tomlConfig)
+
+ config.Storage.Driver = storeOptions.GraphDriverName
+ config.Storage.RunRoot = storeOptions.RunRoot
+ config.Storage.GraphRoot = storeOptions.GraphRoot
+ for _, i := range storeOptions.GraphDriverOptions {
+ s := strings.Split(i, "=")
+ if s[0] == "overlay.mount_program" {
+ config.Storage.Options.MountProgram = s[1]
+ }
+ }
+
+ return config
+}
+
// GetDefaultStoreOptions returns the storage ops for containers
func GetDefaultStoreOptions() (storage.StoreOptions, error) {
storageOpts := storage.DefaultStoreOptions
@@ -304,9 +333,10 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) {
return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf)
}
+ tomlConfiguration := getTomlStorage(&storageOpts)
defer file.Close()
enc := toml.NewEncoder(file)
- if err := enc.Encode(storageOpts); err != nil {
+ if err := enc.Encode(tomlConfiguration); err != nil {
os.Remove(storageConf)
}
}
diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go
index ca1a57048..f9a2db9c8 100644
--- a/pkg/varlinkapi/containers_create.go
+++ b/pkg/varlinkapi/containers_create.go
@@ -13,6 +13,7 @@ import (
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/rootless"
cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/pkg/signal"
@@ -126,7 +127,11 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru
// NETWORK MODE
networkMode := create.Net_mode
if networkMode == "" {
- networkMode = "bridge"
+ if rootless.IsRootless() {
+ networkMode = "slirp4netns"
+ } else {
+ networkMode = "bridge"
+ }
}
// WORKING DIR
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index 1e4f1eeac..4e892d11c 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -2,6 +2,7 @@ package integration
import (
"fmt"
+ "net"
"os"
"github.com/containers/libpod/pkg/criu"
@@ -126,4 +127,164 @@ var _ = Describe("Podman checkpoint", func() {
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
})
+
+ It("podman checkpoint latest running container", func() {
+ session1 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "first", "-d", ALPINE, "top"})
+ session1.WaitWithDefaultTimeout()
+ Expect(session1.ExitCode()).To(Equal(0))
+
+ session2 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "second", "-d", ALPINE, "top"})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"container", "checkpoint", "-l"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+
+ ps := podmanTest.Podman([]string{"ps", "-q", "--no-trunc"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeTrue())
+ Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse())
+
+ result = podmanTest.Podman([]string{"container", "restore", "-l"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+ Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited")))
+
+ result = podmanTest.Podman([]string{"rm", "-fa"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ })
+
+ It("podman checkpoint all running container", func() {
+ session1 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "first", "-d", ALPINE, "top"})
+ session1.WaitWithDefaultTimeout()
+ Expect(session1.ExitCode()).To(Equal(0))
+
+ session2 := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "second", "-d", ALPINE, "top"})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"container", "checkpoint", "-a"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+
+ ps := podmanTest.Podman([]string{"ps", "-q", "--no-trunc"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+ Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse())
+ Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse())
+
+ result = podmanTest.Podman([]string{"container", "restore", "-a"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+ Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited")))
+
+ result = podmanTest.Podman([]string{"rm", "-fa"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ })
+
+ It("podman checkpoint container with established tcp connections", func() {
+ Skip("Seems to not work (yet) in CI")
+ podmanTest.RestoreArtifact(redis)
+ session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--network", "host", "-d", redis})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // Open a network connection to the redis server
+ conn, err := net.Dial("tcp", "127.0.0.1:6379")
+ if err != nil {
+ os.Exit(1)
+ }
+ // This should fail as the container has established TCP connections
+ result := podmanTest.Podman([]string{"container", "checkpoint", "-l"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(125))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ // Now it should work thanks to "--tcp-established"
+ result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "--tcp-established"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ // Restore should fail as the checkpoint image contains established TCP connections
+ result = podmanTest.Podman([]string{"container", "restore", "-l"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(125))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ // Now it should work thanks to "--tcp-established"
+ result = podmanTest.Podman([]string{"container", "restore", "-l", "--tcp-established"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ result = podmanTest.Podman([]string{"rm", "-fa"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+
+ conn.Close()
+ })
+
+ It("podman checkpoint with --leave-running", func() {
+ session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+
+ // Checkpoint container, but leave it running
+ result := podmanTest.Podman([]string{"container", "checkpoint", "--leave-running", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ // Make sure it is still running
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ // Stop the container
+ result = podmanTest.Podman([]string{"container", "stop", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ // Restore the stopped container from the previous checkpoint
+ result = podmanTest.Podman([]string{"container", "restore", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+
+ result = podmanTest.Podman([]string{"rm", "-fa"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ })
+
})
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index 52507c083..f4f154ef2 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -181,6 +181,12 @@ func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration
return &PodmanSessionIntegration{podmanSession}
}
+// PodmanAsUser is the exec call to podman on the filesystem with the specified uid/gid and environment
+func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSessionIntegration {
+ podmanSession := p.PodmanAsUserBase(args, uid, gid, env)
+ return &PodmanSessionIntegration{podmanSession}
+}
+
// PodmanPID execs podman and returns its PID
func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) {
podmanOptions := p.MakeOptions(args)
diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go
index 995744ae5..c75910296 100644
--- a/test/e2e/rootless_test.go
+++ b/test/e2e/rootless_test.go
@@ -205,6 +205,10 @@ var _ = Describe("Podman rootless", func() {
cmd.WaitWithDefaultTimeout()
Expect(cmd.ExitCode()).To(Equal(0))
+ cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l", "--type", "container", "--format", "{{ .State.Status }}"}, 1000, 1000, env)
+ cmd.WaitWithDefaultTimeout()
+ Expect(cmd.LineInOutputContains("exited")).To(BeTrue())
+
cmd = rootlessTest.PodmanAsUser([]string{"start", "-l"}, 1000, 1000, env)
cmd.WaitWithDefaultTimeout()
Expect(cmd.ExitCode()).To(Equal(0))
@@ -217,6 +221,14 @@ var _ = Describe("Podman rootless", func() {
cmd.WaitWithDefaultTimeout()
Expect(cmd.ExitCode()).To(Equal(0))
+ if len(args) == 0 {
+ cmd = rootlessTest.PodmanAsUser([]string{"inspect", "-l"}, 1000, 1000, env)
+ cmd.WaitWithDefaultTimeout()
+ Expect(cmd.ExitCode()).To(Equal(0))
+ data := cmd.InspectContainerToJSON()
+ Expect(data[0].HostConfig.NetworkMode).To(ContainSubstring("slirp4netns"))
+ }
+
if !canUseExec {
Skip("ioctl(NS_GET_PARENT) not supported.")
}
diff --git a/test/e2e/stop_test.go b/test/e2e/stop_test.go
index b172cd81e..5c229b9b4 100644
--- a/test/e2e/stop_test.go
+++ b/test/e2e/stop_test.go
@@ -57,6 +57,20 @@ var _ = Describe("Podman stop", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman stop stopped container", func() {
+ session := podmanTest.RunTopContainer("test1")
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session2 := podmanTest.Podman([]string{"stop", "test1"})
+ session2.WaitWithDefaultTimeout()
+ Expect(session2.ExitCode()).To(Equal(0))
+
+ session3 := podmanTest.Podman([]string{"stop", "test1"})
+ session3.WaitWithDefaultTimeout()
+ Expect(session3.ExitCode()).To(Equal(0))
+ })
+
It("podman stop all containers", func() {
session := podmanTest.RunTopContainer("test1")
session.WaitWithDefaultTimeout()
diff --git a/test/utils/podmantest_test.go b/test/utils/podmantest_test.go
index 87f756920..60e3e2a97 100644
--- a/test/utils/podmantest_test.go
+++ b/test/utils/podmantest_test.go
@@ -19,11 +19,11 @@ var _ = Describe("PodmanTest test", func() {
FakeOutputs = make(map[string][]string)
})
- It("Test PodmanAsUser", func() {
+ It("Test PodmanAsUserBase", func() {
FakeOutputs["check"] = []string{"check"}
os.Setenv("HOOK_OPTION", "hook_option")
env := os.Environ()
- session := podmanTest.PodmanAsUser([]string{"check"}, 1000, 1000, env)
+ session := podmanTest.PodmanAsUserBase([]string{"check"}, 1000, 1000, env)
os.Unsetenv("HOOK_OPTION")
session.WaitWithDefaultTimeout()
Expect(session.Command.Process).ShouldNot(BeNil())
diff --git a/test/utils/utils.go b/test/utils/utils.go
index c9409c9d4..288c768d4 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -56,9 +56,9 @@ func (p *PodmanTest) MakeOptions(args []string) []string {
return p.PodmanMakeOptions(args)
}
-// PodmanAsUser exec podman as user. uid and gid is set for credentials useage. env is used
+// PodmanAsUserBase exec podman as user. uid and gid is set for credentials useage. env is used
// to record the env for debugging
-func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string) *PodmanSession {
+func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, env []string) *PodmanSession {
var command *exec.Cmd
podmanOptions := p.MakeOptions(args)
@@ -86,7 +86,7 @@ func (p *PodmanTest) PodmanAsUser(args []string, uid, gid uint32, env []string)
// PodmanBase exec podman with default env.
func (p *PodmanTest) PodmanBase(args []string) *PodmanSession {
- return p.PodmanAsUser(args, 0, 0, nil)
+ return p.PodmanAsUserBase(args, 0, 0, nil)
}
// WaitForContainer waits on a started container
diff --git a/vendor.conf b/vendor.conf
index 0c05e792c..f9fccfdfb 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -51,7 +51,7 @@ github.com/opencontainers/image-spec v1.0.0
github.com/opencontainers/runc b4e2ecb452d9ee4381137cc0a7e6715b96bed6de
github.com/opencontainers/runtime-spec d810dbc60d8c5aeeb3d054bd1132fab2121968ce
github.com/opencontainers/runtime-tools master
-github.com/opencontainers/selinux 36a9bc45a08c85f2c52bd9eb32e20267876773bd
+github.com/opencontainers/selinux 6ba084dd09db3dfe49a839bab0bbe97fd9274d80
github.com/ostreedev/ostree-go master
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
index 2cd54eac1..bbaa1e0d7 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
@@ -687,7 +687,11 @@ func Chcon(fpath string, label string, recurse bool) error {
return err
}
callback := func(p string, info os.FileInfo, err error) error {
- return SetFileLabel(p, label)
+ e := SetFileLabel(p, label)
+ if os.IsNotExist(e) {
+ return nil
+ }
+ return e
}
if recurse {