1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-30 05:39:12 +02:00

Migrate local info command to C.

The info command will only be executed in C if the repository is local, i.e. not located on a remote repository host.  S3 is considered "local" in this case.

This is a direct migration from Perl to integrate as seamlessly with the remaining Perl code as possible. It should not be possible to determine if the C version is running unless debug-level logging is enabled.

Contributed by Cynthia Shang.
This commit is contained in:
Cynthia Shang 2018-12-13 16:22:34 -05:00 committed by David Steele
parent e6ef40e8a3
commit 205525b607
8 changed files with 1534 additions and 0 deletions

View File

@ -63,6 +63,14 @@
<p>Enable S3 storage and encryption for <cmd>archive-get</cmd> command in C.</p>
</release-item>
<release-item>
<release-item-contributor-list>
<release-item-contributor id="cynthia.shang"/>
</release-item-contributor-list>
<p>Migrate local <cmd>info</cmd> command to C.</p>
</release-item>
<release-item>
<p>Add <proper>S3</proper> storage driver.</p>
</release-item>

View File

@ -183,6 +183,9 @@ command/control/control.o: command/control/control.c command/control/control.h c
command/help/help.o: command/help/help.c common/assert.h common/debug.h common/error.auto.h common/error.h common/io/handle.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h version.h
$(CC) $(CFLAGS) -c command/help/help.c -o command/help/help.o
command/info/info.o: command/info/info.c command/archive/common.h command/info/info.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/group.h common/io/handle.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h crypto/crypto.h crypto/hash.h info/info.h info/infoArchive.h info/infoBackup.h info/infoPg.h perl/exec.h postgres/interface.h storage/fileRead.h storage/fileWrite.h storage/helper.h storage/info.h storage/storage.h
$(CC) $(CFLAGS) -c command/info/info.c -o command/info/info.o
common/debug.o: common/debug.c common/debug.h common/logLevel.h common/stackTrace.h common/type/convert.h
$(CC) $(CFLAGS) -c common/debug.c -o common/debug.o

View File

