diff --git a/pitr/cli/internal/cmd/backup.go b/pitr/cli/internal/cmd/backup.go index 45d292cb..4340a30d 100644 --- a/pitr/cli/internal/cmd/backup.go +++ b/pitr/cli/internal/cmd/backup.go @@ -27,6 +27,7 @@ import ( "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/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/timeutil" "github.com/google/uuid" @@ -42,6 +43,9 @@ const ( defaultInstance = "ins-default-ss" // defaultShowDetailRetryTimes retry times of check backup detail from agent server defaultShowDetailRetryTimes = 3 + + backupPromptFmt = "Please Check All Nodes Disk Space, Make Sure Have Enough Space To Backup Or Restore Data.\n" + + "Are you sure to continue? (Y/N)" ) var filename string @@ -161,10 +165,8 @@ func backup() error { return xerr.NewCliErr(fmt.Sprintf("check disk space failed. err: %s", err)) } - prompt := fmt.Sprintf( - "Please Check All Nodes Disk Space, Make Sure Have Enough Space To Backup Or Restore Data.\n" + - "Are you sure to continue? (Y/N)") - err = getUserApproveInTerminal(prompt) + prompt := fmt.Sprintln(backupPromptFmt) + err = promptutil.GetUserApproveInTerminal(prompt) if err != nil { return xerr.NewCliErr(fmt.Sprintf("%s", err)) } diff --git a/pitr/cli/internal/cmd/backup_test.go b/pitr/cli/internal/cmd/backup_test.go index 4bced1fd..6c42400c 100644 --- a/pitr/cli/internal/cmd/backup_test.go +++ b/pitr/cli/internal/cmd/backup_test.go @@ -27,6 +27,7 @@ import ( "github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr" "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/httputils" mock_httputils "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/httputils/mocks" + "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -327,7 +328,7 @@ var _ = Describe("test backup mock", func() { monkey.Patch(pkg.NewAgentServer, func(addr string) pkg.IAgentServer { return as }) - monkey.Patch(getUserApproveInTerminal, func(_ string) error { + monkey.Patch(promptutil.GetUserApproveInTerminal, func(_ string) error { return nil }) }) diff --git a/pitr/cli/internal/cmd/common.go b/pitr/cli/internal/cmd/common.go index 035dd765..815eb666 100644 --- a/pitr/cli/internal/cmd/common.go +++ b/pitr/cli/internal/cmd/common.go @@ -19,10 +19,13 @@ package cmd import ( "fmt" + "os" "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/jedib0t/go-pretty/v6/table" ) func validate(ls pkg.ILocalStorage, csn, recordID string) ([]*model.LsBackup, error) { @@ -54,3 +57,105 @@ func validate(ls pkg.ILocalStorage, csn, recordID string) ([]*model.LsBackup, er return baks, nil } + +func convertLocalhost(ip string) string { + if ip == "127.0.0.1" { + return Host + } + return ip +} + +func checkAgentServerStatus(lsBackup *model.LsBackup) bool { + + statusList := make([]*model.AgentServerStatus, 0) + + // all agent server are available + available := true + + // IMPORTANT: we don't support multiple agent server run on the same host + asMap := make(map[string]bool) + asDuplicate := false + + for _, node := range lsBackup.SsBackup.StorageNodes { + sn := node + as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort)) + in := &model.HealthCheckIn{ + DBPort: sn.Port, + DBName: sn.Database, + Username: sn.Username, + Password: sn.Password, + } + if err := as.CheckStatus(in); err != nil { + statusList = append(statusList, &model.AgentServerStatus{IP: sn.IP, Port: AgentPort, Status: fmt.Sprintf("Unavailable: %s", err)}) + available = false + } else { + statusList = append(statusList, &model.AgentServerStatus{IP: sn.IP, Port: AgentPort, Status: "Available"}) + } + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetTitle("Agent Server Status") + t.AppendHeader(table.Row{"#", "Agent Server IP", "Agent Server Port", "Status"}) + + for i, s := range statusList { + t.AppendRow([]interface{}{i + 1, s.IP, s.Port, s.Status}) + t.AppendSeparator() + } + + t.Render() + + for _, node := range lsBackup.SsBackup.StorageNodes { + if _, ok := asMap[node.IP]; ok { + asDuplicate = true + break + } + asMap[node.IP] = true + } + + if asDuplicate { + logging.Error("IMPORTANT!: we don't support multiple agent server run on the same host.\n") + return false + } + + return available +} + +func checkDiskSpace(lsBackup *model.LsBackup) error { + var ( + diskspaceList = make([]*model.DiskSpaceStatus, 0) + ) + for _, sn := range lsBackup.SsBackup.StorageNodes { + var data string + as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort)) + in := &model.DiskSpaceIn{ + DiskPath: BackupPath, + } + + out, err := as.ShowDiskSpace(in) + + if err != nil { + data = "Check disk space failed." + } else { + data = out.Data + } + + diskspaceList = append(diskspaceList, &model.DiskSpaceStatus{ + IP: sn.IP, + Path: BackupPath, + DiskSpaceStatus: data, + }) + } + + // print diskspace result formatted + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.SetTitle("Disk Space Status") + t.AppendHeader(table.Row{"#", "Data Node IP", "Disk Path", "Disk Space Status"}) + for i, ds := range diskspaceList { + t.AppendRow([]interface{}{i + 1, ds.IP, ds.Path, ds.DiskSpaceStatus}) + t.AppendSeparator() + } + t.Render() + return nil +} diff --git a/pitr/cli/internal/cmd/delete.go b/pitr/cli/internal/cmd/delete.go index e5006bc0..353f2b31 100644 --- a/pitr/cli/internal/cmd/delete.go +++ b/pitr/cli/internal/cmd/delete.go @@ -27,6 +27,7 @@ import ( "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/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" @@ -79,6 +80,11 @@ func init() { DeleteCmd.Flags().StringVarP(&RecordID, "id", "", "", "backup record id") } +const ( + deletePromptFmt = "The backup record(ID: %s, CSN: %s) will be deleted forever.\n" + + "Are you sure to continue? (Y/N)" +) + //nolint:dupl func deleteRecord() error { // init local storage @@ -101,10 +107,8 @@ func deleteRecord() error { return xerr.NewCliErr("one or more agent server are not available.") } - prompt := fmt.Sprintf( - "The backup record(ID: %s, CSN: %s) will be deleted forever.\n"+ - "Are you sure to continue? (Y/N)", bak.Info.ID, bak.Info.CSN) - err = getUserApproveInTerminal(prompt) + prompt := fmt.Sprintf(deletePromptFmt, bak.Info.ID, bak.Info.CSN) + err = promptutil.GetUserApproveInTerminal(prompt) if err != nil { return xerr.NewCliErr(fmt.Sprintf("%s", err)) } diff --git a/pitr/cli/internal/cmd/delete_test.go b/pitr/cli/internal/cmd/delete_test.go index bbec0ca0..5ad5acf8 100644 --- a/pitr/cli/internal/cmd/delete_test.go +++ b/pitr/cli/internal/cmd/delete_test.go @@ -25,6 +25,7 @@ import ( "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/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -75,7 +76,7 @@ var _ = Describe("test delete", func() { monkey.Patch(pkg.NewLocalStorage, func(rootDir string) (pkg.ILocalStorage, error) { return ls, nil }) - monkey.Patch(getUserApproveInTerminal, func(_ string) error { + monkey.Patch(promptutil.GetUserApproveInTerminal, func(_ string) error { return nil }) }) diff --git a/pitr/cli/internal/cmd/restore.go b/pitr/cli/internal/cmd/restore.go index 5e7f5926..cee712ab 100644 --- a/pitr/cli/internal/cmd/restore.go +++ b/pitr/cli/internal/cmd/restore.go @@ -28,6 +28,7 @@ import ( "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/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/jedib0t/go-pretty/v6/progress" "github.com/jedib0t/go-pretty/v6/table" @@ -86,6 +87,12 @@ func init() { RestoreCmd.Flags().StringVarP(&RecordID, "id", "", "", "backup record id") } +const ( + restorePromptFmt = "Detected That The Database [%s] Already Exists In ShardingSphere-Proxy Metadata.\n" + + "The Logic Database Will Be DROPPED And Then Insert Backup's Metadata Into ShardingSphere-Proxy After Restoring The Backup Data.\n" + + "Are you sure to continue? (Y/N)" +) + //nolint:dupl func restore() error { // init local storage @@ -112,11 +119,8 @@ func restore() error { return xerr.NewCliErr(fmt.Sprintf("check database exist failed. err: %s", err)) } - prompt := fmt.Sprintf( - "Detected That The Database [%s] Already Exists In ShardingSphere-Proxy Metadata.\n"+ - "The Logic Database Will Be DROPPED And Then Insert Backup's Metadata Into ShardingSphere-Proxy After Restoring The Backup Data.\n"+ - "Are you sure to continue? (Y/N)", strings.Join(databaseNamesExist, ",")) - err = getUserApproveInTerminal(prompt) + prompt := fmt.Sprintf(restorePromptFmt, strings.Join(databaseNamesExist, ",")) + err = promptutil.GetUserApproveInTerminal(prompt) if err != nil { return xerr.NewCliErr(fmt.Sprintf("%s", err)) } diff --git a/pitr/cli/internal/cmd/restore_test.go b/pitr/cli/internal/cmd/restore_test.go index bd28ffbb..7fb0a2b6 100644 --- a/pitr/cli/internal/cmd/restore_test.go +++ b/pitr/cli/internal/cmd/restore_test.go @@ -26,6 +26,7 @@ import ( 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/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr" + "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -104,7 +105,7 @@ var _ = Describe("test restore", func() { monkey.Patch(pkg.NewLocalStorage, func(rootDir string) (pkg.ILocalStorage, error) { return ls, nil }) - monkey.Patch(getUserApproveInTerminal, func(_ string) error { + monkey.Patch(promptutil.GetUserApproveInTerminal, func(_ string) error { return nil }) }) @@ -115,7 +116,7 @@ var _ = Describe("test restore", func() { }) It("check database if exists", func() { - monkey.Patch(getUserApproveInTerminal, func(_ string) error { return nil }) + monkey.Patch(promptutil.GetUserApproveInTerminal, func(_ string) error { return nil }) proxy.EXPECT().ExportMetaData() Expect(checkDatabaseExist(proxy, bak)).To(BeNil()) }) diff --git a/pitr/cli/internal/cmd/root.go b/pitr/cli/internal/cmd/root.go index 6f0e5a38..fb9565eb 100644 --- a/pitr/cli/internal/cmd/root.go +++ b/pitr/cli/internal/cmd/root.go @@ -18,16 +18,8 @@ package cmd import ( - "bufio" - "fmt" - "os" - - "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/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" ) @@ -65,119 +57,3 @@ var RootCmd = &cobra.Command{ HiddenDefaultCmd: true, }, } - -func getUserApproveInTerminal(prompt string) error { - logging.Warn(fmt.Sprintf("\n%s", prompt)) - scanner := bufio.NewScanner(os.Stdin) - scanner.Scan() - err := scanner.Err() - if err != nil { - return xerr.NewCliErr(fmt.Sprintf("read user input failed:%s", err.Error())) - } - if scanner.Text() != "Y" && scanner.Text() != "y" && scanner.Text() != "yes" && scanner.Text() != "YES" && scanner.Text() != "Yes" { - return xerr.NewCliErr("User abort") - } - return nil -} - -func convertLocalhost(ip string) string { - if ip == "127.0.0.1" { - return Host - } - return ip -} - -func checkAgentServerStatus(lsBackup *model.LsBackup) bool { - - statusList := make([]*model.AgentServerStatus, 0) - - // all agent server are available - available := true - - // IMPORTANT: we don't support multiple agent server run on the same host - asMap := make(map[string]bool) - asDuplicate := false - - for _, node := range lsBackup.SsBackup.StorageNodes { - sn := node - as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort)) - in := &model.HealthCheckIn{ - DBPort: sn.Port, - DBName: sn.Database, - Username: sn.Username, - Password: sn.Password, - } - if err := as.CheckStatus(in); err != nil { - statusList = append(statusList, &model.AgentServerStatus{IP: sn.IP, Port: AgentPort, Status: fmt.Sprintf("Unavailable: %s", err)}) - available = false - } else { - statusList = append(statusList, &model.AgentServerStatus{IP: sn.IP, Port: AgentPort, Status: "Available"}) - } - } - - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) - t.SetTitle("Agent Server Status") - t.AppendHeader(table.Row{"#", "Agent Server IP", "Agent Server Port", "Status"}) - - for i, s := range statusList { - t.AppendRow([]interface{}{i + 1, s.IP, s.Port, s.Status}) - t.AppendSeparator() - } - - t.Render() - - for _, node := range lsBackup.SsBackup.StorageNodes { - if _, ok := asMap[node.IP]; ok { - asDuplicate = true - break - } - asMap[node.IP] = true - } - - if asDuplicate { - logging.Error("IMPORTANT!: we don't support multiple agent server run on the same host.\n") - return false - } - - return available -} - -func checkDiskSpace(lsBackup *model.LsBackup) error { - var ( - diskspaceList = make([]*model.DiskSpaceStatus, 0) - ) - for _, sn := range lsBackup.SsBackup.StorageNodes { - var data string - as := pkg.NewAgentServer(fmt.Sprintf("%s:%d", convertLocalhost(sn.IP), AgentPort)) - in := &model.DiskSpaceIn{ - DiskPath: BackupPath, - } - - out, err := as.ShowDiskSpace(in) - - if err != nil { - data = "Check disk space failed." - } else { - data = out.Data - } - - diskspaceList = append(diskspaceList, &model.DiskSpaceStatus{ - IP: sn.IP, - Path: BackupPath, - DiskSpaceStatus: data, - }) - } - - // print diskspace result formatted - t := table.NewWriter() - t.SetOutputMirror(os.Stdout) - t.SetTitle("Disk Space Status") - t.AppendHeader(table.Row{"#", "Data Node IP", "Disk Path", "Disk Space Status"}) - for i, ds := range diskspaceList { - t.AppendRow([]interface{}{i + 1, ds.IP, ds.Path, ds.DiskSpaceStatus}) - t.AppendSeparator() - } - t.Render() - return nil -} diff --git a/pitr/cli/internal/cmd/root_test.go b/pitr/cli/internal/cmd/root_test.go index ed411313..a9f2dc92 100644 --- a/pitr/cli/internal/cmd/root_test.go +++ b/pitr/cli/internal/cmd/root_test.go @@ -22,6 +22,7 @@ import ( "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/apache/shardingsphere-on-cloud/pitr/cli/pkg/promptutil" "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -39,7 +40,7 @@ var _ = Describe("Root", func() { monkey.Patch(pkg.NewAgentServer, func(_ string) pkg.IAgentServer { return as }) - monkey.Patch(getUserApproveInTerminal, func(_ string) error { + monkey.Patch(promptutil.GetUserApproveInTerminal, func(_ string) error { return nil }) }) diff --git a/pitr/cli/pkg/promptutil/prompt.go b/pitr/cli/pkg/promptutil/prompt.go new file mode 100644 index 00000000..a16b9dc2 --- /dev/null +++ b/pitr/cli/pkg/promptutil/prompt.go @@ -0,0 +1,41 @@ +/* + * 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 promptutil + +import ( + "bufio" + "fmt" + "os" + + "github.com/apache/shardingsphere-on-cloud/pitr/cli/internal/pkg/xerr" + "github.com/apache/shardingsphere-on-cloud/pitr/cli/pkg/logging" +) + +func GetUserApproveInTerminal(prompt string) error { + logging.Warn(fmt.Sprintf("\n%s", prompt)) + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + err := scanner.Err() + if err != nil { + return xerr.NewCliErr(fmt.Sprintf("read user input failed:%s", err.Error())) + } + if scanner.Text() != "Y" && scanner.Text() != "y" && scanner.Text() != "yes" && scanner.Text() != "YES" && scanner.Text() != "Yes" { + return xerr.NewCliErr("User abort") + } + return nil +}