1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-10-30 23:37:45 +02:00

Add restore progress to info command output.

The prior implementation of the restore command did not provide any progress information. Implement it similarly to the backup command and also update the info command to display restore progress alongside backup progress.

Restore is a read operation and should not block other commands. The only exception is that multiple restores with the same lock and repository path are not allowed, as each restore must write progress to a separate file. Therefore, no lock is needed for restores with a remote role and the restore command should not be terminated when the stop command is executed.
This commit is contained in:
Denis Garsh
2025-07-16 19:49:34 +03:00
committed by GitHub
parent d55836e7e8
commit 8cdd9ce1c4
17 changed files with 606 additions and 164 deletions

View File

@@ -16,6 +16,19 @@
</release-bug-list>
<release-feature-list>
<release-item>
<github-issue id="2546"/>
<github-pull-request id="2652"/>
<release-item-contributor-list>
<release-item-contributor id="denis.garsh"/>
<release-item-contributor id="maxim.michkov"/>
<release-item-reviewer id="david.steele"/>
</release-item-contributor-list>
<p>Add restore progress to <cmd>info</cmd> command output.</p>
</release-item>
<release-item>
<github-pull-request id="2550"/>

View File

@@ -130,6 +130,8 @@ command:
command-role:
local: {}
remote: {}
lock-required: true
lock-type: restore
server: {}

View File

@@ -2554,7 +2554,7 @@
<p>Each stanza has a separate section and it is possible to limit output to a single stanza with the <br-option>--stanza</br-option> option. The stanza '<id>status</id>' gives a brief indication of the stanza's health. If this is '<id>ok</id>' then <backrest/> is functioning normally. If there are multiple repositories, then a status of '<id>mixed</id>' indicates that the stanza is not in a healthy state on one or more of the repositories; in this case the state of the stanza will be detailed per repository. For cases in which an error on a repository occurred that is not one of the known error codes, then an error code of '<id>other</id>' will be used and the full error details will be provided. The '<id>wal archive min/max</id>' shows the minimum and maximum WAL currently stored in the archive and, in the case of multiple repositories, will be reported across all repositories unless the <br-option>{[dash]}-repo</br-option> option is set. Note that there may be gaps due to archive retention policies or other reasons.</p>
<p>The '<id>backup/expire running</id>' message will appear beside the '<id>status</id>' information if one of those commands is currently running on the host.</p>
<p>The '<id>backup/expire running</id>' and/or '<id>restore running</id>' messages will appear beside the '<id>status</id>' information if any of those commands are currently running on the host.</p>
<p>The backups are displayed oldest to newest. The oldest backup will <i>always</i> be a full backup (indicated by an <id>F</id> at the end of the label) but the newest backup can be full, differential (ends with <id>D</id>), or incremental (ends with <id>I</id>).</p>

View File

