diff options
-rw-r--r-- | cmd/podman/pod.go | 2 | ||||
-rw-r--r-- | cmd/podman/pod_kill.go | 114 | ||||
-rw-r--r-- | cmd/podman/pod_restart.go | 98 | ||||
-rw-r--r-- | commands.md | 2 | ||||
-rw-r--r-- | completions/bash/podman | 46 | ||||
-rw-r--r-- | docs/podman-pod-kill.1.md | 43 | ||||
-rw-r--r-- | docs/podman-pod-restart.1.md | 50 | ||||
-rw-r--r-- | docs/podman-pod.1.md | 1 | ||||
-rw-r--r-- | libpod/container_api.go | 34 | ||||
-rw-r--r-- | libpod/container_internal.go | 37 | ||||
-rw-r--r-- | libpod/pod.go | 78 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 9 | ||||
-rw-r--r-- | test/e2e/pod_kill_test.go | 162 | ||||
-rw-r--r-- | test/e2e/pod_restart_test.go | 175 | ||||
-rw-r--r-- | test/e2e/run_test.go | 13 | ||||
-rw-r--r-- | vendor.conf | 2 | ||||
-rw-r--r-- | vendor/github.com/containers/storage/drivers/overlay/overlay.go | 15 | ||||
-rw-r--r-- | vendor/github.com/containers/storage/pkg/mount/flags.go | 6 | ||||
-rw-r--r-- | vendor/github.com/containers/storage/pkg/mount/mount.go | 4 |
19 files changed, 833 insertions, 58 deletions
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index e19fea01b..277b16932 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -18,7 +18,9 @@ var ( UseShortOptionHandling: true, Subcommands: []cli.Command{ podCreateCommand, + podKillCommand, podPsCommand, + podRestartCommand, podRmCommand, podStartCommand, podStopCommand, diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go new file mode 100644 index 000000000..e233c76b1 --- /dev/null +++ b/cmd/podman/pod_kill.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "syscall" + + "github.com/docker/docker/pkg/signal" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + podKillFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "Kill all containers in all pods", + }, + cli.StringFlag{ + Name: "signal, s", + Usage: "Signal to send to the containers in the pod", + Value: "KILL", + }, + LatestFlag, + } + podKillDescription = "The main process of each container inside the specified pod will be sent SIGKILL, or any signal specified with option --signal." + podKillCommand = cli.Command{ + Name: "kill", + Usage: "Send the specified signal or SIGKILL to containers in pod", + Description: podKillDescription, + Flags: podKillFlags, + Action: podKillCmd, + ArgsUsage: "[POD_NAME_OR_ID]", + UseShortOptionHandling: true, + } +) + +// podKillCmd kills one or more pods with a signal +func podKillCmd(c *cli.Context) error { + if err := checkMutuallyExclusiveFlags(c); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + args := c.Args() + var killSignal uint = uint(syscall.SIGTERM) + var lastError error + var pods []*libpod.Pod + + if c.String("signal") != "" { + // Check if the signalString provided by the user is valid + // Invalid signals will return err + sysSignal, err := signal.ParseSignal(c.String("signal")) + if err != nil { + return err + } + killSignal = uint(sysSignal) + } + + if c.Bool("all") { + pods, err = runtime.Pods() + if err != nil { + return errors.Wrapf(err, "unable to get pods") + } + } + if c.Bool("latest") { + pod, err := runtime.GetLatestPod() + if err != nil { + return errors.Wrapf(err, "unable to get latest pod") + } + pods = append(pods, pod) + } + for _, i := range args { + pod, err := runtime.LookupPod(i) + if err != nil { + logrus.Errorf("%q", lastError) + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to find pods %s", i) + continue + } + pods = append(pods, pod) + } + + for _, pod := range pods { + ctr_errs, err := pod.Kill(killSignal) + if ctr_errs != nil { + for ctr, err := range ctr_errs { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to kill container %q in pod %q", ctr, pod.ID()) + } + continue + } + if err != nil { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to kill pod %q", pod.ID()) + continue + } + fmt.Println(pod.ID()) + } + return lastError +} diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go new file mode 100644 index 000000000..cd0081c8c --- /dev/null +++ b/cmd/podman/pod_restart.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/projectatomic/libpod/cmd/podman/libpodruntime" + "github.com/projectatomic/libpod/libpod" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + podRestartFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "restart all pods", + }, + LatestFlag, + } + podRestartDescription = `Restarts one or more pods. The pod ID or name can be used.` + + podRestartCommand = cli.Command{ + Name: "restart", + Usage: "Restart one or more pods", + Description: podRestartDescription, + Flags: podRestartFlags, + Action: podRestartCmd, + ArgsUsage: "POD-NAME|POD-ID [POD-NAME|POD-ID ...]", + UseShortOptionHandling: true, + } +) + +func podRestartCmd(c *cli.Context) error { + if err := checkMutuallyExclusiveFlags(c); err != nil { + return err + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + args := c.Args() + var pods []*libpod.Pod + var lastError error + + if c.Bool("all") { + pods, err = runtime.Pods() + if err != nil { + return errors.Wrapf(err, "unable to get running pods") + } + } + + if c.Bool("latest") { + pod, err := runtime.GetLatestPod() + if err != nil { + return errors.Wrapf(err, "unable to get latest pod") + } + pods = append(pods, pod) + } + + for _, i := range args { + pod, err := runtime.LookupPod(i) + if err != nil { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to find pod %s", i) + continue + } + pods = append(pods, pod) + } + + ctx := getContext() + for _, pod := range pods { + ctr_errs, err := pod.Restart(ctx) + if ctr_errs != nil { + for ctr, err := range ctr_errs { + if lastError != nil { + logrus.Errorf("%q", lastError) + } + lastError = errors.Wrapf(err, "unable to restart 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 restart pod %q", pod.ID()) + continue + } + fmt.Println(pod.ID()) + } + return lastError +} diff --git a/commands.md b/commands.md index f15ff1c3d..8af63b60d 100644 --- a/commands.md +++ b/commands.md @@ -31,7 +31,9 @@ | [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| | [podman-pod(1)](/docs/podman-pod.1.md) | Simple management tool for groups of containers, called pods || | [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-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 || diff --git a/completions/bash/podman b/completions/bash/podman index 5d20bab3e..0019343e2 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2086,6 +2086,29 @@ _podman_pod_create() { _complete_ "$options_with_args" "$boolean_options" } +_podman_pod_kill() { + local options_with_args=" + " + + local boolean_options=" + --all + -a + --signal + -s + --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_ps() { local options_with_args=" -f @@ -2121,6 +2144,27 @@ _podman_pod_ps() { __podman_pod_ps } +_podman_pod_restart() { + 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_rm() { local options_with_args=" " @@ -2194,7 +2238,9 @@ _podman_pod() { " subcommands=" create + kill ps + restart rm start stop diff --git a/docs/podman-pod-kill.1.md b/docs/podman-pod-kill.1.md new file mode 100644 index 000000000..2f4d37f92 --- /dev/null +++ b/docs/podman-pod-kill.1.md @@ -0,0 +1,43 @@ +% podman-pod-kill "1" + +## NAME +podman\-pod\-kill - Kills all containers in one or more pods with a signal + +## SYNOPSIS +**podman pod kill** [*options*] *pod* ... + +## DESCRIPTION +The main process of each container inside the pods specified will be sent SIGKILL, or any signal specified with option --signal. + +## OPTIONS +**--all, -a** + +Sends signal to all containers associated with a pod. + +**--latest, -l** + +Instead of providing the pod name or ID, use the last created pod. If you use methods other than Podman +to run pods such as CRI-O, the last started pod could be from either of those methods. + +**--signal, s** + +Signal to send to the containers in the pod. For more information on Linux signals, refer to *man signal(7)*. + + +## EXAMPLE + +podman pod kill mywebserver + +podman pod kill 860a4b23 + +podman pod kill --signal TERM 860a4b23 + +podman pod kill --latest + +podman pod kill --all + +## SEE ALSO +podman-pod(1), podman-pod-stop(1) + +## HISTORY +July 2018, Originally compiled by Peter Hunt <pehunt@redhat.com> diff --git a/docs/podman-pod-restart.1.md b/docs/podman-pod-restart.1.md new file mode 100644 index 000000000..0990cf840 --- /dev/null +++ b/docs/podman-pod-restart.1.md @@ -0,0 +1,50 @@ +% podman-pod-restart "1" + +## NAME +podman\-pod\-restart - Restart one or more pods + +## SYNOPSIS +**podman pod restart** [*options*] *pod* ... + +## DESCRIPTION +Restart containers in one or more pods. Containers will be stopped if running and then restarted. +Stopped containers will only be started. You may use pod IDs or names as input. +The pod ID will be printed upon successful restart. +When restarting multiple pods, an error from restarting one pod will not effect restarting other pods. + +## OPTIONS + +**--all, -a** + +Restarts all pods + +**--latest, -l** + +Instead of providing the pod name or ID, restart the last created pod. + +## EXAMPLE + +podman pod restart mywebserverpod +cc8f0bea67b1a1a11aec1ecd38102a1be4b145577f21fc843c7c83b77fc28907 + +podman pod restart 490eb 3557fb +490eb241aaf704d4dd2629904410fe4aa31965d9310a735f8755267f4ded1de5 +3557fbea6ad61569de0506fe037479bd9896603c31d3069a6677f23833916fab + +3557fbea6ad61569de0506fe037479bd9896603c31d3069a6677f23833916fab + +podman pod restart --latest +3557fbea6ad61569de0506fe037479bd9896603c31d3069a6677f23833916fab + +podman pod restart --all +19456b4cd557eaf9629825113a552681a6013f8c8cad258e36ab825ef536e818 +3557fbea6ad61569de0506fe037479bd9896603c31d3069a6677f23833916fab +490eb241aaf704d4dd2629904410fe4aa31965d9310a735f8755267f4ded1de5 +70c358daecf71ef9be8f62404f926080ca0133277ef7ce4f6aa2d5af6bb2d3e9 +cc8f0bea67b1a1a11aec1ecd38102a1be4b145577f21fc843c7c83b77fc28907 + +## SEE ALSO +podman-pod(1), podman-pod-start(1), podman-restart(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 9aee75e81..74b6d75b5 100644 --- a/docs/podman-pod.1.md +++ b/docs/podman-pod.1.md @@ -14,6 +14,7 @@ podman pod is a set of subcommands that manage pods, or groups of containers. | Subcommand | Description | | ------------------------------------------------- | ------------------------------------------------------------------------------ | | [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-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. | diff --git a/libpod/container_api.go b/libpod/container_api.go index bb9727ec1..b5104048e 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -717,39 +717,7 @@ func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) (err e depString := strings.Join(notRunning, ",") return errors.Wrapf(ErrCtrStateInvalid, "some dependencies of container %s are not started: %s", c.ID(), depString) } - if c.state.State == ContainerStateUnknown || c.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "unable to restart a container in a paused or unknown state") - } - - if c.state.State == ContainerStateRunning { - if err := c.stop(timeout); err != nil { - return err - } - } - if err := c.prepare(); err != nil { - return err - } - defer func() { - if err != nil { - if err2 := c.cleanup(); err2 != nil { - logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) - } - } - }() - - if c.state.State == ContainerStateStopped { - // Reinitialize the container if we need to - if err := c.reinit(ctx); err != nil { - return err - } - } else if c.state.State == ContainerStateConfigured { - // Initialize the container if it has never been initialized - if err := c.init(ctx); err != nil { - return err - } - } - - return c.start() + return c.restartWithTimeout(ctx, timeout) } // Refresh refreshes a container's state in the database, restarting the diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 55fd7369d..8a96af0ab 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -718,6 +718,43 @@ func (c *Container) unpause() error { return c.save() } +// Internal, non-locking function to restart a container +func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err error) { + if c.state.State == ContainerStateUnknown || c.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "unable to restart a container in a paused or unknown state") + } + + if c.state.State == ContainerStateRunning { + if err := c.stop(timeout); err != nil { + return err + } + } + if err := c.prepare(); err != nil { + return err + } + defer func() { + if err != nil { + if err2 := c.cleanup(); err2 != nil { + logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2) + } + } + }() + + if c.state.State == ContainerStateStopped { + // Reinitialize the container if we need to + if err := c.reinit(ctx); err != nil { + return err + } + } else if c.state.State == ContainerStateConfigured { + // Initialize the container if it has never been initialized + if err := c.init(ctx); err != nil { + return err + } + } + + return c.start() +} + // mountStorage sets up the container's root filesystem // It mounts the image and any other requested mounts // TODO: Add ability to override mount label so we can use this for Mount() too diff --git a/libpod/pod.go b/libpod/pod.go index a5b87f8b5..6e568f2b7 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -212,15 +212,15 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) { // Traverse the graph beginning at nodes with no dependencies for _, node := range graph.noDepNodes { - startNode(ctx, node, false, ctrErrors, ctrsVisited) + startNode(ctx, node, false, ctrErrors, ctrsVisited, false) } return ctrErrors, nil } // Visit a node on a container graph and start the container, or set an error if -// a dependency failed to start -func startNode(ctx context.Context, node *containerNode, setError bool, ctrErrors map[string]error, ctrsVisited map[string]bool) { +// a dependency failed to start. if restart is true, startNode will restart the node instead of starting it. +func startNode(ctx context.Context, node *containerNode, setError bool, ctrErrors map[string]error, ctrsVisited map[string]bool, restart bool) { // First, check if we have already visited the node if ctrsVisited[node.id] { return @@ -235,7 +235,7 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError // Hit anyone who depends on us, and set errors on them too for _, successor := range node.dependedOn { - startNode(ctx, successor, true, ctrErrors, ctrsVisited) + startNode(ctx, successor, true, ctrErrors, ctrsVisited, restart) } return @@ -287,10 +287,18 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError } // Start the container (only if it is not running) - if !ctrErrored && node.container.state.State != ContainerStateRunning { - if err := node.container.initAndStart(ctx); err != nil { - ctrErrored = true - ctrErrors[node.id] = err + if !ctrErrored { + if !restart && node.container.state.State != ContainerStateRunning { + if err := node.container.initAndStart(ctx); err != nil { + ctrErrored = true + ctrErrors[node.id] = err + } + } + if restart && node.container.state.State != ContainerStatePaused && node.container.state.State != ContainerStateUnknown { + if err := node.container.restartWithTimeout(ctx, node.container.config.StopTimeout); err != nil { + ctrErrored = true + ctrErrors[node.id] = err + } } } @@ -298,7 +306,7 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError // Recurse to anyone who depends on us and start them for _, successor := range node.dependedOn { - startNode(ctx, successor, ctrErrored, ctrErrors, ctrsVisited) + startNode(ctx, successor, ctrErrored, ctrErrors, ctrsVisited, restart) } return @@ -375,6 +383,58 @@ func (p *Pod) Stop(cleanup bool) (map[string]error, error) { 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. +// All containers are started independently, in order dictated by their +// dependencies. An error restarting one container +// will not prevent other containers being restarted. +// 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 restarted +// If map is not nil, an error was encountered when restarting 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 restarted without error +func (p *Pod) Restart(ctx context.Context) (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 + } + + // Build a dependency graph of containers in the pod + graph, err := buildContainerGraph(allCtrs) + if err != nil { + return nil, errors.Wrapf(err, "error generating dependency graph for pod %s", p.ID()) + } + + ctrErrors := make(map[string]error) + ctrsVisited := make(map[string]bool) + + // If there are no containers without dependencies, we can't start + // Error out + if len(graph.noDepNodes) == 0 { + return nil, errors.Wrapf(ErrNoSuchCtr, "no containers in pod %s have no dependencies, cannot start pod", p.ID()) + } + + // Traverse the graph beginning at nodes with no dependencies + for _, node := range graph.noDepNodes { + startNode(ctx, node, false, ctrErrors, ctrsVisited, true) + } + + if len(ctrErrors) > 0 { + return ctrErrors, errors.Wrapf(ErrCtrExists, "error stopping some containers") + } + + return nil, nil +} + // Kill sends a signal to all running containers within a pod // Signals will only be sent to running containers. Containers that are not // running will be ignored. All signals are sent independently, and sending will diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 1dba8cdb4..0bd6c6d1c 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -147,8 +147,13 @@ func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) { //GetVolumeMounts takes user provided input for bind mounts and creates Mount structs func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) { var m []spec.Mount - var options []string for _, i := range c.Volumes { + var ( + options []string + foundrw, foundro, foundz, foundZ bool + rootProp string + ) + // We need to handle SELinux options better here, specifically :Z spliti := strings.Split(i, ":") if len(spliti) > 2 { @@ -158,8 +163,6 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e continue } options = append(options, "rbind") - var foundrw, foundro, foundz, foundZ bool - var rootProp string for _, opt := range options { switch opt { case "rw": diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go new file mode 100644 index 000000000..9abc83262 --- /dev/null +++ b/test/e2e/pod_kill_test.go @@ -0,0 +1,162 @@ +package integration + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod kill", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + + }) + + It("podman pod kill bogus", func() { + session := podmanTest.Podman([]string{"pod", "kill", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman pod kill a 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)) + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "kill", podid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("podman pod kill a pod by id with TERM", 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", "kill", "-s", "9", podid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("podman pod kill a pod by name", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "test1"}) + 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", "kill", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + + It("podman pod kill a pod by id with a bogus signal", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "test1"}) + 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", "kill", "-s", "bogus", "test1"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(125)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) + + It("podman pod kill latest pod", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "create", "--name", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid2 := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid2) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("", podid2) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "kill", "-l"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) + + It("podman pod kill all", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "create", "--name", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podid2 := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", podid2) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "kill", "-a"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) +}) diff --git a/test/e2e/pod_restart_test.go b/test/e2e/pod_restart_test.go new file mode 100644 index 000000000..8de3a05b1 --- /dev/null +++ b/test/e2e/pod_restart_test.go @@ -0,0 +1,175 @@ +package integration + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod restart", func() { + var ( + tempdir string + err error + podmanTest PodmanTest + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + }) + + It("podman pod restart bogus pod", func() { + session := podmanTest.Podman([]string{"pod", "restart", "123"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman pod restart single empty pod", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "restart", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) + + It("podman pod restart single pod by name", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "foobar99"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("test1", "foobar99") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1"}) + startTime.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"pod", "restart", "foobar99"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) + }) + + It("podman pod restart multiple pods", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "foobar99"}) + session.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test1", "foobar99") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"pod", "create", "--name", "foobar100"}) + session2.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test2", "foobar100") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("test3", "foobar100") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.RunTopContainerInPod("test4", "foobar100") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2", "test3", "test4"}) + startTime.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"pod", "restart", "foobar99", "foobar100"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2", "test3", "test4"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Not(Equal(startTime.OutputToStringArray()[0]))) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + Expect(restartTime.OutputToStringArray()[2]).To(Not(Equal(startTime.OutputToStringArray()[2]))) + Expect(restartTime.OutputToStringArray()[3]).To(Not(Equal(startTime.OutputToStringArray()[3]))) + }) + + It("podman pod restart all pods", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "foobar99"}) + session.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test1", "foobar99") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"pod", "create", "--name", "foobar100"}) + session2.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test2", "foobar100") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"pod", "restart", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Not(Equal(startTime.OutputToStringArray()[0]))) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) + + It("podman pod restart latest pod", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "foobar99"}) + session.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test1", "foobar99") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session2 := podmanTest.Podman([]string{"pod", "create", "--name", "foobar100"}) + session2.WaitWithDefaultTimeout() + + session = podmanTest.RunTopContainerInPod("test2", "foobar100") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + startTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + startTime.WaitWithDefaultTimeout() + + session = podmanTest.Podman([]string{"pod", "restart", "-l"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + restartTime := podmanTest.Podman([]string{"inspect", "--format='{{.State.StartedAt}}'", "test1", "test2"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToStringArray()[0]).To(Equal(startTime.OutputToStringArray()[0])) + Expect(restartTime.OutputToStringArray()[1]).To(Not(Equal(startTime.OutputToStringArray()[1]))) + }) + + It("podman pod restart multiple pods with bogus", func() { + session := podmanTest.Podman([]string{"pod", "create", "--name", "foobar99"}) + session.WaitWithDefaultTimeout() + cid1 := session.OutputToString() + + session = podmanTest.RunTopContainerInPod("", "foobar99") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "restart", cid1, "doesnotexist"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) +}) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 32206c4f5..92aa2f191 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -547,4 +547,17 @@ USER mail` Expect(session.OutputToString()).To(ContainSubstring("data")) }) + + It("podman run --volumes flag with multiple volumes", func() { + vol1 := filepath.Join(podmanTest.TempDir, "vol-test1") + err := os.MkdirAll(vol1, 0755) + Expect(err).To(BeNil()) + vol2 := filepath.Join(podmanTest.TempDir, "vol-test2") + err = os.MkdirAll(vol2, 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"run", "--volume", vol1 + ":/myvol1:ro", "--volume", vol2 + ":/myvol2", ALPINE, "touch", "/myvol2/foo.txt"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/vendor.conf b/vendor.conf index 7e133bcea..4c2ff88e3 100644 --- a/vendor.conf +++ b/vendor.conf @@ -11,7 +11,7 @@ github.com/containerd/continuity master github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins 1fb94a4222eafc6f948eacdca9c9f2158b427e53 github.com/containers/image c6e0eee0f8eb38e78ae2e44a9aeea0576f451617 -github.com/containers/storage 8b1a0f8d6863cf05709af333b8997a437652ec4c +github.com/containers/storage afdedba2d2ad573350aee35033d4e0c58fdbd57b github.com/containers/psgo 4ccd87a37eaec61a669da89ffacb0f79f3550943 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni master diff --git a/vendor/github.com/containers/storage/drivers/overlay/overlay.go b/vendor/github.com/containers/storage/drivers/overlay/overlay.go index d2f7c373a..c59544aab 100644 --- a/vendor/github.com/containers/storage/drivers/overlay/overlay.go +++ b/vendor/github.com/containers/storage/drivers/overlay/overlay.go @@ -706,8 +706,11 @@ func (d *Driver) Get(id, mountLabel string) (_ string, retErr error) { workDir := path.Join(dir, "work") opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", strings.Join(absLowers, ":"), diffDir, workDir) + if d.options.mountOptions != "" { + opts = fmt.Sprintf("%s,%s", d.options.mountOptions, opts) + } mountData := label.FormatMountLabel(opts, mountLabel) - mount := unix.Mount + mountFunc := unix.Mount mountTarget := mergedDir pageSize := unix.Getpagesize() @@ -719,28 +722,26 @@ func (d *Driver) Get(id, mountLabel string) (_ string, retErr error) { if len(mountData) > pageSize || d.options.mountProgram != "" { //FIXME: We need to figure out to get this to work with additional stores opts = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", strings.Join(relLowers, ":"), path.Join(id, "diff"), path.Join(id, "work")) - if d.options.mountOptions != "" { - opts = fmt.Sprintf("%s,%s", d.options.mountOptions, opts) - } mountData = label.FormatMountLabel(opts, mountLabel) if len(mountData) > pageSize { return "", fmt.Errorf("cannot mount layer, mount label too large %d", len(mountData)) } if d.options.mountProgram != "" { - mount = func(source string, target string, mType string, flags uintptr, label string) error { + mountFunc = func(source string, target string, mType string, flags uintptr, label string) error { mountProgram := exec.Command(d.options.mountProgram, "-o", label, target) mountProgram.Dir = d.home return mountProgram.Run() } } else { - mount = func(source string, target string, mType string, flags uintptr, label string) error { + mountFunc = func(source string, target string, mType string, flags uintptr, label string) error { return mountFrom(d.home, source, target, mType, flags, label) } } mountTarget = path.Join(id, "merged") } - if err := mount("overlay", mountTarget, "overlay", 0, mountData); err != nil { + flags, data := mount.ParseOptions(mountData) + if err := mountFunc("overlay", mountTarget, "overlay", uintptr(flags), data); err != nil { return "", fmt.Errorf("error creating overlay mount to %s: %v", mountTarget, err) } diff --git a/vendor/github.com/containers/storage/pkg/mount/flags.go b/vendor/github.com/containers/storage/pkg/mount/flags.go index 607dbed43..07a0f4847 100644 --- a/vendor/github.com/containers/storage/pkg/mount/flags.go +++ b/vendor/github.com/containers/storage/pkg/mount/flags.go @@ -111,9 +111,9 @@ func MergeTmpfsOptions(options []string) ([]string, error) { return newOptions, nil } -// Parse fstab type mount options into mount() flags +// ParseOptions parses fstab type mount options into mount() flags // and device specific data -func parseOptions(options string) (int, string) { +func ParseOptions(options string) (int, string) { var ( flag int data []string @@ -138,7 +138,7 @@ func parseOptions(options string) (int, string) { // ParseTmpfsOptions parse fstab type mount options into flags and data func ParseTmpfsOptions(options string) (int, string, error) { - flags, data := parseOptions(options) + flags, data := ParseOptions(options) for _, o := range strings.Split(data, ",") { opt := strings.SplitN(o, "=", 2) if !validFlags[opt[0]] { diff --git a/vendor/github.com/containers/storage/pkg/mount/mount.go b/vendor/github.com/containers/storage/pkg/mount/mount.go index d3caa16bd..7197448da 100644 --- a/vendor/github.com/containers/storage/pkg/mount/mount.go +++ b/vendor/github.com/containers/storage/pkg/mount/mount.go @@ -39,7 +39,7 @@ func Mounted(mountpoint string) (bool, error) { // specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See // flags.go for supported option flags. func Mount(device, target, mType, options string) error { - flag, _ := parseOptions(options) + flag, _ := ParseOptions(options) if flag&REMOUNT != REMOUNT { if mounted, err := Mounted(target); err != nil || mounted { return err @@ -53,7 +53,7 @@ func Mount(device, target, mType, options string) error { // specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See // flags.go for supported option flags. func ForceMount(device, target, mType, options string) error { - flag, data := parseOptions(options) + flag, data := ParseOptions(options) return mount(device, target, mType, uintptr(flag), data) } |