diff --git a/doc/xml/release.xml b/doc/xml/release.xml index fe60a4967..9c0145cd9 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -17,6 +17,17 @@ + + + + + + + + +

Multi-stanza check command.

+
+ diff --git a/doc/xml/user-guide.xml b/doc/xml/user-guide.xml index 1440271de..361807a4c 100644 --- a/doc/xml/user-guide.xml +++ b/doc/xml/user-guide.xml @@ -220,6 +220,12 @@ {[host-image]} {[host-mount]} + pgalt + pg-alt + {[host-pg1-user]} + {[host-image]} + {[host-mount]} + repo1 repository {[host-user]} @@ -2720,7 +2726,7 @@ /etc/pgbackrest/cert/client.crt /etc/pgbackrest/cert/client.key - pgbackrest-client=demo + pgbackrest-client=* * /etc/pgbackrest/cert/ca.crt /etc/pgbackrest/cert/server.crt @@ -3431,6 +3437,171 @@ + +
+ Multiple Stanzas + +

supports multiple stanzas. The most common usage is sharing a {[host-repo1]} host among multiple stanzas.

+ + +
+ Installation + +

A new host named {[host-pgalt]} is created to run the new primary.

+ + + + + {[host-pgalt]} + postgres + postgres + +
+ + +
+ Setup Passwordless SSH + + + + + + + + {[host-pgalt]} + postgres + {[pg-home-path]} + +
+ + +
+ Configuration + +

configuration is nearly identical to {[host-pg1]} except that the demo-alt stanza will be used so backups and archive will be stored in a separate location.

+ + + Configure <backrest/> on the new primary + + {[pg-path]} + + {[host-repo1]} + + tls + /etc/pgbackrest/cert/ca.crt + /etc/pgbackrest/cert/client.crt + /etc/pgbackrest/cert/client.key + + pgbackrest-client=demo-alt + * + /etc/pgbackrest/cert/ca.crt + /etc/pgbackrest/cert/server.crt + /etc/pgbackrest/cert/server.key + + detail + + off + n + + + + Configure <br-option>pg1-host</br-option>/<br-option>pg1-host-user</br-option> and <br-option>pg1-path</br-option> + + {[pg-path]} + {[host-pgalt]} + + tls + /etc/pgbackrest/cert/ca.crt + /etc/pgbackrest/cert/client.crt + /etc/pgbackrest/cert/client.key + + + + {[host-pgalt]} + postgres + postgres + +
+ + +
+ Setup Demo Cluster + + + Create the demo cluster + + + + {[pg-bin-path]}/initdb + -D {[pg-path]} -k -A peer + + + + + {[pg-cluster-create]} + + + + cat /root/postgresql.common.conf >> {[postgres-config-demo]} + + + + + Configure <postgres/> settings + + '{[project-exe]} {[dash]}-stanza=demo-alt archive-push %p' + on + {[wal-level]} + 3 + 'postgresql.log' + + + + Start the {[postgres-cluster-demo]} cluster + + + {[pg-cluster-restart]} + + + + {[pg-cluster-wait]} + + +
+ + +
+ Create the Stanza and Check Configuration + +

The stanza-create command must be run to initialize the stanza. It is recommended that the check command be run after stanza-create to ensure archiving and backups are properly configured.

+ + + Create the stanza and check the configuration + + + {[project-exe]} {[dash]}-stanza=demo-alt {[dash]}-log-level-console=info stanza-create + completed successfully + + + + {[project-exe]} {[dash]}-log-level-console=info check + check stanza | successfully archived to + + + +

If the check command is run from the {[host-repo1]} host then all stanzas will be checked.

