diff --git a/cli/pause.go b/cli/pause.go index 92366d6e5..dd5429bae 100644 --- a/cli/pause.go +++ b/cli/pause.go @@ -2,18 +2,20 @@ package main import ( "context" + "errors" "fmt" + "strings" "github.com/spf13/cobra" ) // pauseDescription is used to describe pause command in detail and auto generate command doc. -var pauseDescription = "Pause a running container object in Pouchd. " + +var pauseDescription = "Pause one or more running containers in Pouchd. " + "when pausing, the container will pause its running but hold all the relevant resource." + "This is useful when you wish to pause a container for a while and to restore the running status later." + "The container you paused will pause without being terminated." -// PauseCommand use to implement 'pause' command, it pauses a container. +// PauseCommand use to implement 'pause' command, it pauses one or more containers. type PauseCommand struct { baseCommand } @@ -22,10 +24,10 @@ type PauseCommand struct { func (p *PauseCommand) Init(c *Cli) { p.cli = c p.cmd = &cobra.Command{ - Use: "pause CONTAINER", - Short: "Pause a running container", + Use: "pause CONTAINER [CONTAINER...]", + Short: "Pause one or more running containers", Long: pauseDescription, - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return p.runPause(args) }, @@ -44,21 +46,33 @@ func (p *PauseCommand) runPause(args []string) error { ctx := context.Background() apiClient := p.cli.Client() - container := args[0] + var errs []string + for _, name := range args { + if err := apiClient.ContainerPause(ctx, name); err != nil { + errs = append(errs, err.Error()) + continue + } + fmt.Printf("%s\n", name) + } - if err := apiClient.ContainerPause(ctx, container); err != nil { - return fmt.Errorf("failed to pause container %s: %v", container, err) + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) } + return nil } // pauseExample shows examples in pause command, and is used in auto-generated cli docs. func pauseExample() string { return `$ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Running docker.io/library/busybox:latest runc -$ pouch pause foo +Name ID Status Created Image Runtime +foo2 87259c Up 25 seconds 26 seconds ago registry.hub.docker.com/library/busybox:latest runc +foo1 77188c Up 46 seconds 47 seconds ago registry.hub.docker.com/library/busybox:latest runc +$ pouch pause foo1 foo2 +foo1 +foo2 $ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Paused docker.io/library/busybox:latest runc` +Name ID Status Created Image Runtime +foo2 87259c Up 1 minute(paused) 1 minute ago registry.hub.docker.com/library/busybox:latest runc +foo1 77188c Up 1 minute(paused) 1 minute ago registry.hub.docker.com/library/busybox:latest runc` } diff --git a/cli/restart.go b/cli/restart.go index 2ece7e3df..dd218eff2 100644 --- a/cli/restart.go +++ b/cli/restart.go @@ -2,9 +2,10 @@ package main import ( "context" - "strconv" - + "errors" "fmt" + "strconv" + "strings" "github.com/spf13/cobra" ) @@ -23,7 +24,7 @@ func (rc *RestartCommand) Init(c *Cli) { rc.cli = c rc.cmd = &cobra.Command{ - Use: "restart [OPTION] CONTAINER [CONTAINERS]", + Use: "restart [OPTION] CONTAINER [CONTAINER...]", Short: "restart one or more containers", Long: restartDescription, Args: cobra.MinimumNArgs(1), @@ -46,13 +47,19 @@ func (rc *RestartCommand) runRestart(args []string) error { ctx := context.Background() apiClient := rc.cli.Client() + var errs []string for _, name := range args { if err := apiClient.ContainerRestart(ctx, name, strconv.Itoa(rc.timeout)); err != nil { - return fmt.Errorf("failed to restart container: %v", err) + errs = append(errs, err.Error()) + continue } fmt.Printf("%s\n", name) } + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + return nil } diff --git a/cli/rm.go b/cli/rm.go index 7849267e7..5c2abb37c 100644 --- a/cli/rm.go +++ b/cli/rm.go @@ -2,7 +2,9 @@ package main import ( "context" + "errors" "fmt" + "strings" "github.com/alibaba/pouch/apis/types" @@ -10,10 +12,10 @@ import ( ) var rmDescription = ` -Remove a container object in Pouchd. +Remove one or more containers in Pouchd. If a container be stopped or created, you can remove it. -If the container be running, you can also remove it with flag force. -When the container be removed, the all resource of the container will +If the container is running, you can also remove it with flag force. +When the container is removed, the all resources of the container will be released. ` @@ -58,20 +60,33 @@ func (r *RmCommand) runRm(args []string) error { Volumes: r.removeVolumes, } + var errs []string for _, name := range args { if err := apiClient.ContainerRemove(ctx, name, options); err != nil { - return fmt.Errorf("failed to remove container: %v", err) + errs = append(errs, err.Error()) + continue } fmt.Printf("%s\n", name) } + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + return nil } func rmExample() string { - return `$ pouch rm 5d3152 -5d3152 - -$ pouch rm -f 493028 -493028` + return `$ pouch ps -a +Name ID Status Created Image Runtime +foo 03cd58 Exited (0) 25 seconds 26 seconds ago registry.hub.docker.com/library/busybox:latest runc +$ pouch rm foo +foo +$ pouch ps +Name ID Status Created Image Runtime +foo2 1d979d Up 5 seconds 6 seconds ago registry.hub.docker.com/library/busybox:latest runc +foo1 83e3cf Up 9 seconds 10 seconds ago registry.hub.docker.com/library/busybox:latest runc +$ pouch rm -f foo1 foo2 +foo1 +foo2` } diff --git a/cli/rmi.go b/cli/rmi.go index 4787f9b13..f685cdb0c 100644 --- a/cli/rmi.go +++ b/cli/rmi.go @@ -2,7 +2,9 @@ package main import ( "context" + "errors" "fmt" + "strings" "github.com/spf13/cobra" ) @@ -43,19 +45,27 @@ func (rmi *RmiCommand) runRmi(args []string) error { ctx := context.Background() apiClient := rmi.cli.Client() + var errs []string for _, name := range args { if err := apiClient.ImageRemove(ctx, name, rmi.force); err != nil { - return fmt.Errorf("failed to remove image: %v", err) + errs = append(errs, err.Error()) + continue } fmt.Printf("%s\n", name) } + + if len(errs) > 0 { + return errors.New("failed to remove images: " + strings.Join(errs, "")) + } + return nil } // rmiExample shows examples in rmi command, and is used in auto-generated cli docs. func rmiExample() string { - return `$ pouch rmi registry.hub.docker.com/library/busybox:latest + return `$ pouch rmi registry.hub.docker.com/library/busybox:latest registry.hub.docker.com/library/busybox:1.28 registry.hub.docker.com/library/busybox:latest +registry.hub.docker.com/library/busybox:1.28 $ pouch create --name test registry.hub.docker.com/library/busybox:latest container ID: e5952417f9ee94621bbeaec532be1803ae2dedeb11a80f578a6d621e04a95afd, name: test $ pouch rmi registry.hub.docker.com/library/busybox:latest diff --git a/cli/start.go b/cli/start.go index c4f1f6bfa..ed19f17af 100644 --- a/cli/start.go +++ b/cli/start.go @@ -2,21 +2,23 @@ package main import ( "context" + "errors" "fmt" "io" "os" + "strings" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) // startDescription is used to describe start command in detail and auto generate command doc. -var startDescription = "Start a created container object in Pouchd. " + +var startDescription = "Start one or more created container objects in Pouchd. " + "When starting, the relevant resource preserved during creating period comes into use." + "This is useful when you wish to start a container which has been created in advance." + "The container you started will be running if no error occurs." -// StartCommand use to implement 'start' command, it start a container. +// StartCommand use to implement 'start' command, it start one or more containers. type StartCommand struct { baseCommand detachKeys string @@ -28,10 +30,10 @@ type StartCommand struct { func (s *StartCommand) Init(c *Cli) { s.cli = c s.cmd = &cobra.Command{ - Use: "start [OPTIONS] CONTAINER", - Short: "Start a created or stopped container", + Use: "start [OPTIONS] CONTAINER [CONTAINER...]", + Short: "Start one or more created or stopped containers", Long: startDescription, - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return s.runStart(args) }, @@ -50,14 +52,17 @@ func (s *StartCommand) addFlags() { // runStart is the entry of start command. func (s *StartCommand) runStart(args []string) error { - container := args[0] - - // attach to io. ctx := context.Background() apiClient := s.cli.Client() - var wait chan struct{} + // attach to io. if s.attach || s.stdin { + var wait chan struct{} + // If we want to attach to a container, we should make sure we only have one container. + if len(args) > 1 { + return fmt.Errorf("cannot start and attach multiple containers at once") + } + in, out, err := setRawMode(s.stdin, false) if err != nil { return fmt.Errorf("failed to set raw mode") @@ -68,6 +73,7 @@ func (s *StartCommand) runStart(args []string) error { } }() + container := args[0] conn, br, err := apiClient.ContainerAttach(ctx, container, s.stdin) if err != nil { return fmt.Errorf("failed to attach container: %v", err) @@ -82,28 +88,41 @@ func (s *StartCommand) runStart(args []string) error { go func() { io.Copy(conn, os.Stdin) }() - } - // start container - if err := apiClient.ContainerStart(ctx, container, s.detachKeys); err != nil { - return fmt.Errorf("failed to start container %s: %v", container, err) - } + // start container + if err := apiClient.ContainerStart(ctx, container, s.detachKeys); err != nil { + return fmt.Errorf("failed to start container %s: %v", container, err) + } - // wait the io to finish. - if s.attach || s.stdin { - <-wait - } + // wait the io to finish. + if s.attach || s.stdin { + <-wait + } - info, err := apiClient.ContainerGet(ctx, container) - if err != nil { - return err - } + info, err := apiClient.ContainerGet(ctx, container) + if err != nil { + return err + } - code := info.State.ExitCode - if code != 0 { - return ExitError{Code: int(code)} - } + code := info.State.ExitCode + if code != 0 { + return ExitError{Code: int(code)} + } + } else { + // We're not going to attach to any container, so we just start as many containers as we want. + var errs []string + for _, name := range args { + if err := apiClient.ContainerStart(ctx, name, s.detachKeys); err != nil { + errs = append(errs, err.Error()) + continue + } + fmt.Printf("%s\n", name) + } + if len(errs) > 0 { + return errors.New("failed to start containers: " + strings.Join(errs, "")) + } + } return nil } @@ -144,11 +163,15 @@ func restoreMode(in, out *terminal.State) error { // startExample shows examples in start command, and is used in auto-generated cli docs. func startExample() string { - return `$ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Created docker.io/library/busybox:latest runc -$ pouch start foo + return `$ pouch ps -a +Name ID Status Created Image Runtime +foo2 5a0ede created 1 second ago registry.hub.docker.com/library/busybox:latest runc +foo1 e05637 created 6 seconds ago registry.hub.docker.com/library/busybox:latest runc +$ pouch start foo1 foo2 +foo1 +foo2 $ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Running docker.io/library/busybox:latest runc` +Name ID Status Created Image Runtime +foo2 5a0ede Up 2 seconds 12 seconds ago registry.hub.docker.com/library/busybox:latest runc +foo1 e05637 Up 3 seconds 17 seconds ago registry.hub.docker.com/library/busybox:latest runc` } diff --git a/cli/unpause.go b/cli/unpause.go index d1b4a578b..ed3c4e7cc 100644 --- a/cli/unpause.go +++ b/cli/unpause.go @@ -2,17 +2,19 @@ package main import ( "context" + "errors" "fmt" + "strings" "github.com/spf13/cobra" ) // unpauseDescription is used to describe unpause command in detail and auto generate command doc. -var unpauseDescription = "Unpause a paused container in Pouchd. " + +var unpauseDescription = "Unpause one or more paused containers in Pouchd. " + "when unpausing, the paused container will resumes the process execution within the container." + "The container you unpaused will be running again if no error occurs." -// UnpauseCommand use to implement 'unpause' command, it unpauses a container. +// UnpauseCommand use to implement 'unpause' command, it unpauses one or more containers. type UnpauseCommand struct { baseCommand } @@ -21,10 +23,10 @@ type UnpauseCommand struct { func (p *UnpauseCommand) Init(c *Cli) { p.cli = c p.cmd = &cobra.Command{ - Use: "unpause CONTAINER", - Short: "Unpause a paused container", + Use: "unpause CONTAINER [CONTAINER...]", + Short: "Unpause one or more paused container", Long: unpauseDescription, - Args: cobra.ExactArgs(1), + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return p.runUnpause(args) }, @@ -37,21 +39,33 @@ func (p *UnpauseCommand) runUnpause(args []string) error { ctx := context.Background() apiClient := p.cli.Client() - container := args[0] + var errs []string + for _, name := range args { + if err := apiClient.ContainerUnpause(ctx, name); err != nil { + errs = append(errs, err.Error()) + continue + } + fmt.Printf("%s\n", name) + } - if err := apiClient.ContainerUnpause(ctx, container); err != nil { - return fmt.Errorf("failed to unpause container %s: %v", container, err) + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) } + return nil } // unpauseExample shows examples in unpause command, and is used in auto-generated cli docs. func unpauseExample() string { return `$ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Paused docker.io/library/busybox:latest runc -$ pouch unpause foo +Name ID Status Created Image Runtime +foo2 c95673 Up 13 seconds(paused) 14 seconds ago registry.hub.docker.com/library/busybox:latest runc +foo1 204cc6 Up 17 seconds(paused) 17 seconds ago registry.hub.docker.com/library/busybox:latest runc +$ pouch unpause foo1 foo2 +foo1 +foo2 $ pouch ps -Name ID Status Image Runtime -foo 71b9c1 Running docker.io/library/busybox:latest runc` +Name ID Status Created Image Runtime +foo2 c95673 Up 48 seconds 49 seconds ago registry.hub.docker.com/library/busybox:latest runc +foo1 204cc6 Up 52 seconds 52 seconds ago registry.hub.docker.com/library/busybox:latest runc` } diff --git a/test/cli_restart_test.go b/test/cli_restart_test.go index 80ddc5366..b00eb20ba 100644 --- a/test/cli_restart_test.go +++ b/test/cli_restart_test.go @@ -71,3 +71,20 @@ func (suite *PouchRestartSuite) TestPouchRestartPausedContainer(c *check.C) { command.PouchRun("restart", name).Assert(c, icmd.Success) } + +// TestPouchRestartMultiContainers is to verify the correctness of restarting more than one running container. +func (suite *PouchRestartSuite) TestPouchRestartMultiContainers(c *check.C) { + containernames := []string{"TestPouchRestartMultiContainer-1", "TestPouchRestartMultiContainer-2"} + for _, name := range containernames { + res := command.PouchRun("run", "-d", "--cpu-share", "20", "--name", name, busyboxImage) + defer DelContainerForceMultyTime(c, name) + res.Assert(c, icmd.Success) + } + + res := command.PouchRun("restart", "-t", "1", containernames[0], containernames[1]) + res.Assert(c, icmd.Success) + + if out := res.Combined(); !strings.Contains(out, containernames[0]) || !strings.Contains(out, containernames[1]) { + c.Fatalf("unexpected output: %s, expected: %s\n%s", out, containernames[0], containernames[1]) + } +} diff --git a/test/cli_start_test.go b/test/cli_start_test.go index 17fa51a63..53fb67711 100644 --- a/test/cli_start_test.go +++ b/test/cli_start_test.go @@ -296,3 +296,19 @@ func (suite *PouchStartSuite) TestStartWithPidsLimit(c *check.C) { command.PouchRun("start", name).Assert(c, icmd.Success) } + +// TestStartMultiContainers tries to start more than one container. +func (suite *PouchStartSuite) TestStartMultiContainers(c *check.C) { + containernames := []string{"TestStartMultiContainer-1", "TestStartMultiContainer-2"} + for _, name := range containernames { + res := command.PouchRun("create", "--name", name, busyboxImage, "top") + defer DelContainerForceMultyTime(c, name) + res.Assert(c, icmd.Success) + } + + res := command.PouchRun("start", containernames[0], containernames[1]) + res.Assert(c, icmd.Success) + + res = command.PouchRun("stop", containernames[0], containernames[1]) + res.Assert(c, icmd.Success) +} diff --git a/test/cli_unpause_test.go b/test/cli_unpause_test.go index 6f7f5c8fc..e78866c87 100644 --- a/test/cli_unpause_test.go +++ b/test/cli_unpause_test.go @@ -26,7 +26,7 @@ func (suite *PouchUnpauseSuite) SetUpSuite(c *check.C) { func (suite *PouchUnpauseSuite) TearDownTest(c *check.C) { } -// TestStopWorks tests "pouch unpause" work. +// TestUnpauseWorks tests "pouch unpause" work. func (suite *PouchUnpauseSuite) TestUnpauseWorks(c *check.C) { containernames := []string{"bar1", "bar2"} for _, name := range containernames { @@ -57,3 +57,20 @@ func (suite *PouchUnpauseSuite) TestUnpauseWorks(c *check.C) { } } + +// TestUnpauseMultiContainers is to verify the correctness of unpausing more than one paused container. +func (suite *PouchUnpauseSuite) TestUnpauseMultiContainers(c *check.C) { + containernames := []string{"bar1", "bar2"} + for _, name := range containernames { + command.PouchRun("create", "--name", name, busyboxImage, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, name) + + command.PouchRun("start", name).Assert(c, icmd.Success) + } + + res := command.PouchRun("pause", containernames[0], containernames[1]) + res.Assert(c, icmd.Success) + + res = command.PouchRun("unpause", containernames[0], containernames[1]) + res.Assert(c, icmd.Success) +}