diff --git a/tools-v2/README.md b/tools-v2/README.md index b9fea277f8..f0e2f3f8e3 100644 --- a/tools-v2/README.md +++ b/tools-v2/README.md @@ -56,6 +56,7 @@ A tool for CurveFS & CurveBs. - [list chunkserver](#list-chunkserver) - [list scan-status](#list-scan-status) - [list may-broken-vol](#list-may-broken-vol) + - [list snapshot](#list-snapshot) - [clean-recycle](#clean-recycle) - [query](#query-1) - [query file](#query-file) @@ -1306,6 +1307,27 @@ Output: +----------+ ``` +###### list snapshot + +list curvebs snapshot + +Usage: + +```bash +curve bs list snapshot +``` + +Output: + +```bash ++--------------------------------------+--------------+------+--------+----------------+-------------+----------+---------------------+---------------+ +| SNAPSHOTID | SNAPSHOTNAME | USER | STATUS | SNAPSHOTSEQNUM | FILELENGTH | PROGRESS | CREATETIME | FILE | ++--------------------------------------+--------------+------+--------+----------------+-------------+----------+---------------------+---------------+ +| 807fdac2-5b47-42dc-b884-a4f33b0f2a1a | testsnap | root | 0 | 1 | 10737418240 | 100 | 2023-10-15 16:16:31 | /test/test111 | ++--------------------------------------+--------------+------+--------+----------------+-------------+----------+---------------------+ + +| 204a5316-99cd-44b7-bc58-be6a547b8469 | testsnap1 | root | 0 | 2 | 10737418240 | 100 | 2023-10-25 09:22:12 | | +``` + #### clean-recycle clean the recycle bin diff --git a/tools-v2/internal/utils/row.go b/tools-v2/internal/utils/row.go index 33590c75f8..8196bff29f 100644 --- a/tools-v2/internal/utils/row.go +++ b/tools-v2/internal/utils/row.go @@ -49,6 +49,7 @@ const ( ROW_FILE_NAME = "fileName" ROW_FILE_SIZE = "fileSize" ROW_FILE_TYPE = "fileType" + ROW_FILE_LENGTH = "fileLength" ROW_FS_ID = "fsId" ROW_FS_NAME = "fsName" ROW_FS_TYPE = "fsType" @@ -141,6 +142,10 @@ const ( ROW_UNHEALTHY_COPYSET_RATIO = "unhealthyCopysetRatio" ROW_EXT_ADDR = "extAddr" + ROW_SNAPSHOT_ID = "snapshotId" + ROW_SNAPSHOT_NAME = "snapshotName" + ROW_SNAPSHOT_SEQNUM = "snapshotSeqNum" + // s3 ROW_S3CHUNKINFO_CHUNKID = "s3ChunkId" ROW_S3CHUNKINFO_LENGTH = "s3Length" diff --git a/tools-v2/internal/utils/snapshot.go b/tools-v2/internal/utils/snapshot.go index 358c7b4c44..01e7104618 100644 --- a/tools-v2/internal/utils/snapshot.go +++ b/tools-v2/internal/utils/snapshot.go @@ -25,6 +25,7 @@ package cobrautil import ( "fmt" "net/url" + "strings" ) const ( @@ -42,6 +43,7 @@ const ( QueryOffset = "Offset" QueryStatus = "Status" QueryType = "Type" + QueryFile = "File" ActionClone = "Clone" ActionRecover = "Recover" @@ -56,6 +58,8 @@ const ( ResultCode = "Code" ResultSuccess = "0" + Limit = "100" + Offset = "0" ) func NewSnapshotQuerySubUri(params map[string]any) string { @@ -70,3 +74,19 @@ func NewSnapshotQuerySubUri(params map[string]any) string { return "/SnapshotCloneService?" + values.Encode() } + +func NewSubUri(params map[string]any) string { + values := strings.Builder{} + for key, value := range params { + if value != "" { + values.WriteString(key) + values.WriteString("=") + values.WriteString(value.(string)) + values.WriteString("&") + } + } + str := values.String() + encodedParams := str[:len(str)-1] + subUri := fmt.Sprintf("/SnapshotCloneService?%s", encodedParams) + return subUri +} diff --git a/tools-v2/pkg/cli/command/curvebs/list/list.go b/tools-v2/pkg/cli/command/curvebs/list/list.go index 0b2e4266e3..44472a01a9 100644 --- a/tools-v2/pkg/cli/command/curvebs/list/list.go +++ b/tools-v2/pkg/cli/command/curvebs/list/list.go @@ -32,6 +32,7 @@ import ( may_broken_vol "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/list/may-broken-vol" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/list/scanstatus" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/list/server" + "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/list/snapshot" "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/list/space" "github.com/spf13/cobra" ) @@ -53,6 +54,7 @@ func (listCmd *ListCommand) AddSubCommands() { scanstatus.NewScanStatusCommand(), may_broken_vol.NewMayBrokenVolCommand(), formatstatus.NewFormatStatusCommand(), + snapshot.NewSnapShotCommand(), ) } diff --git a/tools-v2/pkg/cli/command/curvebs/list/snapshot/snapshot.go b/tools-v2/pkg/cli/command/curvebs/list/snapshot/snapshot.go new file mode 100644 index 0000000000..79957c811e --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/list/snapshot/snapshot.go @@ -0,0 +1,176 @@ +package snapshot + +import ( + "encoding/json" + "fmt" + "strconv" + "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 ( + snapshotExample = `$ curve bs list snapshot` +) + +type Response struct { + Code string `json:"Code"` + Message string `json:"Message"` + RequestId string `json:"RequestId"` + TotalCount int `json:"TotalCount"` + SnapShots []*SnapShotInfo `json:"Snapshots"` +} + +type SnapShotInfo struct { + File string `json:"File"` + FileLength int `json:"FileLength"` + Name string `json:"Name"` + Progress int `json:"Progress"` + SeqNum int `json:"SeqNum"` + Status int `json:"Status"` + Time int `json:"Time"` + UUID string `json:"UUID"` + User string `json:"User"` +} + +type SnapShotCommand struct { + basecmd.FinalCurveCmd + snapshotAddrs []string + timeout time.Duration + + user string + file string + uuid string +} + +var _ basecmd.FinalCurveCmdFunc = (*SnapShotCommand)(nil) + +func NewSnapShotCommand() *cobra.Command { + return NewListSnapShotCommand().Cmd +} + +func NewListSnapShotCommand() *SnapShotCommand { + snapShotCommand := &SnapShotCommand{ + FinalCurveCmd: basecmd.FinalCurveCmd{ + Use: "snapshot", + Short: "list snapshot information in curvebs", + Example: snapshotExample, + }, + } + + basecmd.NewFinalCurveCli(&snapShotCommand.FinalCurveCmd, snapShotCommand) + return snapShotCommand +} + +func (sCmd *SnapShotCommand) AddFlags() { + config.AddBsSnapshotCloneFlagOption(sCmd.Cmd) + config.AddHttpTimeoutFlag(sCmd.Cmd) + config.AddBsUserOptionFlag(sCmd.Cmd) + config.AddBsSnapshotIDOptionFlag(sCmd.Cmd) + config.AddBsPathOptionFlag(sCmd.Cmd) +} + +func (sCmd *SnapShotCommand) 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.file = config.GetBsFlagString(sCmd.Cmd, config.CURVEBS_PATH) + sCmd.uuid = config.GetBsFlagString(sCmd.Cmd, config.CURVEBS_SNAPSHOT_ID) + header := []string{ + cobrautil.ROW_SNAPSHOT_ID, + cobrautil.ROW_SNAPSHOT_NAME, + cobrautil.ROW_USER, + cobrautil.ROW_STATUS, + cobrautil.ROW_SNAPSHOT_SEQNUM, + cobrautil.ROW_FILE_LENGTH, + cobrautil.ROW_PROGRESS, + cobrautil.ROW_CREATE_TIME, + cobrautil.ROW_FILE, + } + sCmd.SetHeader(header) + sCmd.TableNew.SetAutoMergeCellsByColumnIndex(cobrautil.GetIndexSlice( + sCmd.Header, []string{cobrautil.ROW_FILE}, + )) + return nil +} + +func (sCmd *SnapShotCommand) Print(cmd *cobra.Command, args []string) error { + return output.FinalCmdOutput(&sCmd.FinalCurveCmd, sCmd) +} + +func (sCmd *SnapShotCommand) RunCommand(cmd *cobra.Command, args []string) error { + params := map[string]any{ + cobrautil.QueryAction: cobrautil.ActionGetFileSnapshotList, + cobrautil.QueryVersion: cobrautil.Version, + cobrautil.QueryUser: sCmd.user, + cobrautil.QueryFile: sCmd.file, + cobrautil.QueryLimit: cobrautil.Limit, + cobrautil.QueryOffset: cobrautil.Offset, + } + if sCmd.uuid != "*" { + params[cobrautil.QueryUUID] = sCmd.uuid + } + snapshotsInfo, err := ListSnapShot(sCmd.snapshotAddrs, sCmd.timeout, params) + if err != nil { + sCmd.Error = err + return sCmd.Error.ToError() + } + rows := make([]map[string]string, 0) + for _, item := range snapshotsInfo { + row := make(map[string]string) + row[cobrautil.ROW_SNAPSHOT_ID] = item.UUID + row[cobrautil.ROW_SNAPSHOT_NAME] = item.Name + row[cobrautil.ROW_USER] = item.User + row[cobrautil.ROW_FILE] = item.File + row[cobrautil.ROW_STATUS] = fmt.Sprintf("%d", item.Status) + row[cobrautil.ROW_SNAPSHOT_SEQNUM] = fmt.Sprintf("%d", item.SeqNum) + row[cobrautil.ROW_FILE_LENGTH] = fmt.Sprintf("%d", item.FileLength) + row[cobrautil.ROW_PROGRESS] = fmt.Sprintf("%d", item.Progress) + row[cobrautil.ROW_CREATE_TIME] = time.Unix(int64(item.Time/1000000), 0).Format("2006-01-02 15:04:05") + rows = append(rows, row) + } + list := cobrautil.ListMap2ListSortByKeys(rows, sCmd.Header, []string{cobrautil.ROW_FILE, cobrautil.ROW_SNAPSHOT_NAME, cobrautil.ROW_SNAPSHOT_ID}) + sCmd.TableNew.AppendBulk(list) + sCmd.Result = rows + sCmd.Error = cmderror.Success() + return nil +} + +func (sCmd *SnapShotCommand) ResultPlainOutput() error { + return output.FinalCmdOutputPlain(&sCmd.FinalCurveCmd) +} + +func ListSnapShot(addrs []string, timeout time.Duration, params map[string]any) ([]*SnapShotInfo, *cmderror.CmdError) { + var snapshotsInfo []*SnapShotInfo + for { + var resp Response + subUri := cobrautil.NewSubUri(params) + metric := basecmd.NewMetric(addrs, subUri, timeout) + + result, err := basecmd.QueryMetric(metric) + if err.TypeCode() != cmderror.CODE_SUCCESS { + return snapshotsInfo, err + } + if err := json.Unmarshal([]byte(result), &resp); err != nil { + retErr := cmderror.ErrUnmarshalJson() + retErr.Format(err.Error()) + return snapshotsInfo, retErr + } + if len(resp.SnapShots) == 0 || resp.SnapShots == nil { + return snapshotsInfo, nil + } + snapshotsInfo = append(snapshotsInfo, resp.SnapShots...) + offsetValue, _ := strconv.Atoi(params[cobrautil.QueryOffset].(string)) + limitValue, _ := strconv.Atoi(params[cobrautil.QueryLimit].(string)) + params[cobrautil.QueryOffset] = strconv.Itoa(offsetValue + limitValue) + } +} diff --git a/tools-v2/pkg/config/bs.go b/tools-v2/pkg/config/bs.go index 08b80dd179..4b18821b78 100644 --- a/tools-v2/pkg/config/bs.go +++ b/tools-v2/pkg/config/bs.go @@ -142,6 +142,9 @@ const ( VIPER_CURVEBS_DEST = "curvebs.dest" CURVEBS_TASKID = "taskid" VIPER_CURVEBS_TASKID = "curvebs.taskid" + CURVEBS_SNAPSHOT_ID = "snapshotid" + VIPER_CURVEBS_SNAPSHOT_ID = "curvebs.snapshotqid" + CURVEBS_DEFAULT_SNAPSHOT_ID = "*" CURVEBS_FAILED = "failed" VIPER_CURVEBS_FAILED = "curvebs.failed" CURVEBS_CHUNK_SIZE = "chunksize" @@ -202,6 +205,7 @@ var ( CURVEBS_SRC: VIPER_CURVEBS_SRC, CURVEBS_DEST: VIPER_CURVEBS_DEST, CURVEBS_TASKID: VIPER_CURVEBS_TASKID, + CURVEBS_SNAPSHOT_ID: VIPER_CURVEBS_SNAPSHOT_ID, CURVEBS_FAILED: VIPER_CURVEBS_FAILED, CURVEBS_CHUNK_SIZE: VIPER_CURVEBS_CHUNK_SIZE, CURVEBS_CHECK_HASH: VIPER_CURVEBS_CHECK_HASH, @@ -229,6 +233,7 @@ var ( CURVEBS_LOGIC_POOL_ID: CURVEBS_DEFAULT_LOGIC_POOL_ID, CURVEBS_COPYSET_ID: CURVEBS_DEFAULT_COPYSET_ID, CURVEBS_CHECK_HASH: CURVEBS_DEFAULT_CHECK_HASH, + CURVEBS_SNAPSHOT_ID: CURVEBS_DEFAULT_SNAPSHOT_ID, } ) @@ -666,6 +671,10 @@ func AddBsTaskIDRequireFlag(cmd *cobra.Command) { AddBsStringRequiredFlag(cmd, CURVEBS_TASKID, "task id") } +func AddBsSnapshotIDOptionFlag(cmd *cobra.Command) { + AddBsStringOptionFlag(cmd, CURVEBS_SNAPSHOT_ID, "snapshot seqId") +} + // get stingslice flag func GetBsFlagStringSlice(cmd *cobra.Command, flagName string) []string { var value []string