You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-10-23 21:28:56 +03:00
Format 6 derives encryption keys from the passphrase with SHA-256 rather than SHA-1. Older repositories keep SHA-1, since the migration does not rewrite the files they already encrypted. The digest is picked when a passphrase is generated rather than at each use, so it is stored with the passphrase in CipherSpec and defaults to SHA-256. A passphrase outlives the format of the file it is stored in, so the format cannot decide how the key is derived. Info files at format 6 store cipher-digest beside cipher-pass, and a passphrase stored without one derives with SHA-1. stanza-upgrade writes back the digest the passphrase already had, so archives and backups from before the migration still load. Only a new passphrase gets SHA-256: the one for a stanza created at format 6, or for the manifest of a full backup written at it. The info file has the same problem in reverse. Its format sits inside the content that the passphrase decrypts, so a reader would have to decrypt the file to find out how to decrypt it. Format 6 puts a plaintext header in front instead: PGBR, the three-digit format, and a spare byte for later. It goes where the OpenSSL Salted__ magic used to go and is the same eight bytes, so the salt still starts in the same place. A file that starts with the magic came from format 5, the only format older than the header. The header format is checked before anything is decrypted, then compared against the format found in the content, so a file stitched together from two others is rejected rather than half read. The format constants move out of version.h into a new common/format, along with repoFormatValidate(), repoFormatDigest(), and CipherBlockFormat. That filter reads the header, builds a block cipher with the digest the format calls for, and streams the rest of the file through it. The block cipher no longer knows anything about repositories: it takes the digest from the spec and takes what sits in front of the content as a parameter. Verify checksums info files as stored rather than as decrypted, since the header has to come off before decryption can start. A file and its copy are written from the same bytes, so comparing the two still works. In an encrypted repository created by a Perl version the file and its copy are separate encryptions, so their checksums differ and verify reports it at detail level. A log line is better than a separate code path for repositories with seven year old backups.
1426 lines
71 KiB
C
1426 lines
71 KiB
C
/***********************************************************************************************************************************
|
|
Test Stanza Commands
|
|
***********************************************************************************************************************************/
|
|
#include "common/crypto/cipherBlock.h"
|
|
#include "common/io/bufferWrite.h"
|
|
#include "postgres/interface.h"
|
|
#include "postgres/version.h"
|
|
#include "storage/posix/storage.h"
|
|
|
|
#include "harness/config.h"
|
|
#include "harness/info.h"
|
|
#include "harness/postgres.h"
|
|
#include "harness/pq.h"
|
|
|
|
/***********************************************************************************************************************************
|
|
Test Run
|
|
***********************************************************************************************************************************/
|
|
static void
|
|
testRun(void)
|
|
{
|
|
FUNCTION_HARNESS_VOID();
|
|
|
|
// Create storage objects for the testing location and for the harness directory for items such as logs, stop files, etc
|
|
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
|
|
Storage *storageHrn = storagePosixNewP(HRN_PATH_STR, .write = true);
|
|
|
|
#define TEST_STANZA "db"
|
|
#define TEST_WAL_MIGRATE "archive/db/15-1/000000010000000000000001"
|
|
#define TEST_STANZA_OTHER "otherstanza"
|
|
|
|
StringList *argListBase = strLstNew();
|
|
hrnCfgArgRawBool(argListBase, cfgOptOnline, false);
|
|
hrnCfgArgRawZ(argListBase, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgRawZ(argListBase, cfgOptPgPath, TEST_PATH "/pg");
|
|
hrnCfgArgRawZ(argListBase, cfgOptRepoPath, TEST_PATH "/repo");
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("cmdStanzaCreate(), checkStanzaInfo(), cmdStanzaDelete()"))
|
|
{
|
|
TEST_TITLE("stanza-create: repo option not valid");
|
|
|
|
// Load Parameters
|
|
StringList *argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgRawZ(argList, cfgOptRepo, "2");
|
|
|
|
TEST_ERROR(
|
|
hrnCfgLoadP(cfgCmdStanzaCreate, argList), OptionInvalidError, "option 'repo' not valid for command 'stanza-create'");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-create: stop file error");
|
|
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argListBase);
|
|
|
|
// Create the stop file, test, then remove the stop file
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))));
|
|
TEST_ERROR(cmdStanzaCreate(), StopError, "stop file exists for stanza db");
|
|
HRN_STORAGE_REMOVE(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))));
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-create: validate info files created");
|
|
|
|
argList = strLstDup(argListBase);
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// Create pg_control
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_96);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create - one repo, no files exist");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":" HRN_PG_SYSTEMID_96_Z ",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put archive info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza archive info files are equal");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put backup info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza backup info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("cmdStanzaCreate success - multi-repo and encryption");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgKeyRawStrId(argList, cfgOptRepoCipherType, 2, cipherTypeAes256Cbc);
|
|
hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 2, "12345678");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 3, TEST_PATH "/repo3");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoFormat, 3, "6");
|
|
// Request a format for repo1 as well, where the stanza already exists
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoFormat, 1, "6");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 4, TEST_PATH "/repo4");
|
|
hrnCfgArgKeyRawStrId(argList, cfgOptRepoCipherType, 4, cipherTypeAes256Cbc);
|
|
hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 4, "87654321");
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create - files already exist on repo1 and both are valid");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-create for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' already exists on repo1 and is valid\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo2\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo3\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo4");
|
|
|
|
InfoArchive *infoArchive = NULL;
|
|
TEST_ASSIGN(
|
|
infoArchive,
|
|
infoArchiveLoadFile(
|
|
storageRepoIdx(1), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewP(cipherTypeAes256Cbc, BUFSTRDEF("12345678"))),
|
|
"load archive info from encrypted repo2");
|
|
TEST_RESULT_UINT(cipherSpecType(infoArchiveCipherSpec(infoArchive)), cipherTypeAes256Cbc, "cipher sub set");
|
|
|
|
InfoBackup *infoBackup = NULL;
|
|
TEST_ASSIGN(
|
|
infoBackup,
|
|
infoBackupLoadFile(
|
|
storageRepoIdx(1), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewP(cipherTypeAes256Cbc, BUFSTRDEF("12345678"))),
|
|
"load backup info from encrypted repo2");
|
|
TEST_RESULT_UINT(cipherSpecType(infoBackupCipherSpec(infoBackup)), cipherTypeAes256Cbc, "cipher sub set");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(cipherSpecPass(infoArchiveCipherSpec(infoArchive)), cipherSpecPass(infoBackupCipherSpec(infoBackup))), false,
|
|
"cipher sub different for archive and backup");
|
|
|
|
// Confirm non-encrypted repo created successfully. This repo was created with an explicit format so also confirm that the
|
|
// requested format was stored rather than the default.
|
|
TEST_ASSIGN(
|
|
infoArchive,
|
|
infoArchiveLoadFile(storageRepoIdx(2), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone()),
|
|
"load archive info from repo3");
|
|
TEST_RESULT_UINT(
|
|
cipherSpecType(infoArchiveCipherSpec(infoArchive)), cipherTypeNone,
|
|
"archive cipher sub not set on non-encrypted repo");
|
|
TEST_RESULT_UINT(infoArchiveFormat(infoArchive), REPOSITORY_FORMAT_6, "archive info at requested format");
|
|
|
|
TEST_ASSIGN(
|
|
infoBackup, infoBackupLoadFile(storageRepoIdx(2), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone()),
|
|
"load backup info from repo3");
|
|
TEST_RESULT_UINT(
|
|
cipherSpecType(infoBackupCipherSpec(infoBackup)), cipherTypeNone,
|
|
"backup cipher sub not set on non-encrypted repo");
|
|
TEST_RESULT_UINT(infoBackupFormat(infoBackup), REPOSITORY_FORMAT_6, "backup info at requested format");
|
|
|
|
// The stanza on repo1 already existed, so the format requested for it was ignored. A stanza is migrated with
|
|
// stanza-upgrade rather than with this command, which sets the format only for a stanza it creates.
|
|
TEST_RESULT_UINT(infoArchiveFormat(infoArchive) != REPOSITORY_FORMAT_DEFAULT, true, "requested format is not the default");
|
|
TEST_ASSIGN(
|
|
infoArchive, infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone()),
|
|
"load archive info from repo1");
|
|
TEST_RESULT_UINT(
|
|
infoArchiveFormat(infoArchive), REPOSITORY_FORMAT_DEFAULT, "archive info on existing stanza still at default format");
|
|
|
|
// Confirm other repo encrypted with different password
|
|
TEST_ASSIGN(
|
|
infoArchive,
|
|
infoArchiveLoadFile(
|
|
storageRepoIdx(3), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewP(cipherTypeAes256Cbc, BUFSTRDEF("87654321"))),
|
|
"load archive info from encrypted repo4");
|
|
TEST_RESULT_UINT(cipherSpecType(infoArchiveCipherSpec(infoArchive)), cipherTypeAes256Cbc, "cipher sub set");
|
|
|
|
TEST_ASSIGN(
|
|
infoBackup,
|
|
infoBackupLoadFile(
|
|
storageRepoIdx(3), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewP(cipherTypeAes256Cbc, BUFSTRDEF("87654321"))),
|
|
"load backup info from encrypted repo4");
|
|
TEST_RESULT_UINT(cipherSpecType(infoBackupCipherSpec(infoBackup)), cipherTypeAes256Cbc, "cipher sub set");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("cmdStanzaCreate missing files - multi-repo and encryption");
|
|
|
|
// Remove backup.info on repo1
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, .remove = true);
|
|
|
|
// Remove archive.info on repo2
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(1), INFO_ARCHIVE_PATH_FILE, .remove = true);
|
|
|
|
// Remove info files on repo3
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(2), INFO_ARCHIVE_PATH_FILE, .remove = true);
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(2), INFO_BACKUP_PATH_FILE, .remove = true);
|
|
|
|
// Remove copy files repo4
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(3), INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT, .remove = true);
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(3), INFO_BACKUP_PATH_FILE INFO_COPY_EXT, .remove = true);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create - success with missing files");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-create for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo2\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo3\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo4");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))),
|
|
true, "backup.info recreated repo1 from backup.info.copy");
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(1), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(1), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))),
|
|
true, "archive.info repo2 recreated from archive.info.copy");
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(2), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(2), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(2), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(2), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))),
|
|
true, "info files recreated repo3 from copy files");
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(3), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(3), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(3), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(3), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))),
|
|
true, "copy files recreated repo4 from info files");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("cmdStanzaDelete - multi-repo and encryption, delete");
|
|
|
|
StringList *argListCmd = strLstNew();
|
|
hrnCfgArgKeyRawZ(argListCmd, cfgOptRepoPath, 1, TEST_PATH "/repo");
|
|
hrnCfgArgKeyRawZ(argListCmd, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgKeyRawZ(argListCmd, cfgOptRepoPath, 3, TEST_PATH "/repo3");
|
|
hrnCfgArgKeyRawZ(argListCmd, cfgOptRepoPath, 4, TEST_PATH "/repo4");
|
|
hrnCfgArgRawZ(argListCmd, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgRawZ(argListCmd, cfgOptPgPath, TEST_PATH "/pg");
|
|
|
|
TEST_ERROR(
|
|
hrnCfgLoadP(cfgCmdStanzaDelete, argListCmd), OptionRequiredError,
|
|
"stanza-delete command requires option: repo\n"
|
|
"HINT: this command requires a specific repository to operate on");
|
|
|
|
// Add the repo option
|
|
StringList *argListDelete = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argListDelete, cfgOptRepo, "4");
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argListDelete);
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaDelete(), FileMissingError,
|
|
"stop file does not exist for stanza 'db'\n"
|
|
"HINT: has the pgbackrest stop command been run on this server for this stanza?");
|
|
|
|
// Create the stop file
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))));
|
|
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo4",
|
|
"archive/\n"
|
|
"archive/db/\n"
|
|
"archive/db/archive.info\n"
|
|
"archive/db/archive.info.copy\n"
|
|
"backup/\n"
|
|
"backup/db/\n"
|
|
"backup/db/backup.info\n"
|
|
"backup/db/backup.info.copy\n",
|
|
.comment = "stanza exists in repo4");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - repo4");
|
|
|
|
TEST_STORAGE_LIST(storageTest, "repo4", "archive/\nbackup/\n", .comment = "stanza deleted");
|
|
|
|
TEST_RESULT_BOOL(
|
|
storageExistsP(storageHrn, lockStopFileName(cfgOptionStr(cfgOptStanza))), false, "confirm stop file removed");
|
|
|
|
// Remove the cipher pass environment variable otherwise stanza-create will recreate the stanza
|
|
hrnCfgEnvKeyRemoveRaw(cfgOptRepoCipherPass, 4);
|
|
|
|
// Stanza with directories only
|
|
argListDelete = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argListDelete, cfgOptRepo, "3");
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argListDelete);
|
|
|
|
HRN_STORAGE_PATH_CREATE(
|
|
storageRepoIdxWrite(2), STORAGE_REPO_ARCHIVE "/9.6-1/1234567812345678", .comment = "create archive sub directory");
|
|
HRN_STORAGE_PATH_CREATE(
|
|
storageRepoIdxWrite(2), STORAGE_REPO_BACKUP "/20190708-154306F", .comment = "create backup sub directory");
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))), .comment = "create stop file");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - repo3 - sub directories only");
|
|
|
|
TEST_STORAGE_LIST(storageTest, "repo3", "archive/\nbackup/\n", .comment = "stanza deleted");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("cmdStanzaCreate errors");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgKeyRawStrId(argList, cfgOptRepoCipherType, 2, cipherTypeAes256Cbc);
|
|
hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 2, "12345678");
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// Backup files removed - archive.info and archive.info.copy exist repo2
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE INFO_COPY_EXT, .remove = true,
|
|
.comment = "repo2: remove backup.info.copy");
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE, .remove = true, .comment = "repo2: remove backup.info");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"archive.info exists but backup.info is missing on repo2\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-create for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' already exists on repo1 and is valid\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo2");
|
|
|
|
// Archive files removed - backup.info and backup.info.copy exist repo1
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT, .remove = true,
|
|
.comment = "repo1: remove archive.info.copy");
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE, .remove = true, .comment = "repo1: remove archive.info");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"backup.info exists but archive.info is missing on repo1\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Delete the last repo so only 1 remains
|
|
argListDelete = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argListDelete, cfgOptRepo, "2");
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argListDelete);
|
|
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))), .comment = "create stop file");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - only 1 remains");
|
|
|
|
// Remove the cipher pass environment variable otherwise stanza-create will recreate the stanza
|
|
hrnCfgEnvKeyRemoveRaw(cfgOptRepoCipherPass, 2);
|
|
|
|
argList = strLstDup(argListBase);
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// Archive files removed - backup.info exists
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE INFO_COPY_EXT, .remove = true,
|
|
.comment = "repo1: remove backup.info.copy");
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"backup.info exists but archive.info is missing on repo1\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Archive files removed - backup.info.copy exists, backup.info moved to backup.info.copy
|
|
HRN_STORAGE_MOVE(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, INFO_BACKUP_PATH_FILE INFO_COPY_EXT);
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"backup.info exists but archive.info is missing on repo1\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Backup files removed - archive.info file exists
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":" HRN_PG_SYSTEMID_96_Z ",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put archive info to file repo1");
|
|
TEST_STORAGE_EXISTS(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE INFO_COPY_EXT, .remove = true,
|
|
.comment = "repo1: remove backup.info.copy");
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"archive.info exists but backup.info is missing on repo1\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Backup files removed - archive.info.copy file exists (renamed from archive.info)
|
|
HRN_STORAGE_MOVE(storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE, INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT);
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileMissingError,
|
|
"archive.info exists but backup.info is missing on repo1\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("checkStanzaInfo() - already checked in checkTest so just a sanity check here");
|
|
|
|
// Create a corrupted backup file - db id
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put backup info to file - bad db-id");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileInvalidError,
|
|
"backup info file and archive info file do not match\n"
|
|
"archive: id = 1, version = 9.6, system-id = " HRN_PG_SYSTEMID_96_Z "\n"
|
|
"backup : id = 2, version = 9.6, system-id = " HRN_PG_SYSTEMID_96_Z "\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("archive.info file and backup.info files that match but do not match the current database version");
|
|
|
|
// Copy files may or may not exist - remove
|
|
HRN_STORAGE_REMOVE(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT, .comment = "repo1: remove archive.info.copy");
|
|
HRN_STORAGE_REMOVE(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE INFO_COPY_EXT, .comment = "repo1: remove backup.info.copy");
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=202506291\n"
|
|
"db-control-version=1800\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_17_Z "\n"
|
|
"db-version=\"18\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202506291,\"db-control-version\":1800,\"db-system-id\":" HRN_PG_SYSTEMID_17_Z
|
|
",\"db-version\":\"18\"}\n");
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_17_Z "\n"
|
|
"db-version=\"18\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":" HRN_PG_SYSTEMID_17_Z ",\"db-version\":\"18\"}\n");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileInvalidError,
|
|
"backup and archive info files exist but do not match the database\n"
|
|
"HINT: is this the correct stanza?\n"
|
|
"HINT: did an error occur during stanza-upgrade?");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("archive.info and backup.info files that match but do not match the current database system-id");
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":6569239123849665999,\"db-version\":\"9.6\"}\n");
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaCreate(), FileInvalidError,
|
|
"backup and archive info files exist but do not match the database\n"
|
|
"HINT: is this the correct stanza?\n"
|
|
"HINT: did an error occur during stanza-upgrade?");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Remove the info files and add sub directory to backup
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE, .remove = true);
|
|
TEST_STORAGE_EXISTS(storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE, .remove = true);
|
|
HRN_STORAGE_PATH_CREATE(
|
|
storageRepoIdxWrite(0), STORAGE_REPO_BACKUP "/backup.history", .comment = "create directory in backup");
|
|
|
|
TEST_ERROR(cmdStanzaCreate(), PathNotEmptyError, "backup directory not empty");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// File in archive, directory in backup
|
|
HRN_STORAGE_PUT_Z(storageRepoIdxWrite(0), STORAGE_REPO_ARCHIVE "/somefile", "some content");
|
|
TEST_ERROR(cmdStanzaCreate(), PathNotEmptyError, "backup directory and/or archive directory not empty");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// File in archive, backup empty
|
|
HRN_STORAGE_PATH_REMOVE(storageRepoIdxWrite(0), STORAGE_REPO_BACKUP "/backup.history", .comment = "remove backup subdir");
|
|
TEST_ERROR(cmdStanzaCreate(), PathNotEmptyError, "archive directory not empty");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("repeat last test using --force (deprecated)");
|
|
|
|
hrnCfgArgRawBool(argList, cfgOptForce, true);
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
TEST_ERROR(cmdStanzaCreate(), PathNotEmptyError, "archive directory not empty");
|
|
TEST_RESULT_LOG(
|
|
"P00 WARN: option --force is no longer supported\n"
|
|
"P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
// Clean-up file in archive directory
|
|
HRN_STORAGE_REMOVE(storageRepoIdxWrite(0), STORAGE_REPO_ARCHIVE "/somefile", .comment = "remove old test file");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-create forcing another PG version");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
HRN_PG_CONTROL_OVERRIDE_VERSION_PUT(storagePgWrite(), PG_VERSION_15, 1501, .catalogVersion = 202211111);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create - forcing another PG version");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-catalog-version=202211111\n"
|
|
"db-control-version=1300\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_15_Z "\n"
|
|
"db-version=\"15\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202211111,\"db-control-version\":1300,\"db-system-id\":" HRN_PG_SYSTEMID_15_Z
|
|
",\"db-version\":\"15\"}\n",
|
|
.comment = "put backup info to test file, control version for PG 15, catalog version forced from pg_control");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza backup info files are equal");
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("pgValidate(), online=y"))
|
|
{
|
|
// Load Parameters
|
|
StringList *argList = strLstNew();
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/pg");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH "/repo");
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("pgControl and database match");
|
|
|
|
// Create pg_control
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_18);
|
|
|
|
HRN_PQ_SCRIPT_SET(
|
|
HRN_PQ_SCRIPT_OPEN(1, "dbname='postgres' port=5432", PG_VERSION_18, TEST_PATH "/pg", false, NULL, NULL));
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create - db online");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, TEST_PATH "/repo",
|
|
"archive/\n"
|
|
"archive/db/\n"
|
|
"archive/db/archive.info\n"
|
|
"archive/db/archive.info.copy\n"
|
|
"backup/\n"
|
|
"backup/db/\n"
|
|
"backup/db/backup.info\n"
|
|
"backup/db/backup.info.copy\n",
|
|
.comment = "stanza created");
|
|
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
HRN_PQ_SCRIPT_SET(
|
|
HRN_PQ_SCRIPT_OPEN(1, "dbname='postgres' port=5432", PG_VERSION_18, TEST_PATH "/pg", false, NULL, NULL));
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - db online");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' on repo1 is already up to date");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("pg_control and version mismatch");
|
|
|
|
// Create pg_control with different version
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_10);
|
|
|
|
HRN_PQ_SCRIPT_SET(
|
|
HRN_PQ_SCRIPT_OPEN(1, "dbname='postgres' port=5432", PG_VERSION_11, TEST_PATH "/pg", false, NULL, NULL));
|
|
|
|
TEST_ERROR(
|
|
pgValidate(), DbMismatchError,
|
|
"version '" PG_VERSION_11_Z "' and path '" TEST_PATH "/pg' queried from cluster do not match version '" PG_VERSION_10_Z
|
|
"' and '" TEST_PATH "/pg' read from '" TEST_PATH "/pg/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL "'\n"
|
|
"HINT: the pg1-path and pg1-port settings likely reference different clusters.");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("pg_control and path mismatch");
|
|
|
|
// Create pg_control
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_15);
|
|
|
|
HRN_PQ_SCRIPT_SET(
|
|
HRN_PQ_SCRIPT_OPEN(1, "dbname='postgres' port=5432", PG_VERSION_15, TEST_PATH "/pg2", false, NULL, NULL));
|
|
|
|
TEST_ERROR(
|
|
pgValidate(), DbMismatchError,
|
|
"version '" PG_VERSION_15_Z "' and path '" TEST_PATH "/pg2' queried from cluster do not match version '" PG_VERSION_15_Z
|
|
"' and '" TEST_PATH "/pg' read from '" TEST_PATH "/pg/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL "'\n"
|
|
"HINT: the pg1-path and pg1-port settings likely reference different clusters.");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("primary at pg2");
|
|
|
|
argList = strLstNew();
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/pg");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH "/repo");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptPgPath, 2, TEST_PATH "/pg1");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptPgPort, 2, "5434");
|
|
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// Create pg_control for primary
|
|
HRN_PG_CONTROL_PUT(storagePgIdxWrite(1), PG_VERSION_13);
|
|
|
|
// Create pg_control for standby
|
|
HRN_PG_CONTROL_PUT(storagePgIdxWrite(0), PG_VERSION_13);
|
|
|
|
HRN_PQ_SCRIPT_SET(
|
|
HRN_PQ_SCRIPT_OPEN(1, "dbname='postgres' port=5432", PG_VERSION_13, TEST_PATH "/pg", true, NULL, NULL),
|
|
HRN_PQ_SCRIPT_OPEN(2, "dbname='postgres' port=5434", PG_VERSION_13, TEST_PATH "/pg1", false, NULL, NULL));
|
|
|
|
PgControl pgControl = {0};
|
|
TEST_ASSIGN(pgControl, pgValidate(), "validate primary on pg2");
|
|
TEST_RESULT_UINT(pgControl.version, PG_VERSION_13, "version set");
|
|
TEST_RESULT_UINT(pgControl.systemId, HRN_PG_SYSTEMID_13, "systemId set");
|
|
TEST_RESULT_UINT(pgControl.catalogVersion, 202007201, "catalogVersion set");
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("cmdStanzaUpgrade()"))
|
|
{
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - config errors");
|
|
|
|
// Load Parameters
|
|
StringList *argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgRawZ(argList, cfgOptRepo, "2");
|
|
|
|
TEST_ERROR(
|
|
hrnCfgLoadP(cfgCmdStanzaUpgrade, argList), OptionInvalidError,
|
|
"option 'repo' not valid for command 'stanza-upgrade'");
|
|
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argListBase);
|
|
|
|
// Create the stop file, test and remove
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))));
|
|
TEST_ERROR(cmdStanzaUpgrade(), StopError, "stop file exists for stanza db");
|
|
HRN_STORAGE_REMOVE(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))));
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
// Create pg_control for the rest of the tests
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_96);
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - info file mismatch: db-id");
|
|
|
|
// Stanza with only archive.info and backup.info but no .copy files
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n");
|
|
|
|
// backup info up to date but archive info db-id mismatch
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"2={\"db-id\":" HRN_PG_SYSTEMID_96_Z ",\"db-version\":\"9.6\"}\n");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaUpgrade(), FileInvalidError,
|
|
"backup info file and archive info file do not match\n"
|
|
"archive: id = 2, version = 9.6, system-id = " HRN_PG_SYSTEMID_96_Z "\n"
|
|
"backup : id = 1, version = 9.6, system-id = " HRN_PG_SYSTEMID_96_Z "\n"
|
|
"HINT: this may be a symptom of repository corruption!");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - info file mismatch: archive version");
|
|
|
|
// backup info up to date but archive info version is not
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202506291,\"db-control-version\":1800,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"18\"}\n"
|
|
"2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n");
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_17_Z "\n"
|
|
"db-version=\"18\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":" HRN_PG_SYSTEMID_17_Z ",\"db-version\":\"18\"}\n");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - archive.info file upgraded - version");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":" HRN_PG_SYSTEMID_17_Z ",\"db-version\":\"18\"}\n"
|
|
"2={\"db-id\":" HRN_PG_SYSTEMID_96_Z ",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put archive info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza archive info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - info file mismatch: backup version");
|
|
|
|
// archive info up to date but backup info version is not
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=202506291\n"
|
|
"db-control-version=1800\n"
|
|
"db-id=1\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_17_Z "\n"
|
|
"db-version=\"18\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202506291,\"db-control-version\":1800,\"db-system-id\":" HRN_PG_SYSTEMID_17_Z
|
|
",\"db-version\":\"18\"}\n");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - backup.info file upgraded - version");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202506291,\"db-control-version\":1800,\"db-system-id\":" HRN_PG_SYSTEMID_17_Z
|
|
",\"db-version\":\"18\"}\n"
|
|
"2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put backup info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza backup info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - info file mismatch: archive system-id");
|
|
|
|
// backup info up to date but archive info system-id is not
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":202506291,\"db-control-version\":1800,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"18\"}\n"
|
|
"2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n");
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":6569239123849665999,\"db-version\":\"9.6\"}\n");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - archive.info file upgraded - system-id");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":6569239123849665999,\"db-version\":\"9.6\"}\n"
|
|
"2={\"db-id\":" HRN_PG_SYSTEMID_96_Z ",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put archive info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_ARCHIVE_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza archive info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - info file mismatch: backup system-id");
|
|
|
|
// archive info up to date but backup info system-id is not
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - backup.info file upgraded - system-id");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_96_Z "\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n"
|
|
"2={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":" HRN_PG_SYSTEMID_96_Z
|
|
",\"db-version\":\"9.6\"}\n",
|
|
.comment = "put backup info to test file");
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), STRDEF(INFO_BACKUP_PATH_FILE INFO_COPY_EXT)))) &&
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza backup info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade forcing another PG version");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
HRN_PG_CONTROL_OVERRIDE_VERSION_PUT(storagePgWrite(), PG_VERSION_15, 1501, .catalogVersion = 202211111);
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=201608131\n"
|
|
"db-control-version=960\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n");
|
|
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE,
|
|
"[db]\n"
|
|
"db-id=1\n"
|
|
"db-system-id=6569239123849665999\n"
|
|
"db-version=\"9.6\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":6569239123849665999,\"db-version\":\"9.6\"}\n");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - forcing another PG version");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_15_Z "\n"
|
|
"db-version=\"15\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-id\":6569239123849665999,\"db-version\":\"9.6\"}\n"
|
|
"2={\"db-id\":" HRN_PG_SYSTEMID_15_Z ",\"db-version\":\"15\"}\n",
|
|
.comment = "put archive info to test file");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza archive info files are equal");
|
|
|
|
HRN_INFO_PUT(
|
|
storageHrn, "test.info",
|
|
"[db]\n"
|
|
"db-catalog-version=202211111\n"
|
|
"db-control-version=1300\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_15_Z "\n"
|
|
"db-version=\"15\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n"
|
|
"2={\"db-catalog-version\":202211111,\"db-control-version\":1300,\"db-system-id\":" HRN_PG_SYSTEMID_15_Z
|
|
",\"db-version\":\"15\"}\n",
|
|
.comment = "put backup info to test file, control version for PG 15, catalog version forced from pg_control");
|
|
|
|
TEST_RESULT_BOOL(
|
|
bufEq(
|
|
storageGetP(storageNewReadP(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR)),
|
|
storageGetP(storageNewReadP(storageHrn, STRDEF("test.info")))),
|
|
true, "test and stanza backup info files are equal");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - repository format");
|
|
|
|
// The format is left alone when repo-format is not specified, since the option default would otherwise downgrade a
|
|
// repository that has already been upgraded
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format not requested");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' on repo1 is already up to date");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoArchiveFormat(infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_DEFAULT, "archive info still at default format");
|
|
|
|
// A format in a configuration file is warned about and ignored, which is why the option is command line only. A format
|
|
// left in the configuration would otherwise migrate a stanza as a side effect of an upgrade run for another reason.
|
|
HRN_STORAGE_PUT_Z(
|
|
storageTest, "repo-format.conf",
|
|
"[global]\n"
|
|
"repo1-format=6\n");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptConfig, TEST_PATH "/repo-format.conf");
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_LOG("P00 WARN: configuration file contains command-line only option 'repo1-format'");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format in configuration file");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' on repo1 is already up to date");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoArchiveFormat(infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_DEFAULT, "archive info still at default format");
|
|
|
|
// Upgrade the format
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoFormat, "6");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format 6");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: upgrade repository format from 5 to 6");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoArchiveFormat(infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_6, "archive info at format 6");
|
|
TEST_RESULT_UINT(
|
|
infoBackupFormat(infoBackupLoadFile(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_6, "backup info at format 6");
|
|
|
|
// Requesting the format the repository is already at does nothing
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format 6 again");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: stanza 'db' on repo1 is already up to date");
|
|
|
|
// The save of archive.info and backup.info is not atomic, so an upgrade interrupted between the two leaves the info files
|
|
// at different formats. Running the upgrade again brings the lagging file forward.
|
|
HRN_INFO_PUT(
|
|
storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE,
|
|
"[db]\n"
|
|
"db-catalog-version=202211111\n"
|
|
"db-control-version=1300\n"
|
|
"db-id=2\n"
|
|
"db-system-id=" HRN_PG_SYSTEMID_15_Z "\n"
|
|
"db-version=\"15\"\n"
|
|
"\n"
|
|
"[db:history]\n"
|
|
"1={\"db-catalog-version\":201608131,\"db-control-version\":960,\"db-system-id\":6569239123849665999"
|
|
",\"db-version\":\"9.6\"}\n"
|
|
"2={\"db-catalog-version\":202211111,\"db-control-version\":1300,\"db-system-id\":" HRN_PG_SYSTEMID_15_Z
|
|
",\"db-version\":\"15\"}\n",
|
|
.comment = "put backup info at prior format to simulate an interrupted upgrade");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoBackupFormat(infoBackupLoadFile(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_DEFAULT, "backup info back at default format");
|
|
|
|
// An upgrade that does not request a format brings the lagging file forward since the higher of the two formats is the
|
|
// only target that does not downgrade a file
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format mismatch between info files");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 WARN: repository format mismatch from an interrupted stanza-upgrade will be repaired\n"
|
|
"P00 INFO: upgrade repository format from 5 to 6");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoBackupFormat(infoBackupLoadFile(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_6, "backup info brought forward to format 6");
|
|
|
|
// The lagging file is normally backup.info since archive.info is saved first, but the format the upgrade reports is the
|
|
// lower of the two whichever file it is
|
|
InfoArchive *const infoArchivePrior = infoArchiveLoadFile(
|
|
storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone());
|
|
infoArchiveFormatSet(infoArchivePrior, REPOSITORY_FORMAT_5);
|
|
infoArchiveSaveFile(infoArchivePrior, storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone());
|
|
|
|
// A downgrade is measured against the file that is ahead, so requesting the format the archive info is at does not put
|
|
// the backup info back to it
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoFormat, "5");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaUpgrade(), FormatError,
|
|
"unable to downgrade repository format from 6 to 5\n"
|
|
"HINT: backups and archives already written at format 6 would not be readable by a version that only supports"
|
|
" format 5.");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoFormat, "6");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - archive info behind backup info");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 WARN: repository format mismatch from an interrupted stanza-upgrade will be repaired\n"
|
|
"P00 INFO: upgrade repository format from 5 to 6");
|
|
|
|
TEST_RESULT_UINT(
|
|
infoBackupFormat(infoBackupLoadFile(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_6, "backup info brought forward to format 6");
|
|
TEST_RESULT_UINT(
|
|
infoArchiveFormat(infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone())),
|
|
REPOSITORY_FORMAT_6, "archive info still at format 6");
|
|
|
|
// The format cannot be downgraded
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgArgRawZ(argList, cfgOptRepoFormat, "5");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaUpgrade(), FormatError,
|
|
"unable to downgrade repository format from 6 to 5\n"
|
|
"HINT: backups and archives already written at format 6 would not be readable by a version that only supports"
|
|
" format 5.");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-upgrade for stanza 'db' on repo1");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - repository format from the environment");
|
|
|
|
// Put the repository back to the default format so it can be migrated again. This is done directly since the command
|
|
// refuses to downgrade.
|
|
InfoArchive *const infoArchiveReset = infoArchiveLoadFile(
|
|
storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone());
|
|
infoArchiveFormatSet(infoArchiveReset, REPOSITORY_FORMAT_DEFAULT);
|
|
infoArchiveSaveFile(infoArchiveReset, storageRepoIdxWrite(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecNewNone());
|
|
|
|
InfoBackup *const infoBackupReset = infoBackupLoadFile(storageRepoIdx(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone());
|
|
infoBackupFormatSet(infoBackupReset, REPOSITORY_FORMAT_DEFAULT);
|
|
infoBackupSaveFile(infoBackupReset, storageRepoIdxWrite(0), INFO_BACKUP_PATH_FILE_STR, cipherSpecNewNone());
|
|
|
|
// The option cannot be set in a configuration file but it can be set in the environment, where it migrates a stanza
|
|
// without appearing in the command that ran. That is how the environment works for every option so it is allowed, but the
|
|
// migration must still happen.
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgEnvKeyRawZ(cfgOptRepoFormat, 1, "6");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format from the environment");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: upgrade repository format from 5 to 6");
|
|
|
|
hrnCfgEnvKeyRemoveRaw(cfgOptRepoFormat, 1);
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - a file encrypted before a format migration is readable after it");
|
|
|
|
// A stanza on an encrypted repository at the format before the digest could be stored
|
|
argList = strLstNew();
|
|
hrnCfgArgRawBool(argList, cfgOptOnline, false);
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/pg");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, TEST_PATH "/repo-migrate");
|
|
hrnCfgArgRawZ(argList, cfgOptPgVersionForce, "15");
|
|
hrnCfgArgKeyRawStrId(argList, cfgOptRepoCipherType, 1, cipherTypeAes256Cbc);
|
|
hrnCfgEnvKeyRawZ(cfgOptRepoCipherPass, 1, "12345678");
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "stanza create on encrypted repo");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'db' on repo1");
|
|
|
|
const CipherSpec *const cipherSpecMain = cipherSpecNewP(cipherTypeAes256Cbc, BUFSTRDEF("12345678"));
|
|
const InfoArchive *infoArchiveMigrate = NULL;
|
|
|
|
TEST_ASSIGN(
|
|
infoArchiveMigrate, infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecMain),
|
|
"load archive info before migration");
|
|
|
|
// Write a file with the archive sub-pass, which is how everything below the info files is encrypted
|
|
Buffer *const walBuffer = bufNew(0);
|
|
IoWrite *const walWrite = ioBufferWriteNew(walBuffer);
|
|
|
|
cipherBlockFilterGroupAdd(
|
|
ioWriteFilterGroup(walWrite), cipherModeEncrypt, infoArchiveCipherSpec(infoArchiveMigrate));
|
|
ioWriteOpen(walWrite);
|
|
ioWrite(walWrite, BUFSTRDEF("WAL BEFORE MIGRATION"));
|
|
ioWriteClose(walWrite);
|
|
|
|
HRN_STORAGE_PUT(storageRepoIdxWrite(0), TEST_WAL_MIGRATE, walBuffer, .comment = "wal written at format 5");
|
|
|
|
// Migrate the repository
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoFormat, 1, "6");
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaUpgrade(), "stanza upgrade - format 6 on encrypted repo");
|
|
TEST_RESULT_LOG(
|
|
"P00 INFO: stanza-upgrade for stanza 'db' on repo1\n"
|
|
"P00 INFO: upgrade repository format from 5 to 6");
|
|
|
|
// The pass is unchanged by the migration, so a file it encrypted before the migration must still be readable. The digest
|
|
// the pass derives with cannot follow the format of the info file storing it or this read would fail.
|
|
TEST_ASSIGN(
|
|
infoArchiveMigrate, infoArchiveLoadFile(storageRepoIdx(0), INFO_ARCHIVE_PATH_FILE_STR, cipherSpecMain),
|
|
"load archive info after migration");
|
|
TEST_RESULT_UINT(infoArchiveFormat(infoArchiveMigrate), REPOSITORY_FORMAT_6, "archive info at format 6");
|
|
|
|
StorageRead *const walRead = storageNewReadP(storageRepoIdx(0), STRDEF(TEST_WAL_MIGRATE));
|
|
|
|
cipherBlockFilterGroupAdd(
|
|
ioReadFilterGroup(storageReadIo(walRead)), cipherModeDecrypt, infoArchiveCipherSpec(infoArchiveMigrate));
|
|
|
|
TEST_RESULT_STR_Z(
|
|
strNewBuf(storageGetP(walRead)), "WAL BEFORE MIGRATION", "wal from before the migration is still readable");
|
|
|
|
hrnCfgEnvKeyRemoveRaw(cfgOptRepoCipherPass, 1);
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-upgrade - every format that can be read can be requested");
|
|
|
|
// The allow list for repo-format in build/config.yaml and REPOSITORY_FORMAT_MIN/MAX in version.h are declared separately,
|
|
// so make sure they match
|
|
for (unsigned int format = REPOSITORY_FORMAT_MIN; format <= REPOSITORY_FORMAT_MAX; format++)
|
|
{
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawFmt(argList, cfgOptRepoFormat, 1, "%u", format);
|
|
HRN_CFG_LOAD(cfgCmdStanzaUpgrade, argList);
|
|
|
|
TEST_RESULT_UINT(cfgOptionIdxUInt(cfgOptRepoFormat, 0), format, "format allowed");
|
|
}
|
|
|
|
const unsigned int formatUnsupported = REPOSITORY_FORMAT_MAX + 1;
|
|
|
|
argList = strLstDup(argListBase);
|
|
hrnCfgArgKeyRawFmt(argList, cfgOptRepoFormat, 1, "%u", formatUnsupported);
|
|
|
|
TEST_ERROR_FMT(
|
|
hrnCfgLoadP(cfgCmdStanzaUpgrade, argList), OptionInvalidValueError,
|
|
"'%u' is not allowed for 'repo1-format' option\n"
|
|
"HINT: allowed values are '5', '6'", formatUnsupported);
|
|
}
|
|
|
|
// *****************************************************************************************************************************
|
|
if (testBegin("cmdStanzaDelete(), stanzaDelete()"))
|
|
{
|
|
// Load Parameters
|
|
StringList *argListCmd = strLstNew();
|
|
hrnCfgArgKeyRawZ(argListCmd, cfgOptRepoPath, 1, TEST_PATH "/repo");
|
|
|
|
// Load Parameters
|
|
StringList *argList = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA_OTHER);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptPgPath, 1, TEST_PATH "/" TEST_STANZA_OTHER);
|
|
hrnCfgArgRawBool(argList, cfgOptOnline, false);
|
|
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList);
|
|
|
|
// Create pg_control for stanza-create
|
|
HRN_PG_CONTROL_PUT(storagePgWrite(), PG_VERSION_96);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaCreate(), "create a stanza that will not be deleted");
|
|
TEST_RESULT_LOG("P00 INFO: stanza-create for stanza 'otherstanza' on repo1");
|
|
|
|
argList = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgKeyRawZ(argList, cfgOptPgPath, 1, TEST_PATH "/pg");
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argList);
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-delete - stanza already deleted");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - success on stanza does not exist");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-delete - only archive exists");
|
|
|
|
// Confirm stanza does not exist
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/archive", "otherstanza/\n", .noRecurse = true,
|
|
.comment = "stanza '" TEST_STANZA "' archive does not exist");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/backup", "otherstanza/\n", .noRecurse = true,
|
|
.comment = "stanza '" TEST_STANZA "' backup does not exist");
|
|
|
|
// Create stanza archive only
|
|
HRN_STORAGE_PUT_EMPTY(
|
|
storageRepoWrite(), INFO_ARCHIVE_PATH_FILE, .comment = "create empty archive info for stanza '" TEST_STANZA "'");
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))), .comment = "create stop file");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/archive",
|
|
"db/\n"
|
|
"otherstanza/\n",
|
|
.noRecurse = true, .comment = "stanza archive exists");
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - archive only");
|
|
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/archive", "otherstanza/\n", .noRecurse = true, .comment = "stanza '" TEST_STANZA "' deleted");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-delete - only backup exists");
|
|
|
|
// Create stanza backup only
|
|
HRN_STORAGE_PUT_EMPTY(
|
|
storageRepoWrite(), INFO_BACKUP_PATH_FILE,
|
|
.comment = "create empty backup info for stanza '" TEST_STANZA "'");
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))), .comment = "create stop file");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/backup",
|
|
"db/\n"
|
|
"otherstanza/\n",
|
|
.noRecurse = true, .comment = "stanza backup exists");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - backup only");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/backup", "otherstanza/\n", .noRecurse = true, .comment = "stanza '" TEST_STANZA "' deleted");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("stanza-delete - empty directories");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "remove stanza '" TEST_STANZA "'");
|
|
|
|
// Create only stanza paths
|
|
HRN_STORAGE_PATH_CREATE(storageTest, "repo/archive/" TEST_STANZA, .comment = "create empty stanza archive path");
|
|
HRN_STORAGE_PATH_CREATE(storageTest, "repo/backup/" TEST_STANZA, .comment = "create empty stanza backup path");
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza delete - empty directories");
|
|
TEST_STORAGE_LIST(
|
|
storageTest, "repo/archive", "otherstanza/\n", .noRecurse = true, .comment = "stanza '" TEST_STANZA "' deleted");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("delete errors when pg appears to be running");
|
|
|
|
HRN_STORAGE_PUT_EMPTY(
|
|
storageRepoWrite(), INFO_BACKUP_PATH_FILE, .comment = "create empty backup info for stanza '" TEST_STANZA "'");
|
|
HRN_STORAGE_PUT_EMPTY(storageHrn, strZ(lockStopFileName(cfgOptionStr(cfgOptStanza))), .comment = "create stop file");
|
|
HRN_STORAGE_PUT_EMPTY(storagePgWrite(), PG_FILE_POSTMTRPID, .comment = "create " PG_FILE_POSTMTRPID " file");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaDelete(), PgRunningError, PG_FILE_POSTMTRPID " exists - looks like " PG_NAME " is running. "
|
|
"To delete stanza 'db' on repo1, shut down " PG_NAME " for stanza 'db' and try again, or use --force.");
|
|
|
|
// Specify repo option
|
|
StringList *argListDel = strLstDup(argList);
|
|
hrnCfgArgKeyRawZ(argListDel, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgRawZ(argListDel, cfgOptRepo, "2");
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argListDel);
|
|
|
|
HRN_STORAGE_PUT_EMPTY(storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE, .comment = "create empty backup info repo2");
|
|
|
|
TEST_ERROR(
|
|
cmdStanzaDelete(), PgRunningError, PG_FILE_POSTMTRPID " exists - looks like " PG_NAME " is running. "
|
|
"To delete stanza 'db' on repo2, shut down " PG_NAME " for stanza 'db' and try again, or use --force.");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("force delete when pg appears to be running, multi-repo");
|
|
|
|
argList = strLstDup(argListCmd);
|
|
hrnCfgArgRawZ(argList, cfgOptStanza, TEST_STANZA);
|
|
hrnCfgArgKeyRawFmt(argList, cfgOptPgPath, 1, TEST_PATH "/pg");
|
|
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
|
|
hrnCfgArgRawZ(argList, cfgOptRepo, "1");
|
|
hrnCfgArgRawBool(argList, cfgOptForce, true);
|
|
HRN_CFG_LOAD(cfgCmdStanzaDelete, argList);
|
|
|
|
TEST_RESULT_VOID(cmdStanzaDelete(), "stanza '" TEST_STANZA "' delete --force");
|
|
TEST_RESULT_BOOL(storagePathExistsP(storageTest, strNewZ("repo/backup/" TEST_STANZA)), false, "repo1: stanza deleted");
|
|
TEST_RESULT_BOOL(storagePathExistsP(storageTest, strNewZ("repo2/backup/" TEST_STANZA)), true, "repo2: stanza not deleted");
|
|
|
|
// -------------------------------------------------------------------------------------------------------------------------
|
|
TEST_TITLE("ensure other stanza never deleted from repo1");
|
|
|
|
TEST_STORAGE_LIST(
|
|
storageRepo(), "",
|
|
"archive/\n"
|
|
"archive/otherstanza/\n"
|
|
"archive/otherstanza/archive.info\n"
|
|
"archive/otherstanza/archive.info.copy\n"
|
|
"backup/\n"
|
|
"backup/otherstanza/\n"
|
|
"backup/otherstanza/backup.info\n"
|
|
"backup/otherstanza/backup.info.copy\n",
|
|
.comment = "otherstanza exists");
|
|
}
|
|
|
|
FUNCTION_HARNESS_RETURN_VOID();
|
|
}
|