@@ -77,10 +77,11 @@ VARIANT_STRDEF_STATIC(STANZA_KEY_DB_VAR, "db");
VARIANT_STRDEF_STATIC(STATUS_KEY_CODE_VAR, "code");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_VAR, "lock");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_BACKUP_VAR, "backup");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_BACKUP_HELD_VAR, "held");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR, "pct-cplt");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_BACKUP_SIZE_COMPLETE_VAR, "size-cplt");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_BACKUP_SIZE_VAR, "size");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_HELD_VAR, "held");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_PERCENT_COMPLETE_VAR, "pct-cplt");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_RESTORE_VAR, "restore");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_SIZE_COMPLETE_VAR, "size-cplt");
VARIANT_STRDEF_STATIC(STATUS_KEY_LOCK_SIZE_VAR, "size");
VARIANT_STRDEF_STATIC(STATUS_KEY_MESSAGE_VAR, "message");
#define INFO_STANZA_STATUS_OK "ok"
@@ -108,6 +109,7 @@ STRING_STATIC(INFO_STANZA_STATUS_MESSAGE_OTHER_STR, INFO_STANZA_
STRING_STATIC(INFO_STANZA_INVALID_STR, "[invalid]");
#define INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP "backup/expire running"
#define INFO_STANZA_STATUS_MESSAGE_LOCK_RESTORE "restore running"
/***********************************************************************************************************************************
Data types and structures
@@ -131,15 +133,27 @@ typedef struct InfoRepoData
#define FUNCTION_LOG_INFO_REPO_DATA_FORMAT(value, buffer, bufferSize) \
objNameToLog(value, "InfoRepoData", buffer, bufferSize)
// Information for a lockfile of a stanza
typedef struct InfoStanzaLock
{
bool held; // Is lock held on the system where info command is run?
uint64_t sizeComplete; // Completed size of the backup/restore in bytes
uint64_t size; // Total size of the backup/restore in bytes
} InfoStanzaLock;
#define FUNCTION_LOG_INFO_STANZA_LOCK_TYPE \
InfoStanzaLock *
#define FUNCTION_LOG_INFO_STANZA_LOCK_FORMAT(value, buffer, bufferSize) \
objNameToLog(value, "InfoStanzaLock", buffer, bufferSize)
// Stanza with repository list of information for each repository
typedef struct InfoStanzaRepo
{
const String *name; // Name of the stanza
uint64_t currentPgSystemId; // Current postgres system id for the stanza
unsigned int currentPgVersion; // Current postgres version for the stanza
bool backupLockHeld; // Is backup lock held on the system where info command is run?
uint64_t sizeComplete; // Completed size of the backup in bytes
uint64_t size; // Total size of the backup in bytes
InfoStanzaLock backupLock; // Info for backup lock
InfoStanzaLock restoreLock; // Info for restore lock
InfoRepoData *repoList; // List of configured repositories
} InfoStanzaRepo;
@@ -182,6 +196,25 @@ infoStanzaErrorAdd(InfoRepoData *const repoList, const ErrorType *const type, co
repoList->manifest = NULL;
}
/***********************************************************************************************************************************
Add lock information to the target key-value
***********************************************************************************************************************************/
static void
stanzaStatusLockAdd(KeyValue *targetKv, const Variant *const lockKey, const InfoStanzaLock *lock)
{
KeyValue *const lockKv = kvPutKv(targetKv, lockKey);
kvPut(lockKv, STATUS_KEY_LOCK_HELD_VAR, VARBOOL(lock->held));
if (lock->size != 0)
{
kvPut(lockKv, STATUS_KEY_LOCK_SIZE_COMPLETE_VAR, VARUINT64(lock->sizeComplete));
kvPut(lockKv, STATUS_KEY_LOCK_SIZE_VAR, VARUINT64(lock->size));
if (cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
kvPut(lockKv, STATUS_KEY_LOCK_PERCENT_COMPLETE_VAR, VARUINT(cvtPctToUInt(lock->sizeComplete, lock->size)));
}
}
/***********************************************************************************************************************************
Set the overall error status code and message for the stanza to the code and message passed
***********************************************************************************************************************************/
@@ -239,21 +272,9 @@ stanzaStatus(const int code, const InfoStanzaRepo *const stanzaData, const Varia
// Construct a specific lock part
KeyValue *const lockKv = kvPutKv(statusKv, STATUS_KEY_LOCK_VAR);
KeyValue *const backupLockKv = kvPutKv(lockKv, STATUS_KEY_LOCK_BACKUP_VAR);
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_HELD_VAR, VARBOOL(stanzaData->backupLockHeld));
if (stanzaData->size != 0)
{
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_COMPLETE_VAR, VARUINT64(stanzaData->sizeComplete));
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_VAR, VARUINT64(stanzaData->size));
if (cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
{
kvPut(
backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR,
VARUINT(cvtPctToUInt(stanzaData->sizeComplete, stanzaData->size)));
}
}
stanzaStatusLockAdd(lockKv, STATUS_KEY_LOCK_BACKUP_VAR, &stanzaData->backupLock);
stanzaStatusLockAdd(lockKv, STATUS_KEY_LOCK_RESTORE_VAR, &stanzaData->restoreLock);
FUNCTION_TEST_RETURN_VOID();
}
@@ -1302,6 +1323,44 @@ formatTextDb(
FUNCTION_TEST_RETURN_VOID();
}
/***********************************************************************************************************************************
Get the lock info of the specified lock type for the stanza
***********************************************************************************************************************************/
static void
infoUpdateStanzaLock(
InfoStanzaLock *const stanzaLock, const String *const stanzaName, const unsigned int repoIdx, const LockType lockType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(INFO_STANZA_LOCK, stanzaLock);
FUNCTION_TEST_PARAM(STRING, stanzaName);
FUNCTION_TEST_PARAM(UINT, repoIdx);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_END();
FUNCTION_AUDIT_HELPER();
ASSERT(stanzaLock != NULL);
ASSERT(stanzaName != NULL);
// If there is a valid lock for this stanza then backup/expire/restore must be running
const LockReadResult lockResult = cmdLockRead(lockType, stanzaName, repoIdx);
if (lockResult.status == lockReadStatusValid)
{
stanzaLock->held = true;
if (lockResult.data.size != NULL)
{
ASSERT(lockResult.data.size != NULL);
stanzaLock->sizeComplete += varUInt64(lockResult.data.sizeComplete);
stanzaLock->size += varUInt64(lockResult.data.size);
}
}
FUNCTION_TEST_RETURN_VOID();
}
/***********************************************************************************************************************************
Get the backup and archive info files on the specified repo for the stanza
***********************************************************************************************************************************/
@@ -1378,25 +1437,12 @@ infoUpdateStanza(
}
}
// Read the lock file if backup.info is present. Exception: when only progress is requested, backup.info is skipped for
// performance, so the lock file is read unconditionally -- though it may be outdated in this case.
// Read the lock files if backup.info is present. Exception: when only progress is requested, backup.info is skipped for
// performance, so the lock files are read unconditionally -- though they may be outdated in this case.
if (stanzaRepo->repoList[repoIdx].backupInfo != NULL || !outputFull)
{
// If there is a valid backup lock for this stanza then backup/expire must be running
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name, repoIdx);
if (lockResult.status == lockReadStatusValid)
{
stanzaRepo->backupLockHeld = true;
if (lockResult.data.size != NULL)
{
ASSERT(lockResult.data.size != NULL);
stanzaRepo->sizeComplete += varUInt64(lockResult.data.sizeComplete);
stanzaRepo->size += varUInt64(lockResult.data.size);
}
}
infoUpdateStanzaLock(&stanzaRepo->backupLock, stanzaRepo->name, repoIdx, lockTypeBackup);
infoUpdateStanzaLock(&stanzaRepo->restoreLock, stanzaRepo->name, repoIdx, lockTypeRestore);
}
stanzaRepo->repoList[repoIdx].stanzaStatus = stanzaStatus;
@@ -1655,91 +1701,109 @@ infoRender(void)
const KeyValue *const stanzaStatus = varKv(kvGet(stanzaInfo, STANZA_KEY_STATUS_VAR));
const int statusCode = varInt(kvGet(stanzaStatus, STATUS_KEY_CODE_VAR));
// Get the lock info
// Get the backup lock info
const KeyValue *const lockKv = varKv(kvGet(stanzaStatus, STATUS_KEY_LOCK_VAR));
const KeyValue *const backupLockKv = varKv(kvGet(lockKv, STATUS_KEY_LOCK_BACKUP_VAR));
const bool backupLockHeld = varBool(kvGet(backupLockKv, STATUS_KEY_LOCK_BACKUP_HELD_VAR));
const Variant *const percentComplete = kvGet(backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR);
const String *const percentCompleteStr =
percentComplete != NULL ?
strNewFmt(" - %u.%02u%% complete", varUInt(percentComplete) / 100, varUInt(percentComplete) % 100) :
EMPTY_STR;
const bool backupLockHeld = varBool(kvGet(backupLockKv, STATUS_KEY_LOCK_HELD_VAR));
const Variant *const backupPercentComplete = kvGet(backupLockKv, STATUS_KEY_LOCK_PERCENT_COMPLETE_VAR);
const String *const backupPercentCompleteStr =
backupPercentComplete != NULL ?
strNewFmt(" - %s complete", strZ(strNewPct(varUInt(backupPercentComplete), 10000))) : EMPTY_STR;
if (statusCode != INFO_STANZA_STATUS_CODE_OK)
// Get the restore lock info
const KeyValue *const restoreLockKv = varKv(kvGet(lockKv, STATUS_KEY_LOCK_RESTORE_VAR));
const bool restoreLockHeld = varBool(kvGet(restoreLockKv, STATUS_KEY_LOCK_HELD_VAR));
const Variant *const restorePercentComplete = kvGet(restoreLockKv, STATUS_KEY_LOCK_PERCENT_COMPLETE_VAR);
const String *const restorePercentCompleteStr =
restorePercentComplete != NULL ?
strNewFmt(" - %s complete", strZ(strNewPct(varUInt(restorePercentComplete), 10000))) : EMPTY_STR;
// Build stanza status
const bool errorStatus =
statusCode != INFO_STANZA_STATUS_CODE_OK &&
(outputFull == false || statusCode != INFO_STANZA_STATUS_CODE_MIXED);
const bool progressStatus = backupLockHeld == true || restoreLockHeld == true;
const String *const statusLabelStr =
statusCode == INFO_STANZA_STATUS_CODE_OK ?
strNewZ(INFO_STANZA_STATUS_OK) :
errorStatus == true ? strNewZ(INFO_STANZA_STATUS_ERROR) : strNewZ(INFO_STANZA_MIXED);
const String *const statusErrorStr =
errorStatus ? varStr(kvGet(stanzaStatus, STATUS_KEY_MESSAGE_VAR)) : EMPTY_STR;
const String *const progressStr =
backupLockHeld == true && restoreLockHeld == true ?
strNewFmt(
INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP "%s, " INFO_STANZA_STATUS_MESSAGE_LOCK_RESTORE "%s",
strZ(backupPercentCompleteStr),
strZ(restorePercentCompleteStr)) :
backupLockHeld == true ?
strNewFmt(INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP "%s", strZ(backupPercentCompleteStr)) :
restoreLockHeld == true ?
strNewFmt(INFO_STANZA_STATUS_MESSAGE_LOCK_RESTORE "%s", strZ(restorePercentCompleteStr)) :
EMPTY_STR;
if (progressStatus)
{
// Update the overall stanza status and change displayed status if backup lock is found
if (outputFull &&
(statusCode == INFO_STANZA_STATUS_CODE_MIXED || statusCode == INFO_STANZA_STATUS_CODE_PG_MISMATCH ||
statusCode == INFO_STANZA_STATUS_CODE_OTHER))
// Status: error (message, progress)
if (errorStatus)
{
// Stanza status
strCatFmt(
resultStr, "%s%s\n",
statusCode == INFO_STANZA_STATUS_CODE_MIXED ?
INFO_STANZA_MIXED :
zNewFmt(
INFO_STANZA_STATUS_ERROR " (%s)",
strZ(varStr(kvGet(stanzaStatus, STATUS_KEY_MESSAGE_VAR)))),
backupLockHeld == true ?
zNewFmt(" (" INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP "%s)", strZ(percentCompleteStr)) : "");
// Output the status per repo
const VariantList *const repoSection = kvGetList(stanzaInfo, STANZA_KEY_REPO_VAR);
const bool multiRepo = varLstSize(repoSection) > 1;
const char *const formatSpacer = multiRepo ? " " : " ";
for (unsigned int repoIdx = 0; repoIdx < varLstSize(repoSection); repoIdx++)
{
const KeyValue *const repoInfo = varKv(varLstGet(repoSection, repoIdx));
const KeyValue *const repoStatus = varKv(kvGet(repoInfo, STANZA_KEY_STATUS_VAR));
// If more than one repo configured, then add the repo status per repo
if (multiRepo)
strCatFmt(resultStr, " repo%u: ", varUInt(kvGet(repoInfo, REPO_KEY_KEY_VAR)));
if (varInt(kvGet(repoStatus, STATUS_KEY_CODE_VAR)) == INFO_STANZA_STATUS_CODE_OK)
strCatZ(resultStr, INFO_STANZA_STATUS_OK "\n");
else
{
if (varInt(kvGet(repoStatus, STATUS_KEY_CODE_VAR)) == INFO_STANZA_STATUS_CODE_OTHER)
{
const StringList *const repoError = strLstNewSplit(
varStr(kvGet(repoStatus, STATUS_KEY_MESSAGE_VAR)), STRDEF("\n"));
strCatFmt(
resultStr, "%s%s%s\n",
multiRepo ? INFO_STANZA_STATUS_ERROR " (" INFO_STANZA_STATUS_MESSAGE_OTHER ")\n" : "",
formatSpacer, strZ(strLstJoin(repoError, zNewFmt("\n%s", formatSpacer))));
}
else
{
strCatFmt(
resultStr, INFO_STANZA_STATUS_ERROR " (%s)\n",
strZ(varStr(kvGet(repoStatus, STATUS_KEY_MESSAGE_VAR))));
}
}
}
strCatFmt(resultStr, "%s (%s, %s)\n", strZ(statusLabelStr), strZ(statusErrorStr), strZ(progressStr));
}
// Status: ok/mixed (progress)
else
{
strCatFmt(
resultStr, "%s (%s%s\n", INFO_STANZA_STATUS_ERROR,
strZ(varStr(kvGet(stanzaStatus, STATUS_KEY_MESSAGE_VAR))),
backupLockHeld == true ?
zNewFmt(", " INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP "%s)", strZ(percentCompleteStr)) : ")");
}
strCatFmt(resultStr, "%s (%s)\n", strZ(statusLabelStr), strZ(progressStr));
}
else
{
// Change displayed status if backup lock is found
if (backupLockHeld)
// Status: error (message)
if (errorStatus)
{
strCatFmt(
resultStr, "%s (%s%s)\n", INFO_STANZA_STATUS_OK, INFO_STANZA_STATUS_MESSAGE_LOCK_BACKUP,
strZ(percentCompleteStr));
strCatFmt(resultStr, "%s (%s)\n", strZ(statusLabelStr), strZ(statusErrorStr));
}
// Status: ok/mixed
else
strCatFmt(resultStr, "%s\n", INFO_STANZA_STATUS_OK);
strCatFmt(resultStr, "%s\n", strZ(statusLabelStr));
}
// Output the status per repo
if (outputFull &&
(statusCode == INFO_STANZA_STATUS_CODE_MIXED || statusCode == INFO_STANZA_STATUS_CODE_PG_MISMATCH ||
statusCode == INFO_STANZA_STATUS_CODE_OTHER))
{
const VariantList *const repoSection = kvGetList(stanzaInfo, STANZA_KEY_REPO_VAR);
const bool multiRepo = varLstSize(repoSection) > 1;
const char *const formatSpacer = multiRepo ? " " : " ";
for (unsigned int repoIdx = 0; repoIdx < varLstSize(repoSection); repoIdx++)
{
const KeyValue *const repoInfo = varKv(varLstGet(repoSection, repoIdx));
const KeyValue *const repoStatus = varKv(kvGet(repoInfo, STANZA_KEY_STATUS_VAR));
// If more than one repo configured, then add the repo status per repo
if (multiRepo)
strCatFmt(resultStr, " repo%u: ", varUInt(kvGet(repoInfo, REPO_KEY_KEY_VAR)));
if (varInt(kvGet(repoStatus, STATUS_KEY_CODE_VAR)) == INFO_STANZA_STATUS_CODE_OK)
strCatZ(resultStr, INFO_STANZA_STATUS_OK "\n");
else
{
if (varInt(kvGet(repoStatus, STATUS_KEY_CODE_VAR)) == INFO_STANZA_STATUS_CODE_OTHER)
{
const StringList *const repoError = strLstNewSplit(
varStr(kvGet(repoStatus, STATUS_KEY_MESSAGE_VAR)), STRDEF("\n"));
strCatFmt(
resultStr, "%s%s%s\n",
multiRepo ? INFO_STANZA_STATUS_ERROR " (" INFO_STANZA_STATUS_MESSAGE_OTHER ")\n" : "",
formatSpacer, strZ(strLstJoin(repoError, zNewFmt("\n%s", formatSpacer))));
}
else
{
strCatFmt(
resultStr, INFO_STANZA_STATUS_ERROR " (%s)\n",
strZ(varStr(kvGet(repoStatus, STATUS_KEY_MESSAGE_VAR))));
}
}
}
}
// Add cipher type if the stanza is found on at least one repo

