From 9db7cf137035d3724a2205075ae32cdcd83082e9 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Tue, 19 Sep 2017 12:26:32 +0000 Subject: [PATCH] Add `kpod pause` and `kpod unpause` Implement the ability to pause and unpause running containers. Signed-off-by: Daniel J Walsh Signed-off-by: TomSweeneyRedHat --- README.md | 4 +- cmd/kpod/main.go | 2 + cmd/kpod/pause.go | 58 ++++++++++++++ cmd/kpod/unpause.go | 58 ++++++++++++++ completions/bash/kpod | 18 +++++ docs/kpod-pause.1.md | 24 ++++++ docs/kpod-unpause.1.md | 24 ++++++ libkpod/pause.go | 46 +++++++++++ libkpod/remove.go | 7 +- libkpod/stop.go | 21 ++++-- oci/oci.go | 18 +++++ test/kpod_pause.bats | 168 +++++++++++++++++++++++++++++++++++++++++ transfer.md | 7 +- 13 files changed, 444 insertions(+), 11 deletions(-) create mode 100644 cmd/kpod/pause.go create mode 100644 cmd/kpod/unpause.go create mode 100644 docs/kpod-pause.1.md create mode 100644 docs/kpod-unpause.1.md create mode 100644 libkpod/pause.go create mode 100644 test/kpod_pause.bats diff --git a/README.md b/README.md index 452d4cbb..3453a3b1 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-logs(1)](/docs/kpod-logs.1.md) | Display the logs of a container || | [kpod-mount(1)](/docs/kpod-mount.1.md) | Mount a working container's root filesystem || | [kpod-ps(1)](/docs/kpod-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| +| [kpod-pause(1)](/docs/kpod-pause.1.md) | Pause one or more running containers || | [kpod-pull(1)](/docs/kpod-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| | [kpod-push(1)](/docs/kpod-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)| | [kpod-rename(1)](/docs/kpod-rename.1.md) | Rename a container || @@ -59,9 +60,10 @@ It is currently in active development in the Kubernetes community through the [d | [kpod-rmi(1)](/docs/kpod-rmi.1.md) | Removes one or more images |[![...](/docs/play.png)](https://asciinema.org/a/133799)| | [kpod-save(1)](/docs/kpod-save.1.md) | Saves an image to an archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| | [kpod-stats(1)](/docs/kpod-stats.1.md) | Display a live stream of one or more containers' resource usage statistics|| -| [kpod-stop(1)](/docs/kpod-stop.1.md) | Stops one or more running containers.|| +| [kpod-stop(1)](/docs/kpod-stop.1.md) | Stops one or more running containers || | [kpod-tag(1)](/docs/kpod-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)| | [kpod-umount(1)](/docs/kpod-umount.1.md) | Unmount a working container's root filesystem || +| [kpod-unpause(1)](/docs/kpod-unpause.1.md) | Unpause one or more running containers || | [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| ## Configuration diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 13f71cd2..75c32d6b 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -40,6 +40,7 @@ func main() { loadCommand, logsCommand, mountCommand, + pauseCommand, psCommand, pullCommand, pushCommand, @@ -51,6 +52,7 @@ func main() { stopCommand, tagCommand, umountCommand, + unpauseCommand, versionCommand, } app.Before = func(c *cli.Context) error { diff --git a/cmd/kpod/pause.go b/cmd/kpod/pause.go new file mode 100644 index 00000000..5a8229eb --- /dev/null +++ b/cmd/kpod/pause.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "github.com/kubernetes-incubator/cri-o/libkpod" + "github.com/pkg/errors" + "github.com/urfave/cli" + "os" +) + +var ( + pauseDescription = ` + kpod pause + + Pauses one or more running containers. The container name or ID can be used. +` + pauseCommand = cli.Command{ + Name: "pause", + Usage: "Pauses all the processes in one or more containers", + Description: pauseDescription, + Action: pauseCmd, + ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + } +) + +func pauseCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("you must provide at least one container name or id") + } + + config, err := getConfig(c) + if err != nil { + return errors.Wrapf(err, "could not get config") + } + server, err := libkpod.New(config) + if err != nil { + return errors.Wrapf(err, "could not get container server") + } + defer server.Shutdown() + if err := server.Update(); err != nil { + return errors.Wrapf(err, "could not update list of containers") + } + var lastError error + for _, container := range c.Args() { + cid, err := server.ContainerPause(container) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to pause container %v", container) + } else { + fmt.Println(cid) + } + } + + return lastError +} diff --git a/cmd/kpod/unpause.go b/cmd/kpod/unpause.go new file mode 100644 index 00000000..a7b7db20 --- /dev/null +++ b/cmd/kpod/unpause.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "github.com/kubernetes-incubator/cri-o/libkpod" + "github.com/pkg/errors" + "github.com/urfave/cli" + "os" +) + +var ( + unpauseDescription = ` + kpod unpause + + Unpauses one or more running containers. The container name or ID can be used. +` + unpauseCommand = cli.Command{ + Name: "unpause", + Usage: "Unpause the processes in one or more containers", + Description: unpauseDescription, + Action: unpauseCmd, + ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", + } +) + +func unpauseCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("you must provide at least one container name or id") + } + + config, err := getConfig(c) + if err != nil { + return errors.Wrapf(err, "could not get config") + } + server, err := libkpod.New(config) + if err != nil { + return errors.Wrapf(err, "could not get container server") + } + defer server.Shutdown() + if err := server.Update(); err != nil { + return errors.Wrapf(err, "could not update list of containers") + } + var lastError error + for _, container := range c.Args() { + cid, err := server.ContainerUnpause(container) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "failed to unpause container %v", container) + } else { + fmt.Println(cid) + } + } + + return lastError +} diff --git a/completions/bash/kpod b/completions/bash/kpod index 07022bc9..e6a95519 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -349,6 +349,14 @@ _kpod_export() { _complete_ "$options_with_args" "$boolean_options" } +_kpod_pause() { + local options_with_args=" + --help -h + " + local boolean_options="" + _complete_ "$options_with_args" "$boolean_options" +} + _kpod_ps() { local options_with_args=" --filter -f @@ -374,6 +382,14 @@ _kpod_stop() { _complete_ "$options_with_args" "$boolean_options" } +_kpod_unpause() { + local options_with_args=" + --help -h + " + local boolean_options="" + _complete_ "$options_with_args" "$boolean_options" +} + _complete_() { local options_with_args=$1 local boolean_options="$2 -h --help" @@ -424,6 +440,7 @@ _kpod_kpod() { load logs mount + pause ps pull push @@ -436,6 +453,7 @@ _kpod_kpod() { tag umount unmount + unpause version " diff --git a/docs/kpod-pause.1.md b/docs/kpod-pause.1.md new file mode 100644 index 00000000..4a1eee92 --- /dev/null +++ b/docs/kpod-pause.1.md @@ -0,0 +1,24 @@ +% kpod(1) kpod-pause - Pause one or more containers +% Dan Walsh +# kpod-pause "1" "September 2017" "kpod" + +## NAME +kpod pause - Pause one or more containers + +## SYNOPSIS +**kpod pause [OPTIONS] CONTAINER [...]** + +## DESCRIPTION +Pauses all the processes in one or more containers. You may use container IDs or names as input. + +## EXAMPLE + +kpod pause mywebserver + +kpod pause 860a4b23 + +## SEE ALSO +kpod(1), kpod-unpause(1) + +## HISTORY +September 2017, Originally compiled by Dan Walsh diff --git a/docs/kpod-unpause.1.md b/docs/kpod-unpause.1.md new file mode 100644 index 00000000..52a81002 --- /dev/null +++ b/docs/kpod-unpause.1.md @@ -0,0 +1,24 @@ +% kpod(1) kpod-unpause - Unpause one or more containers +% Dan Walsh +# kpod-unpause "1" "September 2017" "kpod" + +## NAME +kpod unpause - Unpause one or more containers + +## SYNOPSIS +**kpod unpause [OPTIONS] CONTAINER [...]** + +## DESCRIPTION +Unpauses the processes in one or more containers. You may use container IDs or names as input. + +## EXAMPLE + +kpod unpause mywebserver + +kpod unpause 860a4b23 + +## SEE ALSO +kpod(1), kpod-pause(1) + +## HISTORY +September 2017, Originally compiled by Dan Walsh diff --git a/libkpod/pause.go b/libkpod/pause.go new file mode 100644 index 00000000..29871d32 --- /dev/null +++ b/libkpod/pause.go @@ -0,0 +1,46 @@ +package libkpod + +import ( + "github.com/kubernetes-incubator/cri-o/oci" + "github.com/pkg/errors" +) + +// ContainerPause pauses a running container. +func (c *ContainerServer) ContainerPause(container string) (string, error) { + ctr, err := c.LookupContainer(container) + if err != nil { + return "", errors.Wrapf(err, "failed to find container %s", container) + } + + cStatus := c.runtime.ContainerStatus(ctr) + if cStatus.Status != oci.ContainerStatePaused { + if err := c.runtime.PauseContainer(ctr); err != nil { + return "", errors.Wrapf(err, "failed to pause container %s", ctr.ID()) + } + c.ContainerStateToDisk(ctr) + } else { + return "", errors.Wrapf(err, "container %s is already paused", ctr.ID()) + } + + return ctr.ID(), nil +} + +// ContainerUnpause unpauses a running container with a grace period (i.e., timeout). +func (c *ContainerServer) ContainerUnpause(container string) (string, error) { + ctr, err := c.LookupContainer(container) + if err != nil { + return "", errors.Wrapf(err, "failed to find container %s", container) + } + + cStatus := c.runtime.ContainerStatus(ctr) + if cStatus.Status == oci.ContainerStatePaused { + if err := c.runtime.UnpauseContainer(ctr); err != nil { + return "", errors.Wrapf(err, "failed to unpause container %s", ctr.ID()) + } + c.ContainerStateToDisk(ctr) + } else { + return "", errors.Wrapf(err, "the container %s is not paused", ctr.ID()) + } + + return ctr.ID(), nil +} diff --git a/libkpod/remove.go b/libkpod/remove.go index bfe4fef8..a3aa6eea 100644 --- a/libkpod/remove.go +++ b/libkpod/remove.go @@ -16,8 +16,11 @@ func (c *ContainerServer) Remove(container string, force bool) (string, error) { } ctrID := ctr.ID() - cState := c.runtime.ContainerStatus(ctr) - if cState.Status == oci.ContainerStateCreated || cState.Status == oci.ContainerStateRunning { + cStatus := c.runtime.ContainerStatus(ctr) + switch cStatus.Status { + case oci.ContainerStatePaused: + return "", errors.Errorf("cannot remove paused container %s", ctrID) + case oci.ContainerStateCreated, oci.ContainerStateRunning: if force { _, err = c.ContainerStop(container, -1) if err != nil { diff --git a/libkpod/stop.go b/libkpod/stop.go index af7a8c5d..06712d45 100644 --- a/libkpod/stop.go +++ b/libkpod/stop.go @@ -11,18 +11,25 @@ func (c *ContainerServer) ContainerStop(container string, timeout int64) (string if err != nil { return "", errors.Wrapf(err, "failed to find container %s", container) } + ctrID := ctr.ID() cStatus := c.runtime.ContainerStatus(ctr) - if cStatus.Status != oci.ContainerStateStopped { - if err := c.runtime.StopContainer(ctr, timeout); err != nil { - return "", errors.Wrapf(err, "failed to stop container %s", ctr.ID()) - } - if err := c.storageRuntimeServer.StopContainer(ctr.ID()); err != nil { - return "", errors.Wrapf(err, "failed to unmount container %s", ctr.ID()) + switch cStatus.Status { + + case oci.ContainerStatePaused: + return "", errors.Errorf("cannot stop paused container %s", ctrID) + default: + if cStatus.Status != oci.ContainerStateStopped { + if err := c.runtime.StopContainer(ctr, timeout); err != nil { + return "", errors.Wrapf(err, "failed to stop container %s", ctrID) + } + if err := c.storageRuntimeServer.StopContainer(ctrID); err != nil { + return "", errors.Wrapf(err, "failed to unmount container %s", ctrID) + } } } c.ContainerStateToDisk(ctr) - return ctr.ID(), nil + return ctrID, nil } diff --git a/oci/oci.go b/oci/oci.go index 7f03ef0f..a11316f3 100644 --- a/oci/oci.go +++ b/oci/oci.go @@ -24,6 +24,8 @@ import ( const ( // ContainerStateCreated represents the created state of a container ContainerStateCreated = "created" + // ContainerStatePaused represents the paused state of a container + ContainerStatePaused = "paused" // ContainerStateRunning represents the running state of a container ContainerStateRunning = "running" // ContainerStateStopped represents the stopped state of a container @@ -678,3 +680,19 @@ func (r *Runtime) RuntimeReady() (bool, error) { func (r *Runtime) NetworkReady() (bool, error) { return true, nil } + +// PauseContainer pauses a container. +func (r *Runtime) PauseContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + _, err := utils.ExecCmd(r.Path(c), "pause", c.id) + return err +} + +// UnpauseContainer unpauses a container. +func (r *Runtime) UnpauseContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + _, err := utils.ExecCmd(r.Path(c), "resume", c.id) + return err +} diff --git a/test/kpod_pause.bats b/test/kpod_pause.bats new file mode 100644 index 00000000..ebb96cbb --- /dev/null +++ b/test/kpod_pause.bats @@ -0,0 +1,168 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE="redis:alpine" +ROOT="$TESTDIR/crio" +RUNROOT="$TESTDIR/crio-run" +KPOD_OPTIONS="--root $ROOT --runroot $RUNROOT $STORAGE_OPTS --runtime $RUNTIME_BINARY" +function teardown() { + cleanup_test +} + +@test "pause a bogus container" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause foobar + echo "$output" + [ "$status" -eq 1 ] +} + +@test "unpause a bogus container" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause foobar + echo "$output" + [ "$status" -eq 1 ] +} + +@test "pause a created container by id" { + start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + run crioctl image pull "$IMAGE" + [ "$status" -eq 0 ] + run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter id="$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_pods + stop_crio +} + +@test "pause a running container by id" { + start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + run crioctl image pull "$IMAGE" + [ "$status" -eq 0 ] + run crioctl ctr create --config "$TESTDATA"/container_redis.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run crioctl ctr start --id "$ctr_id" + echo "$output" + id="$output" + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause "$id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause "$id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter id="$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_pods + stop_crio +} + +@test "pause a running container by name" { + start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + run crioctl image pull "$IMAGE" + [ "$status" -eq 0 ] + run crioctl ctr create --config "$TESTDATA"/container_redis.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run crioctl ctr start --id "$ctr_id" + echo "$output" + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause "k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause "k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter id="k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0" + echo "$output" + [ "$status" -eq 0 ] + cleanup_pods + stop_crio +} + +@test "remove a paused container by id" { + start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + run crioctl image pull "$IMAGE" + [ "$status" -eq 0 ] + run crioctl ctr create --config "$TESTDATA"/container_redis.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run crioctl ctr start --id "$ctr_id" + echo "$output" + id="$output" + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause "$id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rm "$id" + echo "$output" + [ "$status" -eq 1 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rm --force "$id" + echo "$output" + [ "$status" -eq 1 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause "$id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} stop "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} rm "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_pods + stop_crio +} + +@test "stop a paused container created by id" { + start_crio + run crioctl pod run --config "$TESTDATA"/sandbox_config.json + echo "$output" + [ "$status" -eq 0 ] + pod_id="$output" + run crioctl image pull "$IMAGE" + [ "$status" -eq 0 ] + run crioctl ctr create --config "$TESTDATA"/container_config.json --pod "$pod_id" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run ${KPOD_BINARY} ${KPOD_OPTIONS} pause "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} stop "$ctr_id" + echo "$output" + [ "$status" -eq 1 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} unpause "$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} ps -a --filter id="$ctr_id" + echo "$output" + [ "$status" -eq 0 ] + cleanup_pods + stop_crio +} diff --git a/transfer.md b/transfer.md index fdf18cbd..674199df 100644 --- a/transfer.md +++ b/transfer.md @@ -40,12 +40,13 @@ There are other equivalents for these tools | Existing Step | CRI-O (and friends) | | :---: | :---: | | `docker build` | [`buildah bud`](https://github.com/projectatomic/buildah/blob/master/docs/buildah-bud.md) | -| `docker cp` | [`kpod mount`](./docs/kpod-cp.1.md) | +| `docker cp` | [`kpod mount`](./docs/kpod-cp.1.md) *** | | `docker diff` | [`kpod diff`](./docs/kpod-diff.1.md) | | `docker export` | [`kpod export`](./docs/kpod-export.1.md) | | `docker history`| [`kpod history`](./docs/kpod-history.1.md)| | `docker images` | [`kpod images`](./docs/kpod-images.1.md) | | `docker load` | [`kpod load`](./docs/kpod-load.1.md) | +| `docker pause` | [`kpod pause`](./docs/kpod-pause.1.md) | | `docker ps` | [`kpod ps`](./docs/kpod-ps.1.md) | | `docker pull` | [`kpod pull`](./docs/kpod-pull.1.md) | | `docker push` | [`kpod push`](./docs/kpod-push.1.md) | @@ -54,4 +55,8 @@ There are other equivalents for these tools | `docker rmi` | [`kpod rmi`](./docs/kpod-rmi.1.md) | | `docker save` | [`kpod save`](./docs/kpod-save.1.md) | | `docker tag` | [`kpod tag`](./docs/kpod-tag.1.md) | +| `docker stop` | [`kpod stop`](./docs/kpod-stop.1.md) | +| `docker unpause`| [`kpod unpause`](./docs/kpod-unpause.1.md)| | `docker version`| [`kpod version`](./docs/kpod-version.1.md)| + +*** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/kpod-cp.1.md) for more information.