From 60c173d0607423212cadb7ea5d736680986a79df Mon Sep 17 00:00:00 2001 From: HusterWan Date: Thu, 8 Mar 2018 06:36:25 -0500 Subject: [PATCH] feature: add upgrade interface Signed-off-by: HusterWan --- apis/server/container_bridge.go | 21 +++++ apis/server/router.go | 1 + apis/swagger.yml | 34 ++++++++ apis/types/container_upgrade_config.go | 104 +++++++++++++++++++++++++ cli/main.go | 1 + cli/upgrade.go | 70 +++++++++++++++++ client/container.go | 5 ++ client/interface.go | 1 + daemon/mgr/container.go | 9 +++ test/api_container_upgrade_test.go | 19 +++++ test/cli_upgrade_test.go | 31 ++++++++ 11 files changed, 296 insertions(+) create mode 100644 apis/types/container_upgrade_config.go create mode 100644 cli/upgrade.go create mode 100644 test/api_container_upgrade_test.go create mode 100644 test/cli_upgrade_test.go diff --git a/apis/server/container_bridge.go b/apis/server/container_bridge.go index bb7585945..915b47e2e 100644 --- a/apis/server/container_bridge.go +++ b/apis/server/container_bridge.go @@ -311,3 +311,24 @@ func (s *Server) updateContainer(ctx context.Context, rw http.ResponseWriter, re rw.WriteHeader(http.StatusOK) return nil } + +func (s *Server) upgradeContainer(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { + config := &types.ContainerUpgradeConfig{} + // decode request body + if err := json.NewDecoder(req.Body).Decode(config); err != nil { + return httputils.NewHTTPError(err, http.StatusBadRequest) + } + // validate request body + if err := config.Validate(strfmt.NewFormats()); err != nil { + return httputils.NewHTTPError(err, http.StatusBadRequest) + } + + name := mux.Vars(req)["name"] + + if err := s.ContainerMgr.Upgrade(ctx, name, config); err != nil { + return err + } + + rw.WriteHeader(http.StatusOK) + return nil +} diff --git a/apis/server/router.go b/apis/server/router.go index 51df16219..91ca9bd4f 100644 --- a/apis/server/router.go +++ b/apis/server/router.go @@ -42,6 +42,7 @@ func initRoute(s *Server) http.Handler { r.Path(versionMatcher + "/containers/{name:.*}/pause").Methods(http.MethodPost).Handler(s.filter(s.pauseContainer)) 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)) // 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 a29780fb4..7e768690a 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -696,6 +696,28 @@ paths: 500: $ref: "#/responses/500ErrorResponse" tags: ["Container"] + /containers/{id}/upgrade: + post: + summary: "Upgrade a container with new image and args" + operationId: "ContainerUpgrade" + parameters: + - $ref: "#/parameters/id" + - name: "upgradeConfig" + in: "body" + schema: + $ref: "#/definitions/ContainerUpgradeConfig" + responses: + 200: + description: "no error" + 400: + description: "bad parameter" + schema: + $ref: "#/definitions/Error" + 404: + $ref: "#/responses/404ErrorResponse" + 500: + $ref: "#/responses/500ErrorResponse" + tags: ["Container"] /volumes: get: @@ -1365,6 +1387,18 @@ definitions: additionalProperties: type: "string" + ContainerUpgradeConfig: + description: | + ContainerUpgradeConfig is used for API "POST /containers/upgrade". + It wraps all kinds of config used in container upgrade. + It can be used to encode client params in client and unmarshal request body in daemon side. + allOf: + - $ref: "#/definitions/ContainerConfig" + - type: "object" + properties: + HostConfig: + $ref: "#/definitions/HostConfig" + Resources: description: "A container's resources (cgroups config, ulimits, etc)" type: "object" diff --git a/apis/types/container_upgrade_config.go b/apis/types/container_upgrade_config.go new file mode 100644 index 000000000..00bb61400 --- /dev/null +++ b/apis/types/container_upgrade_config.go @@ -0,0 +1,104 @@ +// 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" +) + +// ContainerUpgradeConfig ContainerUpgradeConfig is used for API "POST /containers/upgrade". +// It wraps all kinds of config used in container upgrade. +// It can be used to encode client params in client and unmarshal request body in daemon side. +// +// swagger:model ContainerUpgradeConfig + +type ContainerUpgradeConfig struct { + ContainerConfig + + // host config + HostConfig *HostConfig `json:"HostConfig,omitempty"` +} + +// UnmarshalJSON unmarshals this object from a JSON structure +func (m *ContainerUpgradeConfig) UnmarshalJSON(raw []byte) error { + + var aO0 ContainerConfig + if err := swag.ReadJSON(raw, &aO0); err != nil { + return err + } + m.ContainerConfig = aO0 + + var data struct { + HostConfig *HostConfig `json:"HostConfig,omitempty"` + } + if err := swag.ReadJSON(raw, &data); err != nil { + return err + } + + m.HostConfig = data.HostConfig + + return nil +} + +// MarshalJSON marshals this object to a JSON structure +func (m ContainerUpgradeConfig) MarshalJSON() ([]byte, error) { + var _parts [][]byte + + aO0, err := swag.WriteJSON(m.ContainerConfig) + if err != nil { + return nil, err + } + _parts = append(_parts, aO0) + + var data struct { + HostConfig *HostConfig `json:"HostConfig,omitempty"` + } + + data.HostConfig = m.HostConfig + + jsonData, err := swag.WriteJSON(data) + if err != nil { + return nil, err + } + _parts = append(_parts, jsonData) + + return swag.ConcatJSON(_parts...), nil +} + +// Validate validates this container upgrade config +func (m *ContainerUpgradeConfig) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.ContainerConfig.Validate(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// MarshalBinary interface implementation +func (m *ContainerUpgradeConfig) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ContainerUpgradeConfig) UnmarshalBinary(b []byte) error { + var res ContainerUpgradeConfig + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/cli/main.go b/cli/main.go index c804a8c25..aecd5983b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -36,6 +36,7 @@ func main() { cli.AddCommand(base, &LoginCommand{}) cli.AddCommand(base, &UpdateCommand{}) cli.AddCommand(base, &LogoutCommand{}) + cli.AddCommand(base, &UpgradeCommand{}) // add generate doc command cli.AddCommand(base, &GenDocCommand{}) diff --git a/cli/upgrade.go b/cli/upgrade.go new file mode 100644 index 000000000..5deb58cfe --- /dev/null +++ b/cli/upgrade.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" +) + +// upgradeDescription is used to describe upgrade command in detail and auto generate command doc. +var upgradeDescription = "" + +// UpgradeCommand use to implement 'upgrade' command, it is used to upgrade a container. +type UpgradeCommand struct { + baseCommand + *container +} + +// Init initialize upgrade command. +func (ug *UpgradeCommand) Init(c *Cli) { + ug.cli = c + ug.cmd = &cobra.Command{ + Use: "upgrade [OPTIONS] IMAGE [COMMAND] [ARG...]", + Short: "Upgrade a container with new image and args", + Long: upgradeDescription, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return ug.runUpgrade(args) + }, + Example: upgradeExample(), + } + ug.addFlags() +} + +// addFlags adds flags for specific command. +func (ug *UpgradeCommand) addFlags() { + flagSet := ug.cmd.Flags() + flagSet.SetInterspersed(false) + + c := addCommonFlags(flagSet) + ug.container = c +} + +// runUpgrade is the entry of UpgradeCommand command. +func (ug *UpgradeCommand) runUpgrade(args []string) error { + config, err := ug.config() + if err != nil { + return fmt.Errorf("failed to upgrade container: %v", err) + } + + config.Image = args[0] + if len(args) > 1 { + config.Cmd = args[1:] + } + containerName := ug.name + if containerName == "" { + return fmt.Errorf("failed to upgrade container: must specify container name") + } + + ctx := context.Background() + apiClient := ug.cli.Client() + + // TODO if error is image not found, we can pull image, and retry upgrade + return apiClient.ContainerUpgrade(ctx, containerName, config.ContainerConfig, config.HostConfig) +} + +//upgradeExample shows examples in exec command, and is used in auto-generated cli docs. +func upgradeExample() string { + return "" +} diff --git a/client/container.go b/client/container.go index d82dfc935..a4820d295 100644 --- a/client/container.go +++ b/client/container.go @@ -181,3 +181,8 @@ func (client *APIClient) ContainerUpdate(ctx context.Context, name string, confi return err } + +// ContainerUpgrade upgrade a container with new image and args. +func (client *APIClient) ContainerUpgrade(ctx context.Context, name string, config types.ContainerConfig, hostConfig *types.HostConfig) error { + return nil +} diff --git a/client/interface.go b/client/interface.go index 36d711632..e8b1f2a39 100644 --- a/client/interface.go +++ b/client/interface.go @@ -33,6 +33,7 @@ type ContainerAPIClient interface { ContainerPause(ctx context.Context, name string) error 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 } // ImageAPIClient defines methods of Image client. diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index ac12f785d..831abff44 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -72,6 +72,9 @@ type ContainerMgr interface { // Update updates the configurations of a container. Update(ctx context.Context, name string, config *types.UpdateConfig) error + + // Upgrade upgrades a container with new image and args. + Upgrade(ctx context.Context, name string, config *types.ContainerUpgradeConfig) error } // ContainerManager is the default implement of interface ContainerMgr. @@ -788,6 +791,12 @@ func (mgr *ContainerManager) Update(ctx context.Context, name string, config *ty return nil } +// Upgrade upgrades a container with new image and args. +func (mgr *ContainerManager) Upgrade(ctx context.Context, name string, config *types.ContainerUpgradeConfig) error { + // TODO + return nil +} + func (mgr *ContainerManager) openContainerIO(id string, attach *AttachConfig) (*containerio.IO, error) { return mgr.openIO(id, attach, false) } diff --git a/test/api_container_upgrade_test.go b/test/api_container_upgrade_test.go new file mode 100644 index 000000000..0040c3705 --- /dev/null +++ b/test/api_container_upgrade_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/alibaba/pouch/test/environment" + + "github.com/go-check/check" +) + +// APIContainerUpgradeSuite is the test suite for container upgrade API. +type APIContainerUpgradeSuite struct{} + +func init() { + check.Suite(&APIContainerUpgradeSuite{}) +} + +// SetUpTest does common setup in the beginning of each test. +func (suite *APIContainerUpgradeSuite) SetUpTest(c *check.C) { + SkipIfFalse(c, environment.IsLinux) +} diff --git a/test/cli_upgrade_test.go b/test/cli_upgrade_test.go new file mode 100644 index 000000000..64aa1cea4 --- /dev/null +++ b/test/cli_upgrade_test.go @@ -0,0 +1,31 @@ +package main + +import ( + //"github.com/alibaba/pouch/apis/types" + "github.com/alibaba/pouch/test/command" + "github.com/alibaba/pouch/test/environment" + + "github.com/go-check/check" + "github.com/gotestyourself/gotestyourself/icmd" +) + +// PouchUpgradeSuite is the test suite for upgrade CLI. +type PouchUpgradeSuite struct{} + +func init() { + check.Suite(&PouchUpgradeSuite{}) +} + +// SetUpSuite does common setup in the beginning of each test suite. +func (suite *PouchUpgradeSuite) 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 *PouchUpgradeSuite) TeadDownTest(c *check.C) { + // TODO +}