View File

@@ -23,6 +23,7 @@ static const char *const lockTypeName[] =
{
"archive", // lockTypeArchive
"backup", // lockTypeBackup
"restore", // lockTypeRestore
};
/***********************************************************************************************************************************
@@ -140,7 +141,7 @@ cmdLockRead(const LockType lockType, const String *const stanza, const unsigned
FUNCTION_LOG_PARAM(UINT, repoIdx);
FUNCTION_LOG_END();
ASSERT(lockType == lockTypeBackup);
ASSERT(lockType == lockTypeBackup || lockType == lockTypeRestore);
ASSERT(stanza != NULL);
FUNCTION_AUDIT_STRUCT();

View File

@@ -8,6 +8,7 @@ Restore Command
#include <time.h>
#include <unistd.h>
#include "command/lock.h"
#include "command/restore/file.h"
#include "command/restore/protocol.h"
#include "command/restore/restore.h"
@@ -2049,7 +2050,7 @@ restoreFilePgPath(const Manifest *const manifest, const String *const manifestNa
static uint64_t
restoreJobResult(
const Manifest *const manifest, ProtocolParallelJob *const job, RegExp *const zeroExp, const uint64_t sizeTotal,
uint64_t sizeRestored)
uint64_t sizeRestored, unsigned int *const currentPercentComplete)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(MANIFEST, manifest);
@@ -2057,6 +2058,7 @@ restoreJobResult(
FUNCTION_LOG_PARAM(REGEXP, zeroExp);
FUNCTION_LOG_PARAM(UINT64, sizeTotal);
FUNCTION_LOG_PARAM(UINT64, sizeRestored);
FUNCTION_LOG_PARAM_P(UINT, currentPercentComplete);
FUNCTION_LOG_END();
ASSERT(manifest != NULL);
@@ -2067,6 +2069,7 @@ restoreJobResult(
MEM_CONTEXT_TEMP_BEGIN()
{
PackRead *const jobResult = protocolParallelJobResult(job);
unsigned int percentComplete = 0;
while (!pckReadNullP(jobResult))
{
@@ -2138,6 +2141,10 @@ restoreJobResult(
// Add size and percent complete
sizeRestored += file.size;
// Store percentComplete as an integer (used to update progress in the lock file)
percentComplete = cvtPctToUInt(sizeRestored, sizeTotal);
strCatFmt(log, "%s, %s)", strZ(strSizeFormat(file.size)), strZ(strNewPct(sizeRestored, sizeTotal)));
// If not zero-length add the checksum
@@ -2146,6 +2153,15 @@ restoreJobResult(
LOG_DETAIL_PID(protocolParallelJobProcessId(job), strZ(log));
}
// Update currentPercentComplete and lock file when the change is significant enough
if (percentComplete - *currentPercentComplete > 10)
{
*currentPercentComplete = percentComplete;
cmdLockWriteP(
.percentComplete = VARUINT(*currentPercentComplete), .sizeComplete = VARUINT64(sizeRestored),
.size = VARUINT64(sizeTotal));
}
}
MEM_CONTEXT_TEMP_END();
@@ -2449,6 +2465,12 @@ cmdRestore(void)
// Process jobs
uint64_t sizeRestored = 0;
// Initialize percent complete and bytes completed/total
unsigned int currentPercentComplete = 0;
cmdLockWriteP(
.percentComplete = VARUINT(currentPercentComplete), .sizeComplete = VARUINT64(sizeRestored),
.size = VARUINT64(sizeTotal));
MEM_CONTEXT_TEMP_RESET_BEGIN()
{
do
@@ -2458,7 +2480,8 @@ cmdRestore(void)
for (unsigned int jobIdx = 0; jobIdx < completed; jobIdx++)
{
sizeRestored = restoreJobResult(
jobData.manifest, protocolParallelResult(parallelExec), jobData.zeroExp, sizeTotal, sizeRestored);
jobData.manifest, protocolParallelResult(parallelExec), jobData.zeroExp, sizeTotal, sizeRestored,
&currentPercentComplete);
}
// Reset the memory context occasionally so we don't use too much memory or slow down processing

View File

@@ -48,6 +48,7 @@ typedef enum
{
lockTypeArchive,
lockTypeBackup,
lockTypeRestore,
lockTypeAll,
lockTypeNone,
} LockType;

View File

@@ -895,7 +895,8 @@ static const ParseRuleCommand parseRuleCommand[CFG_COMMAND_TOTAL] =
PARSE_RULE_COMMAND // cmd/restore
( // cmd/restore
PARSE_RULE_COMMAND_NAME("restore"), // cmd/restore
PARSE_RULE_COMMAND_LOCK_TYPE(None), // cmd/restore
PARSE_RULE_COMMAND_LOCK_REQUIRED(true), // cmd/restore
PARSE_RULE_COMMAND_LOCK_TYPE(Restore), // cmd/restore
PARSE_RULE_COMMAND_LOG_FILE(true), // cmd/restore
PARSE_RULE_COMMAND_LOG_LEVEL_DEFAULT(Info), // cmd/restore
// cmd/restore

View File

@@ -68,7 +68,7 @@ typedef struct ParseRuleCommand
unsigned int commandRoleValid : CFG_COMMAND_ROLE_TOTAL; // Valid for the command role?
bool lockRequired : 1; // Is an immediate lock required?
bool lockRemoteRequired : 1; // Is a lock required on the remote?
unsigned int lockType : 2; // Lock type required
unsigned int lockType : 3; // Lock type required
bool logFile : 1; // Will the command log to a file?
unsigned int logLevelDefault : 4; // Default log level
bool parameterAllowed : 1; // Command-line parameters are allowed

View File

@@ -2823,6 +2823,14 @@ test/src/common/harnessProtocol.h:
class: test/harness
type: c/h
test/src/common/harnessRestore.c:
class: test/harness
type: c
test/src/common/harnessRestore.h:
class: test/harness
type: c/h
test/src/common/harnessServer.c:
class: test/harness
type: c

View File

@@ -949,7 +949,9 @@ unit:
# ----------------------------------------------------------------------------------------------------------------------------
- name: restore
total: 15
total: 16
harness: restore
coverage:
- command/restore/blockChecksum

View File

@@ -0,0 +1,33 @@
/***********************************************************************************************************************************
Harness for Restore Testing
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/lock.h"
#include "command/restore/restore.h"
#include "common/harnessDebug.h"
#include "common/harnessRestore.h"
#include "common/harnessTest.intern.h"
#include "config/config.h"
/**********************************************************************************************************************************/
void
hrnCmdRestore(void)
{
FUNCTION_HARNESS_VOID();
lockInit(STR(testPath()), cfgOptionStr(cfgOptExecId));
cmdLockAcquireP();
TRY_BEGIN()
{
cmdRestore();
}
FINALLY()
{
cmdLockReleaseP();
}
TRY_END();
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@@ -0,0 +1,13 @@
/***********************************************************************************************************************************
Harness for Restore Testing
***********************************************************************************************************************************/
#ifndef TEST_COMMON_HARNESS_RESTORE_H
#define TEST_COMMON_HARNESS_RESTORE_H
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Restore the database from the test backup and handle all the required locking. The restore configuration must already be loaded.
void hrnCmdRestore(void);
#endif

View File

@@ -84,7 +84,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":1,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"missing stanza path\""
"}"
"}"
@@ -104,7 +107,8 @@ testRun(void)
"\"status\":{"
"\"code\":1,"
"\"lock\":{"
"\"backup\":{\"held\":false}"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"missing stanza path\""
"}"
@@ -176,7 +180,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":3,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"missing stanza data\""
"}"
"}"
@@ -195,7 +202,8 @@ testRun(void)
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":false}"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
@@ -257,7 +265,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":99,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"other\""
"}"
"}"
@@ -314,6 +325,9 @@ testRun(void)
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1)), true, "create backup/expire lock");
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeRestore, 1)), true, "create restore lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@@ -375,7 +389,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":2,"
"\"lock\":{\"backup\":{\"held\":true}},"
"\"lock\":{"
"\"backup\":{\"held\":true},"
"\"restore\":{\"held\":true}"
"},"
"\"message\":\"no valid backups\""
"}"
"}"
@@ -387,7 +404,7 @@ testRun(void)
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: error (no valid backups, backup/expire running)\n"
" status: error (no valid backups, backup/expire running, restore running)\n"
" cipher: none\n"
"\n"
" db (current)\n"
@@ -518,7 +535,7 @@ testRun(void)
"3={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665679"
",\"db-version\":\"9.6\"}\n");
// Execute while a backup lock is held
// Execute while backup and restore locks are held
HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
@@ -528,6 +545,10 @@ testRun(void)
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName), "write lock data");
String *lockFileNameRestore = cmdLockFileName(STRDEF("stanza1"), lockTypeRestore, 1);
TEST_RESULT_BOOL(lockAcquireP(lockFileNameRestore), true, "create restore lock");
TEST_RESULT_VOID(lockWriteP(lockFileNameRestore), "write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@@ -674,7 +695,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":true}},"
"\"lock\":{"
"\"backup\":{\"held\":true},"
"\"restore\":{\"held\":true}"
"},"
"\"message\":\"ok\""
"}"
"}"
@@ -692,20 +716,22 @@ testRun(void)
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":true}"
"\"backup\":{\"held\":true},"
"\"restore\":{\"held\":true}"
"},"
"\"message\":\"ok\""
"}"
"}"
"]",
// {uncrustify_on}
"json (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected");
"json (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire and"
" restore lock detected");
HRN_CFG_LOAD(cfgCmdInfo, argListText);
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: ok (backup/expire running)\n"
" status: ok (backup/expire running, restore running)\n"
" cipher: none\n"
"\n"
" db (prior)\n"
@@ -731,8 +757,9 @@ testRun(void)
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: ok (backup/expire running)\n",
"text (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire lock detected");
" status: ok (backup/expire running, restore running)\n",
"text (progress only) - single stanza, valid backup, no priors, no archives in latest DB, backup/expire and"
" restore lock detected");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
@@ -981,6 +1008,30 @@ testRun(void)
",\"db-version\":\"9.4\"}\n",
.comment = "put backup info to file - stanza2, repo1");
HRN_INFO_PUT(
storageTest, TEST_PATH "/repo/" STORAGE_PATH_ARCHIVE "/stanza4/" INFO_ARCHIVE_FILE,
"[db]\n"
"db-id=1\n"
"db-system-id=6625633699176220261\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-id\":6625633699176220261,\"db-version\":\"9.4\"}\n",
.comment = "put archive info to file - stanza4, repo1");
HRN_INFO_PUT(
storageTest, TEST_PATH "/repo/" STORAGE_PATH_BACKUP "/stanza4/" INFO_BACKUP_FILE,
"[db]\n"
"db-catalog-version=201409291\n"
"db-control-version=942\n"
"db-id=1\n"
"db-system-id=6625633699176220261\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6625633699176220261"
",\"db-version\":\"9.4\"}\n",
.comment = "put backup info to file - stanza4, repo1");
// Write encrypted info files to encrypted repo2
HRN_INFO_PUT(
storageTest, TEST_PATH "/repo2/" STORAGE_PATH_ARCHIVE "/stanza1/" INFO_ARCHIVE_FILE,
@@ -1184,10 +1235,32 @@ testRun(void)
}
HRN_FORK_CHILD_END();
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(STRDEF("stanza4"), lockTypeRestore, 1);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create restore lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileName, .percentComplete = VARUINT(1234), .sizeComplete = VARUINT64(389820),
.size = VARUINT64(3159000)),
"write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockReleaseP();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
HRN_FORK_PARENT_NOTIFY_GET(1);
HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepoJson);
TEST_RESULT_STR_Z(
@@ -1465,7 +1538,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"},"
@@ -1512,7 +1588,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":4,"
"\"lock\":{\"backup\":{\"held\":true,\"size\":3159000,\"size-cplt\":1435765}},"
"\"lock\":{"
"\"backup\":{\"held\":true,\"size\":3159000,\"size-cplt\":1435765},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"different across repos\""
"}"
"},"
@@ -1590,13 +1669,67 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":4,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"different across repos\""
"}"
"},"
"{"
"\"archive\":["
"{"
"\"database\":{"
"\"id\":1,"
"\"repo-key\":1"
"},"
"\"id\":\"9.4-1\","
"\"max\":null,"
"\"min\":null"
"}"
"],"
"\"backup\":[],"
"\"cipher\":\"mixed\","
"\"db\":["
"{"
"\"id\":1,"
"\"repo-key\":1,"
"\"system-id\":6625633699176220261,"
"\"version\":\"9.4\""
"}"
"],"
"\"name\":\"stanza4\","
"\"repo\":["
"{"
"\"cipher\":\"none\","
"\"key\":1,"
"\"status\":{"
"\"code\":2,"
"\"message\":\"no valid backups\""
"}"
"},"
"{"
"\"cipher\":\"aes-256-cbc\","
"\"key\":2,"
"\"status\":{"
"\"code\":1,"
"\"message\":\"missing stanza path\""
"}"
"}"
"],"
"\"status\":{"
"\"code\":4,"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":true,\"size\":3159000,\"size-cplt\":389820}"
"},"
"\"message\":\"different across repos\""
"}"
"}"
"]",
// {uncrustify_on}
"json - multiple stanzas, some with valid backups, archives in latest DB, backup lock held on one stanza");
"json - multiple stanzas, some with valid backups, archives in latest DB, backup and restore lock held on one"
" stanza");
HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepoJsonProgressOnly);
TEST_RESULT_STR_Z(
@@ -1608,7 +1741,8 @@ testRun(void)
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":false}"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
@@ -1618,7 +1752,8 @@ testRun(void)
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":true,\"size\":3159000,\"size-cplt\":1435765}"
"\"backup\":{\"held\":true,\"size\":3159000,\"size-cplt\":1435765},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
@@ -1628,17 +1763,31 @@ testRun(void)
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":false}"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"},"
"{"
"\"name\":\"stanza4\","
"\"status\":{"
"\"code\":0,"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":true,\"size\":3159000,\"size-cplt\":389820}"
"},"
"\"message\":\"ok\""
"}"
"}"
"]",
// {uncrustify_on}
"json (progress only) - multiple stanzas, some with valid backups, archives in latest DB, backup lock held on one stanza");
"json (progress only) - multiple stanzas, some with valid backups, archives in latest DB, backup and restore"
" lock held on one stanza");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
HRN_FORK_PARENT_NOTIFY_PUT(1);
}
HRN_FORK_PARENT_END();
}
@@ -1666,6 +1815,22 @@ testRun(void)
.percentComplete = VARUINT(7500)),
"write lock data");
String *lockFileStanza1Repo1Restore = cmdLockFileName(STRDEF("stanza1"), lockTypeRestore, 1);
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo1Restore), true, "create restore lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileStanza1Repo1Restore, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(1754830),
.percentComplete = VARUINT(5555)),
"write lock data");
String *lockFileStanza1Repo2Restore = cmdLockFileName(STRDEF("stanza1"), lockTypeRestore, 2);
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo2Restore), true, "create restore lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileStanza1Repo2Restore, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(2369250),
.percentComplete = VARUINT(7500)),
"write lock data");
String *lockFileStanza2Repo1 = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup, 1);
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza2Repo1), true, "create backup/expire lock");
TEST_RESULT_VOID(
@@ -1684,16 +1849,39 @@ testRun(void)
}
HRN_FORK_CHILD_END();
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(STRDEF("stanza4"), lockTypeRestore, 1);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create restore lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileName, .percentComplete = VARUINT(1234), .sizeComplete = VARUINT64(389820),
.size = VARUINT64(3159000)),
"write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
lockReleaseP();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
HRN_FORK_PARENT_NOTIFY_GET(1);
HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepo);
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: ok (backup/expire running - 65.28% complete)\n"
" status: ok (backup/expire running - 65.28% complete, restore running - 65.28% complete)\n"
" cipher: mixed\n"
" repo1: none\n"
" repo2: aes-256-cbc\n"
@@ -1770,29 +1958,48 @@ testRun(void)
" timestamp start/stop: 2020-11-10 10:00:00+00 / 2020-11-10 10:00:02+00\n"
" wal start/stop: 000000010000000000000001 / 000000010000000000000002\n"
" database size: 25.7MB, database backup size: 25.7MB\n"
" repo2: backup set size: 3MB, backup size: 3KB\n",
" repo2: backup set size: 3MB, backup size: 3KB\n"
"\n"
"stanza: stanza4\n"
" status: mixed (restore running - 12.34% complete)\n"
" repo1: error (no valid backups)\n"
" repo2: error (missing stanza path)\n"
" cipher: mixed\n"
" repo1: none\n"
" repo2: aes-256-cbc\n"
"\n"
" db (current)\n"
" wal archive min/max (9.4): none present\n",
"text - multiple stanzas, multi-repo with valid backups, backup lock held on one stanza");
HRN_CFG_LOAD(cfgCmdInfo, argListMultiRepoProgressOnly);
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: ok (backup/expire running - 65.28% complete)\n"
" status: ok (backup/expire running - 65.28% complete, restore running - 65.28% complete)\n"
"\n"
"stanza: stanza2\n"
" status: ok (backup/expire running - 55.55% complete)\n"
"\n"
"stanza: stanza3\n"
" status: ok\n",
" status: ok\n"
"\n"
"stanza: stanza4\n"
" status: ok (restore running - 12.34% complete)\n",
"text (progress only) - multiple stanzas, multi-repo with valid backups, backup lock held on one stanza");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
HRN_FORK_PARENT_NOTIFY_PUT(1);
}
HRN_FORK_PARENT_END();
}
HRN_FORK_END();
// Cleanup
HRN_STORAGE_PATH_REMOVE(storageTest, TEST_PATH "/repo/" STORAGE_PATH_ARCHIVE "/stanza4", .recurse = true);
HRN_STORAGE_PATH_REMOVE(storageTest, TEST_PATH "/repo/" STORAGE_PATH_BACKUP "/stanza4", .recurse = true);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("multi-repo: stanza exists but requested backup does not");
@@ -1843,7 +2050,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":6,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"requested backup not found\""
"}"
"}"
@@ -1995,7 +2205,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"}"
@@ -2210,7 +2423,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"}"
@@ -2389,7 +2605,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"}"
@@ -2562,7 +2781,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":0,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"ok\""
"}"
"}"
@@ -2668,7 +2890,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":4,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"different across repos\""
"}"
"}"
@@ -3202,7 +3427,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":5,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"database mismatch across repos\""
"}"
"}"
@@ -3632,7 +3860,10 @@ testRun(void)
"],"
"\"status\":{"
"\"code\":99,"
"\"lock\":{\"backup\":{\"held\":false}},"
"\"lock\":{"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"other\""
"}"
"}"
@@ -3652,7 +3883,8 @@ testRun(void)
"\"status\":{"
"\"code\":99,"
"\"lock\":{"
"\"backup\":{\"held\":false}"
"\"backup\":{\"held\":false},"
"\"restore\":{\"held\":false}"
"},"
"\"message\":\"other\""
"}"

