diff --git a/doc/xml/release.xml b/doc/xml/release.xml
index 4cef71b96..11e33131f 100644
--- a/doc/xml/release.xml
+++ b/doc/xml/release.xml
@@ -41,6 +41,7 @@
+
diff --git a/src/command/restore/file.c b/src/command/restore/file.c
index 65583c4a4..3f16ccd41 100644
--- a/src/command/restore/file.c
+++ b/src/command/restore/file.c
@@ -21,13 +21,14 @@ Restore File
/**********************************************************************************************************************************/
bool
restoreFile(
- const String *repoFile, const String *repoFileReference, CompressType repoFileCompressType, const String *pgFile,
- const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified, mode_t pgFileMode,
- const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
+ const String *repoFile, unsigned int repoIdx, const String *repoFileReference, CompressType repoFileCompressType,
+ const String *pgFile, const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified,
+ mode_t pgFileMode, const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
const String *cipherPass)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, repoFile);
+ FUNCTION_LOG_PARAM(UINT, repoIdx);
FUNCTION_LOG_PARAM(STRING, repoFileReference);
FUNCTION_LOG_PARAM(ENUM, repoFileCompressType);
FUNCTION_LOG_PARAM(STRING, pgFile);
@@ -164,7 +165,7 @@ restoreFile(
// Copy file
storageCopyP(
storageNewReadP(
- storageRepo(),
+ storageRepoIdx(repoIdx),
strNewFmt(
STORAGE_REPO_BACKUP "/%s/%s%s", strZ(repoFileReference), strZ(repoFile),
strZ(compressExtStr(repoFileCompressType))),
diff --git a/src/command/restore/file.h b/src/command/restore/file.h
index 1f23516e4..81ad488f8 100644
--- a/src/command/restore/file.h
+++ b/src/command/restore/file.h
@@ -14,9 +14,9 @@ Functions
***********************************************************************************************************************************/
// Copy a file from the backup to the specified destination
bool restoreFile(
- const String *repoFile, const String *repoFileReference, CompressType repoFileCompressType, const String *pgFile,
- const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified, mode_t pgFileMode,
- const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
+ const String *repoFile, unsigned int repoIdx, const String *repoFileReference, CompressType repoFileCompressType,
+ const String *pgFile, const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified,
+ mode_t pgFileMode, const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
const String *cipherPass);
#endif
diff --git a/src/command/restore/protocol.c b/src/command/restore/protocol.c
index 0d0ce1996..2fa00132e 100644
--- a/src/command/restore/protocol.c
+++ b/src/command/restore/protocol.c
@@ -40,14 +40,14 @@ restoreProtocol(const String *command, const VariantList *paramList, ProtocolSer
server,
VARBOOL(
restoreFile(
- varStr(varLstGet(paramList, 0)), varStr(varLstGet(paramList, 1)),
- (CompressType)varUIntForce(varLstGet(paramList, 2)), varStr(varLstGet(paramList, 3)),
- varStr(varLstGet(paramList, 4)), varBoolForce(varLstGet(paramList, 5)), varUInt64(varLstGet(paramList, 6)),
- (time_t)varInt64Force(varLstGet(paramList, 7)),
- (mode_t)cvtZToUIntBase(strZ(varStr(varLstGet(paramList, 8))), 8),
- varStr(varLstGet(paramList, 9)), varStr(varLstGet(paramList, 10)),
- (time_t)varInt64Force(varLstGet(paramList, 11)), varBoolForce(varLstGet(paramList, 12)),
- varBoolForce(varLstGet(paramList, 13)), varStr(varLstGet(paramList, 14)))));
+ varStr(varLstGet(paramList, 0)), varUIntForce(varLstGet(paramList, 1)), varStr(varLstGet(paramList, 2)),
+ (CompressType)varUIntForce(varLstGet(paramList, 3)), varStr(varLstGet(paramList, 4)),
+ varStr(varLstGet(paramList, 5)), varBoolForce(varLstGet(paramList, 6)), varUInt64(varLstGet(paramList, 7)),
+ (time_t)varInt64Force(varLstGet(paramList, 8)),
+ (mode_t)cvtZToUIntBase(strZ(varStr(varLstGet(paramList, 9))), 8),
+ varStr(varLstGet(paramList, 10)), varStr(varLstGet(paramList, 11)),
+ (time_t)varInt64Force(varLstGet(paramList, 12)), varBoolForce(varLstGet(paramList, 13)),
+ varBoolForce(varLstGet(paramList, 14)), varStr(varLstGet(paramList, 15)))));
}
else
found = false;
diff --git a/src/command/restore/restore.c b/src/command/restore/restore.c
index a558d302d..2f6a153d9 100644
--- a/src/command/restore/restore.c
+++ b/src/command/restore/restore.c
@@ -207,37 +207,120 @@ getEpoch(const String *targetTime)
/***********************************************************************************************************************************
Get the backup set to restore
***********************************************************************************************************************************/
-static String *
-restoreBackupSet(InfoBackup *infoBackup)
+typedef struct RestoreBackupData
{
- FUNCTION_LOG_BEGIN(logLevelDebug);
- FUNCTION_LOG_PARAM(INFO_BACKUP, infoBackup);
- FUNCTION_LOG_END();
+ unsigned int repoIdx; // Internal repo index
+ CipherType repoCipherType; // Repo encryption type (0 = none)
+ const String *backupCipherPass; // Passphrase of backup files if repo is encrypted (else NULL)
+ const String *backupSet; // Backup set to restore
+} RestoreBackupData;
- ASSERT(infoBackup != NULL);
+#define FUNCTION_LOG_RESTORE_BACKUP_DATA_TYPE \
+ RestoreBackupData
+#define FUNCTION_LOG_RESTORE_BACKUP_DATA_FORMAT(value, buffer, bufferSize) \
+ objToLog(&value, "RestoreBackupData", buffer, bufferSize)
- String *result = NULL;
+// Helper function for restoreBackupSet
+static RestoreBackupData
+restoreBackupData(const String *backupLabel, unsigned int repoIdx, const String *backupCipherPass)
+{
+ ASSERT(backupLabel != NULL);
+
+ RestoreBackupData restoreBackup = {0};
+
+ MEM_CONTEXT_PRIOR_BEGIN()
+ {
+ restoreBackup.backupSet = strDup(backupLabel);
+ restoreBackup.repoIdx = repoIdx;
+ restoreBackup.repoCipherType = cipherType(cfgOptionIdxStr(cfgOptRepoCipherType, repoIdx));
+ restoreBackup.backupCipherPass = strDup(backupCipherPass);
+ }
+ MEM_CONTEXT_PRIOR_END();
+
+ return restoreBackup;
+}
+
+static RestoreBackupData
+restoreBackupSet(void)
+{
+ FUNCTION_LOG_VOID(logLevelDebug);
+
+ RestoreBackupData result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
- // If backup set to restore is default (i.e. latest) then get the actual set
- const String *backupSet = NULL;
+ // Initialize the repo index
+ unsigned int repoIdxMin = 0;
+ unsigned int repoIdxMax = cfgOptionGroupIdxTotal(cfgOptGrpRepo) - 1;
+ // If the repo was specified then set index to the array location and max to loop only once
+ if (cfgOptionTest(cfgOptRepo))
+ {
+ repoIdxMin = cfgOptionGroupIdxDefault(cfgOptGrpRepo);
+ repoIdxMax = repoIdxMin;
+ }
+
+ // Initialize a backup candidate list
+ List *backupCandidateList = lstNewP(sizeof(RestoreBackupData));
+
+ const String *backupSetRequested = NULL;
+ time_t timeTargetEpoch = 0;
+
+ // If the set option was not provided by the user but a time to recover was set, then we will need to search for a backup
+ // set that satisfies the time condition, else we will use the backup provided
if (cfgOptionSource(cfgOptSet) == cfgSourceDefault)
{
- if (infoBackupDataTotal(infoBackup) == 0)
- THROW(BackupSetInvalidError, "no backup sets to restore");
-
- time_t timeTargetEpoch = 0;
-
- // If the recovery type is time, attempt to determine the backup set
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_TIME_STR))
- {
timeTargetEpoch = getEpoch(cfgOptionStr(cfgOptTarget));
+ }
+ else
+ backupSetRequested = cfgOptionStr(cfgOptSet);
- // Try to find the newest backup set with a stop time before the target recovery time
+ // Search through the repo list for a backup set to use for recovery
+ for (unsigned int repoIdx = repoIdxMin; repoIdx <= repoIdxMax; repoIdx++)
+ {
+ // Get the repo storage in case it is remote and encryption settings need to be pulled down
+ storageRepoIdx(repoIdx);
+
+ InfoBackup *infoBackup = NULL;
+
+ // Attempt to load backup.info
+ TRY_BEGIN()
+ {
+ infoBackup = infoBackupLoadFile(
+ storageRepoIdx(repoIdx), INFO_BACKUP_PATH_FILE_STR, cipherType(cfgOptionIdxStr(cfgOptRepoCipherType, repoIdx)),
+ cfgOptionIdxStrNull(cfgOptRepoCipherPass, repoIdx));
+ }
+ CATCH_ANY()
+ {
+ LOG_WARN_FMT(
+ "repo%u: [%s] %s", cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx), errorTypeName(errorType()), errorMessage());
+ }
+ TRY_END();
+
+ // If unable to load the backup info file, then move on to next repo
+ if (infoBackup == NULL)
+ continue;
+
+ if (infoBackupDataTotal(infoBackup) == 0)
+ {
+ LOG_WARN_FMT(
+ "repo%u: [%s] no backup sets to restore", cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx),
+ errorTypeName(&BackupSetInvalidError));
+ continue;
+ }
+
+ // If a backup set was not specified, then see if a time to recover was requested
+ if (backupSetRequested == NULL)
+ {
+ // Get the latest backup
+ InfoBackupData latestBackup = infoBackupData(infoBackup, infoBackupDataTotal(infoBackup) - 1);
+
+ // If the recovery type is time, attempt to determine the backup set
if (timeTargetEpoch != 0)
{
+ bool found = false;
+
// Search current backups from newest to oldest
for (unsigned int keyIdx = infoBackupDataTotal(infoBackup) - 1; (int)keyIdx >= 0; keyIdx--)
{
@@ -247,52 +330,76 @@ restoreBackupSet(InfoBackup *infoBackup)
// If the end of the backup is before the target time, then select this backup
if (backupData.backupTimestampStop < timeTargetEpoch)
{
- backupSet = backupData.backupLabel;
+ found = true;
+
+ result = restoreBackupData(
+ backupData.backupLabel, repoIdx, infoPgCipherPass(infoBackupPg(infoBackup)));
break;
}
}
- if (backupSet == NULL)
+ // If a backup was found on this repo matching the criteria for time then exit, else determine if the latest
+ // backup from this repo might be used
+ if (found)
+ break;
+ else
{
- LOG_WARN_FMT(
- "unable to find backup set with stop time less than '%s', latest backup set will be used",
- strZ(cfgOptionStr(cfgOptTarget)));
+ // If a backup was not yet found then set the latest from this repo as the backup that might be used
+ RestoreBackupData candidate = restoreBackupData(
+ latestBackup.backupLabel, repoIdx, infoPgCipherPass(infoBackupPg(infoBackup)));
+
+ lstAdd(backupCandidateList, &candidate);
}
}
- }
-
- // If a backup set was not found or the recovery type was not time, then use the latest backup
- if (backupSet == NULL)
- backupSet = infoBackupData(infoBackup, infoBackupDataTotal(infoBackup) - 1).backupLabel;
- }
- // Otherwise check to make sure the specified backup set is valid
- else
- {
- bool found = false;
- backupSet = cfgOptionStr(cfgOptSet);
-
- for (unsigned int backupIdx = 0; backupIdx < infoBackupDataTotal(infoBackup); backupIdx++)
- {
- if (strEq(infoBackupData(infoBackup, backupIdx).backupLabel, backupSet))
+ else
{
- found = true;
+ // If the recovery type was not time (or time provided was not valid), then use the latest backup from this repo
+ result = restoreBackupData(latestBackup.backupLabel, repoIdx, infoPgCipherPass(infoBackupPg(infoBackup)));
break;
}
}
+ // Otherwise check to see if the specified backup set is on this repo
+ else
+ {
+ for (unsigned int backupIdx = 0; backupIdx < infoBackupDataTotal(infoBackup); backupIdx++)
+ {
+ if (strEq(infoBackupData(infoBackup, backupIdx).backupLabel, backupSetRequested))
+ {
+ result = restoreBackupData(backupSetRequested, repoIdx, infoPgCipherPass(infoBackupPg(infoBackup)));
+ break;
+ }
+ }
- if (!found)
- THROW_FMT(BackupSetInvalidError, "backup set %s is not valid", strZ(backupSet));
+ // If the backup set is found, then exit, else continue to next repo
+ if (result.backupSet != NULL)
+ break;
+ }
}
- MEM_CONTEXT_PRIOR_BEGIN()
+ // Still no backup set to use after checking all the repos required to be checked?
+ if (result.backupSet == NULL)
{
- result = strDup(backupSet);
+ if (backupSetRequested != NULL)
+ THROW_FMT(BackupSetInvalidError, "backup set %s is not valid", strZ(backupSetRequested));
+ else if (timeTargetEpoch != 0 && lstSize(backupCandidateList) > 0)
+ {
+ // Since the repos were scanned in priority order, use the first candidate found
+ result = restoreBackupData(
+ ((RestoreBackupData *)lstGet(backupCandidateList, 0))->backupSet,
+ ((RestoreBackupData *)lstGet(backupCandidateList, 0))->repoIdx,
+ ((RestoreBackupData *)lstGet(backupCandidateList, 0))->backupCipherPass);
+
+ LOG_WARN_FMT(
+ "unable to find backup set with stop time less than '%s', repo%u: latest backup set will be used",
+ strZ(cfgOptionStr(cfgOptTarget)), cfgOptionGroupIdxToKey(cfgOptGrpRepo, result.repoIdx));
+ }
+ else
+ THROW(BackupSetInvalidError, "no backup set found to restore");
}
- MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
- FUNCTION_LOG_RETURN(STRING, result);
+ FUNCTION_LOG_RETURN_STRUCT(result);
}
/***********************************************************************************************************************************
@@ -1928,6 +2035,7 @@ Return new restore jobs as requested
***********************************************************************************************************************************/
typedef struct RestoreJobData
{
+ unsigned int repoIdx; // Internal repo idx
Manifest *manifest; // Backup manifest
List *queueList; // List of processing queues
RegExp *zeroExp; // Identify files that should be sparse zeroed
@@ -1987,8 +2095,8 @@ static ProtocolParallelJob *restoreJobCallback(void *data, unsigned int clientId
// Create restore job
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_RESTORE_FILE_STR);
-
protocolCommandParamAdd(command, VARSTR(file->name));
+ protocolCommandParamAdd(command, VARUINT(jobData->repoIdx));
protocolCommandParamAdd(
command, file->reference != NULL ?
VARSTR(file->reference) : VARSTR(manifestData(jobData->manifest)->backupLabel));
@@ -2042,23 +2150,16 @@ cmdRestore(void)
// Validate restore path
restorePathValidate();
- // Get the repo storage in case it is remote and encryption settings need to be pulled down
- storageRepo();
-
- // Load backup.info
- InfoBackup *infoBackup = infoBackupLoadFile(
- storageRepo(), INFO_BACKUP_PATH_FILE_STR, cipherType(cfgOptionStr(cfgOptRepoCipherType)),
- cfgOptionStrNull(cfgOptRepoCipherPass));
-
// Get the backup set
- const String *backupSet = restoreBackupSet(infoBackup);
+ RestoreBackupData backupData = restoreBackupSet();
// Load manifest
- RestoreJobData jobData = {0};
+ RestoreJobData jobData = {.repoIdx = backupData.repoIdx};
jobData.manifest = manifestLoadFile(
- storageRepo(), strNewFmt(STORAGE_REPO_BACKUP "/%s/" BACKUP_MANIFEST_FILE, strZ(backupSet)),
- cipherType(cfgOptionStr(cfgOptRepoCipherType)), infoPgCipherPass(infoBackupPg(infoBackup)));
+ storageRepoIdx(backupData.repoIdx),
+ strNewFmt(STORAGE_REPO_BACKUP "/%s/" BACKUP_MANIFEST_FILE, strZ(backupData.backupSet)), backupData.repoCipherType,
+ backupData.backupCipherPass);
// Validate manifest. Don't use strict mode because we'd rather ignore problems that won't affect a restore.
manifestValidate(jobData.manifest, false);
@@ -2067,10 +2168,11 @@ cmdRestore(void)
jobData.cipherSubPass = manifestCipherSubPass(jobData.manifest);
// Validate the manifest
- restoreManifestValidate(jobData.manifest, backupSet);
+ restoreManifestValidate(jobData.manifest, backupData.backupSet);
// Log the backup set to restore
- LOG_INFO_FMT("restore backup set %s", strZ(backupSet));
+ LOG_INFO_FMT(
+ "repo%u: restore backup set %s", cfgOptionGroupIdxToKey(cfgOptGrpRepo, backupData.repoIdx), strZ(backupData.backupSet));
// Map manifest
restoreManifestMap(jobData.manifest);
diff --git a/src/config/load.c b/src/config/load.c
index c3d774881..2f7b1c9ce 100644
--- a/src/config/load.c
+++ b/src/config/load.c
@@ -70,8 +70,8 @@ cfgLoadUpdateOption(void)
// Make sure repo option is set for the default command role when it is not internal and more than one repo is configured or the
// first configured repo is not key 1. Filter out any commands where this does not apply.
- if (!cfgCommandHelp() && cfgOptionValid(cfgOptRepo) && !cfgOptionTest(cfgOptRepo) && cfgCommand() != cfgCmdArchiveGet &&
- cfgCommand() != cfgCmdInfo && cfgCommand() != cfgCmdExpire &&
+ if (!cfgCommandHelp() && cfgCommand() != cfgCmdInfo && cfgCommand() != cfgCmdExpire && cfgCommand() != cfgCmdArchiveGet &&
+ cfgCommand() != cfgCmdRestore && cfgOptionValid(cfgOptRepo) && !cfgOptionTest(cfgOptRepo) &&
(cfgOptionGroupIdxTotal(cfgOptGrpRepo) > 1 || cfgOptionGroupIdxToKey(cfgOptGrpRepo, 0) != 1))
{
THROW_FMT(
diff --git a/test/expect/mock-all-001.log b/test/expect/mock-all-001.log
index f04f6e8c0..41725a0ba 100644
--- a/test/expect/mock-all-001.log
+++ b/test/expect/mock-all-001.log
@@ -489,7 +489,7 @@ restore delta, backup '[BACKUP-FULL-2]' - add and delete files (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --set=[BACKUP-FULL-2] --link-all --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --exec-id=[EXEC-ID] --job-retry=0 --link-all --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-FULL-2] --stanza=db
-P00 INFO: restore backup set [BACKUP-FULL-2]
+P00 INFO: repo1: restore backup set [BACKUP-FULL-2]
P00 WARN: unknown user in backup manifest mapped to '[USER-2]'
P00 WARN: unknown group in backup manifest mapped to '[GROUP-2]'
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base' exists
@@ -574,7 +574,7 @@ restore delta, backup '[BACKUP-FULL-2]' - fix permissions (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --set=[BACKUP-FULL-2] --link-all --log-level-console=detail --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --exec-id=[EXEC-ID] --job-retry=0 --link-all --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-FULL-2] --stanza=db
-P00 INFO: restore backup set [BACKUP-FULL-2]
+P00 INFO: repo1: restore backup set [BACKUP-FULL-2]
P00 WARN: unknown user in backup manifest mapped to '[USER-1]'
P00 WARN: unknown group in backup manifest mapped to '[GROUP-1]'
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base' exists
@@ -635,7 +635,7 @@ restore delta, backup '[BACKUP-FULL-2]' - fix broken symlink (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --set=[BACKUP-FULL-2] --link-all --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --exec-id=[EXEC-ID] --job-retry=0 --link-all --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-FULL-2] --stanza=db
-P00 INFO: restore backup set [BACKUP-FULL-2]
+P00 INFO: repo1: restore backup set [BACKUP-FULL-2]
P00 WARN: unknown user in backup manifest mapped to current user
P00 WARN: unknown group in backup manifest mapped to current group
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base' exists
@@ -696,7 +696,7 @@ restore delta, force, backup '[BACKUP-FULL-2]' - restore links as directories (d
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --force --set=[BACKUP-FULL-2] --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --exec-id=[EXEC-ID] --force --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-FULL-2] --stanza=db
-P00 INFO: restore backup set [BACKUP-FULL-2]
+P00 INFO: repo1: restore backup set [BACKUP-FULL-2]
P00 WARN: file link 'pg_hba.conf' will be restored as a file at the same location
P00 WARN: contents of directory link 'pg_stat' will be restored in a directory at the same location
P00 WARN: file link 'postgresql.conf' will be restored as a file at the same location
@@ -1392,7 +1392,7 @@ restore, backup '[BACKUP-DIFF-1]', remap - remap all paths (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --set=[BACKUP-DIFF-1] --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --exec-id=[EXEC-ID] --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base-2 --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-DIFF-1] --stanza=db --tablespace-map=1=[TEST_PATH]/db-primary/db/tablespace/ts1-2 --tablespace-map=2=[TEST_PATH]/db-primary/db/tablespace/ts2-2
-P00 INFO: restore backup set [BACKUP-DIFF-1]
+P00 INFO: repo1: restore backup set [BACKUP-DIFF-1]
P00 INFO: remap data directory to '[TEST_PATH]/db-primary/db/base-2'
P00 INFO: map tablespace 'pg_tblspc/1' to '[TEST_PATH]/db-primary/db/tablespace/ts1-2'
P00 INFO: map tablespace 'pg_tblspc/2' to '[TEST_PATH]/db-primary/db/tablespace/ts2-2'
@@ -1477,7 +1477,7 @@ restore delta, backup '[BACKUP-DIFF-1]', remap - ensure file in tblspc root rema
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --set=[BACKUP-DIFF-1] --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --exec-id=[EXEC-ID] --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base-2 --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --set=[BACKUP-DIFF-1] --stanza=db --tablespace-map=1=[TEST_PATH]/db-primary/db/tablespace/ts1-2 --tablespace-map=2=[TEST_PATH]/db-primary/db/tablespace/ts2-2
-P00 INFO: restore backup set [BACKUP-DIFF-1]
+P00 INFO: repo1: restore backup set [BACKUP-DIFF-1]
P00 INFO: remap data directory to '[TEST_PATH]/db-primary/db/base-2'
P00 INFO: map tablespace 'pg_tblspc/1' to '[TEST_PATH]/db-primary/db/tablespace/ts1-2'
P00 INFO: map tablespace 'pg_tblspc/2' to '[TEST_PATH]/db-primary/db/tablespace/ts2-2'
@@ -2815,7 +2815,7 @@ restore delta, remap - selective restore 16384 (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --db-include=16384 --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --db-include=16384 --delta --exec-id=[EXEC-ID] --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base-2 --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --stanza=db --tablespace-map=2=[TEST_PATH]/db-primary/db/tablespace/ts2-2
-P00 INFO: restore backup set [BACKUP-DIFF-4]
+P00 INFO: repo1: restore backup set [BACKUP-DIFF-4]
P00 INFO: map tablespace 'pg_tblspc/2' to '[TEST_PATH]/db-primary/db/tablespace/ts2-2'
P00 DETAIL: databases found for selective restore (1, 16384, 32768)
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base-2' exists
@@ -2879,7 +2879,7 @@ restore delta, remap - selective restore 32768 (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --delta --db-include=32768 --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --db-include=32768 --delta --exec-id=[EXEC-ID] --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base-2 --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --stanza=db --tablespace-map=2=[TEST_PATH]/db-primary/db/tablespace/ts2-2
-P00 INFO: restore backup set [BACKUP-DIFF-4]
+P00 INFO: repo1: restore backup set [BACKUP-DIFF-4]
P00 INFO: map tablespace 'pg_tblspc/2' to '[TEST_PATH]/db-primary/db/tablespace/ts2-2'
P00 DETAIL: databases found for selective restore (1, 16384, 32768)
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base-2' exists
@@ -2953,7 +2953,7 @@ restore, remap - no tablespace remap (db-primary host)
> [CONTAINER-EXEC] db-primary [BACKREST-BIN] --config=[TEST_PATH]/db-primary/pgbackrest.conf --tablespace-map-all=../../tablespace --repo=1 --stanza=db restore
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: restore command begin [BACKREST-VERSION]: --buffer-size=[BUFFER-SIZE] --config=[TEST_PATH]/db-primary/pgbackrest.conf --exec-id=[EXEC-ID] --job-retry=0 --lock-path=[TEST_PATH]/db-primary/lock --log-level-console=detail --log-level-file=[LOG-LEVEL-FILE] --log-level-stderr=off --log-path=[TEST_PATH]/db-primary/log[] --no-log-timestamp --pg1-path=[TEST_PATH]/db-primary/db/base-2/base --protocol-timeout=60 --repo=1 --repo1-path=[TEST_PATH]/db-primary/repo --stanza=db --tablespace-map-all=../../tablespace
-P00 INFO: restore backup set [BACKUP-DIFF-4]
+P00 INFO: repo1: restore backup set [BACKUP-DIFF-4]
P00 INFO: remap data directory to '[TEST_PATH]/db-primary/db/base-2/base'
P00 INFO: map tablespace 'pg_tblspc/2' to '../../tablespace/ts2'
P00 DETAIL: check '[TEST_PATH]/db-primary/db/base-2/base' exists
diff --git a/test/src/module/command/restoreTest.c b/test/src/module/command/restoreTest.c
index bcf7baa91..79d8126a8 100644
--- a/test/src/module/command/restoreTest.c
+++ b/test/src/module/command/restoreTest.c
@@ -155,6 +155,7 @@ testRun(void)
{
const String *repoFileReferenceFull = strNew("20190509F");
const String *repoFile1 = strNew("pg_data/testfile");
+ unsigned int repoIdx = 0;
// Start a protocol server to test the protocol directly
Buffer *serverWrite = bufNew(8192);
@@ -178,7 +179,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("sparse-zero"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("sparse-zero"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), true, 0x10000000000UL, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
false, "zero sparse 1TB file");
@@ -186,7 +187,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("normal-zero"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("normal-zero"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, false, false, NULL),
true, "zero-length file");
@@ -204,7 +205,7 @@ testRun(void)
TEST_ERROR(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeGz, strNew("normal"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeGz, strNew("normal"),
strNew("ffffffffffffffffffffffffffffffffffffffff"), false, 7, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, false, false, strNew("badpass")),
ChecksumError,
@@ -219,7 +220,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeGz, strNew("normal"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeGz, strNew("normal"),
strNew("d1cd8a7d11daa26814b93eb604e1d49ab4b43770"), false, 7, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, false, false, strNew("badpass")),
true, "copy file");
@@ -242,7 +243,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta missing");
@@ -254,7 +255,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
false, "sha1 delta existing");
@@ -263,7 +264,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 1557432155, true, true, NULL),
false, "sha1 delta force existing");
@@ -273,7 +274,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta existing, size differs");
@@ -284,7 +285,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 1557432155, true, true, NULL),
true, "delta force existing, size differs");
@@ -296,7 +297,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta existing, content differs");
@@ -307,14 +308,14 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 1557432155, true, true, NULL),
true, "delta force existing, timestamp differs");
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 1557432153, true, true, NULL),
true, "delta force existing, timestamp after copy time");
@@ -324,7 +325,7 @@ testRun(void)
TEST_RESULT_BOOL(
restoreFile(
- repoFile1, repoFileReferenceFull, compressTypeNone, strNew("delta"),
+ repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, strNew("delta"),
strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600, strNew(testUser()),
strNew(testGroup()), 0, true, false, NULL),
false, "sha1 delta existing, content differs");
@@ -333,6 +334,7 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
VariantList *paramList = varLstNew();
varLstAdd(paramList, varNewStr(repoFile1));
+ varLstAdd(paramList, varNewUInt(repoIdx));
varLstAdd(paramList, varNewStr(repoFileReferenceFull));
varLstAdd(paramList, varNewUInt(compressTypeNone));
varLstAdd(paramList, varNewStrZ("protocol"));
@@ -364,6 +366,7 @@ testRun(void)
paramList = varLstNew();
varLstAdd(paramList, varNewStr(repoFile1));
+ varLstAdd(paramList, varNewUInt(repoIdx));
varLstAdd(paramList, varNewStr(repoFileReferenceFull));
varLstAdd(paramList, varNewUInt(compressTypeNone));
varLstAdd(paramList, varNewStrZ("protocol"));
@@ -517,14 +520,18 @@ testRun(void)
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error when no backups are present");
- InfoBackup *infoBackup = infoBackupNewLoad(ioBufferReadNew(harnessInfoChecksumZ(TEST_RESTORE_BACKUP_INFO_DB)));
- TEST_ERROR_FMT(restoreBackupSet(infoBackup), BackupSetInvalidError, "no backup sets to restore");
+ HRN_INFO_PUT(storageRepoWrite(), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO_DB);
+ TEST_ERROR_FMT(restoreBackupSet(), BackupSetInvalidError, "no backup set found to restore");
+ TEST_RESULT_LOG("P00 WARN: repo1: [BackupSetInvalidError] no backup sets to restore");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error on invalid backup set");
- infoBackup = infoBackupNewLoad(
- ioBufferReadNew(harnessInfoChecksumZ(TEST_RESTORE_BACKUP_INFO "\n" TEST_RESTORE_BACKUP_INFO_DB)));
+ HRN_INFO_PUT(
+ storageRepoWrite(), INFO_BACKUP_PATH_FILE,
+ TEST_RESTORE_BACKUP_INFO
+ "\n"
+ TEST_RESTORE_BACKUP_INFO_DB);
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
@@ -533,55 +540,154 @@ testRun(void)
strLstAddZ(argList, "--set=BOGUS");
harnessCfgLoad(cfgCmdRestore, argList);
- TEST_ERROR(restoreBackupSet(infoBackup), BackupSetInvalidError, "backup set BOGUS is not valid");
+ TEST_ERROR(restoreBackupSet(), BackupSetInvalidError, "backup set BOGUS is not valid");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("target time");
setenv("TZ", "UTC", true);
- infoBackup = infoBackupNewLoad(
- ioBufferReadNew(harnessInfoChecksumZ(TEST_RESTORE_BACKUP_INFO "\n" TEST_RESTORE_BACKUP_INFO_DB)));
+ const String *repoPath2 = strNewFmt("%s/repo2", testPath());
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
- strLstAdd(argList, strNewFmt("--repo1-path=%s", strZ(repoPath)));
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath2);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath);
strLstAdd(argList, strNewFmt("--pg1-path=%s", strZ(pgPath)));
strLstAddZ(argList, "--type=time");
strLstAddZ(argList, "--target=2016-12-19 16:28:04-0500");
harnessCfgLoad(cfgCmdRestore, argList);
- TEST_RESULT_STR_Z(restoreBackupSet(infoBackup), "20161219-212741F_20161219-212803D", "backup set found");
+ // Write out backup.info with no current backups to repo1
+ HRN_INFO_PUT(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO_DB);
+ RestoreBackupData backupData = {0};
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20161219-212741F_20161219-212803D", "backup set found");
+ TEST_RESULT_UINT(backupData.repoIdx, 1, "backup set found, repo2");
+ TEST_RESULT_LOG("P00 WARN: repo1: [BackupSetInvalidError] no backup sets to restore");
+
+ // Switch repo paths and confirm same result but on repo1
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
- strLstAdd(argList, strNewFmt("--repo1-path=%s", strZ(repoPath)));
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath2);
strLstAdd(argList, strNewFmt("--pg1-path=%s", strZ(pgPath)));
strLstAddZ(argList, "--type=time");
- strLstAddZ(argList, "--target=2016-12-19 16:27:30-0500");
+ strLstAddZ(argList, "--target=2016-12-19 16:28:04-0500");
harnessCfgLoad(cfgCmdRestore, argList);
- TEST_RESULT_STR_Z(restoreBackupSet(infoBackup), "20161219-212741F_20161219-212918I", "default to latest backup set");
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20161219-212741F_20161219-212803D", "backup set found");
+ TEST_RESULT_UINT(backupData.repoIdx, 0, "backup set found, repo1");
+
+ // -------------------------------------------------------------------------------------------------------------------------
+ TEST_TITLE("target time, multi repo, latest used");
+
+ argList = strLstNew();
+ hrnCfgArgRawZ(argList, cfgOptStanza ,"test1");
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath2);
+ hrnCfgArgKeyRaw(argList, cfgOptPgPath, 1, pgPath);
+ hrnCfgArgRawZ(argList, cfgOptType, "time");
+ hrnCfgArgRawZ(argList, cfgOptTarget, "2016-12-19 16:27:30-0500");
+
+ harnessCfgLoad(cfgCmdRestore, argList);
+
+ #define TEST_RESTORE_BACKUP_INFO_NEWEST \
+ "[backup:current]\n" \
+ "20201212-201243F={\"backrest-format\":5,\"backrest-version\":\"2.04\"," \
+ "\"backup-archive-start\":\"00000007000000000000001C\",\"backup-archive-stop\":\"00000007000000000000001C\"," \
+ "\"backup-info-repo-size\":3159776,\"backup-info-repo-size-delta\":3159776,\"backup-info-size\":26897030," \
+ "\"backup-info-size-delta\":26897030,\"backup-timestamp-start\":1607803000,\"backup-timestamp-stop\":1607803963," \
+ "\"backup-type\":\"full\",\"db-id\":1,\"option-archive-check\":true,\"option-archive-copy\":false," \
+ "\"option-backup-standby\":false,\"option-checksum-page\":false,\"option-compress\":true,\"option-hardlink\":false," \
+ "\"option-online\":true}\n"
+
+ // Write out backup.info with current backup newest to repo2 but still does not satisfy time requirement, so repo1 chosen
+ HRN_INFO_PUT(
+ storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE,
+ TEST_RESTORE_BACKUP_INFO_NEWEST
+ "\n"
+ TEST_RESTORE_BACKUP_INFO_DB);
+
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20161219-212741F_20161219-212918I", "default to latest backup set");
+ TEST_RESULT_UINT(backupData.repoIdx, 0, "repo1 chosen because of priority order");
TEST_RESULT_LOG(
- "P00 WARN: unable to find backup set with stop time less than '2016-12-19 16:27:30-0500', latest backup set will be"
- " used");
+ "P00 WARN: unable to find backup set with stop time less than '2016-12-19 16:27:30-0500', repo1: latest backup set"
+ " will be used");
+ // Request repo2 - latest from repo2 will be chosen
+ hrnCfgArgRawZ(argList, cfgOptRepo, "2");
+ harnessCfgLoad(cfgCmdRestore, argList);
+
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20201212-201243F", "default to latest backup set");
+ TEST_RESULT_UINT(backupData.repoIdx, 1, "repo2 chosen because repo option set");
+ TEST_RESULT_LOG(
+ "P00 WARN: unable to find backup set with stop time less than '2016-12-19 16:27:30-0500', repo2: latest backup set"
+ " will be used");
+
+ // Switch paths so newest on repo1
argList = strLstNew();
- strLstAddZ(argList, "--stanza=test1");
- strLstAdd(argList, strNewFmt("--repo1-path=%s", strZ(repoPath)));
- strLstAdd(argList, strNewFmt("--pg1-path=%s", strZ(pgPath)));
- strLstAddZ(argList, "--type=time");
- strLstAddZ(argList, "--target=Tue, 15 Nov 1994 12:45:26");
+ hrnCfgArgRawZ(argList, cfgOptStanza ,"test1");
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath2);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptPgPath, 1, pgPath);
+ hrnCfgArgRawZ(argList, cfgOptType, "time");
+ hrnCfgArgRawZ(argList, cfgOptTarget, "2016-12-19 16:27:30-0500");
harnessCfgLoad(cfgCmdRestore, argList);
- TEST_RESULT_STR_Z(restoreBackupSet(infoBackup), "20161219-212741F_20161219-212918I", "time invalid format, default latest");
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20201212-201243F", "default to latest backup set");
+ TEST_RESULT_UINT(backupData.repoIdx, 0, "repo1 chosen because of priority order");
+ TEST_RESULT_LOG(
+ "P00 WARN: unable to find backup set with stop time less than '2016-12-19 16:27:30-0500', repo1: latest backup set"
+ " will be used");
+
+ argList = strLstNew();
+ hrnCfgArgRawZ(argList, cfgOptStanza ,"test1");
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath2);
+ hrnCfgArgKeyRaw(argList, cfgOptPgPath, 1, pgPath);
+ hrnCfgArgRawZ(argList, cfgOptType, "time");
+ hrnCfgArgRawZ(argList, cfgOptTarget, "Tue, 15 Nov 1994 12:45:26");
+
+ harnessCfgLoad(cfgCmdRestore, argList);
+
+ TEST_ASSIGN(backupData, restoreBackupSet(), "get backup set");
+ TEST_RESULT_STR_Z(backupData.backupSet, "20161219-212741F_20161219-212918I", "time invalid format, default latest");
+ TEST_RESULT_UINT(backupData.repoIdx, 0, "repo1 chosen because of priority order");
TEST_RESULT_LOG(
"P00 WARN: automatic backup set selection cannot be performed with provided time 'Tue, 15 Nov 1994 12:45:26',"
" latest backup set will be used\n"
" HINT: time format must be YYYY-MM-DD HH:MM:SS with optional msec and optional timezone"
" (+/- HH or HHMM or HH:MM) - if timezone is omitted, local time is assumed (for UTC use +00)");
+
+ // -------------------------------------------------------------------------------------------------------------------------
+ TEST_TITLE("target time, multi repo, no candidates found");
+
+ argList = strLstNew();
+ hrnCfgArgRawZ(argList, cfgOptStanza ,"test1");
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPath2);
+ hrnCfgArgKeyRaw(argList, cfgOptPgPath, 1, pgPath);
+ hrnCfgArgRawZ(argList, cfgOptType, "time");
+ hrnCfgArgRawZ(argList, cfgOptTarget, "2016-12-19 16:27:30-0500");
+
+ harnessCfgLoad(cfgCmdRestore, argList);
+
+ // Write out backup.info with no current backups to repo1 and repo2
+ HRN_INFO_PUT(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO_DB);
+ HRN_INFO_PUT(storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO_DB);
+
+ TEST_ERROR_FMT(restoreBackupSet(), BackupSetInvalidError, "no backup set found to restore");
+ TEST_RESULT_LOG(
+ "P00 WARN: repo1: [BackupSetInvalidError] no backup sets to restore\n"
+ "P00 WARN: repo2: [BackupSetInvalidError] no backup sets to restore");
}
// *****************************************************************************************************************************
@@ -1665,6 +1771,7 @@ testRun(void)
{
const String *pgPath = strNewFmt("%s/pg", testPath());
const String *repoPath = strNewFmt("%s/repo", testPath());
+ const String *repoPathEncrpyt = strNewFmt("%s/repo-encrypt", testPath());
// Set log level to detail
harnessLogLevelSet(logLevelDetail);
@@ -1691,20 +1798,17 @@ testRun(void)
TEST_ERROR(cmdRestore(), HostInvalidError, "restore command must be run on the PostgreSQL host");
- // Write backup info
// -------------------------------------------------------------------------------------------------------------------------
- storagePutP(
- storageNewWriteP(storageRepoWrite(), INFO_BACKUP_PATH_FILE_STR),
- harnessInfoChecksumZ(TEST_RESTORE_BACKUP_INFO "\n" TEST_RESTORE_BACKUP_INFO_DB));
-
- // -------------------------------------------------------------------------------------------------------------------------
- TEST_TITLE("full restore without delta");
+ TEST_TITLE("full restore without delta, multi-repo");
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
- strLstAdd(argList, strNewFmt("--repo1-path=%s", strZ(repoPath)));
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 1, repoPath);
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPathEncrpyt);
strLstAdd(argList, strNewFmt("--pg1-path=%s", strZ(pgPath)));
strLstAddZ(argList, "--set=20161219-212741F");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoCipherType, 2, CIPHER_TYPE_AES_256_CBC);
+ hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 2, TEST_CIPHER_PASS);
harnessCfgLoad(cfgCmdRestore, argList);
#define TEST_LABEL "20161219-212741F"
@@ -1743,7 +1847,13 @@ testRun(void)
.mode = 0600, .group = groupName(), .user = userName(),
.checksumSha1 = "797e375b924134687cbf9eacd37a4355f3d825e4"});
storagePutP(
- storageNewWriteP(storageRepoWrite(), STRDEF(TEST_REPO_PATH PG_FILE_PGVERSION)), BUFSTRDEF(PG_VERSION_84_STR "\n"));
+ storageNewWriteP(
+ storageRepoIdxWrite(0), STRDEF(TEST_REPO_PATH PG_FILE_PGVERSION)), BUFSTRDEF(PG_VERSION_84_STR "\n"));
+
+ // Store the file also to the encrypted repo
+ HRN_STORAGE_PUT(
+ storageRepoIdxWrite(1), TEST_REPO_PATH PG_FILE_PGVERSION, BUFSTRDEF(PG_VERSION_84_STR "\n"),
+ .cipherType = cipherTypeAes256Cbc, .cipherPass = TEST_CIPHER_PASS_ARCHIVE);
// pg_tblspc/1
manifestTargetAdd(
@@ -1782,13 +1892,43 @@ testRun(void)
manifestSave(
manifest,
storageWriteIo(
- storageNewWriteP(storageRepoWrite(),
+ storageNewWriteP(storageRepoIdxWrite(0),
strNew(STORAGE_REPO_BACKUP "/" TEST_LABEL "/" BACKUP_MANIFEST_FILE))));
+ // Read the manifest, set a cipher passphrase and store it to the encrypted repo
+ Manifest *manifestEncrypted = manifestLoadFile(
+ storageRepoIdxWrite(0), strNew(STORAGE_REPO_BACKUP "/" TEST_LABEL "/" BACKUP_MANIFEST_FILE), cipherTypeNone, NULL);
+ manifestCipherSubPassSet(manifestEncrypted, STRDEF(TEST_CIPHER_PASS_ARCHIVE));
+
+ // Open file for write
+ IoWrite *write = storageWriteIo(
+ storageNewWriteP(
+ storageRepoIdxWrite(1),
+ strNew(STORAGE_REPO_BACKUP "/" TEST_LABEL "/" BACKUP_MANIFEST_FILE)));
+
+ // Add encryption filter and save the encrypted manifest
+ #define TEST_CIPHER_PASS_MANIFEST "backpass"
+ cipherBlockFilterGroupAdd(
+ ioWriteFilterGroup(write), cipherType(cfgOptionIdxStr(cfgOptRepoCipherType, 1)), cipherModeEncrypt,
+ STRDEF(TEST_CIPHER_PASS_MANIFEST));
+ manifestSave(manifestEncrypted, write);
+
+ // Write backup.info to the encrypted repo
+ HRN_INFO_PUT(
+ storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO "\n[cipher]\ncipher-pass=\""
+ TEST_CIPHER_PASS_MANIFEST "\"\n\n" TEST_RESTORE_BACKUP_INFO_DB, .cipherType = cipherTypeAes256Cbc);
+
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_LOG(
- "P00 INFO: restore backup set 20161219-212741F\n"
+ strZ(strNewFmt(
+ "P00 WARN: repo1: [FileMissingError] unable to load info file"
+ " '%s/repo/backup/test1/backup.info' or '%s/repo/backup/test1/backup.info.copy':\n"
+ " FileMissingError: unable to open missing file '%s/repo/backup/test1/backup.info' for read\n"
+ " FileMissingError: unable to open missing file '%s/repo/backup/test1/backup.info.copy' for read\n"
+ " HINT: backup.info cannot be opened and is required to perform a backup.\n"
+ " HINT: has a stanza-create been performed?\n"
+ "P00 INFO: repo2: restore backup set 20161219-212741F\n"
"P00 DETAIL: check '{[path]}/pg' exists\n"
"P00 DETAIL: check '{[path]}/ts/1' exists\n"
"P00 DETAIL: update mode for '{[path]}/pg' to 0700\n"
@@ -1796,14 +1936,14 @@ testRun(void)
"P00 DETAIL: create path '{[path]}/pg/pg_tblspc'\n"
"P00 DETAIL: create symlink '{[path]}/pg/pg_tblspc/1' to '{[path]}/ts/1'\n"
"P00 DETAIL: create path '{[path]}/pg/pg_tblspc/1/16384'\n"
- "P01 INFO: restore file {[path]}/pg/PG_VERSION (4B, 100%) checksum 797e375b924134687cbf9eacd37a4355f3d825e4\n"
+ "P01 INFO: restore file {[path]}/pg/PG_VERSION (4B, 100%%) checksum 797e375b924134687cbf9eacd37a4355f3d825e4\n"
"P00 INFO: write {[path]}/pg/recovery.conf\n"
"P00 DETAIL: sync path '{[path]}/pg'\n"
"P00 DETAIL: sync path '{[path]}/pg/pg_tblspc'\n"
"P00 DETAIL: sync path '{[path]}/pg/pg_tblspc/1'\n"
"P00 DETAIL: sync path '{[path]}/pg/pg_tblspc/1/16384'\n"
"P00 WARN: backup does not contain 'global/pg_control' -- cluster will not start\n"
- "P00 DETAIL: sync path '{[path]}/pg/global'");
+ "P00 DETAIL: sync path '{[path]}/pg/global'", testPath(), testPath(), testPath(), testPath())));
// Remove recovery.conf before file comparison since it will have a new timestamp. Make sure it existed, though.
storageRemoveP(storagePgWrite(), PG_FILE_RECOVERYCONF_STR, .errorOnMissing = true);
@@ -1827,13 +1967,19 @@ testRun(void)
argList = strLstNew();
strLstAddZ(argList, "--stanza=test1");
strLstAdd(argList, strNewFmt("--repo1-path=%s", strZ(repoPath)));
+ hrnCfgArgKeyRaw(argList, cfgOptRepoPath, 2, repoPathEncrpyt);
strLstAdd(argList, strNewFmt("--pg1-path=%s", strZ(pgPath)));
strLstAddZ(argList, "--type=preserve");
strLstAddZ(argList, "--set=20161219-212741F");
strLstAddZ(argList, "--" CFGOPT_DELTA);
strLstAddZ(argList, "--force");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoCipherType, 2, CIPHER_TYPE_AES_256_CBC);
+ hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 2, TEST_CIPHER_PASS);
harnessCfgLoad(cfgCmdRestore, argList);
+ // Store backup.info to repo1 - repo1 will be selected because of the priority order
+ HRN_INFO_PUT(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, TEST_RESTORE_BACKUP_INFO "\n" TEST_RESTORE_BACKUP_INFO_DB);
+
// Make sure existing backup.manifest file is ignored
storagePutP(storageNewWriteP(storagePgWrite(), BACKUP_MANIFEST_FILE_STR), NULL);
@@ -1898,7 +2044,7 @@ testRun(void)
cmdRestore();
TEST_RESULT_LOG(
- "P00 INFO: restore backup set 20161219-212741F\n"
+ "P00 INFO: repo1: restore backup set 20161219-212741F\n"
"P00 DETAIL: check '{[path]}/pg' exists\n"
"P00 DETAIL: check '{[path]}/ts/1' exists\n"
"P00 INFO: remove invalid files/links/paths from '{[path]}/pg'\n"
@@ -1940,6 +2086,10 @@ testRun(void)
strNewBuf(storageGetP(storageNewReadP(storagePg(), STRDEF(PG_FILE_PGVERSION)))), "BOG\n",
"check PG_VERSION was not restored");
+ // Cleanup
+ hrnCfgEnvKeyRemoveRaw(cfgOptRepoCipherPass, 2);
+ storagePathRemoveP(storageRepoIdxWrite(1), NULL, .recurse = true);
+
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("full restore with force");
@@ -1955,7 +2105,7 @@ testRun(void)
cmdRestore();
TEST_RESULT_LOG(
- "P00 INFO: restore backup set 20161219-212741F\n"
+ "P00 INFO: repo1: restore backup set 20161219-212741F\n"
"P00 DETAIL: check '{[path]}/pg' exists\n"
"P00 DETAIL: check '{[path]}/ts/1' exists\n"
"P00 INFO: remove invalid files/links/paths from '{[path]}/pg'\n"
@@ -2268,7 +2418,7 @@ testRun(void)
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_LOG(
- "P00 INFO: restore backup set 20161219-212741F_20161219-212918I\n"
+ "P00 INFO: repo1: restore backup set 20161219-212741F_20161219-212918I\n"
"P00 INFO: map link 'pg_hba.conf' to '../config/pg_hba.conf'\n"
"P00 INFO: map link 'pg_wal' to '../wal'\n"
"P00 INFO: map link 'postgresql.conf' to '../config/postgresql.conf'\n"
@@ -2377,7 +2527,7 @@ testRun(void)
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_LOG(
- "P00 INFO: restore backup set 20161219-212741F_20161219-212918I\n"
+ "P00 INFO: repo2: restore backup set 20161219-212741F_20161219-212918I\n"
"P00 INFO: map link 'pg_hba.conf' to '../config/pg_hba.conf'\n"
"P00 INFO: map link 'pg_wal' to '../wal'\n"
"P00 INFO: map link 'postgresql.conf' to '../config/postgresql.conf'\n"
diff --git a/test/src/module/config/loadTest.c b/test/src/module/config/loadTest.c
index 32805ccd0..fe30a08f0 100644
--- a/test/src/module/config/loadTest.c
+++ b/test/src/module/config/loadTest.c
@@ -76,13 +76,26 @@ testRun(void)
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptRepo, "3");
- hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionDiff, 4, "4");
- hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionDiff, 3, "3");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 4, "/repo4");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 3, "/repo4");
+ hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoHost, 2, "host2");
TEST_ERROR(
- harnessCfgLoad(cfgCmdExpire, argList), OptionInvalidValueError,
- "local repo3 and repo4 paths are both '/var/lib/pgbackrest' but must be different");
+ harnessCfgLoad(cfgCmdRestore, argList), OptionInvalidValueError,
+ "local repo3 and repo4 paths are both '/repo4' but must be different");
+
+ // -------------------------------------------------------------------------------------------------------------------------
+ TEST_TITLE("repo can be specified for backup");
+
+ argList = strLstNew();
+ hrnCfgArgRawZ(argList, cfgOptStanza, "test");
+ hrnCfgArgRawZ(argList, cfgOptRepo, "1");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo1");
+ hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 1, "1");
+ hrnCfgArgKeyRawZ(argList, cfgOptPgPath, 1, "/pg1");
+
+ harnessCfgLoad(cfgCmdBackup, argList);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("local default repo paths for cifs repo type must be different");