diff options
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 { |