1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-13 01:00:23 +02:00

Allow backups to run concurrently on different repositories.

The prior locking only allowed one backup per stanza, which was required by PostgreSQL <= 9.5 and didn't present a problem when only one stanza could be created.

Now that multiple stanzas are allowed relax this restriction so that backups can run concurrently for PostgreSQL > 9.5. To do this, update the locking to be per stanza and repo rather than per stanza. Remotes are not aware of the repos that require locking so send an explicit list of files to be locked to the remote. Also remove the advisory lock for PostgreSQL > 9.5.

For info output the running backups are combined for progress output in order to avoid changing the JSON format. It definitely makes sense to have per repo progress as well but that will be left for a future commit.
This commit is contained in:
David Steele
2024-07-04 16:22:17 +07:00
committed by GitHub
parent 3a2266f327
commit edd61636a9
24 changed files with 261 additions and 88 deletions

View File

@ -87,6 +87,7 @@ option:
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
job-retry-interval: {type: string, required: false, command: {noop: {}}}
lock: {type: list, required: false, command: {noop: {}}}
log-level-file: {type: string, required: false, command: {noop: {}}}
log-level-stderr: {type: string, required: false, command: {noop: {}}}
pg: {type: string, required: false, command: {noop: {}}}

View File

@ -15,6 +15,7 @@
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
<option id="job-retry"><summary></summary><text><p></p></text></option>
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
<option id="lock"><summary></summary><text><p></p></text></option>
<option id="log-level-file"><summary></summary><text><p></p></text></option>
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
<option id="repo"><summary></summary><text><p></p></text></option>

View File

@ -23,6 +23,24 @@
</release-bug-list>
<release-improvement-list>
<release-item>
<commit subject="Refactor lock module.">
<github-pull-request id="2371"/>
</commit>
<commit subject="Add remote locks for stanza commands missed in 31c7824a."/>
<commit subject="Allow backups to run concurrently on different repositories.">
<github-pull-request id="2389"/>
</commit>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
<release-item-reviewer id="stefan.fercot"/>
</release-item-contributor-list>
<p>Allow backups to run concurrently on different repositories.</p>
</release-item>
<release-item>
<github-pull-request id="2379"/>
@ -46,21 +64,5 @@
<p>Allow alternative WAL segment sizes for PostgreSQL &amp;le; 10.</p>
</release-item>
</release-improvement-list>
<release-development-list>
<release-item>
<commit subject="Refactor lock module.">
<github-pull-request id="2371"/>
</commit>
<commit subject="Add remote locks for stanza commands missed in 31c7824a."/>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
</release-item-contributor-list>
<p>Refactor lock module.</p>
</release-item>
</release-development-list>
</release-core-list>
</release>

View File

@ -543,6 +543,20 @@ option:
local: {}
remote: {}
lock:
type: list
internal: true
required: false
command:
annotate: {}
archive-push: {}
backup: {}
stanza-create: {}
stanza-delete: {}
stanza-upgrade: {}
command-role:
remote: {}
remote-type:
type: string-id
internal: true

View File

@ -49,6 +49,7 @@ cmdStop(void)
cfgOptionTest(cfgOptStanza) ? strNewFmt("%s-", strZ(cfgOptionStr(cfgOptStanza))) : NULL;
const StringList *const lockPathFileList = strLstSort(
storageListP(storageLocal(), lockPath, .errorOnMissing = true), sortOrderAsc);
List *const pidList = lstNewP(sizeof(int), .comparator = lstComparatorInt);
// Find each lock file and send term signals to the processes
for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++)
@ -69,6 +70,13 @@ cmdStop(void)
continue;
}
// If the process has already been killed then skip it. This is possible because a single process may be holding
// multiple lock files.
if (lstFind(pidList, &lockResult.data.processId) != NULL)
continue;
lstAdd(pidList, &lockResult.data.processId);
// The lock file is valid so that means there is a running process -- send a term signal to the process
if (kill(lockResult.data.processId, SIGTERM) != 0)
LOG_WARN_FMT("unable to send term signal to process %d", lockResult.data.processId);

View File

