1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-12 10:04:14 +02:00

Refactor recovery file generation.

Separate the generation of recovery values and formatting them into recovery.conf format.  This is generally a good idea, but also makes the code ready to deal with a different recovery file in PostgreSQL 12.

Also move the recovery file logic out of cmdRestore() into restoreRecoveryWrite().
This commit is contained in:
David Steele 2019-09-27 09:19:12 -04:00
parent cf1e96e827
commit 03a7bda511
2 changed files with 183 additions and 135 deletions

View File

@ -781,7 +781,7 @@ restoreCleanBuild(Manifest *manifest)
// Alse ignore recovery.conf when recovery type = preserve
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_PRESERVE_STR))
strLstAdd(cleanData->fileIgnore, PG_FILE_RECOVERYCONF_STR);
strLstAdd(cleanData->fileIgnore, PG_FILE_RECOVERYCONF_STR);
// If this is a tablespace append the tablespace identifier
if (cleanData->target->type == manifestTargetTypeLink && cleanData->target->tablespaceId != 0)
@ -1125,27 +1125,26 @@ restoreSelectiveExpression(Manifest *manifest)
}
/***********************************************************************************************************************************
Generate the recovery.conf file
Generate the recovery file
***********************************************************************************************************************************/
static String *
restoreRecoveryConf(unsigned int pgVersion)
// Helper to generate recovery options
static KeyValue *
restoreRecoveryOption(unsigned int pgVersion)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, pgVersion);
FUNCTION_LOG_END();
String *result = NULL;
KeyValue *result = NULL;
// Only generate recovery.conf if recovery type is not none
if (!strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_NONE_STR))
MEM_CONTEXT_TEMP_BEGIN()
{
MEM_CONTEXT_TEMP_BEGIN()
{
result = strNew("");
StringList *recoveryOptionKey = strLstNew();
result = kvNew();
if (cfgOptionTest(cfgOptRecoveryOption))
{
StringList *recoveryOptionKey = strLstNew();
if (cfgOptionTest(cfgOptRecoveryOption))
{
const KeyValue *recoveryOption = cfgOptionKv(cfgOptRecoveryOption);
recoveryOptionKey = strLstSort(strLstNewVarLst(kvKeyList(recoveryOption)), sortOrderAsc);
@ -1158,103 +1157,189 @@ restoreRecoveryConf(unsigned int pgVersion)
// Replace - in key with _. Since we use - users naturally will as well.
strReplaceChr(key, '-', '_');
strCatFmt(result, "%s = '%s'\n", strPtr(key), strPtr(value));
}
kvPut(result, VARSTR(key), VARSTR(value));
}
}
// Write restore_command
if (!strLstExists(recoveryOptionKey, RESTORE_COMMAND_STR))
// Write restore_command
if (!strLstExists(recoveryOptionKey, RESTORE_COMMAND_STR))
{
// Null out options that it does not make sense to pass from the restore command to archive-get. All of these have
// reasonable defaults so there is no danger of a error -- they just might not be optimal. In any case, it seems
// better than, for example, passing --process-max=32 to archive-get because it was specified for restore.
KeyValue *optionReplace = kvNew();
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_CONSOLE_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_FILE_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_STDERR_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_SUBPROCESS_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_TIMESTAMP_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_PROCESS_MAX_STR), NULL);
kvPut(
result, VARSTRZ(RESTORE_COMMAND),
VARSTR(
strNewFmt(
"%s %s %%f \"%%p\"", strPtr(cfgExe()),
strPtr(strLstJoin(cfgExecParam(cfgCmdArchiveGet, optionReplace, true), " ")))));
}
// If recovery type is immediate
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_IMMEDIATE_STR))
{
kvPut(result, VARSTRZ(RECOVERY_TARGET), VARSTRZ(RECOVERY_TYPE_IMMEDIATE));
}
// Else recovery type is standby
else if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_STANDBY_STR))
{
// Write standby_mode
kvPut(result, VARSTRZ(STANDBY_MODE), VARSTRDEF("on"));
}
// Else recovery type is not default so write target options
else if (!strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_DEFAULT_STR))
{
// Write the recovery target
kvPut(
result, VARSTR(strNewFmt(RECOVERY_TARGET "_%s", strPtr(cfgOptionStr(cfgOptType)))),
VARSTR(cfgOptionStr(cfgOptTarget)));
// Write recovery_target_inclusive
if (cfgOptionTest(cfgOptTargetExclusive) && cfgOptionBool(cfgOptTargetExclusive))
kvPut(result, VARSTRZ(RECOVERY_TARGET_INCLUSIVE), VARSTR(FALSE_STR));
}
// Write pause_at_recovery_target/recovery_target_action
if (cfgOptionTest(cfgOptTargetAction))
{
const String *targetAction = cfgOptionStr(cfgOptTargetAction);
if (!strEqZ(targetAction, cfgDefOptionDefault(cfgDefCmdRestore, cfgDefOptTargetAction)))
{
// Null out options that it does not make sense to pass from the restore command to archive-get. All of these have
// reasonable defaults so there is no danger of a error -- they just might not be optimal. In any case, it seems
// better than, for example, passing --process-max=32 to archive-get because it was specified for restore.
KeyValue *optionReplace = kvNew();
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_CONSOLE_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_FILE_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_STDERR_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_SUBPROCESS_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_LOG_TIMESTAMP_STR), NULL);
kvPut(optionReplace, VARSTR(CFGOPT_PROCESS_MAX_STR), NULL);
strCatFmt(
result, RESTORE_COMMAND " = '%s %s %%f \"%%p\"'\n", strPtr(cfgExe()),
strPtr(strLstJoin(cfgExecParam(cfgCmdArchiveGet, optionReplace, true), " ")));
}
// If recovery type is immediate
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_IMMEDIATE_STR))
{
strCat(result, RECOVERY_TARGET " = '" RECOVERY_TYPE_IMMEDIATE "'\n");
}
// Else recovery type is standby
else if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_STANDBY_STR))
{
// Write standby_mode
strCatFmt(result, STANDBY_MODE " = 'on'\n");
}
// Else recovery type is not default so write target options
else if (!strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_DEFAULT_STR))
{
// Write the recovery target
strCatFmt(
result, RECOVERY_TARGET "_%s = '%s'\n", strPtr(cfgOptionStr(cfgOptType)), strPtr(cfgOptionStr(cfgOptTarget)));
// Write recovery_target_inclusive
if (cfgOptionTest(cfgOptTargetExclusive) && cfgOptionBool(cfgOptTargetExclusive))
strCatFmt(result, RECOVERY_TARGET_INCLUSIVE " = 'false'\n");
}
// Write pause_at_recovery_target/recovery_target_action
if (cfgOptionTest(cfgOptTargetAction))
{
const String *targetAction = cfgOptionStr(cfgOptTargetAction);
if (!strEqZ(targetAction, cfgDefOptionDefault(cfgDefCmdRestore, cfgDefOptTargetAction)))
// Write recovery_target on supported PostgreSQL versions
if (pgVersion >= PG_VERSION_RECOVERY_TARGET_ACTION)
{
// Write recovery_target on supported PostgreSQL versions
if (pgVersion >= PG_VERSION_RECOVERY_TARGET_ACTION)
{
strCatFmt(result, RECOVERY_TARGET_ACTION " = '%s'\n", strPtr(targetAction));
}
// Write pause_at_recovery_target on supported PostgreSQL versions
else if (pgVersion >= PG_VERSION_RECOVERY_TARGET_PAUSE)
{
// Shutdown action is not supported with pause_at_recovery_target setting
if (strEq(targetAction, RECOVERY_TARGET_ACTION_SHUTDOWN_STR))
{
THROW_FMT(
OptionInvalidError,
CFGOPT_TARGET_ACTION "=" RECOVERY_TARGET_ACTION_SHUTDOWN " is only available in PostgreSQL >= %s",
strPtr(pgVersionToStr(PG_VERSION_RECOVERY_TARGET_ACTION)));
}
strCat(result, PAUSE_AT_RECOVERY_TARGET " = 'false'\n");
}
// Else error on unsupported version
else
kvPut(result, VARSTRZ(RECOVERY_TARGET_ACTION), VARSTR(targetAction));
}
// Write pause_at_recovery_target on supported PostgreSQL versions
else if (pgVersion >= PG_VERSION_RECOVERY_TARGET_PAUSE)
{
// Shutdown action is not supported with pause_at_recovery_target setting
if (strEq(targetAction, RECOVERY_TARGET_ACTION_SHUTDOWN_STR))
{
THROW_FMT(
OptionInvalidError, CFGOPT_TARGET_ACTION " option is only available in PostgreSQL >= %s",
strPtr(pgVersionToStr(PG_VERSION_RECOVERY_TARGET_PAUSE)));
OptionInvalidError,
CFGOPT_TARGET_ACTION "=" RECOVERY_TARGET_ACTION_SHUTDOWN " is only available in PostgreSQL >= %s",
strPtr(pgVersionToStr(PG_VERSION_RECOVERY_TARGET_ACTION)));
}
kvPut(result, VARSTRZ(PAUSE_AT_RECOVERY_TARGET), VARSTR(FALSE_STR));
}
// Else error on unsupported version
else
{
THROW_FMT(
OptionInvalidError, CFGOPT_TARGET_ACTION " option is only available in PostgreSQL >= %s",
strPtr(pgVersionToStr(PG_VERSION_RECOVERY_TARGET_PAUSE)));
}
}
// Write recovery_target_timeline
if (cfgOptionTest(cfgOptTargetTimeline))
strCatFmt(result, RECOVERY_TARGET_TIMELINE " = '%s'\n", strPtr(cfgOptionStr(cfgOptTargetTimeline)));
memContextSwitch(MEM_CONTEXT_OLD());
result = strDup(result);
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
// Write recovery_target_timeline
if (cfgOptionTest(cfgOptTargetTimeline))
kvPut(result, VARSTRZ(RECOVERY_TARGET_TIMELINE), VARSTR(cfgOptionStr(cfgOptTargetTimeline)));
// Move to calling context
kvMove(result, MEM_CONTEXT_OLD());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(KEY_VALUE, result);
}
// Helper to write recovery options into recovery.conf
static String *
restoreRecoveryConf(unsigned int pgVersion)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(UINT, pgVersion);
FUNCTION_LOG_END();
String *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
result = strNew("");
// Output all recovery options
KeyValue *optionKv = restoreRecoveryOption(pgVersion);
const VariantList *optionKeyList = kvKeyList(optionKv);
for (unsigned int optionKeyIdx = 0; optionKeyIdx < varLstSize(optionKeyList); optionKeyIdx++)
{
const Variant *optionKey = varLstGet(optionKeyList, optionKeyIdx);
strCatFmt(result, "%s = '%s'\n", strPtr(varStr(optionKey)), strPtr(varStr(kvGet(optionKv, optionKey))));
}
// Move to calling context
memContextSwitch(MEM_CONTEXT_OLD());
result = strDup(result);
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(STRING, result);
}
static void
restoreRecoveryWrite(const Manifest *manifest)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_TEST_PARAM(MANIFEST, manifest);
FUNCTION_LOG_END();
// Get PostgreSQL version to write recovery for
unsigned int pgVersion = manifestData(manifest)->pgVersion;
// Determine which file recovery setttings will be written to
const String *recoveryFile = PG_FILE_RECOVERYCONF_STR;
// Use the data directory to set permissions and ownership for recovery file
const ManifestPath *dataPath = manifestPathFind(manifest, MANIFEST_TARGET_PGDATA_STR);
mode_t recoveryFileMode = dataPath->mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
MEM_CONTEXT_TEMP_BEGIN()
{
// If recovery type is preserve then leave recovery file as it is
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_PRESERVE_STR))
{
if (!storageExistsNP(storagePg(), recoveryFile))
{
LOG_WARN(
"recovery type is " RECOVERY_TYPE_PRESERVE " but recovery file does not exist at '%s'",
strPtr(storagePathNP(storagePg(), recoveryFile)));
}
}
// Else write recovery file if requested
else
{
// Only generate recovery file if recovery type is not none
if (!strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_NONE_STR))
{
LOG_INFO("write %s", strPtr(storagePathNP(storagePg(), recoveryFile)));
storagePutNP(
storageNewWriteP(
storagePgWrite(), recoveryFile, .noCreatePath = true, .modeFile = recoveryFileMode, .noAtomic = true,
.noSyncPath = true, .user = dataPath->user, .group = dataPath->group),
BUFSTR(restoreRecoveryConf(pgVersion)));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
/***********************************************************************************************************************************
Generate a list of queues that determine the order of file processing
***********************************************************************************************************************************/
@ -1671,36 +1756,8 @@ cmdRestore(void)
}
while (!protocolParallelDone(parallelExec));
// If recovery type is preserve then leave recovery.conf as it is
if (strEq(cfgOptionStr(cfgOptType), RECOVERY_TYPE_PRESERVE_STR))
{
if (!storageExistsNP(storagePg(), PG_FILE_RECOVERYCONF_STR))
{
LOG_WARN(
"recovery type is " RECOVERY_TYPE_PRESERVE " but recovery file does not exist at '%s'",
strPtr(storagePathNP(storagePg(), PG_FILE_RECOVERYCONF_STR)));
}
}
// Else write recovery.conf if requested
else
{
String *recoveryConf = restoreRecoveryConf(manifestData(jobData.manifest)->pgVersion);
if (recoveryConf != NULL)
{
// Use the data directory to set permissions and ownership
const ManifestPath *dataPath = manifestPathFind(jobData.manifest, MANIFEST_TARGET_PGDATA_STR);
LOG_INFO("write %s", strPtr(storagePathNP(storagePg(), PG_FILE_RECOVERYCONF_STR)));
storagePutNP(
storageNewWriteP(
storagePgWrite(), PG_FILE_RECOVERYCONF_STR, .noCreatePath = true,
.modeFile = dataPath->mode & (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH), .noAtomic = true, .noSyncPath = true,
.user = dataPath->user, .group = dataPath->group),
BUFSTR(recoveryConf));
}
}
// Write recovery settings
restoreRecoveryWrite(jobData.manifest);
// Remove backup.manifest
storageRemoveNP(storagePgWrite(), BACKUP_MANIFEST_FILE_STR);

View File

@ -1191,19 +1191,10 @@ testRun(void)
strLstAddZ(argBaseList, "--pg1-path=/pg");
strLstAddZ(argBaseList, "restore");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("no recovery conf");
StringList *argList = strLstDup(argBaseList);
strLstAddZ(argList, "--type=none");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_PTR(restoreRecoveryConf(PG_VERSION_94), NULL, "check recovery options");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("user-specified options");
argList = strLstDup(argBaseList);
StringList *argList = strLstDup(argBaseList);
strLstAddZ(argList, "--recovery-option=a-setting=a");
strLstAddZ(argList, "--recovery-option=b_setting=b");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));