1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-03 00:26:59 +02:00
Files
pgbackrest/test/src/module/protocol/protocolTest.c
David Steele 8d3710b2fe Fix options being ignored by asynchronous commands.
The local, remote, archive-get-async, and archive-push-async commands were used to run functionality that was not directly available to the user. Unfortunately that meant they would not pick up options from the command that the user expected, e.g. backup, archive-get, etc.

Remove the internal commands and add roles which allow pgBackRest to determine what functionality is required without implementing special commands. This way the options are loaded from the expected command section.

Since remote is no longer a specific command with its own options, more manipulation is required when calling remote. This might be something we can improve in the config system but it may be worth leaving as is because it is a one-off, for now at least.
2020-01-15 12:24:58 -07:00

940 lines
48 KiB
C

/***********************************************************************************************************************************
Test Protocol
***********************************************************************************************************************************/
#include "common/io/handleRead.h"
#include "common/io/handleWrite.h"
#include "common/io/bufferRead.h"
#include "common/io/bufferWrite.h"
#include "common/regExp.h"
#include "storage/storage.h"
#include "storage/posix/storage.h"
#include "version.h"
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
/***********************************************************************************************************************************
Test protocol request handler
***********************************************************************************************************************************/
bool
testServerProtocol(const String *command, const VariantList *paramList, ProtocolServer *server)
{
FUNCTION_HARNESS_BEGIN();
FUNCTION_HARNESS_PARAM(STRING, command);
FUNCTION_HARNESS_PARAM(VARIANT_LIST, paramList);
FUNCTION_HARNESS_PARAM(PROTOCOL_SERVER, server);
FUNCTION_HARNESS_END();
ASSERT(command != NULL);
// Attempt to satisfy the request -- we may get requests that are meant for other handlers
bool found = true;
MEM_CONTEXT_TEMP_BEGIN()
{
if (strEq(command, strNew("assert")))
{
THROW(AssertError, "test assert");
}
else if (strEq(command, strNew("request-simple")))
{
protocolServerResponse(server, varNewBool(true));
}
else if (strEq(command, strNew("request-complex")))
{
protocolServerResponse(server, varNewBool(false));
protocolServerWriteLine(server, strNew("LINEOFTEXT"));
protocolServerWriteLine(server, NULL);
ioWriteFlush(protocolServerIoWrite(server));
}
else
found = false;
}
MEM_CONTEXT_TEMP_END();
FUNCTION_HARNESS_RESULT(BOOL, found);
}
/***********************************************************************************************************************************
Test ParallelJobCallback
***********************************************************************************************************************************/
typedef struct TestParallelJobCallback
{
List *jobList; // List of jobs to process
unsigned int jobIdx; // Current index in the list to be processed
bool clientSeen[2]; // Make sure the client idx was seen
} TestParallelJobCallback;
static ProtocolParallelJob *testParallelJobCallback(void *data, unsigned int clientIdx)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM_P(VOID, data);
FUNCTION_TEST_PARAM(UINT, clientIdx);
FUNCTION_TEST_END();
TestParallelJobCallback *listData = data;
// Mark the client idx as seen
listData->clientSeen[clientIdx] = true;
// Get a new job if there are any left
if (listData->jobIdx < lstSize(listData->jobList))
{
ProtocolParallelJob *job = *(ProtocolParallelJob **)lstGet(listData->jobList, listData->jobIdx);
listData->jobIdx++;
FUNCTION_TEST_RETURN(protocolParallelJobMove(job, memContextCurrent()));
}
FUNCTION_TEST_RETURN(NULL);
}
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
Storage *storageTest = storagePosixNew(
strNew(testPath()), STORAGE_MODE_FILE_DEFAULT, STORAGE_MODE_PATH_DEFAULT, true, NULL);
// *****************************************************************************************************************************
if (testBegin("protocolStorageTypeEnum() and protocolStorageTypeEnum()"))
{
TEST_RESULT_UINT(protocolStorageTypeEnum(PROTOCOL_REMOTE_TYPE_PG_STR), protocolStorageTypePg, "pg enum");
TEST_RESULT_UINT(protocolStorageTypeEnum(PROTOCOL_REMOTE_TYPE_REPO_STR), protocolStorageTypeRepo, "repo enum");
TEST_ERROR(protocolStorageTypeEnum(STRDEF(BOGUS_STR)), AssertError, "invalid protocol storage type 'BOGUS'");
TEST_RESULT_STR(protocolStorageTypeStr(protocolStorageTypePg), PROTOCOL_REMOTE_TYPE_PG_STR, "pg str");
TEST_RESULT_STR(protocolStorageTypeStr(protocolStorageTypeRepo), PROTOCOL_REMOTE_TYPE_REPO_STR, "repo str");
TEST_ERROR(protocolStorageTypeStr((ProtocolStorageType)999), AssertError, "invalid protocol storage type 999");
}
// *****************************************************************************************************************************
if (testBegin("repoIsLocal() and pgIsLocal()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "archive-get");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_BOOL(repoIsLocal(), true, "repo is local");
TEST_RESULT_VOID(repoIsLocalVerify(), " local verified");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-host=remote-host");
strLstAddZ(argList, "archive-get");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_BOOL(repoIsLocal(), false, "repo is remote");
TEST_ERROR_FMT(repoIsLocalVerify(), HostInvalidError, "archive-get command must be run on the repository host");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--pg1-path=/path/to");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "backup");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_BOOL(pgIsLocal(1), true, "pg is local");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--" CFGOPT_PG1_PATH "=/path/to/bogus");
strLstAddZ(argList, "--pg7-path=/path/to");
strLstAddZ(argList, "--pg7-host=test1");
strLstAddZ(argList, "--host-id=7");
strLstAddZ(argList, "--" CFGOPT_REMOTE_TYPE "=" PROTOCOL_REMOTE_TYPE_PG);
strLstAddZ(argList, "--process=0");
strLstAddZ(argList, "--" CFGOPT_REPO1_RETENTION_FULL "=1");
strLstAddZ(argList, CFGCMD_BACKUP ":" CONFIG_COMMAND_ROLE_LOCAL);
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_BOOL(pgIsLocal(7), false, "pg is remote");
}
// *****************************************************************************************************************************
if (testBegin("protocolLocalParam()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "archive-get");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolLocalParam(protocolStorageTypeRepo, 1, 0), "|"),
"--host-id=1|--log-level-console=off|--log-level-file=off|--log-level-stderr=error|--process=0|--remote-type=repo"
"|--stanza=test1|archive-get:local",
"local repo protocol params");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--pg1-path=/pg");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--log-subprocess");
strLstAddZ(argList, "backup");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolLocalParam(protocolStorageTypePg, 2, 1), "|"),
"--host-id=2|--log-level-console=off|--log-level-file=info|--log-level-stderr=error|--log-subprocess|--pg1-path=/pg"
"|--process=1|--remote-type=pg|--repo1-retention-full=1|--stanza=test1|backup:local",
"local pg protocol params");
}
// *****************************************************************************************************************************
if (testBegin("protocolRemoteParam()"))
{
storagePutP(storageNewWriteP(storageTest, strNew("pgbackrest.conf")), bufNew(0));
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--repo1-host=repo-host");
strLstAddZ(argList, "--repo1-host-user=repo-host-user");
// Local config settings should never be passed to the remote
strLstAdd(argList, strNewFmt("--config=%s/pgbackrest.conf", testPath()));
strLstAdd(argList, strNewFmt("--config-include-path=%s", testPath()));
strLstAdd(argList, strNewFmt("--config-path=%s", testPath()));
strLstAddZ(argList, "archive-get");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypeRepo, 0, 0), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|repo-host-user@repo-host"
"|pgbackrest --log-level-console=off --log-level-file=off --log-level-stderr=error --process=0 --remote-type=repo"
" --stanza=test1 archive-get:remote",
"remote protocol params");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--log-subprocess");
strLstAddZ(argList, "--repo1-host=repo-host");
strLstAddZ(argList, "--repo1-host-port=444");
strLstAddZ(argList, "--repo1-host-config=/path/pgbackrest.conf");
strLstAddZ(argList, "--repo1-host-config-include-path=/path/include");
strLstAddZ(argList, "--repo1-host-config-path=/path/config");
strLstAddZ(argList, "--repo1-host-user=repo-host-user");
strLstAddZ(argList, "archive-get");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypeRepo, 1, 0), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|-p|444|repo-host-user@repo-host"
"|pgbackrest --config=/path/pgbackrest.conf --config-include-path=/path/include --config-path=/path/config"
" --log-level-console=off --log-level-file=info --log-level-stderr=error --log-subprocess --process=1"
" --remote-type=repo --stanza=test1 archive-get:remote",
"remote protocol params with replacements");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--process=3");
strLstAddZ(argList, "--host-id=1");
strLstAddZ(argList, "--" CFGOPT_REMOTE_TYPE "=" PROTOCOL_REMOTE_TYPE_REPO);
strLstAddZ(argList, "--repo1-host=repo-host");
strLstAddZ(argList, CFGCMD_ARCHIVE_GET ":" CONFIG_COMMAND_ROLE_LOCAL);
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypeRepo, 66, 0), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|pgbackrest@repo-host"
"|pgbackrest --log-level-console=off --log-level-file=off --log-level-stderr=error --process=3 --remote-type=repo"
" --stanza=test1 archive-get:remote",
"remote protocol params for backup local");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--pg1-path=/path/to/1");
strLstAddZ(argList, "--pg1-host=pg1-host");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "backup");
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypePg, 1, 0), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|postgres@pg1-host"
"|pgbackrest --log-level-console=off --log-level-file=off --log-level-stderr=error --pg1-path=/path/to/1"
" --process=1 --remote-type=pg --stanza=test1 backup:remote",
"remote protocol params for db backup");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--process=4");
strLstAddZ(argList, "--host-id=2");
strLstAddZ(argList, "--pg1-path=/path/to/1");
strLstAddZ(argList, "--pg1-socket-path=/socket3");
strLstAddZ(argList, "--pg1-port=1111");
strLstAddZ(argList, "--pg2-path=/path/to/2");
strLstAddZ(argList, "--pg2-host=pg2-host");
strLstAddZ(argList, "--" CFGOPT_REMOTE_TYPE "=" PROTOCOL_REMOTE_TYPE_PG);
strLstAddZ(argList, "--" CFGOPT_REPO1_RETENTION_FULL "=1");
strLstAddZ(argList, CFGCMD_BACKUP ":" CONFIG_COMMAND_ROLE_LOCAL);
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypePg, 1, 1), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|postgres@pg2-host"
"|pgbackrest --log-level-console=off --log-level-file=off --log-level-stderr=error --pg1-path=/path/to/2"
" --process=4 --remote-type=pg --stanza=test1 backup:remote",
"remote protocol params for db local");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAddZ(argList, "--process=4");
strLstAddZ(argList, "--host-id=3");
strLstAddZ(argList, "--pg1-path=/path/to/1");
strLstAddZ(argList, "--pg3-path=/path/to/3");
strLstAddZ(argList, "--pg3-host=pg3-host");
strLstAddZ(argList, "--pg3-socket-path=/socket3");
strLstAddZ(argList, "--pg3-port=3333");
strLstAddZ(argList, "--" CFGOPT_REMOTE_TYPE "=" PROTOCOL_REMOTE_TYPE_PG);
strLstAddZ(argList, "--" CFGOPT_REPO1_RETENTION_FULL "=1");
strLstAddZ(argList, CFGCMD_BACKUP ":" CONFIG_COMMAND_ROLE_LOCAL);
harnessCfgLoadRaw(strLstSize(argList), strLstPtr(argList));
TEST_RESULT_STR_Z(
strLstJoin(protocolRemoteParam(protocolStorageTypePg, 1, 2), "|"),
"-o|LogLevel=error|-o|Compression=no|-o|PasswordAuthentication=no|postgres@pg3-host"
"|pgbackrest --log-level-console=off --log-level-file=off --log-level-stderr=error --pg1-path=/path/to/3"
" --pg1-port=3333 --pg1-socket-path=/socket3 --process=4 --remote-type=pg --stanza=test1 backup:remote",
"remote protocol params for db local");
}
// *****************************************************************************************************************************
if (testBegin("ProtocolCommand"))
{
ProtocolCommand *command = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(command, protocolCommandNew(strNew("command1")), "create command");
TEST_RESULT_PTR(protocolCommandParamAdd(command, varNewStr(strNew("param1"))), command, "add param");
TEST_RESULT_PTR(protocolCommandParamAdd(command, varNewStr(strNew("param2"))), command, "add param");
TEST_RESULT_PTR(protocolCommandMove(command, MEM_CONTEXT_OLD()), command, "move protocol command");
TEST_RESULT_PTR(protocolCommandMove(NULL, MEM_CONTEXT_OLD()), NULL, "move null protocol command");
}
MEM_CONTEXT_TEMP_END();
TEST_RESULT_STR_Z(protocolCommandToLog(command), "{command: command1}", "check log");
TEST_RESULT_STR_Z(protocolCommandJson(command), "{\"cmd\":\"command1\",\"param\":[\"param1\",\"param2\"]}", "check json");
// -------------------------------------------------------------------------------------------------------------------------
TEST_ASSIGN(command, protocolCommandNew(strNew("command2")), "create command");
TEST_RESULT_STR_Z(protocolCommandToLog(command), "{command: command2}", "check log");
TEST_RESULT_STR_Z(protocolCommandJson(command), "{\"cmd\":\"command2\"}", "check json");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(protocolCommandFree(command), "free command");
}
// *****************************************************************************************************************************
if (testBegin("ProtocolClient"))
{
HARNESS_FORK_BEGIN()
{
HARNESS_FORK_CHILD_BEGIN(0, true)
{
IoRead *read = ioHandleReadNew(strNew("server read"), HARNESS_FORK_CHILD_READ(), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("server write"), HARNESS_FORK_CHILD_WRITE());
ioWriteOpen(write);
// Various bogus greetings
ioWriteStrLine(write, strNew("bogus greeting"));
ioWriteFlush(write);
ioWriteStrLine(write, strNew("{\"name\":999}"));
ioWriteFlush(write);
ioWriteStrLine(write, strNew("{\"name\":null}"));
ioWriteFlush(write);
ioWriteStrLine(write, strNew("{\"name\":\"bogus\"}"));
ioWriteFlush(write);
ioWriteStrLine(write, strNew("{\"name\":\"pgBackRest\",\"service\":\"bogus\"}"));
ioWriteFlush(write);
ioWriteStrLine(write, strNew("{\"name\":\"pgBackRest\",\"service\":\"test\",\"version\":\"bogus\"}"));
ioWriteFlush(write);
// Correct greeting with noop
ioWriteStrLine(write, strNew("{\"name\":\"pgBackRest\",\"service\":\"test\",\"version\":\"" PROJECT_VERSION "\"}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop");
ioWriteStrLine(write, strNew("{}"));
ioWriteFlush(write);
// Throw errors
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop with error text");
ioWriteStrLine(write, strNew("{\"err\":25,\"out\":\"sample error message\",\"errStack\":\"stack data\"}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop with no error text");
ioWriteStrLine(write, strNew("{\"err\":255}"));
ioWriteFlush(write);
// No output expected
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop with parameters returned");
ioWriteStrLine(write, strNew("{\"out\":[\"bogus\"]}"));
ioWriteFlush(write);
// Send output
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"test\"}", "test command");
ioWriteStrLine(write, strNew(".OUTPUT"));
ioWriteStrLine(write, strNew("{\"out\":[\"value1\",\"value2\"]}"));
ioWriteFlush(write);
// invalid line
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"invalid-line\"}", "invalid line command");
ioWrite(write, LF_BUF);
ioWriteFlush(write);
// error instead of output
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"error-instead-of-output\"}", "error instead of output command");
ioWriteStrLine(write, strNew("{\"err\":255}"));
ioWriteFlush(write);
// unexpected output
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"unexpected-output\"}", "unexpected output");
ioWriteStrLine(write, strNew("{}"));
ioWriteFlush(write);
// invalid prefix
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"invalid-prefix\"}", "invalid prefix");
ioWriteStrLine(write, strNew("~line"));
ioWriteFlush(write);
// Wait for exit
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"exit\"}", "exit command");
}
HARNESS_FORK_CHILD_END();
HARNESS_FORK_PARENT_BEGIN()
{
IoRead *read = ioHandleReadNew(strNew("client read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("client write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0));
ioWriteOpen(write);
// Various bogus greetings
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), JsonFormatError,
"expected '{' at 'bogus greeting'");
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), ProtocolError,
"greeting key 'name' must be string type");
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), ProtocolError,
"unable to find greeting key 'name'");
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), ProtocolError,
"expected value 'pgBackRest' for greeting key 'name' but got 'bogus'");
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), ProtocolError,
"expected value 'test' for greeting key 'service' but got 'bogus'");
TEST_ERROR(
protocolClientNew(strNew("test client"), strNew("test"), read, write), ProtocolError,
"expected value '" PROJECT_VERSION "' for greeting key 'version' but got 'bogus'");
// Correct greeting
ProtocolClient *client = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(
client,
protocolClientMove(
protocolClientNew(strNew("test client"), strNew("test"), read, write), MEM_CONTEXT_OLD()),
"create client");
TEST_RESULT_VOID(protocolClientMove(NULL, MEM_CONTEXT_OLD()), "move null client");
}
MEM_CONTEXT_TEMP_END();
TEST_RESULT_PTR(protocolClientIoRead(client), client->read, "get read io");
TEST_RESULT_PTR(protocolClientIoWrite(client), client->write, "get write io");
// Throw errors
TEST_ERROR(
protocolClientNoOp(client), AssertError,
"raised from test client: sample error message\nstack data");
harnessLogLevelSet(logLevelDebug);
TEST_ERROR(
protocolClientNoOp(client), UnknownError,
"raised from test client: no details available\nno stack trace available");
harnessLogLevelReset();
// No output expected
TEST_ERROR(protocolClientNoOp(client), AssertError, "no output required by command");
// Get command output
const VariantList *output = NULL;
TEST_RESULT_VOID(
protocolClientWriteCommand(client, protocolCommandNew(strNew("test"))), "execute command with output");
TEST_RESULT_STR_Z(protocolClientReadLine(client), "OUTPUT", "check output");
TEST_ASSIGN(output, varVarLst(protocolClientReadOutput(client, true)), "execute command with output");
TEST_RESULT_UINT(varLstSize(output), 2, "check output size");
TEST_RESULT_STR_Z(varStr(varLstGet(output, 0)), "value1", "check value1");
TEST_RESULT_STR_Z(varStr(varLstGet(output, 1)), "value2", "check value2");
// Invalid line
TEST_RESULT_VOID(
protocolClientWriteCommand(client, protocolCommandNew(strNew("invalid-line"))),
"execute command that returns invalid line");
TEST_ERROR(protocolClientReadLine(client), FormatError, "unexpected empty line");
// Error instead of output
TEST_RESULT_VOID(
protocolClientWriteCommand(client, protocolCommandNew(strNew("error-instead-of-output"))),
"execute command that returns error instead of output");
TEST_ERROR(protocolClientReadLine(client), UnknownError, "raised from test client: no details available");
// Unexpected output
TEST_RESULT_VOID(
protocolClientWriteCommand(client, protocolCommandNew(strNew("unexpected-output"))),
"execute command that returns unexpected output");
TEST_ERROR(protocolClientReadLine(client), FormatError, "expected error but got output");
// Invalid prefix
TEST_RESULT_VOID(
protocolClientWriteCommand(client, protocolCommandNew(strNew("invalid-prefix"))),
"execute command that returns an invalid prefix");
TEST_ERROR(protocolClientReadLine(client), FormatError, "invalid prefix in '~line'");
// Free client
TEST_RESULT_VOID(protocolClientFree(client), "free client");
}
HARNESS_FORK_PARENT_END();
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************
if (testBegin("ProtocolServer"))
{
HARNESS_FORK_BEGIN()
{
HARNESS_FORK_CHILD_BEGIN(0, true)
{
IoRead *read = ioHandleReadNew(strNew("client read"), HARNESS_FORK_CHILD_READ(), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("client write"), HARNESS_FORK_CHILD_WRITE());
ioWriteOpen(write);
// Check greeting
TEST_RESULT_STR_Z(
ioReadLine(read), "{\"name\":\"pgBackRest\",\"service\":\"test\",\"version\":\"" PROJECT_VERSION "\"}",
"check greeting");
// Noop
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"noop\"}")), "write noop");
TEST_RESULT_VOID(ioWriteFlush(write), "flush noop");
TEST_RESULT_STR_Z(ioReadLine(read), "{}", "noop result");
// Invalid command
KeyValue *result = NULL;
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"bogus\"}")), "write bogus");
TEST_RESULT_VOID(ioWriteFlush(write), "flush bogus");
TEST_ASSIGN(result, varKv(jsonToVar(ioReadLine(read))), "parse error result");
TEST_RESULT_INT(varIntForce(kvGet(result, VARSTRDEF("err"))), 39, " check code");
TEST_RESULT_STR_Z(varStr(kvGet(result, VARSTRDEF("out"))), "invalid command 'bogus'", " check message");
TEST_RESULT_BOOL(kvGet(result, VARSTRDEF("errStack")) != NULL, true, " check stack exists");
// Simple request
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"request-simple\"}")), "write simple request");
TEST_RESULT_VOID(ioWriteFlush(write), "flush simple request");
TEST_RESULT_STR_Z(ioReadLine(read), "{\"out\":true}", "simple request result");
// Throw an assert error which will include a stack trace
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"assert\"}")), "write assert");
TEST_RESULT_VOID(ioWriteFlush(write), "flush assert error");
TEST_ASSIGN(result, varKv(jsonToVar(ioReadLine(read))), "parse error result");
TEST_RESULT_INT(varIntForce(kvGet(result, VARSTRDEF("err"))), 25, " check code");
TEST_RESULT_STR_Z(varStr(kvGet(result, VARSTRDEF("out"))), "test assert", " check message");
TEST_RESULT_BOOL(kvGet(result, VARSTRDEF("errStack")) != NULL, true, " check stack exists");
// Complex request -- after process loop has been restarted
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"request-complex\"}")), "write complex request");
TEST_RESULT_VOID(ioWriteFlush(write), "flush complex request");
TEST_RESULT_STR_Z(ioReadLine(read), "{\"out\":false}", "complex request result");
TEST_RESULT_STR_Z(ioReadLine(read), ".LINEOFTEXT", "complex request result");
TEST_RESULT_STR_Z(ioReadLine(read), ".", "complex request result");
// Exit
TEST_RESULT_VOID(ioWriteStrLine(write, strNew("{\"cmd\":\"exit\"}")), "write exit");
TEST_RESULT_VOID(ioWriteFlush(write), "flush exit");
}
HARNESS_FORK_CHILD_END();
HARNESS_FORK_PARENT_BEGIN()
{
IoRead *read = ioHandleReadNew(strNew("server read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("server write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0));
ioWriteOpen(write);
// Send greeting
ProtocolServer *server = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(
server,
protocolServerMove(
protocolServerNew(strNew("test server"), strNew("test"), read, write), MEM_CONTEXT_OLD()),
"create server");
TEST_RESULT_VOID(protocolServerMove(NULL, MEM_CONTEXT_OLD()), "move null server");
}
MEM_CONTEXT_TEMP_END();
TEST_RESULT_PTR(protocolServerIoRead(server), server->read, "get read io");
TEST_RESULT_PTR(protocolServerIoWrite(server), server->write, "get write io");
TEST_RESULT_VOID(protocolServerHandlerAdd(server, testServerProtocol), "add handler");
TEST_RESULT_VOID(protocolServerProcess(server), "run process loop");
TEST_RESULT_VOID(protocolServerFree(server), "free server");
}
HARNESS_FORK_PARENT_END();
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************
if (testBegin("ProtocolParallel and ProtocolParallelJob"))
{
ProtocolParallelJob *job = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
TEST_ASSIGN(job, protocolParallelJobNew(varNewStr(strNew("test")), protocolCommandNew(strNew("command"))), "new job");
TEST_RESULT_PTR(protocolParallelJobMove(job, MEM_CONTEXT_OLD()), job, "move job");
TEST_RESULT_PTR(protocolParallelJobMove(NULL, MEM_CONTEXT_OLD()), NULL, "move null job");
}
MEM_CONTEXT_TEMP_END();
TEST_ERROR(
protocolParallelJobStateSet(job, protocolParallelJobStateDone), AssertError,
"invalid state transition from 'pending' to 'done'");
TEST_RESULT_VOID(protocolParallelJobStateSet(job, protocolParallelJobStateRunning), "transition to running");
TEST_ERROR(
protocolParallelJobStateSet(job, protocolParallelJobStatePending), AssertError,
"invalid state transition from 'running' to 'pending'");
// Free job
TEST_RESULT_VOID(protocolParallelJobFree(job), "free job");
// -------------------------------------------------------------------------------------------------------------------------
HARNESS_FORK_BEGIN()
{
// Local 1
HARNESS_FORK_CHILD_BEGIN(0, true)
{
IoRead *read = ioHandleReadNew(strNew("server read"), HARNESS_FORK_CHILD_READ(), 10000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("server write"), HARNESS_FORK_CHILD_WRITE());
ioWriteOpen(write);
// Greeting with noop
ioWriteStrLine(write, strNew("{\"name\":\"pgBackRest\",\"service\":\"test\",\"version\":\"" PROJECT_VERSION "\"}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop");
ioWriteStrLine(write, strNew("{}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"command1\",\"param\":[\"param1\",\"param2\"]}", "command1");
sleepMSec(4000);
ioWriteStrLine(write, strNew("{\"out\":1}"));
ioWriteFlush(write);
// Wait for exit
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"exit\"}", "exit command");
}
HARNESS_FORK_CHILD_END();
// Local 2
HARNESS_FORK_CHILD_BEGIN(0, true)
{
IoRead *read = ioHandleReadNew(strNew("server read"), HARNESS_FORK_CHILD_READ(), 10000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(strNew("server write"), HARNESS_FORK_CHILD_WRITE());
ioWriteOpen(write);
// Greeting with noop
ioWriteStrLine(write, strNew("{\"name\":\"pgBackRest\",\"service\":\"test\",\"version\":\"" PROJECT_VERSION "\"}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"noop\"}", "noop");
ioWriteStrLine(write, strNew("{}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"command2\",\"param\":[\"param1\"]}", "command2");
sleepMSec(1000);
ioWriteStrLine(write, strNew("{\"out\":2}"));
ioWriteFlush(write);
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"command3\",\"param\":[\"param1\"]}", "command3");
ioWriteStrLine(write, strNew("{\"err\":39,\"out\":\"very serious error\"}"));
ioWriteFlush(write);
// Wait for exit
TEST_RESULT_STR_Z(ioReadLine(read), "{\"cmd\":\"exit\"}", "exit command");
}
HARNESS_FORK_CHILD_END();
HARNESS_FORK_PARENT_BEGIN()
{
// -----------------------------------------------------------------------------------------------------------------
TestParallelJobCallback data = {.jobList = lstNew(sizeof(ProtocolParallelJob *))};
ProtocolParallel *parallel = NULL;
TEST_ASSIGN(parallel, protocolParallelNew(2000, testParallelJobCallback, &data), "create parallel");
TEST_RESULT_STR_Z(protocolParallelToLog(parallel), "{state: pending, clientTotal: 0, jobTotal: 0}", "check log");
// Add client
unsigned int clientTotal = 2;
ProtocolClient *client[HARNESS_FORK_CHILD_MAX];
for (unsigned int clientIdx = 0; clientIdx < clientTotal; clientIdx++)
{
IoRead *read = ioHandleReadNew(
strNewFmt("client %u read", clientIdx), HARNESS_FORK_PARENT_READ_PROCESS(clientIdx), 2000);
ioReadOpen(read);
IoWrite *write = ioHandleWriteNew(
strNewFmt("client %u write", clientIdx), HARNESS_FORK_PARENT_WRITE_PROCESS(clientIdx));
ioWriteOpen(write);
TEST_ASSIGN(
client[clientIdx],
protocolClientNew(strNewFmt("test client %u", clientIdx), strNew("test"), read, write),
"create client %u", clientIdx);
TEST_RESULT_VOID(protocolParallelClientAdd(parallel, client[clientIdx]), "add client %u", clientIdx);
}
// Attempt to add client without handle io
String *protocolString = strNew(
"{\"name\":\"pgBackRest\",\"service\":\"error\",\"version\":\"" PROJECT_VERSION "\"}\n"
"{}\n");
IoRead *read = ioBufferReadNew(BUFSTR(protocolString));
ioReadOpen(read);
IoWrite *write = ioBufferWriteNew(bufNew(1024));
ioWriteOpen(write);
ProtocolClient *clientError = protocolClientNew(strNew("error"), strNew("error"), read, write);
TEST_ERROR(protocolParallelClientAdd(parallel, clientError), AssertError, "client with read handle is required");
protocolClientFree(clientError);
// Add jobs
ProtocolCommand *command = protocolCommandNew(strNew("command1"));
protocolCommandParamAdd(command, varNewStr(strNew("param1")));
protocolCommandParamAdd(command, varNewStr(strNew("param2")));
ProtocolParallelJob *job = protocolParallelJobNew(varNewStr(strNew("job1")), command);
TEST_RESULT_VOID(lstAdd(data.jobList, &job), "add job");
command = protocolCommandNew(strNew("command2"));
protocolCommandParamAdd(command, varNewStr(strNew("param1")));
job = protocolParallelJobNew(varNewStr(strNew("job2")), command);
TEST_RESULT_VOID(lstAdd(data.jobList, &job), "add job");
command = protocolCommandNew(strNew("command3"));
protocolCommandParamAdd(command, varNewStr(strNew("param1")));
job = protocolParallelJobNew(varNewStr(strNew("job3")), command);
TEST_RESULT_VOID(lstAdd(data.jobList, &job), "add job");
// Process jobs
TEST_RESULT_INT(protocolParallelProcess(parallel), 0, "process jobs");
TEST_RESULT_PTR(protocolParallelResult(parallel), NULL, "check no result");
// Process jobs
TEST_RESULT_INT(protocolParallelProcess(parallel), 1, "process jobs");
TEST_ASSIGN(job, protocolParallelResult(parallel), "get result");
TEST_RESULT_STR_Z(varStr(protocolParallelJobKey(job)), "job2", "check key is job2");
TEST_RESULT_BOOL(
protocolParallelJobProcessId(job) >= 1 && protocolParallelJobProcessId(job) <= 2, true,
"check process id is valid");
TEST_RESULT_INT(varIntForce(protocolParallelJobResult(job)), 2, "check result is 2");
TEST_RESULT_PTR(protocolParallelResult(parallel), NULL, "check no more results");
// Process jobs
TEST_RESULT_INT(protocolParallelProcess(parallel), 1, "process jobs");
TEST_ASSIGN(job, protocolParallelResult(parallel), "get result");
TEST_RESULT_STR_Z(varStr(protocolParallelJobKey(job)), "job3", "check key is job3");
TEST_RESULT_INT(protocolParallelJobErrorCode(job), 39, "check error code");
TEST_RESULT_STR_Z(
protocolParallelJobErrorMessage(job), "raised from test client 1: very serious error",
"check error message");
TEST_RESULT_PTR(protocolParallelJobResult(job), NULL, "check result is null");
TEST_RESULT_PTR(protocolParallelResult(parallel), NULL, "check no more results");
// Process jobs
TEST_RESULT_INT(protocolParallelProcess(parallel), 0, "process jobs");
TEST_RESULT_PTR(protocolParallelResult(parallel), NULL, "check no result");
// Process jobs
TEST_RESULT_INT(protocolParallelProcess(parallel), 1, "process jobs");
TEST_ASSIGN(job, protocolParallelResult(parallel), "get result");
TEST_RESULT_STR_Z(varStr(protocolParallelJobKey(job)), "job1", "check key is job1");
TEST_RESULT_INT(varIntForce(protocolParallelJobResult(job)), 1, "check result is 1");
TEST_RESULT_BOOL(protocolParallelDone(parallel), true, "check done");
// Free client
for (unsigned int clientIdx = 0; clientIdx < clientTotal; clientIdx++)
TEST_RESULT_VOID(protocolClientFree(client[clientIdx]), "free client %u", clientIdx);
// Free parallel
TEST_RESULT_VOID(protocolParallelFree(parallel), "free parallel");
}
HARNESS_FORK_PARENT_END();
}
HARNESS_FORK_END();
}
// *****************************************************************************************************************************
if (testBegin("protocolGet()"))
{
// Call remote free before any remotes exist
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(protocolRemoteFree(1), "free remote (non exist)");
// Call keep alive before any remotes exist
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(protocolKeepAlive(), "keep alive");
// Simple protocol start
// -------------------------------------------------------------------------------------------------------------------------
StringList *argList = strLstNew();
strLstAddZ(argList, "--stanza=db");
strLstAddZ(argList, "--protocol-timeout=10");
strLstAddZ(argList, "--repo1-host=localhost");
strLstAdd(argList, strNewFmt("--repo1-host-user=%s", testUser()));
strLstAdd(argList, strNewFmt("--repo1-path=%s", testPath()));
harnessCfgLoad(cfgCmdInfo, argList);
ProtocolClient *client = NULL;
TEST_RESULT_VOID(protocolFree(), "free protocol objects before anything has been created");
TEST_ASSIGN(client, protocolRemoteGet(protocolStorageTypeRepo, 1), "get remote protocol");
TEST_RESULT_PTR(protocolRemoteGet(protocolStorageTypeRepo, 1), client, "get remote cached protocol");
TEST_RESULT_PTR(protocolHelper.clientRemote[0].client, client, "check position in cache");
TEST_RESULT_VOID(protocolKeepAlive(), "keep alive");
TEST_RESULT_VOID(protocolFree(), "free remote protocol objects");
TEST_RESULT_VOID(protocolFree(), "free remote protocol objects again");
// Start protocol with local encryption settings
// -------------------------------------------------------------------------------------------------------------------------
storagePut(
storageNewWriteP(storageTest, strNew("pgbackrest.conf")),
BUFSTRDEF(
"[global]\n"
"repo1-cipher-type=aes-256-cbc\n"
"repo1-cipher-pass=acbd\n"));
argList = strLstNew();
strLstAddZ(argList, "--stanza=db");
strLstAddZ(argList, "--protocol-timeout=10");
strLstAdd(argList, strNewFmt("--config=%s/pgbackrest.conf", testPath()));
strLstAddZ(argList, "--repo1-host=localhost");
strLstAdd(argList, strNewFmt("--repo1-host-user=%s", testUser()));
strLstAdd(argList, strNewFmt("--repo1-path=%s", testPath()));
strLstAddZ(argList, "--process=999");
strLstAddZ(argList, "--host-id=1");
strLstAddZ(argList, "--" CFGOPT_REMOTE_TYPE "=" PROTOCOL_REMOTE_TYPE_PG);
harnessCfgLoadRole(cfgCmdArchiveGet, cfgCmdRoleLocal, argList);
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptRepoCipherPass), "acbd", "check cipher pass before");
TEST_ASSIGN(client, protocolRemoteGet(protocolStorageTypeRepo, 1), "get remote protocol");
TEST_RESULT_PTR(protocolHelper.clientRemote[0].client, client, "check position in cache");
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptRepoCipherPass), "acbd", "check cipher pass after");
TEST_RESULT_VOID(protocolFree(), "free remote protocol objects");
// Start protocol with remote encryption settings
// -------------------------------------------------------------------------------------------------------------------------
storagePut(
storageNewWriteP(storageTest, strNew("pgbackrest.conf")),
BUFSTRDEF(
"[global]\n"
"repo1-cipher-type=aes-256-cbc\n"
"repo1-cipher-pass=dcba\n"));
argList = strLstNew();
strLstAddZ(argList, "--stanza=db");
strLstAddZ(argList, "--protocol-timeout=10");
strLstAdd(argList, strNewFmt("--repo1-host-config=%s/pgbackrest.conf", testPath()));
strLstAddZ(argList, "--repo1-host=localhost");
strLstAdd(argList, strNewFmt("--repo1-host-user=%s", testUser()));
strLstAdd(argList, strNewFmt("--repo1-path=%s", testPath()));
harnessCfgLoad(cfgCmdInfo, argList);
TEST_RESULT_PTR(cfgOptionStr(cfgOptRepoCipherPass), NULL, "check cipher pass before");
TEST_ASSIGN(client, protocolRemoteGet(protocolStorageTypeRepo, 1), "get remote protocol");
TEST_RESULT_STR_Z(cfgOptionStr(cfgOptRepoCipherPass), "dcba", "check cipher pass after");
TEST_RESULT_VOID(protocolFree(), "free remote protocol objects");
// Start remote protocol
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "--stanza=db");
strLstAddZ(argList, "--protocol-timeout=10");
strLstAddZ(argList, "--repo1-retention-full=1");
strLstAddZ(argList, "--pg1-host=localhost");
strLstAdd(argList, strNewFmt("--pg1-host-user=%s", testUser()));
strLstAdd(argList, strNewFmt("--pg1-path=%s", testPath()));
harnessCfgLoad(cfgCmdBackup, argList);
TEST_ASSIGN(client, protocolRemoteGet(protocolStorageTypePg, 1), "get remote protocol");
// Start local protocol
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "--stanza=db");
strLstAddZ(argList, "--protocol-timeout=10");
strLstAddZ(argList, "--process-max=2");
harnessCfgLoad(cfgCmdArchiveGet, argList);
TEST_ASSIGN(client, protocolLocalGet(protocolStorageTypeRepo, 1, 1), "get local protocol");
TEST_RESULT_PTR(protocolLocalGet(protocolStorageTypeRepo, 1, 1), client, "get local cached protocol");
TEST_RESULT_PTR(protocolHelper.clientLocal[0].client, client, "check location in cache");
TEST_RESULT_VOID(protocolFree(), "free local and remote protocol objects");
}
FUNCTION_HARNESS_RESULT_VOID();
}