@ -133,11 +133,9 @@ 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 backupLockChecked; // Has the check for a backup lock already been performed?
bool backupLockHeld; // Is backup lock held on the system where info command is run?
const Variant *percentComplete; // Percentage of backup complete * 100 (when not NULL)
const Variant *sizeComplete; // Completed size of the backup in bytes
const Variant *size; // Total size of the backup in bytes
uint64_t sizeComplete; // Completed size of the backup in bytes
uint64_t size; // Total size of the backup in bytes
InfoRepoData *repoList; // List of configured repositories
} InfoStanzaRepo;
@ -240,14 +238,18 @@ stanzaStatus(const int code, const InfoStanzaRepo *const stanzaData, const Varia
KeyValue *const backupLockKv = kvPutKv(lockKv, STATUS_KEY_LOCK_BACKUP_VAR);
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_HELD_VAR, VARBOOL(stanzaData->backupLockHeld));
if (stanzaData->percentComplete != NULL && cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR, stanzaData->percentComplete);
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 (stanzaData->sizeComplete != NULL)
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_COMPLETE_VAR, stanzaData->sizeComplete);
if (stanzaData->size != NULL)
kvPut(backupLockKv, STATUS_KEY_LOCK_BACKUP_SIZE_VAR, stanzaData->size);
if (cfgOptionStrId(cfgOptOutput) != CFGOPTVAL_OUTPUT_JSON)
{
kvPut(
backupLockKv, STATUS_KEY_LOCK_BACKUP_PERCENT_COMPLETE_VAR,
VARUINT((unsigned int)(((double)stanzaData->sizeComplete / (double)stanzaData->size) * 10000)));
}
}
FUNCTION_TEST_RETURN_VOID();
}
@ -1295,20 +1297,19 @@ infoUpdateStanza(
infoPgCipherPass(infoBackupPg(stanzaRepo->repoList[repoIdx].backupInfo)));
}
// If a backup lock check has not already been performed, then do so
if (!stanzaRepo->backupLockChecked)
{
// If there is a valid backup lock for this stanza then backup/expire must be running
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name);
const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name, repoIdx);
stanzaRepo->backupLockHeld = lockResult.status == lockReadStatusValid;
stanzaRepo->backupLockChecked = true;
if (stanzaRepo->backupLockHeld)
if (lockResult.status == lockReadStatusValid)
{
stanzaRepo->percentComplete = lockResult.data.percentComplete;
stanzaRepo->sizeComplete = lockResult.data.sizeComplete;
stanzaRepo->size = lockResult.data.size;
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);
}
}
}

View File

