blob: dd02854b40e68cc0dadb01889434c8eb47a4ff66 [file]
package restore
import (
"fmt"
"runtime"
"strconv"
"strings"
"github.com/apache/cloudberry-backup/options"
"github.com/apache/cloudberry-go-libs/cluster"
"github.com/apache/cloudberry-go-libs/gplog"
"github.com/apache/cloudberry-go-libs/iohelper"
"github.com/pkg/errors"
)
/*
* Functions to run commands on entire cluster during restore
*/
func VerifyBackupDirectoriesExistOnAllHosts() {
_, err := globalCluster.ExecuteLocalCommand(fmt.Sprintf("test -d %s", globalFPInfo.GetDirForContent(-1)))
gplog.FatalOnError(err, "Backup directory %s missing or inaccessible", globalFPInfo.GetDirForContent(-1))
if MustGetFlagString(options.PLUGIN_CONFIG) == "" || backupConfig.SingleDataFile {
origSize, destSize, isResizeRestore, _ := GetResizeClusterInfo()
remoteOutput := globalCluster.GenerateAndExecuteCommand("Verifying backup directories exist", cluster.ON_SEGMENTS, func(contentID int) string {
if isResizeRestore { // Map origin content to destination content to find where the original files have been placed
if contentID >= origSize { // Don't check for directories for contents that aren't part of the backup set
return ""
}
contentID = contentID % destSize
}
return fmt.Sprintf("test -d %s", globalFPInfo.GetDirForContent(contentID))
})
globalCluster.CheckClusterError(remoteOutput, "Backup directories missing or inaccessible", func(contentID int) string {
return fmt.Sprintf("Backup directory %s missing or inaccessible", globalFPInfo.GetDirForContent(contentID))
})
}
}
func VerifyBackupFileCountOnSegments() {
// In the current backup directory format, all content IDs are intermingled in one directory, so we need to get a list of which contents
// correspond to the content ID we're going to check in order to provide a useful count to the user in the case of an error.
origSize, destSize, isResizeRestore, _ := GetResizeClusterInfo()
contentMap := make(map[int][]string, destSize) // []string instead of []int so we can join them later
for i := 0; i < origSize; i++ {
contentMap[i%destSize] = append(contentMap[i%destSize], fmt.Sprintf("%d", i))
}
remoteOutput := globalCluster.GenerateAndExecuteCommand("Verifying backup file count", cluster.ON_SEGMENTS, func(contentID int) string {
// Coordinator backup files (and any gprestore report files) will be mixed in with segment backup files on a single-node cluster,
// so we explicitly look for filenames in the segment filename format. In a smaller-to-larger restore, the contents list for a segment
// outside the destination array will be "[]", which the find command can handle safely in this context.
contentsList := fmt.Sprintf("(%s)", strings.Join(contentMap[contentID], "|"))
var cmdString string
if runtime.GOOS == "linux" {
cmdString = fmt.Sprintf(`find %s -type f -regextype posix-extended -regex ".*gpbackup_%s_%s.*" | wc -l`, globalFPInfo.GetDirForContent(contentID), contentsList, globalFPInfo.Timestamp)
} else if runtime.GOOS == "darwin" {
cmdString = fmt.Sprintf(`find -E %s -type f -regex ".*gpbackup_%s_%s.*" | wc -l`, globalFPInfo.GetDirForContent(contentID), contentsList, globalFPInfo.Timestamp)
}
return cmdString
})
globalCluster.CheckClusterError(remoteOutput, "Could not verify backup file count", func(contentID int) string {
return "Could not verify backup file count"
})
// these are the file counts for non-resize restores.
fileCount := 2 // 1 for the actual data file, 1 for the segment TOC file
if !backupConfig.SingleDataFile {
fileCount = len(globalTOC.DataEntries)
}
batchMap := make(map[int]int, len(remoteOutput.Commands))
for i := 0; i < origSize; i++ {
batchMap[i%destSize] += fileCount
}
numIncorrect := 0
for contentID, cmd := range remoteOutput.Commands {
numFound, _ := strconv.Atoi(strings.TrimSpace(cmd.Stdout))
if isResizeRestore {
fileCount = batchMap[contentID]
}
if numFound != fileCount {
gplog.Verbose("Expected to find %d file(s) on segment %d on host %s, but found %d instead.", fileCount, contentID, globalCluster.GetHostForContent(contentID), numFound)
numIncorrect++
}
}
if numIncorrect > 0 {
cluster.LogFatalClusterError("Found incorrect number of backup files", cluster.ON_SEGMENTS, numIncorrect)
}
}
func VerifyMetadataFilePaths(withStats bool) {
filetypes := []string{"config", "table of contents", "metadata"}
missing := false
for _, filetype := range filetypes {
filepath := globalFPInfo.GetBackupFilePath(filetype)
if !iohelper.FileExistsAndIsReadable(filepath) {
missing = true
gplog.Error("Cannot access %s file %s", filetype, filepath)
}
}
if withStats {
filepath := globalFPInfo.GetStatisticsFilePath()
if !iohelper.FileExistsAndIsReadable(filepath) {
missing = true
gplog.Error("Cannot access statistics file %s", filepath)
gplog.Error(`Note that the "-with-stats" flag must be passed to gpbackup to generate a statistics file.`)
}
}
if missing {
gplog.Fatal(errors.Errorf("One or more metadata files do not exist or are not readable."), "Cannot proceed with restore")
}
}
func GetResizeClusterInfo() (int, int, bool, int) {
isResizeCluster := MustGetFlagBool(options.RESIZE_CLUSTER)
origSize := backupConfig.SegmentCount
destSize := len(globalCluster.ContentIDs) - 1
if !isResizeCluster && origSize == 0 { // Backup taken with version <1.26, no SegmentCount stored
origSize = destSize
}
batches := 1
if isResizeCluster && origSize > destSize {
batches = origSize / destSize
// If dest doesn't divide evenly into orig, there's one more incomplete batch
if origSize%destSize != 0 {
batches += 1
}
}
return origSize, destSize, isResizeCluster, batches
}