libdb/test/c/suites/TestChannel.c
2011-09-13 13:44:24 -04:00

1265 lines
35 KiB
C

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
*/
#include <ctype.h>
#include <stdio.h>
#include <db.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#ifndef _WIN32
#include <pthread.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/uio.h>
#endif
#include "../cutest/CuTest.h"
#include "../common/test_util.h"
#define MAX_SEGS 10
#define MAX_MSGS 10
#undef LOCK_MUTEX
#undef UNLOCK_MUTEX
#ifdef _WIN32
#define sleep(s) Sleep(1000 * (s))
typedef HANDLE mutex_t;
#define mutex_init(m) \
(((*(m) = CreateMutex(NULL, FALSE, NULL)) != NULL) ? 0 : -1)
#define mutex_lock(m) \
((WaitForSingleObject(*(m), INFINITE) == WAIT_OBJECT_0) ? \
0 : GetLastError())
#define mutex_unlock(m) (ReleaseMutex(*(m)) ? 0 : GetLastError())
#define mutex_destroy(m) (CloseHandle(*(m)) ? 0 : GetLastError())
typedef HANDLE cond_t;
#define cond_init(c) ((*(c) = CreateEvent(NULL, \
TRUE, FALSE, NULL)) == NULL ? GetLastError() : 0)
#define cond_wait(c, m) (SignalObjectAndWait(*(m), *(c), INFINITE, FALSE) == WAIT_OBJECT_0 ? \
0 : GetLastError())
#define cond_wake(c) (SetEvent(*(c)) ? 0 : GetLastError())
#else
typedef pthread_mutex_t mutex_t;
#define mutex_init(m) pthread_mutex_init((m), NULL)
#define mutex_lock(m) pthread_mutex_lock(m)
#define mutex_unlock(m) pthread_mutex_unlock(m)
#define mutex_destroy(m) pthread_mutex_destroy(m)
typedef pthread_cond_t cond_t;
#define cond_init(c) pthread_cond_init((c), NULL)
#define cond_wait(c, m) pthread_cond_wait((c), (m))
#define cond_wake(c) pthread_cond_broadcast(c)
#endif
struct channel_test_globals {
CuTest *test;
mutex_t mtx;
cond_t cond;
};
struct report {
int dbt_count;
DBT dbt[MAX_SEGS];
int msg_count;
char *msg[MAX_MSGS];
int done, ret;
};
struct reports {
mutex_t m;
int count;
struct report rpt[2];
};
struct env_info {
struct report *rpt;
struct reports *rpts;
struct channel_test_globals *g;
int startupdone;
};
struct msginfo {
DB_ENV *dbenv;
int count;
};
typedef int (*PRED) __P((void *));
static int await_condition __P((PRED, void *, long));
static int check_dbt_string __P((DBT *, const char *));
static void clear_rpt __P((DB_ENV *));
static void clear_rpt_int __P((struct report *));
static int env_done __P((void *));
static struct report *get_rpt __P((const DB_ENV *));
static int fortify __P((DB_ENV *, struct channel_test_globals *));
static int has_msgs __P((void *));
static void msg_disp __P((DB_ENV *, DB_CHANNEL *, DBT *, u_int32_t, u_int32_t));
static void msg_disp2 __P((DB_ENV *, DB_CHANNEL *, DBT *, u_int32_t, u_int32_t));
static void msg_disp3 __P((DB_ENV *, DB_CHANNEL *, DBT *, u_int32_t, u_int32_t));
static void msg_disp4 __P((DB_ENV *, DB_CHANNEL *, DBT *, u_int32_t, u_int32_t));
static void msg_disp5 __P((DB_ENV *, DB_CHANNEL *, DBT *, u_int32_t, u_int32_t));
static int mystrcmp __P((char *, const char *));
static void notify __P((DB_ENV *, u_int32_t, void *));
static int is_started __P((void *));
static void td __P((DB_ENV *));
static void test_data_init __P((DBT *, char *));
static void test_zeroes __P((DB_CHANNEL *, DB_ENV *, CuTest *));
static int two_done __P((void *));
#define LOCK_MUTEX(m) do { \
int __ret; \
__ret = mutex_lock(m); \
assert(__ret == 0); \
} while (0)
#define UNLOCK_MUTEX(m) do { \
int __ret; \
__ret = mutex_unlock(m); \
assert(__ret == 0); \
} while (0)
int TestChannelSuiteSetup(CuSuite *suite) {
return (0);
}
int TestChannelSuiteTeardown(CuSuite *suite) {
return (0);
}
int TestChannelTestSetup(CuTest *test) {
struct channel_test_globals *g;
int ret;
if ((g = calloc(1, sizeof(*g))) == NULL)
return (ENOMEM);
if ((ret = mutex_init(&g->mtx)) != 0) {
free(g);
return (ret);
}
if ((ret = cond_init(&g->cond)) != 0) {
mutex_destroy(&g->mtx);
free(g);
return (ret);
}
g->test = test;
test->context = g;
return (0);
}
int TestChannelTestTeardown(CuTest *test) {
struct channel_test_globals *g;
int ret;
g = test->context;
assert(g != NULL);
ret = mutex_destroy(&g->mtx);
free(g);
test->context = NULL;
return (ret);
}
static void
myerrcall(const DB_ENV *dbenv, const char *errpfx, const char *msg) {
struct report *rpt = get_rpt(dbenv);
assert(rpt->msg_count < MAX_MSGS);
assert((rpt->msg[rpt->msg_count++] = strdup(msg)) != NULL);
}
static int
fortify(dbenv, g)
DB_ENV *dbenv;
struct channel_test_globals *g;
{
struct report *rpt;
struct env_info *info;
if ((info = calloc(1, sizeof(*info))) == NULL)
return (ENOMEM);
if ((rpt = calloc(1, sizeof(*rpt))) == NULL) {
free(info);
return (ENOMEM);
}
info->rpt = rpt;
info->rpts = NULL;
info->g = g;
info->startupdone = 0;
dbenv->app_private = info;
return (0);
}
static int
setup(envp1, envp2, envp3, g)
DB_ENV **envp1, **envp2, **envp3;
struct channel_test_globals *g;
{
DB_ENV *dbenv1, *dbenv2, *dbenv3;
DB_SITE *dbsite;
u_int32_t flags;
int ret;
#define CHECK(call) \
do { \
if ((ret = (call)) != 0) { \
fprintf(stderr, "error %d from %s", ret, #call); \
goto err; \
} \
} while (0);
dbenv1 = dbenv2 = dbenv3 = NULL;
CHECK(db_env_create(&dbenv1, 0));
CHECK(fortify(dbenv1, g));
dbenv1->set_errpfx(dbenv1, "ENV1");
dbenv1->set_errcall(dbenv1, myerrcall);
flags = DB_INIT_REP | DB_INIT_LOG | DB_INIT_LOCK | DB_INIT_MPOOL |
DB_INIT_TXN | DB_RECOVER | DB_THREAD | DB_CREATE;
setup_envdir("DIR1", 1);
CHECK(dbenv1->open(dbenv1, "DIR1", flags, 0));
CHECK(dbenv1->rep_set_config(dbenv1, DB_REPMGR_CONF_ELECTIONS, 0));
CHECK(dbenv1->repmgr_site(dbenv1, "localhost", 6000, &dbsite, 0));
CHECK(dbsite->set_config(dbsite, DB_LOCAL_SITE, 1));
CHECK(dbsite->close(dbsite));
CHECK(dbenv1->set_event_notify(dbenv1, notify));
CHECK(dbenv1->repmgr_msg_dispatch(dbenv1, msg_disp, 0));
CHECK(dbenv1->rep_set_timeout(dbenv1,
DB_REP_CONNECTION_RETRY, 3000000));
CHECK(dbenv1->repmgr_start(dbenv1, 2, DB_REP_MASTER));
CHECK(db_env_create(&dbenv2, 0));
CHECK(fortify(dbenv2, g));
dbenv2->set_errpfx(dbenv2, "ENV2");
dbenv2->set_errcall(dbenv2, myerrcall);
setup_envdir("DIR2", 1);
CHECK(dbenv2->open(dbenv2, "DIR2", flags, 0));
CHECK(dbenv2->rep_set_config(dbenv2, DB_REPMGR_CONF_ELECTIONS, 0));
CHECK(dbenv2->repmgr_site(dbenv2, "localhost", 6001, &dbsite, 0));
CHECK(dbsite->set_config(dbsite, DB_LOCAL_SITE, 1));
CHECK(dbsite->close(dbsite));
CHECK(dbenv2->repmgr_site(dbenv2, "localhost", 6000, &dbsite, 0));
CHECK(dbsite->set_config(dbsite, DB_BOOTSTRAP_HELPER, 1));
CHECK(dbsite->close(dbsite));
CHECK(dbenv2->set_event_notify(dbenv2, notify));
CHECK(dbenv2->rep_set_timeout(dbenv2,
DB_REP_CONNECTION_RETRY, 3000000));
CHECK(dbenv2->repmgr_start(dbenv2, 2, DB_REP_CLIENT));
await_condition(is_started, dbenv2, 60);
if (!is_started(dbenv2)) {
dbenv2->errx(dbenv2, "startup done not achieved in 60 seconds");
ret = DB_TIMEOUT;
goto err;
}
CHECK(db_env_create(&dbenv3, 0));
CHECK(fortify(dbenv3, g));
dbenv3->set_errpfx(dbenv3, "ENV3");
dbenv3->set_errcall(dbenv3, myerrcall);
CHECK(dbenv3->repmgr_msg_dispatch(dbenv3, msg_disp2, 0));
setup_envdir("DIR3", 1);
CHECK(dbenv3->open(dbenv3, "DIR3", flags, 0));
CHECK(dbenv3->rep_set_config(dbenv3, DB_REPMGR_CONF_ELECTIONS, 0));
CHECK(dbenv3->repmgr_site(dbenv3, "localhost", 6002, &dbsite, 0));
CHECK(dbsite->set_config(dbsite, DB_LOCAL_SITE, 1));
CHECK(dbsite->close(dbsite));
CHECK(dbenv3->repmgr_site(dbenv3, "localhost", 6000, &dbsite, 0));
CHECK(dbsite->set_config(dbsite, DB_BOOTSTRAP_HELPER, 1));
CHECK(dbsite->close(dbsite));
CHECK(dbenv3->set_event_notify(dbenv3, notify));
CHECK(dbenv3->rep_set_timeout(dbenv3,
DB_REP_CONNECTION_RETRY, 3000000));
CHECK(dbenv3->repmgr_start(dbenv3, 2, DB_REP_CLIENT));
await_condition(is_started, dbenv3, 60);
if (!is_started(dbenv3)) {
dbenv3->errx(dbenv3, "startup done not achieved in 60 seconds");
ret = DB_TIMEOUT;
goto err;
}
*envp1 = dbenv1;
*envp2 = dbenv2;
*envp3 = dbenv3;
return (0);
err:
if (dbenv3 != NULL)
td(dbenv3);
if (dbenv2 != NULL)
td(dbenv2);
if (dbenv1 != NULL)
td(dbenv1);
return (ret);
}
static void
td(dbenv)
DB_ENV *dbenv;
{
struct env_info *info;
dbenv->set_errcall(dbenv, NULL);
dbenv->set_event_notify(dbenv, NULL);
info = dbenv->app_private;
dbenv->close(dbenv, 0);
if (info != NULL) {
clear_rpt_int(info->rpt);
free(info->rpt);
free(info);
}
}
static void
clear_rpt_int(rpt)
struct report *rpt;
{
int i;
for (i = 0; i < rpt->dbt_count; i++)
free(rpt->dbt[i].data);
rpt->dbt_count = 0;
for (i = 0; i < rpt->msg_count; i++)
free(rpt->msg[i]);
rpt->msg_count = 0;
rpt->done = 0;
}
static void
clear_rpt(dbenv)
DB_ENV *dbenv;
{
struct env_info *info;
struct report *rpt;
info = dbenv->app_private;
rpt = info->rpt;
clear_rpt_int(rpt);
}
static int
env_done(ctx)
void *ctx;
{
DB_ENV *dbenv = ctx;
struct report *rpt = get_rpt(dbenv);
return (rpt->done);
}
static void
await_done(dbenv)
DB_ENV *dbenv;
{
await_condition(env_done, dbenv, 60);
assert(env_done(dbenv));
}
static int
has_msgs(ctx)
void *ctx;
{
struct msginfo *inf = ctx;
DB_ENV *dbenv = inf->dbenv;
struct report *rpt = get_rpt(dbenv);
return (rpt->msg_count == inf->count);
}
static struct report *
get_rpt(dbenv)
const DB_ENV *dbenv;
{
struct env_info *info;
if ((info = dbenv->app_private) == NULL)
return (NULL);
return (info->rpt);
}
int TestChannelFeature(CuTest *ct) {
DB_ENV *dbenv1, *dbenv2, *dbenv3;
DB_CHANNEL *ch;
DB_REP_STAT *stats;
DB_SITE *dbsite;
DBT dbt, rdbts[10], resp;
struct channel_test_globals *g;
struct report *rpt;
struct reports rpts;
struct msginfo info;
char *p;
void *pointer, *vp, *buffer;
u_int8_t short_buf[4];
size_t sz;
int done, eid, ret;
#ifdef _WIN32
setvbuf(stdout, NULL, _IONBF, 0);
#endif
printf("this is a test for repmgr channels feature\n");
g = ct->context;
CuAssertTrue(ct, (ret = setup(&dbenv1, &dbenv2, &dbenv3, g) == 0));
/*
* For this first section, we're sending to ENV2. TODO: make that more
* clear, without duplicating the hard-coding of the port.
*/
CuAssertTrue(ct,
(ret = dbenv1->repmgr_site(dbenv1,
"localhost", 6001, &dbsite, 0)) == 0);
CuAssertTrue(ct, (ret = dbsite->get_eid(dbsite, &eid)) == 0);
CuAssertTrue(ct, (ret = dbsite->close(dbsite)) == 0);
CuAssertTrue(ct, (ret = dbenv1->repmgr_channel(dbenv1, eid, &ch, 0)) == 0);
memset(&dbt, 0, sizeof(dbt));
p = "foobar";
dbt.data = p;
dbt.size = (u_int32_t)strlen(p) + 1;
memset(&resp, 0, sizeof(resp));
resp.flags = DB_DBT_MALLOC;
printf("1. send async msg with no msg dispatch in place\n");
clear_rpt(dbenv2);
CuAssertTrue(ct, (ret = ch->send_msg(ch, &dbt, 1, 0)) == 0);
/* Wait til dbenv2 has reported 1 msg. */
info.dbenv = dbenv2;
info.count = 1;
await_condition(has_msgs, &info, 60);
rpt = get_rpt(dbenv2);
CuAssertTrue(ct, rpt->msg_count == 1);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"No message dispatch call-back function has been configured") == 0);
printf("2. send request with no msg dispatch in place\n");
clear_rpt(dbenv2);
ret = ch->send_request(ch, &dbt, 1, &resp, 0, 0);
CuAssertTrue(ct, ret == DB_NOSERVER);
if (resp.data != NULL)
free(resp.data);
await_condition(has_msgs, &info, 60);
CuAssertTrue(ct, rpt->msg_count == 1);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"No message dispatch call-back function has been configured") == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_msg_dispatch(dbenv2, msg_disp, 0)) == 0);
printf("3. send request where recip forgot resp\n");
clear_rpt(dbenv2);
ret = ch->send_request(ch, &dbt, 1, &resp, 0, 0);
CuAssertTrue(ct, ret == DB_KEYEMPTY);
if (resp.data != NULL)
free(resp.data);
await_done(dbenv2);
CuAssertTrue(ct, rpt->msg_count == 1);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"Application failed to provide a response") == 0);
printf("4. now with dispatch fn installed, send a simple async msg\n");
clear_rpt(dbenv2);
test_data_init(&dbt, "Mr. Watson -- come here -- I want to see you.");
CuAssertTrue(ct, (ret = ch->send_msg(ch, &dbt, 1, 0)) == 0);
await_done(dbenv2);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0],
"Mr. Watson -- come here -- I want to see you.");
CuAssertTrue(ct, rpt->msg_count == 0);
printf("5. send a multi-seg request\n");
clear_rpt(dbenv2);
memset(&resp, 0, sizeof(resp));
resp.flags = DB_DBT_MALLOC;
test_data_init(&rdbts[0], "I wish I were a fish");
test_data_init(&rdbts[1], "I wish I were a bass");
test_data_init(&rdbts[2],
"I'd climb up on a slippery rock and slide down on my ... hands and knees");
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 3, &resp, 0, 0)) == 0);
check_dbt_string(&resp, "this is the answer to the request");
if (resp.data)
free(resp.data);
await_done(dbenv2);
CuAssertTrue(ct, rpt->dbt_count == 3);
check_dbt_string(&rpt->dbt[0], "I wish I were a fish");
check_dbt_string(&rpt->dbt[1], "I wish I were a bass");
check_dbt_string(&rpt->dbt[2],
"I'd climb up on a slippery rock and slide down on my ... hands and knees");
CuAssertTrue(ct, rpt->msg_count == 0);
test_zeroes(ch, dbenv2, ct);
printf("7. send request with too-small USERMEM buffer\n");
clear_rpt(dbenv2);
resp.data = short_buf;
resp.ulen = sizeof(short_buf);
resp.flags = DB_DBT_USERMEM;
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 3, &resp, 0, 0)) == DB_BUFFER_SMALL);
await_done(dbenv2);
CuAssertTrue(ct, rpt->msg_count == 1);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"originator's USERMEM buffer too small") == 0);
CuAssertTrue(ct, rpt->ret == EINVAL);
#define BUFLEN 20000
buffer = malloc(BUFLEN);
if (buffer == NULL)
return (2);
resp.data = buffer;
resp.ulen = BUFLEN;
resp.flags = DB_DBT_USERMEM;
printf("8. send USERMEM request without necessary DB_MULTIPLE\n");
clear_rpt(dbenv2);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 2, &resp, 0, 0)) == DB_BUFFER_SMALL);
await_done(dbenv2);
CuAssertTrue(ct, rpt->msg_count == 1);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"originator does not accept multi-segment response") == 0);
CuAssertTrue(ct, rpt->ret == EINVAL);
printf("9. send USERMEM request with DB_MULTIPLE\n");
clear_rpt(dbenv2);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 2, &resp, 0, DB_MULTIPLE)) == 0);
DB_MULTIPLE_INIT(pointer, &resp);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, strcmp((char*)vp, "roses are red") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, strcmp((char*)vp, "violets are blue") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, pointer == NULL);
ch->close(ch, 0);
/* ------------------------------- */
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, DB_EID_MASTER, &ch, 0)) == 0);
CuAssertTrue(ct, (ret = dbenv1->repmgr_msg_dispatch(dbenv1, msg_disp2, 0)) == 0);
// do a request to master
// switch masters
// do a request to new master
printf("(now we try a couple of operations on a master channel)\n");
printf("10. send request to original master\n");
rpt = get_rpt(dbenv1);
clear_rpt(dbenv1);
resp.data = buffer;
resp.ulen = BUFLEN;
resp.flags = DB_DBT_USERMEM;
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 1, &resp, 0, 0)) == 0);
check_dbt_string(&resp, "ENV1");
await_done(dbenv1);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0], "I wish I were a fish");
printf("switch master and wait for our client to see the change\n");
((struct env_info *)dbenv2->app_private)->startupdone = 0;
CuAssertTrue(ct, (ret = dbenv1->repmgr_start(dbenv1, 0, DB_REP_CLIENT)) == 0);
sleep(1); /* workaround for 19329 */
for (done = 0; ; ) {
/*
* Become master, and then make sure it really happened.
* Occasionally a race develops, where we're still holding on to
* the msg lockout at env3 at this point, in which case the
* rep_start() call (underlying our repmgr_start() call here) is
* simply dropped on the floor.
*/
CuAssertTrue(ct, (ret = dbenv3->repmgr_start(dbenv3,
0, DB_REP_MASTER)) == 0);
CuAssertTrue(ct, (ret = dbenv3->rep_stat(dbenv3,
&stats, 0)) == 0);
done = stats->st_status == DB_REP_MASTER;
free(stats);
if (done)
break;
sleep(1);
};
/*
* !!!
* Workaround for 19297: wait until verify dance is complete at env2,
* because (just a little bit) later we're going to switch master again,
* to env2. If rep_start(MASTER) at env2 happens while processing
* VERIFY match record, core rep ignores the rep_start() (even though it
* returns 0).
*/
LOCK_MUTEX(&g->mtx);
while (!((struct env_info *)dbenv2->app_private)->startupdone) {
cond_wait(&g->cond, &g->mtx);
}
// TODO: fix these macros so that this ridiculous hack isn't necessary
#ifndef _WIN32
UNLOCK_MUTEX(&g->mtx);
#endif
printf("11. send request which should go to new master (only)\n");
clear_rpt(dbenv1);
clear_rpt(dbenv3);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 1, &resp, 0, 0)) == 0);
check_dbt_string(&resp, "ENV3");
rpt = get_rpt(dbenv3);
await_done(dbenv3);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0], "I wish I were a fish");
rpt = get_rpt(dbenv1);
CuAssertTrue(ct, !rpt->done); /* old master shouldn't have recvd anything */
printf("switch master again, to ``self''\n");
CuAssertTrue(ct, (ret = dbenv3->repmgr_start(dbenv3, 0, DB_REP_CLIENT)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_start(dbenv2, 0, DB_REP_MASTER)) == 0);
/* No need to wait for env2 to see that env2 has become master. */
clear_rpt(dbenv1);
clear_rpt(dbenv2);
clear_rpt(dbenv3);
printf("12. send to self, async\n");
CuAssertTrue(ct, (ret = ch->send_msg(ch, &dbt, 1, 0)) == 0);
await_done(dbenv2);
rpt = get_rpt(dbenv2);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0],
"Mr. Watson -- come here -- I want to see you.");
CuAssertTrue(ct, rpt->msg_count == 0);
printf(" (check that other two sites didn't receive it)\n");
sleep(1);
CuAssertTrue(ct, !get_rpt(dbenv1)->done);
CuAssertTrue(ct, !get_rpt(dbenv3)->done);
printf("13. send-to-self request\n");
clear_rpt(dbenv2);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 2, &resp, 0, DB_MULTIPLE)) == 0);
DB_MULTIPLE_INIT(pointer, &resp);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, strcmp((char*)vp, "roses are red") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, strcmp((char*)vp, "violets are blue") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, pointer == NULL);
/*
* re-test the 0-length cases, in the send-to-self context (the
* implementation has a bunch of separate code)
*/
test_zeroes(ch, dbenv2, ct);
ch->close(ch, 0);
/* ---------------------------------------- */
// If you go from env2 to env, we know that it's port 6000. TODO: make this
// more robust by storing it some more structured form.
//
CuAssertTrue(ct, (ret = dbenv1->repmgr_msg_dispatch(dbenv1, msg_disp3, 0)) == 0);
CuAssertTrue(ct,
(ret = dbenv2->repmgr_site(dbenv2,
"localhost", 6000, &dbsite, 0)) == 0);
CuAssertTrue(ct, (ret = dbsite->get_eid(dbsite, &eid)) == 0);
CuAssertTrue(ct, (ret = dbsite->close(dbsite)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, eid, &ch, 0)) == 0);
printf("14. send request to site that has been shut down\n");
td(dbenv1);
memset(&resp, 0, sizeof(resp));
resp.flags = DB_DBT_MALLOC;
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 2, &resp, 0, 0)) ==
DB_REP_UNAVAIL);
if (resp.data != NULL)
free(resp.data);
// TODO: a much more interesting case is to have the remote site shut
// down while waiting for the response, because that exercises some
// clean-up code. But I guess that requires running in a couple of
// threads.
ch->close(ch, 0);
printf("15. try to connect to a down site\n");
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, eid, &ch, 0)) == DB_REP_UNAVAIL);
printf("16. try to connect to a non-existent EID\n");
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, 1732, &ch, 0)) == EINVAL);
printf("17. connect master to self from the start\n");
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, DB_EID_MASTER, &ch, 0)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_msg_dispatch(dbenv2, msg_disp2, 0)) == 0);
rpt = get_rpt(dbenv2);
clear_rpt(dbenv2);
resp.data = buffer;
resp.ulen = BUFLEN;
resp.flags = DB_DBT_USERMEM;
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 1, &resp, 0, 0)) == 0);
check_dbt_string(&resp, "ENV2");
await_done(dbenv2);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0], "I wish I were a fish");
ch->close(ch, 0);
/*
* Send an async message from env2 to env3, at which point env3 will
* reply by returning two async messages back to env2.
*/
printf("18. test async replies to (async) messages\n");
CuAssertTrue(ct, (ret = dbenv3->repmgr_msg_dispatch(dbenv3, msg_disp3, 0)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_msg_dispatch(dbenv2, msg_disp4, 0)) == 0);
CuAssertTrue(ct,
(ret = dbenv2->repmgr_site(dbenv2,
"localhost", 6002, &dbsite, 0)) == 0);
CuAssertTrue(ct, (ret = dbsite->get_eid(dbsite, &eid)) == 0);
CuAssertTrue(ct, (ret = dbsite->close(dbsite)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, eid, &ch, 0)) == 0);
rpt = get_rpt(dbenv3);
clear_rpt(dbenv3);
((struct env_info *)dbenv2->app_private)->rpts = &rpts;
memset(&rpts, 0, sizeof(rpts));
mutex_init(&rpts.m);
CuAssertTrue(ct, (ret = ch->send_msg(ch, rdbts, 1, 0)) == 0);
await_done(dbenv3);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, rpt->dbt_count == 1);
check_dbt_string(&rpt->dbt[0], "I wish I were a fish");
CuAssertTrue(ct, await_condition(two_done, dbenv2, 10));
CuAssertTrue(ct, rpts.rpt[0].done);
CuAssertTrue(ct, rpts.rpt[0].dbt_count == 1);
check_dbt_string(&rpts.rpt[0].dbt[0], "roses may be pink");
CuAssertTrue(ct, rpts.rpt[1].done);
CuAssertTrue(ct, rpts.rpt[1].dbt_count == 1);
check_dbt_string(&rpts.rpt[1].dbt[0], "I think");
clear_rpt_int(&rpts.rpt[0]);
clear_rpt_int(&rpts.rpt[1]);
ch->close(ch, 0);
sleep(1); /* wait for "EOF on connection" msg before cleaning, below */
// This kluge disappears when GM fixes that err msg to become an event
printf("19. test illegal calls from the msg disp function\n");
clear_rpt(dbenv3);
CuAssertTrue(ct, (ret = dbenv3->repmgr_msg_dispatch(dbenv3, msg_disp5, 0)) == 0);
CuAssertTrue(ct, (ret = dbenv2->repmgr_channel(dbenv2, eid, &ch, 0)) == 0);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 1, &resp, 0, 0)) == 0);
await_done(dbenv3);
rpt = get_rpt(dbenv3);
CuAssertTrue(ct, rpt->ret == EINVAL);
CuAssertTrue(ct, rpt->msg_count == 3);
CuAssertTrue(ct, mystrcmp(rpt->msg[0],
"set_timeout() invalid on DB_CHANNEL supplied to msg dispatch function") == 0);
CuAssertTrue(ct, mystrcmp(rpt->msg[1],
"close() invalid on DB_CHANNEL supplied to msg dispatch function") == 0);
CuAssertTrue(ct, mystrcmp(rpt->msg[2],
"send_request() invalid on DB_CHANNEL supplied to msg dispatch function") == 0);
ch->close(ch, 0);
free(buffer);
td(dbenv2);
td(dbenv3);
return (0);
}
static int
two_done(ctx)
void *ctx;
{
DB_ENV *dbenv = ctx;
struct reports *rpts = ((struct env_info *)dbenv->app_private)->rpts;
return (rpts->count == 2 && rpts->rpt[0].done && rpts->rpt[1].done);
}
/* return 1 ("true") for a match, 0 ("false") otherwise */
static int
check_dbt_string(dbt, s)
DBT *dbt;
const char *s;
{
if (dbt->size != strlen(s))
return (0);
if (dbt->size == 0)
return (1);
return (strcmp((char*)dbt->data, s) == 0);
}
static int
is_started(ctx)
void *ctx;
{
DB_ENV *dbenv = ctx;
DB_REP_STAT *st;
u_int32_t ans;
int ret;
if ((ret = dbenv->rep_stat(dbenv, &st, 0)) != 0) {
dbenv->err(dbenv, ret, "rep_stat");
return (0);
}
ans = st->st_startup_complete;
free(st);
return (ans);
}
static int
await_condition(pred, ctx, limit)
PRED pred;
void *ctx;
long limit;
{
#ifndef _WIN32
struct timeval t;
#endif
time_t tim;
tim = time(NULL) + limit;
while (time(NULL) < tim) {
if ((*pred)(ctx))
return (1);
// sleep 1/10th of a second at a time
// (maybe Windows can use select() too, if include Winsock2.h)
#ifdef _WIN32
Sleep(100);
#else
t.tv_sec = 0;
t.tv_usec = 100000;
select(0, NULL, NULL, NULL, &t);
#endif
}
return (0);
}
static void
notify(dbenv, event, unused)
DB_ENV *dbenv;
u_int32_t event;
void *unused;
{
struct channel_test_globals *g;
struct env_info *info;
if (event == DB_EVENT_PANIC) {
fprintf(stderr, "BDB panic");
abort();
} else if (event == DB_EVENT_REP_STARTUPDONE) {
info = dbenv->app_private;
g = info->g;
LOCK_MUTEX(&g->mtx);
info->startupdone = 1;
cond_wake(&g->cond);
UNLOCK_MUTEX(&g->mtx);
}
}
static void
msg_disp(dbenv, ch, request, nseg, flags)
DB_ENV *dbenv;
DB_CHANNEL *ch;
DBT *request;
u_int32_t nseg;
u_int32_t flags;
{
CuTest *ct;
struct report *rpt = get_rpt(dbenv);
DBT answer, mult[3];
char *p;
size_t sz;
u_int32_t i;
int ret;
ct = ((struct env_info *)dbenv->app_private)->g->test;
CuAssertTrue(ct, nseg < MAX_SEGS);
for (i = 0; i < nseg; i++) {
if ((sz = (rpt->dbt[rpt->dbt_count].size = request[i].size)) > 0) {
CuAssertTrue(ct, (rpt->dbt[rpt->dbt_count].data = malloc(sz)) != NULL);
memcpy(rpt->dbt[rpt->dbt_count].data,
request[i].data, sz);
} else
rpt->dbt[rpt->dbt_count].data = NULL;
rpt->dbt_count++;
}
ret = 0;
if (flags & DB_REPMGR_NEED_RESPONSE) {
if (nseg == 2) {
/* Try a multi-segment response. */
memset(&mult, 0, sizeof(mult));
p = "roses are red";
mult[0].data = p;
mult[0].size = (u_int32_t)strlen(p) + 1;
p = "violets are blue";
mult[1].data = p;
mult[1].size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &mult[0], 2, 0);
} else if (nseg == 1) {
// pretend to ``forget'' to respond
} else if (nseg == 4) {
// send a response of zero segments
ret = ch->send_msg(ch, &answer, 0, 0);
} else if (nseg == 5) {
// send a response with a segment of zero length
memset(&answer, 0, sizeof(answer));
answer.size = 0;
ret = ch->send_msg(ch, &answer, 1, 0);
// TODO: we still need to try this with the DB_MULTIPLE approach too
} else if (nseg == 6) {
// patience, ...
/* Try a multi-segment response. */
memset(&mult, 0, sizeof(mult));
p = "roses are red";
mult[0].data = p;
mult[0].size = (u_int32_t)strlen(p) + 1;
p = "violets are blue";
mult[1].size = 0;
mult[2].data = p;
mult[2].size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &mult[0], 3, 0);
} else {
memset(&answer, 0, sizeof(answer));
p = "this is the answer to the request";
answer.data = p;
answer.size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &answer, 1, 0);
}
}
rpt->ret = ret;
rpt->done = 1;
}
static void
msg_disp2(dbenv, ch, request, nseg, flags)
DB_ENV *dbenv;
DB_CHANNEL *ch;
DBT *request;
u_int32_t nseg;
u_int32_t flags;
{
CuTest *ct;
struct report *rpt = get_rpt(dbenv);
DBT answer;
const char *p;
char buf[100];
size_t sz;
u_int32_t i;
int ret;
ct = ((struct env_info *)dbenv->app_private)->g->test;
CuAssertTrue(ct, nseg < MAX_SEGS);
for (i = 0; i < nseg; i++) {
if ((sz = (rpt->dbt[rpt->dbt_count].size = request[i].size)) > 0) {
CuAssertTrue(ct, (rpt->dbt[rpt->dbt_count].data = malloc(sz)) != NULL);
memcpy(rpt->dbt[rpt->dbt_count].data,
request[i].data, sz);
} else
rpt->dbt[rpt->dbt_count].data = NULL;
rpt->dbt_count++;
}
if (flags & DB_REPMGR_NEED_RESPONSE) {
memset(&answer, 0, sizeof(answer));
dbenv->get_errpfx(dbenv, &p);
strncpy(buf, p, sizeof(buf));
answer.data = buf;
answer.size = (u_int32_t)strlen(p) + 1;
if (answer.size > sizeof(buf))
answer.size = sizeof(buf);
ret = ch->send_msg(ch, &answer, 1, 0);
}
rpt->ret = ret;
rpt->done = 1;
}
/* Test async replies to (async) messages. */
static void
msg_disp3(dbenv, ch, request, nseg, flags)
DB_ENV *dbenv;
DB_CHANNEL *ch;
DBT *request;
u_int32_t nseg;
u_int32_t flags;
{
CuTest *ct;
struct report *rpt = get_rpt(dbenv);
DBT answer;
char *p;
size_t sz;
u_int32_t i;
int ret;
ct = ((struct env_info *)dbenv->app_private)->g->test;
CuAssertTrue(ct, nseg < MAX_SEGS);
for (i = 0; i < nseg; i++) {
if ((sz = (rpt->dbt[rpt->dbt_count].size = request[i].size)) > 0) {
CuAssertTrue(ct, (rpt->dbt[rpt->dbt_count].data = malloc(sz)) != NULL);
memcpy(rpt->dbt[rpt->dbt_count].data,
request[i].data, sz);
} else
rpt->dbt[rpt->dbt_count].data = NULL;
rpt->dbt_count++;
}
ret = 0;
// TODO: test that multiple calls to send_msg are not allowed on a request.
CuAssertTrue(ct, !(flags & DB_REPMGR_NEED_RESPONSE));
memset(&answer, 0, sizeof(answer));
p = "roses may be pink";
answer.data = p;
answer.size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &answer, 1, 0);
if (ret == 0) {
p = "I think";
answer.data = p;
answer.size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &answer, 1, 0);
}
rpt->ret = ret;
rpt->done = 1;
}
static void
msg_disp4(dbenv, ch, request, nseg, flags)
DB_ENV *dbenv;
DB_CHANNEL *ch;
DBT *request;
u_int32_t nseg;
u_int32_t flags;
{
CuTest *ct;
struct reports *rpts = ((struct env_info *)dbenv->app_private)->rpts;
struct report *rpt;
size_t sz;
u_int32_t i;
ct = ((struct env_info *)dbenv->app_private)->g->test;
LOCK_MUTEX(&rpts->m);
rpt = &rpts->rpt[rpts->count++];
UNLOCK_MUTEX(&rpts->m);
CuAssertTrue(ct, !(flags & DB_REPMGR_NEED_RESPONSE));
CuAssertTrue(ct, nseg < MAX_SEGS);
for (i = 0; i < nseg; i++) {
if ((sz = (rpt->dbt[rpt->dbt_count].size = request[i].size)) > 0) {
CuAssertTrue(ct, (rpt->dbt[rpt->dbt_count].data = malloc(sz)) != NULL);
memcpy(rpt->dbt[rpt->dbt_count].data,
request[i].data, sz);
} else
rpt->dbt[rpt->dbt_count].data = NULL;
rpt->dbt_count++;
}
rpt->done = 1;
}
static void
msg_disp5(dbenv, ch, request, nseg, flags)
DB_ENV *dbenv;
DB_CHANNEL *ch;
DBT *request;
u_int32_t nseg;
u_int32_t flags;
{
struct report *rpt = get_rpt(dbenv);
DBT answer;
u_int8_t buf[100];
char *p;
int ret;
memset(&answer, 0, sizeof(answer));
answer.flags = DB_DBT_USERMEM;
answer.ulen = sizeof(buf);
answer.data = buf;
if ((ret = ch->set_timeout(ch, 45000000)) != EINVAL ||
(ret = ch->close(ch, 0)) != EINVAL)
rpt->ret = ret;
else
rpt->ret = ch->send_request(ch, request, nseg, &answer, 0, 0);
memset(&answer, 0, sizeof(answer));
p = "roses may be pink";
answer.data = p;
answer.size = (u_int32_t)strlen(p) + 1;
ret = ch->send_msg(ch, &answer, 1, 0);
rpt->done = 1;
}
static void
test_data_init(dbt, data)
DBT *dbt;
char *data;
{
memset(dbt, 0, sizeof(*dbt));
dbt->data = data;
dbt->size = (u_int32_t)strlen(data) + 1;
}
static void
test_zeroes(ch, dest, ct)
DB_CHANNEL *ch;
DB_ENV *dest; /* destination env handle */
CuTest *ct;
{
DBT resp;
DBT rdbts[6];
struct report *rpt;
void *pointer, *vp;
size_t sz;
int i, ret;
memset(&resp, 0, sizeof(resp));
resp.flags = DB_DBT_MALLOC;
#define DATA0 "Dear kindly judge, your honor"
#define DATA1 "my parents treat me rough"
#define DATA2 "with all their marijuana"
#define DATA3 "they won't give me a puff"
#define DATA4 "The didn't wanna have me, but somehow I was had"
#define DATA5 "Leapin' lizards, that's why I'm so bad!"
#undef ADD_TEST_DATA
#define ADD_TEST_DATA(x) do { test_data_init(&rdbts[i++], (x)); } while (0);
i = 0;
ADD_TEST_DATA(DATA0);
ADD_TEST_DATA(DATA1);
ADD_TEST_DATA(DATA2);
ADD_TEST_DATA(DATA3);
ADD_TEST_DATA(DATA4);
ADD_TEST_DATA(DATA5);
rpt = get_rpt(dest);
printf("6. send zero-segment request\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 0, &resp, 0, 0)) == 0);
CuAssertTrue(ct, rpt->dbt_count == 0);
CuAssertTrue(ct, rpt->msg_count == 0);
check_dbt_string(&resp, "this is the answer to the request");
if (resp.data)
free(resp.data);
printf("6.a) send request with a zero-length segment (now why would anyone want to do that?)\n");
clear_rpt(dest);
rdbts[1].size = 0;
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 3, &resp, 0, 0)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->dbt_count == 3);
check_dbt_string(&rpt->dbt[0], DATA0);
check_dbt_string(&rpt->dbt[1], "");
check_dbt_string(&rpt->dbt[2], DATA2);
CuAssertTrue(ct, rpt->msg_count == 0);
check_dbt_string(&resp, "this is the answer to the request");
if (resp.data)
free(resp.data);
i = 1; ADD_TEST_DATA(DATA1); /* restore perturbed test data */
printf("6.b) get a zero-length response\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 4, &resp, 0, 0)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->dbt_count == 4);
CuAssertTrue(ct, rpt->msg_count == 0);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, resp.size == 0);
if (resp.data)
free(resp.data);
printf("6.c) get a zero-length response (alternate version)\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 5, &resp, 0, 0)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->dbt_count == 5);
CuAssertTrue(ct, rpt->msg_count == 0);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, resp.size == 0);
if (resp.data)
free(resp.data);
printf("6.d) get a zero-length response (DB_MULTIPLE, zero segments)\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 4, &resp, 0, DB_MULTIPLE)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->msg_count == 0);
CuAssertTrue(ct, rpt->ret == 0);
DB_MULTIPLE_INIT(pointer, &resp);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, pointer == NULL);
if (resp.data)
free(resp.data);
printf("6.e) get a zero-length response (DB_MULTIPLE, a zero-length segment)\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 5, &resp, 0, DB_MULTIPLE)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->msg_count == 0);
CuAssertTrue(ct, rpt->ret == 0);
DB_MULTIPLE_INIT(pointer, &resp);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, sz == 0);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, pointer == NULL);
if (resp.data)
free(resp.data);
printf("6.f) get a zero-length response (DB_MULTIPLE, a zero-length segment in the middle)\n");
clear_rpt(dest);
CuAssertTrue(ct, (ret = ch->send_request(ch, rdbts, 6, &resp, 0, DB_MULTIPLE)) == 0);
await_done(dest);
CuAssertTrue(ct, rpt->msg_count == 0);
CuAssertTrue(ct, rpt->ret == 0);
DB_MULTIPLE_INIT(pointer, &resp);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, rpt->ret == 0);
CuAssertTrue(ct, strcmp((char*)vp, "roses are red") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, sz == 0);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, strcmp((char*)vp, "violets are blue") == 0);
CuAssertTrue(ct, sz == strlen((char*)vp) + 1);
DB_MULTIPLE_NEXT(pointer, &resp, vp, sz);
CuAssertTrue(ct, pointer == NULL);
if (resp.data)
free(resp.data);
}
/*
* Compare, but skip over BDB error msg number at beginning of `actual'.
*/
static int
mystrcmp(actual, expected)
char *actual;
const char *expected;
{
char *p;
for (p = actual; *p != '\0' && !isspace(*p); p++)
;
for (; *p != '\0' && isspace(*p); p++)
;
return (strcmp(p, expected));
}