From 95942dba89456433d6386613d005ae8a3866a9c7 Mon Sep 17 00:00:00 2001 From: xiechengsheng Date: Sat, 19 May 2018 18:45:03 +0800 Subject: [PATCH] enable managing more containers for some commands enable managing more containers for some commands --- cli/pause.go | 26 ++++++++++----- cli/restart.go | 13 ++++++-- cli/rm.go | 16 +++++++--- cli/rmi.go | 11 ++++++- cli/start.go | 69 +++++++++++++++++++++++++--------------- cli/unpause.go | 26 ++++++++++----- test/cli_restart_test.go | 21 ++++++++++++ test/cli_start_test.go | 18 +++++++++++ test/cli_unpause_test.go | 19 ++++++++++- 9 files changed, 169 insertions(+), 50 deletions(-) diff --git a/cli/pause.go b/cli/pause.go index 92366d6e59..eb992959fd 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 object 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 [CONTAINERS]", + 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,11 +46,19 @@ 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 } diff --git a/cli/restart.go b/cli/restart.go index 2ece7e3dfa..82c3e20a3d 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" ) @@ -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 7849267e77..307e07aa84 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 container objects 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,13 +60,19 @@ 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 } diff --git a/cli/rmi.go b/cli/rmi.go index 4787f9b134..44694a3a2c 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,12 +45,19 @@ 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 } diff --git a/cli/start.go b/cli/start.go index c4f1f6bfa2..c578e94656 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 @@ -29,9 +31,9 @@ func (s *StartCommand) Init(c *Cli) { s.cli = c s.cmd = &cobra.Command{ Use: "start [OPTIONS] CONTAINER", - Short: "Start a created or stopped 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{} + // We're going 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 } diff --git a/cli/unpause.go b/cli/unpause.go index d1b4a578bc..1d5a319a72 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,11 +39,19 @@ 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 } diff --git a/test/cli_restart_test.go b/test/cli_restart_test.go index 80ddc53661..353d9793f2 100644 --- a/test/cli_restart_test.go +++ b/test/cli_restart_test.go @@ -71,3 +71,24 @@ 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) { + name1 := "TestPouchRestartMultiContainer-1" + name2 := "TestPouchRestartMultiContainer-2" + + res1 := command.PouchRun("run", "-d", "--cpu-share", "20", "--name", name1, busyboxImage) + defer DelContainerForceMultyTime(c, name1) + res1.Assert(c, icmd.Success) + + res2 := command.PouchRun("run", "-d", "--cpu-share", "20", "--name", name2, busyboxImage) + defer DelContainerForceMultyTime(c, name2) + res2.Assert(c, icmd.Success) + + res := command.PouchRun("restart", "-t", "1", name1, name2) + res.Assert(c, icmd.Success) + + if out := res.Combined(); !strings.Contains(out, name1) || !strings.Contains(out, name2) { + c.Fatalf("unexpected output: %s, expected: %s\n%s", out, name1, name2) + } +} diff --git a/test/cli_start_test.go b/test/cli_start_test.go index 17fa51a631..33d8d9d6f0 100644 --- a/test/cli_start_test.go +++ b/test/cli_start_test.go @@ -296,3 +296,21 @@ 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) { + name1 := "TestStartMultiContainer-1" + name2 := "TestStartMultiContainer-2" + + res1 := command.PouchRun("create", "--name", name1, busyboxImage, "top") + defer DelContainerForceMultyTime(c, name1) + res1.Assert(c, icmd.Success) + + res2 := command.PouchRun("create", "--name", name2, busyboxImage, "top") + defer DelContainerForceMultyTime(c, name2) + res2.Assert(c, icmd.Success) + + command.PouchRun("start", name1, name2).Assert(c, icmd.Success) + + command.PouchRun("stop", name1, name2).Assert(c, icmd.Success) +} diff --git a/test/cli_unpause_test.go b/test/cli_unpause_test.go index 6f7f5c8fc4..e78866c87a 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) +}