diff --git a/tools-v2/README.md b/tools-v2/README.md index 309ed57734..a4d45ee605 100644 --- a/tools-v2/README.md +++ b/tools-v2/README.md @@ -97,6 +97,8 @@ A tool for CurveFS & CurveBs. - [snapshot copyset](#snapshot-copyset) - [stop](#stop) - [stop snapshot](#stop-snapshot) + - [recover](#recover) + - [recover volume](#recover-volume) - [Comparison of old and new commands](#comparison-of-old-and-new-commands) - [curve fs](#curve-fs) - [curve bs](#curve-bs) @@ -1997,6 +1999,27 @@ Output: +--------------------------------------+--------------+---------+ ``` +#### recover + +##### recover volume + +recover volume from recycleBin + +Usage: +```shell +curve bs recover volume --path /test/path --user root +``` + +Output: +``` ++-----------+---------+ +| FILENAME | RESULT | ++-----------+---------+ +| test/path | success | ++-----------+---------+ +``` + + ## Comparison of old and new commands ### curve fs diff --git a/tools-v2/internal/error/error.go b/tools-v2/internal/error/error.go index aea8ad92d3..381db50a67 100644 --- a/tools-v2/internal/error/error.go +++ b/tools-v2/internal/error/error.go @@ -500,7 +500,9 @@ var ( ErrInvalidMetaServerAddr = func() *CmdError { return NewInternalCmdError(80, "invalid metaserver external addr: %s") } - + ErrBsRecoverFile = func() *CmdError { + return NewInternalCmdError(81, "recover file fail, err: %s") + } // http error ErrHttpUnreadableResult = func() *CmdError { return NewHttpResultCmdError(1, "http response is unreadable, the uri is: %s, the error is: %s") diff --git a/tools-v2/pkg/cli/command/curvebs/recover/recover.go b/tools-v2/pkg/cli/command/curvebs/recover/recover.go new file mode 100644 index 0000000000..61f749e5ea --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/recover/recover.go @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023 NetEase Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Project: CurveCli + * Created Date: 2023-11-15 + * Author: CrystalAnalyst + */ +package recover + +import ( + basecmd "github.com/opencurve/curve/tools-v2/pkg/cli/command" + volume "github.com/opencurve/curve/tools-v2/pkg/cli/command/curvebs/recover/volume" + "github.com/spf13/cobra" +) + +type RecoverCommand struct { + basecmd.MidCurveCmd +} + +var _ basecmd.MidCurveCmdFunc = (*RecoverCommand)(nil) + +func (rCmd *RecoverCommand) AddSubCommands() { + rCmd.Cmd.AddCommand( + volume.NewVolumeCommand(), + ) +} + +func NewRecoverCommand() *cobra.Command { + rCmd := &RecoverCommand{ + basecmd.MidCurveCmd{ + Use: "recover", + Short: "recover resources in curvebs cluster", + }, + } + return basecmd.NewMidCurveCli(&rCmd.MidCurveCmd, rCmd) +} diff --git a/tools-v2/pkg/cli/command/curvebs/recover/volume/volume.go b/tools-v2/pkg/cli/command/curvebs/recover/volume/volume.go new file mode 100644 index 0000000000..9fdd3f3b28 --- /dev/null +++ b/tools-v2/pkg/cli/command/curvebs/recover/volume/volume.go @@ -0,0 +1,165 @@ +package volume + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "google.golang.org/grpc" + + 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/opencurve/curve/tools-v2/proto/proto/nameserver2" +) + +const ( + RecoverExample = `curve bs recover volume --path /curvebs-volume-path + --user username [--password password] [--fileid fileid]` +) + +func NewVolumeCommand() *cobra.Command { + return NewRecoverFileCommand().Cmd +} + +func NewRecoverFileCommand() *RecoverFileCommand { + RecoverFileCommand := &RecoverFileCommand{ + FinalCurveCmd: basecmd.FinalCurveCmd{ + Use: "volume", + Short: "recover volume in curvebs", + Example: RecoverExample, + }, + } + basecmd.NewFinalCurveCli(&RecoverFileCommand.FinalCurveCmd, RecoverFileCommand) + return RecoverFileCommand +} + +type RecoverCertainFileRPC struct { + Info *basecmd.Rpc + Request *nameserver2.RecoverFileRequest + mdsClient nameserver2.CurveFSServiceClient +} + +var _ basecmd.RpcFunc = (*RecoverCertainFileRPC)(nil) + +func (gRpc *RecoverCertainFileRPC) NewRpcClient(cc grpc.ClientConnInterface) { + gRpc.mdsClient = nameserver2.NewCurveFSServiceClient(cc) +} + +func (gRpc *RecoverCertainFileRPC) Stub_Func(ctx context.Context) (interface{}, error) { + return gRpc.mdsClient.RecoverFile(ctx, gRpc.Request) +} + +type RecoverFileCommand struct { + basecmd.FinalCurveCmd + Rpc *RecoverCertainFileRPC + Response *nameserver2.RecoverFileResponse +} + +var _ basecmd.FinalCurveCmdFunc = (*RecoverFileCommand)(nil) + +func (recoverCommand *RecoverFileCommand) Init(cmd *cobra.Command, args []string) error { + mdsAddrs, err := config.GetBsMdsAddrSlice(recoverCommand.Cmd) + if err.TypeCode() != cmderror.CODE_SUCCESS { + return err.ToError() + } + //get the default timeout and retrytimes + timeout := config.GetFlagDuration(recoverCommand.Cmd, config.RPCTIMEOUT) + retrytimes := config.GetFlagInt32(recoverCommand.Cmd, config.RPCRETRYTIMES) + //get the params needed from commandline + path := config.GetBsFlagString(recoverCommand.Cmd, config.CURVEBS_PATH) + username := config.GetBsFlagString(recoverCommand.Cmd, config.CURVEBS_USER) + password := config.GetBsFlagString(recoverCommand.Cmd, config.CURVEBS_PASSWORD) + fileId := config.GetBsFlagUint64(recoverCommand.Cmd, config.CURVEBS_FILE_ID) + date, errDat := cobrautil.GetTimeofDayUs() + if errDat.TypeCode() != cmderror.CODE_SUCCESS { + return errDat.ToError() + } + + //add basic info + recoverRequest := nameserver2.RecoverFileRequest{ + FileName: &path, + Owner: &username, + FileId: &fileId, + Date: &date, + } + //add signature + if username == viper.GetString(config.VIPER_CURVEBS_USER) && len(password) != 0 { + strSig := cobrautil.GetString2Signature(date, username) + sig := cobrautil.CalcString2Signature(strSig, password) + recoverRequest.Signature = &sig + } + recoverCommand.Rpc = &RecoverCertainFileRPC{ + Info: basecmd.NewRpc(mdsAddrs, timeout, retrytimes, "recoverFile"), + Request: &recoverRequest, + } + header := []string{ + cobrautil.ROW_FILE_NAME, + cobrautil.ROW_RESULT, + } + recoverCommand.SetHeader(header) + return nil +} + +func (recoverCommand *RecoverFileCommand) AddFlags() { + config.AddRpcTimeoutFlag(recoverCommand.Cmd) + config.AddRpcRetryTimesFlag(recoverCommand.Cmd) + config.AddBsMdsFlagOption(recoverCommand.Cmd) + //file path and user name is required. + config.AddBsPathRequiredFlag(recoverCommand.Cmd) + config.AddBsUserRequireFlag(recoverCommand.Cmd) + //password and fileID is optional. + config.AddBsPasswordOptionFlag(recoverCommand.Cmd) + config.AddBsFileIdOptionFlag(recoverCommand.Cmd) +} + +func (recoverCommand *RecoverFileCommand) RunCommand(cmd *cobra.Command, args []string) error { + result, err := basecmd.GetRpcResponse(recoverCommand.Rpc.Info, recoverCommand.Rpc) + if err.TypeCode() != cmderror.CODE_SUCCESS { + recoverCommand.Error = err + recoverCommand.Result = result + return err.ToError() + } + recoverCommand.Response = result.(*nameserver2.RecoverFileResponse) + if recoverCommand.Response.GetStatusCode() != nameserver2.StatusCode_kOK { + recoverCommand.Error = cmderror.ErrBsRecoverFile() + recoverCommand.Result = result + return recoverCommand.Error.ToError() + } + out := make(map[string]string) + out[cobrautil.ROW_FILE_NAME] = *recoverCommand.Rpc.Request.FileName + out[cobrautil.ROW_RESULT] = cobrautil.ROW_VALUE_SUCCESS + list := cobrautil.Map2List(out, []string{cobrautil.ROW_FILE_NAME, cobrautil.ROW_RESULT}) + recoverCommand.TableNew.Append(list) + recoverCommand.Result, recoverCommand.Error = result, cmderror.Success() + return nil +} + +func (recoverCommand *RecoverFileCommand) Print(cmd *cobra.Command, args []string) error { + return output.FinalCmdOutput(&recoverCommand.FinalCurveCmd, recoverCommand) +} + +func (recoverCommand *RecoverFileCommand) ResultPlainOutput() error { + return output.FinalCmdOutputPlain(&recoverCommand.FinalCurveCmd) +} + +func RecoverFile(caller *cobra.Command) (*nameserver2.RecoverFileResponse, *cmderror.CmdError) { + rCmd := NewRecoverFileCommand() + config.AlignFlagsValue(caller, rCmd.Cmd, []string{ + config.RPCRETRYTIMES, config.RPCTIMEOUT, config.CURVEBS_MDSADDR, + config.CURVEBS_PATH, config.CURVEBS_USER, + config.CURVEBS_PASSWORD, config.CURVEBS_FILE_ID, + }) + rCmd.Cmd.SilenceErrors = true + rCmd.Cmd.SilenceUsage = true + rCmd.Cmd.SetArgs([]string{"--format", config.FORMAT_NOOUT}) + err := rCmd.Cmd.Execute() + if err != nil { + retErr := cmderror.ErrBsRecoverFile() + retErr.Format(err.Error()) + return rCmd.Response, retErr + } + return rCmd.Response, cmderror.Success() +} diff --git a/tools-v2/pkg/config/bs.go b/tools-v2/pkg/config/bs.go index 2eceb2b9ca..6d91487a12 100644 --- a/tools-v2/pkg/config/bs.go +++ b/tools-v2/pkg/config/bs.go @@ -147,11 +147,13 @@ const ( CURVEBS_DEFAULT_SNAPSHOT_ID = "*" CURVEBS_FAILED = "failed" VIPER_CURVEBS_FAILED = "curvebs.failed" - CURVEBS_CHUNK_SIZE = "chunksize" - VIPER_CURVEBS_CHUNK_SIZE = "curvebs.chunksize" - CURVEBS_CHECK_HASH = "checkhash" - VIPER_CURVEBS_CHECK_HASH = "curvebs.checkhash" - CURVEBS_DEFAULT_CHECK_HASH = false + CURVEBS_CHUNK_SIZE = "chunksize" + VIPER_CURVEBS_CHUNK_SIZE = "curvebs.chunksize" + CURVEBS_CHECK_HASH = "checkhash" + VIPER_CURVEBS_CHECK_HASH = "curvebs.checkhash" + CURVEBS_DEFAULT_CHECK_HASH = false + CURVEBS_FILE_ID = "fileId" + VIPER_CURVEBS_FILE_ID = "curvebs.fileId" ) var ( @@ -207,8 +209,8 @@ var ( 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, + CURVEBS_CHUNK_SIZE: VIPER_CURVEBS_CHUNK_SIZE, + CURVEBS_CHECK_HASH: VIPER_CURVEBS_CHECK_HASH, } BSFLAG2DEFAULT = map[string]interface{}{ @@ -232,7 +234,7 @@ var ( CURVEBS_ALL: CURVEBS_DEFAULT_ALL, CURVEBS_LOGIC_POOL_ID: CURVEBS_DEFAULT_LOGIC_POOL_ID, CURVEBS_COPYSET_ID: CURVEBS_DEFAULT_COPYSET_ID, - CURVEBS_CHECK_HASH: CURVEBS_DEFAULT_CHECK_HASH, + CURVEBS_CHECK_HASH: CURVEBS_DEFAULT_CHECK_HASH, CURVEBS_SNAPSHOT_ID: CURVEBS_DEFAULT_SNAPSHOT_ID, } ) @@ -679,6 +681,10 @@ func AddBsTaskTypeOptionFlag(cmd *cobra.Command) { AddBsStringOptionFlag(cmd, CURVEBS_TYPE, "only query target type (clone or recover)") } +func AddBsFileIdOptionFlag(cmd *cobra.Command) { + AddBsUint64OptionFlag(cmd, CURVEBS_FILE_ID, "recover fileId") +} + // get stingslice flag func GetBsFlagStringSlice(cmd *cobra.Command, flagName string) []string { var value []string @@ -862,3 +868,7 @@ func GetBsChunkServerId(cmd *cobra.Command) []uint32 { } return chunkserveridSlice } + +func GetBsFileId(cmd *cobra.Command) uint64 { + return GetBsFlagUint64(cmd, CURVEBS_FILE_ID) +}