@ -21,6 +21,8 @@ WAL segment constants
***********************************************************************************************************************************/
STRING_EXTERN(WAL_SEGMENT_REGEXP_STR, WAL_SEGMENT_REGEXP);
STRING_EXTERN(WAL_SEGMENT_PARTIAL_REGEXP_STR, WAL_SEGMENT_PARTIAL_REGEXP);
STRING_EXTERN(WAL_SEGMENT_DIR_REGEXP_STR, WAL_SEGMENT_DIR_REGEXP);
STRING_EXTERN(WAL_SEGMENT_FILE_REGEXP_STR, WAL_SEGMENT_FILE_REGEXP);
/***********************************************************************************************************************************
Check for ok/error status files in the spool in/out directory

View File

@ -40,6 +40,12 @@ WAL segment constants
// Defines the size of standard WAL segment name -- hopefully this won't change
#define WAL_SEGMENT_NAME_SIZE ((unsigned int)24)
// WAL segment directory/file
#define WAL_SEGMENT_DIR_REGEXP "^[0-F]{16}$"
STRING_DECLARE(WAL_SEGMENT_DIR_REGEXP_STR);
#define WAL_SEGMENT_FILE_REGEXP "^[0-F]{24}-[0-f]{40}(\\.gz){0,1}$"
STRING_DECLARE(WAL_SEGMENT_FILE_REGEXP_STR);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/

643
src/command/info/info.c Normal file
View File

@ -0,0 +1,643 @@
/***********************************************************************************************************************************
Info Command
***********************************************************************************************************************************/
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "command/archive/common.h"
#include "command/info/info.h"
#include "common/debug.h"
#include "common/io/handle.h"
#include "common/log.h"
#include "common/memContext.h"
#include "common/type/json.h"
#include "config/config.h"
#include "info/info.h"
#include "info/infoArchive.h"
#include "info/infoBackup.h"
#include "info/infoPg.h"
#include "perl/exec.h"
#include "postgres/interface.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
STRING_STATIC(CFGOPTVAL_INFO_OUTPUT_TEXT_STR, "text");
// Naming convention: <sectionname>_KEY_<keyname>_STR. If the key exists in multiple sections, then <sectionname>_ is omitted.
STRING_STATIC(ARCHIVE_KEY_MIN_STR, "min");
STRING_STATIC(ARCHIVE_KEY_MAX_STR, "max");
STRING_STATIC(BACKREST_KEY_FORMAT_STR, "format");
STRING_STATIC(BACKREST_KEY_VERSION_STR, "version");
STRING_STATIC(BACKUP_KEY_BACKREST_STR, "backrest");
STRING_STATIC(BACKUP_KEY_INFO_STR, "info");
STRING_STATIC(BACKUP_KEY_LABEL_STR, "label");
STRING_STATIC(BACKUP_KEY_PRIOR_STR, "prior");
STRING_STATIC(BACKUP_KEY_REFERENCE_STR, "reference");
STRING_STATIC(BACKUP_KEY_TIMESTAMP_STR, "timestamp");
STRING_STATIC(BACKUP_KEY_TYPE_STR, "type");
STRING_STATIC(DB_KEY_ID_STR, "id");
STRING_STATIC(DB_KEY_SYSTEM_ID_STR, "system-id");
STRING_STATIC(DB_KEY_VERSION_STR, "version");
STRING_STATIC(INFO_KEY_REPOSITORY_STR, "repository");
STRING_STATIC(KEY_ARCHIVE_STR, "archive");
STRING_STATIC(KEY_DATABASE_STR, "database");
STRING_STATIC(KEY_DELTA_STR, "delta");
STRING_STATIC(KEY_SIZE_STR, "size");
STRING_STATIC(KEY_START_STR, "start");
STRING_STATIC(KEY_STOP_STR, "stop");
STRING_STATIC(STANZA_KEY_BACKUP_STR, "backup");
STRING_STATIC(STANZA_KEY_CIPHER_STR, "cipher");
STRING_STATIC(STANZA_KEY_NAME_STR, "name");
STRING_STATIC(STANZA_KEY_STATUS_STR, "status");
STRING_STATIC(STANZA_KEY_DB_STR, "db");
STRING_STATIC(STATUS_KEY_CODE_STR, "code");
STRING_STATIC(STATUS_KEY_MESSAGE_STR, "message");
STRING_STATIC(INFO_STANZA_STATUS_OK, "ok");
STRING_STATIC(INFO_STANZA_STATUS_ERROR, "error");
#define INFO_STANZA_STATUS_CODE_OK 0
STRING_STATIC(INFO_STANZA_STATUS_MESSAGE_OK_STR, "ok");
#define INFO_STANZA_STATUS_CODE_MISSING_STANZA_PATH 1
STRING_STATIC(INFO_STANZA_STATUS_MESSAGE_MISSING_STANZA_PATH_STR, "missing stanza path");
#define INFO_STANZA_STATUS_CODE_NO_BACKUP 2
STRING_STATIC(INFO_STANZA_STATUS_MESSAGE_NO_BACKUP_STR, "no valid backups");
#define INFO_STANZA_STATUS_CODE_MISSING_STANZA_DATA 3
STRING_STATIC(INFO_STANZA_STATUS_MESSAGE_MISSING_STANZA_DATA_STR, "missing stanza data");
/***********************************************************************************************************************************
Set error status code and message for the stanza to the code and message passed.
***********************************************************************************************************************************/
static void
stanzaStatus(const int code, const String *message, Variant *stanzaInfo)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(INT, code);
FUNCTION_TEST_PARAM(STRING, message);
FUNCTION_TEST_PARAM(VARIANT, stanzaInfo);
FUNCTION_TEST_ASSERT(code >= 0 && code <= 3);
FUNCTION_TEST_ASSERT(message != NULL);
FUNCTION_TEST_ASSERT(stanzaInfo != NULL);
FUNCTION_TEST_END();
Variant *stanzaStatus = varNewStr(STANZA_KEY_STATUS_STR);
KeyValue *statusKv = kvPutKv(varKv(stanzaInfo), stanzaStatus);
kvAdd(statusKv, varNewStr(STATUS_KEY_CODE_STR), varNewInt(code));
kvAdd(statusKv, varNewStr(STATUS_KEY_MESSAGE_STR), varNewStr(message));
FUNCTION_TEST_RESULT_VOID();
}
/***********************************************************************************************************************************
Set the data for the archive section of the stanza for the database info from the backup.info file.
***********************************************************************************************************************************/
void
archiveDbList(const String *stanza, const InfoPgData *pgData, VariantList *archiveSection, const InfoArchive *info, bool currentDb)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(INFO_PG_DATAP, pgData);
FUNCTION_TEST_PARAM(VARIANT, archiveSection);
FUNCTION_TEST_PARAM(BOOL, currentDb);
FUNCTION_TEST_ASSERT(stanza != NULL);
FUNCTION_TEST_ASSERT(pgData != NULL);
FUNCTION_TEST_ASSERT(archiveSection != NULL);
FUNCTION_TEST_END();
// With multiple DB versions, the backup.info history-id may not be the same as archive.info history-id, so the
// archive path must be built by retrieving the archive id given the db version and system id of the backup.info file.
// If there is no match, an error will be thrown.
const String *archiveId = infoArchiveIdHistoryMatch(info, pgData->id, pgData->version, pgData->systemId);
String *archivePath = strNewFmt("%s/%s/%s", STORAGE_REPO_ARCHIVE, strPtr(stanza), strPtr(archiveId));
String *archiveStart = NULL;
String *archiveStop = NULL;
Variant *archiveInfo = varNewKv();
// Get a list of WAL directories in the archive repo from oldest to newest, if any exist
StringList *walDir = storageListP(storageRepo(), archivePath, .expression = WAL_SEGMENT_DIR_REGEXP_STR);
if (walDir != NULL)
{
unsigned int sizeWalDir = strLstSize(walDir);
if (sizeWalDir > 1)
walDir = strLstSort(walDir, sortOrderAsc);
// Not every WAL dir has WAL files so check each
for (unsigned int idx = 0; idx < sizeWalDir; idx++)
{
// Get a list of all WAL in this WAL dir
StringList *list = storageListP(
storageRepo(), strNewFmt("%s/%s", strPtr(archivePath), strPtr(strLstGet(walDir, idx))),
.expression = WAL_SEGMENT_FILE_REGEXP_STR);
// If wal segments are found, get the oldest one as the archive start
if (strLstSize(list) > 0)
{
// Sort the list from oldest to newest to get the oldest starting WAL archived for this DB
list = strLstSort(list, sortOrderAsc);
archiveStart = strSubN(strLstGet(list, 0), 0, 24);
break;
}
}
// Iterate through the directory list in the reverse so processing newest first. Cast comparison to an int for readability.
for (unsigned int idx = sizeWalDir - 1; (int)idx > 0; idx--)
{
// Get a list of all WAL in this WAL dir
StringList *list = storageListP(
storageRepo(), strNewFmt("%s/%s", strPtr(archivePath), strPtr(strLstGet(walDir, idx))),
.expression = WAL_SEGMENT_FILE_REGEXP_STR);
// If wal segments are found, get the newest one as the archive stop
if (strLstSize(list) > 0)
{
// Sort the list from newest to oldest to get the newest ending WAL archived for this DB
list = strLstSort(list, sortOrderDesc);
archiveStop = strSubN(strLstGet(list, 0), 0, 24);
break;
}
}
}
// If there is an archive or the database is the current database then store it
if (currentDb || archiveStart != NULL)
{
// Add empty database section to archiveInfo and then fill in database id from the backup.info
KeyValue *databaseInfo = kvPutKv(varKv(archiveInfo), varNewStr(KEY_DATABASE_STR));
kvAdd(databaseInfo, varNewStr(DB_KEY_ID_STR), varNewUInt64(pgData->id));
kvPut(varKv(archiveInfo), varNewStr(DB_KEY_ID_STR), varNewStr(archiveId));
kvPut(
varKv(archiveInfo), varNewStr(ARCHIVE_KEY_MIN_STR), (archiveStart != NULL ? varNewStr(archiveStart) : (Variant *)NULL));
kvPut(varKv(archiveInfo), varNewStr(ARCHIVE_KEY_MAX_STR), (archiveStop != NULL ? varNewStr(archiveStop) : (Variant *)NULL));
varLstAdd(archiveSection, archiveInfo);
}
FUNCTION_TEST_RESULT_VOID();
}
/***********************************************************************************************************************************
For each current backup in the backup.info file of the stanza, set the data for the backup section.
***********************************************************************************************************************************/
void
backupList(const String *stanza, VariantList *backupSection, InfoBackup *info)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(VARIANT, backupSection);
FUNCTION_TEST_PARAM(INFO_BACKUP, info);
FUNCTION_TEST_ASSERT(stanza != NULL);
FUNCTION_TEST_ASSERT(backupSection != NULL);
FUNCTION_TEST_ASSERT(info != NULL);
FUNCTION_TEST_END();
// For each current backup, get the label and corresponding data and build the backup section
for (unsigned int keyIdx = 0; keyIdx < infoBackupDataTotal(info); keyIdx++)
{
// Get the backup data
InfoBackupData backupData = infoBackupData(info, keyIdx);
Variant *backupInfo = varNewKv();
// main keys
kvPut(varKv(backupInfo), varNewStr(BACKUP_KEY_LABEL_STR), varNewStr(backupData.backupLabel));
kvPut(varKv(backupInfo), varNewStr(BACKUP_KEY_TYPE_STR), varNewStr(backupData.backupType));
kvPut(
varKv(backupInfo), varNewStr(BACKUP_KEY_PRIOR_STR),
(backupData.backupPrior != NULL ? varNewStr(backupData.backupPrior) : NULL));
kvPut(
varKv(backupInfo), varNewStr(BACKUP_KEY_REFERENCE_STR),
(backupData.backupReference != NULL ? varNewVarLst(varLstNewStrLst(backupData.backupReference)) : NULL));
// archive section
KeyValue *archiveInfo = kvPutKv(varKv(backupInfo), varNewStr(KEY_ARCHIVE_STR));
kvAdd(
archiveInfo, varNewStr(KEY_START_STR),
(backupData.backupArchiveStart != NULL ? varNewStr(backupData.backupArchiveStart) : NULL));
kvAdd(
archiveInfo, varNewStr(KEY_STOP_STR),
(backupData.backupArchiveStop != NULL ? varNewStr(backupData.backupArchiveStop) : NULL));
// backrest section
KeyValue *backrestInfo = kvPutKv(varKv(backupInfo), varNewStr(BACKUP_KEY_BACKREST_STR));
kvAdd(backrestInfo, varNewStr(BACKREST_KEY_FORMAT_STR), varNewUInt64(backupData.backrestFormat));
kvAdd(backrestInfo, varNewStr(BACKREST_KEY_VERSION_STR), varNewStr(backupData.backrestVersion));
// database section
KeyValue *dbInfo = kvPutKv(varKv(backupInfo), varNewStr(KEY_DATABASE_STR));
kvAdd(dbInfo, varNewStr(DB_KEY_ID_STR), varNewUInt64(backupData.backupPgId));
// info section
KeyValue *infoInfo = kvPutKv(varKv(backupInfo), varNewStr(BACKUP_KEY_INFO_STR));
kvAdd(infoInfo, varNewStr(KEY_SIZE_STR), varNewUInt64(backupData.backupInfoSize));
kvAdd(infoInfo, varNewStr(KEY_DELTA_STR), varNewUInt64(backupData.backupInfoSizeDelta));
// info:repository section
KeyValue *repoInfo = kvPutKv(infoInfo, varNewStr(INFO_KEY_REPOSITORY_STR));
kvAdd(repoInfo, varNewStr(KEY_SIZE_STR), varNewUInt64(backupData.backupInfoRepoSize));
kvAdd(repoInfo, varNewStr(KEY_DELTA_STR), varNewUInt64(backupData.backupInfoRepoSizeDelta));
// timestamp section
KeyValue *timeInfo = kvPutKv(varKv(backupInfo), varNewStr(BACKUP_KEY_TIMESTAMP_STR));
kvAdd(timeInfo, varNewStr(KEY_START_STR), varNewUInt64(backupData.backupTimestampStart));
kvAdd(timeInfo, varNewStr(KEY_STOP_STR), varNewUInt64(backupData.backupTimestampStop));
varLstAdd(backupSection, backupInfo);
}
FUNCTION_TEST_RESULT_VOID();
}
/***********************************************************************************************************************************
Set the stanza data for each stanza found in the repo.
***********************************************************************************************************************************/
static VariantList *
stanzaInfoList(const String *stanza, StringList *stanzaList)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(STRING_LIST, stanzaList);
FUNCTION_TEST_ASSERT(stanzaList != NULL);
FUNCTION_TEST_END();
VariantList *result = varLstNew();
bool stanzaFound = false;
// Sort the list
stanzaList = strLstSort(stanzaList, sortOrderAsc);
for (unsigned int idx = 0; idx < strLstSize(stanzaList); idx++)
{
String *stanzaListName = strLstGet(stanzaList, idx);
// If a specific stanza has been requested and this is not it, then continue to the next in the list else indicate found
if (stanza != NULL)
{
if (!strEq(stanza, stanzaListName))
continue;
else
stanzaFound = true;
}
// Create the stanzaInfo and section variables
Variant *stanzaInfo = varNewKv();
VariantList *dbSection = varLstNew();
VariantList *backupSection = varLstNew();
VariantList *archiveSection = varLstNew();
InfoBackup *info = NULL;
// Catch certain errors
TRY_BEGIN()
{
// Attempt to load the backup info file
info = infoBackupNew(
storageRepo(), strNewFmt("%s/%s/%s", STORAGE_REPO_BACKUP, strPtr(stanzaListName), INFO_BACKUP_FILE), false,
cipherType(cfgOptionStr(cfgOptRepoCipherType)), cfgOptionStr(cfgOptRepoCipherPass));
}
CATCH(FileMissingError)
{
// If there is no backup.info then set the status to indicate missing
stanzaStatus(
INFO_STANZA_STATUS_CODE_MISSING_STANZA_DATA, INFO_STANZA_STATUS_MESSAGE_MISSING_STANZA_DATA_STR, stanzaInfo);
}
CATCH(CryptoError)
{
// If a reason for the error is due to a an ecryption error, add a hint
THROW_FMT(
CryptoError,
"%s\n"
"HINT: use option --stanza if encryption settings are different for the stanza than the global settings",
errorMessage());
}
TRY_END();
// Set the stanza name and cipher
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_NAME_STR), varNewStr(stanzaListName));
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_CIPHER_STR), varNewStr(cfgOptionStr(cfgOptRepoCipherType)));
// If the backup.info file exists, get the database history information (newest to oldest) and corresponding archive
if (info != NULL)
{
for (unsigned int pgIdx = 0; pgIdx < infoPgDataTotal(infoBackupPg(info)); pgIdx++)
{
InfoPgData pgData = infoPgData(infoBackupPg(info), pgIdx);
Variant *pgInfo = varNewKv();
kvPut(varKv(pgInfo), varNewStr(DB_KEY_ID_STR), varNewUInt64(pgData.id));
kvPut(varKv(pgInfo), varNewStr(DB_KEY_SYSTEM_ID_STR), varNewUInt64(pgData.systemId));
kvPut(varKv(pgInfo), varNewStr(DB_KEY_VERSION_STR), varNewStr(pgVersionToStr(pgData.version)));
varLstAdd(dbSection, pgInfo);
// Get the archive info for the DB from the archive.info file
InfoArchive *info = infoArchiveNew(
storageRepo(), strNewFmt("%s/%s/%s", STORAGE_REPO_ARCHIVE, strPtr(stanzaListName), INFO_ARCHIVE_FILE), false,
cipherType(cfgOptionStr(cfgOptRepoCipherType)), cfgOptionStr(cfgOptRepoCipherPass));
archiveDbList(stanzaListName, &pgData, archiveSection, info, (pgIdx == 0 ? true : false));
}
// Get data for all existing backups for this stanza
backupList(stanzaListName, backupSection, info);
}
// Add the database history, backup and archive sections to the stanza info
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_DB_STR), varNewVarLst(dbSection));
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_BACKUP_STR), varNewVarLst(backupSection));
kvPut(varKv(stanzaInfo), varNewStr(KEY_ARCHIVE_STR), varNewVarLst(archiveSection));
// If a status has not already been set and there are no backups then set status to no backup
if (kvGet(varKv(stanzaInfo), varNewStr(STANZA_KEY_STATUS_STR)) == NULL &&
varLstSize(kvGetList(varKv(stanzaInfo), varNewStr(STANZA_KEY_BACKUP_STR))) == 0)
{
stanzaStatus(INFO_STANZA_STATUS_CODE_NO_BACKUP, INFO_STANZA_STATUS_MESSAGE_NO_BACKUP_STR, stanzaInfo);
}
// If a status has still not been set then set it to OK
if (kvGet(varKv(stanzaInfo), varNewStr(STANZA_KEY_STATUS_STR)) == NULL)
stanzaStatus(INFO_STANZA_STATUS_CODE_OK, INFO_STANZA_STATUS_MESSAGE_OK_STR, stanzaInfo);
varLstAdd(result, stanzaInfo);
}
// If looking for a specific stanza and it was not found, set minimum info and the status
if (stanza != NULL && !stanzaFound)
{
Variant *stanzaInfo = varNewKv();
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_NAME_STR), varNewStr(stanza));
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_DB_STR), varNewVarLst(varLstNew()));
kvPut(varKv(stanzaInfo), varNewStr(STANZA_KEY_BACKUP_STR), varNewVarLst(varLstNew()));
stanzaStatus(INFO_STANZA_STATUS_CODE_MISSING_STANZA_PATH, INFO_STANZA_STATUS_MESSAGE_MISSING_STANZA_PATH_STR, stanzaInfo);
varLstAdd(result, stanzaInfo);
}
FUNCTION_TEST_RESULT(VARIANT_LIST, result);
}
/***********************************************************************************************************************************
Format the text output for each database of the stanza.
***********************************************************************************************************************************/
static void
formatTextDb(const KeyValue *stanzaInfo, String *resultStr)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(KEY_VALUE, stanzaInfo);
FUNCTION_TEST_PARAM(STRING, resultStr);
FUNCTION_TEST_ASSERT(stanzaInfo != NULL);
FUNCTION_TEST_END();
VariantList *dbSection = kvGetList(stanzaInfo, varNewStr(STANZA_KEY_DB_STR));
VariantList *archiveSection = kvGetList(stanzaInfo, varNewStr(KEY_ARCHIVE_STR));
VariantList *backupSection = kvGetList(stanzaInfo, varNewStr(STANZA_KEY_BACKUP_STR));
// For each database find the corresponding archive and backup info
for (unsigned int dbIdx = 0; dbIdx < varLstSize(dbSection); dbIdx++)
{
KeyValue *pgInfo = varKv(varLstGet(dbSection, dbIdx));
uint64_t dbId = varUInt64(kvGet(pgInfo, varNewStr(DB_KEY_ID_STR)));
// List is ordered so 0 is always the current DB index
if (dbIdx == 0)
strCat(resultStr, "\n db (current)");
// Get the min/max archive information for the database
String *archiveResult = strNew("");
for (unsigned int archiveIdx = 0; archiveIdx < varLstSize(archiveSection); archiveIdx++)
{
KeyValue *archiveInfo = varKv(varLstGet(archiveSection, archiveIdx));
KeyValue *archiveDbInfo = varKv(kvGet(archiveInfo, varNewStr(KEY_DATABASE_STR)));
uint64_t archiveDbId = varUInt64(kvGet(archiveDbInfo, varNewStr(DB_KEY_ID_STR)));
if (archiveDbId == dbId)
{
strCatFmt(
archiveResult, "\n wal archive min/max (%s): ",
strPtr(varStr(kvGet(archiveInfo, varNewStr(DB_KEY_ID_STR)))));
// Get the archive min/max if there are any archives for the database
if (kvGet(archiveInfo, varNewStr(ARCHIVE_KEY_MIN_STR)) != NULL)
{
strCatFmt(
archiveResult, "%s/%s\n", strPtr(varStr(kvGet(archiveInfo, varNewStr(ARCHIVE_KEY_MIN_STR)))),
strPtr(varStr(kvGet(archiveInfo, varNewStr(ARCHIVE_KEY_MAX_STR)))));
}
else
strCat(archiveResult, "none present\n");
}
}
// Get the information for each current backup
String *backupResult = strNew("");
for (unsigned int backupIdx = 0; backupIdx < varLstSize(backupSection); backupIdx++)
{
KeyValue *backupInfo = varKv(varLstGet(backupSection, backupIdx));
KeyValue *backupDbInfo = varKv(kvGet(backupInfo, varNewStr(KEY_DATABASE_STR)));
uint64_t backupDbId = varUInt64(kvGet(backupDbInfo, varNewStr(DB_KEY_ID_STR)));
if (backupDbId == dbId)
{
strCatFmt(
backupResult, "\n %s backup: %s\n", strPtr(varStr(kvGet(backupInfo, varNewStr(BACKUP_KEY_TYPE_STR)))),
strPtr(varStr(kvGet(backupInfo, varNewStr(BACKUP_KEY_LABEL_STR)))));
KeyValue *timestampInfo = varKv(kvGet(backupInfo, varNewStr(BACKUP_KEY_TIMESTAMP_STR)));
// Get and format the backup start/stop time
static char timeBufferStart[20];
static char timeBufferStop[20];
time_t timeStart = (time_t) varUInt64(kvGet(timestampInfo, varNewStr(KEY_START_STR)));
time_t timeStop = (time_t) varUInt64(kvGet(timestampInfo, varNewStr(KEY_STOP_STR)));
strftime(timeBufferStart, 20, "%Y-%m-%d %H:%M:%S", localtime(&timeStart));
strftime(timeBufferStop, 20, "%Y-%m-%d %H:%M:%S", localtime(&timeStop));
strCatFmt(
backupResult, " timestamp start/stop: %s / %s\n", timeBufferStart, timeBufferStop);
strCat(backupResult, " wal start/stop: ");
KeyValue *archiveDBackupInfo = varKv(kvGet(backupInfo, varNewStr(KEY_ARCHIVE_STR)));
if (kvGet(archiveDBackupInfo, varNewStr(KEY_START_STR)) != NULL &&
kvGet(archiveDBackupInfo, varNewStr(KEY_STOP_STR)) != NULL)
{
strCatFmt(backupResult, "%s / %s\n", strPtr(varStr(kvGet(archiveDBackupInfo, varNewStr(KEY_START_STR)))),
strPtr(varStr(kvGet(archiveDBackupInfo, varNewStr(KEY_STOP_STR)))));
}
else
strCat(backupResult, "n/a\n");
KeyValue *info = varKv(kvGet(backupInfo, varNewStr(BACKUP_KEY_INFO_STR)));
strCatFmt(
backupResult, " database size: %s, backup size: %s\n",
strPtr(strSizeFormat(varUInt64Force(kvGet(info, varNewStr(KEY_SIZE_STR))))),
strPtr(strSizeFormat(varUInt64Force(kvGet(info, varNewStr(KEY_DELTA_STR))))));
KeyValue *repoInfo = varKv(kvGet(info, varNewStr(INFO_KEY_REPOSITORY_STR)));
strCatFmt(
backupResult, " repository size: %s, repository backup size: %s\n",
strPtr(strSizeFormat(varUInt64Force(kvGet(repoInfo, varNewStr(KEY_SIZE_STR))))),
strPtr(strSizeFormat(varUInt64Force(kvGet(repoInfo, varNewStr(KEY_DELTA_STR))))));
if (kvGet(backupInfo, varNewStr(BACKUP_KEY_REFERENCE_STR)) != NULL)
{
StringList *referenceList = strLstNewVarLst(varVarLst(kvGet(backupInfo, varNewStr(BACKUP_KEY_REFERENCE_STR))));
strCatFmt(backupResult, " backup reference list: %s\n", strPtr(strLstJoin(referenceList, ", ")));
}
}
}
// If there is data to display, then display it.
if (strSize(archiveResult) > 0 || strSize(backupResult) > 0)
{
if (dbIdx != 0)
strCat(resultStr, "\n db (prior)");
if (strSize(archiveResult) > 0)
strCat(resultStr, strPtr(archiveResult));
if (strSize(backupResult) > 0)
strCat(resultStr, strPtr(backupResult));
}
}
FUNCTION_TEST_RESULT_VOID();
}
/***********************************************************************************************************************************
Render the information for the stanza based on the command parameters.
***********************************************************************************************************************************/
static String *
infoRender(void)
{
FUNCTION_DEBUG_VOID(logLevelDebug);
String *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
// Get stanza if specified
const String *stanza = cfgOptionTest(cfgOptStanza) ? cfgOptionStr(cfgOptStanza) : NULL;
// Get a list of stanzas in the backup directory.
StringList *stanzaList = storageListP(storageRepo(), strNew(STORAGE_REPO_BACKUP), .errorOnMissing = true);
VariantList *infoList = varLstNew();
String *resultStr = strNew("");
// If the backup storage exists, then search for and process any stanzas
if (strLstSize(stanzaList) > 0)
infoList = stanzaInfoList(stanza, stanzaList);
// Format text output
if (strEq(cfgOptionStr(cfgOptOutput), CFGOPTVAL_INFO_OUTPUT_TEXT_STR))
{
// Process any stanza directories
if (varLstSize(infoList) > 0)
{
for (unsigned int stanzaIdx = 0; stanzaIdx < varLstSize(infoList); stanzaIdx++)
{
KeyValue *stanzaInfo = varKv(varLstGet(infoList, stanzaIdx));
// Add a carriage return between stanzas
if (stanzaIdx > 0)
strCatFmt(resultStr, "\n");
// Stanza name and status
strCatFmt(
resultStr, "stanza: %s\n status: ", strPtr(varStr(kvGet(stanzaInfo, varNewStr(STANZA_KEY_NAME_STR)))));
// If an error has occurred, provide the information that is available and move onto next stanza
KeyValue *stanzaStatus = varKv(kvGet(stanzaInfo, varNewStr(STANZA_KEY_STATUS_STR)));
int statusCode = varInt(kvGet(stanzaStatus, varNewStr(STATUS_KEY_CODE_STR)));
if (statusCode != INFO_STANZA_STATUS_CODE_OK)
{
strCatFmt(
resultStr, "%s (%s)\n", strPtr(INFO_STANZA_STATUS_ERROR),
strPtr(varStr(kvGet(stanzaStatus, varNewStr(STATUS_KEY_MESSAGE_STR)))));
if (statusCode == INFO_STANZA_STATUS_CODE_MISSING_STANZA_DATA ||
statusCode == INFO_STANZA_STATUS_CODE_NO_BACKUP)
{
strCatFmt(
resultStr, " cipher: %s\n", strPtr(varStr(kvGet(stanzaInfo, varNewStr(STANZA_KEY_CIPHER_STR)))));
// If there is a backup.info file but no backups, then process the archive info
if (statusCode == INFO_STANZA_STATUS_CODE_NO_BACKUP)
formatTextDb(stanzaInfo, resultStr);
}
continue;
}
else
strCatFmt(resultStr, "%s\n", strPtr(INFO_STANZA_STATUS_OK));
// Cipher
strCatFmt(resultStr, " cipher: %s\n",
strPtr(varStr(kvGet(stanzaInfo, varNewStr(STANZA_KEY_CIPHER_STR)))));
formatTextDb(stanzaInfo, resultStr);
}
}
else
resultStr = strNewFmt("No stanzas exist in %s\n", strPtr(storagePathNP(storageRepo(), NULL)));
}
// Format json output
else
resultStr = varToJson(varNewVarLst(infoList), 4);
memContextSwitch(MEM_CONTEXT_OLD());
result = strDup(resultStr);
memContextSwitch(MEM_CONTEXT_TEMP());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_DEBUG_RESULT(STRING, result);
}
/***********************************************************************************************************************************
Render info and output to stdout
***********************************************************************************************************************************/
void
cmdInfo(void)
{
FUNCTION_DEBUG_VOID(logLevelDebug);
MEM_CONTEXT_TEMP_BEGIN()
{
if (!cfgOptionTest(cfgOptRepoHost)) // {uncovered - Perl code is covered in integration tests}
{
ioHandleWriteOneStr(STDOUT_FILENO, infoRender());
}
// Else do it in Perl
else
perlExec(); // {+uncovered}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_DEBUG_RESULT_VOID();
}

12
src/command/info/info.h Normal file
View File

@ -0,0 +1,12 @@
/***********************************************************************************************************************************
Info Command
***********************************************************************************************************************************/
#ifndef COMMAND_INFO_INFO_H
#define COMMAND_INFO_INFO_H
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void cmdInfo(void);
#endif

View File

@ -612,6 +612,13 @@ unit:
coverage:
command/control/control: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: info
total: 3
coverage:
command/info/info: full
# ********************************************************************************************************************************
- name: archive

View File

@ -0,0 +1,853 @@
/***********************************************************************************************************************************
Test Info Command
***********************************************************************************************************************************/
#include "storage/driver/posix/storage.h"
#include "common/harnessConfig.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create the repo directories
String *repoPath = strNewFmt("%s/repo", testPath());
String *archivePath = strNewFmt("%s/%s", strPtr(repoPath), "archive");
String *backupPath = strNewFmt("%s/%s", strPtr(repoPath), "backup");
String *archiveStanza1Path = strNewFmt("%s/stanza1", strPtr(archivePath));
String *backupStanza1Path = strNewFmt("%s/stanza1", strPtr(backupPath));
// *****************************************************************************************************************************
if (testBegin("infoRender()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAdd(argList, strNewFmt("--repo-path=%s", strPtr(repoPath)));
strLstAddZ(argList, "info");
StringList *argListText = strLstDup(argList);
strLstAddZ(argList, "--output=json");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
// No repo path
//--------------------------------------------------------------------------------------------------------------------------
TEST_ERROR_FMT(
infoRender(), PathOpenError,
"unable to open path '%s' for read: [2] No such file or directory", strPtr(backupPath));
storagePathCreateNP(storageLocalWrite(), archivePath);
storagePathCreateNP(storageLocalWrite(), backupPath);
// No stanzas have been created
//--------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_STR(strPtr(infoRender()), "[]\n", "json - repo but no stanzas");
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_RESULT_STR(strPtr(infoRender()),
strPtr(strNewFmt("No stanzas exist in %s\n", strPtr(storagePathNP(storageRepo(), NULL)))), "text - no stanzas");
// Empty stanza
//--------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), backupStanza1Path), "backup stanza1 directory");
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveStanza1Path), "archive stanza1 directory");
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: stanza1\n"
" status: error (missing stanza data)\n"
" cipher: none\n", "text - missing stanza data");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"archive\" : [],\n"
" \"backup\" : [],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [],\n"
" \"name\" : \"stanza1\",\n"
" \"status\" : {\n"
" \"code\" : 3,\n"
" \"message\" : \"missing stanza data\"\n"
" }\n"
" }\n"
"]\n", "json - missing stanza data");
// backup.info file exists, but archive.info does not
//--------------------------------------------------------------------------------------------------------------------------
String *content = strNew
(
"[backrest]\n"
"backrest-checksum=\"51774ffab293c5cfb07511d7d2e101e92416f4ed\"\n"
"backrest-format=5\n"
"backrest-version=\"2.04\"\n"
"\n"
"[db]\n"
"db-catalog-version=201409291\n"
"db-control-version=942\n"
"db-id=2\n"
"db-system-id=6569239123849665679\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-catalog-version\":201306121,\"db-control-version\":937,\"db-system-id\":6569239123849665666,"
"\"db-version\":\"9.3\"}\n"
"2={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6569239123849665679,"
"\"db-version\":\"9.4\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/backup.info", strPtr(backupStanza1Path))),
bufNewStr(content)), "put backup info to file");
TEST_ERROR_FMT(infoRender(), FileMissingError,
"unable to load info file '%s/archive.info' or '%s/archive.info.copy':\n"
"FileMissingError: unable to open '%s/archive.info' for read: [2] No such file or directory\n"
"FileMissingError: unable to open '%s/archive.info.copy' for read: [2] No such file or directory\n"
"HINT: archive.info cannot be opened but is required to push/get WAL segments.\n"
"HINT: is archive_command configured correctly in postgresql.conf?\n"
"HINT: has a stanza-create been performed?\n"
"HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme.",
strPtr(archiveStanza1Path), strPtr(archiveStanza1Path), strPtr(archiveStanza1Path), strPtr(archiveStanza1Path));
// backup.info/archive.info files exist, mismatched db ids, no backup:current section so no valid backups
// Only the current db information from the db:history will be processed.
//--------------------------------------------------------------------------------------------------------------------------
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"0da11608456bae64c42cc1dc8df4ae79b953d597\"\n"
"backrest-format=5\n"
"backrest-version=\"2.04\"\n"
"\n"
"[db]\n"
"db-id=1\n"
"db-system-id=6569239123849665679\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-id\":6569239123849665679,\"db-version\":\"9.4\"}\n"
"2={\"db-id\":6569239123849665666,\"db-version\":\"9.3\"}\n"
"3={\"db-id\":6569239123849665679,\"db-version\":\"9.4\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/archive.info", strPtr(archiveStanza1Path))),
bufNewStr(content)), "put archive info to file");
// archive section will cross reference backup db-id 2 to archive db-id 3 but db section will only use the db-ids from
// backup.info
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"archive\" : [\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 2\n"
" },\n"
" \"id\" : \"9.4-3\",\n"
" \"max\" : null,\n"
" \"min\" : null\n"
" }\n"
" ],\n"
" \"backup\" : [],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [\n"
" {\n"
" \"id\" : 2,\n"
" \"system-id\" : 6569239123849665679,\n"
" \"version\" : \"9.4\"\n"
" },\n"
" {\n"
" \"id\" : 1,\n"
" \"system-id\" : 6569239123849665666,\n"
" \"version\" : \"9.3\"\n"
" }\n"
" ],\n"
" \"name\" : \"stanza1\",\n"
" \"status\" : {\n"
" \"code\" : 2,\n"
" \"message\" : \"no valid backups\"\n"
" }\n"
" }\n"
"]\n", "json - single stanza, no valid backups");
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: stanza1\n"
" status: error (no valid backups)\n"
" cipher: none\n"
"\n"
" db (current)\n"
" wal archive min/max (9.4-3): none present\n",
"text - single stanza, no valid backups");
// Coverage for stanzaStatus branches
//--------------------------------------------------------------------------------------------------------------------------
String *archiveDb1_1 = strNewFmt("%s/9.4-1/0000000100000000", strPtr(archiveStanza1Path));
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveDb1_1), "create db1 archive WAL1 directory");
TEST_RESULT_INT(system(
strPtr(strNewFmt("touch %s", strPtr(strNewFmt("%s/000000010000000000000002-ac61b8f1ec7b1e6c3eaee9345214595eb7daa9a1.gz",
strPtr(archiveDb1_1)))))), 0, "touch WAL1 file");
TEST_RESULT_INT(system(
strPtr(strNewFmt("touch %s", strPtr(strNewFmt("%s/000000010000000000000003-37dff2b7552a9d66e4bae1a762488a6885e7082c.gz",
strPtr(archiveDb1_1)))))), 0, "touch WAL1 file");
String *archiveDb1_2 = strNewFmt("%s/9.4-1/0000000200000000", strPtr(archiveStanza1Path));
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveDb1_2), "create db1 archive WAL2 directory");
TEST_RESULT_INT(system(
strPtr(strNewFmt("touch %s", strPtr(strNewFmt("%s/000000020000000000000003-37dff2b7552a9d66e4bae1a762488a6885e7082c.gz",
strPtr(archiveDb1_2)))))), 0, "touch WAL2 file");
String *archiveDb1_3 = strNewFmt("%s/9.4-1/0000000300000000", strPtr(archiveStanza1Path));
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveDb1_3), "create db1 archive WAL3 directory");
String *archiveDb3 = strNewFmt("%s/9.4-3/0000000100000000", strPtr(archiveStanza1Path));
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveDb3), "create db3 archive WAL1 directory");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"2edbac400200cc0bd559951f1ee166de5c6f5f49\"\n"
"backrest-format=5\n"
"backrest-version=\"2.04\"\n"
"\n"
"[db]\n"
"db-catalog-version=201409291\n"
"db-control-version=942\n"
"db-id=3\n"
"db-system-id=6569239123849665679\n"
"db-version=\"9.4\"\n"
"\n"
"[backup:current]\n"
"20181116-154756F={\"backrest-format\":5,\"backrest-version\":\"2.04\","
"\"backup-archive-start\":null,\"backup-archive-stop\":null,"
"\"backup-info-repo-size\":3159776,\"backup-info-repo-size-delta\":3159,\"backup-info-size\":26897030,"
"\"backup-info-size-delta\":26897030,\"backup-timestamp-start\":1542383276,\"backup-timestamp-stop\":1542383289,"
"\"backup-type\":\"full\",\"db-id\":1,\"option-archive-check\":true,\"option-archive-copy\":false,"
"\"option-backup-standby\":false,\"option-checksum-page\":true,\"option-compress\":true,\"option-hardlink\":false,"
"\"option-online\":true}\n"
"\n"
"[db:history]\n"
"1={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6569239123849665679,"
"\"db-version\":\"9.4\"}\n"
"2={\"db-catalog-version\":201306121,\"db-control-version\":937,\"db-system-id\":6569239123849665666,"
"\"db-version\":\"9.3\"}\n"
"3={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6569239123849665679,"
"\"db-version\":\"9.4\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/backup.info", strPtr(backupStanza1Path))),
bufNewStr(content)), "put backup info to file");
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"archive\" : [\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 3\n"
" },\n"
" \"id\" : \"9.4-3\",\n"
" \"max\" : null,\n"
" \"min\" : null\n"
" },\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"id\" : \"9.4-1\",\n"
" \"max\" : \"000000020000000000000003\",\n"
" \"min\" : \"000000010000000000000002\"\n"
" }\n"
" ],\n"
" \"backup\" : [\n"
" {\n"
" \"archive\" : {\n"
" \"start\" : null,\n"
" \"stop\" : null\n"
" },\n"
" \"backrest\" : {\n"
" \"format\" : 5,\n"
" \"version\" : \"2.04\"\n"
" },\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"info\" : {\n"
" \"delta\" : 26897030,\n"
" \"repository\" : {\n"
" \"delta\" : 3159,\n"
" \"size\" : 3159776\n"
" },\n"
" \"size\" : 26897030\n"
" },\n"
" \"label\" : \"20181116-154756F\",\n"
" \"prior\" : null,\n"
" \"reference\" : null,\n"
" \"timestamp\" : {\n"
" \"start\" : 1542383276,\n"
" \"stop\" : 1542383289\n"
" },\n"
" \"type\" : \"full\"\n"
" }\n"
" ],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [\n"
" {\n"
" \"id\" : 3,\n"
" \"system-id\" : 6569239123849665679,\n"
" \"version\" : \"9.4\"\n"
" },\n"
" {\n"
" \"id\" : 2,\n"
" \"system-id\" : 6569239123849665666,\n"
" \"version\" : \"9.3\"\n"
" },\n"
" {\n"
" \"id\" : 1,\n"
" \"system-id\" : 6569239123849665679,\n"
" \"version\" : \"9.4\"\n"
" }\n"
" ],\n"
" \"name\" : \"stanza1\",\n"
" \"status\" : {\n"
" \"code\" : 0,\n"
" \"message\" : \"ok\"\n"
" }\n"
" }\n"
"]\n", "json - single stanza, valid backup, no priors, no archives in latest DB");
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: stanza1\n"
" status: ok\n"
" cipher: none\n"
"\n"
" db (current)\n"
" wal archive min/max (9.4-3): none present\n"
"\n"
" db (prior)\n"
" wal archive min/max (9.4-1): 000000010000000000000002/000000020000000000000003\n"
"\n"
" full backup: 20181116-154756F\n"
" timestamp start/stop: 2018-11-16 15:47:56 / 2018-11-16 15:48:09\n"
" wal start/stop: n/a\n"
" database size: 25.7MB, backup size: 25.7MB\n"
" repository size: 3MB, repository backup size: 3KB\n"
,"text - single stanza, valid backup, no priors, no archives in latest DB");
// backup.info/archive.info files exist, backups exist, archives exist
//--------------------------------------------------------------------------------------------------------------------------
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"075a202d42c3b6a0257da5f73a68fa77b342f777\"\n"
"backrest-format=5\n"
"backrest-version=\"2.08dev\"\n"
"\n"
"[db]\n"
"db-id=2\n"
"db-system-id=6626363367545678089\n"
"db-version=\"9.5\"\n"
"\n"
"[db:history]\n"
"1={\"db-id\":6625592122879095702,\"db-version\":\"9.4\"}\n"
"2={\"db-id\":6626363367545678089,\"db-version\":\"9.5\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/archive.info", strPtr(archiveStanza1Path))),
bufNewStr(content)), "put archive info to file - stanza1");
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"b50db7cf8f659ac15a0c7a2f45a0813f46a68c6b\"\n"
"backrest-format=5\n"
"backrest-version=\"2.08dev\"\n"
"\n"
"[backup:current]\n"
"20181119-152138F={"
"\"backrest-format\":5,\"backrest-version\":\"2.08dev\","
"\"backup-archive-start\":\"000000010000000000000002\",\"backup-archive-stop\":\"000000010000000000000002\","
"\"backup-info-repo-size\":2369186,\"backup-info-repo-size-delta\":2369186,"
"\"backup-info-size\":20162900,\"backup-info-size-delta\":20162900,"
"\"backup-timestamp-start\":1542640898,\"backup-timestamp-stop\":1542640911,\"backup-type\":\"full\","
"\"db-id\":1,\"option-archive-check\":true,\"option-archive-copy\":false,\"option-backup-standby\":false,"
"\"option-checksum-page\":true,\"option-compress\":true,\"option-hardlink\":false,\"option-online\":true}\n"
"20181119-152138F_20181119-152152D={"
"\"backrest-format\":5,\"backrest-version\":\"2.08dev\",\"backup-archive-start\":\"000000010000000000000003\","
"\"backup-archive-stop\":\"000000010000000000000003\",\"backup-info-repo-size\":2369186,"
"\"backup-info-repo-size-delta\":346,\"backup-info-size\":20162900,\"backup-info-size-delta\":8428,"
"\"backup-prior\":\"20181119-152138F\",\"backup-reference\":[\"20181119-152138F\"],"
"\"backup-timestamp-start\":1542640912,\"backup-timestamp-stop\":1542640915,\"backup-type\":\"diff\","
"\"db-id\":1,\"option-archive-check\":true,\"option-archive-copy\":false,\"option-backup-standby\":false,"
"\"option-checksum-page\":true,\"option-compress\":true,\"option-hardlink\":false,\"option-online\":true}\n"
"20181119-152138F_20181119-152152I={"
"\"backrest-format\":5,\"backrest-version\":\"2.08dev\",\"backup-archive-start\":\"000000010000000000000003\","
"\"backup-info-repo-size\":2369186,"
"\"backup-info-repo-size-delta\":346,\"backup-info-size\":20162900,\"backup-info-size-delta\":8428,"
"\"backup-prior\":\"20181119-152138F_20181119-152152D\","
"\"backup-reference\":[\"20181119-152138F\",\"20181119-152138F_20181119-152152D\"],"
"\"backup-timestamp-start\":1542640912,\"backup-timestamp-stop\":1542640915,\"backup-type\":\"incr\","
"\"db-id\":1,\"option-archive-check\":true,\"option-archive-copy\":false,\"option-backup-standby\":false,"
"\"option-checksum-page\":true,\"option-compress\":true,\"option-hardlink\":false,\"option-online\":true}\n"
"\n"
"[db]\n"
"db-catalog-version=201510051\n"
"db-control-version=942\n"
"db-id=2\n"
"db-system-id=6626363367545678089\n"
"db-version=\"9.5\"\n"
"\n"
"[db:history]\n"
"1={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6625592122879095702,"
"\"db-version\":\"9.4\"}\n"
"2={\"db-catalog-version\":201510051,\"db-control-version\":942,\"db-system-id\":6626363367545678089,"
"\"db-version\":\"9.5\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/backup.info", strPtr(backupStanza1Path))),
bufNewStr(content)), "put backup info to file - stanza1");
String *archiveStanza2Path = strNewFmt("%s/stanza2", strPtr(archivePath));
String *backupStanza2Path = strNewFmt("%s/stanza2", strPtr(backupPath));
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), backupStanza1Path), "backup stanza2 directory");
TEST_RESULT_VOID(storagePathCreateNP(storageLocalWrite(), archiveStanza1Path), "archive stanza2 directory");
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"6779d476833114925a73e058ef9ff04e5a8c7bd2\"\n"
"backrest-format=5\n"
"backrest-version=\"2.08dev\"\n"
"\n"
"[db]\n"
"db-id=1\n"
"db-system-id=6625633699176220261\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-id\":6625633699176220261,\"db-version\":\"9.4\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/archive.info", strPtr(archiveStanza2Path))),
bufNewStr(content)), "put archive info to file - stanza2");
content = strNew
(
"[backrest]\n"
"backrest-checksum=\"2393c52cb48aff2d6c6e87e21a34a3e28200f42e\"\n"
"backrest-format=5\n"
"backrest-version=\"2.08dev\"\n"
"\n"
"[db]\n"
"db-catalog-version=201409291\n"
"db-control-version=942\n"
"db-id=1\n"
"db-system-id=6625633699176220261\n"
"db-version=\"9.4\"\n"
"\n"
"[db:history]\n"
"1={\"db-catalog-version\":201409291,\"db-control-version\":942,\"db-system-id\":6625633699176220261,"
"\"db-version\":\"9.4\"}\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/backup.info", strPtr(backupStanza2Path))),
bufNewStr(content)), "put backup info to file - stanza2");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"archive\" : [\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 2\n"
" },\n"
" \"id\" : \"9.5-2\",\n"
" \"max\" : null,\n"
" \"min\" : null\n"
" },\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"id\" : \"9.4-1\",\n"
" \"max\" : \"000000020000000000000003\",\n"
" \"min\" : \"000000010000000000000002\"\n"
" }\n"
" ],\n"
" \"backup\" : [\n"
" {\n"
" \"archive\" : {\n"
" \"start\" : \"000000010000000000000002\",\n"
" \"stop\" : \"000000010000000000000002\"\n"
" },\n"
" \"backrest\" : {\n"
" \"format\" : 5,\n"
" \"version\" : \"2.08dev\"\n"
" },\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"info\" : {\n"
" \"delta\" : 20162900,\n"
" \"repository\" : {\n"
" \"delta\" : 2369186,\n"
" \"size\" : 2369186\n"
" },\n"
" \"size\" : 20162900\n"
" },\n"
" \"label\" : \"20181119-152138F\",\n"
" \"prior\" : null,\n"
" \"reference\" : null,\n"
" \"timestamp\" : {\n"
" \"start\" : 1542640898,\n"
" \"stop\" : 1542640911\n"
" },\n"
" \"type\" : \"full\"\n"
" },\n"
" {\n"
" \"archive\" : {\n"
" \"start\" : \"000000010000000000000003\",\n"
" \"stop\" : \"000000010000000000000003\"\n"
" },\n"
" \"backrest\" : {\n"
" \"format\" : 5,\n"
" \"version\" : \"2.08dev\"\n"
" },\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"info\" : {\n"
" \"delta\" : 8428,\n"
" \"repository\" : {\n"
" \"delta\" : 346,\n"
" \"size\" : 2369186\n"
" },\n"
" \"size\" : 20162900\n"
" },\n"
" \"label\" : \"20181119-152138F_20181119-152152D\",\n"
" \"prior\" : \"20181119-152138F\",\n"
" \"reference\" : [\n"
" \"20181119-152138F\"\n"
" ],\n"
" \"timestamp\" : {\n"
" \"start\" : 1542640912,\n"
" \"stop\" : 1542640915\n"
" },\n"
" \"type\" : \"diff\"\n"
" },\n"
" {\n"
" \"archive\" : {\n"
" \"start\" : \"000000010000000000000003\",\n"
" \"stop\" : null\n"
" },\n"
" \"backrest\" : {\n"
" \"format\" : 5,\n"
" \"version\" : \"2.08dev\"\n"
" },\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"info\" : {\n"
" \"delta\" : 8428,\n"
" \"repository\" : {\n"
" \"delta\" : 346,\n"
" \"size\" : 2369186\n"
" },\n"
" \"size\" : 20162900\n"
" },\n"
" \"label\" : \"20181119-152138F_20181119-152152I\",\n"
" \"prior\" : \"20181119-152138F_20181119-152152D\",\n"
" \"reference\" : [\n"
" \"20181119-152138F\",\n"
" \"20181119-152138F_20181119-152152D\"\n"
" ],\n"
" \"timestamp\" : {\n"
" \"start\" : 1542640912,\n"
" \"stop\" : 1542640915\n"
" },\n"
" \"type\" : \"incr\"\n"
" }\n"
" ],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [\n"
" {\n"
" \"id\" : 2,\n"
" \"system-id\" : 6626363367545678089,\n"
" \"version\" : \"9.5\"\n"
" },\n"
" {\n"
" \"id\" : 1,\n"
" \"system-id\" : 6625592122879095702,\n"
" \"version\" : \"9.4\"\n"
" }\n"
" ],\n"
" \"name\" : \"stanza1\",\n"
" \"status\" : {\n"
" \"code\" : 0,\n"
" \"message\" : \"ok\"\n"
" }\n"
" },\n"
" {\n"
" \"archive\" : [\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"id\" : \"9.4-1\",\n"
" \"max\" : null,\n"
" \"min\" : null\n"
" }\n"
" ],\n"
" \"backup\" : [],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [\n"
" {\n"
" \"id\" : 1,\n"
" \"system-id\" : 6625633699176220261,\n"
" \"version\" : \"9.4\"\n"
" }\n"
" ],\n"
" \"name\" : \"stanza2\",\n"
" \"status\" : {\n"
" \"code\" : 2,\n"
" \"message\" : \"no valid backups\"\n"
" }\n"
" }\n"
"]\n", "json - multiple stanzas, one with valid backups, archives in latest DB");
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: stanza1\n"
" status: ok\n"
" cipher: none\n"
"\n"
" db (current)\n"
" wal archive min/max (9.5-2): none present\n"
"\n"
" db (prior)\n"
" wal archive min/max (9.4-1): 000000010000000000000002/000000020000000000000003\n"
"\n"
" full backup: 20181119-152138F\n"
" timestamp start/stop: 2018-11-19 15:21:38 / 2018-11-19 15:21:51\n"
" wal start/stop: 000000010000000000000002 / 000000010000000000000002\n"
" database size: 19.2MB, backup size: 19.2MB\n"
" repository size: 2.3MB, repository backup size: 2.3MB\n"
"\n"
" diff backup: 20181119-152138F_20181119-152152D\n"
" timestamp start/stop: 2018-11-19 15:21:52 / 2018-11-19 15:21:55\n"
" wal start/stop: 000000010000000000000003 / 000000010000000000000003\n"
" database size: 19.2MB, backup size: 8.2KB\n"
" repository size: 2.3MB, repository backup size: 346B\n"
" backup reference list: 20181119-152138F\n"
"\n"
" incr backup: 20181119-152138F_20181119-152152I\n"
" timestamp start/stop: 2018-11-19 15:21:52 / 2018-11-19 15:21:55\n"
" wal start/stop: n/a\n"
" database size: 19.2MB, backup size: 8.2KB\n"
" repository size: 2.3MB, repository backup size: 346B\n"
" backup reference list: 20181119-152138F, 20181119-152138F_20181119-152152D\n"
"\n"
"stanza: stanza2\n"
" status: error (no valid backups)\n"
" cipher: none\n"
"\n"
" db (current)\n"
" wal archive min/max (9.4-1): none present\n"
, "text - multiple stanzas, one with valid backups, archives in latest DB");
// Stanza not found
//--------------------------------------------------------------------------------------------------------------------------
StringList *argList2 = strLstDup(argList);
strLstAddZ(argList2, "--stanza=silly");
harnessCfgLoad(strLstSize(argList2), strLstPtr(argList2));
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"backup\" : [],\n"
" \"db\" : [],\n"
" \"name\" : \"silly\",\n"
" \"status\" : {\n"
" \"code\" : 1,\n"
" \"message\" : \"missing stanza path\"\n"
" }\n"
" }\n"
"]\n", "json - missing stanza path");
StringList *argListText2 = strLstDup(argListText);
strLstAddZ(argListText2, "--stanza=silly");
harnessCfgLoad(strLstSize(argListText2), strLstPtr(argListText2));
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: silly\n"
" status: error (missing stanza path)\n"
, "text - missing stanza path");
// Stanza found
//--------------------------------------------------------------------------------------------------------------------------
strLstAddZ(argList, "--stanza=stanza2");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR(strPtr(infoRender()),
"[\n"
" {\n"
" \"archive\" : [\n"
" {\n"
" \"database\" : {\n"
" \"id\" : 1\n"
" },\n"
" \"id\" : \"9.4-1\",\n"
" \"max\" : null,\n"
" \"min\" : null\n"
" }\n"
" ],\n"
" \"backup\" : [],\n"
" \"cipher\" : \"none\",\n"
" \"db\" : [\n"
" {\n"
" \"id\" : 1,\n"
" \"system-id\" : 6625633699176220261,\n"
" \"version\" : \"9.4\"\n"
" }\n"
" ],\n"
" \"name\" : \"stanza2\",\n"
" \"status\" : {\n"
" \"code\" : 2,\n"
" \"message\" : \"no valid backups\"\n"
" }\n"
" }\n"
"]\n"
, "json - multiple stanzas - selected found");
strLstAddZ(argListText, "--stanza=stanza2");
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_RESULT_STR(strPtr(infoRender()),
"stanza: stanza2\n"
" status: error (no valid backups)\n"
" cipher: none\n"
"\n"
" db (current)\n"
" wal archive min/max (9.4-1): none present\n"
,"text - multiple stanzas - selected found");
// Crypto error
//--------------------------------------------------------------------------------------------------------------------------
content = strNew
(
"[global]\n"
"repo-cipher-pass=123abc\n"
);
TEST_RESULT_VOID(
storagePutNP(storageNewWriteNP(storageLocalWrite(), strNewFmt("%s/pgbackrest.conf", testPath())),
bufNewStr(content)), "put pgbackrest.conf file");
strLstAddZ(argListText, "--repo-cipher-type=aes-256-cbc");
strLstAdd(argListText, strNewFmt("--config=%s/pgbackrest.conf", testPath()));
harnessCfgLoad(strLstSize(argListText), strLstPtr(argListText));
TEST_ERROR_FMT(
infoRender(), CryptoError,
"unable to load info file '%s/backup.info' or '%s/backup.info.copy':\n"
"CryptoError: '%s/backup.info' cipher header invalid\n"
"HINT: Is or was the repo encrypted?\n"
"FileMissingError: unable to open '%s/backup.info.copy' for read: [2] No such file or directory\n"
"HINT: backup.info cannot be opened and is required to perform a backup.\n"
"HINT: has a stanza-create been performed?\n"
"HINT: use option --stanza if encryption settings are different for the stanza than the global settings"
,strPtr(backupStanza2Path), strPtr(backupStanza2Path), strPtr(backupStanza2Path), strPtr(backupStanza2Path));
}
//******************************************************************************************************************************
if (testBegin("formatTextDb()"))
{
// These tests cover branches not covered in other tests
KeyValue *stanzaInfo = kvNew();
VariantList *dbSection = varLstNew();
Variant *pgInfo = varNewKv();
kvPut(varKv(pgInfo), varNewStr(DB_KEY_ID_STR), varNewUInt64(1));
kvPut(varKv(pgInfo), varNewStr(DB_KEY_SYSTEM_ID_STR), varNewUInt64(6625633699176220261));
kvPut(varKv(pgInfo), varNewStr(DB_KEY_VERSION_STR), varNewStr(pgVersionToStr(90500)));
varLstAdd(dbSection, pgInfo);
// Add the database history, backup and archive sections to the stanza info
kvPut(stanzaInfo, varNewStr(STANZA_KEY_DB_STR), varNewVarLst(dbSection));
VariantList *backupSection = varLstNew();
Variant *backupInfo = varNewKv();
kvPut(varKv(backupInfo), varNewStr(BACKUP_KEY_LABEL_STR), varNewStr(strNew("20181119-152138F")));
kvPut(varKv(backupInfo), varNewStr(BACKUP_KEY_TYPE_STR), varNewStr(strNew("full")));
kvPutKv(varKv(backupInfo), varNewStr(KEY_ARCHIVE_STR));
KeyValue *infoInfo = kvPutKv(varKv(backupInfo), varNewStr(BACKUP_KEY_INFO_STR));
kvPut(infoInfo, varNewStr(KEY_SIZE_STR), varNewUInt64(0));
kvPut(infoInfo, varNewStr(KEY_DELTA_STR), varNewUInt64(0));
KeyValue *repoInfo = kvPutKv(infoInfo, varNewStr(INFO_KEY_REPOSITORY_STR));
kvAdd(repoInfo, varNewStr(KEY_SIZE_STR), varNewUInt64(0));
kvAdd(repoInfo, varNewStr(KEY_DELTA_STR), varNewUInt64(0));
KeyValue *databaseInfo = kvPutKv(varKv(backupInfo), varNewStr(KEY_DATABASE_STR));
kvAdd(databaseInfo, varNewStr(DB_KEY_ID_STR), varNewUInt64(1));
KeyValue *timeInfo = kvPutKv(varKv(backupInfo), varNewStr(BACKUP_KEY_TIMESTAMP_STR));
kvAdd(timeInfo, varNewStr(KEY_START_STR), varNewUInt64(1542383276));
kvAdd(timeInfo, varNewStr(KEY_STOP_STR), varNewUInt64(1542383289));
varLstAdd(backupSection, backupInfo);
kvPut(stanzaInfo, varNewStr(STANZA_KEY_BACKUP_STR), varNewVarLst(backupSection));
kvPut(stanzaInfo, varNewStr(KEY_ARCHIVE_STR), varNewVarLst(varLstNew()));
String *result = strNew("");
formatTextDb(stanzaInfo, result);
TEST_RESULT_STR(strPtr(result),
"\n"
" db (current)\n"
" full backup: 20181119-152138F\n"
" timestamp start/stop: 2018-11-16 15:47:56 / 2018-11-16 15:48:09\n"
" wal start/stop: n/a\n"
" database size: 0B, backup size: 0B\n"
" repository size: 0B, repository backup size: 0B\n"
,"formatTextDb only backup section (code cverage only)");
}
//******************************************************************************************************************************
if (testBegin("cmdInfo()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAdd(argList, strNewFmt("--repo-path=%s", strPtr(repoPath)));
strLstAddZ(argList, "info");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
storagePathCreateNP(storageLocalWrite(), archivePath);
storagePathCreateNP(storageLocalWrite(), backupPath);
// Redirect stdout to a file
int stdoutSave = dup(STDOUT_FILENO);
String *stdoutFile = strNewFmt("%s/stdout.info", testPath());
if (freopen(strPtr(stdoutFile), "w", stdout) == NULL) // {uncoverable - does not fail}
THROW_SYS_ERROR(FileWriteError, "unable to reopen stdout"); // {uncoverable+}
// Not in a test wrapper to avoid writing to stdout
cmdInfo();
// Restore normal stdout
dup2(stdoutSave, STDOUT_FILENO);
const char *generalHelp = strPtr(strNewFmt("No stanzas exist in %s\n", strPtr(repoPath)));
Storage *storage = storageDriverPosixInterface(
storageDriverPosixNew(strNew(testPath()), STORAGE_MODE_FILE_DEFAULT, STORAGE_MODE_PATH_DEFAULT, false, NULL));
TEST_RESULT_STR(strPtr(strNewBuf(storageGetNP(storageNewReadNP(storage, stdoutFile)))), generalHelp, " check text");
}
FUNCTION_HARNESS_RESULT_VOID();
}