@ -25,19 +25,57 @@ static const char *const lockTypeName[] =
"backup", // lockTypeBackup
};
/**********************************************************************************************************************************/
/***********************************************************************************************************************************
Generate lock file name
***********************************************************************************************************************************/
static String *
cmdLockFileName(const String *const stanza, const LockType lockType)
cmdLockFileName(const String *const stanza, const LockType lockType, const unsigned int repoKey)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_PARAM(UINT, repoKey);
FUNCTION_TEST_END();
ASSERT(stanza != NULL);
ASSERT(lockType < lockTypeAll);
ASSERT(repoKey > 0);
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s-%u" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType], repoKey));
}
/**********************************************************************************************************************************/
FN_EXTERN StringList *
cmdLockList(void)
{
FUNCTION_TEST_VOID();
ASSERT(cfgLockType() != lockTypeNone);
StringList *const result = strLstNew();
MEM_CONTEXT_TEMP_BEGIN()
{
const LockType lockType = cfgLockType();
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
const bool repoSet = cfgOptionTest(cfgOptRepo);
const unsigned int repoMin = repoSet ? cfgOptionGroupIdxDefault(cfgOptGrpRepo) : 0;
const unsigned int repoMax = repoSet ? repoMin : cfgOptionGroupIdxTotal(cfgOptGrpRepo) - 1;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{
for (unsigned int repoIdx = repoMin; repoIdx <= repoMax; repoIdx++)
{
strLstAdd(
result, cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx, cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx)));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_TEST_RETURN(STRING_LIST, result);
}
/**********************************************************************************************************************************/
@ -51,24 +89,21 @@ cmdLockAcquire(const LockAcquireParam param)
bool result = true;
// Don't allow return on no lock when locking more than one file. This makes cleanup difficult and there are no known use cases.
const LockType lockType = cfgLockType();
ASSERT(!param.returnOnNoLock || lockType != lockTypeAll);
ASSERT(!param.returnOnNoLock || cfgLockType() != lockTypeAll);
// Don't allow another lock if one is already held
if (cmdLockLocal.held)
THROW(AssertError, "lock is already held by this process");
// Lock files
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
MEM_CONTEXT_TEMP_BEGIN()
{
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx);
const StringList *const lockList = cfgOptionTest(cfgOptLock) ? strLstNewVarLst(cfgOptionLst(cfgOptLock)) : cmdLockList();
result = lockAcquire(lockFileName, param);
strFree(lockFileName);
for (unsigned int lockListIdx = 0; lockListIdx < strLstSize(lockList); lockListIdx++)
result = lockAcquire(strLstGet(lockList, lockListIdx), param);
}
MEM_CONTEXT_TEMP_END();
// Set lock held flag
cmdLockLocal.held = result;
@ -86,7 +121,8 @@ cmdLockWrite(const LockWriteParam param)
FUNCTION_LOG_PARAM(VARIANT, param.size);
FUNCTION_LOG_END();
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), cfgLockType());
String *const lockFileName = cmdLockFileName(
cfgOptionStr(cfgOptStanza), cfgLockType(), cfgOptionGroupIdxToKey(cfgOptGrpRepo, cfgOptionGroupIdxDefault(cfgOptGrpRepo)));
lockWrite(lockFileName, param);
strFree(lockFileName);
@ -96,11 +132,12 @@ cmdLockWrite(const LockWriteParam param)
/**********************************************************************************************************************************/
FN_EXTERN LockReadResult
cmdLockRead(const LockType lockType, const String *const stanza)
cmdLockRead(const LockType lockType, const String *const stanza, const unsigned int repoIdx)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_PARAM(STRING, stanza);
FUNCTION_LOG_PARAM(UINT, repoIdx);
FUNCTION_LOG_END();
ASSERT(lockType == lockTypeBackup);
@ -112,7 +149,7 @@ cmdLockRead(const LockType lockType, const String *const stanza)
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFileName = cmdLockFileName(stanza, lockType);
const String *const lockFileName = cmdLockFileName(stanza, lockType, cfgOptionGroupIdxToKey(cfgOptGrpRepo, repoIdx));
MEM_CONTEXT_PRIOR_BEGIN()
{

View File

@ -10,6 +10,9 @@ Command Lock Handler
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Get list of required locks
FN_EXTERN StringList *cmdLockList(void);
// Acquire a command lock. This will involve locking one or more files on disk depending on the lock type. Most operations only
// acquire a single lock type (archive or backup), but the stanza commands all need to lock both.
#define cmdLockAcquireP(...) \
@ -24,7 +27,7 @@ FN_EXTERN bool cmdLockAcquire(LockAcquireParam param);
FN_EXTERN void cmdLockWrite(LockWriteParam param);
// Read a command lock file held by another process to get information about what the process is doing
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza);
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza, unsigned int repoKey);
// Release command lock(s)
#define cmdLockReleaseP(...) \

View File

@ -87,6 +87,24 @@ lstComparatorStr(const void *const item1, const void *const item2)
FUNCTION_TEST_RETURN(INT, strCmp(*(String **)item1, *(String **)item2));
}
/**********************************************************************************************************************************/
FN_EXTERN int
lstComparatorInt(const void *const intPtr1, const void *const intPtr2)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, intPtr1);
FUNCTION_TEST_PARAM_P(VOID, intPtr2);
FUNCTION_TEST_END();
ASSERT(intPtr1 != NULL);
ASSERT(intPtr2 != NULL);
const int int1 = *(int *)intPtr1;
const int int2 = *(int *)intPtr2;
FUNCTION_TEST_RETURN(INT, LST_COMPARATOR_CMP(int1, int2));
}
/**********************************************************************************************************************************/
FN_EXTERN int
lstComparatorUInt(const void *const uintPtr1, const void *const uintPtr2)

View File

@ -49,6 +49,9 @@ FN_EXTERN int lstComparatorStr(const void *item1, const void *item2);
// General purpose list comparator for unsigned int or structs with an unsigned int as the first member
FN_EXTERN int lstComparatorUInt(const void *item1, const void *item2);
// General purpose list comparator for int or structs with an int as the first member
FN_EXTERN int lstComparatorInt(const void *item1, const void *item2);
// General purpose list comparator for zero-terminated strings or structs with a zero-terminated string as the first member
FN_EXTERN int lstComparatorZ(const void *item1, const void *item2);

View File

