You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2023/11/15 09:38:48 UTC

(shardingsphere-on-cloud) branch main updated: feat: introduce new pitr subcommand delete (#461)

This is an automated email from the ASF dual-hosted git repository.

zhonghongsheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shardingsphere-on-cloud.git


The following commit(s) were added to refs/heads/main by this push:
     new e2a5864  feat: introduce new pitr subcommand delete (#461)
e2a5864 is described below

commit e2a586401673dccf0e6c6abe1e45627a352ed3d4
Author: liyao <ma...@126.com>
AuthorDate: Wed Nov 15 17:38:43 2023 +0800

    feat: introduce new pitr subcommand delete (#461)
    
    * feat: use common header in DELETE request
    
    * feat: add filename as backup metainfo
    
    * feat: add HideByName and DeleteByHidedName
    
    * feat: add delete backup record
    
    * chore: update local storage mock
    
    * chore: add test for delete
    
    * refactor: change func name delete to deleteRecord
    
    * refactor: optimize _execDelete
    
    * feat: add add delete status
    
    * chore: update delete func name
    
    * chore: add nolint for command definition
    
    * refactor: seperate common func validate
    
    * chore: adjust style according to lint
---
 pitr/cli/internal/cmd/common.go              |  47 +++++++
 pitr/cli/internal/cmd/delete.go              | 192 +++++++++++++++++++++++++++
 pitr/cli/internal/cmd/delete_test.go         | 116 ++++++++++++++++
 pitr/cli/internal/cmd/restore.go             |  17 +--
 pitr/cli/internal/pkg/local-storage.go       |  30 ++++-
 pitr/cli/internal/pkg/mocks/local-storage.go | 112 +++++++++-------
 pitr/cli/internal/pkg/model/ls_backup.go     |   1 +
 pitr/cli/pkg/httputils/req.go                |  10 +-
 8 files changed, 456 insertions(+), 69 deletions(-)

diff --git a/pitr/cli/internal/cmd/common.go b/pitr/cli/internal/cmd/common.go
new file mode 100644
index 0000000..0e80d5d
--- /dev/null
+++ b/pitr/cli/internal/cmd/common.go
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package cmd
+
+import (
+	"fmt"
+
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/model"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr"
+)
+
+func validate(ls pkg.ILocalStorage, csn, recordID string) (*model.LsBackup, error) {
+	var (
+		bak *model.LsBackup
+		err error
+	)
+	if CSN != "" {
+		bak, err = ls.ReadByCSN(csn)
+		if err != nil {
+			return bak, xerr.NewCliErr(fmt.Sprintf("read backup record by csn failed. err: %s", err))
+		}
+	}
+
+	if RecordID != "" {
+		bak, err = ls.ReadByID(recordID)
+		if err != nil {
+			return bak, xerr.NewCliErr(fmt.Sprintf("read backup record by id failed. err: %s", err))
+		}
+	}
+	return bak, nil
+}
diff --git a/pitr/cli/internal/cmd/delete.go b/pitr/cli/internal/cmd/delete.go
new file mode 100644
index 0000000..6107097
--- /dev/null
+++ b/pitr/cli/internal/cmd/delete.go
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/model"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/logging"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/prettyoutput"
+	"github.com/jedib0t/go-pretty/v6/table"
+
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+//nolint:dupl
+var DeleteCmd = &cobra.Command{
+	Use:   "delete",
+	Short: "Delete a backup record",
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Flags().VisitAll(func(flag *pflag.Flag) {
+			fmt.Printf("Flag: %s Value: %s\n", flag.Name, flag.Value)
+		})
+
+		if CSN == "" && RecordID == "" {
+			logging.Error("Please specify csn or record id")
+			return
+		}
+
+		if CSN != "" && RecordID != "" {
+			logging.Error("Please specify only one of csn and record id")
+			return
+		}
+
+		if err := deleteRecord(); err != nil {
+			logging.Error(err.Error())
+		}
+	},
+}
+
+//nolint:dupl
+func init() {
+	RootCmd.AddCommand(DeleteCmd)
+
+	DeleteCmd.Flags().StringVarP(&Host, "host", "H", "", "ss-proxy hostname or ip")
+	_ = DeleteCmd.MarkFlagRequired("host")
+	DeleteCmd.Flags().Uint16VarP(&Port, "port", "P", 0, "ss-proxy port")
+	_ = DeleteCmd.MarkFlagRequired("port")
+	DeleteCmd.Flags().StringVarP(&Username, "username", "u", "", "ss-proxy username")
+	_ = DeleteCmd.MarkFlagRequired("username")
+	DeleteCmd.Flags().StringVarP(&Password, "password", "p", "", "ss-proxy password")
+	_ = DeleteCmd.MarkFlagRequired("password")
+	DeleteCmd.Flags().StringVarP(&BackupPath, "dn-backup-path", "B", "", "openGauss data backup path")
+	_ = DeleteCmd.MarkFlagRequired("dn-backup-path")
+	DeleteCmd.Flags().Uint16VarP(&AgentPort, "agent-port", "a", 443, "agent server port")
+	_ = DeleteCmd.MarkFlagRequired("agent-port")
+
+	DeleteCmd.Flags().StringVarP(&CSN, "csn", "", "", "commit sequence number")
+	DeleteCmd.Flags().StringVarP(&RecordID, "id", "", "", "backup record id")
+}
+
+func deleteRecord() error {
+	// init local storage
+	ls, err := pkg.NewLocalStorage(pkg.DefaultRootDir())
+	if err != nil {
+		return xerr.NewCliErr(fmt.Sprintf("new local storage failed. err: %s", err.Error()))
+	}
+
+	// get backup record
+	var bak *model.LsBackup
+	bak, err = validate(ls, CSN, RecordID)
+	if err != nil {
+		return err
+	}
+
+	if bak == nil {
+		return xerr.NewCliErr(fmt.Sprintf("backup record not found. err: %s", err))
+	}
+
+	// check agent server status
+	logging.Info("Checking agent server status...")
+	if available := checkAgentServerStatus(bak); !available {
+		return xerr.NewCliErr("one or more agent server are not available.")
+	}
+
+	// mark the target backup record to be deleted
+	// meanwhile this record cannot be restored
+	if err := ls.HideByName(bak.Info.FileName); err != nil {
+		return xerr.NewCliErr("cannot mark backup record.")
+	}
+
+	// exec delete
+	logging.Info("Start delete backup data to openGauss...")
+	if err := _execDelete(bak); err != nil {
+		return xerr.NewCliErr(fmt.Sprintf("exec delete failed. err: %s", err))
+	}
+	logging.Info("Delete backup data success!")
+
+	// delete the backup record
+	if err := ls.DeleteByHidedName(bak.Info.FileName); err != nil {
+		return xerr.NewCliErr(fmt.Sprintf("exec delete backup record failed. err: %s", err))
+	}
+
+	logging.Info("Delete success!")
+	return nil
+}
+
+func _execDelete(lsBackup *model.LsBackup) error {
+	var (
+		dataNodeMap       = make(map[string]*model.DataNode)
+		totalNum          = len(lsBackup.SsBackup.StorageNodes)
+		resultCh          = make(chan *model.DeleteBackupResult, totalNum)
+		dnResult          = make([]*model.DeleteBackupResult, 0)
+		deleteFinalStatus = "Completed"
+	)
+	for _, dn := range lsBackup.DnList {
+		dataNodeMap[dn.IP] = dn
+	}
+
+	if totalNum == 0 {
+		logging.Info("No data node need to delete backup files")
+		return nil
+	}
+
+	pw := prettyoutput.NewPW(totalNum)
+	go pw.Render()
+
+	for _, storagenode := range lsBackup.SsBackup.StorageNodes {
+		sn := storagenode
+		if dn, ok := dataNodeMap[sn.IP]; !ok {
+			logging.Warn(fmt.Sprintf("SKIPPED! data node %s:%d not found in backup info.", sn.IP, sn.Port))
+			continue
+		} else {
+			as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort))
+			go doDelete(as, sn, dn, resultCh, pw)
+		}
+	}
+
+	time.Sleep(time.Millisecond * 100)
+
+	for pw.IsRenderInProgress() {
+		time.Sleep(time.Millisecond * 100)
+	}
+
+	close(resultCh)
+
+	for result := range resultCh {
+		dnResult = append(dnResult, result)
+		if result.Status != "Completed" {
+			deleteFinalStatus = "Failed"
+		}
+	}
+
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+	t.SetTitle("Delete Backup Files Result")
+	t.AppendHeader(table.Row{"#", "Node IP", "Node Port", "Result", "Message"})
+	t.SetColumnConfigs([]table.ColumnConfig{{Number: 5, WidthMax: 50}})
+
+	for i, result := range dnResult {
+		t.AppendRow([]interface{}{i + 1, result.IP, result.Port, result.Status, result.Msg})
+		t.AppendSeparator()
+	}
+
+	t.Render()
+
+	if deleteFinalStatus == "Failed" {
+		return xerr.NewCliErr("delete failed, please check the log for more details.")
+	}
+
+	return nil
+}
diff --git a/pitr/cli/internal/cmd/delete_test.go b/pitr/cli/internal/cmd/delete_test.go
new file mode 100644
index 0000000..194e617
--- /dev/null
+++ b/pitr/cli/internal/cmd/delete_test.go
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package cmd
+
+import (
+	"reflect"
+	"time"
+
+	"bou.ke/monkey"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg"
+	mock_pkg "github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/mocks"
+	"github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/model"
+	"github.com/golang/mock/gomock"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("test delete", func() {
+	var (
+		proxy *mock_pkg.MockIShardingSphereProxy
+		ls    *mock_pkg.MockILocalStorage
+		as    *mock_pkg.MockIAgentServer
+		bak   = &model.LsBackup{
+			Info: &model.BackupMetaInfo{
+				ID:       "backup-id-1",
+				FileName: "backup",
+			},
+			DnList: []*model.DataNode{
+				{
+					IP: "127.0.0.1",
+				},
+			},
+			SsBackup: &model.SsBackup{
+				Status: "",
+				ClusterInfo: &model.ClusterInfo{
+					MetaData: model.MetaData{
+						Databases: map[string]string{},
+						Props:     "",
+						Rules:     "",
+					},
+					SnapshotInfo: nil,
+				},
+				StorageNodes: []*model.StorageNode{
+					{
+						IP: "127.0.0.1",
+					},
+				},
+			},
+		}
+	)
+
+	BeforeEach(func() {
+		ctrl = gomock.NewController(GinkgoT())
+		proxy = mock_pkg.NewMockIShardingSphereProxy(ctrl)
+		as = mock_pkg.NewMockIAgentServer(ctrl)
+		ls = mock_pkg.NewMockILocalStorage(ctrl)
+		monkey.Patch(pkg.NewShardingSphereProxy, func(user, password, database, host string, port uint16) (pkg.IShardingSphereProxy, error) {
+			return proxy, nil
+		})
+		monkey.Patch(pkg.NewLocalStorage, func(rootDir string) (pkg.ILocalStorage, error) {
+			return ls, nil
+		})
+	})
+
+	AfterEach(func() {
+		ctrl.Finish()
+		monkey.UnpatchAll()
+	})
+
+	It("test exec delete main func", func() {
+		// patch ReadByID of mock ls
+		monkey.PatchInstanceMethod(reflect.TypeOf(ls), "ReadByID", func(_ *mock_pkg.MockILocalStorage, _ string) (*model.LsBackup, error) { return bak, nil })
+		monkey.Patch(pkg.NewAgentServer, func(_ string) pkg.IAgentServer { return as })
+
+		RecordID = "backup-id"
+		as.EXPECT().CheckStatus(gomock.Any()).Return(nil)
+		ls.EXPECT().HideByName(bak.Info.FileName).Return(nil)
+		as.EXPECT().DeleteBackup(gomock.Any()).Return(nil)
+		ls.EXPECT().DeleteByHidedName(bak.Info.FileName).Return(nil)
+
+		Expect(deleteRecord()).To(BeNil())
+	})
+
+	Context("test exec delete", func() {
+		It("should be success", func() {
+			ctrl := gomock.NewController(GinkgoT())
+			as := mock_pkg.NewMockIAgentServer(ctrl)
+			monkey.Patch(pkg.NewAgentServer, func(_ string) pkg.IAgentServer {
+				return as
+			})
+			defer func() {
+				ctrl.Finish()
+				monkey.UnpatchAll()
+			}()
+			as.EXPECT().DeleteBackup(gomock.Any()).Do(func(_ *model.DeleteBackupIn) {
+				time.Sleep(3 * time.Second)
+			}).Return(nil)
+			Expect(_execDelete(bak)).To(BeNil())
+		})
+	})
+})
diff --git a/pitr/cli/internal/cmd/restore.go b/pitr/cli/internal/cmd/restore.go
index 92ca2a7..791a043 100644
--- a/pitr/cli/internal/cmd/restore.go
+++ b/pitr/cli/internal/cmd/restore.go
@@ -40,6 +40,7 @@ var (
 	databaseNamesExist []string
 )
 
+//nolint:dupl
 var RestoreCmd = &cobra.Command{
 	Use:   "restore",
 	Short: "Restore a database cluster ",
@@ -64,6 +65,7 @@ var RestoreCmd = &cobra.Command{
 	},
 }
 
+//nolint:dupl
 func init() {
 	RootCmd.AddCommand(RestoreCmd)
 
@@ -97,18 +99,9 @@ func restore() error {
 
 	// get backup record
 	var bak *model.LsBackup
-	if CSN != "" {
-		bak, err = ls.ReadByCSN(CSN)
-		if err != nil {
-			return xerr.NewCliErr(fmt.Sprintf("read backup record by csn failed. err: %s", err))
-		}
-	}
-
-	if RecordID != "" {
-		bak, err = ls.ReadByID(RecordID)
-		if err != nil {
-			return xerr.NewCliErr(fmt.Sprintf("read backup record by id failed. err: %s", err))
-		}
+	bak, err = validate(ls, CSN, RecordID)
+	if err != nil {
+		return err
 	}
 	if bak == nil {
 		return xerr.NewCliErr(fmt.Sprintf("backup record not found. err: %s", err))
diff --git a/pitr/cli/internal/pkg/local-storage.go b/pitr/cli/internal/pkg/local-storage.go
index ca00480..47693a2 100644
--- a/pitr/cli/internal/pkg/local-storage.go
+++ b/pitr/cli/internal/pkg/local-storage.go
@@ -44,6 +44,8 @@ type (
 		ReadByID(id string) (*model.LsBackup, error)
 		ReadByCSN(csn string) (*model.LsBackup, error)
 		DeleteByName(name string) error
+		HideByName(name string) error
+		DeleteByHidedName(name string) error
 	}
 
 	Extension string
@@ -164,7 +166,7 @@ func (ls *localStorage) ReadAll() ([]*model.LsBackup, error) {
 		if err := json.Unmarshal(file, b); err != nil {
 			return nil, xerr.NewCliErr(fmt.Sprintf("invalid contents[filePath=%s]. err: %s", path, err))
 		}
-
+		b.Info.FileName = info.Name()
 		backups = append(backups, b)
 	}
 	return backups, nil
@@ -214,10 +216,34 @@ func (ls *localStorage) GenFilename(extn Extension) string {
 	}
 }
 
-func (ls *localStorage) DeleteByName(name string) error {
+type mode int
+
+const (
+	normal mode = iota
+	hided
+)
+
+func (ls *localStorage) deleteByName(name string, mode mode) error {
 	path := fmt.Sprintf("%s/%s", ls.backupDir, name)
 	if err := os.Remove(path); err != nil {
 		return xerr.NewCliErr(fmt.Sprintf("delete file failed. err: %s", err))
 	}
 	return nil
 }
+
+func (ls *localStorage) DeleteByName(name string) error {
+	return ls.deleteByName(name, normal)
+}
+
+func (ls *localStorage) DeleteByHidedName(name string) error {
+	return ls.deleteByName(fmt.Sprintf(".%s", name), hided)
+}
+
+func (ls *localStorage) HideByName(name string) error {
+	path := fmt.Sprintf("%s/%s", ls.backupDir, name)
+	hided := fmt.Sprintf("%s/.%s", ls.backupDir, name)
+	if err := os.Rename(path, hided); err != nil {
+		return xerr.NewCliErr(fmt.Sprintf("hide file failed. err: %s", err))
+	}
+	return nil
+}
diff --git a/pitr/cli/internal/pkg/mocks/local-storage.go b/pitr/cli/internal/pkg/mocks/local-storage.go
index 9284834..5005197 100644
--- a/pitr/cli/internal/pkg/mocks/local-storage.go
+++ b/pitr/cli/internal/pkg/mocks/local-storage.go
@@ -1,72 +1,54 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements.  See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You 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.
- */
-
 // Code generated by MockGen. DO NOT EDIT.
-// Source: local-storage.go
+// Source: internal/pkg/local-storage.go
 
 // Package mock_pkg is a generated GoMock package.
 package mock_pkg
 
 import (
-	reflect "reflect"
-
 	pkg "github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg"
 	model "github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/model"
 	gomock "github.com/golang/mock/gomock"
+	reflect "reflect"
 )
 
-// MockILocalStorage is a mock of ILocalStorage interface.
+// MockILocalStorage is a mock of ILocalStorage interface
 type MockILocalStorage struct {
 	ctrl     *gomock.Controller
 	recorder *MockILocalStorageMockRecorder
 }
 
-// MockILocalStorageMockRecorder is the mock recorder for MockILocalStorage.
+// MockILocalStorageMockRecorder is the mock recorder for MockILocalStorage
 type MockILocalStorageMockRecorder struct {
 	mock *MockILocalStorage
 }
 
-// NewMockILocalStorage creates a new mock instance.
+// NewMockILocalStorage creates a new mock instance
 func NewMockILocalStorage(ctrl *gomock.Controller) *MockILocalStorage {
 	mock := &MockILocalStorage{ctrl: ctrl}
 	mock.recorder = &MockILocalStorageMockRecorder{mock}
 	return mock
 }
 
-// EXPECT returns an object that allows the caller to indicate expected use.
+// EXPECT returns an object that allows the caller to indicate expected use
 func (m *MockILocalStorage) EXPECT() *MockILocalStorageMockRecorder {
 	return m.recorder
 }
 
-// DeleteByName mocks base method.
-func (m *MockILocalStorage) DeleteByName(name string) error {
+// WriteByJSON mocks base method
+func (m *MockILocalStorage) WriteByJSON(name string, contents *model.LsBackup) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "DeleteByName", name)
+	ret := m.ctrl.Call(m, "WriteByJSON", name, contents)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// DeleteByName indicates an expected call of DeleteByName.
-func (mr *MockILocalStorageMockRecorder) DeleteByName(name interface{}) *gomock.Call {
+// WriteByJSON indicates an expected call of WriteByJSON
+func (mr *MockILocalStorageMockRecorder) WriteByJSON(name, contents interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByName", reflect.TypeOf((*MockILocalStorage)(nil).DeleteByName), name)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteByJSON", reflect.TypeOf((*MockILocalStorage)(nil).WriteByJSON), name, contents)
 }
 
-// GenFilename mocks base method.
+// GenFilename mocks base method
 func (m *MockILocalStorage) GenFilename(extn pkg.Extension) string {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GenFilename", extn)
@@ -74,13 +56,13 @@ func (m *MockILocalStorage) GenFilename(extn pkg.Extension) string {
 	return ret0
 }
 
-// GenFilename indicates an expected call of GenFilename.
+// GenFilename indicates an expected call of GenFilename
 func (mr *MockILocalStorageMockRecorder) GenFilename(extn interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GenFilename", reflect.TypeOf((*MockILocalStorage)(nil).GenFilename), extn)
 }
 
-// ReadAll mocks base method.
+// ReadAll mocks base method
 func (m *MockILocalStorage) ReadAll() ([]*model.LsBackup, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ReadAll")
@@ -89,13 +71,28 @@ func (m *MockILocalStorage) ReadAll() ([]*model.LsBackup, error) {
 	return ret0, ret1
 }
 
-// ReadAll indicates an expected call of ReadAll.
+// ReadAll indicates an expected call of ReadAll
 func (mr *MockILocalStorageMockRecorder) ReadAll() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadAll", reflect.TypeOf((*MockILocalStorage)(nil).ReadAll))
 }
 
-// ReadByCSN mocks base method.
+// ReadByID mocks base method
+func (m *MockILocalStorage) ReadByID(id string) (*model.LsBackup, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ReadByID", id)
+	ret0, _ := ret[0].(*model.LsBackup)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ReadByID indicates an expected call of ReadByID
+func (mr *MockILocalStorageMockRecorder) ReadByID(id interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadByID", reflect.TypeOf((*MockILocalStorage)(nil).ReadByID), id)
+}
+
+// ReadByCSN mocks base method
 func (m *MockILocalStorage) ReadByCSN(csn string) (*model.LsBackup, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ReadByCSN", csn)
@@ -104,37 +101,50 @@ func (m *MockILocalStorage) ReadByCSN(csn string) (*model.LsBackup, error) {
 	return ret0, ret1
 }
 
-// ReadByCSN indicates an expected call of ReadByCSN.
+// ReadByCSN indicates an expected call of ReadByCSN
 func (mr *MockILocalStorageMockRecorder) ReadByCSN(csn interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadByCSN", reflect.TypeOf((*MockILocalStorage)(nil).ReadByCSN), csn)
 }
 
-// ReadByID mocks base method.
-func (m *MockILocalStorage) ReadByID(id string) (*model.LsBackup, error) {
+// DeleteByName mocks base method
+func (m *MockILocalStorage) DeleteByName(name string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ReadByID", id)
-	ret0, _ := ret[0].(*model.LsBackup)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
+	ret := m.ctrl.Call(m, "DeleteByName", name)
+	ret0, _ := ret[0].(error)
+	return ret0
 }
 
-// ReadByID indicates an expected call of ReadByID.
-func (mr *MockILocalStorageMockRecorder) ReadByID(id interface{}) *gomock.Call {
+// DeleteByName indicates an expected call of DeleteByName
+func (mr *MockILocalStorageMockRecorder) DeleteByName(name interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadByID", reflect.TypeOf((*MockILocalStorage)(nil).ReadByID), id)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByName", reflect.TypeOf((*MockILocalStorage)(nil).DeleteByName), name)
 }
 
-// WriteByJSON mocks base method.
-func (m *MockILocalStorage) WriteByJSON(name string, contents *model.LsBackup) error {
+// HideByName mocks base method
+func (m *MockILocalStorage) HideByName(name string) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WriteByJSON", name, contents)
+	ret := m.ctrl.Call(m, "HideByName", name)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
-// WriteByJSON indicates an expected call of WriteByJSON.
-func (mr *MockILocalStorageMockRecorder) WriteByJSON(name, contents interface{}) *gomock.Call {
+// HideByName indicates an expected call of HideByName
+func (mr *MockILocalStorageMockRecorder) HideByName(name interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteByJSON", reflect.TypeOf((*MockILocalStorage)(nil).WriteByJSON), name, contents)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HideByName", reflect.TypeOf((*MockILocalStorage)(nil).HideByName), name)
+}
+
+// DeleteByHidedName mocks base method
+func (m *MockILocalStorage) DeleteByHidedName(name string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteByHidedName", name)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteByHidedName indicates an expected call of DeleteByHidedName
+func (mr *MockILocalStorageMockRecorder) DeleteByHidedName(name interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByHidedName", reflect.TypeOf((*MockILocalStorage)(nil).DeleteByHidedName), name)
 }
diff --git a/pitr/cli/internal/pkg/model/ls_backup.go b/pitr/cli/internal/pkg/model/ls_backup.go
index 8301a95..8b5a428 100644
--- a/pitr/cli/internal/pkg/model/ls_backup.go
+++ b/pitr/cli/internal/pkg/model/ls_backup.go
@@ -31,6 +31,7 @@ type (
 		BackupMode DBBackupMode `json:"backup_mode"`
 		StartTime  int64        `json:"start_time"` // Unix time
 		EndTime    int64        `json:"end_time"`   // Unix time
+		FileName   string
 	}
 
 	DataNode struct {
diff --git a/pitr/cli/pkg/httputils/req.go b/pitr/cli/pkg/httputils/req.go
index 49138d8..c38a5ca 100644
--- a/pitr/cli/pkg/httputils/req.go
+++ b/pitr/cli/pkg/httputils/req.go
@@ -131,14 +131,16 @@ func (r *req) setReqHeader(req *http.Request) *http.Request {
 		req.Header.Set(k, v)
 	}
 
+	if req.Header.Get("x-request-id") == "" {
+		req.Header.Set("x-request-id", uuid.New().String())
+	}
+
 	// set default header if method is post
-	if r.method == http.MethodPost {
+	if r.method == http.MethodPost || r.method == http.MethodDelete {
 		if req.Header.Get("Content-Type") == "" {
 			req.Header.Set("Content-Type", "application/json")
 		}
-		if req.Header.Get("x-request-id") == "" {
-			req.Header.Set("x-request-id", uuid.New().String())
-		}
+
 	}
 	return req
 }