View File

@@ -76,7 +76,9 @@ testRun(void)
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire stanza-create lock");
TEST_STORAGE_LIST(
storageTest, NULL, "test-archive-1.lock\ntest-archive-2.lock\ntest-backup-1.lock\ntest-backup-2.lock\n",
storageTest, NULL,
"test-archive-1.lock\ntest-archive-2.lock\ntest-backup-1.lock\ntest-backup-2.lock\ntest-restore-1.lock\n"
"test-restore-2.lock\n",
.comment = "check lock files");
// -------------------------------------------------------------------------------------------------------------------------

View File

@@ -19,6 +19,7 @@ Test Restore Command
#include "common/harnessManifest.h"
#include "common/harnessPostgres.h"
#include "common/harnessProtocol.h"
#include "common/harnessRestore.h"
#include "common/harnessStorage.h"
#include "common/harnessStorageHelper.h"
#include "common/harnessTime.h"
@@ -2152,6 +2153,51 @@ testRun(void)
"HINT: was the target timeline created by promoting from a timeline < latest?");
}
// *****************************************************************************************************************************
if (testBegin("restoreJobResult()"))
{
// Set log level to detail
harnessLogLevelSet(logLevelDetail);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("report host/100% progress on noop result");
// Create job that skips file
ProtocolParallelJob *job = protocolParallelJobNew(VARSTRDEF("pg_data/test"), strIdFromZ("x"), NULL);
PackWrite *const resultPack = protocolPackNew();
pckWriteStrP(resultPack, STRDEF("pg_data/test"));
pckWriteU32P(resultPack, restoreResultZero);
// No more fields need to be written since noop will ignore them anyway
pckWriteEndP(resultPack);
protocolParallelJobResultSet(job, pckReadNew(pckWriteResult(resultPack)));
// Create manifest with file
Manifest *manifest = NULL;
OBJ_NEW_BASE_BEGIN(Manifest, .childQty = MEM_CONTEXT_QTY_MAX)
{
manifest = manifestNewInternal();
HRN_MANIFEST_TARGET_ADD(manifest, .name = MANIFEST_TARGET_PGDATA, .path = "pg_data");
HRN_MANIFEST_FILE_ADD(manifest, .name = "pg_data/test");
}
OBJ_NEW_END();
unsigned int currentPercentComplete = 4567;
lockInit(TEST_PATH_STR, cfgOptionStr(cfgOptExecId));
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire restore lock");
TEST_RESULT_UINT(
restoreJobResult(manifest, job, NULL, 0, 0,
&currentPercentComplete), 0, "log noop result");
TEST_RESULT_VOID(cmdLockReleaseP(), "release restore lock");
TEST_RESULT_LOG("P00 DETAIL: restore file pg_data/test (0B, 100.00%)");
}
// *****************************************************************************************************************************
if (testBegin("cmdRestore()"))
{
@@ -2182,7 +2228,7 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptPgHost, "pg1");
HRN_CFG_LOAD(cfgCmdRestore, argList);
TEST_ERROR(cmdRestore(), HostInvalidError, "restore command must be run on the PostgreSQL host");
TEST_ERROR(hrnCmdRestore(), HostInvalidError, "restore command must be run on the PostgreSQL host");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("full restore without delta, multi-repo");
@@ -2279,7 +2325,7 @@ testRun(void)
infoArchiveSaveFile(
infoArchive, storageRepoIdxWrite(1), INFO_ARCHIVE_PATH_FILE_STR, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS));
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_VOID(hrnCmdRestore(), "successful restore");
TEST_RESULT_LOG(
zNewFmt(
@@ -2437,7 +2483,7 @@ testRun(void)
#undef TEST_PGDATA
#undef TEST_REPO_PATH
cmdRestore();
hrnCmdRestore();
TEST_RESULT_LOG(
"P00 INFO: repo1: restore backup set 20161219-212741F, recovery will start at 2016-12-19 21:27:40\n"
@@ -2514,7 +2560,7 @@ testRun(void)
hrnCfgArgRawBool(argList, cfgOptForce, true);
HRN_CFG_LOAD(cfgCmdRestore, argList);
cmdRestore();
hrnCmdRestore();
TEST_RESULT_LOG(
"P00 INFO: repo1: restore backup set 20161219-212741F, recovery will start at 2016-12-19 21:27:40\n"
@@ -2908,7 +2954,7 @@ testRun(void)
// Add a few bogus links to be deleted
THROW_ON_SYS_ERROR(symlink("../wal", zNewFmt("%s/pg_wal2", strZ(pgPath))) == -1, FileOpenError, "unable to create symlink");
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_VOID(hrnCmdRestore(), "successful restore");
TEST_RESULT_LOG_FMT(
"P00 INFO: repo1: restore backup set 20161219-212741F_20161219-212918I\n"
@@ -3102,7 +3148,7 @@ testRun(void)
storageWriteIo(
storageNewWriteP(storageRepoWrite(), STRDEF(STORAGE_REPO_BACKUP "/" TEST_LABEL "/" BACKUP_MANIFEST_FILE))));
TEST_RESULT_VOID(cmdRestore(), "successful restore");
TEST_RESULT_VOID(hrnCmdRestore(), "successful restore");
TEST_RESULT_LOG_FMT(
"P00 INFO: repo2: restore backup set 20161219-212741F_20161219-212918I, recovery will start at [TIME]\n"
@@ -3199,7 +3245,7 @@ testRun(void)
harnessLogLevelSet(logLevelWarn);
TEST_ERROR(
cmdRestore(), FileMissingError,
hrnCmdRestore(), FileMissingError,
"raised from local-1 shim protocol: unable to open missing file"
" '" TEST_PATH "/repo/backup/test1/20161219-212741F_20161219-212918I/pg_data/global/pg_control' for read\n"
"[RETRY DETAIL OMITTED]");
@@ -3314,7 +3360,7 @@ testRun(void)
hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 2, TEST_CIPHER_PASS);
HRN_CFG_LOAD(cfgCmdRestore, argList);
TEST_RESULT_VOID(cmdRestore(), "restore");
TEST_RESULT_VOID(hrnCmdRestore(), "restore");
TEST_STORAGE_LIST(
storagePg(), NULL,
@@ -3354,7 +3400,7 @@ testRun(void)
HRN_STORAGE_PUT(storageRepoWrite(), strZ(strNewFmt(STORAGE_REPO_BACKUP "/%s/bundle/1", strZ(backupFull))), NULL);
// Make sure restore fails with the invalid file
TEST_ERROR(cmdRestore(), CryptoError, "raised from local-1 shim protocol: cipher header missing");
TEST_ERROR(hrnCmdRestore(), CryptoError, "raised from local-1 shim protocol: cipher header missing");
// Use detail log level to catch block incremental restore message
harnessLogLevelSet(logLevelDetail);
@@ -3377,7 +3423,7 @@ testRun(void)
hrnCfgArgRaw(argList, cfgOptRepoTargetTime, targetTime);
HRN_CFG_LOAD(cfgCmdRestore, argList);
TEST_RESULT_VOID(cmdRestore(), "restore");
TEST_RESULT_VOID(hrnCmdRestore(), "restore");
TEST_STORAGE_LIST(
storagePg(), NULL,

View File

@@ -2135,7 +2135,8 @@ testRun(void)
TEST_RESULT_STR_Z(varStr(kvGet(recoveryKv, VARSTRDEF("a"))), "b", "check recovery option");
TEST_ASSIGN(recoveryKv, varKv(cfgOptionIdxVar(cfgOptRecoveryOption, 0)), "get recovery options");
TEST_RESULT_STR_Z(varStr(kvGet(recoveryKv, VARSTRDEF("c"))), "de=fg hi", "check recovery option");
TEST_RESULT_BOOL(cfgLockRequired(), false, "restore command does not require lock");
TEST_RESULT_BOOL(cfgLockRequired(), true, "restore command requires lock");
TEST_RESULT_UINT(cfgLockType(), lockTypeRestore, "restore command requires restore lock type");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("recovery options, config file");