diff options
author | haircommander <pehunt@redhat.com> | 2018-07-20 13:28:19 -0400 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-07-27 14:20:08 +0000 |
commit | f258e43c7c4685102fbbf52303348f434ca8cb3e (patch) | |
tree | 6292f4d7ec86f70d0c85a9e9c6c6effaf4234a0e | |
parent | 50fea69fbc017fd0da160b083780d933aa5462b5 (diff) | |
download | podman-f258e43c7c4685102fbbf52303348f434ca8cb3e.tar.gz podman-f258e43c7c4685102fbbf52303348f434ca8cb3e.tar.bz2 podman-f258e43c7c4685102fbbf52303348f434ca8cb3e.zip |
Add pod pause/unpause
Added Pause() and Unpause() to libpod/pod.go
Added man pages, tests and completions
Signed-off-by: haircommander <pehunt@redhat.com>
Closes: #1126
Approved by: rhatdan
-rw-r--r-- | cmd/podman/pod.go | 2 | ||||
-rw-r--r-- | cmd/podman/pod_pause.go | 73 | ||||
-rw-r--r-- | cmd/podman/pod_unpause.go | 73 | ||||
-rw-r--r-- | commands.md | 2 | ||||
-rw-r--r-- | completions/bash/podman | 62 | ||||
-rw-r--r-- | docs/podman-pod-pause.1.md | 32 | ||||
-rw-r--r-- | docs/podman-pod-unpause.1.md | 32 | ||||
-rw-r--r-- | docs/podman-pod.1.md | 2 | ||||
-rw-r--r-- | libpod/pod.go | 119 | ||||
-rw-r--r-- | test/e2e/pod_pause_test.go | 116 |
10 files changed, 504 insertions, 9 deletions
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 65bc9efda..ed79f0711 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -12,11 +12,13 @@ Pods are a group of one or more containers sharing the same network, pid and ipc podSubCommands = []cli.Command{ podCreateCommand, podKillCommand, + podPauseCommand, podPsCommand, podRestartCommand, podRmCommand, podStartCommand, podStopCommand, + podUnpauseCommand, } podCommand = cli.Command{ Name: "pod", diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go new file mode 100644 index 000000000..df51a037b --- /dev/null +++ b/cmd/podman/pod_pause.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + podPauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "pause all running pods", + }, + LatestPodFlag, + } + podPauseDescription = ` + Pauses one or more pods. The pod name or ID can be used. +` + + podPauseCommand = cli.Command{ + Name: "pause", + Usage: "Pause one or more pods", + Description: podPauseDescription, + Flags: podPauseFlags, + Action: podPauseCmd, + ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", + UseShortOptionHandling: true, + } +) + +func podPauseCmd(c *cli.Context) error { + if err := checkMutuallyExclusiveFlags(c); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + // getPodsFromContext returns an error when a requested pod + // isn't found. The only fatal error scenerio is when there are no pods + // in which case the following loop will be skipped. + pods, lastError := getPodsFromContext(c, runtime) + + for _, pod := range pods { + ctr_errs, err := pod.Pause() + if ctr_errs != nil { + for ctr, err := range ctr_errs { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to pause container %q on pod %q", ctr, pod.ID()) + } + continue + } + if err != nil { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to pause pod %q", pod.ID()) + continue + } + fmt.Println(pod.ID()) + } + + return lastError +} diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go new file mode 100644 index 000000000..5dd5c79e4 --- /dev/null +++ b/cmd/podman/pod_unpause.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + podUnpauseFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "unpause all paused pods", + }, + LatestPodFlag, + } + podUnpauseDescription = ` + Unpauses one or more pods. The pod name or ID can be used. +` + + podUnpauseCommand = cli.Command{ + Name: "unpause", + Usage: "Unpause one or more pods", + Description: podUnpauseDescription, + Flags: podUnpauseFlags, + Action: podUnpauseCmd, + ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", + UseShortOptionHandling: true, + } +) + +func podUnpauseCmd(c *cli.Context) error { + if err := checkMutuallyExclusiveFlags(c); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + // getPodsFromContext returns an error when a requested pod + // isn't found. The only fatal error scenerio is when there are no pods + // in which case the following loop will be skipped. + pods, lastError := getPodsFromContext(c, runtime) + + for _, pod := range pods { + ctr_errs, err := pod.Unpause() + if ctr_errs != nil { + for ctr, err := range ctr_errs { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to unpause container %q on pod %q", ctr, pod.ID()) + } + continue + } + if err != nil { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to unpause pod %q", pod.ID()) + continue + } + fmt.Println(pod.ID()) + } + + return lastError +} diff --git a/commands.md b/commands.md index 8af63b60d..3ac195476 100644 --- a/commands.md +++ b/commands.md @@ -33,10 +33,12 @@ | [podman-pod-create(1)](/docs/podman-pod-create.1.md) | Create a new pod || | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. || | [podman-pod-ps(1)](/docs/podman-pod-ps.1.md) | List the pods on the system || +| [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. || | [podman-pod-restart](/docs/podman-pod-restart.1.md) | Restart one or more pods || | [podman-pod-rm(1)](/docs/podman-pod-rm.1.md) | Remove one or more pods || | [podman-pod-start(1)](/docs/podman-pod-start.1.md) | Start one or more pods || | [podman-pod-stop(1)](/docs/podman-pod-stop.1.md) | Stop one or more pods || +| [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. || | [podman-port(1)](/docs/podman-port.1.md) | List port mappings for running containers |[![...](/docs/play.png)]()| | [podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| | [podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| diff --git a/completions/bash/podman b/completions/bash/podman index 0019343e2..2c43a2d8f 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2193,10 +2193,10 @@ _podman_pod_start() { " local boolean_options=" - all - a - latest - l + --all + -a + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in @@ -2214,11 +2214,53 @@ _podman_pod_stop() { " local boolean_options=" - all - a - cleanup - latest - l + --all + -a + --cleanup + --latest + -l + " + _complete_ "$options_with_args" "$boolean_options" + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; + esac +} + +_podman_pod_pause() { + local options_with_args=" + " + + local boolean_options=" + --all + -a + --latest + -l + " + _complete_ "$options_with_args" "$boolean_options" + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_pod_names + ;; + esac +} + +_podman_pod_unpause() { + local options_with_args=" + " + + local boolean_options=" + --all + -a + --latest + -l " _complete_ "$options_with_args" "$boolean_options" case "$cur" in @@ -2244,6 +2286,8 @@ _podman_pod() { rm start stop + pause + unpause " local aliases=" list diff --git a/docs/podman-pod-pause.1.md b/docs/podman-pod-pause.1.md new file mode 100644 index 000000000..56d63e2e2 --- /dev/null +++ b/docs/podman-pod-pause.1.md @@ -0,0 +1,32 @@ +% podman-pod-pause "1" + +## NAME +podman\-pod\-pause - Pause one or more pods + +## SYNOPSIS +**podman pod pause** [*options*] *pod* ... + +## DESCRIPTION +Pauses all the running processes in the containers of one or more pods. You may use pod IDs or names as input. + +## OPTIONS + +**--all, a** + +Pause all pods. + +**--latest, -l** + +Instead of providing the pod name or ID, pause the last created pod. + +## EXAMPLE + +podman pod pause mywebserverpod + +podman pod pause 860a4b23 + +## SEE ALSO +podman-pod(1), podman-pod-unpause(1), podman-pause(1) + +## HISTORY +July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com> diff --git a/docs/podman-pod-unpause.1.md b/docs/podman-pod-unpause.1.md new file mode 100644 index 000000000..2c065976c --- /dev/null +++ b/docs/podman-pod-unpause.1.md @@ -0,0 +1,32 @@ +% podman-pod-unpause "1" + +## NAME +podman\-pod\-unpause - Unpause one or more pods + +## SYNOPSIS +**podman pod unpause** [*options*] *pod* ... + +## DESCRIPTION +Unpauses all the paused processes in the containers of one or more pods. You may use pod IDs or names as input. + +## OPTIONS + +**--all, a** + +Unpause all pods. + +**--latest, -l** + +Instead of providing the pod name or ID, unpause the last created pod. + +## EXAMPLE + +podman pod unpause mywebserverpod + +podman pod unpause 860a4b23 + +## SEE ALSO +podman-pod(1), podman-pod-pause(1), podman-unpause(1) + +## HISTORY +July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com> diff --git a/docs/podman-pod.1.md b/docs/podman-pod.1.md index 74b6d75b5..56b93819d 100644 --- a/docs/podman-pod.1.md +++ b/docs/podman-pod.1.md @@ -15,10 +15,12 @@ podman pod is a set of subcommands that manage pods, or groups of containers. | ------------------------------------------------- | ------------------------------------------------------------------------------ | | [podman-pod-create(1)](podman-pod-create.1.md) | Create a new pod. | | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. | +| [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. | | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. | | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. | | [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more pods. | +| [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. | ## HISTORY July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com> diff --git a/libpod/pod.go b/libpod/pod.go index 6e568f2b7..58becfa7a 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -383,6 +383,124 @@ func (p *Pod) Stop(cleanup bool) (map[string]error, error) { return nil, nil } +// Pause pauses all containers within a pod that are running. +// Only running containers will be paused. Paused, stopped, or created +// containers will be ignored. +// All containers are paused independently. An error pausing one container +// will not prevent other containers being paused. +// An error and a map[string]error are returned +// If the error is not nil and the map is nil, an error was encountered before +// any containers were paused +// If map is not nil, an error was encountered when pausing one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrCtrExists +// If both error and the map are nil, all containers were paused without error +func (p *Pod) Pause() (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrors := make(map[string]error) + + // Pause to all containers + for _, ctr := range allCtrs { + ctr.lock.Lock() + + if err := ctr.syncContainer(); err != nil { + ctr.lock.Unlock() + ctrErrors[ctr.ID()] = err + continue + } + + // Ignore containers that are not running + if ctr.state.State != ContainerStateRunning { + ctr.lock.Unlock() + continue + } + + if err := ctr.pause(); err != nil { + ctr.lock.Unlock() + ctrErrors[ctr.ID()] = err + continue + } + + ctr.lock.Unlock() + } + + if len(ctrErrors) > 0 { + return ctrErrors, errors.Wrapf(ErrCtrExists, "error pausing some containers") + } + + return nil, nil +} + +// Unpause unpauses all containers within a pod that are running. +// Only paused containers will be unpaused. Running, stopped, or created +// containers will be ignored. +// All containers are unpaused independently. An error unpausing one container +// will not prevent other containers being unpaused. +// An error and a map[string]error are returned +// If the error is not nil and the map is nil, an error was encountered before +// any containers were unpaused +// If map is not nil, an error was encountered when unpausing one or more +// containers. The container ID is mapped to the error encountered. The error is +// set to ErrCtrExists +// If both error and the map are nil, all containers were unpaused without error +func (p *Pod) Unpause() (map[string]error, error) { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return nil, ErrPodRemoved + } + + allCtrs, err := p.runtime.state.PodContainers(p) + if err != nil { + return nil, err + } + + ctrErrors := make(map[string]error) + + // Pause to all containers + for _, ctr := range allCtrs { + ctr.lock.Lock() + + if err := ctr.syncContainer(); err != nil { + ctr.lock.Unlock() + ctrErrors[ctr.ID()] = err + continue + } + + // Ignore containers that are not paused + if ctr.state.State != ContainerStatePaused { + ctr.lock.Unlock() + continue + } + + if err := ctr.unpause(); err != nil { + ctr.lock.Unlock() + ctrErrors[ctr.ID()] = err + continue + } + + ctr.lock.Unlock() + } + + if len(ctrErrors) > 0 { + return ctrErrors, errors.Wrapf(ErrCtrExists, "error unpausing some containers") + } + + return nil, nil +} + // Restart restarts all containers within a pod that are not paused or in an error state. // It combines the effects of Stop() and Start() on a container // Each container will use its own stop timeout. @@ -403,6 +521,7 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) { if !p.valid { return nil, ErrPodRemoved } + allCtrs, err := p.runtime.state.PodContainers(p) if err != nil { return nil, err diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go new file mode 100644 index 000000000..aa9638ed2 --- /dev/null +++ b/test/e2e/pod_pause_test.go @@ -0,0 +1,116 @@ +package integration + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod pause", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + pausedState := "Paused" + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + + }) + + It("podman pod pause bogus pod", func() { + session := podmanTest.Podman([]string{"pod", "pause", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman unpause bogus pod", func() { + session := podmanTest.Podman([]string{"pod", "unpause", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman pod pause a created pod by id", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid := session.OutputToString() + + result := podmanTest.Podman([]string{"pod", "pause", podid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) + + It("podman pod pause a running pod by id", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "pause", podid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) + + result = podmanTest.Podman([]string{"pod", "unpause", podid}) + result.WaitWithDefaultTimeout() + + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) + + It("podman unpause a running pod by id", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "unpause", podid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) + + It("podman pod pause a running pod by name", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("", "test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "pause", "test1"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(Equal(pausedState)) + + result = podmanTest.Podman([]string{"pod", "unpause", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) +}) |