/*********************************************************************************************************************************** Remote command ***********************************************************************************************************************************/ void cmdRemote(int handleRead, int handleWrite) { FUNCTION_LOG_VOID(logLevelDebug); MEM_CONTEXT_TEMP_BEGIN() { String *name = strNewFmt(PROTOCOL_SERVICE_REMOTE "-%u", cfgOptionUInt(cfgOptProcess)); IoRead *read = ioHandleReadNew(name, handleRead, (TimeMSec)(cfgOptionDbl(cfgOptProtocolTimeout) * 1000)); ioReadOpen(read); IoWrite *write = ioHandleWriteNew(name, handleWrite); ioWriteOpen(write); ProtocolServer *server = protocolServerNew(name, PROTOCOL_SERVICE_REMOTE_STR, read, write); protocolServerHandlerAdd(server, storageRemoteProtocol); protocolServerHandlerAdd(server, configProtocol); // Acquire a lock if this command needs one. We'll use the noop that is always sent from the client right after the // handshake to return an error. volatile bool success = false; TRY_BEGIN() { // Read the command. No need to parse it since we know this is the first noop. ioReadLine(read); // Only try the lock if this is process 0, i.e. the remote started from the main process if (cfgOptionUInt(cfgOptProcess) == 0) { ConfigCommand commandId = cfgCommandId(strPtr(cfgOptionStr(cfgOptCommand))); // Acquire a lock if this command requires a lock if (cfgLockRemoteRequired(commandId)) lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockRemoteType(commandId), 0, true); } protocolServerResponse(server, NULL); success = true; } CATCH_ANY() { protocolServerError(server, errorCode(), STR(errorMessage()), STR(errorStackTrace())); } TRY_END(); // If not successful we'll just exit if (success) protocolServerProcess(server); } MEM_CONTEXT_TEMP_END(); FUNCTION_LOG_RETURN_VOID(); }
/*********************************************************************************************************************************** Get an archive file from the repository (WAL segment, history file, etc.) ***********************************************************************************************************************************/ int cmdArchiveGet(void) { FUNCTION_LOG_VOID(logLevelDebug); // Set the result assuming the archive file will not be found int result = 1; MEM_CONTEXT_TEMP_BEGIN() { // Check the parameters const StringList *commandParam = cfgCommandParam(); if (strLstSize(commandParam) != 2) { if (strLstSize(commandParam) == 0) THROW(ParamRequiredError, "WAL segment to get required"); if (strLstSize(commandParam) == 1) THROW(ParamRequiredError, "path to copy WAL segment required"); THROW(ParamInvalidError, "extra parameters found"); } // Get the segment name String *walSegment = strBase(strLstGet(commandParam, 0)); // Destination is wherever we were told to move the WAL segment const String *walDestination = walPath(strLstGet(commandParam, 1), cfgOptionStr(cfgOptPgPath), STR(cfgCommandName(cfgCommand()))); // Async get can only be performed on WAL segments, history or other files must use synchronous mode if (cfgOptionBool(cfgOptArchiveAsync) && walIsSegment(walSegment)) { bool found = false; // Has the WAL segment been found yet? bool queueFull = false; // Is the queue half or more full? bool forked = false; // Has the async process been forked yet? bool confessOnError = false; // Should we confess errors? // Loop and wait for the WAL segment to be pushed Wait *wait = waitNew((TimeMSec)(cfgOptionDbl(cfgOptArchiveTimeout) * MSEC_PER_SEC)); do { // Check for errors or missing files. For archive-get ok indicates that the process succeeded but there is no WAL // file to download. if (archiveAsyncStatus(archiveModeGet, walSegment, confessOnError)) { storageRemoveP( storageSpoolWrite(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s" STATUS_EXT_OK, strPtr(walSegment)), .errorOnMissing = true); break; } // Check if the WAL segment is already in the queue found = storageExistsNP(storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); // If found then move the WAL segment to the destination directory if (found) { // Source is the WAL segment in the spool queue StorageFileRead *source = storageNewReadNP( storageSpool(), strNewFmt(STORAGE_SPOOL_ARCHIVE_IN "/%s", strPtr(walSegment))); // A move will be attempted but if the spool queue and the WAL path are on different file systems then a copy // will be performed instead. // // It looks scary that we are disabling syncs and atomicity (in case we need to copy intead of move) but this // is safe because if the system crashes Postgres will not try to reuse a restored WAL segment but will instead // request it again using the restore_command. In the case of a move this hardly matters since path syncs are // cheap but if a copy is required we could save a lot of writes. StorageFileWrite *destination = storageNewWriteP( storageLocalWrite(), walDestination, .noCreatePath = true, .noSyncFile = true, .noSyncPath = true, .noAtomic = true); // Move (or copy if required) the file storageMoveNP(storageSpoolWrite(), source, destination); // Return success result = 0; // Get a list of WAL segments left in the queue StringList *queue = storageListP( storageSpool(), STORAGE_SPOOL_ARCHIVE_IN_STR, .expression = WAL_SEGMENT_REGEXP_STR); if (strLstSize(queue) > 0) { // Get size of the WAL segment uint64_t walSegmentSize = storageInfoNP(storageLocal(), walDestination).size; // Use WAL segment size to estimate queue size and determine if the async process should be launched queueFull = strLstSize(queue) * walSegmentSize > cfgOptionUInt64(cfgOptArchiveGetQueueMax) / 2; } } // If the WAL segment has not already been found then start the async process to get it. There's no point in // forking the async process off more than once so track that as well. Use an archive lock to prevent forking if // the async process was launched by another process. if (!forked && (!found || !queueFull) && lockAcquire(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptStanza), cfgLockType(), 0, false)) { // Get control info PgControl pgControl = pgControlFromFile(cfgOptionStr(cfgOptPgPath)); // Create the queue storagePathCreateNP(storageSpoolWrite(), STORAGE_SPOOL_ARCHIVE_IN_STR); // The async process should not output on the console at all KeyValue *optionReplace = kvNew(); kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_CONSOLE_STR), VARSTRDEF("off")); kvPut(optionReplace, VARSTR(CFGOPT_LOG_LEVEL_STDERR_STR), VARSTRDEF("off")); // Generate command options StringList *commandExec = cfgExecParam(cfgCmdArchiveGetAsync, optionReplace); strLstInsert(commandExec, 0, cfgExe()); // Clean the current queue using the list of WAL that we ideally want in the queue. queueNeed() // will return the list of WAL needed to fill the queue and this will be passed to the async process. const StringList *queue = queueNeed( walSegment, found, cfgOptionUInt64(cfgOptArchiveGetQueueMax), pgControl.walSegmentSize, pgControl.version); for (unsigned int queueIdx = 0; queueIdx < strLstSize(queue); queueIdx++) strLstAdd(commandExec, strLstGet(queue, queueIdx)); // Release the lock so the child process can acquire it lockRelease(true); // Fork off the async process if (forkSafe() == 0) { // Disable logging and close log file logClose(); // Detach from parent process forkDetach(); // Execute the binary. This statement will not return if it is successful. THROW_ON_SYS_ERROR( execvp(strPtr(cfgExe()), (char ** const)strLstPtr(commandExec)) == -1, ExecuteError, "unable to execute '" CFGCMD_ARCHIVE_GET_ASYNC "'"); } // Mark the async process as forked so it doesn't get forked again. A single run of the async process should be // enough to do the job, running it again won't help anything. forked = true; } // Exit loop if WAL was found if (found) break; // Now that the async process has been launched, confess any errors that are found confessOnError = true; } while (waitMore(wait)); } // Else perform synchronous get else {
/*********************************************************************************************************************************** Test run ***********************************************************************************************************************************/ void testRun(void) { FUNCTION_HARNESS_VOID(); // ***************************************************************************************************************************** if (testBegin("cfgLoadLogSetting()")) { cfgInit(); TEST_RESULT_VOID(cfgLoadLogSetting(), "load log settings all defaults"); TEST_RESULT_INT(logLevelStdOut, logLevelOff, "console logging is off"); TEST_RESULT_INT(logLevelStdErr, logLevelOff, "stderr logging is off"); TEST_RESULT_INT(logLevelFile, logLevelOff, "file logging is off"); TEST_RESULT_BOOL(logTimestamp, true, "timestamp logging is on"); // ------------------------------------------------------------------------------------------------------------------------- cfgInit(); cfgCommandSet(cfgCmdLocal); cfgOptionValidSet(cfgOptLogLevelConsole, true); cfgOptionSet(cfgOptLogLevelConsole, cfgSourceParam, varNewStrZ("info")); cfgOptionValidSet(cfgOptLogLevelStderr, true); cfgOptionSet(cfgOptLogLevelStderr, cfgSourceParam, varNewStrZ("error")); cfgOptionValidSet(cfgOptLogLevelFile, true); cfgOptionSet(cfgOptLogLevelFile, cfgSourceParam, varNewStrZ("debug")); cfgOptionValidSet(cfgOptLogTimestamp, true); cfgOptionSet(cfgOptLogTimestamp, cfgSourceParam, varNewBool(false)); TEST_RESULT_VOID(cfgLoadLogSetting(), "load log settings no defaults"); TEST_RESULT_INT(logLevelStdOut, logLevelInfo, "console logging is info"); TEST_RESULT_INT(logLevelStdErr, logLevelError, "stderr logging is error"); TEST_RESULT_INT(logLevelFile, logLevelDebug, "file logging is debugging"); TEST_RESULT_BOOL(logTimestamp, false, "timestamp logging is off"); // ------------------------------------------------------------------------------------------------------------------------- cfgInit(); cfgCommandSet(cfgCmdLocal); cfgOptionValidSet(cfgOptLogLevelStderr, true); cfgOptionSet(cfgOptLogLevelStderr, cfgSourceParam, varNewStrZ("info")); TEST_RESULT_VOID(cfgLoadLogSetting(), "load log settings reset stderr"); TEST_RESULT_INT(logLevelStdErr, logLevelError, "stderr logging is error"); } // ***************************************************************************************************************************** if (testBegin("cfgLoadUpdateOption()")) { String *exe = strNew("/path/to/pgbackrest"); String *exeOther = strNew("/other/path/to/pgbackrest"); cfgInit(); cfgCommandSet(cfgCmdBackup); cfgExeSet(exe); cfgOptionValidSet(cfgOptRepoHost, true); cfgOptionValidSet(cfgOptPgHost, true); TEST_RESULT_VOID(cfgLoadUpdateOption(), "hosts are not set so don't update commands"); cfgOptionSet(cfgOptRepoHost, cfgSourceParam, varNewStrZ("repo-host")); TEST_RESULT_VOID(cfgLoadUpdateOption(), "repo remote command is updated"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptRepoHostCmd)), strPtr(exe), " check repo1-host-cmd"); cfgOptionSet(cfgOptRepoHostCmd, cfgSourceParam, varNewStr(exeOther)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "repo remote command was already set"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptRepoHostCmd)), strPtr(exeOther), " check repo1-host-cmd"); cfgOptionSet(cfgOptRepoHost, cfgSourceParam, NULL); // ------------------------------------------------------------------------------------------------------------------------- cfgOptionValidSet(cfgOptPgHostCmd, true); cfgOptionSet(cfgOptPgHost, cfgSourceParam, varNewStrZ("pg1-host")); cfgOptionValidSet(cfgOptPgHost + 1, true); cfgOptionSet(cfgOptPgHost + 1, cfgSourceParam, varNewStrZ("pg2-host")); cfgOptionValidSet(cfgOptPgHostCmd + 1, true); cfgOptionSet(cfgOptPgHostCmd + 1, cfgSourceParam, varNewStr(exeOther)); cfgOptionValidSet(cfgOptPgHost + cfgOptionIndexTotal(cfgOptPgHost) - 1, true); cfgOptionSet(cfgOptPgHost + cfgOptionIndexTotal(cfgOptPgHost) - 1, cfgSourceParam, varNewStrZ("pgX-host")); TEST_RESULT_VOID(cfgLoadUpdateOption(), "pg remote command is updated"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptPgHostCmd)), strPtr(exe), " check pg1-host-cmd"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptPgHostCmd + 1)), strPtr(exeOther), " check pg2-host-cmd is already set"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptPgHostCmd + 2)), NULL, " check pg3-host-cmd is not set"); TEST_RESULT_STR( strPtr(cfgOptionStr(cfgOptPgHostCmd + cfgOptionIndexTotal(cfgOptPgHost) - 1)), strPtr(exe), " check pgX-host-cmd"); // ------------------------------------------------------------------------------------------------------------------------- cfgInit(); cfgOptionValidSet(cfgOptDbTimeout, true); cfgOptionSet(cfgOptDbTimeout, cfgSourceParam, varNewDbl(100)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "pg timeout set but not protocol timeout"); cfgOptionValidSet(cfgOptProtocolTimeout, true); cfgOptionSet(cfgOptProtocolTimeout, cfgSourceDefault, varNewDbl(101)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "protocol timeout > pg timeout"); cfgOptionSet(cfgOptDbTimeout, cfgSourceParam, varNewDbl(100000)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "protocol timeout set automatically"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 100030, " check protocol timeout"); cfgOptionValidSet(cfgOptProtocolTimeout, true); cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(50.5)); TEST_ERROR( cfgLoadUpdateOption(), OptionInvalidValueError, "'50.5' is not valid for 'protocol-timeout' option\n" "HINT 'protocol-timeout' option (50.5) should be greater than 'db-timeout' option (100000)."); cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(45)); cfgOptionSet(cfgOptDbTimeout, cfgSourceDefault, varNewDbl(3600)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "set default pg timeout to be less than protocol timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 45, " check protocol timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptDbTimeout), 15, " check db timeout"); cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(11)); cfgOptionSet(cfgOptDbTimeout, cfgSourceDefault, varNewDbl(3600)); TEST_RESULT_VOID(cfgLoadUpdateOption(), "set default pg timeout to be less than test protocol timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 11, " check protocol timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptDbTimeout), 5.5, " check db timeout"); // ------------------------------------------------------------------------------------------------------------------------- cfgInit(); cfgCommandSet(cfgCmdBackup); cfgExeSet(exe); cfgOptionValidSet(cfgOptPgHost, true); TEST_RESULT_VOID(cfgLoadUpdateOption(), "only repo-host is valid"); cfgOptionValidSet(cfgOptRepoHost, true); cfgOptionSet(cfgOptRepoHost, cfgSourceParam, varNewStrZ("repo-host")); cfgOptionValidSet(cfgOptPgHost + 4, true); cfgOptionSet(cfgOptPgHost + 4, cfgSourceParam, varNewStrZ("pg5-host")); TEST_ERROR(cfgLoadUpdateOption(), ConfigError, "pg and repo hosts cannot both be configured as remote"); // ------------------------------------------------------------------------------------------------------------------------- StringList *argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("help")); strLstAdd(argList, strNew("backup")); strLstAdd(argList, strNew("process-max")); harnessLogLevelSet(logLevelWarn); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load help config -- no retention warning"); TEST_RESULT_BOOL(cfgCommandHelp(), true, " command is help"); argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--no-log-timestamp")); strLstAdd(argList, strNew("expire")); harnessLogLevelSet(logLevelWarn); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for retention warning"); harnessLogResult( "P00 WARN: option repo1-retention-full is not set, the repository may run out of space\n" " HINT: to retain full backups indefinitely (without warning), set option" " 'repo1-retention-full' to the maximum."); TEST_RESULT_BOOL(cfgOptionTest(cfgOptRepoRetentionArchive), false, " repo1-retention-archive not set"); strLstAdd(argList, strNew("--repo1-retention-full=1")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config no retention warning"); TEST_RESULT_INT(cfgOptionInt(cfgOptRepoRetentionArchive), 1, " repo1-retention-archive set"); // Munge repo-type for coverage. This will go away when there are multiple repos. cfgOptionSet(cfgOptRepoType, cfgSourceParam, NULL); TEST_RESULT_VOID(cfgLoadUpdateOption(), "load config no repo-type"); argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--no-log-timestamp")); strLstAdd(argList, strNew("--repo1-retention-archive-type=incr")); strLstAdd(argList, strNew("expire")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for retention warning"); harnessLogResult( "P00 WARN: option repo1-retention-full is not set, the repository may run out of space\n" " HINT: to retain full backups indefinitely (without warning), set option 'repo1-retention-full'" " to the maximum.\n" "P00 WARN: WAL segments will not be expired: option 'repo1-retention-archive-type=incr' but option" " 'repo1-retention-archive' is not set"); TEST_RESULT_BOOL(cfgOptionTest(cfgOptRepoRetentionArchive), false, " repo1-retention-archive not set"); argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--no-log-timestamp")); strLstAdd(argList, strNew("--repo1-retention-archive-type=diff")); strLstAdd(argList, strNew("expire")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for retention warning"); harnessLogResult( "P00 WARN: option repo1-retention-full is not set, the repository may run out of space\n" " HINT: to retain full backups indefinitely (without warning), set option" " 'repo1-retention-full' to the maximum.\n" "P00 WARN: WAL segments will not be expired: option 'repo1-retention-archive-type=diff' but neither option" " 'repo1-retention-archive' nor option 'repo1-retention-diff' is set"); TEST_RESULT_BOOL(cfgOptionTest(cfgOptRepoRetentionArchive), false, " repo1-retention-archive not set"); strLstAdd(argList, strNew("--repo1-retention-diff=2")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for retention warning"); harnessLogResult( "P00 WARN: option repo1-retention-full is not set, the repository may run out of space\n" " HINT: to retain full backups indefinitely (without warning), set option" " 'repo1-retention-full' to the maximum."); TEST_RESULT_INT(cfgOptionInt(cfgOptRepoRetentionArchive), 2, " repo1-retention-archive set to retention-diff"); argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--no-log-timestamp")); strLstAdd(argList, strNew("--repo1-retention-archive-type=diff")); strLstAdd(argList, strNew("--repo1-retention-archive=3")); strLstAdd(argList, strNew("--repo1-retention-full=1")); strLstAdd(argList, strNew("expire")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for retention warning"); harnessLogResult( "P00 WARN: option 'repo1-retention-diff' is not set for 'repo1-retention-archive-type=diff'\n" " HINT: to retain differential backups indefinitely (without warning), set option 'repo1-retention-diff'" " to the maximum."); // ------------------------------------------------------------------------------------------------------------------------- setenv("PGBACKREST_REPO1_S3_KEY", "mykey", true); setenv("PGBACKREST_REPO1_S3_KEY_SECRET", "mysecretkey", true); // Invalid bucket name with verification enabled fails argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--repo1-type=s3")); strLstAdd(argList, strNew("--repo1-s3-bucket=bogus.bucket")); strLstAdd(argList, strNew("--repo1-s3-region=region")); strLstAdd(argList, strNew("--repo1-s3-endpoint=endpoint")); strLstAdd(argList, strNew("--repo1-path=/repo")); strLstAdd(argList, strNew("archive-get")); TEST_ERROR( harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError, "'bogus.bucket' is not valid for option 'repo1-s3-bucket'" "\nHINT: RFC-2818 forbids dots in wildcard matches" "\nHINT: TLS/SSL verification cannot proceed with this bucket name" "\nHINT: remove dots from the bucket name"); // Invalid bucket name with verification disabled succeeds argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--repo1-type=s3")); strLstAdd(argList, strNew("--repo1-s3-bucket=bogus.bucket")); strLstAdd(argList, strNew("--repo1-s3-region=region")); strLstAdd(argList, strNew("--repo1-s3-endpoint=endpoint")); strLstAdd(argList, strNew("--no-repo1-s3-verify-ssl")); strLstAdd(argList, strNew("--repo1-path=/repo")); strLstAdd(argList, strNew("archive-get")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "invalid bucket with no verification"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptRepoS3Bucket)), "bogus.bucket", " check bucket value"); // Valid bucket name argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--repo1-type=s3")); strLstAdd(argList, strNew("--repo1-s3-bucket=cool-bucket")); strLstAdd(argList, strNew("--repo1-s3-region=region")); strLstAdd(argList, strNew("--repo1-s3-endpoint=endpoint")); strLstAdd(argList, strNew("--repo1-path=/repo")); strLstAdd(argList, strNew("archive-get")); TEST_RESULT_VOID(harnessCfgLoad(strLstSize(argList), strLstPtr(argList)), "valid bucket name"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptRepoS3Bucket)), "cool-bucket", " check bucket value"); unsetenv("PGBACKREST_REPO1_S3_KEY"); unsetenv("PGBACKREST_REPO1_S3_KEY_SECRET"); } // ***************************************************************************************************************************** if (testBegin("cfgLoadLogFile()")) { cfgInit(); cfgOptionValidSet(cfgOptLogLevelFile, true); cfgOptionSet(cfgOptLogLevelFile, cfgSourceParam, varNewStrZ("detail")); // On the error case is tested here, success is tested in cfgLoad() TEST_RESULT_VOID(cfgLoadLogFile(strNew("/BOGUS")), "attempt to open bogus log file"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptLogLevelFile)), "off", "log-level-file should now be off"); } // ***************************************************************************************************************************** if (testBegin("cfgLoad()")) { // Command does not have umask // ------------------------------------------------------------------------------------------------------------------------- StringList *argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("info")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config and don't set umask"); // Set a distinct umask value and test that the umask is reset by configLoad since default for neutral-umask=y // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--log-level-console=off")); strLstAdd(argList, strNew("--log-level-stderr=off")); strLstAdd(argList, strNew("--log-level-file=off")); strLstAdd(argList, strNew("archive-get")); umask(0111); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for neutral-umask"); TEST_RESULT_INT(umask(0111), 0000, " umask was reset"); // Set a distinct umask value and test that the umask is not reset by configLoad with option --no-neutral-umask // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--no-neutral-umask")); strLstAdd(argList, strNew("--log-level-console=off")); strLstAdd(argList, strNew("--log-level-stderr=off")); strLstAdd(argList, strNew("--log-level-file=off")); strLstAdd(argList, strNew("archive-get")); umask(0111); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for no-neutral-umask"); TEST_RESULT_INT(umask(0), 0111, " umask was not reset"); // No command // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "no command"); // Help command only // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("help")); ioBufferSizeSet(333); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "help command"); TEST_RESULT_SIZE(ioBufferSize(), 333, "buffer size not updated by help command"); // Help command for backup // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("help")); strLstAdd(argList, strNew("backup")); strLstAdd(argList, strNew("--log-level-console=off")); strLstAdd(argList, strNew("--log-level-stderr=off")); strLstAdd(argList, strNew("--log-level-file=off")); strLstAdd(argList, strNew("--repo1-retention-full=2")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "help command for backup"); TEST_RESULT_SIZE(ioBufferSize(), 4 * 1024 * 1024, "buffer size set to option default"); // Command takes lock and opens log file // ------------------------------------------------------------------------------------------------------------------------- struct stat statLog; argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--pg1-path=/path")); strLstAdd(argList, strNew("--repo1-retention-full=1")); strLstAdd(argList, strNewFmt("--log-path=%s", testPath())); strLstAdd(argList, strNew("--log-level-console=off")); strLstAdd(argList, strNew("--log-level-stderr=off")); strLstAdd(argList, strNew("--log-level-file=warn")); strLstAdd(argList, strNew("backup")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "lock and open log file"); TEST_RESULT_INT(lstat(strPtr(strNewFmt("%s/db-backup.log", testPath())), &statLog), 0, " check log file exists"); // Local command opens log file with special filename // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--stanza=db")); strLstAdd(argList, strNew("--command=backup")); strLstAdd(argList, strNewFmt("--log-path=%s", testPath())); strLstAdd(argList, strNew("--process=1")); strLstAdd(argList, strNew("--host-id=1")); strLstAdd(argList, strNew("--type=backup")); strLstAdd(argList, strNew("--log-level-file=warn")); strLstAdd(argList, strNew("local")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "open log file"); TEST_RESULT_INT( lstat(strPtr(strNewFmt("%s/db-backup-local-001.log", testPath())), &statLog), 0, " check log file exists"); // Remote command opens log file with special filename // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); strLstAdd(argList, strNew("pgbackrest")); strLstAdd(argList, strNew("--command=backup")); strLstAdd(argList, strNewFmt("--log-path=%s", testPath())); strLstAdd(argList, strNew("--type=backup")); strLstAdd(argList, strNew("--log-level-file=warn")); strLstAdd(argList, strNew("--process=0")); strLstAdd(argList, strNew("remote")); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "open log file"); TEST_RESULT_INT( lstat(strPtr(strNewFmt("%s/all-backup-remote-000.log", testPath())), &statLog), 0, " check log file exists"); } FUNCTION_HARNESS_RESULT_VOID(); }
/*********************************************************************************************************************************** Test run ***********************************************************************************************************************************/ void testRun(void) { FUNCTION_HARNESS_VOID(); // Static tests against known values -- these may break as options change so will need to be kept up to date. The tests have // generally been selected to favor values that are not expected to change but adjustments are welcome as long as the type of // test is not drastically changed. // ***************************************************************************************************************************** if (testBegin("check known values")) { TEST_ERROR(cfgCommandId(BOGUS_STR), AssertError, "invalid command 'BOGUS'"); TEST_RESULT_INT(cfgCommandId("archive-push"), cfgCmdArchivePush, "command id from name"); TEST_ERROR( cfgCommandDefIdFromId(CFG_COMMAND_TOTAL), AssertError, "assertion 'commandId < cfgCmdNone' failed"); TEST_RESULT_INT(cfgCommandDefIdFromId(cfgCmdBackup), cfgDefCmdBackup, "command id to def id"); TEST_RESULT_STR(cfgCommandName(cfgCmdBackup), "backup", "command name from id"); TEST_RESULT_INT(cfgOptionDefIdFromId(cfgOptPgHost + 6), cfgDefOptPgHost, "option id to def id"); TEST_RESULT_INT(cfgOptionId("target"), cfgOptTarget, "option id from name"); TEST_RESULT_INT(cfgOptionId(BOGUS_STR), -1, "option id from invalid option name"); TEST_ERROR( cfgOptionIdFromDefId(cfgDefOptionTotal(), 6), AssertError, "assertion 'optionDefId < cfgDefOptionTotal()' failed"); TEST_ERROR( cfgOptionIdFromDefId(0, 999999), AssertError, "assertion 'index < cfgDefOptionIndexTotal(optionDefId)' failed"); TEST_RESULT_INT(cfgOptionIdFromDefId(cfgDefOptPgHost, 6), cfgOptPgHost + 6, "option def id to id"); TEST_ERROR(cfgOptionIndex(CFG_OPTION_TOTAL), AssertError, "assertion 'optionId < CFG_OPTION_TOTAL' failed"); TEST_RESULT_INT(cfgOptionIndex(cfgOptPgHostCmd + 6), 6, "option index"); TEST_RESULT_INT(cfgOptionIndex(cfgOptCompressLevel), 0, "option index"); TEST_RESULT_INT(cfgOptionIndexTotal(cfgOptPgPath), 8, "option index total"); TEST_RESULT_INT(cfgOptionIndexTotal(cfgOptLogLevelConsole), 1, "option index total"); TEST_RESULT_STR(cfgOptionName(cfgOptBackupStandby), "backup-standby", "option id from name"); } // ***************************************************************************************************************************** if (testBegin("configuration")) { TEST_RESULT_VOID(cfgInit(), "config init"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_INT(cfgCommand(), cfgCmdNone, "command begins as none"); TEST_RESULT_VOID(cfgCommandSet(cfgCmdBackup), "command set to backup"); TEST_RESULT_INT(cfgCommand(), cfgCmdBackup, "command is backup"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_VOID(cfgCommandSet(cfgCmdBackup), "command set to backup"); TEST_RESULT_INT(cfgLogLevelDefault(), logLevelInfo, "default log level is info"); TEST_RESULT_BOOL(cfgLogFile(), true, "log file is on"); TEST_RESULT_BOOL(cfgLockRequired(), true, "lock is required"); TEST_RESULT_BOOL(cfgLockRemoteRequired(cfgCmdBackup), true, "remote lock is required"); TEST_RESULT_INT(cfgLockType(), lockTypeBackup, "lock is type backup"); TEST_RESULT_INT(cfgLockRemoteType(cfgCmdBackup), lockTypeBackup, "remote lock is type backup"); TEST_RESULT_BOOL(cfgParameterAllowed(), false, "parameters not allowed"); TEST_RESULT_VOID(cfgCommandSet(cfgCmdInfo), "command set to info"); TEST_RESULT_INT(cfgLogLevelDefault(), logLevelDebug, "default log level is debug"); TEST_RESULT_INT(cfgLogLevelStdErrMax(), logLevelTrace, "max stderr log level is trace"); TEST_RESULT_BOOL(cfgLogFile(), false, "log file is off"); TEST_RESULT_BOOL(cfgLockRequired(), false, "lock is not required"); TEST_RESULT_BOOL(cfgLockRemoteRequired(cfgCmdInfo), false, "remote lock is not required"); TEST_RESULT_INT(cfgLockType(), lockTypeNone, "lock is type none"); TEST_RESULT_INT(cfgLockRemoteType(cfgCmdInfo), lockTypeNone, "remote lock is type none"); TEST_RESULT_VOID(cfgCommandSet(cfgCmdStanzaCreate), "command set to stanza-create"); TEST_RESULT_BOOL(cfgLockRequired(), true, "lock is required"); TEST_RESULT_INT(cfgLockType(), lockTypeAll, "lock is type all"); TEST_RESULT_VOID(cfgCommandSet(cfgCmdLocal), "command set to local"); TEST_RESULT_INT(cfgLogLevelStdErrMax(), logLevelError, "max stderr log level is error"); TEST_RESULT_BOOL(cfgLogFile(), true, "log file is on"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_BOOL(cfgCommandHelp(), false, "command help defaults to false"); TEST_RESULT_VOID(cfgCommandHelpSet(true), "set command help"); TEST_RESULT_BOOL(cfgCommandHelp(), true, "command help is set"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_INT(strLstSize(cfgCommandParam()), 0, "command param list defaults to empty"); TEST_RESULT_VOID(cfgCommandParamSet(strLstAddZ(strLstNew(), "param")), "set command param list"); TEST_RESULT_INT(strLstSize(cfgCommandParam()), 1, "command param list is set"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_PTR(cfgExe(), NULL, "exe defaults to null"); TEST_RESULT_VOID(cfgExeSet(strNew("/path/to/exe")), "set exe"); TEST_RESULT_STR(strPtr(cfgExe()), "/path/to/exe", "exe is set"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_BOOL(cfgOptionNegate(cfgOptConfig), false, "negate defaults to false"); TEST_RESULT_VOID(cfgOptionNegateSet(cfgOptConfig, true), "set negate"); TEST_RESULT_BOOL(cfgOptionNegate(cfgOptConfig), true, "negate is set"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_BOOL(cfgOptionReset(cfgOptConfig), false, "reset defaults to false"); TEST_RESULT_VOID(cfgOptionResetSet(cfgOptConfig, true), "set reset"); TEST_RESULT_BOOL(cfgOptionReset(cfgOptConfig), true, "reset is set"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_BOOL(cfgOptionValid(cfgOptConfig), false, "valid defaults to false"); TEST_RESULT_BOOL(cfgOptionTest(cfgOptConfig), false, "option not valid for the command"); TEST_RESULT_VOID(cfgOptionValidSet(cfgOptConfig, true), "set valid"); TEST_RESULT_BOOL(cfgOptionValid(cfgOptConfig), true, "valid is set"); TEST_RESULT_BOOL(cfgOptionTest(cfgOptConfig), false, "option valid but value is null"); TEST_RESULT_VOID(cfgOptionSet(cfgOptConfig, cfgSourceParam, varNewStrZ("cfg")), "set option config"); TEST_RESULT_BOOL(cfgOptionTest(cfgOptConfig), true, "option valid and value not null"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_PTR(cfgOption(cfgOptOnline), NULL, "online is null"); TEST_RESULT_VOID(cfgOptionSet(cfgOptOnline, cfgSourceParam, varNewBool(false)), "set online"); TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), false, "online is set"); TEST_RESULT_VOID(cfgOptionSet(cfgOptOnline, cfgSourceParam, varNewStrZ("1")), "set online"); TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), true, "online is set"); TEST_RESULT_INT(cfgOptionSource(cfgOptOnline), cfgSourceParam, "online source is set"); TEST_ERROR( cfgOptionDbl(cfgOptOnline), AssertError, "assertion 'varType(configOptionValue[optionId].value) == varTypeDouble' failed"); TEST_ERROR( cfgOptionInt64(cfgOptOnline), AssertError, "assertion 'varType(configOptionValue[optionId].value) == varTypeInt64' failed"); TEST_RESULT_VOID(cfgOptionSet(cfgOptCompressLevel, cfgSourceParam, varNewInt64(1)), "set compress-level"); TEST_RESULT_INT(cfgOptionInt(cfgOptCompressLevel), 1, "compress-level is set"); TEST_RESULT_VOID(cfgOptionSet(cfgOptCompressLevel, cfgSourceDefault, varNewStrZ("3")), "set compress-level"); TEST_RESULT_INT(cfgOptionUInt(cfgOptCompressLevel), 3, "compress-level is set"); TEST_RESULT_INT(cfgOptionSource(cfgOptCompressLevel), cfgSourceDefault, "compress source is set"); TEST_ERROR( cfgOptionBool(cfgOptCompressLevel), AssertError, "assertion 'varType(configOptionValue[optionId].value) == varTypeBool' failed"); TEST_RESULT_VOID( cfgOptionSet(cfgOptArchivePushQueueMax, cfgSourceParam, varNewInt64(999999999999)), "set archive-push-queue-max"); TEST_RESULT_INT(cfgOptionInt64(cfgOptArchivePushQueueMax), 999999999999, "archive-push-queue-max is set"); TEST_RESULT_INT(cfgOptionUInt64(cfgOptArchivePushQueueMax), 999999999999, "archive-push-queue-max is set"); TEST_RESULT_VOID(cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(1.1)), "set protocol-timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 1.1, "protocol-timeout is set"); TEST_RESULT_VOID(cfgOptionSet(cfgOptProtocolTimeout, cfgSourceConfig, varNewStrZ("3.3")), "set protocol-timeout"); TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 3.3, "protocol-timeout is set"); TEST_RESULT_INT(cfgOptionSource(cfgOptProtocolTimeout), cfgSourceConfig, "protocol-timeout source is set"); TEST_ERROR( cfgOptionKv(cfgOptProtocolTimeout), AssertError, "assertion 'varType(configOptionValue[optionId].value) == varTypeKeyValue' failed"); TEST_RESULT_VOID(cfgOptionSet(cfgOptProtocolTimeout, cfgSourceConfig, NULL), "set protocol-timeout to NULL"); TEST_RESULT_PTR(cfgOption(cfgOptProtocolTimeout), NULL, "protocol-timeout is not set"); TEST_ERROR( cfgOptionSet(cfgOptRecoveryOption, cfgSourceParam, varNewDbl(1.1)), AssertError, "option 'recovery-option' must be set with KeyValue variant"); TEST_RESULT_VOID(cfgOptionSet(cfgOptRecoveryOption, cfgSourceConfig, varNewKv(kvNew())), "set recovery-option"); TEST_RESULT_INT(varLstSize(kvKeyList(cfgOptionKv(cfgOptRecoveryOption))), 0, "recovery-option is set"); TEST_ERROR( cfgOptionLst(cfgOptRecoveryOption), AssertError, "assertion 'configOptionValue[optionId].value == NULL" " || varType(configOptionValue[optionId].value) == varTypeVariantList' failed"); TEST_RESULT_INT(varLstSize(cfgOptionLst(cfgOptDbInclude)), 0, "db-include defaults to empty"); TEST_ERROR( cfgOptionSet(cfgOptDbInclude, cfgSourceParam, varNewDbl(1.1)), AssertError, "option 'db-include' must be set with VariantList variant"); TEST_RESULT_VOID(cfgOptionSet(cfgOptDbInclude, cfgSourceConfig, varNewVarLst(varLstNew())), "set db-include"); TEST_RESULT_INT(varLstSize(cfgOptionLst(cfgOptDbInclude)), 0, "db-include is set"); TEST_ERROR( cfgOptionStr(cfgOptDbInclude), AssertError, "assertion 'configOptionValue[optionId].value == NULL" " || varType(configOptionValue[optionId].value) == varTypeString' failed"); TEST_RESULT_PTR(cfgOptionStr(cfgOptStanza), NULL, "stanza defaults to null"); TEST_ERROR( cfgOptionSet(cfgOptStanza, cfgSourceParam, varNewDbl(1.1)), AssertError, "option 'stanza' must be set with String variant"); TEST_RESULT_VOID(cfgOptionSet(cfgOptStanza, cfgSourceConfig, varNewStrZ("db")), "set stanza"); TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptStanza)), "db", "stanza is set"); TEST_ERROR( cfgOptionInt(cfgOptStanza), AssertError, "assertion 'varType(configOptionValue[optionId].value) == varTypeInt64' failed"); // ------------------------------------------------------------------------------------------------------------------------- TEST_RESULT_VOID(cfgInit(), "config init resets value"); TEST_RESULT_INT(cfgCommand(), cfgCmdNone, "command begins as none"); } // ***************************************************************************************************************************** if (testBegin("cfgOptionHostPort()")) { unsigned int port = 55555; cfgInit(); cfgCommandSet(cfgCmdBackup); cfgOptionValidSet(cfgOptRepoS3Host, true); cfgOptionSet(cfgOptRepoS3Host, cfgSourceConfig, varNewStrZ("host.com")) ; TEST_RESULT_STR(strPtr(cfgOptionHostPort(cfgOptRepoS3Host, &port)), "host.com", "check plain host"); TEST_RESULT_UINT(port, 55555, "check that port was not updated"); cfgOptionSet(cfgOptRepoS3Host, cfgSourceConfig, varNewStrZ("myhost.com:777")) ; TEST_RESULT_STR(strPtr(cfgOptionHostPort(cfgOptRepoS3Host, &port)), "myhost.com", "check host with port"); TEST_RESULT_UINT(port, 777, "check that port was updated"); TEST_RESULT_STR(strPtr(cfgOptionHostPort(cfgOptRepoS3Endpoint, &port)), NULL, "check null host"); TEST_RESULT_UINT(port, 777, "check that port was not updated"); cfgOptionSet(cfgOptRepoS3Host, cfgSourceConfig, varNewStrZ("myhost.com:777:888")) ; TEST_ERROR( cfgOptionHostPort(cfgOptRepoS3Host, &port), OptionInvalidError, "'myhost.com:777:888' is not valid for option 'repo1-s3-host'" "\nHINT: is more than one port specified?"); TEST_RESULT_UINT(port, 777, "check that port was not updated"); cfgOptionValidSet(cfgOptRepoS3Endpoint, true); cfgOptionSet(cfgOptRepoS3Endpoint, cfgSourceConfig, varNewStrZ("myendpoint.com:ZZZ")) ; TEST_ERROR( cfgOptionHostPort(cfgOptRepoS3Endpoint, &port), OptionInvalidError, "'myendpoint.com:ZZZ' is not valid for option 'repo1-s3-endpoint'" "\nHINT: port is not a positive integer."); TEST_RESULT_UINT(port, 777, "check that port was not updated"); } // ***************************************************************************************************************************** if (testBegin("cfgOptionDefault() and cfgOptionDefaultSet()")) { TEST_RESULT_VOID(cfgInit(), "config init"); TEST_RESULT_VOID(cfgCommandSet(cfgCmdBackup), "backup command"); TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptType))), "incr", "backup type default"); TEST_RESULT_BOOL(varBool(cfgOptionDefault(cfgOptCompress)), "true", "backup compress default"); TEST_RESULT_DOUBLE(varDbl(cfgOptionDefault(cfgOptProtocolTimeout)), 1830, "backup protocol-timeout default"); TEST_RESULT_INT(varIntForce(cfgOptionDefault(cfgOptCompressLevel)), 6, "backup compress-level default"); TEST_RESULT_PTR(cfgOptionDefault(cfgOptDbInclude), NULL, "backup db-include default is null"); TEST_RESULT_VOID(cfgOptionSet(cfgOptPgHost, cfgSourceParam, varNewStrZ("backup")), "backup host set"); TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptPgHost, varNewStrZ("backup-default")), "backup host default"); TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptPgHost, varNewStrZ("backup-default2")), "reset backup host default"); TEST_RESULT_STR(strPtr(varStr(cfgOption(cfgOptPgHost))), "backup", "backup host value"); TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptPgHost))), "backup-default2", "backup host default"); TEST_RESULT_VOID(cfgOptionSet(cfgOptPgSocketPath, cfgSourceDefault, NULL), "backup pg-socket-path set"); TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptPgSocketPath, varNewStrZ("/to/socket")), "backup pg-socket-path default"); TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptPgSocketPath, varNewStrZ("/to/socket2")), "reset backup pg-socket-path default"); TEST_RESULT_STR(strPtr(varStr(cfgOption(cfgOptPgSocketPath))), "/to/socket2", "backup pg-socket-path value"); TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptPgSocketPath))), "/to/socket2", "backup pg-socket-path value default"); } FUNCTION_HARNESS_RESULT_VOID(); }