mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-03-03 14:52:21 +02:00
Bundle files in the repository during backup.
Bundle (combine) smaller files during backup to reduce the number of files written to the repository (enable with --bundle). Reducing the number of files is a benefit on all file systems, but especially so on object stores such as S3 that have a high file creation cost. Another benefit is that zero-length files are only stored as metadata in the manifest. Files are batched up to bundle-size and then compressed/encrypted individually and stored sequentially in the bundle. The bundle id and offset of each file is stored in the manifest so files can be retrieved randomly without needing to read the entire bundle. Files are ordered by timestamp descending when being assigned to bundles to reduce the amount of random access that needs to be done. The idea is that bundles with older files can be read in their entirety on restore and only bundles with newer files will get fragmented. Bundles are a custom format with metadata stored in the manifest. Tar was considered but it is too limited a format, the major issue being that the size of the file must be known in advance and that is very contrary to how pgBackRest works, especially once we introduce page-level incremental backups. Bundles are stored numbered in the bundle directory. Some files may still end up in pg_data if they are added after the backup is complete. backup_label is an example. Currently, only the backup command works in batches. The restore and verify commands use the offsets to pull individual files out of the bundle. It seems better to finalize how this is going to work before optimizing the other commands. Even as is, this is a major step forward, and all commands function with bundling. One caveat: resume is currently not supported when bundle is enabled.
This commit is contained in:
parent
8046f06307
commit
34d649579e
@ -128,6 +128,7 @@
|
|||||||
<commit subject="Optimization for jsonFromStrInternal()."/>
|
<commit subject="Optimization for jsonFromStrInternal()."/>
|
||||||
<commit subject="Simplify manifest file defaults."/>
|
<commit subject="Simplify manifest file defaults."/>
|
||||||
<commit subject="Simplify filename construction in command/verify module."/>
|
<commit subject="Simplify filename construction in command/verify module."/>
|
||||||
|
<commit subject="Bundle files in the repository during backup."/>
|
||||||
|
|
||||||
<release-item-contributor-list>
|
<release-item-contributor-list>
|
||||||
<release-item-contributor id="david.steele"/>
|
<release-item-contributor id="david.steele"/>
|
||||||
|
@ -1106,6 +1106,31 @@ option:
|
|||||||
command-role:
|
command-role:
|
||||||
main: {}
|
main: {}
|
||||||
|
|
||||||
|
bundle:
|
||||||
|
internal: true
|
||||||
|
section: global
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
command:
|
||||||
|
backup: {}
|
||||||
|
command-role:
|
||||||
|
main: {}
|
||||||
|
|
||||||
|
bundle-size:
|
||||||
|
internal: true
|
||||||
|
section: global
|
||||||
|
type: size
|
||||||
|
default: 100MiB
|
||||||
|
allow-range: [1MiB, 1PiB]
|
||||||
|
command:
|
||||||
|
backup: {}
|
||||||
|
command-role:
|
||||||
|
main: {}
|
||||||
|
depend:
|
||||||
|
option: bundle
|
||||||
|
list:
|
||||||
|
- true
|
||||||
|
|
||||||
checksum-page:
|
checksum-page:
|
||||||
section: global
|
section: global
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -1151,6 +1176,10 @@ option:
|
|||||||
backup: {}
|
backup: {}
|
||||||
command-role:
|
command-role:
|
||||||
main: {}
|
main: {}
|
||||||
|
depend:
|
||||||
|
option: bundle
|
||||||
|
list:
|
||||||
|
- false
|
||||||
|
|
||||||
start-fast:
|
start-fast:
|
||||||
section: global
|
section: global
|
||||||
|
@ -1109,6 +1109,30 @@
|
|||||||
<example>y</example>
|
<example>y</example>
|
||||||
</config-key>
|
</config-key>
|
||||||
|
|
||||||
|
<!-- ======================================================================================================= -->
|
||||||
|
<config-key id="bundle" name="Repository Bundles">
|
||||||
|
<summary>Bundle files in repository.</summary>
|
||||||
|
|
||||||
|
<text>
|
||||||
|
<p>Bundle (combine) smaller files to reduce the total number of files written to the repository. Writing fewer files is generally more efficient, especially on object stores such as <proper>S3</proper>. In addition, zero-length files are not stored (except in the manifest), which saves time and space.</p>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<example>y</example>
|
||||||
|
</config-key>
|
||||||
|
|
||||||
|
<!-- ======================================================================================================= -->
|
||||||
|
<config-key id="bundle-size" name="Repository Bundle Size">
|
||||||
|
<summary>Target size for file bundles.</summary>
|
||||||
|
|
||||||
|
<text>
|
||||||
|
<p>Defines the target size of bundled files. Most bundles will be smaller than this target but it is possible that some will be slightly larger, so do not set this option to the maximum size that your file system allows.</p>
|
||||||
|
|
||||||
|
<p>In general, it is not a good idea to set this option too high because retries will need to redo the entire bundle.</p>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<example>10MiB</example>
|
||||||
|
</config-key>
|
||||||
|
|
||||||
<!-- CONFIG - BACKUP SECTION - CHECKSUM-PAGE KEY -->
|
<!-- CONFIG - BACKUP SECTION - CHECKSUM-PAGE KEY -->
|
||||||
<config-key id="checksum-page" name="Page Checksums">
|
<config-key id="checksum-page" name="Page Checksums">
|
||||||
<summary>Validate data page checksums.</summary>
|
<summary>Validate data page checksums.</summary>
|
||||||
|
@ -610,7 +610,7 @@ void backupResumeCallback(void *data, const StorageInfo *info)
|
|||||||
{
|
{
|
||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
resumeData->manifest, manifestName, file.size, fileResume.sizeRepo, fileResume.checksumSha1, NULL,
|
resumeData->manifest, manifestName, file.size, fileResume.sizeRepo, fileResume.checksumSha1, NULL,
|
||||||
fileResume.checksumPage, fileResume.checksumPageError, fileResume.checksumPageErrorList);
|
fileResume.checksumPage, fileResume.checksumPageError, fileResume.checksumPageErrorList, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,6 +674,7 @@ backupResumeFind(const Manifest *manifest, const String *cipherPassBackup)
|
|||||||
// Resumable backups do not have backup.manifest
|
// Resumable backups do not have backup.manifest
|
||||||
if (!storageExistsP(storageRepo(), manifestFile))
|
if (!storageExistsP(storageRepo(), manifestFile))
|
||||||
{
|
{
|
||||||
|
const bool resume = cfgOptionTest(cfgOptResume) && cfgOptionBool(cfgOptResume);
|
||||||
bool usable = false;
|
bool usable = false;
|
||||||
const String *reason = STRDEF("partially deleted by prior resume or invalid");
|
const String *reason = STRDEF("partially deleted by prior resume or invalid");
|
||||||
Manifest *manifestResume = NULL;
|
Manifest *manifestResume = NULL;
|
||||||
@ -685,7 +686,7 @@ backupResumeFind(const Manifest *manifest, const String *cipherPassBackup)
|
|||||||
|
|
||||||
// Attempt to read the manifest file in the resumable backup to see if it can be used. If any error at all
|
// Attempt to read the manifest file in the resumable backup to see if it can be used. If any error at all
|
||||||
// occurs then the backup will be considered unusable and a resume will not be attempted.
|
// occurs then the backup will be considered unusable and a resume will not be attempted.
|
||||||
if (cfgOptionBool(cfgOptResume))
|
if (resume)
|
||||||
{
|
{
|
||||||
TRY_BEGIN()
|
TRY_BEGIN()
|
||||||
{
|
{
|
||||||
@ -751,7 +752,9 @@ backupResumeFind(const Manifest *manifest, const String *cipherPassBackup)
|
|||||||
// Else warn and remove the unusable backup
|
// Else warn and remove the unusable backup
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_WARN_FMT("backup '%s' cannot be resumed: %s", strZ(backupLabel), strZ(reason));
|
LOG_FMT(
|
||||||
|
resume ? logLevelWarn : logLevelInfo, 0, "backup '%s' cannot be resumed: %s", strZ(backupLabel),
|
||||||
|
strZ(reason));
|
||||||
|
|
||||||
storagePathRemoveP(
|
storagePathRemoveP(
|
||||||
storageRepoWrite(), strNewFmt(STORAGE_REPO_BACKUP "/%s", strZ(backupLabel)), .recurse = true);
|
storageRepoWrite(), strNewFmt(STORAGE_REPO_BACKUP "/%s", strZ(backupLabel)), .recurse = true);
|
||||||
@ -1148,21 +1151,22 @@ Log the results of a job and throw errors
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
static void
|
static void
|
||||||
backupJobResult(
|
backupJobResult(
|
||||||
Manifest *manifest, const String *host, const String *const fileName, StringList *fileRemove, ProtocolParallelJob *const job,
|
Manifest *const manifest, const String *const host, const Storage *const storagePg, StringList *const fileRemove,
|
||||||
const uint64_t sizeTotal, uint64_t *sizeProgress)
|
ProtocolParallelJob *const job, const bool bundle, const uint64_t sizeTotal, uint64_t *const sizeProgress)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(MANIFEST, manifest);
|
FUNCTION_LOG_PARAM(MANIFEST, manifest);
|
||||||
FUNCTION_LOG_PARAM(STRING, host);
|
FUNCTION_LOG_PARAM(STRING, host);
|
||||||
FUNCTION_LOG_PARAM(STRING, fileName);
|
FUNCTION_LOG_PARAM(STORAGE, storagePg);
|
||||||
FUNCTION_LOG_PARAM(STRING_LIST, fileRemove);
|
FUNCTION_LOG_PARAM(STRING_LIST, fileRemove);
|
||||||
FUNCTION_LOG_PARAM(PROTOCOL_PARALLEL_JOB, job);
|
FUNCTION_LOG_PARAM(PROTOCOL_PARALLEL_JOB, job);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, bundle);
|
||||||
FUNCTION_LOG_PARAM(UINT64, sizeTotal);
|
FUNCTION_LOG_PARAM(UINT64, sizeTotal);
|
||||||
FUNCTION_LOG_PARAM_P(UINT64, sizeProgress);
|
FUNCTION_LOG_PARAM_P(UINT64, sizeProgress);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(manifest != NULL);
|
ASSERT(manifest != NULL);
|
||||||
ASSERT(fileName != NULL);
|
ASSERT(storagePg != NULL);
|
||||||
ASSERT(fileRemove != NULL);
|
ASSERT(fileRemove != NULL);
|
||||||
ASSERT(job != NULL);
|
ASSERT(job != NULL);
|
||||||
|
|
||||||
@ -1171,143 +1175,150 @@ backupJobResult(
|
|||||||
{
|
{
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
const ManifestFile file = manifestFileFind(manifest, varStr(protocolParallelJobKey(job)));
|
|
||||||
const unsigned int processId = protocolParallelJobProcessId(job);
|
const unsigned int processId = protocolParallelJobProcessId(job);
|
||||||
|
const uint64_t bundleId = bundle ? varUInt64(protocolParallelJobKey(job)) : 0;
|
||||||
PackRead *const jobResult = protocolParallelJobResult(job);
|
PackRead *const jobResult = protocolParallelJobResult(job);
|
||||||
const BackupCopyResult copyResult = (BackupCopyResult)pckReadU32P(jobResult);
|
|
||||||
const uint64_t copySize = pckReadU64P(jobResult);
|
|
||||||
const uint64_t repoSize = pckReadU64P(jobResult);
|
|
||||||
const String *const copyChecksum = pckReadStrP(jobResult);
|
|
||||||
PackRead *const checksumPageResult = pckReadPackReadP(jobResult);
|
|
||||||
|
|
||||||
// Increment backup copy progress
|
while (!pckReadNullP(jobResult))
|
||||||
*sizeProgress += copySize;
|
{
|
||||||
|
const ManifestFile file = manifestFileFind(manifest, pckReadStrP(jobResult));
|
||||||
|
const BackupCopyResult copyResult = (BackupCopyResult)pckReadU32P(jobResult);
|
||||||
|
const uint64_t copySize = pckReadU64P(jobResult);
|
||||||
|
const uint64_t bundleOffset = pckReadU64P(jobResult);
|
||||||
|
const uint64_t repoSize = pckReadU64P(jobResult);
|
||||||
|
const String *const copyChecksum = pckReadStrP(jobResult);
|
||||||
|
PackRead *const checksumPageResult = pckReadPackReadP(jobResult);
|
||||||
|
|
||||||
// Create log file name
|
// Increment backup copy progress
|
||||||
const String *fileLog = host == NULL ? fileName : strNewFmt("%s:%s", strZ(host), strZ(fileName));
|
*sizeProgress += copySize;
|
||||||
|
|
||||||
// Format log strings
|
// Create log file name
|
||||||
const String *const logProgress =
|
const String *const fileName = storagePathP(storagePg, manifestPathPg(file.name));
|
||||||
strNewFmt(
|
const String *fileLog = host == NULL ? fileName : strNewFmt("%s:%s", strZ(host), strZ(fileName));
|
||||||
"%s, %" PRIu64 "%%", strZ(strSizeFormat(copySize)), sizeTotal == 0 ? 100 : *sizeProgress * 100 / sizeTotal);
|
|
||||||
const String *const logChecksum = copySize != 0 ? strNewFmt(" checksum %s", strZ(copyChecksum)) : EMPTY_STR;
|
|
||||||
|
|
||||||
// If the file is in a prior backup and nothing changed, just log it
|
// Format log strings
|
||||||
if (copyResult == backupCopyResultNoOp)
|
const String *const logProgress =
|
||||||
{
|
strNewFmt(
|
||||||
LOG_DETAIL_PID_FMT(
|
"%s, %" PRIu64 "%%", strZ(strSizeFormat(copySize)), sizeTotal == 0 ? 100 : *sizeProgress * 100 / sizeTotal);
|
||||||
processId, "match file from prior backup %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
const String *const logChecksum = copySize != 0 ? strNewFmt(" checksum %s", strZ(copyChecksum)) : EMPTY_STR;
|
||||||
}
|
|
||||||
// Else if the repo matched the expect checksum, just log it
|
// If the file is in a prior backup and nothing changed, just log it
|
||||||
else if (copyResult == backupCopyResultChecksum)
|
if (copyResult == backupCopyResultNoOp)
|
||||||
{
|
|
||||||
LOG_DETAIL_PID_FMT(
|
|
||||||
processId, "checksum resumed file %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
|
||||||
}
|
|
||||||
// Else if the file was removed during backup add it to the list of files to be removed from the manifest when the
|
|
||||||
// backup is complete. It can't be removed right now because that will invalidate the pointers that are being used for
|
|
||||||
// processing.
|
|
||||||
else if (copyResult == backupCopyResultSkip)
|
|
||||||
{
|
|
||||||
LOG_DETAIL_PID_FMT(processId, "skip file removed by database %s", strZ(fileLog));
|
|
||||||
strLstAdd(fileRemove, file.name);
|
|
||||||
}
|
|
||||||
// Else file was copied so update manifest
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the file had to be recopied then warn that there may be an issue with corruption in the repository
|
|
||||||
// ??? This should really be below the message below for more context -- can be moved after the migration
|
|
||||||
// ??? The name should be a pg path not manifest name -- can be fixed after the migration
|
|
||||||
if (copyResult == backupCopyResultReCopy)
|
|
||||||
{
|
{
|
||||||
LOG_WARN_FMT(
|
LOG_DETAIL_PID_FMT(
|
||||||
"resumed backup file %s does not have expected checksum %s. The file will be recopied and backup will"
|
processId, "match file from prior backup %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
||||||
" continue but this may be an issue unless the resumed backup path in the repository is known to be"
|
|
||||||
" corrupted.\n"
|
|
||||||
"NOTE: this does not indicate a problem with the PostgreSQL page checksums.",
|
|
||||||
strZ(file.name), file.checksumSha1);
|
|
||||||
}
|
}
|
||||||
|
// Else if the repo matched the expect checksum, just log it
|
||||||
LOG_DETAIL_PID_FMT(processId, "backup file %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
else if (copyResult == backupCopyResultChecksum)
|
||||||
|
|
||||||
// If the file had page checksums calculated during the copy
|
|
||||||
ASSERT((!file.checksumPage && checksumPageResult == NULL) || (file.checksumPage && checksumPageResult != NULL));
|
|
||||||
|
|
||||||
bool checksumPageError = false;
|
|
||||||
const VariantList *checksumPageErrorList = NULL;
|
|
||||||
|
|
||||||
if (checksumPageResult != NULL)
|
|
||||||
{
|
{
|
||||||
checksumPageErrorList = backupJobResultPageChecksum(checksumPageResult);
|
LOG_DETAIL_PID_FMT(
|
||||||
|
processId, "checksum resumed file %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
||||||
// If the checksum was valid
|
}
|
||||||
if (!pckReadBoolP(checksumPageResult))
|
// Else if the file was removed during backup add it to the list of files to be removed from the manifest when the
|
||||||
|
// backup is complete. It can't be removed right now because that will invalidate the pointers that are being used for
|
||||||
|
// processing.
|
||||||
|
else if (copyResult == backupCopyResultSkip)
|
||||||
|
{
|
||||||
|
LOG_DETAIL_PID_FMT(processId, "skip file removed by database %s", strZ(fileLog));
|
||||||
|
strLstAdd(fileRemove, file.name);
|
||||||
|
}
|
||||||
|
// Else file was copied so update manifest
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the file had to be recopied then warn that there may be an issue with corruption in the repository
|
||||||
|
// ??? This should really be below the message below for more context -- can be moved after the migration
|
||||||
|
// ??? The name should be a pg path not manifest name -- can be fixed after the migration
|
||||||
|
if (copyResult == backupCopyResultReCopy)
|
||||||
{
|
{
|
||||||
checksumPageError = true;
|
LOG_WARN_FMT(
|
||||||
|
"resumed backup file %s does not have expected checksum %s. The file will be recopied and backup will"
|
||||||
|
" continue but this may be an issue unless the resumed backup path in the repository is known to be"
|
||||||
|
" corrupted.\n"
|
||||||
|
"NOTE: this does not indicate a problem with the PostgreSQL page checksums.",
|
||||||
|
strZ(file.name), file.checksumSha1);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DETAIL_PID_FMT(processId, "backup file %s (%s)%s", strZ(fileLog), strZ(logProgress), strZ(logChecksum));
|
||||||
|
|
||||||
|
// If the file had page checksums calculated during the copy
|
||||||
|
ASSERT((!file.checksumPage && checksumPageResult == NULL) || (file.checksumPage && checksumPageResult != NULL));
|
||||||
|
|
||||||
|
bool checksumPageError = false;
|
||||||
|
const VariantList *checksumPageErrorList = NULL;
|
||||||
|
|
||||||
|
if (checksumPageResult != NULL)
|
||||||
|
{
|
||||||
|
checksumPageErrorList = backupJobResultPageChecksum(checksumPageResult);
|
||||||
|
|
||||||
|
// If the checksum was valid
|
||||||
if (!pckReadBoolP(checksumPageResult))
|
if (!pckReadBoolP(checksumPageResult))
|
||||||
{
|
{
|
||||||
checksumPageErrorList = NULL;
|
checksumPageError = true;
|
||||||
|
|
||||||
// ??? Update formatting after migration
|
if (!pckReadBoolP(checksumPageResult))
|
||||||
LOG_WARN_FMT(
|
|
||||||
"page misalignment in file %s: file size %" PRIu64 " is not divisible by page size %u",
|
|
||||||
strZ(fileLog), copySize, PG_PAGE_SIZE_DEFAULT);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Format the page checksum errors
|
|
||||||
CHECK(FormatError, checksumPageErrorList != NULL, "page checksum error list is missing");
|
|
||||||
CHECK(FormatError, !varLstEmpty(checksumPageErrorList), "page checksum error list is empty");
|
|
||||||
|
|
||||||
String *error = strNew();
|
|
||||||
unsigned int errorTotalMin = 0;
|
|
||||||
|
|
||||||
for (unsigned int errorIdx = 0; errorIdx < varLstSize(checksumPageErrorList); errorIdx++)
|
|
||||||
{
|
{
|
||||||
const Variant *const errorItem = varLstGet(checksumPageErrorList, errorIdx);
|
checksumPageErrorList = NULL;
|
||||||
|
|
||||||
// Add a comma if this is not the first item
|
// ??? Update formatting after migration
|
||||||
if (errorIdx != 0)
|
LOG_WARN_FMT(
|
||||||
strCatZ(error, ", ");
|
"page misalignment in file %s: file size %" PRIu64 " is not divisible by page size %u",
|
||||||
|
strZ(fileLog), copySize, PG_PAGE_SIZE_DEFAULT);
|
||||||
// If an error range
|
|
||||||
if (varType(errorItem) == varTypeVariantList)
|
|
||||||
{
|
|
||||||
const VariantList *const errorItemList = varVarLst(errorItem);
|
|
||||||
ASSERT(varLstSize(errorItemList) == 2);
|
|
||||||
|
|
||||||
strCatFmt(
|
|
||||||
error, "%" PRIu64 "-%" PRIu64, varUInt64(varLstGet(errorItemList, 0)),
|
|
||||||
varUInt64(varLstGet(errorItemList, 1)));
|
|
||||||
errorTotalMin += 2;
|
|
||||||
}
|
|
||||||
// Else a single error
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ASSERT(varType(errorItem) == varTypeUInt64);
|
|
||||||
|
|
||||||
strCatFmt(error, "%" PRIu64, varUInt64(errorItem));
|
|
||||||
errorTotalMin++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Format the page checksum errors
|
||||||
|
CHECK(FormatError, checksumPageErrorList != NULL, "page checksum error list is missing");
|
||||||
|
CHECK(FormatError, !varLstEmpty(checksumPageErrorList), "page checksum error list is empty");
|
||||||
|
|
||||||
// Make message plural when appropriate
|
String *error = strNew();
|
||||||
const String *const plural = errorTotalMin > 1 ? STRDEF("s") : EMPTY_STR;
|
unsigned int errorTotalMin = 0;
|
||||||
|
|
||||||
// ??? Update formatting after migration
|
for (unsigned int errorIdx = 0; errorIdx < varLstSize(checksumPageErrorList); errorIdx++)
|
||||||
LOG_WARN_FMT(
|
{
|
||||||
"invalid page checksum%s found in file %s at page%s %s", strZ(plural), strZ(fileLog), strZ(plural),
|
const Variant *const errorItem = varLstGet(checksumPageErrorList, errorIdx);
|
||||||
strZ(error));
|
|
||||||
|
// Add a comma if this is not the first item
|
||||||
|
if (errorIdx != 0)
|
||||||
|
strCatZ(error, ", ");
|
||||||
|
|
||||||
|
// If an error range
|
||||||
|
if (varType(errorItem) == varTypeVariantList)
|
||||||
|
{
|
||||||
|
const VariantList *const errorItemList = varVarLst(errorItem);
|
||||||
|
ASSERT(varLstSize(errorItemList) == 2);
|
||||||
|
|
||||||
|
strCatFmt(
|
||||||
|
error, "%" PRIu64 "-%" PRIu64, varUInt64(varLstGet(errorItemList, 0)),
|
||||||
|
varUInt64(varLstGet(errorItemList, 1)));
|
||||||
|
errorTotalMin += 2;
|
||||||
|
}
|
||||||
|
// Else a single error
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ASSERT(varType(errorItem) == varTypeUInt64);
|
||||||
|
|
||||||
|
strCatFmt(error, "%" PRIu64, varUInt64(errorItem));
|
||||||
|
errorTotalMin++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make message plural when appropriate
|
||||||
|
const String *const plural = errorTotalMin > 1 ? STRDEF("s") : EMPTY_STR;
|
||||||
|
|
||||||
|
// ??? Update formatting after migration
|
||||||
|
LOG_WARN_FMT(
|
||||||
|
"invalid page checksum%s found in file %s at page%s %s", strZ(plural), strZ(fileLog),
|
||||||
|
strZ(plural), strZ(error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update file info and remove any reference to the file's existence in a prior backup
|
// Update file info and remove any reference to the file's existence in a prior backup
|
||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
manifest, file.name, copySize, repoSize, strZ(copyChecksum), VARSTR(NULL), file.checksumPage,
|
manifest, file.name, copySize, repoSize, strZ(copyChecksum), VARSTR(NULL), file.checksumPage,
|
||||||
checksumPageError, checksumPageErrorList != NULL ? jsonFromVar(varNewVarLst(checksumPageErrorList)) : NULL);
|
checksumPageError, checksumPageErrorList != NULL ? jsonFromVar(varNewVarLst(checksumPageErrorList)) : NULL,
|
||||||
|
bundleId, bundleOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
@ -1395,6 +1406,9 @@ typedef struct BackupJobData
|
|||||||
const int compressLevel; // Compress level if backup is compressed
|
const int compressLevel; // Compress level if backup is compressed
|
||||||
const bool delta; // Is this a checksum delta backup?
|
const bool delta; // Is this a checksum delta backup?
|
||||||
const uint64_t lsnStart; // Starting lsn for the backup
|
const uint64_t lsnStart; // Starting lsn for the backup
|
||||||
|
const bool bundle; // Bundle files?
|
||||||
|
const uint64_t bundleSize; // Target bundle size
|
||||||
|
uint64_t bundleId; // Bundle id
|
||||||
|
|
||||||
List *queueList; // List of processing queues
|
List *queueList; // List of processing queues
|
||||||
} BackupJobData;
|
} BackupJobData;
|
||||||
@ -1415,8 +1429,10 @@ backupProcessFilePrimary(RegExp *const standbyExp, const String *const name)
|
|||||||
strEqZ(name, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) || !regExpMatch(standbyExp, name));
|
strEqZ(name, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) || !regExpMatch(standbyExp, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparator to order ManifestFile objects by size then name
|
// Comparator to order ManifestFile objects by size, date, and name
|
||||||
static const Manifest *backupProcessQueueComparatorManifest = NULL;
|
static const Manifest *backupProcessQueueComparatorManifest = NULL;
|
||||||
|
static bool backupProcessQueueComparatorBundle;
|
||||||
|
static uint64_t backupProcessQueueComparatorBundleSize;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
backupProcessQueueComparator(const void *item1, const void *item2)
|
backupProcessQueueComparator(const void *item1, const void *item2)
|
||||||
@ -1434,20 +1450,34 @@ backupProcessQueueComparator(const void *item1, const void *item2)
|
|||||||
ManifestFile file2 = manifestFileUnpack(backupProcessQueueComparatorManifest, *(const ManifestFilePack **)item2);
|
ManifestFile file2 = manifestFileUnpack(backupProcessQueueComparatorManifest, *(const ManifestFilePack **)item2);
|
||||||
|
|
||||||
// If the size differs then that's enough to determine order
|
// If the size differs then that's enough to determine order
|
||||||
if (file1.size < file2.size)
|
if (!backupProcessQueueComparatorBundle || file1.size >= backupProcessQueueComparatorBundleSize ||
|
||||||
FUNCTION_TEST_RETURN(-1);
|
file2.size >= backupProcessQueueComparatorBundleSize)
|
||||||
else if (file1.size > file2.size)
|
{
|
||||||
FUNCTION_TEST_RETURN(1);
|
if (file1.size < file2.size)
|
||||||
|
FUNCTION_TEST_RETURN(-1);
|
||||||
|
else if (file1.size > file2.size)
|
||||||
|
FUNCTION_TEST_RETURN(1);
|
||||||
|
}
|
||||||
|
|
||||||
// If size is the same then use name to generate a deterministic ordering (names must be unique)
|
// If bundling order by time ascending so that older files are bundled with older files and newer with newer
|
||||||
|
if (backupProcessQueueComparatorBundle)
|
||||||
|
{
|
||||||
|
if (file1.timestamp > file2.timestamp)
|
||||||
|
FUNCTION_TEST_RETURN(-1);
|
||||||
|
else if (file1.timestamp < file2.timestamp)
|
||||||
|
FUNCTION_TEST_RETURN(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If size/time is the same then use name to generate a deterministic ordering (names must be unique)
|
||||||
FUNCTION_TEST_RETURN(strCmp(file1.name, file2.name));
|
FUNCTION_TEST_RETURN(strCmp(file1.name, file2.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to generate the backup queues
|
// Helper to generate the backup queues
|
||||||
static uint64_t
|
static uint64_t
|
||||||
backupProcessQueue(Manifest *const manifest, BackupJobData *const jobData)
|
backupProcessQueue(const BackupData *const backupData, Manifest *const manifest, BackupJobData *const jobData)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
|
FUNCTION_LOG_PARAM(BACKUP_DATA, backupData);
|
||||||
FUNCTION_LOG_PARAM(MANIFEST, manifest);
|
FUNCTION_LOG_PARAM(MANIFEST, manifest);
|
||||||
FUNCTION_LOG_PARAM_P(VOID, jobData);
|
FUNCTION_LOG_PARAM_P(VOID, jobData);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
@ -1499,6 +1529,17 @@ backupProcessQueue(Manifest *const manifest, BackupJobData *const jobData)
|
|||||||
if (file.reference != NULL && (!jobData->delta || file.size == 0))
|
if (file.reference != NULL && (!jobData->delta || file.size == 0))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// If bundling store zero-length files immediately in the manifest without copying them
|
||||||
|
if (jobData->bundle && file.size == 0)
|
||||||
|
{
|
||||||
|
LOG_DETAIL_FMT(
|
||||||
|
"store zero-length file %s", strZ(storagePathP(backupData->storagePrimary, manifestPathPg(file.name))));
|
||||||
|
manifestFileUpdate(
|
||||||
|
manifest, file.name, 0, 0, strZ(HASH_TYPE_SHA1_ZERO_STR), VARSTR(NULL), file.checksumPage, false, NULL, 0, 0);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Is pg_control in the backup?
|
// Is pg_control in the backup?
|
||||||
if (strEq(file.name, STRDEF(MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL)))
|
if (strEq(file.name, STRDEF(MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL)))
|
||||||
pgControlFound = true;
|
pgControlFound = true;
|
||||||
@ -1552,6 +1593,8 @@ backupProcessQueue(Manifest *const manifest, BackupJobData *const jobData)
|
|||||||
|
|
||||||
// Sort the queues
|
// Sort the queues
|
||||||
backupProcessQueueComparatorManifest = manifest;
|
backupProcessQueueComparatorManifest = manifest;
|
||||||
|
backupProcessQueueComparatorBundle = jobData->bundle;
|
||||||
|
backupProcessQueueComparatorBundleSize = jobData->bundleSize;
|
||||||
|
|
||||||
for (unsigned int queueIdx = 0; queueIdx < lstSize(jobData->queueList); queueIdx++)
|
for (unsigned int queueIdx = 0; queueIdx < lstSize(jobData->queueList); queueIdx++)
|
||||||
lstSort(*(List **)lstGet(jobData->queueList, queueIdx), sortOrderDesc);
|
lstSort(*(List **)lstGet(jobData->queueList, queueIdx), sortOrderDesc);
|
||||||
@ -1610,17 +1653,47 @@ static ProtocolParallelJob *backupJobCallback(void *data, unsigned int clientIdx
|
|||||||
0 : (int)(clientIdx % (lstSize(jobData->queueList) - queueOffset));
|
0 : (int)(clientIdx % (lstSize(jobData->queueList) - queueOffset));
|
||||||
int queueEnd = queueIdx;
|
int queueEnd = queueIdx;
|
||||||
|
|
||||||
|
// Create backup job
|
||||||
|
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_BACKUP_FILE);
|
||||||
|
PackWrite *param = NULL;
|
||||||
|
uint64_t fileTotal = 0;
|
||||||
|
uint64_t fileSize = 0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
List *queue = *(List **)lstGet(jobData->queueList, (unsigned int)queueIdx + queueOffset);
|
List *queue = *(List **)lstGet(jobData->queueList, (unsigned int)queueIdx + queueOffset);
|
||||||
|
unsigned int fileIdx = 0;
|
||||||
|
|
||||||
if (!lstEmpty(queue))
|
while (fileIdx < lstSize(queue))
|
||||||
{
|
{
|
||||||
const ManifestFile file = manifestFileUnpack(jobData->manifest, *(ManifestFilePack **)lstGet(queue, 0));
|
const ManifestFile file = manifestFileUnpack(jobData->manifest, *(ManifestFilePack **)lstGet(queue, fileIdx));
|
||||||
|
|
||||||
// Create backup job
|
// Continue if the next file would make the bundle too large. There may be a smaller one that will fit.
|
||||||
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_BACKUP_FILE);
|
if (fileTotal > 0 && fileSize + file.size >= jobData->bundleSize)
|
||||||
PackWrite *const param = protocolCommandParam(command);
|
{
|
||||||
|
fileIdx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add common parameters before first file
|
||||||
|
if (param == NULL)
|
||||||
|
{
|
||||||
|
param = protocolCommandParam(command);
|
||||||
|
|
||||||
|
String *const repoFile = strCatFmt(strNew(), STORAGE_REPO_BACKUP "/%s/", strZ(jobData->backupLabel));
|
||||||
|
|
||||||
|
if (jobData->bundle)
|
||||||
|
strCatFmt(repoFile, MANIFEST_PATH_BUNDLE "/%" PRIu64, jobData->bundleId);
|
||||||
|
else
|
||||||
|
strCatFmt(repoFile, "%s%s", strZ(file.name), strZ(compressExtStr(jobData->compressType)));
|
||||||
|
|
||||||
|
pckWriteStrP(param, repoFile);
|
||||||
|
pckWriteU32P(param, jobData->compressType);
|
||||||
|
pckWriteI32P(param, jobData->compressLevel);
|
||||||
|
pckWriteBoolP(param, jobData->delta);
|
||||||
|
pckWriteU64P(param, jobData->cipherSubPass == NULL ? cipherTypeNone : cipherTypeAes256Cbc);
|
||||||
|
pckWriteStrP(param, jobData->cipherSubPass);
|
||||||
|
}
|
||||||
|
|
||||||
pckWriteStrP(param, manifestPathPg(file.name));
|
pckWriteStrP(param, manifestPathPg(file.name));
|
||||||
pckWriteBoolP(param, !strEq(file.name, STRDEF(MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL)));
|
pckWriteBoolP(param, !strEq(file.name, STRDEF(MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL)));
|
||||||
@ -1631,24 +1704,28 @@ static ProtocolParallelJob *backupJobCallback(void *data, unsigned int clientIdx
|
|||||||
pckWriteU64P(param, jobData->lsnStart);
|
pckWriteU64P(param, jobData->lsnStart);
|
||||||
pckWriteStrP(param, file.name);
|
pckWriteStrP(param, file.name);
|
||||||
pckWriteBoolP(param, file.reference != NULL);
|
pckWriteBoolP(param, file.reference != NULL);
|
||||||
pckWriteU32P(param, jobData->compressType);
|
|
||||||
pckWriteI32P(param, jobData->compressLevel);
|
fileTotal++;
|
||||||
pckWriteStrP(param, jobData->backupLabel);
|
fileSize += file.size;
|
||||||
pckWriteBoolP(param, jobData->delta);
|
|
||||||
pckWriteU64P(param, jobData->cipherSubPass == NULL ? cipherTypeNone : cipherTypeAes256Cbc);
|
|
||||||
pckWriteStrP(param, jobData->cipherSubPass);
|
|
||||||
|
|
||||||
// Remove job from the queue
|
// Remove job from the queue
|
||||||
lstRemoveIdx(queue, 0);
|
lstRemoveIdx(queue, fileIdx);
|
||||||
|
|
||||||
|
// Break if not bundling or bundle size has been reached
|
||||||
|
if (!jobData->bundle || fileSize >= jobData->bundleSize)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileTotal > 0)
|
||||||
|
{
|
||||||
// Assign job to result
|
// Assign job to result
|
||||||
MEM_CONTEXT_PRIOR_BEGIN()
|
MEM_CONTEXT_PRIOR_BEGIN()
|
||||||
{
|
{
|
||||||
result = protocolParallelJobNew(VARSTR(file.name), command);
|
result = protocolParallelJobNew(VARUINT64(jobData->bundleId), command);
|
||||||
|
jobData->bundleId++;
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_PRIOR_END();
|
MEM_CONTEXT_PRIOR_END();
|
||||||
|
|
||||||
// Break out of the loop early since we found a job
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1686,10 +1763,33 @@ backupProcess(BackupData *backupData, Manifest *manifest, const String *lsnStart
|
|||||||
bool hardLink = cfgOptionBool(cfgOptRepoHardlink) && storageFeature(storageRepoWrite(), storageFeatureHardLink);
|
bool hardLink = cfgOptionBool(cfgOptRepoHardlink) && storageFeature(storageRepoWrite(), storageFeatureHardLink);
|
||||||
bool backupStandby = cfgOptionBool(cfgOptBackupStandby);
|
bool backupStandby = cfgOptionBool(cfgOptBackupStandby);
|
||||||
|
|
||||||
|
BackupJobData jobData =
|
||||||
|
{
|
||||||
|
.manifest = manifest,
|
||||||
|
.backupLabel = backupLabel,
|
||||||
|
.backupStandby = backupStandby,
|
||||||
|
.compressType = compressTypeEnum(cfgOptionStrId(cfgOptCompressType)),
|
||||||
|
.compressLevel = cfgOptionInt(cfgOptCompressLevel),
|
||||||
|
.cipherType = cfgOptionStrId(cfgOptRepoCipherType),
|
||||||
|
.cipherSubPass = manifestCipherSubPass(manifest),
|
||||||
|
.delta = cfgOptionBool(cfgOptDelta),
|
||||||
|
.lsnStart = cfgOptionBool(cfgOptOnline) ? pgLsnFromStr(lsnStart) : 0xFFFFFFFFFFFFFFFF,
|
||||||
|
.bundle = cfgOptionBool(cfgOptBundle),
|
||||||
|
.bundleSize = cfgOptionTest(cfgOptBundleSize) ? cfgOptionUInt64(cfgOptBundleSize) : 0,
|
||||||
|
.bundleId = 1,
|
||||||
|
|
||||||
|
// Build expression to identify files that can be copied from the standby when standby backup is supported
|
||||||
|
.standbyExp = regExpNew(
|
||||||
|
strNewFmt(
|
||||||
|
"^((" MANIFEST_TARGET_PGDATA "/(" PG_PATH_BASE "|" PG_PATH_GLOBAL "|%s|" PG_PATH_PGMULTIXACT "))|"
|
||||||
|
MANIFEST_TARGET_PGTBLSPC ")/",
|
||||||
|
strZ(pgXactPath(backupData->version)))),
|
||||||
|
};
|
||||||
|
|
||||||
// If this is a full backup or hard-linked and paths are supported then create all paths explicitly so that empty paths will
|
// If this is a full backup or hard-linked and paths are supported then create all paths explicitly so that empty paths will
|
||||||
// exist in to repo. Also create tablespace symlinks when symlinks are available. This makes it possible for the user to
|
// exist in to repo. Also create tablespace symlinks when symlinks are available. This makes it possible for the user to
|
||||||
// make a copy of the backup path and get a valid cluster.
|
// make a copy of the backup path and get a valid cluster.
|
||||||
if (backupType == backupTypeFull || hardLink)
|
if ((backupType == backupTypeFull && !jobData.bundle) || hardLink)
|
||||||
{
|
{
|
||||||
// Create paths when available
|
// Create paths when available
|
||||||
if (storageFeature(storageRepoWrite(), storageFeaturePath))
|
if (storageFeature(storageRepoWrite(), storageFeaturePath))
|
||||||
@ -1726,27 +1826,7 @@ backupProcess(BackupData *backupData, Manifest *manifest, const String *lsnStart
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate processing queues
|
// Generate processing queues
|
||||||
BackupJobData jobData =
|
sizeTotal = backupProcessQueue(backupData, manifest, &jobData);
|
||||||
{
|
|
||||||
.manifest = manifest,
|
|
||||||
.backupLabel = backupLabel,
|
|
||||||
.backupStandby = backupStandby,
|
|
||||||
.compressType = compressTypeEnum(cfgOptionStrId(cfgOptCompressType)),
|
|
||||||
.compressLevel = cfgOptionInt(cfgOptCompressLevel),
|
|
||||||
.cipherType = cfgOptionStrId(cfgOptRepoCipherType),
|
|
||||||
.cipherSubPass = manifestCipherSubPass(manifest),
|
|
||||||
.delta = cfgOptionBool(cfgOptDelta),
|
|
||||||
.lsnStart = cfgOptionBool(cfgOptOnline) ? pgLsnFromStr(lsnStart) : 0xFFFFFFFFFFFFFFFF,
|
|
||||||
|
|
||||||
// Build expression to identify files that can be copied from the standby when standby backup is supported
|
|
||||||
.standbyExp = regExpNew(
|
|
||||||
strNewFmt(
|
|
||||||
"^((" MANIFEST_TARGET_PGDATA "/(" PG_PATH_BASE "|" PG_PATH_GLOBAL "|%s|" PG_PATH_PGMULTIXACT "))|"
|
|
||||||
MANIFEST_TARGET_PGTBLSPC ")/",
|
|
||||||
strZ(pgXactPath(backupData->version)))),
|
|
||||||
};
|
|
||||||
|
|
||||||
sizeTotal = backupProcessQueue(manifest, &jobData);
|
|
||||||
|
|
||||||
// Create the parallel executor
|
// Create the parallel executor
|
||||||
ProtocolParallel *parallelExec = protocolParallelNew(
|
ProtocolParallel *parallelExec = protocolParallelNew(
|
||||||
@ -1789,10 +1869,8 @@ backupProcess(BackupData *backupData, Manifest *manifest, const String *lsnStart
|
|||||||
backupJobResult(
|
backupJobResult(
|
||||||
manifest,
|
manifest,
|
||||||
backupStandby && protocolParallelJobProcessId(job) > 1 ? backupData->hostStandby : backupData->hostPrimary,
|
backupStandby && protocolParallelJobProcessId(job) > 1 ? backupData->hostStandby : backupData->hostPrimary,
|
||||||
storagePathP(
|
protocolParallelJobProcessId(job) > 1 ? storagePgIdx(pgIdx) : backupData->storagePrimary,
|
||||||
protocolParallelJobProcessId(job) > 1 ? storagePgIdx(pgIdx) : backupData->storagePrimary,
|
fileRemove, job, jobData.bundle, sizeTotal, &sizeProgress);
|
||||||
manifestPathPg(manifestFileFind(manifest, varStr(protocolParallelJobKey(job))).name)), fileRemove, job,
|
|
||||||
sizeTotal, &sizeProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A keep-alive is required here for the remote holding open the backup connection
|
// A keep-alive is required here for the remote holding open the backup connection
|
||||||
@ -1866,7 +1944,9 @@ backupProcess(BackupData *backupData, Manifest *manifest, const String *lsnStart
|
|||||||
{
|
{
|
||||||
const String *const path = strNewFmt("%s/%s", strZ(backupPathExp), strZ(manifestPath(manifest, pathIdx)->name));
|
const String *const path = strNewFmt("%s/%s", strZ(backupPathExp), strZ(manifestPath(manifest, pathIdx)->name));
|
||||||
|
|
||||||
if (backupType == backupTypeFull || hardLink || storagePathExistsP(storageRepo(), path))
|
// Always sync the path if it exists or if the backup is full (without bundling) or hardlinked. In the latter cases
|
||||||
|
// the directory should always exist so we want to error if it does not.
|
||||||
|
if ((backupType == backupTypeFull && !jobData.bundle) || hardLink || storagePathExistsP(storageRepo(), path))
|
||||||
storagePathSyncP(storageRepoWrite(), path);
|
storagePathSyncP(storageRepoWrite(), path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2125,7 +2205,8 @@ cmdBackup(void)
|
|||||||
// Build the manifest
|
// Build the manifest
|
||||||
Manifest *manifest = manifestNewBuild(
|
Manifest *manifest = manifestNewBuild(
|
||||||
backupData->storagePrimary, infoPg.version, infoPg.catalogVersion, cfgOptionBool(cfgOptOnline),
|
backupData->storagePrimary, infoPg.version, infoPg.catalogVersion, cfgOptionBool(cfgOptOnline),
|
||||||
cfgOptionBool(cfgOptChecksumPage), strLstNewVarLst(cfgOptionLst(cfgOptExclude)), backupStartResult.tablespaceList);
|
cfgOptionBool(cfgOptChecksumPage), cfgOptionBool(cfgOptBundle), strLstNewVarLst(cfgOptionLst(cfgOptExclude)),
|
||||||
|
backupStartResult.tablespaceList);
|
||||||
|
|
||||||
// Validate the manifest using the copy start time
|
// Validate the manifest using the copy start time
|
||||||
manifestBuildValidate(
|
manifestBuildValidate(
|
||||||
|
@ -17,6 +17,7 @@ Backup File
|
|||||||
#include "common/regExp.h"
|
#include "common/regExp.h"
|
||||||
#include "common/type/convert.h"
|
#include "common/type/convert.h"
|
||||||
#include "common/type/json.h"
|
#include "common/type/json.h"
|
||||||
|
#include "info/manifest.h"
|
||||||
#include "postgres/interface.h"
|
#include "postgres/interface.h"
|
||||||
#include "storage/helper.h"
|
#include "storage/helper.h"
|
||||||
|
|
||||||
@ -35,232 +36,266 @@ segmentNumber(const String *pgFile)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
BackupFileResult
|
List *
|
||||||
backupFile(
|
backupFile(
|
||||||
const String *pgFile, bool pgFileIgnoreMissing, uint64_t pgFileSize, bool pgFileCopyExactSize, const String *pgFileChecksum,
|
const String *const repoFile, const CompressType repoFileCompressType, const int repoFileCompressLevel,
|
||||||
bool pgFileChecksumPage, uint64_t pgFileChecksumPageLsnLimit, const String *repoFile, bool repoFileHasReference,
|
const bool delta, const CipherType cipherType, const String *const cipherPass, const List *const fileList)
|
||||||
CompressType repoFileCompressType, int repoFileCompressLevel, const String *backupLabel, bool delta, CipherType cipherType,
|
|
||||||
const String *cipherPass)
|
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STRING, pgFile); // Database file to copy to the repo
|
FUNCTION_LOG_PARAM(STRING, repoFile); // Repo file
|
||||||
FUNCTION_LOG_PARAM(BOOL, pgFileIgnoreMissing); // Is it OK if the database file is missing?
|
|
||||||
FUNCTION_LOG_PARAM(UINT64, pgFileSize); // Size of the database file
|
|
||||||
FUNCTION_LOG_PARAM(BOOL, pgFileCopyExactSize); // Copy only pgFileSize bytes even if the file has grown
|
|
||||||
FUNCTION_LOG_PARAM(STRING, pgFileChecksum); // Checksum to verify the database file
|
|
||||||
FUNCTION_LOG_PARAM(BOOL, pgFileChecksumPage); // Should page checksums be validated
|
|
||||||
FUNCTION_LOG_PARAM(UINT64, pgFileChecksumPageLsnLimit); // Upper LSN limit to which page checksums must be valid
|
|
||||||
FUNCTION_LOG_PARAM(STRING, repoFile); // Destination in the repo to copy the pg file
|
|
||||||
FUNCTION_LOG_PARAM(BOOL, repoFileHasReference); // Does the repo file exist in a prior backup in the set?
|
|
||||||
FUNCTION_LOG_PARAM(ENUM, repoFileCompressType); // Compress type for repo file
|
FUNCTION_LOG_PARAM(ENUM, repoFileCompressType); // Compress type for repo file
|
||||||
FUNCTION_LOG_PARAM(INT, repoFileCompressLevel); // Compression level for repo file
|
FUNCTION_LOG_PARAM(INT, repoFileCompressLevel); // Compression level for repo file
|
||||||
FUNCTION_LOG_PARAM(STRING, backupLabel); // Label of current backup
|
|
||||||
FUNCTION_LOG_PARAM(BOOL, delta); // Is the delta option on?
|
FUNCTION_LOG_PARAM(BOOL, delta); // Is the delta option on?
|
||||||
FUNCTION_LOG_PARAM(STRING_ID, cipherType); // Encryption type
|
FUNCTION_LOG_PARAM(STRING_ID, cipherType); // Encryption type
|
||||||
FUNCTION_TEST_PARAM(STRING, cipherPass); // Password to access the repo file if encrypted
|
FUNCTION_TEST_PARAM(STRING, cipherPass); // Password to access the repo file if encrypted
|
||||||
|
FUNCTION_LOG_PARAM(LIST, fileList); // List of files to backup
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(pgFile != NULL);
|
|
||||||
ASSERT(repoFile != NULL);
|
ASSERT(repoFile != NULL);
|
||||||
ASSERT(backupLabel != NULL);
|
|
||||||
ASSERT((cipherType == cipherTypeNone && cipherPass == NULL) || (cipherType != cipherTypeNone && cipherPass != NULL));
|
ASSERT((cipherType == cipherTypeNone && cipherPass == NULL) || (cipherType != cipherTypeNone && cipherPass != NULL));
|
||||||
|
ASSERT(fileList != NULL && !lstEmpty(fileList));
|
||||||
|
|
||||||
// Backup file results
|
// Backup file results
|
||||||
BackupFileResult result = {.backupCopyResult = backupCopyResultCopy};
|
List *result = NULL;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
// Generate complete repo path and add compression extension if needed
|
result = lstNewP(sizeof(BackupFileResult));
|
||||||
const String *repoPathFile = strNewFmt(
|
|
||||||
STORAGE_REPO_BACKUP "/%s/%s%s", strZ(backupLabel), strZ(repoFile), strZ(compressExtStr(repoFileCompressType)));
|
|
||||||
|
|
||||||
// If checksum is defined then the file needs to be checked. If delta option then check the DB and possibly the repo, else
|
// Check files to determine which ones need to be copied
|
||||||
// just check the repo.
|
for (unsigned int fileIdx = 0; fileIdx < lstSize(fileList); fileIdx++)
|
||||||
if (pgFileChecksum != NULL)
|
|
||||||
{
|
{
|
||||||
// Does the file in pg match the checksum and size passed?
|
const BackupFile *const file = lstGet(fileList, fileIdx);
|
||||||
bool pgFileMatch = false;
|
ASSERT(file->pgFile != NULL);
|
||||||
|
ASSERT(file->manifestFile != NULL);
|
||||||
|
|
||||||
// If delta, then check the DB checksum and possibly the repo. If the checksum does not match in either case then
|
BackupFileResult *const fileResult = lstAdd(
|
||||||
// recopy.
|
result, &(BackupFileResult){.manifestFile = file->manifestFile, .backupCopyResult = backupCopyResultCopy});
|
||||||
if (delta)
|
|
||||||
|
// If checksum is defined then the file needs to be checked. If delta option then check the DB and possibly the repo,
|
||||||
|
// else just check the repo.
|
||||||
|
if (file->pgFileChecksum != NULL)
|
||||||
{
|
{
|
||||||
// Generate checksum/size for the pg file. Only read as many bytes as passed in pgFileSize. If the file has grown
|
// Does the file in pg match the checksum and size passed?
|
||||||
// since the manifest was built we don't need to consider the extra bytes since they will be replayed from WAL
|
bool pgFileMatch = false;
|
||||||
// during recovery.
|
|
||||||
IoRead *read = storageReadIo(
|
|
||||||
storageNewReadP(
|
|
||||||
storagePg(), pgFile, .ignoreMissing = pgFileIgnoreMissing,
|
|
||||||
.limit = pgFileCopyExactSize ? VARUINT64(pgFileSize) : NULL));
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), ioSizeNew());
|
|
||||||
|
|
||||||
// If the pg file exists check the checksum/size
|
// If delta, then check the DB checksum and possibly the repo. If the checksum does not match in either case then
|
||||||
if (ioReadDrain(read))
|
// recopy.
|
||||||
|
if (delta)
|
||||||
{
|
{
|
||||||
const String *pgTestChecksum = pckReadStrP(
|
// Generate checksum/size for the pg file. Only read as many bytes as passed in pgFileSize. If the file has
|
||||||
ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE));
|
// grown since the manifest was built we don't need to consider the extra bytes since they will be replayed from
|
||||||
uint64_t pgTestSize = pckReadU64P(ioFilterGroupResultP(ioReadFilterGroup(read), SIZE_FILTER_TYPE));
|
// WAL during recovery.
|
||||||
|
IoRead *read = storageReadIo(
|
||||||
|
storageNewReadP(
|
||||||
|
storagePg(), file->pgFile, .ignoreMissing = file->pgFileIgnoreMissing,
|
||||||
|
.limit = file->pgFileCopyExactSize ? VARUINT64(file->pgFileSize) : NULL));
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), ioSizeNew());
|
||||||
|
|
||||||
// Does the pg file match?
|
// If the pg file exists check the checksum/size
|
||||||
if (pgFileSize == pgTestSize && strEq(pgFileChecksum, pgTestChecksum))
|
if (ioReadDrain(read))
|
||||||
{
|
{
|
||||||
pgFileMatch = true;
|
|
||||||
|
|
||||||
// If it matches and is a reference to a previous backup then no need to copy the file
|
|
||||||
if (repoFileHasReference)
|
|
||||||
{
|
|
||||||
MEM_CONTEXT_PRIOR_BEGIN()
|
|
||||||
{
|
|
||||||
result.backupCopyResult = backupCopyResultNoOp;
|
|
||||||
result.copySize = pgTestSize;
|
|
||||||
result.copyChecksum = strDup(pgTestChecksum);
|
|
||||||
}
|
|
||||||
MEM_CONTEXT_PRIOR_END();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Else the source file is missing from the database so skip this file
|
|
||||||
else
|
|
||||||
result.backupCopyResult = backupCopyResultSkip;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is not a delta backup or it is and the file exists and the checksum from the DB matches, then also test the
|
|
||||||
// checksum of the file in the repo (unless it is in a prior backup) and if the checksum doesn't match, then there may
|
|
||||||
// be corruption in the repo, so recopy
|
|
||||||
if (!delta || !repoFileHasReference)
|
|
||||||
{
|
|
||||||
// If this is a delta backup and the file is missing from the DB, then remove it from the repo (backupManifestUpdate
|
|
||||||
// will remove it from the manifest)
|
|
||||||
if (result.backupCopyResult == backupCopyResultSkip)
|
|
||||||
{
|
|
||||||
storageRemoveP(storageRepoWrite(), repoPathFile);
|
|
||||||
}
|
|
||||||
else if (!delta || pgFileMatch)
|
|
||||||
{
|
|
||||||
// Check the repo file in a try block because on error (e.g. missing or corrupt file that can't be decrypted or
|
|
||||||
// decompressed) we should recopy rather than ending the backup.
|
|
||||||
TRY_BEGIN()
|
|
||||||
{
|
|
||||||
// Generate checksum/size for the repo file
|
|
||||||
IoRead *read = storageReadIo(storageNewReadP(storageRepo(), repoPathFile));
|
|
||||||
|
|
||||||
if (cipherType != cipherTypeNone)
|
|
||||||
{
|
|
||||||
ioFilterGroupAdd(
|
|
||||||
ioReadFilterGroup(read), cipherBlockNew(cipherModeDecrypt, cipherType, BUFSTR(cipherPass), NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress the file if compressed
|
|
||||||
if (repoFileCompressType != compressTypeNone)
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), decompressFilter(repoFileCompressType));
|
|
||||||
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(read), ioSizeNew());
|
|
||||||
|
|
||||||
ioReadDrain(read);
|
|
||||||
|
|
||||||
// Test checksum/size
|
|
||||||
const String *pgTestChecksum = pckReadStrP(
|
const String *pgTestChecksum = pckReadStrP(
|
||||||
ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE));
|
ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE));
|
||||||
uint64_t pgTestSize = pckReadU64P(ioFilterGroupResultP(ioReadFilterGroup(read), SIZE_FILTER_TYPE));
|
uint64_t pgTestSize = pckReadU64P(ioFilterGroupResultP(ioReadFilterGroup(read), SIZE_FILTER_TYPE));
|
||||||
|
|
||||||
// No need to recopy if checksum/size match
|
// Does the pg file match?
|
||||||
if (pgFileSize == pgTestSize && strEq(pgFileChecksum, pgTestChecksum))
|
if (file->pgFileSize == pgTestSize && strEq(file->pgFileChecksum, pgTestChecksum))
|
||||||
{
|
{
|
||||||
MEM_CONTEXT_PRIOR_BEGIN()
|
pgFileMatch = true;
|
||||||
|
|
||||||
|
// If it matches and is a reference to a previous backup then no need to copy the file
|
||||||
|
if (file->manifestFileHasReference)
|
||||||
{
|
{
|
||||||
result.backupCopyResult = backupCopyResultChecksum;
|
MEM_CONTEXT_BEGIN(lstMemContext(result))
|
||||||
result.copySize = pgTestSize;
|
{
|
||||||
result.copyChecksum = strDup(pgTestChecksum);
|
fileResult->backupCopyResult = backupCopyResultNoOp;
|
||||||
|
fileResult->copySize = pgTestSize;
|
||||||
|
fileResult->copyChecksum = strDup(pgTestChecksum);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_PRIOR_END();
|
|
||||||
}
|
}
|
||||||
// Else recopy when repo file is not as expected
|
|
||||||
else
|
|
||||||
result.backupCopyResult = backupCopyResultReCopy;
|
|
||||||
}
|
}
|
||||||
// Recopy on any kind of error
|
// Else the source file is missing from the database so skip this file
|
||||||
CATCH_ANY()
|
else
|
||||||
{
|
fileResult->backupCopyResult = backupCopyResultSkip;
|
||||||
result.backupCopyResult = backupCopyResultReCopy;
|
|
||||||
}
|
|
||||||
TRY_END();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the file
|
// If this is not a delta backup or it is and the file exists and the checksum from the DB matches, then also test
|
||||||
if (result.backupCopyResult == backupCopyResultCopy || result.backupCopyResult == backupCopyResultReCopy)
|
// the checksum of the file in the repo (unless it is in a prior backup) and if the checksum doesn't match, then
|
||||||
{
|
// there may be corruption in the repo, so recopy
|
||||||
// Is the file compressible during the copy?
|
if (!delta || !file->manifestFileHasReference)
|
||||||
bool compressible = repoFileCompressType == compressTypeNone && cipherType == cipherTypeNone;
|
|
||||||
|
|
||||||
// Setup pg file for read. Only read as many bytes as passed in pgFileSize. If the file is growing it does no good to
|
|
||||||
// copy data past the end of the size recorded in the manifest since those blocks will need to be replayed from WAL
|
|
||||||
// during recovery.
|
|
||||||
StorageRead *read = storageNewReadP(
|
|
||||||
storagePg(), pgFile, .ignoreMissing = pgFileIgnoreMissing, .compressible = compressible,
|
|
||||||
.limit = pgFileCopyExactSize ? VARUINT64(pgFileSize) : NULL);
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), ioSizeNew());
|
|
||||||
|
|
||||||
// Add page checksum filter
|
|
||||||
if (pgFileChecksumPage)
|
|
||||||
{
|
|
||||||
ioFilterGroupAdd(
|
|
||||||
ioReadFilterGroup(storageReadIo(read)), pageChecksumNew(segmentNumber(pgFile), PG_SEGMENT_PAGE_DEFAULT,
|
|
||||||
pgFileChecksumPageLsnLimit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add compression
|
|
||||||
if (repoFileCompressType != compressTypeNone)
|
|
||||||
{
|
|
||||||
ioFilterGroupAdd(
|
|
||||||
ioReadFilterGroup(storageReadIo(read)), compressFilter(repoFileCompressType, repoFileCompressLevel));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a cipher then add the encrypt filter
|
|
||||||
if (cipherType != cipherTypeNone)
|
|
||||||
{
|
|
||||||
ioFilterGroupAdd(
|
|
||||||
ioReadFilterGroup(
|
|
||||||
storageReadIo(read)), cipherBlockNew(cipherModeEncrypt, cipherType, BUFSTR(cipherPass), NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the repo file for write. There is no need to write the file atomically (e.g. via a temp file on Posix) because
|
|
||||||
// checksums are tested on resume after a failed backup. The path does not need to be synced for each file because all
|
|
||||||
// paths are synced at the end of the backup.
|
|
||||||
StorageWrite *write = storageNewWriteP(
|
|
||||||
storageRepoWrite(), repoPathFile, .compressible = compressible, .noAtomic = true, .noSyncPath = true);
|
|
||||||
ioFilterGroupAdd(ioWriteFilterGroup(storageWriteIo(write)), ioSizeNew());
|
|
||||||
|
|
||||||
// Open the source and destination and copy the file
|
|
||||||
if (storageCopy(read, write))
|
|
||||||
{
|
|
||||||
MEM_CONTEXT_PRIOR_BEGIN()
|
|
||||||
{
|
{
|
||||||
// Get sizes and checksum
|
// If this is a delta backup and the file is missing from the DB, then remove it from the repo
|
||||||
result.copySize = pckReadU64P(
|
// (backupManifestUpdate will remove it from the manifest)
|
||||||
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), SIZE_FILTER_TYPE));
|
if (fileResult->backupCopyResult == backupCopyResultSkip)
|
||||||
result.copyChecksum = strDup(
|
|
||||||
pckReadStrP(ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE)));
|
|
||||||
result.repoSize =
|
|
||||||
pckReadU64P(ioFilterGroupResultP(ioWriteFilterGroup(storageWriteIo(write)), SIZE_FILTER_TYPE));
|
|
||||||
|
|
||||||
// Get results of page checksum validation
|
|
||||||
if (pgFileChecksumPage)
|
|
||||||
{
|
{
|
||||||
result.pageChecksumResult = pckDup(
|
storageRemoveP(storageRepoWrite(), repoFile);
|
||||||
ioFilterGroupResultPackP(ioReadFilterGroup(storageReadIo(read)), PAGE_CHECKSUM_FILTER_TYPE));
|
}
|
||||||
|
else if (!delta || pgFileMatch)
|
||||||
|
{
|
||||||
|
// Check the repo file in a try block because on error (e.g. missing or corrupt file that can't be decrypted
|
||||||
|
// or decompressed) we should recopy rather than ending the backup.
|
||||||
|
TRY_BEGIN()
|
||||||
|
{
|
||||||
|
// Generate checksum/size for the repo file
|
||||||
|
IoRead *read = storageReadIo(storageNewReadP(storageRepo(), repoFile));
|
||||||
|
|
||||||
|
if (cipherType != cipherTypeNone)
|
||||||
|
{
|
||||||
|
ioFilterGroupAdd(
|
||||||
|
ioReadFilterGroup(read),
|
||||||
|
cipherBlockNew(cipherModeDecrypt, cipherType, BUFSTR(cipherPass), NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress the file if compressed
|
||||||
|
if (repoFileCompressType != compressTypeNone)
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), decompressFilter(repoFileCompressType));
|
||||||
|
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(read), ioSizeNew());
|
||||||
|
|
||||||
|
ioReadDrain(read);
|
||||||
|
|
||||||
|
// Test checksum/size
|
||||||
|
const String *pgTestChecksum = pckReadStrP(
|
||||||
|
ioFilterGroupResultP(ioReadFilterGroup(read), CRYPTO_HASH_FILTER_TYPE));
|
||||||
|
uint64_t pgTestSize = pckReadU64P(ioFilterGroupResultP(ioReadFilterGroup(read), SIZE_FILTER_TYPE));
|
||||||
|
|
||||||
|
// No need to recopy if checksum/size match
|
||||||
|
if (file->pgFileSize == pgTestSize && strEq(file->pgFileChecksum, pgTestChecksum))
|
||||||
|
{
|
||||||
|
MEM_CONTEXT_BEGIN(lstMemContext(result))
|
||||||
|
{
|
||||||
|
fileResult->backupCopyResult = backupCopyResultChecksum;
|
||||||
|
fileResult->copySize = pgTestSize;
|
||||||
|
fileResult->copyChecksum = strDup(pgTestChecksum);
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
|
}
|
||||||
|
// Else recopy when repo file is not as expected
|
||||||
|
else
|
||||||
|
fileResult->backupCopyResult = backupCopyResultReCopy;
|
||||||
|
}
|
||||||
|
// Recopy on any kind of error
|
||||||
|
CATCH_ANY()
|
||||||
|
{
|
||||||
|
fileResult->backupCopyResult = backupCopyResultReCopy;
|
||||||
|
}
|
||||||
|
TRY_END();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_PRIOR_END();
|
|
||||||
}
|
}
|
||||||
// Else if source file is missing and the read setup indicated ignore a missing file, the database removed it so skip it
|
|
||||||
else
|
|
||||||
result.backupCopyResult = backupCopyResultSkip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Are the file compressible during the copy?
|
||||||
|
const bool compressible = repoFileCompressType == compressTypeNone && cipherType == cipherTypeNone;
|
||||||
|
|
||||||
|
// Copy files that need to be copied
|
||||||
|
StorageWrite *write = NULL;
|
||||||
|
uint64_t bundleOffset = 0;
|
||||||
|
|
||||||
|
for (unsigned int fileIdx = 0; fileIdx < lstSize(fileList); fileIdx++)
|
||||||
|
{
|
||||||
|
const BackupFile *const file = lstGet(fileList, fileIdx);
|
||||||
|
BackupFileResult *const fileResult = lstGet(result, fileIdx);
|
||||||
|
|
||||||
|
if (fileResult->backupCopyResult == backupCopyResultCopy || fileResult->backupCopyResult == backupCopyResultReCopy)
|
||||||
|
{
|
||||||
|
// Setup pg file for read. Only read as many bytes as passed in pgFileSize. If the file is growing it does no good
|
||||||
|
// to copy data past the end of the size recorded in the manifest since those blocks will need to be replayed from
|
||||||
|
// WAL during recovery.
|
||||||
|
StorageRead *read = storageNewReadP(
|
||||||
|
storagePg(), file->pgFile, .ignoreMissing = file->pgFileIgnoreMissing, .compressible = compressible,
|
||||||
|
.limit = file->pgFileCopyExactSize ? VARUINT64(file->pgFileSize) : NULL);
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), ioSizeNew());
|
||||||
|
|
||||||
|
// Add page checksum filter
|
||||||
|
if (file->pgFileChecksumPage)
|
||||||
|
{
|
||||||
|
ioFilterGroupAdd(
|
||||||
|
ioReadFilterGroup(storageReadIo(read)),
|
||||||
|
pageChecksumNew(segmentNumber(file->pgFile), PG_SEGMENT_PAGE_DEFAULT, file->pgFileChecksumPageLsnLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add compression
|
||||||
|
if (repoFileCompressType != compressTypeNone)
|
||||||
|
{
|
||||||
|
ioFilterGroupAdd(
|
||||||
|
ioReadFilterGroup(storageReadIo(read)), compressFilter(repoFileCompressType, repoFileCompressLevel));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a cipher then add the encrypt filter
|
||||||
|
if (cipherType != cipherTypeNone)
|
||||||
|
{
|
||||||
|
ioFilterGroupAdd(
|
||||||
|
ioReadFilterGroup(storageReadIo(read)),
|
||||||
|
cipherBlockNew(cipherModeEncrypt, cipherType, BUFSTR(cipherPass), NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add size filter last to calculate repo size
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), ioSizeNew());
|
||||||
|
|
||||||
|
// Open the source and destination and copy the file
|
||||||
|
if (ioReadOpen(storageReadIo(read)))
|
||||||
|
{
|
||||||
|
if (write == NULL)
|
||||||
|
{
|
||||||
|
// Setup the repo file for write. There is no need to write the file atomically (e.g. via a temp file on
|
||||||
|
// Posix) because checksums are tested on resume after a failed backup. The path does not need to be synced
|
||||||
|
// for each file because all paths are synced at the end of the backup.
|
||||||
|
write = storageNewWriteP(
|
||||||
|
storageRepoWrite(), repoFile, .compressible = compressible, .noAtomic = true, .noSyncPath = true);
|
||||||
|
ioWriteOpen(storageWriteIo(write));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data from source to destination
|
||||||
|
ioCopy(storageReadIo(read), storageWriteIo(write));
|
||||||
|
|
||||||
|
// Close the source
|
||||||
|
ioReadClose(storageReadIo(read));
|
||||||
|
|
||||||
|
MEM_CONTEXT_BEGIN(lstMemContext(result))
|
||||||
|
{
|
||||||
|
// Get sizes and checksum
|
||||||
|
fileResult->copySize = pckReadU64P(
|
||||||
|
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), SIZE_FILTER_TYPE, .idx = 0));
|
||||||
|
fileResult->bundleOffset = bundleOffset;
|
||||||
|
fileResult->copyChecksum = strDup(
|
||||||
|
pckReadStrP(ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE)));
|
||||||
|
fileResult->repoSize = pckReadU64P(
|
||||||
|
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), SIZE_FILTER_TYPE, .idx = 1));
|
||||||
|
|
||||||
|
// Get results of page checksum validation
|
||||||
|
if (file->pgFileChecksumPage)
|
||||||
|
{
|
||||||
|
fileResult->pageChecksumResult = pckDup(
|
||||||
|
ioFilterGroupResultPackP(ioReadFilterGroup(storageReadIo(read)), PAGE_CHECKSUM_FILTER_TYPE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MEM_CONTEXT_END();
|
||||||
|
|
||||||
|
// Free the read object. This is very important if many files are being read because they can each contain a lot
|
||||||
|
// of buffers.
|
||||||
|
storageReadFree(read);
|
||||||
|
|
||||||
|
bundleOffset += fileResult->repoSize;
|
||||||
|
}
|
||||||
|
// Else if source file is missing and the read setup indicated ignore a missing file, the database removed it so
|
||||||
|
// skip it
|
||||||
|
else
|
||||||
|
fileResult->backupCopyResult = backupCopyResultSkip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the repository file if it was opened
|
||||||
|
if (write != NULL)
|
||||||
|
ioWriteClose(storageWriteIo(write));
|
||||||
|
|
||||||
|
lstMove(result, memContextPrior());
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
|
||||||
|
@ -23,20 +23,34 @@ typedef enum
|
|||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Functions
|
Functions
|
||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
|
// Copy a file from the archive to the specified destination
|
||||||
|
typedef struct BackupFile
|
||||||
|
{
|
||||||
|
const String *pgFile; // Pg file to backup
|
||||||
|
bool pgFileIgnoreMissing; // Ignore missing pg file
|
||||||
|
uint64_t pgFileSize; // Expected pg file size
|
||||||
|
bool pgFileCopyExactSize; // Copy only pg expected size
|
||||||
|
const String *pgFileChecksum; // Expected pg file checksum
|
||||||
|
bool pgFileChecksumPage; // Validate page checksums?
|
||||||
|
uint64_t pgFileChecksumPageLsnLimit; // Upper limit of pages to validate
|
||||||
|
const String *manifestFile; // Repo file
|
||||||
|
bool manifestFileHasReference; // Reference to prior backup, if any
|
||||||
|
} BackupFile;
|
||||||
|
|
||||||
// Copy a file from the PostgreSQL data directory to the repository
|
// Copy a file from the PostgreSQL data directory to the repository
|
||||||
typedef struct BackupFileResult
|
typedef struct BackupFileResult
|
||||||
{
|
{
|
||||||
|
const String *manifestFile; // Manifest file
|
||||||
BackupCopyResult backupCopyResult;
|
BackupCopyResult backupCopyResult;
|
||||||
uint64_t copySize;
|
uint64_t copySize;
|
||||||
String *copyChecksum;
|
String *copyChecksum;
|
||||||
|
uint64_t bundleOffset; // Offset in bundle if any
|
||||||
uint64_t repoSize;
|
uint64_t repoSize;
|
||||||
Pack *pageChecksumResult;
|
Pack *pageChecksumResult;
|
||||||
} BackupFileResult;
|
} BackupFileResult;
|
||||||
|
|
||||||
BackupFileResult backupFile(
|
List *backupFile(
|
||||||
const String *pgFile, bool pgFileIgnoreMissing, uint64_t pgFileSize, bool pgFileCopyExactSize, const String *pgFileChecksum,
|
const String *repoFile, CompressType repoFileCompressType, int repoFileCompressLevel, bool delta, CipherType cipherType,
|
||||||
bool pgFileChecksumPage, uint64_t pgFileChecksumPageLsnLimit, const String *repoFile, bool repoFileHasReference,
|
const String *cipherPass, const List *fileList);
|
||||||
CompressType repoFileCompressType, int repoFileCompressLevel, const String *backupLabel, bool delta, CipherType cipherType,
|
|
||||||
const String *cipherPass);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -27,35 +27,51 @@ backupFileProtocol(PackRead *const param, ProtocolServer *const server)
|
|||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
// Backup file
|
// Backup options that apply to all files
|
||||||
const String *const pgFile = pckReadStrP(param);
|
|
||||||
const bool pgFileIgnoreMissing = pckReadBoolP(param);
|
|
||||||
const uint64_t pgFileSize = pckReadU64P(param);
|
|
||||||
const bool pgFileCopyExactSize = pckReadBoolP(param);
|
|
||||||
const String *const pgFileChecksum = pckReadStrP(param);
|
|
||||||
const bool pgFileChecksumPage = pckReadBoolP(param);
|
|
||||||
const uint64_t pgFileChecksumPageLsnLimit = pckReadU64P(param);
|
|
||||||
const String *const repoFile = pckReadStrP(param);
|
const String *const repoFile = pckReadStrP(param);
|
||||||
const bool repoFileHasReference = pckReadBoolP(param);
|
|
||||||
const CompressType repoFileCompressType = (CompressType)pckReadU32P(param);
|
const CompressType repoFileCompressType = (CompressType)pckReadU32P(param);
|
||||||
const int repoFileCompressLevel = pckReadI32P(param);
|
const int repoFileCompressLevel = pckReadI32P(param);
|
||||||
const String *const backupLabel = pckReadStrP(param);
|
|
||||||
const bool delta = pckReadBoolP(param);
|
const bool delta = pckReadBoolP(param);
|
||||||
const CipherType cipherType = (CipherType)pckReadU64P(param);
|
const CipherType cipherType = (CipherType)pckReadU64P(param);
|
||||||
const String *const cipherPass = pckReadStrP(param);
|
const String *const cipherPass = pckReadStrP(param);
|
||||||
|
|
||||||
const BackupFileResult result = backupFile(
|
// Build the file list
|
||||||
pgFile, pgFileIgnoreMissing, pgFileSize, pgFileCopyExactSize, pgFileChecksum, pgFileChecksumPage,
|
List *fileList = lstNewP(sizeof(BackupFile));
|
||||||
pgFileChecksumPageLsnLimit, repoFile, repoFileHasReference, repoFileCompressType, repoFileCompressLevel,
|
|
||||||
backupLabel, delta, cipherType, cipherPass);
|
while (!pckReadNullP(param))
|
||||||
|
{
|
||||||
|
BackupFile file = {.pgFile = pckReadStrP(param)};
|
||||||
|
file.pgFileIgnoreMissing = pckReadBoolP(param);
|
||||||
|
file.pgFileSize = pckReadU64P(param);
|
||||||
|
file.pgFileCopyExactSize = pckReadBoolP(param);
|
||||||
|
file.pgFileChecksum = pckReadStrP(param);
|
||||||
|
file.pgFileChecksumPage = pckReadBoolP(param);
|
||||||
|
file.pgFileChecksumPageLsnLimit = pckReadU64P(param);
|
||||||
|
file.manifestFile = pckReadStrP(param);
|
||||||
|
file.manifestFileHasReference = pckReadBoolP(param);
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup file
|
||||||
|
const List *const result = backupFile(
|
||||||
|
repoFile, repoFileCompressType, repoFileCompressLevel, delta, cipherType, cipherPass, fileList);
|
||||||
|
|
||||||
// Return result
|
// Return result
|
||||||
PackWrite *const resultPack = protocolPackNew();
|
PackWrite *const resultPack = protocolPackNew();
|
||||||
pckWriteU32P(resultPack, result.backupCopyResult);
|
|
||||||
pckWriteU64P(resultPack, result.copySize);
|
for (unsigned int resultIdx = 0; resultIdx < lstSize(result); resultIdx++)
|
||||||
pckWriteU64P(resultPack, result.repoSize);
|
{
|
||||||
pckWriteStrP(resultPack, result.copyChecksum);
|
const BackupFileResult *const fileResult = lstGet(result, resultIdx);
|
||||||
pckWritePackP(resultPack, result.pageChecksumResult);
|
|
||||||
|
pckWriteStrP(resultPack, fileResult->manifestFile);
|
||||||
|
pckWriteU32P(resultPack, fileResult->backupCopyResult);
|
||||||
|
pckWriteU64P(resultPack, fileResult->copySize);
|
||||||
|
pckWriteU64P(resultPack, fileResult->bundleOffset);
|
||||||
|
pckWriteU64P(resultPack, fileResult->repoSize);
|
||||||
|
pckWriteStrP(resultPack, fileResult->copyChecksum);
|
||||||
|
pckWritePackP(resultPack, fileResult->pageChecksumResult);
|
||||||
|
}
|
||||||
|
|
||||||
protocolServerDataPut(server, resultPack);
|
protocolServerDataPut(server, resultPack);
|
||||||
protocolServerDataEndPut(server);
|
protocolServerDataEndPut(server);
|
||||||
|
@ -16,20 +16,23 @@ Restore File
|
|||||||
#include "common/io/io.h"
|
#include "common/io/io.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
|
#include "info/manifest.h"
|
||||||
#include "storage/helper.h"
|
#include "storage/helper.h"
|
||||||
|
|
||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
bool
|
bool
|
||||||
restoreFile(
|
restoreFile(
|
||||||
const String *repoFile, unsigned int repoIdx, const String *repoFileReference, CompressType repoFileCompressType,
|
const String *const repoFile, unsigned int repoIdx, const uint64_t offset, const Variant *const limit,
|
||||||
const String *pgFile, const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified,
|
const CompressType repoFileCompressType, const String *const pgFile, const String *const pgFileChecksum, const bool pgFileZero,
|
||||||
mode_t pgFileMode, const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
|
const uint64_t pgFileSize, const time_t pgFileModified, const mode_t pgFileMode, const String *const pgFileUser,
|
||||||
const String *cipherPass)
|
const String *const pgFileGroup, const time_t copyTimeBegin, const bool delta, const bool deltaForce,
|
||||||
|
const String *const cipherPass)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STRING, repoFile);
|
FUNCTION_LOG_PARAM(STRING, repoFile);
|
||||||
FUNCTION_LOG_PARAM(UINT, repoIdx);
|
FUNCTION_LOG_PARAM(UINT, repoIdx);
|
||||||
FUNCTION_LOG_PARAM(STRING, repoFileReference);
|
FUNCTION_LOG_PARAM(UINT64, offset);
|
||||||
|
FUNCTION_LOG_PARAM(VARIANT, limit);
|
||||||
FUNCTION_LOG_PARAM(ENUM, repoFileCompressType);
|
FUNCTION_LOG_PARAM(ENUM, repoFileCompressType);
|
||||||
FUNCTION_LOG_PARAM(STRING, pgFile);
|
FUNCTION_LOG_PARAM(STRING, pgFile);
|
||||||
FUNCTION_LOG_PARAM(STRING, pgFileChecksum);
|
FUNCTION_LOG_PARAM(STRING, pgFileChecksum);
|
||||||
@ -46,8 +49,8 @@ restoreFile(
|
|||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
|
|
||||||
ASSERT(repoFile != NULL);
|
ASSERT(repoFile != NULL);
|
||||||
ASSERT(repoFileReference != NULL);
|
|
||||||
ASSERT(pgFile != NULL);
|
ASSERT(pgFile != NULL);
|
||||||
|
ASSERT(limit == NULL || varType(limit) == varTypeUInt64);
|
||||||
|
|
||||||
// Was the file copied?
|
// Was the file copied?
|
||||||
bool result = true;
|
bool result = true;
|
||||||
@ -166,11 +169,7 @@ restoreFile(
|
|||||||
// Copy file
|
// Copy file
|
||||||
storageCopyP(
|
storageCopyP(
|
||||||
storageNewReadP(
|
storageNewReadP(
|
||||||
storageRepoIdx(repoIdx),
|
storageRepoIdx(repoIdx), repoFile, .compressible = compressible, .offset = offset, .limit = limit),
|
||||||
strNewFmt(
|
|
||||||
STORAGE_REPO_BACKUP "/%s/%s%s", strZ(repoFileReference), strZ(repoFile),
|
|
||||||
strZ(compressExtStr(repoFileCompressType))),
|
|
||||||
.compressible = compressible),
|
|
||||||
pgFileWrite);
|
pgFileWrite);
|
||||||
|
|
||||||
// Validate checksum
|
// Validate checksum
|
||||||
|
@ -14,7 +14,7 @@ Functions
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
// Copy a file from the backup to the specified destination
|
// Copy a file from the backup to the specified destination
|
||||||
bool restoreFile(
|
bool restoreFile(
|
||||||
const String *repoFile, unsigned int repoIdx, const String *repoFileReference, CompressType repoFileCompressType,
|
const String *repoFile, unsigned int repoIdx, uint64_t offset, const Variant *limit, CompressType repoFileCompressType,
|
||||||
const String *pgFile, const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified,
|
const String *pgFile, const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified,
|
||||||
mode_t pgFileMode, const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
|
mode_t pgFileMode, const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
|
||||||
const String *cipherPass);
|
const String *cipherPass);
|
||||||
|
@ -28,8 +28,17 @@ restoreFileProtocol(PackRead *const param, ProtocolServer *const server)
|
|||||||
{
|
{
|
||||||
// Restore file
|
// Restore file
|
||||||
const String *const repoFile = pckReadStrP(param);
|
const String *const repoFile = pckReadStrP(param);
|
||||||
|
|
||||||
|
uint64_t offset = 0;
|
||||||
|
const Variant *limit = NULL;
|
||||||
|
|
||||||
|
if (pckReadBoolP(param))
|
||||||
|
{
|
||||||
|
offset = pckReadU64P(param);
|
||||||
|
limit = varNewUInt64(pckReadU64P(param));
|
||||||
|
}
|
||||||
|
|
||||||
const unsigned int repoIdx = pckReadU32P(param);
|
const unsigned int repoIdx = pckReadU32P(param);
|
||||||
const String *const repoFileReference = pckReadStrP(param);
|
|
||||||
const CompressType repoFileCompressType = (CompressType)pckReadU32P(param);
|
const CompressType repoFileCompressType = (CompressType)pckReadU32P(param);
|
||||||
const String *const pgFile = pckReadStrP(param);
|
const String *const pgFile = pckReadStrP(param);
|
||||||
const String *const pgFileChecksum = pckReadStrP(param);
|
const String *const pgFileChecksum = pckReadStrP(param);
|
||||||
@ -45,8 +54,8 @@ restoreFileProtocol(PackRead *const param, ProtocolServer *const server)
|
|||||||
const String *const cipherPass = pckReadStrP(param);
|
const String *const cipherPass = pckReadStrP(param);
|
||||||
|
|
||||||
const bool result = restoreFile(
|
const bool result = restoreFile(
|
||||||
repoFile, repoIdx, repoFileReference, repoFileCompressType, pgFile, pgFileChecksum, pgFileZero, pgFileSize,
|
repoFile, repoIdx, offset, limit, repoFileCompressType, pgFile, pgFileChecksum, pgFileZero, pgFileSize, pgFileModified,
|
||||||
pgFileModified, pgFileMode, pgFileUser, pgFileGroup, copyTimeBegin, delta, deltaForce, cipherPass);
|
pgFileMode, pgFileUser, pgFileGroup, copyTimeBegin, delta, deltaForce, cipherPass);
|
||||||
|
|
||||||
// Return result
|
// Return result
|
||||||
protocolServerDataPut(server, pckWriteBoolP(protocolPackNew(), result));
|
protocolServerDataPut(server, pckWriteBoolP(protocolPackNew(), result));
|
||||||
|
@ -2194,9 +2194,28 @@ static ProtocolParallelJob *restoreJobCallback(void *data, unsigned int clientId
|
|||||||
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_RESTORE_FILE);
|
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_RESTORE_FILE);
|
||||||
PackWrite *const param = protocolCommandParam(command);
|
PackWrite *const param = protocolCommandParam(command);
|
||||||
|
|
||||||
pckWriteStrP(param, file.name);
|
const String *const repoPath = strNewFmt(
|
||||||
|
STORAGE_REPO_BACKUP "/%s/",
|
||||||
|
strZ(file.reference != NULL ? file.reference : manifestData(jobData->manifest)->backupLabel));
|
||||||
|
|
||||||
|
if (file.bundleId != 0)
|
||||||
|
{
|
||||||
|
pckWriteStrP(param, strNewFmt("%s" MANIFEST_PATH_BUNDLE "/%" PRIu64, strZ(repoPath), file.bundleId));
|
||||||
|
pckWriteBoolP(param, true);
|
||||||
|
pckWriteU64P(param, file.bundleOffset);
|
||||||
|
pckWriteU64P(param, file.sizeRepo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pckWriteStrP(
|
||||||
|
param,
|
||||||
|
strNewFmt(
|
||||||
|
"%s%s%s", strZ(repoPath), strZ(file.name),
|
||||||
|
strZ(compressExtStr(manifestData(jobData->manifest)->backupOptionCompressType))));
|
||||||
|
pckWriteBoolP(param, false);
|
||||||
|
}
|
||||||
|
|
||||||
pckWriteU32P(param, jobData->repoIdx);
|
pckWriteU32P(param, jobData->repoIdx);
|
||||||
pckWriteStrP(param, file.reference != NULL ? file.reference : manifestData(jobData->manifest)->backupLabel);
|
|
||||||
pckWriteU32P(param, manifestData(jobData->manifest)->backupOptionCompressType);
|
pckWriteU32P(param, manifestData(jobData->manifest)->backupOptionCompressType);
|
||||||
pckWriteStrP(param, restoreFilePgPath(jobData->manifest, file.name));
|
pckWriteStrP(param, restoreFilePgPath(jobData->manifest, file.name));
|
||||||
pckWriteStrP(param, STR(file.checksumSha1));
|
pckWriteStrP(param, STR(file.checksumSha1));
|
||||||
|
@ -17,10 +17,14 @@ Verify File
|
|||||||
/**********************************************************************************************************************************/
|
/**********************************************************************************************************************************/
|
||||||
VerifyResult
|
VerifyResult
|
||||||
verifyFile(
|
verifyFile(
|
||||||
const String *filePathName, const String *fileChecksum, uint64_t fileSize, const String *cipherPass)
|
const String *const filePathName, const uint64_t offset, const Variant *const limit, const CompressType compressType,
|
||||||
|
const String *const fileChecksum, const uint64_t fileSize, const String *const cipherPass)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STRING, filePathName); // Fully qualified file name
|
FUNCTION_LOG_PARAM(STRING, filePathName); // Fully qualified file name
|
||||||
|
FUNCTION_LOG_PARAM(UINT64, offset); // Offset to read in file
|
||||||
|
FUNCTION_LOG_PARAM(VARIANT, limit); // Limit to read from file
|
||||||
|
FUNCTION_LOG_PARAM(ENUM, compressType); // Compression type
|
||||||
FUNCTION_LOG_PARAM(STRING, fileChecksum); // Checksum for the file
|
FUNCTION_LOG_PARAM(STRING, fileChecksum); // Checksum for the file
|
||||||
FUNCTION_LOG_PARAM(UINT64, fileSize); // Size of file
|
FUNCTION_LOG_PARAM(UINT64, fileSize); // Size of file
|
||||||
FUNCTION_TEST_PARAM(STRING, cipherPass); // Password to access the repo file if encrypted
|
FUNCTION_TEST_PARAM(STRING, cipherPass); // Password to access the repo file if encrypted
|
||||||
@ -28,6 +32,7 @@ verifyFile(
|
|||||||
|
|
||||||
ASSERT(filePathName != NULL);
|
ASSERT(filePathName != NULL);
|
||||||
ASSERT(fileChecksum != NULL);
|
ASSERT(fileChecksum != NULL);
|
||||||
|
ASSERT(limit == NULL || varType(limit) == varTypeUInt64);
|
||||||
|
|
||||||
// Is the file valid?
|
// Is the file valid?
|
||||||
VerifyResult result = verifyOk;
|
VerifyResult result = verifyOk;
|
||||||
@ -35,7 +40,8 @@ verifyFile(
|
|||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
// Prepare the file for reading
|
// Prepare the file for reading
|
||||||
IoRead *read = storageReadIo(storageNewReadP(storageRepo(), filePathName, .ignoreMissing = true));
|
IoRead *read = storageReadIo(
|
||||||
|
storageNewReadP(storageRepo(), filePathName, .ignoreMissing = true, .offset = offset, .limit = limit));
|
||||||
IoFilterGroup *filterGroup = ioReadFilterGroup(read);
|
IoFilterGroup *filterGroup = ioReadFilterGroup(read);
|
||||||
|
|
||||||
// Add decryption filter
|
// Add decryption filter
|
||||||
@ -43,8 +49,8 @@ verifyFile(
|
|||||||
ioFilterGroupAdd(filterGroup, cipherBlockNew(cipherModeDecrypt, cipherTypeAes256Cbc, BUFSTR(cipherPass), NULL));
|
ioFilterGroupAdd(filterGroup, cipherBlockNew(cipherModeDecrypt, cipherTypeAes256Cbc, BUFSTR(cipherPass), NULL));
|
||||||
|
|
||||||
// Add decompression filter
|
// Add decompression filter
|
||||||
if (compressTypeFromName(filePathName) != compressTypeNone)
|
if (compressType != compressTypeNone)
|
||||||
ioFilterGroupAdd(filterGroup, decompressFilter(compressTypeFromName(filePathName)));
|
ioFilterGroupAdd(filterGroup, decompressFilter(compressType));
|
||||||
|
|
||||||
// Add sha1 filter
|
// Add sha1 filter
|
||||||
ioFilterGroupAdd(filterGroup, cryptoHashNew(HASH_TYPE_SHA1_STR));
|
ioFilterGroupAdd(filterGroup, cryptoHashNew(HASH_TYPE_SHA1_STR));
|
||||||
|
@ -24,6 +24,7 @@ Functions
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
// Verify a file in the pgBackRest repository
|
// Verify a file in the pgBackRest repository
|
||||||
VerifyResult verifyFile(
|
VerifyResult verifyFile(
|
||||||
const String *filePathName, const String *fileChecksum, uint64_t fileSize, const String *cipherPass);
|
const String *filePathName, uint64_t offset, const Variant *limit, CompressType compressType, const String *fileChecksum,
|
||||||
|
uint64_t fileSize, const String *cipherPass);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -28,11 +28,22 @@ verifyFileProtocol(PackRead *const param, ProtocolServer *const server)
|
|||||||
{
|
{
|
||||||
// Verify file
|
// Verify file
|
||||||
const String *const filePathName = pckReadStrP(param);
|
const String *const filePathName = pckReadStrP(param);
|
||||||
|
|
||||||
|
uint64_t offset = 0;
|
||||||
|
const Variant *limit = NULL;
|
||||||
|
|
||||||
|
if (pckReadBoolP(param))
|
||||||
|
{
|
||||||
|
offset = pckReadU64P(param);
|
||||||
|
limit = varNewUInt64(pckReadU64P(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompressType compressType = (CompressType)pckReadU32P(param);
|
||||||
const String *const fileChecksum = pckReadStrP(param);
|
const String *const fileChecksum = pckReadStrP(param);
|
||||||
const uint64_t fileSize = pckReadU64P(param);
|
const uint64_t fileSize = pckReadU64P(param);
|
||||||
const String *const cipherPass = pckReadStrP(param);
|
const String *const cipherPass = pckReadStrP(param);
|
||||||
|
|
||||||
const VerifyResult result = verifyFile(filePathName, fileChecksum, fileSize, cipherPass);
|
const VerifyResult result = verifyFile(filePathName, offset, limit, compressType, fileChecksum, fileSize, cipherPass);
|
||||||
|
|
||||||
// Return result
|
// Return result
|
||||||
protocolServerDataPut(server, pckWriteU32P(protocolPackNew(), result));
|
protocolServerDataPut(server, pckWriteU32P(protocolPackNew(), result));
|
||||||
|
@ -757,6 +757,8 @@ verifyArchive(void *data)
|
|||||||
PackWrite *const param = protocolCommandParam(command);
|
PackWrite *const param = protocolCommandParam(command);
|
||||||
|
|
||||||
pckWriteStrP(param, filePathName);
|
pckWriteStrP(param, filePathName);
|
||||||
|
pckWriteBoolP(param, false);
|
||||||
|
pckWriteU32P(param, compressTypeFromName(filePathName));
|
||||||
pckWriteStrP(param, checksum);
|
pckWriteStrP(param, checksum);
|
||||||
pckWriteU64P(param, archiveResult->pgWalInfo.size);
|
pckWriteU64P(param, archiveResult->pgWalInfo.size);
|
||||||
pckWriteStrP(param, jobData->walCipherPass);
|
pckWriteStrP(param, jobData->walCipherPass);
|
||||||
@ -907,81 +909,106 @@ verifyBackup(void *data)
|
|||||||
// Track the files verified in order to determine when the processing of the backup is complete
|
// Track the files verified in order to determine when the processing of the backup is complete
|
||||||
backupResult->totalFileVerify++;
|
backupResult->totalFileVerify++;
|
||||||
|
|
||||||
// Check if the file is referenced in a prior backup
|
// Check the file if it is not zero-length or not bundled
|
||||||
const String *fileBackupLabel = NULL;
|
if (fileData.size != 0 || !manifestData(jobData->manifest)->bundle)
|
||||||
|
|
||||||
if (fileData.reference != NULL)
|
|
||||||
{
|
{
|
||||||
// If the prior backup is not in the result list, then that backup was never processed (likely due to the --set
|
// Check if the file is referenced in a prior backup
|
||||||
// option) so verify the file
|
const String *fileBackupLabel = NULL;
|
||||||
unsigned int backupPriorIdx = lstFindIdx(jobData->backupResultList, &fileData.reference);
|
|
||||||
|
|
||||||
if (backupPriorIdx == LIST_NOT_FOUND)
|
if (fileData.reference != NULL)
|
||||||
{
|
{
|
||||||
fileBackupLabel = fileData.reference;
|
// If the prior backup is not in the result list, then that backup was never processed (likely due to the
|
||||||
}
|
// --set option) so verify the file
|
||||||
// Else the backup this file references has a result so check the processing state for the referenced backup
|
unsigned int backupPriorIdx = lstFindIdx(jobData->backupResultList, &fileData.reference);
|
||||||
else
|
|
||||||
{
|
|
||||||
VerifyBackupResult *backupResultPrior = lstGet(jobData->backupResultList, backupPriorIdx);
|
|
||||||
|
|
||||||
// If the verify-state of the backup is not complete then verify the file
|
if (backupPriorIdx == LIST_NOT_FOUND)
|
||||||
if (!backupResultPrior->fileVerifyComplete)
|
|
||||||
{
|
{
|
||||||
fileBackupLabel = fileData.reference;
|
fileBackupLabel = fileData.reference;
|
||||||
}
|
}
|
||||||
// Else skip verification
|
// Else the backup this file references has a result so check the processing state for the referenced backup
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
String *priorFile = strNewFmt(
|
VerifyBackupResult *backupResultPrior = lstGet(jobData->backupResultList, backupPriorIdx);
|
||||||
"%s/%s%s", strZ(fileData.reference), strZ(fileData.name),
|
|
||||||
strZ(compressExtStr((manifestData(jobData->manifest))->backupOptionCompressType)));
|
|
||||||
|
|
||||||
unsigned int backupPriorInvalidIdx = lstFindIdx(backupResultPrior->invalidFileList, &priorFile);
|
// If the verify-state of the backup is not complete then verify the file
|
||||||
|
if (!backupResultPrior->fileVerifyComplete)
|
||||||
// If the file is in the invalid file list of the prior backup where it is referenced then add the file
|
|
||||||
// as invalid to this backup result and set the backup result status; since already logged an error on
|
|
||||||
// this file, don't log again
|
|
||||||
if (backupPriorInvalidIdx != LIST_NOT_FOUND)
|
|
||||||
{
|
{
|
||||||
VerifyInvalidFile *invalidFile = lstGet(
|
fileBackupLabel = fileData.reference;
|
||||||
backupResultPrior->invalidFileList, backupPriorInvalidIdx);
|
|
||||||
verifyInvalidFileAdd(backupResult->invalidFileList, invalidFile->reason, invalidFile->fileName);
|
|
||||||
backupResult->status = backupInvalid;
|
|
||||||
}
|
}
|
||||||
// Else the file in the prior backup was valid so increment the total valid files for this backup
|
// Else skip verification
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
backupResult->totalFileValid++;
|
String *priorFile = strNewFmt(
|
||||||
|
"%s/%s%s", strZ(fileData.reference), strZ(fileData.name),
|
||||||
|
strZ(compressExtStr((manifestData(jobData->manifest))->backupOptionCompressType)));
|
||||||
|
|
||||||
|
unsigned int backupPriorInvalidIdx = lstFindIdx(backupResultPrior->invalidFileList, &priorFile);
|
||||||
|
|
||||||
|
// If the file is in the invalid file list of the prior backup where it is referenced then add the
|
||||||
|
// file as invalid to this backup result and set the backup result status; since already logged an
|
||||||
|
// error on this file, don't log again
|
||||||
|
if (backupPriorInvalidIdx != LIST_NOT_FOUND)
|
||||||
|
{
|
||||||
|
VerifyInvalidFile *invalidFile = lstGet(
|
||||||
|
backupResultPrior->invalidFileList, backupPriorInvalidIdx);
|
||||||
|
verifyInvalidFileAdd(backupResult->invalidFileList, invalidFile->reason, invalidFile->fileName);
|
||||||
|
backupResult->status = backupInvalid;
|
||||||
|
}
|
||||||
|
// Else the file in the prior backup was valid so increment the total valid files for this backup
|
||||||
|
else
|
||||||
|
{
|
||||||
|
backupResult->totalFileValid++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Else file is not referenced in a prior backup
|
||||||
|
else
|
||||||
|
fileBackupLabel = backupResult->backupLabel;
|
||||||
|
|
||||||
|
// If backup label is not null then send it off for processing
|
||||||
|
if (fileBackupLabel != NULL)
|
||||||
|
{
|
||||||
|
// Set up the job
|
||||||
|
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_VERIFY_FILE);
|
||||||
|
PackWrite *const param = protocolCommandParam(command);
|
||||||
|
|
||||||
|
const String *const filePathName = strNewFmt(
|
||||||
|
STORAGE_REPO_BACKUP "/%s/%s%s", strZ(fileBackupLabel), strZ(fileData.name),
|
||||||
|
strZ(compressExtStr((manifestData(jobData->manifest))->backupOptionCompressType)));
|
||||||
|
|
||||||
|
if (fileData.bundleId != 0)
|
||||||
|
{
|
||||||
|
pckWriteStrP(
|
||||||
|
param,
|
||||||
|
strNewFmt(
|
||||||
|
STORAGE_REPO_BACKUP "/%s/" MANIFEST_PATH_BUNDLE "/%" PRIu64, strZ(fileBackupLabel),
|
||||||
|
fileData.bundleId));
|
||||||
|
pckWriteBoolP(param, true);
|
||||||
|
pckWriteU64P(param, fileData.bundleOffset);
|
||||||
|
pckWriteU64P(param, fileData.sizeRepo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pckWriteStrP(param, filePathName);
|
||||||
|
pckWriteBoolP(param, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pckWriteU32P(param, manifestData(jobData->manifest)->backupOptionCompressType);
|
||||||
|
// If the checksum is not present in the manifest, it will be calculated by manifest load
|
||||||
|
pckWriteStrP(param, STR(fileData.checksumSha1));
|
||||||
|
pckWriteU64P(param, fileData.size);
|
||||||
|
pckWriteStrP(param, jobData->backupCipherPass);
|
||||||
|
|
||||||
|
// Assign job to result (prepend backup label being processed to the key since some files are in a prior
|
||||||
|
// backup)
|
||||||
|
result = protocolParallelJobNew(
|
||||||
|
VARSTR(strNewFmt("%s/%s", strZ(backupResult->backupLabel), strZ(filePathName))), command);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Else file is not referenced in a prior backup
|
// Else mark the zero-length file as valid
|
||||||
else
|
else
|
||||||
fileBackupLabel = backupResult->backupLabel;
|
backupResult->totalFileValid++;
|
||||||
|
|
||||||
// If backup label is not null then send it off for processing
|
|
||||||
if (fileBackupLabel != NULL)
|
|
||||||
{
|
|
||||||
// Set up the job
|
|
||||||
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_VERIFY_FILE);
|
|
||||||
PackWrite *const param = protocolCommandParam(command);
|
|
||||||
|
|
||||||
const String *const filePathName = strNewFmt(
|
|
||||||
STORAGE_REPO_BACKUP "/%s/%s%s", strZ(fileBackupLabel), strZ(fileData.name),
|
|
||||||
strZ(compressExtStr((manifestData(jobData->manifest))->backupOptionCompressType)));
|
|
||||||
|
|
||||||
pckWriteStrP(param, filePathName);
|
|
||||||
// If the checksum is not present in the manifest, it will be calculated by manifest load
|
|
||||||
pckWriteStrP(param, STR(fileData.checksumSha1));
|
|
||||||
pckWriteU64P(param, fileData.size);
|
|
||||||
pckWriteStrP(param, jobData->backupCipherPass);
|
|
||||||
|
|
||||||
// Assign job to result (prepend backup label being processed to the key since some files are in a prior backup)
|
|
||||||
result = protocolParallelJobNew(
|
|
||||||
VARSTR(strNewFmt("%s/%s", strZ(backupResult->backupLabel), strZ(filePathName))), command);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the index to point to the next file
|
// Increment the index to point to the next file
|
||||||
jobData->manifestFileIdx++;
|
jobData->manifestFileIdx++;
|
||||||
|
@ -53,6 +53,8 @@ Option constants
|
|||||||
#define CFGOPT_ARCHIVE_TIMEOUT "archive-timeout"
|
#define CFGOPT_ARCHIVE_TIMEOUT "archive-timeout"
|
||||||
#define CFGOPT_BACKUP_STANDBY "backup-standby"
|
#define CFGOPT_BACKUP_STANDBY "backup-standby"
|
||||||
#define CFGOPT_BUFFER_SIZE "buffer-size"
|
#define CFGOPT_BUFFER_SIZE "buffer-size"
|
||||||
|
#define CFGOPT_BUNDLE "bundle"
|
||||||
|
#define CFGOPT_BUNDLE_SIZE "bundle-size"
|
||||||
#define CFGOPT_CHECKSUM_PAGE "checksum-page"
|
#define CFGOPT_CHECKSUM_PAGE "checksum-page"
|
||||||
#define CFGOPT_CIPHER_PASS "cipher-pass"
|
#define CFGOPT_CIPHER_PASS "cipher-pass"
|
||||||
#define CFGOPT_CMD "cmd"
|
#define CFGOPT_CMD "cmd"
|
||||||
@ -126,7 +128,7 @@ Option constants
|
|||||||
#define CFGOPT_TLS_SERVER_PORT "tls-server-port"
|
#define CFGOPT_TLS_SERVER_PORT "tls-server-port"
|
||||||
#define CFGOPT_TYPE "type"
|
#define CFGOPT_TYPE "type"
|
||||||
|
|
||||||
#define CFG_OPTION_TOTAL 150
|
#define CFG_OPTION_TOTAL 152
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Option value constants
|
Option value constants
|
||||||
@ -363,6 +365,8 @@ typedef enum
|
|||||||
cfgOptArchiveTimeout,
|
cfgOptArchiveTimeout,
|
||||||
cfgOptBackupStandby,
|
cfgOptBackupStandby,
|
||||||
cfgOptBufferSize,
|
cfgOptBufferSize,
|
||||||
|
cfgOptBundle,
|
||||||
|
cfgOptBundleSize,
|
||||||
cfgOptChecksumPage,
|
cfgOptChecksumPage,
|
||||||
cfgOptCipherPass,
|
cfgOptCipherPass,
|
||||||
cfgOptCmd,
|
cfgOptCmd,
|
||||||
|
@ -16,6 +16,7 @@ static const StringPub parseRuleValueStr[] =
|
|||||||
PARSE_RULE_STRPUB("/var/log/pgbackrest"),
|
PARSE_RULE_STRPUB("/var/log/pgbackrest"),
|
||||||
PARSE_RULE_STRPUB("/var/spool/pgbackrest"),
|
PARSE_RULE_STRPUB("/var/spool/pgbackrest"),
|
||||||
PARSE_RULE_STRPUB("1"),
|
PARSE_RULE_STRPUB("1"),
|
||||||
|
PARSE_RULE_STRPUB("100MiB"),
|
||||||
PARSE_RULE_STRPUB("128MiB"),
|
PARSE_RULE_STRPUB("128MiB"),
|
||||||
PARSE_RULE_STRPUB("15"),
|
PARSE_RULE_STRPUB("15"),
|
||||||
PARSE_RULE_STRPUB("1800"),
|
PARSE_RULE_STRPUB("1800"),
|
||||||
@ -63,6 +64,7 @@ typedef enum
|
|||||||
parseRuleValStrQT_FS_var_FS_log_FS_pgbackrest_QT,
|
parseRuleValStrQT_FS_var_FS_log_FS_pgbackrest_QT,
|
||||||
parseRuleValStrQT_FS_var_FS_spool_FS_pgbackrest_QT,
|
parseRuleValStrQT_FS_var_FS_spool_FS_pgbackrest_QT,
|
||||||
parseRuleValStrQT_1_QT,
|
parseRuleValStrQT_1_QT,
|
||||||
|
parseRuleValStrQT_100MiB_QT,
|
||||||
parseRuleValStrQT_128MiB_QT,
|
parseRuleValStrQT_128MiB_QT,
|
||||||
parseRuleValStrQT_15_QT,
|
parseRuleValStrQT_15_QT,
|
||||||
parseRuleValStrQT_1800_QT,
|
parseRuleValStrQT_1800_QT,
|
||||||
@ -258,10 +260,12 @@ static const int64_t parseRuleValueInt[] =
|
|||||||
9999999,
|
9999999,
|
||||||
16777216,
|
16777216,
|
||||||
86400000,
|
86400000,
|
||||||
|
104857600,
|
||||||
134217728,
|
134217728,
|
||||||
604800000,
|
604800000,
|
||||||
1073741824,
|
1073741824,
|
||||||
1099511627776,
|
1099511627776,
|
||||||
|
1125899906842624,
|
||||||
4503599627370496,
|
4503599627370496,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -304,10 +308,12 @@ typedef enum
|
|||||||
parseRuleValInt9999999,
|
parseRuleValInt9999999,
|
||||||
parseRuleValInt16777216,
|
parseRuleValInt16777216,
|
||||||
parseRuleValInt86400000,
|
parseRuleValInt86400000,
|
||||||
|
parseRuleValInt104857600,
|
||||||
parseRuleValInt134217728,
|
parseRuleValInt134217728,
|
||||||
parseRuleValInt604800000,
|
parseRuleValInt604800000,
|
||||||
parseRuleValInt1073741824,
|
parseRuleValInt1073741824,
|
||||||
parseRuleValInt1099511627776,
|
parseRuleValInt1099511627776,
|
||||||
|
parseRuleValInt1125899906842624,
|
||||||
parseRuleValInt4503599627370496,
|
parseRuleValInt4503599627370496,
|
||||||
} ParseRuleValueInt;
|
} ParseRuleValueInt;
|
||||||
|
|
||||||
@ -1142,6 +1148,72 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
PARSE_RULE_OPTION
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTION_NAME("bundle"),
|
||||||
|
PARSE_RULE_OPTION_TYPE(cfgOptTypeBoolean),
|
||||||
|
PARSE_RULE_OPTION_NEGATE(true),
|
||||||
|
PARSE_RULE_OPTION_RESET(true),
|
||||||
|
PARSE_RULE_OPTION_REQUIRED(true),
|
||||||
|
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup)
|
||||||
|
),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTIONAL
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTIONAL_GROUP
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTIONAL_DEFAULT
|
||||||
|
(
|
||||||
|
PARSE_RULE_VAL_BOOL_FALSE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
PARSE_RULE_OPTION
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTION_NAME("bundle-size"),
|
||||||
|
PARSE_RULE_OPTION_TYPE(cfgOptTypeSize),
|
||||||
|
PARSE_RULE_OPTION_RESET(true),
|
||||||
|
PARSE_RULE_OPTION_REQUIRED(true),
|
||||||
|
PARSE_RULE_OPTION_SECTION(cfgSectionGlobal),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTION_COMMAND(cfgCmdBackup)
|
||||||
|
),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTIONAL
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTIONAL_GROUP
|
||||||
|
(
|
||||||
|
PARSE_RULE_OPTIONAL_DEPEND
|
||||||
|
(
|
||||||
|
PARSE_RULE_VAL_OPT(cfgOptBundle),
|
||||||
|
PARSE_RULE_VAL_BOOL_TRUE,
|
||||||
|
),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTIONAL_ALLOW_RANGE
|
||||||
|
(
|
||||||
|
PARSE_RULE_VAL_INT(parseRuleValInt1048576),
|
||||||
|
PARSE_RULE_VAL_INT(parseRuleValInt1125899906842624),
|
||||||
|
),
|
||||||
|
|
||||||
|
PARSE_RULE_OPTIONAL_DEFAULT
|
||||||
|
(
|
||||||
|
PARSE_RULE_VAL_INT(parseRuleValInt104857600),
|
||||||
|
PARSE_RULE_VAL_STR(parseRuleValStrQT_100MiB_QT),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------------------------------
|
||||||
PARSE_RULE_OPTION
|
PARSE_RULE_OPTION
|
||||||
(
|
(
|
||||||
@ -7709,6 +7781,12 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
|
|||||||
(
|
(
|
||||||
PARSE_RULE_OPTIONAL_GROUP
|
PARSE_RULE_OPTIONAL_GROUP
|
||||||
(
|
(
|
||||||
|
PARSE_RULE_OPTIONAL_DEPEND
|
||||||
|
(
|
||||||
|
PARSE_RULE_VAL_OPT(cfgOptBundle),
|
||||||
|
PARSE_RULE_VAL_BOOL_FALSE,
|
||||||
|
),
|
||||||
|
|
||||||
PARSE_RULE_OPTIONAL_DEFAULT
|
PARSE_RULE_OPTIONAL_DEFAULT
|
||||||
(
|
(
|
||||||
PARSE_RULE_VAL_BOOL_TRUE,
|
PARSE_RULE_VAL_BOOL_TRUE,
|
||||||
@ -9087,6 +9165,8 @@ static const ConfigOption optionResolveOrder[] =
|
|||||||
cfgOptArchiveTimeout,
|
cfgOptArchiveTimeout,
|
||||||
cfgOptBackupStandby,
|
cfgOptBackupStandby,
|
||||||
cfgOptBufferSize,
|
cfgOptBufferSize,
|
||||||
|
cfgOptBundle,
|
||||||
|
cfgOptBundleSize,
|
||||||
cfgOptChecksumPage,
|
cfgOptChecksumPage,
|
||||||
cfgOptCipherPass,
|
cfgOptCipherPass,
|
||||||
cfgOptCmd,
|
cfgOptCmd,
|
||||||
|
@ -50,6 +50,8 @@ STRING_STATIC(MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, "target:path
|
|||||||
STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_START_STR, MANIFEST_KEY_BACKUP_ARCHIVE_START);
|
STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_START_STR, MANIFEST_KEY_BACKUP_ARCHIVE_START);
|
||||||
#define MANIFEST_KEY_BACKUP_ARCHIVE_STOP "backup-archive-stop"
|
#define MANIFEST_KEY_BACKUP_ARCHIVE_STOP "backup-archive-stop"
|
||||||
STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR, MANIFEST_KEY_BACKUP_ARCHIVE_STOP);
|
STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR, MANIFEST_KEY_BACKUP_ARCHIVE_STOP);
|
||||||
|
#define MANIFEST_KEY_BACKUP_BUNDLE "backup-bundle"
|
||||||
|
STRING_STATIC(MANIFEST_KEY_BACKUP_BUNDLE_STR, MANIFEST_KEY_BACKUP_BUNDLE);
|
||||||
#define MANIFEST_KEY_BACKUP_LABEL "backup-label"
|
#define MANIFEST_KEY_BACKUP_LABEL "backup-label"
|
||||||
STRING_STATIC(MANIFEST_KEY_BACKUP_LABEL_STR, MANIFEST_KEY_BACKUP_LABEL);
|
STRING_STATIC(MANIFEST_KEY_BACKUP_LABEL_STR, MANIFEST_KEY_BACKUP_LABEL);
|
||||||
#define MANIFEST_KEY_BACKUP_LSN_START "backup-lsn-start"
|
#define MANIFEST_KEY_BACKUP_LSN_START "backup-lsn-start"
|
||||||
@ -66,6 +68,10 @@ STRING_STATIC(MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, "target:path
|
|||||||
STRING_STATIC(MANIFEST_KEY_BACKUP_TIMESTAMP_STOP_STR, MANIFEST_KEY_BACKUP_TIMESTAMP_STOP);
|
STRING_STATIC(MANIFEST_KEY_BACKUP_TIMESTAMP_STOP_STR, MANIFEST_KEY_BACKUP_TIMESTAMP_STOP);
|
||||||
#define MANIFEST_KEY_BACKUP_TYPE "backup-type"
|
#define MANIFEST_KEY_BACKUP_TYPE "backup-type"
|
||||||
STRING_STATIC(MANIFEST_KEY_BACKUP_TYPE_STR, MANIFEST_KEY_BACKUP_TYPE);
|
STRING_STATIC(MANIFEST_KEY_BACKUP_TYPE_STR, MANIFEST_KEY_BACKUP_TYPE);
|
||||||
|
#define MANIFEST_KEY_BUNDLE_ID "bni"
|
||||||
|
VARIANT_STRDEF_STATIC(MANIFEST_KEY_BUNDLE_ID_VAR, MANIFEST_KEY_BUNDLE_ID);
|
||||||
|
#define MANIFEST_KEY_BUNDLE_OFFSET "bno"
|
||||||
|
VARIANT_STRDEF_STATIC(MANIFEST_KEY_BUNDLE_OFFSET_VAR, MANIFEST_KEY_BUNDLE_OFFSET);
|
||||||
#define MANIFEST_KEY_CHECKSUM "checksum"
|
#define MANIFEST_KEY_CHECKSUM "checksum"
|
||||||
VARIANT_STRDEF_STATIC(MANIFEST_KEY_CHECKSUM_VAR, MANIFEST_KEY_CHECKSUM);
|
VARIANT_STRDEF_STATIC(MANIFEST_KEY_CHECKSUM_VAR, MANIFEST_KEY_CHECKSUM);
|
||||||
#define MANIFEST_KEY_CHECKSUM_PAGE "checksum-page"
|
#define MANIFEST_KEY_CHECKSUM_PAGE "checksum-page"
|
||||||
@ -209,6 +215,7 @@ static time_t manifestPackBaseTime = -1;
|
|||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
manifestFilePackFlagReference,
|
manifestFilePackFlagReference,
|
||||||
|
manifestFilePackFlagBundle,
|
||||||
manifestFilePackFlagChecksumPage,
|
manifestFilePackFlagChecksumPage,
|
||||||
manifestFilePackFlagChecksumPageError,
|
manifestFilePackFlagChecksumPageError,
|
||||||
manifestFilePackFlagChecksumPageErrorList,
|
manifestFilePackFlagChecksumPageErrorList,
|
||||||
@ -246,6 +253,9 @@ manifestFilePack(const Manifest *const manifest, const ManifestFile *const file)
|
|||||||
if (file->reference != NULL)
|
if (file->reference != NULL)
|
||||||
flag |= 1 << manifestFilePackFlagReference;
|
flag |= 1 << manifestFilePackFlagReference;
|
||||||
|
|
||||||
|
if (file->bundleId != 0)
|
||||||
|
flag |= 1 << manifestFilePackFlagBundle;
|
||||||
|
|
||||||
if (file->mode != manifest->fileModeDefault)
|
if (file->mode != manifest->fileModeDefault)
|
||||||
flag |= 1 << manifestFilePackFlagMode;
|
flag |= 1 << manifestFilePackFlagMode;
|
||||||
|
|
||||||
@ -294,6 +304,13 @@ manifestFilePack(const Manifest *const manifest, const ManifestFile *const file)
|
|||||||
// Repo size
|
// Repo size
|
||||||
cvtUInt64ToVarInt128(file->sizeRepo, buffer, &bufferPos, sizeof(buffer));
|
cvtUInt64ToVarInt128(file->sizeRepo, buffer, &bufferPos, sizeof(buffer));
|
||||||
|
|
||||||
|
// Bundle
|
||||||
|
if (flag & (1 << manifestFilePackFlagBundle))
|
||||||
|
{
|
||||||
|
cvtUInt64ToVarInt128(file->bundleId, buffer, &bufferPos, sizeof(buffer));
|
||||||
|
cvtUInt64ToVarInt128(file->bundleOffset, buffer, &bufferPos, sizeof(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate memory for the file pack
|
// Allocate memory for the file pack
|
||||||
uint8_t *const result = memNew(
|
uint8_t *const result = memNew(
|
||||||
sizeof(StringPub) + strSize(file->name) + 1 + bufferPos + (file->checksumPageErrorList != NULL ?
|
sizeof(StringPub) + strSize(file->name) + 1 + bufferPos + (file->checksumPageErrorList != NULL ?
|
||||||
@ -383,6 +400,13 @@ manifestFileUnpack(const Manifest *const manifest, const ManifestFilePack *const
|
|||||||
// Repo size
|
// Repo size
|
||||||
result.sizeRepo = cvtUInt64FromVarInt128((const uint8_t *)filePack, &bufferPos);
|
result.sizeRepo = cvtUInt64FromVarInt128((const uint8_t *)filePack, &bufferPos);
|
||||||
|
|
||||||
|
// Bundle
|
||||||
|
if (flag & (1 << manifestFilePackFlagBundle))
|
||||||
|
{
|
||||||
|
result.bundleId = cvtUInt64FromVarInt128((const uint8_t *)filePack, &bufferPos);
|
||||||
|
result.bundleOffset = cvtUInt64FromVarInt128((const uint8_t *)filePack, &bufferPos);
|
||||||
|
}
|
||||||
|
|
||||||
// Checksum page error
|
// Checksum page error
|
||||||
result.checksumPageError = flag & (1 << manifestFilePackFlagChecksumPageError) ? true : false;
|
result.checksumPageError = flag & (1 << manifestFilePackFlagChecksumPageError) ? true : false;
|
||||||
|
|
||||||
@ -1214,8 +1238,8 @@ manifestBuildCallback(void *data, const StorageInfo *info)
|
|||||||
|
|
||||||
Manifest *
|
Manifest *
|
||||||
manifestNewBuild(
|
manifestNewBuild(
|
||||||
const Storage *storagePg, unsigned int pgVersion, unsigned int pgCatalogVersion, bool online, bool checksumPage,
|
const Storage *const storagePg, const unsigned int pgVersion, const unsigned int pgCatalogVersion, const bool online,
|
||||||
const StringList *excludeList, const VariantList *tablespaceList)
|
const bool checksumPage, const bool bundle, const StringList *const excludeList, const VariantList *const tablespaceList)
|
||||||
{
|
{
|
||||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||||
FUNCTION_LOG_PARAM(STORAGE, storagePg);
|
FUNCTION_LOG_PARAM(STORAGE, storagePg);
|
||||||
@ -1223,6 +1247,7 @@ manifestNewBuild(
|
|||||||
FUNCTION_LOG_PARAM(UINT, pgCatalogVersion);
|
FUNCTION_LOG_PARAM(UINT, pgCatalogVersion);
|
||||||
FUNCTION_LOG_PARAM(BOOL, online);
|
FUNCTION_LOG_PARAM(BOOL, online);
|
||||||
FUNCTION_LOG_PARAM(BOOL, checksumPage);
|
FUNCTION_LOG_PARAM(BOOL, checksumPage);
|
||||||
|
FUNCTION_LOG_PARAM(BOOL, bundle);
|
||||||
FUNCTION_LOG_PARAM(STRING_LIST, excludeList);
|
FUNCTION_LOG_PARAM(STRING_LIST, excludeList);
|
||||||
FUNCTION_LOG_PARAM(VARIANT_LIST, tablespaceList);
|
FUNCTION_LOG_PARAM(VARIANT_LIST, tablespaceList);
|
||||||
FUNCTION_LOG_END();
|
FUNCTION_LOG_END();
|
||||||
@ -1243,6 +1268,7 @@ manifestNewBuild(
|
|||||||
this->pub.data.backupType = backupTypeFull;
|
this->pub.data.backupType = backupTypeFull;
|
||||||
this->pub.data.backupOptionOnline = online;
|
this->pub.data.backupOptionOnline = online;
|
||||||
this->pub.data.backupOptionChecksumPage = varNewBool(checksumPage);
|
this->pub.data.backupOptionChecksumPage = varNewBool(checksumPage);
|
||||||
|
this->pub.data.bundle = bundle;
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
{
|
{
|
||||||
@ -1584,7 +1610,8 @@ manifestBuildIncr(Manifest *this, const Manifest *manifestPrior, BackupType type
|
|||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
this, file.name, file.size, filePrior.sizeRepo, filePrior.checksumSha1,
|
this, file.name, file.size, filePrior.sizeRepo, filePrior.checksumSha1,
|
||||||
VARSTR(filePrior.reference != NULL ? filePrior.reference : manifestPrior->pub.data.backupLabel),
|
VARSTR(filePrior.reference != NULL ? filePrior.reference : manifestPrior->pub.data.backupLabel),
|
||||||
filePrior.checksumPage, filePrior.checksumPageError, filePrior.checksumPageErrorList);
|
filePrior.checksumPage, filePrior.checksumPageError, filePrior.checksumPageErrorList,
|
||||||
|
filePrior.bundleId, filePrior.bundleOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1814,6 +1841,12 @@ manifestLoadCallback(void *callbackData, const String *section, const String *ke
|
|||||||
file.checksumPageErrorList = jsonFromVar(checksumPageErrorList);
|
file.checksumPageErrorList = jsonFromVar(checksumPageErrorList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bundle info
|
||||||
|
file.bundleId = varUInt64(kvGetDefault(fileKv, MANIFEST_KEY_BUNDLE_ID_VAR, VARUINT64(0)));
|
||||||
|
|
||||||
|
if (file.bundleId != 0)
|
||||||
|
file.bundleOffset = varUInt64(kvGetDefault(fileKv, MANIFEST_KEY_BUNDLE_OFFSET_VAR, VARUINT64(0)));
|
||||||
|
|
||||||
// Group
|
// Group
|
||||||
const Variant *value = kvGet(fileKv, MANIFEST_KEY_GROUP_VAR);
|
const Variant *value = kvGet(fileKv, MANIFEST_KEY_GROUP_VAR);
|
||||||
|
|
||||||
@ -2006,6 +2039,8 @@ manifestLoadCallback(void *callbackData, const String *section, const String *ke
|
|||||||
manifest->pub.data.archiveStart = strDup(varStr(value));
|
manifest->pub.data.archiveStart = strDup(varStr(value));
|
||||||
else if (strEq(key, MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR))
|
else if (strEq(key, MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR))
|
||||||
manifest->pub.data.archiveStop = strDup(varStr(value));
|
manifest->pub.data.archiveStop = strDup(varStr(value));
|
||||||
|
else if (strEq(key, MANIFEST_KEY_BACKUP_BUNDLE_STR))
|
||||||
|
manifest->pub.data.bundle = varBool(value);
|
||||||
else if (strEq(key, MANIFEST_KEY_BACKUP_LABEL_STR))
|
else if (strEq(key, MANIFEST_KEY_BACKUP_LABEL_STR))
|
||||||
manifest->pub.data.backupLabel = strDup(varStr(value));
|
manifest->pub.data.backupLabel = strDup(varStr(value));
|
||||||
else if (strEq(key, MANIFEST_KEY_BACKUP_LSN_START_STR))
|
else if (strEq(key, MANIFEST_KEY_BACKUP_LSN_START_STR))
|
||||||
@ -2231,6 +2266,12 @@ manifestSaveCallback(void *callbackData, const String *sectionNext, InfoSave *in
|
|||||||
jsonFromStr(manifest->pub.data.archiveStop));
|
jsonFromStr(manifest->pub.data.archiveStop));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manifest->pub.data.bundle)
|
||||||
|
{
|
||||||
|
infoSaveValue(
|
||||||
|
infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_BUNDLE_STR, jsonFromBool(manifest->pub.data.bundle));
|
||||||
|
}
|
||||||
|
|
||||||
infoSaveValue(
|
infoSaveValue(
|
||||||
infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_LABEL_STR,
|
infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_LABEL_STR,
|
||||||
jsonFromStr(manifest->pub.data.backupLabel));
|
jsonFromStr(manifest->pub.data.backupLabel));
|
||||||
@ -2433,6 +2474,15 @@ manifestSaveCallback(void *callbackData, const String *sectionNext, InfoSave *in
|
|||||||
const ManifestFile file = manifestFile(manifest, fileIdx);
|
const ManifestFile file = manifestFile(manifest, fileIdx);
|
||||||
KeyValue *fileKv = kvNew();
|
KeyValue *fileKv = kvNew();
|
||||||
|
|
||||||
|
// Bundle info
|
||||||
|
if (file.bundleId != 0)
|
||||||
|
{
|
||||||
|
kvPut(fileKv, MANIFEST_KEY_BUNDLE_ID_VAR, VARUINT64(file.bundleId));
|
||||||
|
|
||||||
|
if (file.bundleOffset != 0)
|
||||||
|
kvPut(fileKv, MANIFEST_KEY_BUNDLE_OFFSET_VAR, VARUINT64(file.bundleOffset));
|
||||||
|
}
|
||||||
|
|
||||||
// Save if the file size is not zero and the checksum exists. The checksum might not exist if this is a partial
|
// Save if the file size is not zero and the checksum exists. The checksum might not exist if this is a partial
|
||||||
// save performed during a backup.
|
// save performed during a backup.
|
||||||
if (file.size != 0 && file.checksumSha1[0] != 0)
|
if (file.size != 0 && file.checksumSha1[0] != 0)
|
||||||
@ -2731,7 +2781,7 @@ void
|
|||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
Manifest *const this, const String *const name, const uint64_t size, const uint64_t sizeRepo, const char *const checksumSha1,
|
Manifest *const this, const String *const name, const uint64_t size, const uint64_t sizeRepo, const char *const checksumSha1,
|
||||||
const Variant *const reference, const bool checksumPage, const bool checksumPageError,
|
const Variant *const reference, const bool checksumPage, const bool checksumPageError,
|
||||||
const String *const checksumPageErrorList)
|
const String *const checksumPageErrorList, const uint64_t bundleId, const uint64_t bundleOffset)
|
||||||
{
|
{
|
||||||
FUNCTION_TEST_BEGIN();
|
FUNCTION_TEST_BEGIN();
|
||||||
FUNCTION_TEST_PARAM(MANIFEST, this);
|
FUNCTION_TEST_PARAM(MANIFEST, this);
|
||||||
@ -2743,6 +2793,8 @@ manifestFileUpdate(
|
|||||||
FUNCTION_TEST_PARAM(BOOL, checksumPage);
|
FUNCTION_TEST_PARAM(BOOL, checksumPage);
|
||||||
FUNCTION_TEST_PARAM(BOOL, checksumPageError);
|
FUNCTION_TEST_PARAM(BOOL, checksumPageError);
|
||||||
FUNCTION_TEST_PARAM(STRING, checksumPageErrorList);
|
FUNCTION_TEST_PARAM(STRING, checksumPageErrorList);
|
||||||
|
FUNCTION_TEST_PARAM(UINT64, bundleId);
|
||||||
|
FUNCTION_TEST_PARAM(UINT64, bundleOffset);
|
||||||
FUNCTION_TEST_END();
|
FUNCTION_TEST_END();
|
||||||
|
|
||||||
ASSERT(this != NULL);
|
ASSERT(this != NULL);
|
||||||
@ -2776,6 +2828,10 @@ manifestFileUpdate(
|
|||||||
file.checksumPageError = checksumPageError;
|
file.checksumPageError = checksumPageError;
|
||||||
file.checksumPageErrorList = checksumPageErrorList;
|
file.checksumPageErrorList = checksumPageErrorList;
|
||||||
|
|
||||||
|
// Update bundle info
|
||||||
|
file.bundleId = bundleId;
|
||||||
|
file.bundleOffset = bundleOffset;
|
||||||
|
|
||||||
manifestFilePackUpdate(this, filePack, &file);
|
manifestFilePackUpdate(this, filePack, &file);
|
||||||
|
|
||||||
FUNCTION_TEST_RETURN_VOID();
|
FUNCTION_TEST_RETURN_VOID();
|
||||||
|
@ -16,6 +16,9 @@ Constants
|
|||||||
#define BACKUP_MANIFEST_FILE "backup.manifest"
|
#define BACKUP_MANIFEST_FILE "backup.manifest"
|
||||||
STRING_DECLARE(BACKUP_MANIFEST_FILE_STR);
|
STRING_DECLARE(BACKUP_MANIFEST_FILE_STR);
|
||||||
|
|
||||||
|
#define MANIFEST_PATH_BUNDLE "bundle"
|
||||||
|
STRING_DECLARE(MANIFEST_PATH_BUNDLE_STR);
|
||||||
|
|
||||||
#define MANIFEST_TARGET_PGDATA "pg_data"
|
#define MANIFEST_TARGET_PGDATA "pg_data"
|
||||||
STRING_DECLARE(MANIFEST_TARGET_PGDATA_STR);
|
STRING_DECLARE(MANIFEST_TARGET_PGDATA_STR);
|
||||||
#define MANIFEST_TARGET_PGTBLSPC "pg_tblspc"
|
#define MANIFEST_TARGET_PGTBLSPC "pg_tblspc"
|
||||||
@ -49,6 +52,7 @@ typedef struct ManifestData
|
|||||||
time_t backupTimestampStart; // When did the backup start?
|
time_t backupTimestampStart; // When did the backup start?
|
||||||
time_t backupTimestampStop; // When did the backup stop?
|
time_t backupTimestampStop; // When did the backup stop?
|
||||||
BackupType backupType; // Type of backup: full, diff, incr
|
BackupType backupType; // Type of backup: full, diff, incr
|
||||||
|
bool bundle; // Does the backup bundle files?
|
||||||
|
|
||||||
// ??? Note that these fields are redundant and verbose since storing the start/stop lsn as a uint64 would be sufficient.
|
// ??? Note that these fields are redundant and verbose since storing the start/stop lsn as a uint64 would be sufficient.
|
||||||
// However, we currently lack the functions to transform these values back and forth so this will do for now.
|
// However, we currently lack the functions to transform these values back and forth so this will do for now.
|
||||||
@ -100,6 +104,8 @@ typedef struct ManifestFile
|
|||||||
const String *user; // User name
|
const String *user; // User name
|
||||||
const String *group; // Group name
|
const String *group; // Group name
|
||||||
const String *reference; // Reference to a prior backup
|
const String *reference; // Reference to a prior backup
|
||||||
|
uint64_t bundleId; // Bundle id
|
||||||
|
uint64_t bundleOffset; // Bundle offset
|
||||||
uint64_t size; // Original size
|
uint64_t size; // Original size
|
||||||
uint64_t sizeRepo; // Size in repo
|
uint64_t sizeRepo; // Size in repo
|
||||||
time_t timestamp; // Original timestamp
|
time_t timestamp; // Original timestamp
|
||||||
@ -151,7 +157,7 @@ Constructors
|
|||||||
***********************************************************************************************************************************/
|
***********************************************************************************************************************************/
|
||||||
// Build a new manifest for a PostgreSQL data directory
|
// Build a new manifest for a PostgreSQL data directory
|
||||||
Manifest *manifestNewBuild(
|
Manifest *manifestNewBuild(
|
||||||
const Storage *storagePg, unsigned int pgVersion, unsigned int pgCatalogVersion, bool online, bool checksumPage,
|
const Storage *storagePg, unsigned int pgVersion, unsigned int pgCatalogVersion, bool online, bool checksumPage, bool bundle,
|
||||||
const StringList *excludeList, const VariantList *tablespaceList);
|
const StringList *excludeList, const VariantList *tablespaceList);
|
||||||
|
|
||||||
// Load a manifest from IO
|
// Load a manifest from IO
|
||||||
@ -317,7 +323,7 @@ manifestFileTotal(const Manifest *const this)
|
|||||||
// Update a file with new data
|
// Update a file with new data
|
||||||
void manifestFileUpdate(
|
void manifestFileUpdate(
|
||||||
Manifest *this, const String *name, uint64_t size, uint64_t sizeRepo, const char *checksumSha1, const Variant *reference,
|
Manifest *this, const String *name, uint64_t size, uint64_t sizeRepo, const char *checksumSha1, const Variant *reference,
|
||||||
bool checksumPage, bool checksumPageError, const String *checksumPageErrorList);
|
bool checksumPage, bool checksumPageError, const String *checksumPageErrorList, uint64_t bundleId, uint64_t bundleOffset);
|
||||||
|
|
||||||
/***********************************************************************************************************************************
|
/***********************************************************************************************************************************
|
||||||
Link functions and getters/setters
|
Link functions and getters/setters
|
||||||
|
@ -30,6 +30,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -62,6 +64,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -108,6 +112,8 @@ pg256-port=6544
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -141,6 +147,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -173,6 +181,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -219,6 +229,8 @@ pg256-port=6544
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -252,6 +264,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -284,6 +298,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -330,6 +346,8 @@ pg256-port=6544
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -382,6 +400,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -416,6 +436,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -463,6 +485,8 @@ pg256-port=6544
|
|||||||
[global]
|
[global]
|
||||||
archive-async=y
|
archive-async=y
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -500,6 +524,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -534,6 +560,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -581,6 +609,8 @@ pg256-port=6544
|
|||||||
[global]
|
[global]
|
||||||
archive-async=y
|
archive-async=y
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -634,6 +664,8 @@ pg1-socket-path=[TEST_PATH]/db-primary/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -668,6 +700,8 @@ pg1-socket-path=[TEST_PATH]/db-standby/db
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
@ -715,6 +749,8 @@ pg256-port=6544
|
|||||||
[global]
|
[global]
|
||||||
archive-async=y
|
archive-async=y
|
||||||
buffer-size=[BUFFER-SIZE]
|
buffer-size=[BUFFER-SIZE]
|
||||||
|
bundle=y
|
||||||
|
bundle-size=1MiB
|
||||||
compress-level=3
|
compress-level=3
|
||||||
compress-level-network=1
|
compress-level-network=1
|
||||||
compress-type=none
|
compress-type=none
|
||||||
|
@ -409,6 +409,7 @@ sub backupCreate
|
|||||||
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef, false);
|
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef, false);
|
||||||
$oManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START, undef, $strArchiveStart);
|
$oManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START, undef, $strArchiveStart);
|
||||||
$oManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP, undef, $strArchiveStop);
|
$oManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP, undef, $strArchiveStop);
|
||||||
|
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, 'backup-bundle', undef, true);
|
||||||
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE, undef, true);
|
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE, undef, true);
|
||||||
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, true);
|
$oManifest->boolSet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef, true);
|
||||||
$oManifest->numericSet(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, REPOSITORY_FORMAT);
|
$oManifest->numericSet(INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, REPOSITORY_FORMAT);
|
||||||
|
@ -413,7 +413,8 @@ sub backupEnd
|
|||||||
# Make sure tablespace links are correct
|
# Make sure tablespace links are correct
|
||||||
if ($self->hasLink())
|
if ($self->hasLink())
|
||||||
{
|
{
|
||||||
if ($strType eq CFGOPTVAL_BACKUP_TYPE_FULL || $self->hardLink())
|
if (($strType eq CFGOPTVAL_BACKUP_TYPE_FULL || $self->hardLink()) &&
|
||||||
|
!$oExpectedManifest->{&MANIFEST_SECTION_BACKUP}{'backup-bundle'})
|
||||||
{
|
{
|
||||||
my $hTablespaceManifest = storageTest()->manifest(
|
my $hTablespaceManifest = storageTest()->manifest(
|
||||||
$self->repoBackupPath("${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC));
|
$self->repoBackupPath("${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC));
|
||||||
@ -1174,6 +1175,13 @@ sub configCreate
|
|||||||
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'log-timestamp'} = 'n';
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'log-timestamp'} = 'n';
|
||||||
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'buffer-size'} = '64k';
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'buffer-size'} = '64k';
|
||||||
|
|
||||||
|
if ($oParam->{bBundle})
|
||||||
|
{
|
||||||
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'bundle'} = 'y';
|
||||||
|
# Set bundle size smaller for testing and because FakeGCS does not do multi-part upload
|
||||||
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'bundle-size'} = '1MiB';
|
||||||
|
}
|
||||||
|
|
||||||
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'log-path'} = $self->logPath();
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'log-path'} = $self->logPath();
|
||||||
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'lock-path'} = $self->lockPath();
|
$oParamHash{&CFGDEF_SECTION_GLOBAL}{'lock-path'} = $self->lockPath();
|
||||||
|
|
||||||
@ -2070,9 +2078,13 @@ sub restoreCompare
|
|||||||
${$oExpectedManifestRef}{&MANIFEST_SECTION_TARGET_FILE}{$strName}{size});
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_TARGET_FILE}{$strName}{size});
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove repo-size from the manifest. ??? This could be improved to get actual sizes from the backup.
|
# Remove repo-size, bno, bni from the manifest
|
||||||
$oActualManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strName, MANIFEST_SUBKEY_REPO_SIZE);
|
$oActualManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strName, MANIFEST_SUBKEY_REPO_SIZE);
|
||||||
delete($oExpectedManifestRef->{&MANIFEST_SECTION_TARGET_FILE}{$strName}{&MANIFEST_SUBKEY_REPO_SIZE});
|
delete($oExpectedManifestRef->{&MANIFEST_SECTION_TARGET_FILE}{$strName}{&MANIFEST_SUBKEY_REPO_SIZE});
|
||||||
|
$oActualManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strName, "bni");
|
||||||
|
delete($oExpectedManifestRef->{&MANIFEST_SECTION_TARGET_FILE}{$strName}{"bni"});
|
||||||
|
$oActualManifest->remove(MANIFEST_SECTION_TARGET_FILE, $strName, "bno");
|
||||||
|
delete($oExpectedManifestRef->{&MANIFEST_SECTION_TARGET_FILE}{$strName}{"bno"});
|
||||||
|
|
||||||
if ($oActualManifest->get(MANIFEST_SECTION_TARGET_FILE, $strName, MANIFEST_SUBKEY_SIZE) != 0)
|
if ($oActualManifest->get(MANIFEST_SECTION_TARGET_FILE, $strName, MANIFEST_SUBKEY_SIZE) != 0)
|
||||||
{
|
{
|
||||||
@ -2134,6 +2146,8 @@ sub restoreCompare
|
|||||||
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_ARCHIVE_COPY});
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_ARCHIVE_COPY});
|
||||||
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef,
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef,
|
||||||
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_BACKUP_STANDBY});
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_BACKUP_STANDBY});
|
||||||
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY, undef,
|
||||||
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_BACKUP_STANDBY});
|
||||||
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BUFFER_SIZE, undef,
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BUFFER_SIZE, undef,
|
||||||
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_BUFFER_SIZE});
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_BUFFER_SIZE});
|
||||||
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef,
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS, undef,
|
||||||
@ -2201,6 +2215,13 @@ sub restoreCompare
|
|||||||
MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE, undef,
|
MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE, undef,
|
||||||
$oExpectedManifestRef->{&MANIFEST_SECTION_BACKUP}{&MANIFEST_KEY_TYPE});
|
$oExpectedManifestRef->{&MANIFEST_SECTION_BACKUP}{&MANIFEST_KEY_TYPE});
|
||||||
|
|
||||||
|
if (defined($oExpectedManifestRef->{&MANIFEST_SECTION_BACKUP}{'backup-bundle'}))
|
||||||
|
{
|
||||||
|
$oActualManifest->set(
|
||||||
|
MANIFEST_SECTION_BACKUP, 'backup-bundle', undef,
|
||||||
|
$oExpectedManifestRef->{&MANIFEST_SECTION_BACKUP}{'backup-bundle'});
|
||||||
|
}
|
||||||
|
|
||||||
$oActualManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_START, undef,
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_START, undef,
|
||||||
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP}{&MANIFEST_KEY_LSN_START});
|
${$oExpectedManifestRef}{&MANIFEST_SECTION_BACKUP}{&MANIFEST_KEY_LSN_START});
|
||||||
$oActualManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_STOP, undef,
|
$oActualManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LSN_STOP, undef,
|
||||||
|
@ -146,7 +146,8 @@ sub setup
|
|||||||
bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink},
|
bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink},
|
||||||
bArchiveAsync => $$oConfigParam{bArchiveAsync},
|
bArchiveAsync => $$oConfigParam{bArchiveAsync},
|
||||||
strStorage => $oConfigParam->{strStorage},
|
strStorage => $oConfigParam->{strStorage},
|
||||||
iRepoTotal => $oConfigParam->{iRepoTotal}});
|
iRepoTotal => $oConfigParam->{iRepoTotal},
|
||||||
|
bBundle => $oConfigParam->{bBundle}});
|
||||||
|
|
||||||
# Create backup config if backup host exists
|
# Create backup config if backup host exists
|
||||||
if (defined($oHostBackup))
|
if (defined($oHostBackup))
|
||||||
@ -156,7 +157,8 @@ sub setup
|
|||||||
strCompressType => $$oConfigParam{strCompressType},
|
strCompressType => $$oConfigParam{strCompressType},
|
||||||
bHardlink => $$oConfigParam{bHardLink},
|
bHardlink => $$oConfigParam{bHardLink},
|
||||||
strStorage => $oConfigParam->{strStorage},
|
strStorage => $oConfigParam->{strStorage},
|
||||||
iRepoTotal => $oConfigParam->{iRepoTotal}});
|
iRepoTotal => $oConfigParam->{iRepoTotal},
|
||||||
|
bBundle => $oConfigParam->{bBundle}});
|
||||||
}
|
}
|
||||||
# If backup host is not defined set it to db-primary
|
# If backup host is not defined set it to db-primary
|
||||||
else
|
else
|
||||||
@ -184,7 +186,8 @@ sub setup
|
|||||||
bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink},
|
bHardlink => $bHostBackup ? undef : $$oConfigParam{bHardLink},
|
||||||
bArchiveAsync => $$oConfigParam{bArchiveAsync},
|
bArchiveAsync => $$oConfigParam{bArchiveAsync},
|
||||||
strStorage => $oConfigParam->{strStorage},
|
strStorage => $oConfigParam->{strStorage},
|
||||||
iRepoTotal => $oConfigParam->{iRepoTotal}});
|
iRepoTotal => $oConfigParam->{iRepoTotal},
|
||||||
|
bBundle => $oConfigParam->{bBundle}});
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create object storage
|
# Create object storage
|
||||||
|
@ -52,18 +52,18 @@ sub run
|
|||||||
|
|
||||||
foreach my $rhRun
|
foreach my $rhRun
|
||||||
(
|
(
|
||||||
{pg => PG_VERSION_90, repoDest => HOST_DB_PRIMARY, tls => 0, storage => GCS, encrypt => 1, compress => BZ2, repo => 2},
|
{pg => '9.0', repoDest => HOST_DB_PRIMARY, tls => 0, storage => GCS, encrypt => 1, compress => BZ2, repo => 2, bnd => 1},
|
||||||
{pg => PG_VERSION_91, repoDest => HOST_DB_STANDBY, tls => 1, storage => GCS, encrypt => 0, compress => GZ, repo => 1},
|
{pg => '9.1', repoDest => HOST_DB_STANDBY, tls => 1, storage => GCS, encrypt => 0, compress => GZ, repo => 1, bnd => 0},
|
||||||
{pg => PG_VERSION_92, repoDest => HOST_DB_STANDBY, tls => 0, storage => POSIX, encrypt => 1, compress => NONE, repo => 1},
|
{pg => '9.2', repoDest => HOST_DB_STANDBY, tls => 0, storage => POSIX, encrypt => 1, compress => NONE, repo => 1, bnd => 1},
|
||||||
{pg => PG_VERSION_93, repoDest => HOST_BACKUP, tls => 0, storage => AZURE, encrypt => 0, compress => NONE, repo => 2},
|
{pg => '9.3', repoDest => HOST_BACKUP, tls => 0, storage => AZURE, encrypt => 0, compress => NONE, repo => 2, bnd => 0},
|
||||||
{pg => PG_VERSION_94, repoDest => HOST_DB_STANDBY, tls => 0, storage => POSIX, encrypt => 1, compress => LZ4, repo => 1},
|
{pg => '9.4', repoDest => HOST_DB_STANDBY, tls => 0, storage => POSIX, encrypt => 1, compress => LZ4, repo => 1, bnd => 1},
|
||||||
{pg => PG_VERSION_95, repoDest => HOST_BACKUP, tls => 1, storage => S3, encrypt => 0, compress => BZ2, repo => 1},
|
{pg => '9.5', repoDest => HOST_BACKUP, tls => 1, storage => S3, encrypt => 0, compress => BZ2, repo => 1, bnd => 0},
|
||||||
{pg => PG_VERSION_96, repoDest => HOST_BACKUP, tls => 0, storage => POSIX, encrypt => 0, compress => NONE, repo => 2},
|
{pg => '9.6', repoDest => HOST_BACKUP, tls => 0, storage => POSIX, encrypt => 0, compress => NONE, repo => 2, bnd => 1},
|
||||||
{pg => PG_VERSION_10, repoDest => HOST_DB_STANDBY, tls => 1, storage => S3, encrypt => 1, compress => GZ, repo => 2},
|
{pg => '10', repoDest => HOST_DB_STANDBY, tls => 1, storage => S3, encrypt => 1, compress => GZ, repo => 2, bnd => 0},
|
||||||
{pg => PG_VERSION_11, repoDest => HOST_BACKUP, tls => 1, storage => AZURE, encrypt => 0, compress => ZST, repo => 2},
|
{pg => '11', repoDest => HOST_BACKUP, tls => 1, storage => AZURE, encrypt => 0, compress => ZST, repo => 2, bnd => 1},
|
||||||
{pg => PG_VERSION_12, repoDest => HOST_BACKUP, tls => 0, storage => S3, encrypt => 1, compress => LZ4, repo => 1},
|
{pg => '12', repoDest => HOST_BACKUP, tls => 0, storage => S3, encrypt => 1, compress => LZ4, repo => 1, bnd => 0},
|
||||||
{pg => PG_VERSION_13, repoDest => HOST_DB_STANDBY, tls => 1, storage => GCS, encrypt => 0, compress => ZST, repo => 1},
|
{pg => '13', repoDest => HOST_DB_STANDBY, tls => 1, storage => GCS, encrypt => 0, compress => ZST, repo => 1, bnd => 1},
|
||||||
{pg => PG_VERSION_14, repoDest => HOST_BACKUP, tls => 0, storage => POSIX, encrypt => 1, compress => LZ4, repo => 2},
|
{pg => '14', repoDest => HOST_BACKUP, tls => 0, storage => POSIX, encrypt => 1, compress => LZ4, repo => 2, bnd => 0},
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
# Only run tests for this pg version
|
# Only run tests for this pg version
|
||||||
@ -78,6 +78,7 @@ sub run
|
|||||||
my $bRepoEncrypt = $rhRun->{encrypt};
|
my $bRepoEncrypt = $rhRun->{encrypt};
|
||||||
my $strCompressType = $rhRun->{compress};
|
my $strCompressType = $rhRun->{compress};
|
||||||
my $iRepoTotal = $rhRun->{repo};
|
my $iRepoTotal = $rhRun->{repo};
|
||||||
|
my $bBundle = $rhRun->{bnd};
|
||||||
|
|
||||||
# Use a specific VM and version of PostgreSQL for expect testing. This version will also be used to run tests that are not
|
# Use a specific VM and version of PostgreSQL for expect testing. This version will also be used to run tests that are not
|
||||||
# version specific.
|
# version specific.
|
||||||
@ -94,7 +95,7 @@ sub run
|
|||||||
false, $self->expect(),
|
false, $self->expect(),
|
||||||
{bHostBackup => $bHostBackup, bStandby => $bHostStandby, bTls => $bTls, strBackupDestination => $strBackupDestination,
|
{bHostBackup => $bHostBackup, bStandby => $bHostStandby, bTls => $bTls, strBackupDestination => $strBackupDestination,
|
||||||
strCompressType => $strCompressType, bArchiveAsync => false, strStorage => $strStorage,
|
strCompressType => $strCompressType, bArchiveAsync => false, strStorage => $strStorage,
|
||||||
bRepoEncrypt => $bRepoEncrypt, iRepoTotal => $iRepoTotal});
|
bRepoEncrypt => $bRepoEncrypt, iRepoTotal => $iRepoTotal, bBundle => $bBundle});
|
||||||
|
|
||||||
# Some commands will fail because of the bogus host created when a standby is present. These options reset the bogus host
|
# Some commands will fail because of the bogus host created when a standby is present. These options reset the bogus host
|
||||||
# so it won't interfere with commands that won't tolerate a connection failure.
|
# so it won't interfere with commands that won't tolerate a connection failure.
|
||||||
|
@ -42,95 +42,141 @@ testBackupValidateCallback(void *callbackData, const StorageInfo *info)
|
|||||||
(strEqZ(info->name, BACKUP_MANIFEST_FILE) || strEqZ(info->name, BACKUP_MANIFEST_FILE INFO_COPY_EXT)))
|
(strEqZ(info->name, BACKUP_MANIFEST_FILE) || strEqZ(info->name, BACKUP_MANIFEST_FILE INFO_COPY_EXT)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get manifest name
|
|
||||||
const String *manifestName = info->name;
|
|
||||||
|
|
||||||
strCatFmt(data->content, "%s {", strZ(info->name));
|
|
||||||
|
|
||||||
switch (info->type)
|
switch (info->type)
|
||||||
{
|
{
|
||||||
case storageTypeFile:
|
case storageTypeFile:
|
||||||
{
|
{
|
||||||
strCatZ(data->content, "file");
|
|
||||||
|
|
||||||
// Calculate checksum/size and decompress if needed
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
|
||||||
StorageRead *read = storageNewReadP(
|
|
||||||
data->storage, data->path != NULL ? strNewFmt("%s/%s", strZ(data->path), strZ(info->name)) : info->name);
|
|
||||||
|
|
||||||
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
|
||||||
{
|
|
||||||
ioFilterGroupAdd(
|
|
||||||
ioReadFilterGroup(storageReadIo(read)), decompressFilter(data->manifestData->backupOptionCompressType));
|
|
||||||
manifestName = strSubN(
|
|
||||||
info->name, 0, strSize(info->name) - strSize(compressExtStr(data->manifestData->backupOptionCompressType)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
|
||||||
|
|
||||||
uint64_t size = bufUsed(storageGetP(read));
|
|
||||||
const String *checksum = pckReadStrP(
|
|
||||||
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE));
|
|
||||||
|
|
||||||
strCatFmt(data->content, ", s=%" PRIu64, size);
|
|
||||||
|
|
||||||
// Check against the manifest
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
|
||||||
ManifestFilePack **const filePack = manifestFilePackFindInternal(data->manifest, manifestName);
|
|
||||||
ManifestFile file = manifestFileUnpack(data->manifest, *filePack);
|
|
||||||
|
|
||||||
// Test size and repo-size. If compressed then set the repo-size to size so it will not be in test output. Even the same
|
|
||||||
// compression algorithm can give slightly different results based on the version so repo-size is not deterministic for
|
|
||||||
// compression.
|
|
||||||
if (size != file.size)
|
|
||||||
THROW_FMT(AssertError, "'%s' size does match manifest", strZ(manifestName));
|
|
||||||
|
|
||||||
if (info->size != file.sizeRepo)
|
|
||||||
THROW_FMT(AssertError, "'%s' repo size does match manifest", strZ(manifestName));
|
|
||||||
|
|
||||||
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
|
||||||
file.sizeRepo = file.size;
|
|
||||||
|
|
||||||
// Test the checksum. pg_control and WAL headers have different checksums depending on cpu architecture so remove
|
|
||||||
// the checksum from the test output.
|
|
||||||
if (!strEqZ(checksum, file.checksumSha1))
|
|
||||||
THROW_FMT(AssertError, "'%s' checksum does match manifest", strZ(manifestName));
|
|
||||||
|
|
||||||
if (strEqZ(manifestName, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) ||
|
|
||||||
strBeginsWith(
|
|
||||||
manifestName, strNewFmt(MANIFEST_TARGET_PGDATA "/%s/", strZ(pgWalPath(data->manifestData->pgVersion)))))
|
|
||||||
{
|
|
||||||
file.checksumSha1[0] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
||||||
// mode and current user/group.
|
// mode and current user/group.
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
if (info->mode != 0640)
|
if (info->mode != 0640)
|
||||||
THROW_FMT(AssertError, "'%s' mode is not 0640", strZ(manifestName));
|
THROW_FMT(AssertError, "'%s' mode is not 0640", strZ(info->name));
|
||||||
|
|
||||||
if (!strEq(info->user, TEST_USER_STR))
|
if (!strEq(info->user, TEST_USER_STR))
|
||||||
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(manifestName));
|
THROW_FMT(AssertError, "'%s' user should be '" TEST_USER "'", strZ(info->name));
|
||||||
|
|
||||||
if (!strEq(info->group, TEST_GROUP_STR))
|
if (!strEq(info->group, TEST_GROUP_STR))
|
||||||
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(manifestName));
|
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info->name));
|
||||||
|
|
||||||
// Update changes to manifest file
|
// Build file list (needed because bundles can contain multiple files)
|
||||||
manifestFilePackUpdate(data->manifest, filePack, &file);
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
List *const fileList = lstNewP(sizeof(ManifestFilePack **));
|
||||||
|
bool bundle = strBeginsWithZ(info->name, "bundle/");
|
||||||
|
|
||||||
|
if (bundle)
|
||||||
|
{
|
||||||
|
const uint64_t bundleId = cvtZToUInt64(strZ(strSub(info->name, sizeof("bundle"))));
|
||||||
|
|
||||||
|
for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(data->manifest); fileIdx++)
|
||||||
|
{
|
||||||
|
ManifestFilePack **const filePack = lstGet(data->manifest->pub.fileList, fileIdx);
|
||||||
|
|
||||||
|
if (manifestFileUnpack(data->manifest, *filePack).bundleId == bundleId)
|
||||||
|
lstAdd(fileList, &filePack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const String *manifestName = info->name;
|
||||||
|
|
||||||
|
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
|
{
|
||||||
|
manifestName = strSubN(
|
||||||
|
info->name, 0, strSize(info->name) - strSize(compressExtStr(data->manifestData->backupOptionCompressType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ManifestFilePack **const filePack = manifestFilePackFindInternal(data->manifest, manifestName);
|
||||||
|
lstAdd(fileList, &filePack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check files
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
for (unsigned int fileIdx = 0; fileIdx < lstSize(fileList); fileIdx++)
|
||||||
|
{
|
||||||
|
ManifestFilePack **const filePack = *(ManifestFilePack ***)lstGet(fileList, fileIdx);
|
||||||
|
ManifestFile file = manifestFileUnpack(data->manifest, *filePack);
|
||||||
|
|
||||||
|
if (bundle)
|
||||||
|
strCatFmt(data->content, "%s/%s {file", strZ(info->name), strZ(file.name));
|
||||||
|
else
|
||||||
|
strCatFmt(data->content, "%s {file", strZ(info->name));
|
||||||
|
|
||||||
|
// Calculate checksum/size and decompress if needed
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
StorageRead *read = storageNewReadP(
|
||||||
|
data->storage, strNewFmt("%s/%s", strZ(data->path), strZ(info->name)), .offset = file.bundleOffset,
|
||||||
|
.limit = VARUINT64(file.sizeRepo));
|
||||||
|
|
||||||
|
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
|
{
|
||||||
|
ioFilterGroupAdd(
|
||||||
|
ioReadFilterGroup(storageReadIo(read)), decompressFilter(data->manifestData->backupOptionCompressType));
|
||||||
|
}
|
||||||
|
|
||||||
|
ioFilterGroupAdd(ioReadFilterGroup(storageReadIo(read)), cryptoHashNew(HASH_TYPE_SHA1_STR));
|
||||||
|
|
||||||
|
uint64_t size = bufUsed(storageGetP(read));
|
||||||
|
const String *checksum = pckReadStrP(
|
||||||
|
ioFilterGroupResultP(ioReadFilterGroup(storageReadIo(read)), CRYPTO_HASH_FILTER_TYPE));
|
||||||
|
|
||||||
|
strCatFmt(data->content, ", s=%" PRIu64, size);
|
||||||
|
|
||||||
|
if (!strEqZ(checksum, file.checksumSha1))
|
||||||
|
THROW_FMT(AssertError, "'%s' checksum does match manifest", strZ(file.name));
|
||||||
|
|
||||||
|
// Test size and repo-size. If compressed then set the repo-size to size so it will not be in test output. Even the
|
||||||
|
// same compression algorithm can give slightly different results based on the version so repo-size is not
|
||||||
|
// deterministic for compression.
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
if (size != file.size)
|
||||||
|
THROW_FMT(AssertError, "'%s' size does match manifest", strZ(file.name));
|
||||||
|
|
||||||
|
// Repo size can only be compared to file size when not bundled
|
||||||
|
if (!bundle)
|
||||||
|
{
|
||||||
|
if (info->size != file.sizeRepo)
|
||||||
|
THROW_FMT(AssertError, "'%s' repo size does match manifest", strZ(file.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->manifestData->backupOptionCompressType != compressTypeNone)
|
||||||
|
file.sizeRepo = file.size;
|
||||||
|
|
||||||
|
// Bundle id/offset are too noisy so remove them. They are checked size/checksum and listed with the files.
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
file.bundleId = 0;
|
||||||
|
file.bundleOffset = 0;
|
||||||
|
|
||||||
|
// pg_control and WAL headers have different checksums depending on cpu architecture so remove the checksum from the
|
||||||
|
// test output.
|
||||||
|
// -----------------------------------------------------------------------------------------------------------------
|
||||||
|
if (strEqZ(file.name, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) ||
|
||||||
|
strBeginsWith(
|
||||||
|
file.name, strNewFmt(MANIFEST_TARGET_PGDATA "/%s/", strZ(pgWalPath(data->manifestData->pgVersion)))))
|
||||||
|
{
|
||||||
|
file.checksumSha1[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
strCatZ(data->content, "}\n");
|
||||||
|
|
||||||
|
// Update changes to manifest file
|
||||||
|
manifestFilePackUpdate(data->manifest, filePack, &file);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case storageTypeLink:
|
case storageTypeLink:
|
||||||
strCatFmt(data->content, "link, d=%s", strZ(info->linkDestination));
|
strCatFmt(data->content, "%s {link, d=%s}\n", strZ(info->name), strZ(info->linkDestination));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case storageTypePath:
|
case storageTypePath:
|
||||||
{
|
{
|
||||||
strCatZ(data->content, "path");
|
strCatFmt(data->content, "%s {path", strZ(info->name));
|
||||||
|
|
||||||
// Check against the manifest
|
// Check against the manifest
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
manifestPathFind(data->manifest, info->name);
|
if (!strEq(info->name, STRDEF("bundle")))
|
||||||
|
manifestPathFind(data->manifest, info->name);
|
||||||
|
|
||||||
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
// Test mode, user, group. These values are not in the manifest but we know what they should be based on the default
|
||||||
// mode and current user/group.
|
// mode and current user/group.
|
||||||
@ -143,6 +189,7 @@ testBackupValidateCallback(void *callbackData, const StorageInfo *info)
|
|||||||
if (!strEq(info->group, TEST_GROUP_STR))
|
if (!strEq(info->group, TEST_GROUP_STR))
|
||||||
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info->name));
|
THROW_FMT(AssertError, "'%s' group should be '" TEST_GROUP "'", strZ(info->name));
|
||||||
|
|
||||||
|
strCatZ(data->content, "}\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +197,6 @@ testBackupValidateCallback(void *callbackData, const StorageInfo *info)
|
|||||||
THROW_FMT(AssertError, "unexpected special file '%s'", strZ(info->name));
|
THROW_FMT(AssertError, "unexpected special file '%s'", strZ(info->name));
|
||||||
}
|
}
|
||||||
|
|
||||||
strCatZ(data->content, "}\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static String *
|
static String *
|
||||||
@ -161,6 +207,9 @@ testBackupValidate(const Storage *storage, const String *path)
|
|||||||
FUNCTION_HARNESS_PARAM(STRING, path);
|
FUNCTION_HARNESS_PARAM(STRING, path);
|
||||||
FUNCTION_HARNESS_END();
|
FUNCTION_HARNESS_END();
|
||||||
|
|
||||||
|
ASSERT(storage != NULL);
|
||||||
|
ASSERT(path != NULL);
|
||||||
|
|
||||||
String *result = strNew();
|
String *result = strNew();
|
||||||
|
|
||||||
MEM_CONTEXT_TEMP_BEGIN()
|
MEM_CONTEXT_TEMP_BEGIN()
|
||||||
@ -599,11 +648,28 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("pg file missing - ignoreMissing=true");
|
TEST_TITLE("pg file missing - ignoreMissing=true");
|
||||||
|
|
||||||
|
List *fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
BackupFile file =
|
||||||
|
{
|
||||||
|
.pgFile = missingFile,
|
||||||
|
.pgFileIgnoreMissing = true,
|
||||||
|
.pgFileSize = 0,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = missingFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
const String *repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
missingFile, true, 0, true, NULL, false, 0, missingFile, false, compressTypeNone, 1, backupLabel, false,
|
|
||||||
cipherTypeNone, NULL),
|
|
||||||
"pg file missing, ignoreMissing=true, no delta");
|
"pg file missing, ignoreMissing=true, no delta");
|
||||||
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy/repo size 0");
|
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy/repo size 0");
|
||||||
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultSkip, "skip file");
|
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultSkip, "skip file");
|
||||||
@ -611,11 +677,26 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("pg file missing - ignoreMissing=false");
|
TEST_TITLE("pg file missing - ignoreMissing=false");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = missingFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 0,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = missingFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
backupFile(
|
backupFile(repoFile, compressTypeNone, 1, false, cipherTypeNone, NULL, fileList), FileMissingError,
|
||||||
missingFile, false, 0, true, NULL, false, 0, missingFile, false, compressTypeNone, 1, backupLabel, false,
|
"unable to open missing file '" TEST_PATH "/pg/missing' for read");
|
||||||
cipherTypeNone, NULL),
|
|
||||||
FileMissingError, "unable to open missing file '" TEST_PATH "/pg/missing' for read");
|
|
||||||
|
|
||||||
// Create a pg file to backup
|
// Create a pg file to backup
|
||||||
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile");
|
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile");
|
||||||
@ -630,11 +711,28 @@ testRun(void)
|
|||||||
// where a file grows while a backup is running.
|
// where a file grows while a backup is running.
|
||||||
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile###");
|
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile###");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = true,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0xFFFFFFFFFFFFFFFF,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, NULL, true, 0xFFFFFFFFFFFFFFFF, pgFile, false, compressTypeNone, 1, backupLabel, false,
|
|
||||||
cipherTypeNone, NULL),
|
|
||||||
"file checksummed with pageChecksum enabled");
|
"file checksummed with pageChecksum enabled");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
||||||
TEST_RESULT_UINT(result.repoSize, 9, "repo=pgFile size");
|
TEST_RESULT_UINT(result.repoSize, 9, "repo=pgFile size");
|
||||||
@ -646,11 +744,26 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("pgFileSize, ignoreMissing=false, backupLabel, pgFileChecksumPage, pgFileChecksumPageLsnLimit");
|
TEST_TITLE("pgFileSize, ignoreMissing=false, backupLabel, pgFileChecksumPage, pgFileChecksumPageLsnLimit");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 8,
|
||||||
|
.pgFileCopyExactSize = false,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = true,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0xFFFFFFFFFFFFFFFF,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 8, false, NULL, true, 0xFFFFFFFFFFFFFFFF, pgFile, false, compressTypeNone, 1, backupLabel, false,
|
|
||||||
cipherTypeNone, NULL),
|
|
||||||
"backup file");
|
"backup file");
|
||||||
TEST_RESULT_UINT(result.copySize, 12, "copy size");
|
TEST_RESULT_UINT(result.copySize, 12, "copy size");
|
||||||
TEST_RESULT_UINT(result.repoSize, 12, "repo size");
|
TEST_RESULT_UINT(result.repoSize, 12, "repo size");
|
||||||
@ -662,12 +775,27 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file exists in repo and db, checksum match - NOOP");
|
TEST_TITLE("file exists in repo and db, checksum match - NOOP");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
// File exists in repo and db, pg checksum match, delta set, ignoreMissing false, hasReference - NOOP
|
// File exists in repo and db, pg checksum match, delta set, ignoreMissing false, hasReference - NOOP
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, true,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"file in db and repo, checksum equal, no ignoreMissing, no pageChecksum, delta, hasReference");
|
"file in db and repo, checksum equal, no ignoreMissing, no pageChecksum, delta, hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
||||||
TEST_RESULT_UINT(result.repoSize, 0, "repo size not set since already exists in repo");
|
TEST_RESULT_UINT(result.repoSize, 0, "repo size not set since already exists in repo");
|
||||||
@ -679,12 +807,27 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file exists in repo and db, checksum mismatch - COPY");
|
TEST_TITLE("file exists in repo and db, checksum mismatch - COPY");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("1234567890123456789012345678901234567890"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
// File exists in repo and db, pg checksum mismatch, delta set, ignoreMissing false, hasReference - COPY
|
// File exists in repo and db, pg checksum mismatch, delta set, ignoreMissing false, hasReference - COPY
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, STRDEF("1234567890123456789012345678901234567890"), false, 0, pgFile, true,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"file in db and repo, pg checksum not equal, no ignoreMissing, no pageChecksum, delta, hasReference");
|
"file in db and repo, pg checksum not equal, no ignoreMissing, no pageChecksum, delta, hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
||||||
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
||||||
@ -696,12 +839,27 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file exists in repo and pg, copy only exact file even if size passed is greater - COPY");
|
TEST_TITLE("file exists in repo and pg, copy only exact file even if size passed is greater - COPY");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9999999,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
// File exists in repo and pg, pg checksum same, pg size passed is different, delta set, ignoreMissing false, hasReference
|
// File exists in repo and pg, pg checksum same, pg size passed is different, delta set, ignoreMissing false, hasReference
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9999999, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, true,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"db & repo file, pg checksum same, pg size different, no ignoreMissing, no pageChecksum, delta, hasReference");
|
"db & repo file, pg checksum same, pg size different, no ignoreMissing, no pageChecksum, delta, hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 12, "copy=pgFile size");
|
TEST_RESULT_UINT(result.copySize, 12, "copy=pgFile size");
|
||||||
TEST_RESULT_UINT(result.repoSize, 12, "repo=pgFile size");
|
TEST_RESULT_UINT(result.repoSize, 12, "repo=pgFile size");
|
||||||
@ -713,13 +871,30 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("resumed file is missing in repo but present in resumed manifest, file same name in repo - RECOPY");
|
TEST_TITLE("resumed file is missing in repo but present in resumed manifest, file same name in repo - RECOPY");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = STRDEF(BOGUS_STR),
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
TEST_STORAGE_LIST(
|
TEST_STORAGE_LIST(
|
||||||
storageRepo(), STORAGE_REPO_BACKUP "/20190718-155825F", "testfile\n", .comment = "resumed file is missing in repo");
|
storageRepo(), STORAGE_REPO_BACKUP "/20190718-155825F", "testfile\n", .comment = "resumed file is missing in repo");
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, STRDEF(BOGUS_STR), false,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"backup 9 bytes of pgfile to file to resume in repo");
|
"backup 9 bytes of pgfile to file to resume in repo");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
||||||
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
||||||
@ -738,12 +913,29 @@ testRun(void)
|
|||||||
storageRepoWrite(), strZ(backupPathFile), "adifferentfile",
|
storageRepoWrite(), strZ(backupPathFile), "adifferentfile",
|
||||||
.comment = "create different file (size and checksum) with same name in repo");
|
.comment = "create different file (size and checksum) with same name in repo");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
// Delta set, ignoreMissing false, no hasReference
|
// Delta set, ignoreMissing false, no hasReference
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, false,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"db & repo file, pgFileMatch, repo checksum no match, no ignoreMissing, no pageChecksum, delta, no hasReference");
|
"db & repo file, pgFileMatch, repo checksum no match, no ignoreMissing, no pageChecksum, delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
TEST_RESULT_UINT(result.copySize, 9, "copy 9 bytes");
|
||||||
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
TEST_RESULT_UINT(result.repoSize, 9, "repo=copy size");
|
||||||
@ -755,11 +947,26 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file exists in repo but missing from db, checksum same in repo - SKIP");
|
TEST_TITLE("file exists in repo but missing from db, checksum same in repo - SKIP");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = missingFile,
|
||||||
|
.pgFileIgnoreMissing = true,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, true, cipherTypeNone, NULL, fileList), 0),
|
||||||
missingFile, true, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, false,
|
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeNone, NULL),
|
|
||||||
"file in repo only, checksum in repo equal, ignoreMissing=true, no pageChecksum, delta, no hasReference");
|
"file in repo only, checksum in repo equal, ignoreMissing=true, no pageChecksum, delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy=repo=0 size");
|
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy=repo=0 size");
|
||||||
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultSkip, "skip file");
|
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultSkip, "skip file");
|
||||||
@ -771,10 +978,28 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("compression set, all other boolean parameters false - COPY");
|
TEST_TITLE("compression set, all other boolean parameters false - COPY");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s.gz", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeGz, 3, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, NULL, false, 0, pgFile, false, compressTypeGz, 3, backupLabel, false, cipherTypeNone, NULL),
|
|
||||||
"pg file exists, no checksum, no ignoreMissing, compression, no pageChecksum, no delta, no hasReference");
|
"pg file exists, no checksum, no ignoreMissing, compression, no pageChecksum, no delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
||||||
TEST_RESULT_UINT(result.repoSize, 29, "repo compress size");
|
TEST_RESULT_UINT(result.repoSize, 29, "repo compress size");
|
||||||
@ -788,11 +1013,26 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("pg and repo file exist & match, prior checksum, compression - COPY CHECKSUM");
|
TEST_TITLE("pg and repo file exist & match, prior checksum, compression - COPY CHECKSUM");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeGz, 3, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
pgFile, false, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, false, compressTypeGz,
|
|
||||||
3, backupLabel, false, cipherTypeNone, NULL),
|
|
||||||
"pg file & repo exists, match, checksum, no ignoreMissing, compression, no pageChecksum, no delta, no hasReference");
|
"pg file & repo exists, match, checksum, no ignoreMissing, compression, no pageChecksum, no delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
TEST_RESULT_UINT(result.copySize, 9, "copy=pgFile size");
|
||||||
TEST_RESULT_UINT(result.repoSize, 0, "repo size not calculated");
|
TEST_RESULT_UINT(result.repoSize, 0, "repo size not calculated");
|
||||||
@ -808,12 +1048,29 @@ testRun(void)
|
|||||||
// Create zero sized file in pg
|
// Create zero sized file in pg
|
||||||
HRN_STORAGE_PUT_EMPTY(storagePgWrite(), "zerofile");
|
HRN_STORAGE_PUT_EMPTY(storagePgWrite(), "zerofile");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = STRDEF("zerofile"),
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 0,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = STRDEF("zerofile"),
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
// No prior checksum, no compression, no pageChecksum, no delta, no hasReference
|
// No prior checksum, no compression, no pageChecksum, no delta, no hasReference
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(backupFile(repoFile, compressTypeNone, 1, false, cipherTypeNone, NULL, fileList), 0),
|
||||||
STRDEF("zerofile"), false, 0, true, NULL, false, 0, STRDEF("zerofile"), false, compressTypeNone, 1, backupLabel,
|
|
||||||
false, cipherTypeNone, NULL),
|
|
||||||
"zero-sized pg file exists, no repo file, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
"zero-sized pg file exists, no repo file, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy=repo=pgFile size 0");
|
TEST_RESULT_UINT(result.copySize + result.repoSize, 0, "copy=repo=pgFile size 0");
|
||||||
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultCopy, "copy file");
|
TEST_RESULT_UINT(result.backupCopyResult, backupCopyResultCopy, "copy file");
|
||||||
@ -843,12 +1100,30 @@ testRun(void)
|
|||||||
// Create the pg path and pg file to backup
|
// Create the pg path and pg file to backup
|
||||||
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile");
|
HRN_STORAGE_PUT_Z(storagePgWrite(), strZ(pgFile), "atestfile");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = NULL,
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
|
repoFile = strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(backupLabel), strZ(file.manifestFile));
|
||||||
|
|
||||||
// No prior checksum, no compression, no pageChecksum, no delta, no hasReference
|
// No prior checksum, no compression, no pageChecksum, no delta, no hasReference
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(
|
||||||
pgFile, false, 9, true, NULL, false, 0, pgFile, false, compressTypeNone, 1, backupLabel, false, cipherTypeAes256Cbc,
|
backupFile(repoFile, compressTypeNone, 1, false, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS), fileList), 0),
|
||||||
STRDEF(TEST_CIPHER_PASS)),
|
|
||||||
"pg file exists, no repo file, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
"pg file exists, no repo file, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
||||||
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
||||||
@ -862,12 +1137,28 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("delta, copy file (size mismatch) to encrypted repo");
|
TEST_TITLE("delta, copy file (size mismatch) to encrypted repo");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 8,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
// Delta but pgFile does not match size passed, prior checksum, no compression, no pageChecksum, delta, no hasReference
|
// Delta but pgFile does not match size passed, prior checksum, no compression, no pageChecksum, delta, no hasReference
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(
|
||||||
pgFile, false, 8, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, false,
|
backupFile(repoFile, compressTypeNone, 1, true, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS), fileList), 0),
|
||||||
compressTypeNone, 1, backupLabel, true, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS)),
|
|
||||||
"pg and repo file exists, pgFileMatch false, no ignoreMissing, no pageChecksum, delta, no hasReference");
|
"pg and repo file exists, pgFileMatch false, no ignoreMissing, no pageChecksum, delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 8, "copy size set");
|
TEST_RESULT_UINT(result.copySize, 8, "copy size set");
|
||||||
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
||||||
@ -881,11 +1172,27 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("no delta, recopy (size mismatch) file to encrypted repo");
|
TEST_TITLE("no delta, recopy (size mismatch) file to encrypted repo");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(
|
||||||
pgFile, false, 9, true, STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, pgFile, false,
|
backupFile(repoFile, compressTypeNone, 0, false, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS), fileList), 0),
|
||||||
compressTypeNone, 0, backupLabel, false, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS)),
|
|
||||||
"pg and repo file exists, checksum mismatch, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
"pg and repo file exists, checksum mismatch, no ignoreMissing, no pageChecksum, no delta, no hasReference");
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
||||||
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
TEST_RESULT_UINT(result.repoSize, 32, "repo size set");
|
||||||
@ -899,11 +1206,27 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("no delta, recopy (checksum mismatch), file to encrypted repo");
|
TEST_TITLE("no delta, recopy (checksum mismatch), file to encrypted repo");
|
||||||
|
|
||||||
|
fileList = lstNewP(sizeof(BackupFile));
|
||||||
|
|
||||||
|
file = (BackupFile)
|
||||||
|
{
|
||||||
|
.pgFile = pgFile,
|
||||||
|
.pgFileIgnoreMissing = false,
|
||||||
|
.pgFileSize = 9,
|
||||||
|
.pgFileCopyExactSize = true,
|
||||||
|
.pgFileChecksum = STRDEF("1234567890123456789012345678901234567890"),
|
||||||
|
.pgFileChecksumPage = false,
|
||||||
|
.pgFileChecksumPageLsnLimit = 0,
|
||||||
|
.manifestFile = pgFile,
|
||||||
|
.manifestFileHasReference = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
lstAdd(fileList, &file);
|
||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
result,
|
result,
|
||||||
backupFile(
|
*(BackupFileResult *)lstGet(
|
||||||
pgFile, false, 9, true, STRDEF("1234567890123456789012345678901234567890"), false, 0, pgFile, false,
|
backupFile(repoFile, compressTypeNone, 0, false, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS), fileList), 0),
|
||||||
compressTypeNone, 0, backupLabel, false, cipherTypeAes256Cbc, STRDEF(TEST_CIPHER_PASS)),
|
|
||||||
"backup file");
|
"backup file");
|
||||||
|
|
||||||
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
TEST_RESULT_UINT(result.copySize, 9, "copy size set");
|
||||||
@ -1247,7 +1570,7 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_PTR(backupResumeFind((Manifest *)1, NULL), NULL, "find resumable backup");
|
TEST_RESULT_PTR(backupResumeFind((Manifest *)1, NULL), NULL, "find resumable backup");
|
||||||
|
|
||||||
TEST_RESULT_LOG("P00 WARN: backup '20191003-105320F' cannot be resumed: resume is disabled");
|
TEST_RESULT_LOG("P00 INFO: backup '20191003-105320F' cannot be resumed: resume is disabled");
|
||||||
|
|
||||||
TEST_STORAGE_LIST_EMPTY(storageRepo(), STORAGE_REPO_BACKUP, .comment = "check backup path removed");
|
TEST_STORAGE_LIST_EMPTY(storageRepo(), STORAGE_REPO_BACKUP, .comment = "check backup path removed");
|
||||||
|
|
||||||
@ -1373,6 +1696,30 @@ testRun(void)
|
|||||||
TEST_STORAGE_LIST_EMPTY(storageRepo(), STORAGE_REPO_BACKUP, .comment = "check backup path removed");
|
TEST_STORAGE_LIST_EMPTY(storageRepo(), STORAGE_REPO_BACKUP, .comment = "check backup path removed");
|
||||||
|
|
||||||
manifestResume->pub.data.backupOptionCompressType = compressTypeNone;
|
manifestResume->pub.data.backupOptionCompressType = compressTypeNone;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("cannot resume when bundling");
|
||||||
|
|
||||||
|
argList = strLstNew();
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptStanza, "test1");
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH "/repo");
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg");
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
|
||||||
|
hrnCfgArgRawStrId(argList, cfgOptType, backupTypeFull);
|
||||||
|
hrnCfgArgRawBool(argList, cfgOptBundle, true);
|
||||||
|
HRN_CFG_LOAD(cfgCmdBackup, argList);
|
||||||
|
|
||||||
|
manifestSave(
|
||||||
|
manifestResume,
|
||||||
|
storageWriteIo(
|
||||||
|
storageNewWriteP(
|
||||||
|
storageRepoWrite(), STRDEF(STORAGE_REPO_BACKUP "/20191003-105320F/" BACKUP_MANIFEST_FILE INFO_COPY_EXT))));
|
||||||
|
|
||||||
|
TEST_RESULT_PTR(backupResumeFind(manifest, NULL), NULL, "find resumable backup");
|
||||||
|
|
||||||
|
TEST_RESULT_LOG("P00 INFO: backup '20191003-105320F' cannot be resumed: resume is disabled");
|
||||||
|
|
||||||
|
TEST_STORAGE_LIST_EMPTY(storageRepo(), STORAGE_REPO_BACKUP, .comment = "check backup path removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
@ -1387,7 +1734,8 @@ testRun(void)
|
|||||||
ProtocolParallelJob *job = protocolParallelJobNew(VARSTRDEF("key"), protocolCommandNew(strIdFromZ("x")));
|
ProtocolParallelJob *job = protocolParallelJobNew(VARSTRDEF("key"), protocolCommandNew(strIdFromZ("x")));
|
||||||
protocolParallelJobErrorSet(job, errorTypeCode(&AssertError), STRDEF("error message"));
|
protocolParallelJobErrorSet(job, errorTypeCode(&AssertError), STRDEF("error message"));
|
||||||
|
|
||||||
TEST_ERROR(backupJobResult((Manifest *)1, NULL, STRDEF("log"), strLstNew(), job, 0, NULL), AssertError, "error message");
|
TEST_ERROR(
|
||||||
|
backupJobResult((Manifest *)1, NULL, storageTest, strLstNew(), job, false, 0, NULL), AssertError, "error message");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("report host/100% progress on noop result");
|
TEST_TITLE("report host/100% progress on noop result");
|
||||||
@ -1396,6 +1744,7 @@ testRun(void)
|
|||||||
job = protocolParallelJobNew(VARSTRDEF("pg_data/test"), protocolCommandNew(strIdFromZ("x")));
|
job = protocolParallelJobNew(VARSTRDEF("pg_data/test"), protocolCommandNew(strIdFromZ("x")));
|
||||||
|
|
||||||
PackWrite *const resultPack = protocolPackNew();
|
PackWrite *const resultPack = protocolPackNew();
|
||||||
|
pckWriteStrP(resultPack, STRDEF("pg_data/test"));
|
||||||
pckWriteU32P(resultPack, backupCopyResultNoOp);
|
pckWriteU32P(resultPack, backupCopyResultNoOp);
|
||||||
pckWriteU64P(resultPack, 0);
|
pckWriteU64P(resultPack, 0);
|
||||||
pckWriteU64P(resultPack, 0);
|
pckWriteU64P(resultPack, 0);
|
||||||
@ -1418,9 +1767,9 @@ testRun(void)
|
|||||||
uint64_t sizeProgress = 0;
|
uint64_t sizeProgress = 0;
|
||||||
|
|
||||||
TEST_RESULT_VOID(
|
TEST_RESULT_VOID(
|
||||||
backupJobResult(manifest, STRDEF("host"), STRDEF("log-test"), strLstNew(), job, 0, &sizeProgress), "log noop result");
|
backupJobResult(manifest, STRDEF("host"), storageTest, strLstNew(), job, false, 0, &sizeProgress), "log noop result");
|
||||||
|
|
||||||
TEST_RESULT_LOG("P00 DETAIL: match file from prior backup host:log-test (0B, 100%)");
|
TEST_RESULT_LOG("P00 DETAIL: match file from prior backup host:" TEST_PATH "/test (0B, 100%)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Offline tests should only be used to test offline functionality and errors easily tested in offline mode
|
// Offline tests should only be used to test offline functionality and errors easily tested in offline mode
|
||||||
@ -1717,7 +2066,7 @@ testRun(void)
|
|||||||
|
|
||||||
// Create a backup manifest that looks like a halted backup manifest
|
// Create a backup manifest that looks like a halted backup manifest
|
||||||
Manifest *manifestResume = manifestNewBuild(
|
Manifest *manifestResume = manifestNewBuild(
|
||||||
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, NULL, NULL);
|
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, false, NULL, NULL);
|
||||||
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
||||||
|
|
||||||
manifestResumeData->backupType = backupTypeFull;
|
manifestResumeData->backupType = backupTypeFull;
|
||||||
@ -1808,7 +2157,7 @@ testRun(void)
|
|||||||
|
|
||||||
// Create a backup manifest that looks like a halted backup manifest
|
// Create a backup manifest that looks like a halted backup manifest
|
||||||
Manifest *manifestResume = manifestNewBuild(
|
Manifest *manifestResume = manifestNewBuild(
|
||||||
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, NULL, NULL);
|
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, false, NULL, NULL);
|
||||||
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
||||||
|
|
||||||
manifestResumeData->backupType = backupTypeFull;
|
manifestResumeData->backupType = backupTypeFull;
|
||||||
@ -1998,7 +2347,7 @@ testRun(void)
|
|||||||
|
|
||||||
// Create a backup manifest that looks like a halted backup manifest
|
// Create a backup manifest that looks like a halted backup manifest
|
||||||
Manifest *manifestResume = manifestNewBuild(
|
Manifest *manifestResume = manifestNewBuild(
|
||||||
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, NULL, NULL);
|
storagePg(), PG_VERSION_95, hrnPgCatalogVersion(PG_VERSION_95), true, false, false, NULL, NULL);
|
||||||
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
ManifestData *manifestResumeData = (ManifestData *)manifestData(manifestResume);
|
||||||
|
|
||||||
manifestResumeData->backupType = backupTypeDiff;
|
manifestResumeData->backupType = backupTypeDiff;
|
||||||
@ -2621,6 +2970,138 @@ testRun(void)
|
|||||||
"pg_tblspc/32768/PG_11_201809051/1={}\n",
|
"pg_tblspc/32768/PG_11_201809051/1={}\n",
|
||||||
"compare file list");
|
"compare file list");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
|
TEST_TITLE("online 11 full backup with tablespaces and bundles");
|
||||||
|
|
||||||
|
backupTimeStart = BACKUP_EPOCH + 2400000;
|
||||||
|
|
||||||
|
{
|
||||||
|
// Load options
|
||||||
|
StringList *argList = strLstNew();
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptStanza, "test1");
|
||||||
|
hrnCfgArgRaw(argList, cfgOptRepoPath, repoPath);
|
||||||
|
hrnCfgArgRaw(argList, cfgOptPgPath, pg1Path);
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
|
||||||
|
hrnCfgArgRawStrId(argList, cfgOptType, backupTypeFull);
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptManifestSaveThreshold, "1");
|
||||||
|
hrnCfgArgRawBool(argList, cfgOptArchiveCopy, true);
|
||||||
|
hrnCfgArgRawZ(argList, cfgOptBufferSize, "16K");
|
||||||
|
hrnCfgArgRawBool(argList, cfgOptBundle, true);
|
||||||
|
HRN_CFG_LOAD(cfgCmdBackup, argList);
|
||||||
|
|
||||||
|
// Set to a smaller values than the defaults allow
|
||||||
|
cfgOptionSet(cfgOptBundleSize, cfgSourceParam, VARINT64(PG_PAGE_SIZE_DEFAULT));
|
||||||
|
|
||||||
|
// Zeroed file which passes page checksums
|
||||||
|
Buffer *relation = bufNew(PG_PAGE_SIZE_DEFAULT * 3);
|
||||||
|
memset(bufPtr(relation), 0, bufSize(relation));
|
||||||
|
bufUsedSet(relation, bufSize(relation));
|
||||||
|
|
||||||
|
HRN_STORAGE_PUT(storagePgWrite(), PG_PATH_BASE "/1/2", relation, .timeModified = backupTimeStart);
|
||||||
|
|
||||||
|
// Old files
|
||||||
|
HRN_STORAGE_PUT_Z(storagePgWrite(), "postgresql.auto.conf", "CONFIGSTUFF2", .timeModified = 1500000000);
|
||||||
|
HRN_STORAGE_PUT_Z(storagePgWrite(), "stuff.conf", "CONFIGSTUFF3", .timeModified = 1500000000);
|
||||||
|
|
||||||
|
// File that will get skipped while bundling smaller files and end up a bundle by itself
|
||||||
|
Buffer *bigish = bufNew(PG_PAGE_SIZE_DEFAULT - 1);
|
||||||
|
memset(bufPtr(bigish), 0, bufSize(bigish));
|
||||||
|
bufUsedSet(bigish, bufSize(bigish));
|
||||||
|
|
||||||
|
HRN_STORAGE_PUT(storagePgWrite(), "bigish.dat", bigish, .timeModified = 1500000001);
|
||||||
|
|
||||||
|
// Run backup
|
||||||
|
testBackupPqScriptP(PG_VERSION_11, backupTimeStart, .walCompressType = compressTypeGz, .walTotal = 2);
|
||||||
|
TEST_RESULT_VOID(cmdBackup(), "backup");
|
||||||
|
|
||||||
|
TEST_RESULT_LOG(
|
||||||
|
"P00 INFO: execute non-exclusive pg_start_backup(): backup begins after the next regular checkpoint completes\n"
|
||||||
|
"P00 INFO: backup start archive = 0000000105DB8EB000000000, lsn = 5db8eb0/0\n"
|
||||||
|
"P00 INFO: check archive for segment 0000000105DB8EB000000000\n"
|
||||||
|
"P00 DETAIL: store zero-length file " TEST_PATH "/pg1/pg_tblspc/32768/PG_11_201809051/1/5\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/base/1/2 (24KB, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/base/1/1 (8KB, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/global/pg_control (8KB, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/stuff.conf (12B, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.auto.conf (12B, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/postgresql.conf (11B, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/PG_VERSION (2B, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P01 DETAIL: backup file " TEST_PATH "/pg1/bigish.dat (8.0KB, [PCT]) checksum [SHA1]\n"
|
||||||
|
"P00 INFO: execute non-exclusive pg_stop_backup() and wait for all WAL segments to archive\n"
|
||||||
|
"P00 INFO: backup stop archive = 0000000105DB8EB000000001, lsn = 5db8eb0/180000\n"
|
||||||
|
"P00 DETAIL: wrote 'backup_label' file returned from pg_stop_backup()\n"
|
||||||
|
"P00 DETAIL: wrote 'tablespace_map' file returned from pg_stop_backup()\n"
|
||||||
|
"P00 INFO: check archive for segment(s) 0000000105DB8EB000000000:0000000105DB8EB000000001\n"
|
||||||
|
"P00 DETAIL: copy segment 0000000105DB8EB000000000 to backup\n"
|
||||||
|
"P00 DETAIL: copy segment 0000000105DB8EB000000001 to backup\n"
|
||||||
|
"P00 INFO: new backup label = 20191030-014640F\n"
|
||||||
|
"P00 INFO: full backup size = [SIZE], file total = 13");
|
||||||
|
|
||||||
|
TEST_RESULT_STR_Z(
|
||||||
|
testBackupValidate(storageRepo(), STRDEF(STORAGE_REPO_BACKUP "/latest")),
|
||||||
|
". {link, d=20191030-014640F}\n"
|
||||||
|
"bundle {path}\n"
|
||||||
|
"bundle/1/pg_data/base/1/2 {file, s=24576}\n"
|
||||||
|
"bundle/2/pg_data/base/1/1 {file, s=8192}\n"
|
||||||
|
"bundle/3/pg_data/global/pg_control {file, s=8192}\n"
|
||||||
|
"bundle/4/pg_data/PG_VERSION {file, s=2}\n"
|
||||||
|
"bundle/4/pg_data/postgresql.auto.conf {file, s=12}\n"
|
||||||
|
"bundle/4/pg_data/postgresql.conf {file, s=11}\n"
|
||||||
|
"bundle/4/pg_data/stuff.conf {file, s=12}\n"
|
||||||
|
"bundle/5/pg_data/bigish.dat {file, s=8191}\n"
|
||||||
|
"pg_data {path}\n"
|
||||||
|
"pg_data/backup_label.gz {file, s=17}\n"
|
||||||
|
"pg_data/pg_wal {path}\n"
|
||||||
|
"pg_data/pg_wal/0000000105DB8EB000000000.gz {file, s=1048576}\n"
|
||||||
|
"pg_data/pg_wal/0000000105DB8EB000000001.gz {file, s=1048576}\n"
|
||||||
|
"pg_data/tablespace_map.gz {file, s=19}\n"
|
||||||
|
"--------\n"
|
||||||
|
"[backup:target]\n"
|
||||||
|
"pg_data={\"path\":\"" TEST_PATH "/pg1\",\"type\":\"path\"}\n"
|
||||||
|
"pg_tblspc/32768={\"path\":\"../../pg1-tblspc/32768\",\"tablespace-id\":\"32768\""
|
||||||
|
",\"tablespace-name\":\"tblspc32768\",\"type\":\"link\"}\n"
|
||||||
|
"\n"
|
||||||
|
"[target:file]\n"
|
||||||
|
"pg_data/PG_VERSION={\"checksum\":\"17ba0791499db908433b80f37c5fbc89b870084b\",\"size\":2"
|
||||||
|
",\"timestamp\":1572200000}\n"
|
||||||
|
"pg_data/backup_label={\"checksum\":\"8e6f41ac87a7514be96260d65bacbffb11be77dc\",\"size\":17"
|
||||||
|
",\"timestamp\":1572400002}\n"
|
||||||
|
"pg_data/base/1/1={\"checksum\":\"0631457264ff7f8d5fb1edc2c0211992a67c73e6\",\"checksum-page\":true,\"size\":8192"
|
||||||
|
",\"timestamp\":1572200000}\n"
|
||||||
|
"pg_data/base/1/2={\"checksum\":\"ebdd38b69cd5b9f2d00d273c981e16960fbbb4f7\",\"checksum-page\":true,\"size\":24576"
|
||||||
|
",\"timestamp\":1572400000}\n"
|
||||||
|
"pg_data/bigish.dat={\"checksum\":\"3e5175386be683d2f231f3fa3eab892a799082f7\",\"size\":8191"
|
||||||
|
",\"timestamp\":1500000001}\n"
|
||||||
|
"pg_data/global/pg_control={\"size\":8192,\"timestamp\":1572400000}\n"
|
||||||
|
"pg_data/pg_wal/0000000105DB8EB000000000={\"size\":1048576,\"timestamp\":1572400002}\n"
|
||||||
|
"pg_data/pg_wal/0000000105DB8EB000000001={\"size\":1048576,\"timestamp\":1572400002}\n"
|
||||||
|
"pg_data/postgresql.auto.conf={\"checksum\":\"e873a5cb5a67e48761e7b619c531311404facdce\",\"size\":12"
|
||||||
|
",\"timestamp\":1500000000}\n"
|
||||||
|
"pg_data/postgresql.conf={\"checksum\":\"e3db315c260e79211b7b52587123b7aa060f30ab\",\"size\":11"
|
||||||
|
",\"timestamp\":1570000000}\n"
|
||||||
|
"pg_data/stuff.conf={\"checksum\":\"55a9d0d18b77789c7722abe72aa905e2dc85bb5d\",\"size\":12"
|
||||||
|
",\"timestamp\":1500000000}\n"
|
||||||
|
"pg_data/tablespace_map={\"checksum\":\"87fe624d7976c2144e10afcb7a9a49b071f35e9c\",\"size\":19"
|
||||||
|
",\"timestamp\":1572400002}\n"
|
||||||
|
"pg_tblspc/32768/PG_11_201809051/1/5={\"checksum-page\":true,\"size\":0,\"timestamp\":1572200000}\n"
|
||||||
|
"\n"
|
||||||
|
"[target:link]\n"
|
||||||
|
"pg_data/pg_tblspc/32768={\"destination\":\"../../pg1-tblspc/32768\"}\n"
|
||||||
|
"\n"
|
||||||
|
"[target:path]\n"
|
||||||
|
"pg_data={}\n"
|
||||||
|
"pg_data/base={}\n"
|
||||||
|
"pg_data/base/1={}\n"
|
||||||
|
"pg_data/global={}\n"
|
||||||
|
"pg_data/pg_tblspc={}\n"
|
||||||
|
"pg_data/pg_wal={}\n"
|
||||||
|
"pg_tblspc={}\n"
|
||||||
|
"pg_tblspc/32768={}\n"
|
||||||
|
"pg_tblspc/32768/PG_11_201809051={}\n"
|
||||||
|
"pg_tblspc/32768/PG_11_201809051/1={}\n",
|
||||||
|
"compare file list");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FUNCTION_HARNESS_RETURN_VOID();
|
FUNCTION_HARNESS_RETURN_VOID();
|
||||||
|
@ -175,9 +175,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("sparse-zero"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), true, 0x10000000000UL, 1557432154, 0600, TEST_USER_STR,
|
STRDEF("sparse-zero"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), true, 0x10000000000UL, 1557432154, 0600,
|
||||||
TEST_GROUP_STR, 0, true, false, NULL),
|
TEST_USER_STR, TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
false, "zero sparse 1TB file");
|
false, "zero sparse 1TB file");
|
||||||
TEST_RESULT_UINT(storageInfoP(storagePg(), STRDEF("sparse-zero")).size, 0x10000000000UL, "check size");
|
TEST_RESULT_UINT(storageInfoP(storagePg(), STRDEF("sparse-zero")).size, 0x10000000000UL, "check size");
|
||||||
|
|
||||||
@ -186,9 +186,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("normal-zero"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("normal-zero"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600,
|
||||||
false, false, NULL),
|
TEST_USER_STR, TEST_GROUP_STR, 0, false, false, NULL),
|
||||||
true, "zero-length file");
|
true, "zero-length file");
|
||||||
TEST_RESULT_UINT(storageInfoP(storagePg(), STRDEF("normal-zero")).size, 0, "check size");
|
TEST_RESULT_UINT(storageInfoP(storagePg(), STRDEF("normal-zero")).size, 0, "check size");
|
||||||
|
|
||||||
@ -202,9 +202,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeGz, STRDEF("normal"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s.gz", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeGz,
|
||||||
STRDEF("ffffffffffffffffffffffffffffffffffffffff"), false, 7, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("normal"), STRDEF("ffffffffffffffffffffffffffffffffffffffff"), false, 7, 1557432154, 0600, TEST_USER_STR,
|
||||||
false, false, STRDEF("badpass")),
|
TEST_GROUP_STR, 0, false, false, STRDEF("badpass")),
|
||||||
ChecksumError,
|
ChecksumError,
|
||||||
"error restoring 'normal': actual checksum 'd1cd8a7d11daa26814b93eb604e1d49ab4b43770' does not match expected checksum"
|
"error restoring 'normal': actual checksum 'd1cd8a7d11daa26814b93eb604e1d49ab4b43770' does not match expected checksum"
|
||||||
" 'ffffffffffffffffffffffffffffffffffffffff'");
|
" 'ffffffffffffffffffffffffffffffffffffffff'");
|
||||||
@ -218,9 +218,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeGz, STRDEF("normal"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s.gz", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeGz,
|
||||||
STRDEF("d1cd8a7d11daa26814b93eb604e1d49ab4b43770"), false, 7, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("normal"), STRDEF("d1cd8a7d11daa26814b93eb604e1d49ab4b43770"), false, 7, 1557432154, 0600, TEST_USER_STR,
|
||||||
false, false, STRDEF("badpass")),
|
TEST_GROUP_STR, 0, false, false, STRDEF("badpass")),
|
||||||
true, "copy file");
|
true, "copy file");
|
||||||
|
|
||||||
StorageInfo info = storageInfoP(storagePg(), STRDEF("normal"));
|
StorageInfo info = storageInfoP(storagePg(), STRDEF("normal"));
|
||||||
@ -242,9 +242,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
true, false, NULL),
|
TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
true, "sha1 delta missing");
|
true, "sha1 delta missing");
|
||||||
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
||||||
|
|
||||||
@ -256,18 +256,18 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
true, false, NULL),
|
TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
false, "sha1 delta existing");
|
false, "sha1 delta existing");
|
||||||
|
|
||||||
ioBufferSizeSet(oldBufferSize);
|
ioBufferSizeSet(oldBufferSize);
|
||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
1557432155, true, true, NULL),
|
TEST_GROUP_STR, 1557432155, true, true, NULL),
|
||||||
false, "sha1 delta force existing");
|
false, "sha1 delta force existing");
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
@ -278,9 +278,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
true, false, NULL),
|
TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
true, "sha1 delta existing, size differs");
|
true, "sha1 delta existing, size differs");
|
||||||
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
||||||
|
|
||||||
@ -288,9 +288,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
1557432155, true, true, NULL),
|
TEST_GROUP_STR, 1557432155, true, true, NULL),
|
||||||
true, "delta force existing, size differs");
|
true, "delta force existing, size differs");
|
||||||
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
||||||
|
|
||||||
@ -302,9 +302,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
true, false, NULL),
|
TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
true, "sha1 delta existing, content differs");
|
true, "sha1 delta existing, content differs");
|
||||||
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
TEST_STORAGE_GET(storagePg(), "delta", "atestfile", .comment = "check contents");
|
||||||
|
|
||||||
@ -312,16 +312,16 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
1557432155, true, true, NULL),
|
TEST_GROUP_STR, 1557432155, true, true, NULL),
|
||||||
true, "delta force existing, timestamp differs");
|
true, "delta force existing, timestamp differs");
|
||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 9, 1557432154, 0600, TEST_USER_STR,
|
||||||
1557432153, true, true, NULL),
|
TEST_GROUP_STR, 1557432153, true, true, NULL),
|
||||||
true, "delta force existing, timestamp after copy time");
|
true, "delta force existing, timestamp after copy time");
|
||||||
|
|
||||||
// Change the existing file to zero-length
|
// Change the existing file to zero-length
|
||||||
@ -329,9 +329,9 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_RESULT_BOOL(
|
TEST_RESULT_BOOL(
|
||||||
restoreFile(
|
restoreFile(
|
||||||
repoFile1, repoIdx, repoFileReferenceFull, compressTypeNone, STRDEF("delta"),
|
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strZ(repoFileReferenceFull), strZ(repoFile1)), repoIdx, 0, NULL, compressTypeNone,
|
||||||
STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600, TEST_USER_STR, TEST_GROUP_STR, 0,
|
STRDEF("delta"), STRDEF("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"), false, 0, 1557432154, 0600, TEST_USER_STR,
|
||||||
true, false, NULL),
|
TEST_GROUP_STR, 0, true, false, NULL),
|
||||||
false, "sha1 delta existing, content differs");
|
false, "sha1 delta existing, content differs");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2412,17 +2412,18 @@ testRun(void)
|
|||||||
(ManifestFile){
|
(ManifestFile){
|
||||||
.name = STRDEF(TEST_PGDATA PG_PATH_GLOBAL "/999"), .size = 0, .timestamp = 1482182860,
|
.name = STRDEF(TEST_PGDATA PG_PATH_GLOBAL "/999"), .size = 0, .timestamp = 1482182860,
|
||||||
.mode = 0600, .group = groupName(), .user = userName(),
|
.mode = 0600, .group = groupName(), .user = userName(),
|
||||||
.checksumSha1 = HASH_TYPE_SHA1_ZERO, .reference = STRDEF(TEST_LABEL)});
|
.checksumSha1 = HASH_TYPE_SHA1_ZERO});
|
||||||
HRN_STORAGE_PUT_EMPTY(storageRepoWrite(), TEST_REPO_PATH PG_PATH_GLOBAL "/999");
|
HRN_STORAGE_PUT_EMPTY(storageRepoWrite(), TEST_REPO_PATH PG_PATH_GLOBAL "/999");
|
||||||
|
|
||||||
// PG_VERSION
|
// PG_VERSION
|
||||||
manifestFileAdd(
|
manifestFileAdd(
|
||||||
manifest,
|
manifest,
|
||||||
(ManifestFile){
|
(ManifestFile){
|
||||||
.name = STRDEF(TEST_PGDATA PG_FILE_PGVERSION), .size = 4, .timestamp = 1482182860,
|
.name = STRDEF(TEST_PGDATA PG_FILE_PGVERSION), .size = 4, .sizeRepo = 4, .timestamp = 1482182860,
|
||||||
.mode = 0600, .group = groupName(), .user = userName(),
|
.mode = 0600, .group = groupName(), .user = userName(), .bundleId = 1, .bundleOffset = 0,
|
||||||
.checksumSha1 = "8dbabb96e032b8d9f1993c0e4b9141e71ade01a1"});
|
.reference = STRDEF(TEST_LABEL), .checksumSha1 = "8dbabb96e032b8d9f1993c0e4b9141e71ade01a1"});
|
||||||
HRN_STORAGE_PUT_Z(storageRepoWrite(), TEST_REPO_PATH PG_FILE_PGVERSION, PG_VERSION_94_STR "\n");
|
HRN_STORAGE_PUT_Z(
|
||||||
|
storageRepoWrite(), STORAGE_REPO_BACKUP "/" TEST_LABEL "/bundle/1", PG_VERSION_94_STR "\n" PG_VERSION_94_STR "\n");
|
||||||
|
|
||||||
// base directory
|
// base directory
|
||||||
manifestPathAdd(
|
manifestPathAdd(
|
||||||
@ -2436,14 +2437,13 @@ testRun(void)
|
|||||||
&(ManifestPath){
|
&(ManifestPath){
|
||||||
.name = STRDEF(TEST_PGDATA PG_PATH_BASE "/1"), .mode = 0700, .group = groupName(), .user = userName()});
|
.name = STRDEF(TEST_PGDATA PG_PATH_BASE "/1"), .mode = 0700, .group = groupName(), .user = userName()});
|
||||||
|
|
||||||
// base/1/PG_VERSION
|
// base/1/PG_VERSION. File was written as part of bundle 1 above
|
||||||
manifestFileAdd(
|
manifestFileAdd(
|
||||||
manifest,
|
manifest,
|
||||||
(ManifestFile){
|
(ManifestFile){
|
||||||
.name = STRDEF(TEST_PGDATA "base/1/" PG_FILE_PGVERSION), .size = 4, .timestamp = 1482182860,
|
.name = STRDEF(TEST_PGDATA "base/1/" PG_FILE_PGVERSION), .size = 4, .sizeRepo = 4, .timestamp = 1482182860,
|
||||||
.mode = 0600, .group = groupName(), .user = userName(),
|
.mode = 0600, .group = groupName(), .user = userName(), .bundleId = 1, .bundleOffset = 4,
|
||||||
.checksumSha1 = "8dbabb96e032b8d9f1993c0e4b9141e71ade01a1"});
|
.checksumSha1 = "8dbabb96e032b8d9f1993c0e4b9141e71ade01a1"});
|
||||||
HRN_STORAGE_PUT_Z(storageRepoWrite(), TEST_REPO_PATH "base/1/" PG_FILE_PGVERSION, PG_VERSION_94_STR "\n");
|
|
||||||
|
|
||||||
// base/1/2
|
// base/1/2
|
||||||
fileBuffer = bufNew(8192);
|
fileBuffer = bufNew(8192);
|
||||||
|
@ -856,19 +856,22 @@ testRun(void)
|
|||||||
|
|
||||||
String *filePathName = strNewZ(STORAGE_REPO_ARCHIVE "/testfile");
|
String *filePathName = strNewZ(STORAGE_REPO_ARCHIVE "/testfile");
|
||||||
HRN_STORAGE_PUT_EMPTY(storageRepoWrite(), strZ(filePathName));
|
HRN_STORAGE_PUT_EMPTY(storageRepoWrite(), strZ(filePathName));
|
||||||
TEST_RESULT_UINT(verifyFile(filePathName, STRDEF(HASH_TYPE_SHA1_ZERO), 0, NULL), verifyOk, "file ok");
|
TEST_RESULT_UINT(
|
||||||
|
verifyFile(filePathName, 0, NULL, compressTypeNone, STRDEF(HASH_TYPE_SHA1_ZERO), 0, NULL), verifyOk, "file ok");
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file size invalid in archive");
|
TEST_TITLE("file size invalid in archive");
|
||||||
|
|
||||||
HRN_STORAGE_PUT_Z(storageRepoWrite(), strZ(filePathName), fileContents);
|
HRN_STORAGE_PUT_Z(storageRepoWrite(), strZ(filePathName), fileContents);
|
||||||
TEST_RESULT_UINT(verifyFile(filePathName, fileChecksum, 0, NULL), verifySizeInvalid, "file size invalid");
|
TEST_RESULT_UINT(
|
||||||
|
verifyFile(filePathName, 0, NULL, compressTypeNone, fileChecksum, 0, NULL), verifySizeInvalid, "file size invalid");
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("file missing in archive");
|
TEST_TITLE("file missing in archive");
|
||||||
|
|
||||||
TEST_RESULT_UINT(
|
TEST_RESULT_UINT(
|
||||||
verifyFile(
|
verifyFile(strNewFmt(STORAGE_REPO_ARCHIVE "/missingFile"), 0, NULL, compressTypeNone, fileChecksum, 0, NULL),
|
||||||
strNewFmt(STORAGE_REPO_ARCHIVE "/missingFile"), fileChecksum, 0, NULL), verifyFileMissing, "file missing");
|
verifyFileMissing, "file missing");
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_TITLE("encrypted/compressed file in backup");
|
TEST_TITLE("encrypted/compressed file in backup");
|
||||||
@ -881,10 +884,11 @@ testRun(void)
|
|||||||
|
|
||||||
strCatZ(filePathName, ".gz");
|
strCatZ(filePathName, ".gz");
|
||||||
TEST_RESULT_UINT(
|
TEST_RESULT_UINT(
|
||||||
verifyFile(filePathName, fileChecksum, fileSize, STRDEF("pass")), verifyOk, "file encrypted compressed ok");
|
verifyFile(filePathName, 0, NULL, compressTypeGz, fileChecksum, fileSize, STRDEF("pass")),
|
||||||
|
verifyOk, "file encrypted compressed ok");
|
||||||
TEST_RESULT_UINT(
|
TEST_RESULT_UINT(
|
||||||
verifyFile(
|
verifyFile(
|
||||||
filePathName, STRDEF("badchecksum"), fileSize, STRDEF("pass")), verifyChecksumMismatch,
|
filePathName, 0, NULL, compressTypeGz, STRDEF("badchecksum"), fileSize, STRDEF("pass")), verifyChecksumMismatch,
|
||||||
"file encrypted compressed checksum mismatch");
|
"file encrypted compressed checksum mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1383,6 +1387,7 @@ testRun(void)
|
|||||||
// Create valid full backup and valid diff backup
|
// Create valid full backup and valid diff backup
|
||||||
manifestContent = strNewFmt(
|
manifestContent = strNewFmt(
|
||||||
TEST_MANIFEST_HEADER
|
TEST_MANIFEST_HEADER
|
||||||
|
"backup-bundle=true\n"
|
||||||
"\n"
|
"\n"
|
||||||
"[backup:db]\n"
|
"[backup:db]\n"
|
||||||
TEST_BACKUP_DB2_11
|
TEST_BACKUP_DB2_11
|
||||||
@ -1391,7 +1396,8 @@ testRun(void)
|
|||||||
TEST_MANIFEST_DB
|
TEST_MANIFEST_DB
|
||||||
"\n"
|
"\n"
|
||||||
"[target:file]\n"
|
"[target:file]\n"
|
||||||
"pg_data/validfile={\"checksum\":\"%s\",\"size\":%u,\"timestamp\":1565282114}\n"
|
"pg_data/validfile={\"bni\":1,\"bno\":3,\"checksum\":\"%s\",\"size\":%u,\"timestamp\":1565282114}\n"
|
||||||
|
"pg_data/zerofile={\"size\":0,\"timestamp\":1565282114}\n"
|
||||||
TEST_MANIFEST_FILE_DEFAULT
|
TEST_MANIFEST_FILE_DEFAULT
|
||||||
TEST_MANIFEST_LINK
|
TEST_MANIFEST_LINK
|
||||||
TEST_MANIFEST_LINK_DEFAULT
|
TEST_MANIFEST_LINK_DEFAULT
|
||||||
@ -1407,7 +1413,8 @@ testRun(void)
|
|||||||
.comment = "valid manifest copy - full");
|
.comment = "valid manifest copy - full");
|
||||||
|
|
||||||
HRN_STORAGE_PUT_Z(
|
HRN_STORAGE_PUT_Z(
|
||||||
storageRepoWrite(), STORAGE_REPO_BACKUP "/20201119-163000F/pg_data/validfile", fileContents, .comment = "valid file");
|
storageRepoWrite(), STORAGE_REPO_BACKUP "/20201119-163000F/bundle/1", strZ(strNewFmt("XXX%s", fileContents)),
|
||||||
|
.comment = "valid file");
|
||||||
|
|
||||||
// Create WAL file with just header info and small WAL size
|
// Create WAL file with just header info and small WAL size
|
||||||
Buffer *walBuffer = bufNew((size_t)(1024 * 1024));
|
Buffer *walBuffer = bufNew((size_t)(1024 * 1024));
|
||||||
@ -1434,7 +1441,7 @@ testRun(void)
|
|||||||
" missing: 1, checksum invalid: 1, size invalid: 1, other: 0\n"
|
" missing: 1, checksum invalid: 1, size invalid: 1, other: 0\n"
|
||||||
" backup: 20181119-152900F_20181119-152909D, status: invalid, total files checked: 1, total valid files: 0\n"
|
" backup: 20181119-152900F_20181119-152909D, status: invalid, total files checked: 1, total valid files: 0\n"
|
||||||
" missing: 0, checksum invalid: 1, size invalid: 0, other: 0\n"
|
" missing: 0, checksum invalid: 1, size invalid: 0, other: 0\n"
|
||||||
" backup: 20201119-163000F, status: valid, total files checked: 1, total valid files: 1\n"
|
" backup: 20201119-163000F, status: valid, total files checked: 2, total valid files: 2\n"
|
||||||
" missing: 0, checksum invalid: 0, size invalid: 0, other: 0");
|
" missing: 0, checksum invalid: 0, size invalid: 0, other: 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ testRun(void)
|
|||||||
// -------------------------------------------------------------------------------------------------------------------------
|
// -------------------------------------------------------------------------------------------------------------------------
|
||||||
TEST_RESULT_UINT(sizeof(ManifestLoadFound), TEST_64BIT() ? 1 : 1, "check size of ManifestLoadFound");
|
TEST_RESULT_UINT(sizeof(ManifestLoadFound), TEST_64BIT() ? 1 : 1, "check size of ManifestLoadFound");
|
||||||
TEST_RESULT_UINT(sizeof(ManifestPath), TEST_64BIT() ? 32 : 16, "check size of ManifestPath");
|
TEST_RESULT_UINT(sizeof(ManifestPath), TEST_64BIT() ? 32 : 16, "check size of ManifestPath");
|
||||||
TEST_RESULT_UINT(sizeof(ManifestFile), TEST_64BIT() ? 120 : 92, "check size of ManifestFile");
|
TEST_RESULT_UINT(sizeof(ManifestFile), TEST_64BIT() ? 136 : 108, "check size of ManifestFile");
|
||||||
}
|
}
|
||||||
|
|
||||||
// *****************************************************************************************************************************
|
// *****************************************************************************************************************************
|
||||||
@ -291,7 +291,7 @@ testRun(void)
|
|||||||
// Test tablespace error
|
// Test tablespace error
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(
|
manifestNewBuild(
|
||||||
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, exclusionList, tablespaceList),
|
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, exclusionList, tablespaceList),
|
||||||
AssertError,
|
AssertError,
|
||||||
"tablespace with oid 1 not found in tablespace map\n"
|
"tablespace with oid 1 not found in tablespace map\n"
|
||||||
"HINT: was a tablespace created or dropped during the backup?");
|
"HINT: was a tablespace created or dropped during the backup?");
|
||||||
@ -307,7 +307,8 @@ testRun(void)
|
|||||||
|
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest,
|
manifest,
|
||||||
manifestNewBuild(storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, NULL, tablespaceList),
|
manifestNewBuild(
|
||||||
|
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, NULL, tablespaceList),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
Buffer *contentSave = bufNew(0);
|
Buffer *contentSave = bufNew(0);
|
||||||
@ -405,7 +406,8 @@ testRun(void)
|
|||||||
|
|
||||||
// Test manifest - temp tables, unlogged tables, pg_serial and pg_xlog files ignored
|
// Test manifest - temp tables, unlogged tables, pg_serial and pg_xlog files ignored
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest, manifestNewBuild(storagePg, PG_VERSION_91, hrnPgCatalogVersion(PG_VERSION_91), true, false, NULL, NULL),
|
manifest,
|
||||||
|
manifestNewBuild(storagePg, PG_VERSION_91, hrnPgCatalogVersion(PG_VERSION_91), true, false, false, NULL, NULL),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
contentSave = bufNew(0);
|
contentSave = bufNew(0);
|
||||||
@ -484,7 +486,8 @@ testRun(void)
|
|||||||
|
|
||||||
// Test manifest - pg_snapshots files ignored
|
// Test manifest - pg_snapshots files ignored
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest, manifestNewBuild(storagePg, PG_VERSION_92, hrnPgCatalogVersion(PG_VERSION_92), false, false, NULL, NULL),
|
manifest,
|
||||||
|
manifestNewBuild(storagePg, PG_VERSION_92, hrnPgCatalogVersion(PG_VERSION_92), false, false, false, NULL, NULL),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
contentSave = bufNew(0);
|
contentSave = bufNew(0);
|
||||||
@ -546,7 +549,7 @@ testRun(void)
|
|||||||
THROW_ON_SYS_ERROR(symlink(TEST_PATH "/wal", TEST_PATH "/wal/wal") == -1, FileOpenError, "unable to create symlink");
|
THROW_ON_SYS_ERROR(symlink(TEST_PATH "/wal", TEST_PATH "/wal/wal") == -1, FileOpenError, "unable to create symlink");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_92, hrnPgCatalogVersion(PG_VERSION_92), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_92, hrnPgCatalogVersion(PG_VERSION_92), false, false, false, NULL, NULL),
|
||||||
LinkDestinationError,
|
LinkDestinationError,
|
||||||
"link 'pg_xlog/wal' (" TEST_PATH "/wal) destination is the same directory as link 'pg_xlog' (" TEST_PATH "/wal)");
|
"link 'pg_xlog/wal' (" TEST_PATH "/wal) destination is the same directory as link 'pg_xlog' (" TEST_PATH "/wal)");
|
||||||
|
|
||||||
@ -600,7 +603,8 @@ testRun(void)
|
|||||||
|
|
||||||
// Test manifest - pg_dynshmem, pg_replslot and postgresql.auto.conf.tmp files ignored
|
// Test manifest - pg_dynshmem, pg_replslot and postgresql.auto.conf.tmp files ignored
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest, manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, true, NULL, NULL),
|
manifest,
|
||||||
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, true, false, NULL, NULL),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
contentSave = bufNew(0);
|
contentSave = bufNew(0);
|
||||||
@ -693,7 +697,7 @@ testRun(void)
|
|||||||
|
|
||||||
// Tablespace link errors when correct verion not found
|
// Tablespace link errors when correct verion not found
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_12, hrnPgCatalogVersion(PG_VERSION_12), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_12, hrnPgCatalogVersion(PG_VERSION_12), false, false, false, NULL, NULL),
|
||||||
FileOpenError,
|
FileOpenError,
|
||||||
"unable to get info for missing path/file '" TEST_PATH "/pg/pg_tblspc/1/PG_12_201909212': [2] No such file or"
|
"unable to get info for missing path/file '" TEST_PATH "/pg/pg_tblspc/1/PG_12_201909212': [2] No such file or"
|
||||||
" directory");
|
" directory");
|
||||||
@ -712,7 +716,8 @@ testRun(void)
|
|||||||
// and backup_label ignored. Old recovery files and pg_xlog are now just another file/directory and will not be ignored.
|
// and backup_label ignored. Old recovery files and pg_xlog are now just another file/directory and will not be ignored.
|
||||||
// pg_wal contents will be ignored online. pg_clog pgVersion > 10 primary:true, pg_xact pgVersion > 10 primary:false
|
// pg_wal contents will be ignored online. pg_clog pgVersion > 10 primary:true, pg_xact pgVersion > 10 primary:false
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest, manifestNewBuild(storagePg, PG_VERSION_12, hrnPgCatalogVersion(PG_VERSION_12), true, false, NULL, NULL),
|
manifest,
|
||||||
|
manifestNewBuild(storagePg, PG_VERSION_12, hrnPgCatalogVersion(PG_VERSION_12), true, false, false, NULL, NULL),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
contentSave = bufNew(0);
|
contentSave = bufNew(0);
|
||||||
@ -784,7 +789,8 @@ testRun(void)
|
|||||||
|
|
||||||
// pg_wal not ignored
|
// pg_wal not ignored
|
||||||
TEST_ASSIGN(
|
TEST_ASSIGN(
|
||||||
manifest, manifestNewBuild(storagePg, PG_VERSION_13, hrnPgCatalogVersion(PG_VERSION_13), false, false, NULL, NULL),
|
manifest,
|
||||||
|
manifestNewBuild(storagePg, PG_VERSION_13, hrnPgCatalogVersion(PG_VERSION_13), false, false, false, NULL, NULL),
|
||||||
"build manifest");
|
"build manifest");
|
||||||
|
|
||||||
contentSave = bufNew(0);
|
contentSave = bufNew(0);
|
||||||
@ -854,7 +860,7 @@ testRun(void)
|
|||||||
THROW_ON_SYS_ERROR(symlink(TEST_PATH "/pg/base", TEST_PATH "/pg/link") == -1, FileOpenError, "unable to create symlink");
|
THROW_ON_SYS_ERROR(symlink(TEST_PATH "/pg/base", TEST_PATH "/pg/link") == -1, FileOpenError, "unable to create symlink");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, false, NULL, NULL),
|
||||||
LinkDestinationError, "link 'link' destination '" TEST_PATH "/pg/base' is in PGDATA");
|
LinkDestinationError, "link 'link' destination '" TEST_PATH "/pg/base' is in PGDATA");
|
||||||
|
|
||||||
THROW_ON_SYS_ERROR(unlink(TEST_PATH "/pg/link") == -1, FileRemoveError, "unable to remove symlink");
|
THROW_ON_SYS_ERROR(unlink(TEST_PATH "/pg/link") == -1, FileRemoveError, "unable to remove symlink");
|
||||||
@ -865,7 +871,7 @@ testRun(void)
|
|||||||
HRN_STORAGE_PATH_CREATE(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somedir", .mode = 0700);
|
HRN_STORAGE_PATH_CREATE(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somedir", .mode = 0700);
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, false, NULL, NULL),
|
||||||
LinkExpectedError, "'pg_data/pg_tblspc/somedir' is not a symlink - pg_tblspc should contain only symlinks");
|
LinkExpectedError, "'pg_data/pg_tblspc/somedir' is not a symlink - pg_tblspc should contain only symlinks");
|
||||||
|
|
||||||
HRN_STORAGE_PATH_REMOVE(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somedir");
|
HRN_STORAGE_PATH_REMOVE(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somedir");
|
||||||
@ -876,7 +882,7 @@ testRun(void)
|
|||||||
HRN_STORAGE_PUT_EMPTY(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somefile");
|
HRN_STORAGE_PUT_EMPTY(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somefile");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, false, NULL, NULL),
|
||||||
LinkExpectedError, "'pg_data/pg_tblspc/somefile' is not a symlink - pg_tblspc should contain only symlinks");
|
LinkExpectedError, "'pg_data/pg_tblspc/somefile' is not a symlink - pg_tblspc should contain only symlinks");
|
||||||
|
|
||||||
TEST_STORAGE_EXISTS(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somefile", .remove = true);
|
TEST_STORAGE_EXISTS(storagePgWrite, MANIFEST_TARGET_PGTBLSPC "/somefile", .remove = true);
|
||||||
@ -887,7 +893,8 @@ testRun(void)
|
|||||||
THROW_ON_SYS_ERROR(symlink("../bogus-link", TEST_PATH "/pg/link-to-link") == -1, FileOpenError, "unable to create symlink");
|
THROW_ON_SYS_ERROR(symlink("../bogus-link", TEST_PATH "/pg/link-to-link") == -1, FileOpenError, "unable to create symlink");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, true, NULL, NULL), FileOpenError,
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, true, false, NULL, NULL),
|
||||||
|
FileOpenError,
|
||||||
"unable to get info for missing path/file '" TEST_PATH "/pg/link-to-link': [2] No such file or directory");
|
"unable to get info for missing path/file '" TEST_PATH "/pg/link-to-link': [2] No such file or directory");
|
||||||
|
|
||||||
THROW_ON_SYS_ERROR(unlink(TEST_PATH "/pg/link-to-link") == -1, FileRemoveError, "unable to remove symlink");
|
THROW_ON_SYS_ERROR(unlink(TEST_PATH "/pg/link-to-link") == -1, FileRemoveError, "unable to remove symlink");
|
||||||
@ -902,7 +909,7 @@ testRun(void)
|
|||||||
symlink(TEST_PATH "/linktest", TEST_PATH "/pg/linktolink") == -1, FileOpenError, "unable to create symlink");
|
symlink(TEST_PATH "/linktest", TEST_PATH "/pg/linktolink") == -1, FileOpenError, "unable to create symlink");
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, NULL, NULL),
|
manifestNewBuild(storagePg, PG_VERSION_94, hrnPgCatalogVersion(PG_VERSION_94), false, false, false, NULL, NULL),
|
||||||
LinkDestinationError, "link '" TEST_PATH "/pg/linktolink' cannot reference another link '" TEST_PATH "/linktest'");
|
LinkDestinationError, "link '" TEST_PATH "/pg/linktolink' cannot reference another link '" TEST_PATH "/linktest'");
|
||||||
|
|
||||||
#undef TEST_MANIFEST_HEADER
|
#undef TEST_MANIFEST_HEADER
|
||||||
@ -1413,6 +1420,7 @@ testRun(void)
|
|||||||
"[backup]\n" \
|
"[backup]\n" \
|
||||||
"backup-archive-start=\"000000030000028500000089\"\n" \
|
"backup-archive-start=\"000000030000028500000089\"\n" \
|
||||||
"backup-archive-stop=\"000000030000028500000089\"\n" \
|
"backup-archive-stop=\"000000030000028500000089\"\n" \
|
||||||
|
"backup-bundle=true\n" \
|
||||||
"backup-label=\"20190818-084502F_20190820-084502D\"\n" \
|
"backup-label=\"20190818-084502F_20190820-084502D\"\n" \
|
||||||
"backup-lsn-start=\"285/89000028\"\n" \
|
"backup-lsn-start=\"285/89000028\"\n" \
|
||||||
"backup-lsn-stop=\"285/89001F88\"\n" \
|
"backup-lsn-stop=\"285/89001F88\"\n" \
|
||||||
@ -1472,10 +1480,10 @@ testRun(void)
|
|||||||
"pg_data/=equal=more=={\"mode\":\"0640\",\"size\":0,\"timestamp\":1565282120}\n" \
|
"pg_data/=equal=more=={\"mode\":\"0640\",\"size\":0,\"timestamp\":1565282120}\n" \
|
||||||
"pg_data/PG_VERSION={\"checksum\":\"184473f470864e067ee3a22e64b47b0a1c356f29\"" \
|
"pg_data/PG_VERSION={\"checksum\":\"184473f470864e067ee3a22e64b47b0a1c356f29\"" \
|
||||||
",\"reference\":\"20190818-084502F_20190819-084506D\",\"size\":4,\"timestamp\":1565282114}\n" \
|
",\"reference\":\"20190818-084502F_20190819-084506D\",\"size\":4,\"timestamp\":1565282114}\n" \
|
||||||
"pg_data/base/16384/17000={\"checksum\":\"e0101dd8ffb910c9c202ca35b5f828bcb9697bed\",\"checksum-page\":false" \
|
"pg_data/base/16384/17000={\"bni\":1,\"checksum\":\"e0101dd8ffb910c9c202ca35b5f828bcb9697bed\",\"checksum-page\":false"\
|
||||||
",\"checksum-page-error\":[1],\"repo-size\":4096,\"size\":8192,\"timestamp\":1565282114}\n" \
|
",\"checksum-page-error\":[1],\"repo-size\":4096,\"size\":8192,\"timestamp\":1565282114}\n" \
|
||||||
"pg_data/base/16384/PG_VERSION={\"checksum\":\"184473f470864e067ee3a22e64b47b0a1c356f29\",\"group\":\"group2\"" \
|
"pg_data/base/16384/PG_VERSION={\"bni\":1,\"bno\":1,\"checksum\":\"184473f470864e067ee3a22e64b47b0a1c356f29\"" \
|
||||||
",\"size\":4,\"timestamp\":1565282115,\"user\":false}\n" \
|
",\"group\":\"group2\",\"size\":4,\"timestamp\":1565282115,\"user\":false}\n" \
|
||||||
"pg_data/base/32768/33000={\"checksum\":\"7a16d165e4775f7c92e8cdf60c0af57313f0bf90\",\"checksum-page\":true" \
|
"pg_data/base/32768/33000={\"checksum\":\"7a16d165e4775f7c92e8cdf60c0af57313f0bf90\",\"checksum-page\":true" \
|
||||||
",\"reference\":\"20190818-084502F\",\"size\":1073741824,\"timestamp\":1565282116}\n" \
|
",\"reference\":\"20190818-084502F\",\"size\":1073741824,\"timestamp\":1565282116}\n" \
|
||||||
"pg_data/base/32768/33000.32767={\"checksum\":\"6e99b589e550e68e934fd235ccba59fe5b592a9e\",\"checksum-page\":true" \
|
"pg_data/base/32768/33000.32767={\"checksum\":\"6e99b589e550e68e934fd235ccba59fe5b592a9e\",\"checksum-page\":true" \
|
||||||
@ -1524,6 +1532,7 @@ testRun(void)
|
|||||||
"[backup]\n"
|
"[backup]\n"
|
||||||
"backup-archive-start=\"000000040000028500000089\"\n"
|
"backup-archive-start=\"000000040000028500000089\"\n"
|
||||||
"backup-archive-stop=\"000000040000028500000089\"\n"
|
"backup-archive-stop=\"000000040000028500000089\"\n"
|
||||||
|
"backup-bundle=true\n"
|
||||||
"backup-label=\"20190818-084502F\"\n"
|
"backup-label=\"20190818-084502F\"\n"
|
||||||
"backup-lsn-start=\"300/89000028\"\n"
|
"backup-lsn-start=\"300/89000028\"\n"
|
||||||
"backup-lsn-stop=\"300/89001F88\"\n"
|
"backup-lsn-stop=\"300/89001F88\"\n"
|
||||||
@ -1575,8 +1584,8 @@ testRun(void)
|
|||||||
TEST_TITLE("manifest validation");
|
TEST_TITLE("manifest validation");
|
||||||
|
|
||||||
// Munge files to produce errors
|
// Munge files to produce errors
|
||||||
manifestFileUpdate(manifest, STRDEF("pg_data/postgresql.conf"), 4457, 0, NULL, NULL, false, false, NULL);
|
manifestFileUpdate(manifest, STRDEF("pg_data/postgresql.conf"), 4457, 0, NULL, NULL, false, false, NULL, 0, 0);
|
||||||
manifestFileUpdate(manifest, STRDEF("pg_data/base/32768/33000.32767"), 0, 0, NULL, NULL, true, false, NULL);
|
manifestFileUpdate(manifest, STRDEF("pg_data/base/32768/33000.32767"), 0, 0, NULL, NULL, true, false, NULL, 0, 0);
|
||||||
|
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
manifestValidate(manifest, false), FormatError,
|
manifestValidate(manifest, false), FormatError,
|
||||||
@ -1591,10 +1600,10 @@ testRun(void)
|
|||||||
"repo size must be > 0 for file 'pg_data/postgresql.conf'");
|
"repo size must be > 0 for file 'pg_data/postgresql.conf'");
|
||||||
|
|
||||||
// Undo changes made to files
|
// Undo changes made to files
|
||||||
manifestFileUpdate(manifest, STRDEF("pg_data/base/32768/33000.32767"), 32768, 32768, NULL, NULL, true, false, NULL);
|
manifestFileUpdate(manifest, STRDEF("pg_data/base/32768/33000.32767"), 32768, 32768, NULL, NULL, true, false, NULL, 0, 0);
|
||||||
manifestFileUpdate(
|
manifestFileUpdate(
|
||||||
manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, "184473f470864e067ee3a22e64b47b0a1c356f29", NULL, false,
|
manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, "184473f470864e067ee3a22e64b47b0a1c356f29", NULL, false,
|
||||||
false, NULL);
|
false, NULL, 0, 0);
|
||||||
|
|
||||||
TEST_RESULT_VOID(manifestValidate(manifest, true), "successful validate");
|
TEST_RESULT_VOID(manifestValidate(manifest, true), "successful validate");
|
||||||
|
|
||||||
@ -1739,10 +1748,11 @@ testRun(void)
|
|||||||
TEST_RESULT_BOOL(manifestFileExists(manifest, STRDEF("bogus")), false, "manifest file does not exist");
|
TEST_RESULT_BOOL(manifestFileExists(manifest, STRDEF("bogus")), false, "manifest file does not exist");
|
||||||
|
|
||||||
TEST_RESULT_VOID(
|
TEST_RESULT_VOID(
|
||||||
manifestFileUpdate(manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, "", NULL, false, false, NULL),
|
manifestFileUpdate(manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, "", NULL, false, false, NULL, 0, 0),
|
||||||
"update file");
|
"update file");
|
||||||
TEST_RESULT_VOID(
|
TEST_RESULT_VOID(
|
||||||
manifestFileUpdate(manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, NULL, varNewStr(NULL), false, false, NULL),
|
manifestFileUpdate(
|
||||||
|
manifest, STRDEF("pg_data/postgresql.conf"), 4457, 4457, NULL, varNewStr(NULL), false, false, NULL, 0, 0),
|
||||||
"update file");
|
"update file");
|
||||||
|
|
||||||
// ManifestDb getters
|
// ManifestDb getters
|
||||||
|
@ -271,7 +271,8 @@ testRun(void)
|
|||||||
|
|
||||||
MEM_CONTEXT_BEGIN(testContext)
|
MEM_CONTEXT_BEGIN(testContext)
|
||||||
{
|
{
|
||||||
TEST_ASSIGN(manifest, manifestNewBuild(storagePg, PG_VERSION_91, 999999999, false, false, NULL, NULL), "build files");
|
TEST_ASSIGN(
|
||||||
|
manifest, manifestNewBuild(storagePg, PG_VERSION_91, 999999999, false, false, false, NULL, NULL), "build files");
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_END();
|
MEM_CONTEXT_END();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user