+ + + Check the configuration for all stanzas + + + {[project-exe]} {[dash]}-log-level-console=info check + check stanza | successfully archived to + + +
+
+
Asynchronous Archiving diff --git a/src/build/config/config.yaml b/src/build/config/config.yaml index 1d65e1cfc..5991c961a 100644 --- a/src/build/config/config.yaml +++ b/src/build/config/config.yaml @@ -368,7 +368,8 @@ option: archive-get: {} archive-push: {} backup: {} - check: {} + check: + required: false expire: {} info: required: false @@ -1544,7 +1545,9 @@ option: archive-push: required: false backup: {} - check: {} + check: + depend: + option: stanza manifest: required: false restore: {} diff --git a/src/command/check/check.c b/src/command/check/check.c index d2e98f372..3ad9010d4 100644 --- a/src/command/check/check.c +++ b/src/command/check/check.c @@ -10,6 +10,8 @@ Check Command #include "common/log.h" #include "common/memContext.h" #include "config/config.h" +#include "config/load.h" +#include "config/parse.h" #include "db/helper.h" #include "info/infoArchive.h" #include "postgres/interface.h" @@ -176,15 +178,54 @@ cmdCheck(void) MEM_CONTEXT_TEMP_BEGIN() { - // Get the primary/standby connections (standby is only required if backup from standby is enabled) - DbGetResult dbGroup = dbGet(false, false, false); + // Build stanza list based on whether a stanza was specified or not + StringList *stanzaList; + bool stanzaSpecified = cfgOptionTest(cfgOptStanza); - if (dbGroup.standby == NULL && dbGroup.primary == NULL) - THROW(ConfigError, "no database found\nHINT: check indexed pg-path/pg-host configurations"); + if (stanzaSpecified) + { + stanzaList = strLstNew(); + strLstAdd(stanzaList, cfgOptionStr(cfgOptStanza)); + } + else + { + stanzaList = cfgParseStanzaList(); - const unsigned int pgPathDefinedTotal = checkManifest(); - checkStandby(dbGroup, pgPathDefinedTotal); - checkPrimary(dbGroup); + if (strLstSize(stanzaList) == 0) + { + LOG_WARN( + "no stanzas found to check\n" + "HINT: are there non-empty stanza sections in the configuration?"); + } + } + + // Iterate stanzas + for (unsigned int stanzaIdx = 0; stanzaIdx < strLstSize(stanzaList); stanzaIdx++) + { + // Switch stanza if required + if (!stanzaSpecified) + { + const String *const stanza = strLstGet(stanzaList, stanzaIdx); + LOG_INFO_FMT("check stanza '%s'", strZ(stanza)); + + // Free storage and protocol cache + storageHelperFree(); + protocolFree(); + + // Reload config with new stanza + cfgLoadStanza(stanza); + } + + // Get the primary/standby connections (standby is only required if backup from standby is enabled) + DbGetResult dbGroup = dbGet(false, false, false); + + if (dbGroup.standby == NULL && dbGroup.primary == NULL) + THROW(ConfigError, "no database found\nHINT: check indexed pg-path/pg-host configurations"); + + const unsigned int pgPathDefinedTotal = checkManifest(); + checkStandby(dbGroup, pgPathDefinedTotal); + checkPrimary(dbGroup); + } } MEM_CONTEXT_TEMP_END(); diff --git a/src/common/ini.c b/src/common/ini.c index 117a498a4..ac6fd538d 100644 --- a/src/common/ini.c +++ b/src/common/ini.c @@ -297,6 +297,20 @@ iniSectionKeyIsList(const Ini *const this, const String *const section, const St FUNCTION_TEST_RETURN(BOOL, varType(result) == varTypeVariantList); } +/**********************************************************************************************************************************/ +FN_EXTERN StringList * +iniSectionList(const Ini *const this) +{ + FUNCTION_TEST_BEGIN(); + FUNCTION_TEST_PARAM(INI, this); + FUNCTION_TEST_END(); + + ASSERT(this != NULL); + ASSERT(this->store != NULL); + + FUNCTION_TEST_RETURN(STRING_LIST, strLstNewVarLst(kvKeyList(this->store))); +} + /**********************************************************************************************************************************/ FN_EXTERN StringList * iniSectionKeyList(const Ini *const this, const String *const section) diff --git a/src/common/ini.h b/src/common/ini.h index 76fbf1286..854638b68 100644 --- a/src/common/ini.h +++ b/src/common/ini.h @@ -68,6 +68,9 @@ FN_EXTERN StringList *iniGetList(const Ini *this, const String *section, const S // The key's value is a list FN_EXTERN bool iniSectionKeyIsList(const Ini *this, const String *section, const String *key); +// List of sections +FN_EXTERN StringList *iniSectionList(const Ini *const this); + // List of keys for a section FN_EXTERN StringList *iniSectionKeyList(const Ini *this, const String *section); diff --git a/src/config/load.c b/src/config/load.c index d11dbd868..bccc1b174 100644 --- a/src/config/load.c +++ b/src/config/load.c @@ -25,6 +25,15 @@ Configuration Load #include "storage/posix/storage.h" #include "storage/sftp/storage.h" +/*********************************************************************************************************************************** +Local variables +***********************************************************************************************************************************/ +static struct ConfigLoadLocal +{ + unsigned int argListSize; // Argument list size + const char **argList; // Argument list +} configLoadLocal; + /*********************************************************************************************************************************** Load log settings ***********************************************************************************************************************************/ @@ -437,17 +446,24 @@ cfgLoadLogFile(void) /**********************************************************************************************************************************/ FN_EXTERN void -cfgLoad(unsigned int argListSize, const char *argList[]) +cfgLoad(const unsigned int argListSize, const char *argList[]) { FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_PARAM(UINT, argListSize); FUNCTION_LOG_PARAM(CHARPY, argList); FUNCTION_LOG_END(); + ASSERT(argListSize > 0); + ASSERT(argList != NULL); + + // Store arguments + configLoadLocal.argListSize = argListSize; + configLoadLocal.argList = argList; + MEM_CONTEXT_TEMP_BEGIN() { // Parse config from command line and config file - cfgParseP(storageLocal(), argListSize, argList); + cfgParseP(storageLocal(), configLoadLocal.argListSize, configLoadLocal.argList); // Initialize dry-run mode for storage when valid for the current command storageHelperDryRunInit(cfgOptionValid(cfgOptDryRun) && cfgOptionBool(cfgOptDryRun)); @@ -517,3 +533,41 @@ cfgLoad(unsigned int argListSize, const char *argList[]) FUNCTION_LOG_RETURN_VOID(); } + +/**********************************************************************************************************************************/ +FN_EXTERN void +cfgLoadStanza(const String *const stanza) +{ + FUNCTION_LOG_BEGIN(logLevelDebug); + FUNCTION_LOG_PARAM(STRING, stanza); + FUNCTION_LOG_END(); + + ASSERT(stanza != NULL); + ASSERT(configLoadLocal.argListSize > 0); + + MEM_CONTEXT_TEMP_BEGIN() + { + // Store the exec id so it can be preserved after reload + const Variant *const execId = varNewStr(cfgOptionStr(cfgOptExecId)); + + // Make a copy of the arguments and add the stanza (this assumes the stanza was not originally specified) + StringList *const argListNew = strLstNew(); + + for (unsigned int argListIdx = 0; argListIdx < configLoadLocal.argListSize; argListIdx++) + strLstAddZ(argListNew, configLoadLocal.argList[argListIdx]); + + strLstAddFmt(argListNew, "--" CFGOPT_STANZA "=%s", strZ(stanza)); + + // Parse config from command line and config file + cfgParseP(storageLocal(), strLstSize(argListNew), strLstPtr(argListNew), .noConfigLoad = true, .noResetLogLevel = true); + + // Update options that have complex rules + cfgLoadUpdateOption(); + + // Set execId to prior value + cfgOptionSet(cfgOptExecId, cfgSourceParam, execId); + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} diff --git a/src/config/load.h b/src/config/load.h index 066e39aff..d7085dce8 100644 --- a/src/config/load.h +++ b/src/config/load.h @@ -14,6 +14,9 @@ Functions // Load the configuration FN_EXTERN void cfgLoad(unsigned int argListSize, const char *argList[]); +// Load the configuration using the specified stanza +FN_EXTERN void cfgLoadStanza(const String *stanza); + // Generate log file path and name. Only the command role is configurable here because log settings may vary between commands. FN_EXTERN String *cfgLoadLogFileName(ConfigCommandRole commandRole); diff --git a/src/config/parse.auto.c.inc b/src/config/parse.auto.c.inc index 236b9743f..5b6be7f61 100644 --- a/src/config/parse.auto.c.inc +++ b/src/config/parse.auto.c.inc @@ -4020,6 +4020,19 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] = ), // opt/pg-path // opt/pg-path PARSE_RULE_OPTIONAL_NOT_REQUIRED(), // opt/pg-path + ), // opt/pg-path + // opt/pg-path + PARSE_RULE_OPTIONAL_GROUP // opt/pg-path + ( // opt/pg-path + PARSE_RULE_FILTER_CMD // opt/pg-path + ( // opt/pg-path + PARSE_RULE_VAL_CMD(cfgCmdCheck), // opt/pg-path + ), // opt/pg-path + // opt/pg-path + PARSE_RULE_OPTIONAL_DEPEND // opt/pg-path + ( // opt/pg-path + PARSE_RULE_VAL_OPT(cfgOptStanza), // opt/pg-path + ), // opt/pg-path ), // opt/pg-path ), // opt/pg-path ), // opt/pg-path @@ -9556,6 +9569,7 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] = ( // opt/stanza PARSE_RULE_FILTER_CMD // opt/stanza ( // opt/stanza + PARSE_RULE_VAL_CMD(cfgCmdCheck), // opt/stanza PARSE_RULE_VAL_CMD(cfgCmdInfo), // opt/stanza PARSE_RULE_VAL_CMD(cfgCmdRepoCreate), // opt/stanza PARSE_RULE_VAL_CMD(cfgCmdRepoGet), // opt/stanza diff --git a/src/config/parse.c b/src/config/parse.c index 53e6748e1..825bae84c 100644 --- a/src/config/parse.c +++ b/src/config/parse.c @@ -56,6 +56,15 @@ Prefix for environment variables // In some environments this will not be externed extern char **environ; +/*********************************************************************************************************************************** +Mem context and local variables +***********************************************************************************************************************************/ +static struct ConfigParseLocal +{ + MemContext *memContext; // Mem context + Ini *ini; // Parsed ini data +} configParseLocal; + /*********************************************************************************************************************************** Define how a command is parsed ***********************************************************************************************************************************/ @@ -481,6 +490,43 @@ cfgParseCommandRoleName(const ConfigCommand commandId, const ConfigCommandRole c FUNCTION_TEST_RETURN(STRING, result); } +/**********************************************************************************************************************************/ +FN_EXTERN StringList * +cfgParseStanzaList(void) +{ + FUNCTION_TEST_VOID(); + + StringList *const result = strLstNew(); + + if (configParseLocal.ini != NULL) + { + MEM_CONTEXT_TEMP_BEGIN() + { + const StringList *const sectionList = strLstSort(iniSectionList(configParseLocal.ini), sortOrderAsc); + + for (unsigned int sectionIdx = 0; sectionIdx < strLstSize(sectionList); sectionIdx++) + { + const String *const section = strLstGet(sectionList, sectionIdx); + + // Skip global sections + if (strEqZ(section, CFGDEF_SECTION_GLOBAL) || strBeginsWithZ(section, CFGDEF_SECTION_GLOBAL ":")) + continue; + + // Extract stanza + const StringList *const sectionPart = strLstNewSplitZ(section, ":"); + ASSERT(strLstSize(sectionPart) <= 2); + + strLstAddIfMissing(result, strLstGet(sectionPart, 0)); + } + } + MEM_CONTEXT_TEMP_END(); + } + + strLstSort(result, sortOrderAsc); + + FUNCTION_TEST_RETURN(STRING_LIST, result); +} + /*********************************************************************************************************************************** Find an option by name in the option list ***********************************************************************************************************************************/ @@ -1046,7 +1092,8 @@ cfgParseOptionalFilterDepend(PackRead *const filter, const Config *const config, // Get the depend option value const ConfigOption dependId = (ConfigOption)pckReadU32P(filter); ASSERT(config->option[dependId].index != NULL); - const ConfigOptionValue *const dependValue = &config->option[dependId].index[optionListIdx]; + const ConfigOptionValue *const dependValue = + &config->option[dependId].index[parseRuleOption[dependId].group ? optionListIdx : 0]; // Is the dependency resolved? if (dependValue->set) @@ -1468,8 +1515,23 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha FUNCTION_LOG_PARAM(UINT, argListSize); FUNCTION_LOG_PARAM(CHARPY, argList); FUNCTION_LOG_PARAM(BOOL, param.noResetLogLevel); + FUNCTION_LOG_PARAM(BOOL, param.noConfigLoad); FUNCTION_LOG_END(); + // Initialize local mem context + if (configParseLocal.memContext == NULL) + { + MEM_CONTEXT_BEGIN(memContextTop()) + { + MEM_CONTEXT_NEW_BEGIN(ConfigParse, .childQty = MEM_CONTEXT_QTY_MAX) + { + configParseLocal.memContext = MEM_CONTEXT_NEW(); + } + MEM_CONTEXT_NEW_END(); + } + MEM_CONTEXT_END(); + } + MEM_CONTEXT_TEMP_BEGIN() { // Create the config struct @@ -1821,16 +1883,30 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha // Phase 3: parse config file unless --no-config passed // --------------------------------------------------------------------------------------------------------------------- // Load the configuration file(s) - String *configString = cfgFileLoad( - storage, parseOptionList, - (const String *)&parseRuleValueStr[parseRuleValStrCFGOPTDEF_CONFIG_PATH_SP_QT_FS_QT_SP_PROJECT_CONFIG_FILE], - (const String *)&parseRuleValueStr[parseRuleValStrCFGOPTDEF_CONFIG_PATH_SP_QT_FS_QT_SP_PROJECT_CONFIG_INCLUDE_PATH], - PGBACKREST_CONFIG_ORIG_PATH_FILE_STR); - - if (configString != NULL) + if (!param.noConfigLoad) { - const Ini *const ini = iniNewP(ioBufferReadNew(BUFSTR(configString)), .store = true); + const String *const configString = cfgFileLoad( + storage, parseOptionList, + (const String *)&parseRuleValueStr[parseRuleValStrCFGOPTDEF_CONFIG_PATH_SP_QT_FS_QT_SP_PROJECT_CONFIG_FILE], + (const String *)&parseRuleValueStr[ + parseRuleValStrCFGOPTDEF_CONFIG_PATH_SP_QT_FS_QT_SP_PROJECT_CONFIG_INCLUDE_PATH], + PGBACKREST_CONFIG_ORIG_PATH_FILE_STR); + iniFree(configParseLocal.ini); + configParseLocal.ini = NULL; + + if (configString != NULL) + { + MEM_CONTEXT_BEGIN(configParseLocal.memContext) + { + configParseLocal.ini = iniNewP(ioBufferReadNew(BUFSTR(configString)), .store = true); + } + MEM_CONTEXT_END(); + } + } + + if (configParseLocal.ini != NULL) + { // Get the stanza name String *stanza = NULL; @@ -1853,7 +1929,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha for (unsigned int sectionIdx = 0; sectionIdx < strLstSize(sectionList); sectionIdx++) { String *section = strLstGet(sectionList, sectionIdx); - StringList *keyList = iniSectionKeyList(ini, section); + const StringList *const keyList = iniSectionKeyList(configParseLocal.ini, section); KeyValue *optionFound = kvNew(); // Loop through keys to search for options @@ -1938,7 +2014,7 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha optionValue->source = cfgSourceConfig; // Process list - if (iniSectionKeyIsList(ini, section, key)) + if (iniSectionKeyIsList(configParseLocal.ini, section, key)) { // Error if the option cannot be specified multiple times if (!parseRuleOption[option.id].multi) @@ -1948,12 +2024,12 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha cfgParseOptionKeyIdxName(option.id, option.keyIdx)); } - optionValue->valueList = iniGetList(ini, section, key); + optionValue->valueList = iniGetList(configParseLocal.ini, section, key); } else { // Get the option value - const String *value = iniGet(ini, section, key); + const String *value = iniGet(configParseLocal.ini, section, key); if (strSize(value) == 0) { @@ -2164,7 +2240,8 @@ cfgParse(const Storage *const storage, const unsigned int argListSize, const cha // Get depend option id and name ConfigOption dependId = pckReadU32P(filter); - const String *dependOptionName = STR(cfgParseOptionKeyIdxName(dependId, optionKeyIdx)); + const String *dependOptionName = STR( + cfgParseOptionKeyIdxName(dependId, parseRuleOption[dependId].group ? optionKeyIdx : 0)); // If depend value is not set ASSERT(config->option[dependId].index != NULL); diff --git a/src/config/parse.h b/src/config/parse.h index 1d6a9346d..b296e8bd2 100644 --- a/src/config/parse.h +++ b/src/config/parse.h @@ -44,6 +44,8 @@ typedef struct CfgParseParam { VAR_PARAM_HEADER; bool noResetLogLevel; // Do not reset log level + bool noConfigLoad; // Do not reload the config file + const String *stanza; // Load config as stanza } CfgParseParam; #define cfgParseP(storage, argListSize, argList, ...) \ @@ -105,6 +107,8 @@ FN_EXTERN ConfigOptionDataType cfgParseOptionDataType(ConfigOption optionId); // Is the option required? FN_EXTERN bool cfgParseOptionRequired(ConfigCommand commandId, ConfigOption optionId); +FN_EXTERN StringList *cfgParseStanzaList(void); + // Is the option valid for the command? FN_EXTERN bool cfgParseOptionValid(ConfigCommand commandId, ConfigCommandRole commandRoleId, ConfigOption optionId); diff --git a/src/storage/helper.c b/src/storage/helper.c index 49f3c8146..99d49be74 100644 --- a/src/storage/helper.c +++ b/src/storage/helper.c @@ -568,3 +568,17 @@ storageSpoolWrite(void) FUNCTION_TEST_RETURN_CONST(STORAGE, storageHelper.storageSpoolWrite); } + +/**********************************************************************************************************************************/ +FN_EXTERN void +storageHelperFree(void) +{ + FUNCTION_TEST_VOID(); + + if (storageHelper.memContext != NULL) + memContextFree(storageHelper.memContext); + + storageHelper = (struct StorageHelperLocal){.memContext = NULL, .helperList = storageHelper.helperList}; + + FUNCTION_TEST_RETURN_VOID(); +} diff --git a/src/storage/helper.h b/src/storage/helper.h index 6fc10d426..032a751c9 100644 --- a/src/storage/helper.h +++ b/src/storage/helper.h @@ -62,4 +62,7 @@ FN_EXTERN const Storage *storageRepoWrite(void); FN_EXTERN const Storage *storageSpool(void); FN_EXTERN const Storage *storageSpoolWrite(void); +// Free cached storage objects +FN_EXTERN void storageHelperFree(void); + #endif diff --git a/test/code-count/file-type.yaml b/test/code-count/file-type.yaml index c3a092db8..8648317c8 100644 --- a/test/code-count/file-type.yaml +++ b/test/code-count/file-type.yaml @@ -2379,10 +2379,6 @@ test/src/common/harnessStorageHelper.c: class: test/harness type: c -test/src/common/harnessStorageHelper.h: - class: test/harness - type: c/h - test/src/common/harnessTest.c: class: test/harness type: c diff --git a/test/define.yaml b/test/define.yaml index 675295605..ae4ebf5b3 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -420,11 +420,10 @@ unit: # ---------------------------------------------------------------------------------------------------------------------------- - name: lock total: 3 - harness: config harness: - name: storageHelper + name: config shim: - storage/helper: ~ + config/load: ~ harness: name: lock shim: @@ -832,7 +831,6 @@ unit: # ---------------------------------------------------------------------------------------------------------------------------- - name: check total: 3 - containerReq: true coverage: - command/check/common diff --git a/test/lib/pgBackRestTest/Env/Host/HostBackupTest.pm b/test/lib/pgBackRestTest/Env/Host/HostBackupTest.pm index 3bca14a48..f20af1789 100644 --- a/test/lib/pgBackRestTest/Env/Host/HostBackupTest.pm +++ b/test/lib/pgBackRestTest/Env/Host/HostBackupTest.pm @@ -775,7 +775,7 @@ sub check ' --config=' . $self->backrestConfig() . (defined($$oParam{iTimeout}) ? " --archive-timeout=$$oParam{iTimeout}" : '') . (defined($$oParam{strOptionalParam}) ? " $$oParam{strOptionalParam}" : '') . - ' --stanza=' . $self->stanza() . ' check', + (!defined($oParam->{bStanza}) || $oParam->{bStanza} ? ' --stanza=' . $self->stanza() : '') . ' check', {strComment => $strComment, iExpectedExitStatus => $$oParam{iExpectedExitStatus}, bLogOutput => $self->synthetic()}); # Return from function and log return values if any diff --git a/test/lib/pgBackRestTest/Module/Real/RealAllTest.pm b/test/lib/pgBackRestTest/Module/Real/RealAllTest.pm index 9f4eeca7d..551158ba4 100644 --- a/test/lib/pgBackRestTest/Module/Real/RealAllTest.pm +++ b/test/lib/pgBackRestTest/Module/Real/RealAllTest.pm @@ -155,7 +155,7 @@ sub run # -------------------------------------------------------------------------------------------------------------------------- my $strComment = 'verify check command runs successfully'; - $oHostDbPrimary->check($strComment, {iTimeout => 10}); + $oHostDbPrimary->check($strComment, {iTimeout => 10, bStanza => false}); # Also run check on the backup host when present if ($bHostBackup) diff --git a/test/src/common/harnessConfig.c b/test/src/common/harnessConfig.c index 0d29e5dbb..1031eccdd 100644 --- a/test/src/common/harnessConfig.c +++ b/test/src/common/harnessConfig.c @@ -19,9 +19,13 @@ Harness for Loading Test Configurations #include "common/harnessDebug.h" #include "common/harnessLock.h" #include "common/harnessLog.h" -#include "common/harnessStorageHelper.h" #include "common/harnessTest.h" +/*********************************************************************************************************************************** +Include shimmed C modules +***********************************************************************************************************************************/ +{[SHIM_MODULE]} + /**********************************************************************************************************************************/ void hrnCfgLoad(ConfigCommand commandId, const StringList *argListParam, const HrnCfgLoadParam param) @@ -79,7 +83,11 @@ hrnCfgLoad(ConfigCommand commandId, const StringList *argListParam, const HrnCfg } // Free objects in storage helper - hrnStorageHelperFree(); + storageHelperFree(); + + // Store config so it can be reloaded with a stanza + configLoadLocal.argListSize = strLstSize(argList); + configLoadLocal.argList = strLstPtr(argList); // Parse config cfgParseP(storageLocal(), strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true); diff --git a/test/src/common/harnessStorageHelper.c b/test/src/common/harnessStorageHelper.c deleted file mode 100644 index 42864b54d..000000000 --- a/test/src/common/harnessStorageHelper.c +++ /dev/null @@ -1,25 +0,0 @@ -/*********************************************************************************************************************************** -Storage Helper Test Harness -***********************************************************************************************************************************/ -#include "build.auto.h" - -#include "common/harnessStorageHelper.h" - -/*********************************************************************************************************************************** -Include shimmed C modules -***********************************************************************************************************************************/ -{[SHIM_MODULE]} - -/**********************************************************************************************************************************/ -void -hrnStorageHelperFree(void) -{ - FUNCTION_TEST_VOID(); - - if (storageHelper.memContext != NULL) - memContextFree(storageHelper.memContext); - - storageHelper = (struct StorageHelperLocal){.memContext = NULL, .helperList = storageHelper.helperList}; - - FUNCTION_TEST_RETURN_VOID(); -} diff --git a/test/src/common/harnessStorageHelper.h b/test/src/common/harnessStorageHelper.h deleted file mode 100644 index 0d3249800..000000000 --- a/test/src/common/harnessStorageHelper.h +++ /dev/null @@ -1,17 +0,0 @@ -/*********************************************************************************************************************************** -Storage Helper Test Harness - -Helper functions for testing the storage helper. -***********************************************************************************************************************************/ -#ifndef TEST_COMMON_HARNESS_STORAGE_HELPER_H -#define TEST_COMMON_HARNESS_STORAGE_HELPER_H - -#include "storage/helper.h" - -/*********************************************************************************************************************************** -Functions -***********************************************************************************************************************************/ -// Free all storage helper objects. This should be done on any config load to ensure that stanza changes are honored. -void hrnStorageHelperFree(void); - -#endif diff --git a/test/src/module/command/checkTest.c b/test/src/module/command/checkTest.c index 8cdaf92ac..9846bb33e 100644 --- a/test/src/module/command/checkTest.c +++ b/test/src/module/command/checkTest.c @@ -6,11 +6,13 @@ Test Check Command #include "info/infoBackup.h" #include "postgres/version.h" #include "storage/helper.h" +#include "storage/posix/storage.h" #include "common/harnessConfig.h" #include "common/harnessInfo.h" #include "common/harnessPostgres.h" #include "common/harnessPq.h" +#include "common/harnessStorage.h" /*********************************************************************************************************************************** Test Run @@ -20,6 +22,9 @@ testRun(void) { FUNCTION_HARNESS_VOID(); + // Create default storage object for testing + const Storage *const storageTest = storagePosixNewP(TEST_PATH_STR, .write = true); + // PQfinish() is strictly checked harnessPqScriptStrictSet(true); @@ -272,9 +277,13 @@ testRun(void) // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("multi-repo - primary database only, WAL not found"); + HRN_STORAGE_PUT_Z( + storageTest, "pgbackrest.conf", + "[test1]\n" + "pg1-path=" TEST_PATH "/pg\n"); + argList = strLstNew(); - hrnCfgArgRawZ(argList, cfgOptStanza, "test1"); - hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/pg"); + hrnCfgArgRawZ(argList, cfgOptConfig, TEST_PATH "/pgbackrest.conf"); hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH "/repo"); hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2"); hrnCfgArgRawZ(argList, cfgOptArchiveTimeout, ".5"); @@ -282,7 +291,7 @@ testRun(void) // Create stanza files on repo2 HRN_INFO_PUT( - storageRepoIdxWrite(1), INFO_ARCHIVE_PATH_FILE, + storageTest, "repo2/archive/test1/" INFO_ARCHIVE_FILE, "[db]\n" "db-id=1\n" "db-system-id=" HRN_PG_SYSTEMID_15_Z "\n" @@ -291,7 +300,7 @@ testRun(void) "[db:history]\n" "1={\"db-id\":" HRN_PG_SYSTEMID_15_Z ",\"db-version\":\"15\"}\n"); HRN_INFO_PUT( - storageRepoIdxWrite(1), INFO_BACKUP_PATH_FILE, + storageTest, "repo2/backup/test1/" INFO_BACKUP_FILE, "[db]\n" "db-catalog-version=202209061\n" "db-control-version=1300\n" @@ -320,13 +329,35 @@ testRun(void) "HINT: check the PostgreSQL server log for errors.\n" "HINT: run the 'start' command if the stanza was previously stopped."); TEST_RESULT_LOG( + "P00 INFO: check stanza 'test1'\n" "P00 INFO: check repo1 configuration (primary)\n" "P00 INFO: check repo2 configuration (primary)\n" "P00 INFO: check repo1 archive for WAL (primary)"); + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("no stanzas in config file"); + + HRN_STORAGE_PUT_Z( + storageTest, "pgbackrest.conf", + "[test1]\n"); + HRN_CFG_LOAD(cfgCmdCheck, argList); + + TEST_RESULT_VOID(cmdCheck(), "check"); + TEST_RESULT_LOG( + "P00 WARN: no stanzas found to check\n" + " HINT: are there non-empty stanza sections in the configuration?"); + // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("multi-repo - WAL segment switch performed once for all repos"); + argList = strLstNew(); + hrnCfgArgRawZ(argList, cfgOptStanza, "test1"); + hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/pg"); + hrnCfgArgRawZ(argList, cfgOptRepoPath, TEST_PATH "/repo"); + hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2"); + hrnCfgArgRawZ(argList, cfgOptArchiveTimeout, ".5"); + HRN_CFG_LOAD(cfgCmdCheck, argList); + // Create WAL segment Buffer *buffer = bufNew(16 * 1024 * 1024); memset(bufPtr(buffer), 0, bufSize(buffer)); diff --git a/test/src/module/common/iniTest.c b/test/src/module/common/iniTest.c index a3312e601..2c8255f37 100644 --- a/test/src/module/common/iniTest.c +++ b/test/src/module/common/iniTest.c @@ -146,6 +146,7 @@ testRun(void) TEST_RESULT_STRLST_Z(iniGetList(ini, STRDEF("global"), STRDEF("repeat")), "1\n2\n", "key list"); TEST_RESULT_PTR(iniGetList(ini, STRDEF("globalx"), STRDEF("repeat2")), NULL, "null key list"); + TEST_RESULT_STRLST_Z(iniSectionList(ini), "global\ndb\n", "sections"); TEST_RESULT_STRLST_Z(iniSectionKeyList(ini, STRDEF("global")), "compress\nrepeat\n", "section keys"); TEST_RESULT_STRLST_Z(iniSectionKeyList(ini, STRDEF("bogus")), NULL, "empty section keys"); diff --git a/test/src/module/config/loadTest.c b/test/src/module/config/loadTest.c index ef2c0de15..5b54b28d3 100644 --- a/test/src/module/config/loadTest.c +++ b/test/src/module/config/loadTest.c @@ -706,6 +706,12 @@ testRun(void) TEST_RESULT_BOOL(socketLocal.keepAlive, false, "check socketLocal.keepAlive"); TEST_RESULT_UINT(ioTimeoutMs(), 60000, "check io timeout"); + String *execId = strDup(cfgOptionStr(cfgOptExecId)); + + TEST_RESULT_VOID(cfgLoadStanza(STRDEF("test1")), "load config with stanza"); + TEST_RESULT_STR_Z(cfgOptionStr(cfgOptStanza), "test1", "check stanza"); + TEST_RESULT_STR(cfgOptionStr(cfgOptExecId), execId, "check execId"); + // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("umask is reset, neutral-umask=y"); diff --git a/test/src/module/config/parseTest.c b/test/src/module/config/parseTest.c index 9a8809d4f..3469f2632 100644 --- a/test/src/module/config/parseTest.c +++ b/test/src/module/config/parseTest.c @@ -100,6 +100,11 @@ testRun(void) const String *configIncludePath = STRDEF(TEST_PATH "/conf.d"); HRN_SYSTEM_FMT("mkdir -m 750 %s", strZ(configIncludePath)); + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("no stanzas loaded yet"); + + TEST_RESULT_STRLST_Z(cfgParseStanzaList(), NULL, "stanza list"); + // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("check old config file constants"); @@ -621,8 +626,8 @@ testRun(void) strLstAddZ(argList, TEST_BACKREST_EXE); strLstAddZ(argList, "-bogus"); TEST_ERROR( - cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true), OptionInvalidError, - "option '-bogus' must begin with --"); + cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true), + OptionInvalidError, "option '-bogus' must begin with --"); // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("error when option argument is invalid"); @@ -1261,6 +1266,17 @@ testRun(void) cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true), OptionInvalidError, "option 'target-exclusive' not valid without option 'type' in ('lsn', 'time', 'xid')"); + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("dependent option missing (indexed option depends on non-indexed option"); + + argList = strLstNew(); + strLstAddZ(argList, TEST_BACKREST_EXE); + hrnCfgArgRawZ(argList, cfgOptPgPath, "/path/to/db"); + strLstAddZ(argList, CFGCMD_CHECK); + TEST_ERROR( + cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true), OptionInvalidError, + "option 'pg1-path' not valid without option 'stanza'"); + // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("option invalid for command"); @@ -1453,6 +1469,9 @@ testRun(void) TEST_ERROR( cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true), OptionInvalidError, "configuration file contains duplicate options ('db-path', 'pg1-path') in section '[db]'"); + TEST_ERROR( + cfgParseP(storageTest, strLstSize(argList), strLstPtr(argList), .noResetLogLevel = true, .noConfigLoad = true), + OptionInvalidError, "configuration file contains duplicate options ('db-path', 'pg1-path') in section '[db]'"); // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("config file - option set multiple times"); @@ -1711,6 +1730,7 @@ testRun(void) "P00 WARN: configuration file contains invalid option 'backup-standb'\n" "P00 WARN: configuration file contains stanza-only option 'pg1-path' in global section 'global'"); + TEST_RESULT_STRLST_Z(cfgParseStanzaList(), "db\n", "stanza list"); TEST_RESULT_STR_Z(jsonFromVar(varNewVarLst(cfgCommandJobRetry())), "[0,33000,33000]", "custom job retries"); TEST_RESULT_BOOL(cfgOptionIdxTest(cfgOptPgHost, 0), false, "pg1-host is not set (command line reset override)"); TEST_RESULT_BOOL(cfgOptionIdxReset(cfgOptPgHost, 0), true, "pg1-host was reset");