1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-10-23 21:28:56 +03:00
Files
pgbackrest/test/src/module/command/stanzaTest.c
David Steele 700722b8b5 Derive the cipher key with SHA-256 at repository format 6.
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.
2026-09-11 18:25:19 +03:00

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();
}