diff --git a/apis/server/container_bridge.go b/apis/server/container_bridge.go index 915b47e2e..0e9e4cd03 100644 --- a/apis/server/container_bridge.go +++ b/apis/server/container_bridge.go @@ -332,3 +332,7 @@ func (s *Server) upgradeContainer(ctx context.Context, rw http.ResponseWriter, r rw.WriteHeader(http.StatusOK) return nil } + +func (s *Server) topContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { + return nil +} diff --git a/apis/server/router.go b/apis/server/router.go index 91ca9bd4f..58310fef2 100644 --- a/apis/server/router.go +++ b/apis/server/router.go @@ -43,6 +43,7 @@ func initRoute(s *Server) http.Handler { r.Path(versionMatcher + "/containers/{name:.*}/unpause").Methods(http.MethodPost).Handler(s.filter(s.unpauseContainer)) r.Path(versionMatcher + "/containers/{name:.*}/update").Methods(http.MethodPost).Handler(s.filter(s.updateContainer)) r.Path(versionMatcher + "/containers/{name:.*}/upgrade").Methods(http.MethodPost).Handler(s.filter(s.upgradeContainer)) + r.Path(versionMatcher + "/containers/{name:.*}/top").Methods(http.MethodGet).Handler(s.filter(s.topContainer)) // image r.Path(versionMatcher + "/images/create").Methods(http.MethodPost).Handler(s.filter(s.pullImage)) diff --git a/apis/swagger.yml b/apis/swagger.yml index de201ccd9..efc484561 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -450,6 +450,27 @@ paths: $ref: "#/responses/500ErrorResponse" tags: ["Container"] + /containers/{id}/top: + post: + summary: "Display the running processes of a container" + operationId: "ContainerTop" + parameters: + - $ref: "#/parameters/id" + - name: "ps_args" + in: "query" + description: "top arguments" + type: "string" + responses: + 200: + description: "no error" + schema: + $ref: "#/definitions/ContainerProcessList" + 404: + $ref: "#/responses/404ErrorResponse" + 500: + $ref: "#/responses/500ErrorResponse" + tags: ["Container"] + /containers/{id}: delete: summary: "Remove one container" @@ -2002,6 +2023,22 @@ definitions: minItems: 1 items: type: "string" + ContainerProcessList: + description: OK Response to ContainerTop operation + type: "object" + properties: + Titles: + description: "The ps column titles" + type: "array" + items: + type: "string" + Processes: + description: "Each process running in the container, where each is process is an array of values corresponding to the titles" + type: "array" + items: + type: "array" + items: + type: "string" ExecCreateResp: type: "object" diff --git a/apis/types/container_process_list.go b/apis/types/container_process_list.go new file mode 100644 index 000000000..d9733a845 --- /dev/null +++ b/apis/types/container_process_list.go @@ -0,0 +1,85 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package types + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" + "github.com/go-openapi/swag" +) + +// ContainerProcessList OK Response to ContainerTop operation +// swagger:model ContainerProcessList + +type ContainerProcessList struct { + + // Each process running in the container, where each is process is an array of values corresponding to the titles + Processes [][]string `json:"Processes"` + + // The ps column titles + Titles []string `json:"Titles"` +} + +/* polymorph ContainerProcessList Processes false */ + +/* polymorph ContainerProcessList Titles false */ + +// Validate validates this container process list +func (m *ContainerProcessList) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateProcesses(formats); err != nil { + // prop + res = append(res, err) + } + + if err := m.validateTitles(formats); err != nil { + // prop + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ContainerProcessList) validateProcesses(formats strfmt.Registry) error { + + if swag.IsZero(m.Processes) { // not required + return nil + } + + return nil +} + +func (m *ContainerProcessList) validateTitles(formats strfmt.Registry) error { + + if swag.IsZero(m.Titles) { // not required + return nil + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ContainerProcessList) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ContainerProcessList) UnmarshalBinary(b []byte) error { + var res ContainerProcessList + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/cli/top.go b/cli/top.go new file mode 100644 index 000000000..e68190d34 --- /dev/null +++ b/cli/top.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "fmt" + + //"github.com/alibaba/pouch/apis/types" + //"github.com/alibaba/pouch/pkg/reference" + + "github.com/spf13/cobra" +) + +// topDescription +var topDescription = "" + +// TopCommand use to implement 'top' command, it displays all processes in a container. +type TopCommand struct { + baseCommand + args []string +} + +// Init initialize top command. +func (top *TopCommand) Init(c *Cli) { + top.cli = c + top.cmd = &cobra.Command{ + Use: "top CONTAINER", + Short: "Display the running processes of a container", + Long: topDescription, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return top.runTop(args) + }, + Example: topExamples(), + } +} + +// runTop is the entry of top command. +func (top *TopCommand) runTop(args []string) error { + ctx := context.Background() + apiClient := top.cli.Client() + + container := args[0] + + arguments := args[1:] + + resp, err := apiClient.ContainerTop(ctx, container, arguments) + if err != nil { + return fmt.Errorf("failed to execute top command in container %s: %v", container, err) + } + + fmt.Println(resp) + return nil +} + +// topExamples shows examples in top command, and is used in auto-generated cli docs. +func topExamples() string { + return `` +} diff --git a/client/container.go b/client/container.go index a4820d295..5375b2416 100644 --- a/client/container.go +++ b/client/container.go @@ -5,6 +5,7 @@ import ( "context" "net" "net/url" + "strings" "github.com/alibaba/pouch/apis/types" ) @@ -186,3 +187,21 @@ func (client *APIClient) ContainerUpdate(ctx context.Context, name string, confi func (client *APIClient) ContainerUpgrade(ctx context.Context, name string, config types.ContainerConfig, hostConfig *types.HostConfig) error { return nil } + +// ContainerTop shows process information from within a container. +func (client *APIClient) ContainerTop(ctx context.Context, name string, arguments []string) (types.ContainerProcessList, error) { + response := types.ContainerProcessList{} + query := url.Values{} + if len(arguments) > 0 { + query.Set("ps_args", strings.Join(arguments, " ")) + } + + resp, err := client.get(ctx, "/containers/"+name+"/top", query, nil) + if err != nil { + return response, err + } + + err = decodeBody(&response, resp.Body) + ensureCloseReader(resp) + return response, err +} diff --git a/client/interface.go b/client/interface.go index e8b1f2a39..b0685392b 100644 --- a/client/interface.go +++ b/client/interface.go @@ -34,6 +34,7 @@ type ContainerAPIClient interface { ContainerUnpause(ctx context.Context, name string) error ContainerUpdate(ctx context.Context, name string, config *types.UpdateConfig) error ContainerUpgrade(ctx context.Context, name string, config types.ContainerConfig, hostConfig *types.HostConfig) error + ContainerTop(ctx context.Context, name string, arguments []string) (types.ContainerProcessList, error) } // ImageAPIClient defines methods of Image client. diff --git a/test/api_container_top_test.go b/test/api_container_top_test.go new file mode 100644 index 000000000..0ad4fa6c9 --- /dev/null +++ b/test/api_container_top_test.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/alibaba/pouch/test/environment" + //"github.com/alibaba/pouch/test/request" + + "github.com/go-check/check" +) + +// APIContainerTopSuite is the test suite for container top API. +type APIContainerTopSuite struct{} + +func init() { + check.Suite(&APIContainerTopSuite{}) +} + +// SetUpTest does common setup in the beginning of each test. +func (suite *APIContainerTopSuite) SetUpTest(c *check.C) { + SkipIfFalse(c, environment.IsLinux) + + // TODO Add more +} diff --git a/test/cli_top_test.go b/test/cli_top_test.go new file mode 100644 index 000000000..3ee885aef --- /dev/null +++ b/test/cli_top_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/alibaba/pouch/test/command" + "github.com/alibaba/pouch/test/environment" + + "github.com/go-check/check" + "github.com/gotestyourself/gotestyourself/icmd" +) + +// PouchTopSuite is the test suite for top CLI. +type PouchTopSuite struct{} + +func init() { + check.Suite(&PouchTopSuite{}) +} + +// SetupSuite does common setup in the beginning of each test suite. +func (suite *PouchTopSuite) SetupSuite(c *check.C) { + SkipIfFalse(c, environment.IsLinux) + + environment.PruneAllContainers(apiClient) + + command.PouchRun("pull", busyboxImage).Assert(c, icmd.Success) +} + +// TearDownTest does cleanup work in the end of each test. +func (suite *PouchTopSuite) TearDownTest(c *check.C) { +}