@ -85,6 +85,7 @@ Option constants
#define CFGOPT_JOB_RETRY_INTERVAL "job-retry-interval"
#define CFGOPT_LINK_ALL "link-all"
#define CFGOPT_LINK_MAP "link-map"
#define CFGOPT_LOCK "lock"
#define CFGOPT_LOCK_PATH "lock-path"
#define CFGOPT_LOG_LEVEL_CONSOLE "log-level-console"
#define CFGOPT_LOG_LEVEL_FILE "log-level-file"
@ -136,7 +137,7 @@ Option constants
#define CFGOPT_TYPE "type"
#define CFGOPT_VERBOSE "verbose"
#define CFG_OPTION_TOTAL 180
#define CFG_OPTION_TOTAL 181
/***********************************************************************************************************************************
Option value constants
@ -425,6 +426,7 @@ typedef enum
cfgOptJobRetryInterval,
cfgOptLinkAll,
cfgOptLinkMap,
cfgOptLock,
cfgOptLockPath,
cfgOptLogLevelConsole,
cfgOptLogLevelFile,

View File

@ -2455,6 +2455,25 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), // opt/link-map
), // opt/link-map
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/lock
( // opt/lock
PARSE_RULE_OPTION_NAME("lock"), // opt/lock
PARSE_RULE_OPTION_TYPE(List), // opt/lock
PARSE_RULE_OPTION_REQUIRED(false), // opt/lock
PARSE_RULE_OPTION_SECTION(CommandLine), // opt/lock
PARSE_RULE_OPTION_MULTI(true), // opt/lock
// opt/lock
PARSE_RULE_OPTION_COMMAND_ROLE_REMOTE_VALID_LIST // opt/lock
( // opt/lock
PARSE_RULE_OPTION_COMMAND(Annotate) // opt/lock
PARSE_RULE_OPTION_COMMAND(ArchivePush) // opt/lock
PARSE_RULE_OPTION_COMMAND(Backup) // opt/lock
PARSE_RULE_OPTION_COMMAND(StanzaCreate) // opt/lock
PARSE_RULE_OPTION_COMMAND(StanzaDelete) // opt/lock
PARSE_RULE_OPTION_COMMAND(StanzaUpgrade) // opt/lock
), // opt/lock
), // opt/lock
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/lock-path
( // opt/lock-path
PARSE_RULE_OPTION_NAME("lock-path"), // opt/lock-path
@ -10846,6 +10865,7 @@ static const uint8_t optionResolveOrder[] =
cfgOptJobRetryInterval, // opt-resolve-order
cfgOptLinkAll, // opt-resolve-order
cfgOptLinkMap, // opt-resolve-order
cfgOptLock, // opt-resolve-order
cfgOptLockPath, // opt-resolve-order
cfgOptLogLevelConsole, // opt-resolve-order
cfgOptLogLevelFile, // opt-resolve-order

View File

@ -382,8 +382,10 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
MEM_CONTEXT_TEMP_BEGIN()
{
// Acquire the backup advisory lock to make sure that backups are not running from multiple backup servers against the same
// database cluster. This lock helps make the stop-auto option safe.
if (!pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
// database cluster when PostgreSQL <= 9.5. This lock helps make the stop-auto option safe. On PostgreSQL > 9.5 multiple
// backups are allowed on the same cluster.
if (dbPgVersion(this) <= PG_VERSION_95 &&
!pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
{
THROW(
LockAcquireError,

View File

@ -5,6 +5,7 @@ Protocol Helper
#include <string.h>
#include "command/lock.h"
#include "common/crypto/common.h"
#include "common/debug.h"
#include "common/exec.h"
@ -599,6 +600,10 @@ protocolRemoteParam(ProtocolStorageType protocolStorageType, unsigned int hostId
// Add the remote type
kvPut(optionReplace, VARSTRDEF(CFGOPT_REMOTE_TYPE), VARSTR(strIdToStr(protocolStorageType)));
// Add lock required on the remote
if (cfgLockRemoteRequired())
kvPut(optionReplace, VARSTRDEF(CFGOPT_LOCK), varNewVarLst(varLstNewStrLst(cmdLockList())));
MEM_CONTEXT_PRIOR_BEGIN()
{
result = cfgExecParam(cfgCommand(), cfgCmdRoleRemote, optionReplace, false, true);

View File

@ -173,6 +173,7 @@ option:
type: string, default: CFGOPTDEF_CONFIG_PATH "/" PROJECT_CONFIG_INCLUDE_PATH, default-literal: true, command: {noop: {}}}
job-retry: {type: string, required: false, deprecate: {job-retry-old: {}}, command: {noop: {}}}
job-retry-interval: {type: string, required: false, command: {noop: {}}}
lock: {type: list, required: false, command: {noop: {}}}
log-level-file: {type: string, required: false, command: {noop: {}}}
log-level-stderr: {type: string, required: false, command: {noop: {}}}
pg: {type: string, required: false, command: {noop: {}}}

View File

@ -15,6 +15,7 @@
<option id="compress-level-network"><summary></summary><text><p></p></text></option>
<option id="job-retry"><summary></summary><text><p></p></text></option>
<option id="job-retry-interval"><summary></summary><text><p></p></text></option>
<option id="lock"><summary></summary><text><p></p></text></option>
<option id="log-level-file"><summary></summary><text><p></p></text></option>
<option id="log-level-stderr"><summary></summary><text><p></p></text></option>
<option id="repo"><summary></summary><text><p></p></text></option>

View File

@ -270,12 +270,12 @@ hrnBackupPqScript(const unsigned int pgVersion, const time_t backupTimeStart, Hr
// Get start time
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_TIME_QUERY(1, (int64_t)backupTimeStart * 1000));
// Advisory lock
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true));
// Check if backup is in progress (only for exclusive backup)
// Get advisory lock and check if backup is in progress (only for exclusive backup)
if (pgVersion <= PG_VERSION_95)
{
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true));
HRN_PQ_SCRIPT_ADD(HRN_PQ_SCRIPT_IS_IN_BACKUP(1, false));
}
// Perform archive check
if (!param.noArchiveCheck)

View File

@ -255,9 +255,11 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive);
lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create archive lock");
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive, 1)), true, "create archive lock");
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive, 2)), true, "create archive lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();

View File

@ -238,7 +238,7 @@ testRun(void)
{
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup)), true, "create backup/expire lock");
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1)), true, "create backup/expire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -429,7 +429,7 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup);
String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName), "write lock data");
@ -1039,7 +1039,7 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup, 1);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(
@ -1483,10 +1483,31 @@ testRun(void)
{
HRN_FORK_CHILD_BEGIN()
{
String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName, .percentComplete = VARUINT(5555)), "write lock data");
String *lockFileStanza1Repo1 = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 1);
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo1), true, "create backup/expire lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileStanza1Repo1, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(1754830),
.percentComplete = VARUINT(5555)),
"write lock data");
String *lockFileStanza1Repo2 = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup, 2);
TEST_RESULT_BOOL(lockAcquireP(lockFileStanza1Repo2), true, "create backup/expire lock");
TEST_RESULT_VOID(
lockWriteP(
lockFileStanza1Repo2, .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(
lockWriteP(
lockFileStanza2Repo1, .size = VARUINT64(3159000), .sizeComplete = VARUINT64(1754830),
.percentComplete = VARUINT(5555)),
"write lock data");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
@ -1507,7 +1528,7 @@ testRun(void)
TEST_RESULT_STR_Z(
infoRender(),
"stanza: stanza1\n"
" status: ok\n"
" status: ok (backup/expire running - 65.27% complete)\n"
" cipher: mixed\n"
" repo1: none\n"
" repo2: aes-256-cbc\n"

View File

@ -27,12 +27,15 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 1, "1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 2, "1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, "/repo2");
hrnCfgArgRawZ(argList, cfgOptRepo, "1");
HRN_CFG_LOAD(cfgCmdBackup, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
TEST_ERROR(cmdLockAcquireP(), AssertError, "lock is already held by this process");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n", .comment = "check lock file");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup-1.lock\n", .comment = "check lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("write backup data");
@ -45,7 +48,7 @@ testRun(void)
TEST_TITLE("read backup data");
LockReadResult lockReadResult;
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test")), "read backup data");
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test"), 0), "read backup data");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
@ -67,10 +70,34 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, "/repo1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, "/repo2");
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire stanza-create lock");
TEST_STORAGE_LIST(storageTest, NULL, "test-archive.lock\ntest-backup.lock\n", .comment = "check lock file");
TEST_STORAGE_LIST(
storageTest, NULL, "test-archive-1.lock\ntest-archive-2.lock\ntest-backup-1.lock\ntest-backup-2.lock\n",
.comment = "check lock files");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire backup:remote command lock");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgRawZ(argList, cfgOptLock, "test-lock-99.lock");
hrnCfgArgRawZ(argList, cfgOptLock, "other-lock.lock");
HRN_CFG_LOAD(cfgCmdBackup, argList, .role = cfgCmdRoleRemote, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup:remote lock");
TEST_STORAGE_LIST(storageTest, NULL, "other-lock.lock\ntest-lock-99.lock\n", .comment = "check lock files");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");

View File

@ -169,7 +169,7 @@ testRun(void)
"create client");
protocolClientNoOp(client);
TEST_STORAGE_EXISTS(hrnStorage, "lock/test-archive" LOCK_FILE_EXT, .comment = "lock exists");
TEST_STORAGE_EXISTS(hrnStorage, "lock/test-archive-1" LOCK_FILE_EXT, .comment = "lock exists");
protocolClientFree(client);
}

View File

@ -190,6 +190,16 @@ testRun(void)
TEST_RESULT_BOOL(lstComparatorUInt(&uint1, &uint2) < 0, true, "first uint is less");
TEST_RESULT_BOOL(lstComparatorUInt(&uint2, &uint1) > 0, true, "first uint is greater");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lstComparatorInt()");
int int1 = -1;
int int2 = 2;
TEST_RESULT_INT(lstComparatorInt(&int1, &int1), 0, "ints are equal");
TEST_RESULT_BOOL(lstComparatorInt(&int1, &int2) < 0, true, "first int is less");
TEST_RESULT_BOOL(lstComparatorInt(&int2, &int1) > 0, true, "first int is greater");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lstComparatorZ()");

View File

@ -365,7 +365,6 @@ testRun(void)
// Start backup with timeline error
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000020000000300000002"),
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "3/3", "000000020000000300000003"));
@ -374,7 +373,6 @@ testRun(void)
// Start backup with checkpoint error
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000010000000400000003"),
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "4/4", "000000010000000400000004"));
@ -384,7 +382,6 @@ testRun(void)
// Start backup
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_LE_96(1, "000000010000000300000002"),
HRN_PQ_SCRIPT_START_BACKUP_96(1, false, "3/3", "000000010000000300000003"));
@ -494,7 +491,6 @@ testRun(void)
// Start backup
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000050000000500000005"),
HRN_PQ_SCRIPT_START_BACKUP_GE_10(1, false, "5/5", "000000050000000500000005"),
@ -618,7 +614,6 @@ testRun(void)
// Start backup
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000050000000500000004"),
HRN_PQ_SCRIPT_START_BACKUP_GE_10(1, false, "5/5", "000000050000000500000005"));
@ -651,7 +646,6 @@ testRun(void)
// Start backup
HRN_PQ_SCRIPT_SET(
HRN_PQ_SCRIPT_ADVISORY_LOCK(1, true),
HRN_PQ_SCRIPT_CURRENT_WAL_GE_10(1, "000000060000000600000005"),
HRN_PQ_SCRIPT_START_BACKUP_GE_15(1, false, "6/6", "000000060000000600000006"));

View File

@ -430,8 +430,8 @@ testRun(void)
TEST_RESULT_STRLST_Z(
protocolRemoteParamSsh(protocolStorageTypePg, 0),
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg1-host\n"
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
" --pg1-path=/path/to/1 --process=0 --remote-type=pg --stanza=test1 backup:remote\n",
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
" --log-level-stderr=error --pg1-path=/path/to/1 --process=0 --remote-type=pg --stanza=test1 backup:remote\n",
"check config");
// -------------------------------------------------------------------------------------------------------------------------
@ -452,8 +452,8 @@ testRun(void)
TEST_RESULT_STRLST_Z(
protocolRemoteParamSsh(protocolStorageTypePg, 1),
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg2-host\n"
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
" --pg1-path=/path/to/2 --process=4 --remote-type=pg --stanza=test1 backup:remote\n",
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
" --log-level-stderr=error --pg1-path=/path/to/2 --process=4 --remote-type=pg --stanza=test1 backup:remote\n",
"check config");
// -------------------------------------------------------------------------------------------------------------------------
@ -474,9 +474,9 @@ testRun(void)
TEST_RESULT_STRLST_Z(
protocolRemoteParamSsh(protocolStorageTypePg, 1),
"-o\nLogLevel=error\n-o\nCompression=no\n-o\nPasswordAuthentication=no\npostgres@pg3-host\n"
TEST_PROJECT_EXE " --exec-id=1-test --log-level-console=off --log-level-file=off --log-level-stderr=error"
" --pg1-path=/path/to/3 --pg1-port=3333 --pg1-socket-path=/socket3 --process=4 --remote-type=pg --stanza=test1"
" backup:remote\n",
TEST_PROJECT_EXE " --exec-id=1-test --lock=test1-backup-1.lock --log-level-console=off --log-level-file=off"
" --log-level-stderr=error --pg1-path=/path/to/3 --pg1-port=3333 --pg1-socket-path=/socket3 --process=4"
" --remote-type=pg --stanza=test1 backup:remote\n",
"check config");
}