diff --git a/tools-v2/README.md b/tools-v2/README.md index 5ee19eb6ae..07b0f2f310 100644 --- a/tools-v2/README.md +++ b/tools-v2/README.md @@ -83,12 +83,15 @@ A tool for CurveFS & CurveBs. - [update copyset availflag](#update-copyset-availflag) - [update leader-schedule](#update-leader-schedule) - [create](#create-1) + - [create file](#create-file) - [create dir](#create-dir) - [check](#check-1) - [check copyset](#check-copyset-1) - [check chunkserver](#check-chunkserver) - [check server](#check-server) + - [stop](#stop) + - [stop volume snapshot](#stop-volume-snapshot) - [snapshot](#snapshot) - [snapshot copyset](#snapshot-copyset) - [Comparison of old and new commands](#comparison-of-old-and-new-commands) @@ -1871,6 +1874,29 @@ Output: +--------+-----------+-------+------------------+ ``` +#### stop + +##### stop volume snapshot + +stop volume snapshot + +Usage: + +```shell +curve bs stop volumeSnapshot +``` + +Output: + +``` ++--------------------------------------+--------+ +| ID | RESULT | ++--------------------------------------+--------+ +| 4f46d5c5-10cb-4542-adea-1062d9c39018 | success | ++--------------------------------------+--------+ +| d938db41-fb52-4d53-85ee-d667c9653dfb | success | +``` + #### snapshot ##### snapshot copyset diff --git a/tools-v2/internal/error/error.go b/tools-v2/internal/error/error.go index d9676184db..663c17b3da 100644 --- a/tools-v2/internal/error/error.go +++ b/tools-v2/internal/error/error.go @@ -481,6 +481,12 @@ var ( ErrListWarmup = func() *CmdError { return NewInternalCmdError(74, "list warmup progress fail, err: %s") } + ErrBsGetSnapShotListResult = func() *CmdError { + return NewInternalCmdError(75, "get snapshot list results fail, err: %s") + } + ErrBsEligibleSnapShot = func() *CmdError { + return NewInternalCmdError(76, "no eligible snapshot") + } // http error ErrHttpUnreadableResult = func() *CmdError { diff --git a/tools-v2/pkg/cli/command/curvebs/bs.go b/tools-v2/pkg/cli/command/curvebs/bs.go index 3fa7fa86c6..c82ca69528 100644 --- a/tools-v2/pkg/cli/command/curvebs/bs.go +++ b/tools-v2/pkg/cli/command/curvebs/bs.go @@ -34,6 +34,7 @@ import ( "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/query" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/snapshot" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/status" + "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/stop" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/update" ) @@ -54,6 +55,7 @@ func (bsCmd *CurveBsCommand) AddSubCommands() { clean_recycle.NewCleanRecycleCommand(), check.NewCheckCommand(), snapshot.NewSnapshotCommand(), + stop.NewStopCommand(), ) } diff --git a/tools-v2/pkg/cli/command/curvebs/stop/stop.go b/tools-v2/pkg/cli/command/curvebs/stop/stop.go new file mode 100644 index 0000000000..3938f511c8 --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/stop/stop.go @@ -0,0 +1,29 @@ +package stop + +import ( + basecmd "github.com/opencurve/curve/tools-v2/pkg/cli/command" + snapshot "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot" + "github.com/spf13/cobra" +) + +type StopCommand struct { + basecmd.MidCurveCmd +} + +var _ basecmd.MidCurveCmdFunc = (*StopCommand)(nil) + +func (sCmd *StopCommand) AddSubCommands() { + sCmd.Cmd.AddCommand( + snapshot.NewStopVolumeSnapshotCommand(), + ) +} + +func NewStopCommand() *cobra.Command { + sCmd := &StopCommand{ + basecmd.MidCurveCmd{ + Use: "stop", + Short: "stop volume snapshot in the curvebs", + }, + } + return basecmd.NewMidCurveCli(&sCmd.MidCurveCmd, sCmd) +} diff --git a/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/stop_volume.go b/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/stop_volume.go new file mode 100644 index 0000000000..d9cddd967c --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/stop_volume.go @@ -0,0 +1,170 @@ +package volumeSnapshot + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + cmderror "github.com/opencurve/curve/tools-v2/internal/error" + basecmd "github.com/opencurve/curve/tools-v2/pkg/cli/command" +) + +const ( + VERSION = "0.0.6" + LIMIT = "100" + OFFSET = "0" +) + +type Stop struct { + serverAddress []string + timeout time.Duration + + User string + FileName string + UUID string + + messages []string +} + +func newStopSnapShot(serverAddress []string, timeout time.Duration, user, fileName, uuid string) *Stop { + return &Stop{ + serverAddress: serverAddress, + timeout: timeout, + User: user, + FileName: fileName, + UUID: uuid, + } +} + +type Record struct { + UUID string `json:"UUID"` + User string `json:"User"` + File string `json:"File"` + + Result string `json:"Result"` +} + +func (s *Stop) queryStopBy() ([]*Record, *cmderror.CmdError) { + records, err := s.getSnapShotListAll() + return records, err +} + +func (s *Stop) getSnapShotListAll() ([]*Record, *cmderror.CmdError) { + var receiveRecords []*Record + params := QueryParams{ + Action: "GetFileSnapshotList", + Version: VERSION, + User: s.User, + UUID: s.UUID, + File: s.FileName, + Limit: LIMIT, + Offset: OFFSET, + } + for { + records, err := s.getSnapShotList(params) + if err != nil || len(records) == 0 || records == nil { + return receiveRecords, err + } + receiveRecords = append(receiveRecords, records...) + + params.Offset = params.Offset + params.Limit + } +} + +type QueryParams struct { + Action string `json:"Action"` + Version string `json:"Version"` + User string `json:"User"` + File string `json:"File"` + UUID string `json:"UUID"` + Limit string `json:"limit"` + Offset string `json:"Offset"` +} + +func (s *Stop) getSnapShotList(params QueryParams) ([]*Record, *cmderror.CmdError) { + var resp struct { + Code string + Message string + RequestId string + TotalCount int + SnapShots []*Record + } + err := s.query(params, &resp) + if err != nil || resp.Code != "0" { + return resp.SnapShots, err + } + return resp.SnapShots, nil +} + +func (s *Stop) query(params QueryParams, data interface{}) *cmderror.CmdError { + encodedParams := s.encodeParam(params) + + subUri := fmt.Sprintf("/SnapshotCloneService?%s", encodedParams) + + metric := basecmd.NewMetric(s.serverAddress, subUri, s.timeout) + + result, err := basecmd.QueryMetric(metric) + if err.TypeCode() != cmderror.CODE_SUCCESS { + return err + } + + if err := json.Unmarshal([]byte(result), &data); err != nil { + retErr := cmderror.ErrUnmarshalJson() + retErr.Format(err.Error()) + return retErr + } + + return nil +} + +func (s *Stop) encodeParam(params QueryParams) string { + paramsMap := map[string]string{} + if params.Action == "CancelSnapshot" { + paramsMap = map[string]string{ + "Action": params.Action, + "Version": params.Version, + "User": params.User, + "UUID": params.UUID, + "File": params.File, + } + } else { + paramsMap = map[string]string{ + "Action": params.Action, + "Version": params.Version, + "User": params.User, + "UUID": params.UUID, + "File": params.File, + "Limit": params.Limit, + "Offset": params.Offset, + } + } + + values := strings.Builder{} + for key, value := range paramsMap { + if value != "" { + values.WriteString(key) + values.WriteString("=") + values.WriteString(value) + values.WriteString("&") + } + } + str := values.String() + return str[:len(str)-1] +} + +func (s *Stop) stopSnapShot(uuid, user, file string) *cmderror.CmdError { + params := QueryParams{ + Action: "CancelSnapshot", + Version: VERSION, + User: user, + UUID: uuid, + File: file, + } + + err := s.query(params, nil) + if err != nil { + return err + } + return err +} diff --git a/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/volumeSnapshot.go b/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/volumeSnapshot.go new file mode 100644 index 0000000000..444a2ec680 --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/stop/volumeSnapshot/volumeSnapshot.go @@ -0,0 +1,97 @@ +package volumeSnapshot + +import ( + "time" + + cmderror "github.com/opencurve/curve/tools-v2/internal/error" + cobrautil "github.com/opencurve/curve/tools-v2/internal/utils" + basecmd "github.com/opencurve/curve/tools-v2/pkg/cli/command" + "github.com/opencurve/curve/tools-v2/pkg/config" + "github.com/opencurve/curve/tools-v2/pkg/output" + "github.com/spf13/cobra" +) + +const ( + stopExample = `$ curve bs stop volumeSnapshot` +) + +type StopCmd struct { + basecmd.FinalCurveCmd + snapshotAddrs []string + timeout time.Duration + + user string + fileName string + uuid string +} + +var _ basecmd.FinalCurveCmdFunc = (*StopCmd)(nil) + +func (sCmd *StopCmd) Init(cmd *cobra.Command, args []string) error { + snapshotAddrs, err := config.GetBsSnapshotAddrSlice(sCmd.Cmd) + if err.TypeCode() != cmderror.CODE_SUCCESS || len(snapshotAddrs) == 0 { + return err.ToError() + } + sCmd.snapshotAddrs = snapshotAddrs + sCmd.timeout = config.GetFlagDuration(sCmd.Cmd, config.HTTPTIMEOUT) + sCmd.user = config.GetBsFlagString(sCmd.Cmd, config.CURVEBS_USER) + sCmd.fileName = config.GetBsFlagString(sCmd.Cmd, config.CURVEBS_PATH) + sCmd.uuid = config.GetBsFlagString(sCmd.Cmd, config.CURVEBS_SNAPSHOTSEQID) + header := []string{cobrautil.ROW_ID, cobrautil.ROW_RESULT} + sCmd.SetHeader(header) + return nil +} + +func (sCmd *StopCmd) RunCommand(cmd *cobra.Command, args []string) error { + s := newStopSnapShot(sCmd.snapshotAddrs, sCmd.timeout, sCmd.user, sCmd.fileName, sCmd.uuid) + records, err := s.queryStopBy() + if err != nil { + sCmd.Error = err + return sCmd.Error.ToError() + } + if records == nil || len(records) == 0 { + sCmd.Result = cobrautil.ROW_VALUE_FAILED + sCmd.Error = cmderror.ErrBsEligibleSnapShot() + return sCmd.Error.ToError() + } + for _, item := range records { + err := s.stopSnapShot(item.UUID, item.User, item.File) + if err.TypeCode() == cmderror.CODE_SUCCESS { + item.Result = cobrautil.ROW_VALUE_SUCCESS + } else { + item.Result = cobrautil.ROW_VALUE_FAILED + } + sCmd.TableNew.Append([]string{item.UUID, item.Result}) + } + sCmd.Result = records + sCmd.Error = cmderror.Success() + return nil +} + +func (sCmd *StopCmd) Print(cmd *cobra.Command, args []string) error { + return output.FinalCmdOutput(&sCmd.FinalCurveCmd, sCmd) +} + +func (sCmd *StopCmd) ResultPlainOutput() error { + return output.FinalCmdOutputPlain(&sCmd.FinalCurveCmd) +} + +func (sCmd *StopCmd) AddFlags() { + config.AddBsSnapshotCloneFlagOption(sCmd.Cmd) + config.AddHttpTimeoutFlag(sCmd.Cmd) + config.AddBsUserOptionFlag(sCmd.Cmd) + config.AddBsSnapshotSeqIDOptionFlag(sCmd.Cmd) + config.AddBsPathOptionFlag(sCmd.Cmd) +} + +func NewStopVolumeSnapshotCommand() *cobra.Command { + sCmd := &StopCmd{ + FinalCurveCmd: basecmd.FinalCurveCmd{ + Use: "volumeSnapshot", + Short: "stop volume snapshot in curvebs cluster", + Example: stopExample, + }, + } + basecmd.NewFinalCurveCli(&sCmd.FinalCurveCmd, sCmd) + return sCmd.Cmd +} diff --git a/tools-v2/pkg/config/bs.go b/tools-v2/pkg/config/bs.go index 6b1da40887..24277612ad 100644 --- a/tools-v2/pkg/config/bs.go +++ b/tools-v2/pkg/config/bs.go @@ -142,6 +142,8 @@ const ( VIPER_CURVEBS_DEST = "curvebs.dest" CURVEBS_TASKID = "taskid" VIPER_CURVEBS_TASKID = "curvebs.taskid" + CURVEBS_SNAPSHOTSEQID = "snapshotseqid" + VIPER_CURVEBS_SNAPSHOTSEQID = "curvebs.snapshotseqid" CURVEBS_FAILED = "failed" VIPER_CURVEBS_FAILED = "curvebs.failed" ) @@ -197,6 +199,7 @@ var ( CURVEBS_SRC: VIPER_CURVEBS_SRC, CURVEBS_DEST: VIPER_CURVEBS_DEST, CURVEBS_TASKID: VIPER_CURVEBS_TASKID, + CURVEBS_SNAPSHOTSEQID: VIPER_CURVEBS_SNAPSHOTSEQID, CURVEBS_FAILED: VIPER_CURVEBS_FAILED, } @@ -642,6 +645,10 @@ func AddBsTaskIDRequireFlag(cmd *cobra.Command) { AddBsStringRequiredFlag(cmd, CURVEBS_TASKID, "task id") } +func AddBsSnapshotSeqIDOptionFlag(cmd *cobra.Command) { + AddBsStringOptionFlag(cmd, CURVEBS_SNAPSHOTSEQID, "snapshot seqId") +} + // get stingslice flag func GetBsFlagStringSlice(cmd *cobra.Command, flagName string) []string { var value []string