diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /ctdb/tests/src | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ctdb/tests/src')
67 files changed, 29245 insertions, 0 deletions
diff --git a/ctdb/tests/src/cluster_mutex_test.c b/ctdb/tests/src/cluster_mutex_test.c new file mode 100644 index 0000000..2576163 --- /dev/null +++ b/ctdb/tests/src/cluster_mutex_test.c @@ -0,0 +1,844 @@ +/* + CTDB cluster mutex test + + Copyright (C) Martin Schwenke 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" + +#include "tests/src/test_backtrace.h" + +/* + * ctdb_cluster_mutex.c is included below. This requires a few hacks... + */ + +/* Avoid inclusion of ctdb_private.h */ +#define _CTDB_PRIVATE_H + +/* Fake ctdb_context */ +struct ctdb_context { + struct tevent_context *ev; +}; + +/* + * ctdb_fork() and ctdb_kill() are used in ctdb_cluster_mutex.c for + * safer tracking of PIDs. Fake them here to avoid dragging in the + * world. + */ + +static pid_t ctdb_fork(struct ctdb_context *ctdb) +{ + return fork(); +} + +static int ctdb_kill(struct ctdb_context *ctdb, pid_t pid, int signum) +{ + /* + * Tests need to wait for the child to exit to ensure that the + * lock really has been released. The PID is only accessible + * in ctdb_cluster_mutex.c, so make a best attempt to ensure + * that the child process is waited for after it is killed. + * Avoid waiting if the process is already gone. + */ + int ret; + + if (signum == 0) { + return kill(pid, signum); + } + + ret = kill(pid, signum); + waitpid(pid, NULL, 0); + + return ret; +} + +#include "server/ctdb_cluster_mutex.c" + +/* + * Mutex testing support + */ + +struct mutex_handle { + bool done; + bool locked; + struct ctdb_cluster_mutex_handle *h; +}; + +struct do_lock_context { + struct mutex_handle *mh; + struct ctdb_context *ctdb; +}; + +static void do_lock_handler(char status, double latency, void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + struct mutex_handle *mh; + + assert(dl->mh != NULL); + mh = dl->mh; + + mh->locked = (status == '0') ; + + /* + * If unsuccessful then ensure the process has exited and that + * the file descriptor event handler has been cancelled + */ + if (! mh->locked) { + TALLOC_FREE(mh->h); + } + + switch (status) { + case '0': + printf("LOCK\n"); + break; + + case '1': + printf("CONTENTION\n"); + break; + + case '2': + printf("TIMEOUT\n"); + break; + + default: + printf("ERROR\n"); + } + + fflush(stdout); + mh->done = true; +} + +static void do_lock_lost_handler(void *private_data) +{ + struct do_lock_context *dl = talloc_get_type_abort( + private_data, struct do_lock_context); + + printf("LOST\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void do_lock_take(struct do_lock_context *dl, + const char *mutex_string) +{ + struct ctdb_cluster_mutex_handle *h; + + dl->mh = talloc_zero(dl, struct mutex_handle); + assert(dl->mh != NULL); + + h = ctdb_cluster_mutex(dl->mh, + dl->ctdb, + mutex_string, + 120, + do_lock_handler, + dl, + do_lock_lost_handler, + dl); + assert(h != NULL); + + dl->mh->h = h; +} + +static void do_lock_wait_done(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + while (! dl->mh->done) { + tevent_loop_once(dl->ctdb->ev); + } +} + +static void do_lock_check(struct do_lock_context *dl) +{ + assert(dl->mh != NULL); + + if (! dl->mh->locked) { + printf("NOLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); + } +} + +static void do_lock(struct do_lock_context *dl, + const char *mutex_string) +{ + do_lock_take(dl, mutex_string); + + do_lock_wait_done(dl); + + do_lock_check(dl); +} + +static void do_unlock(struct do_lock_context *dl) +{ + if (dl->mh == NULL) { + return; + } + + if (! dl->mh->done) { + /* + * Taking of lock still in progress. Free the cluster + * mutex handle to release it but leave the lock + * handle in place to allow taking of the lock to + * fail. + */ + printf("CANCEL\n"); + fflush(stdout); + TALLOC_FREE(dl->mh->h); + dl->mh->done = true; + dl->mh->locked = false; + return; + } + + printf("UNLOCK\n"); + fflush(stdout); + TALLOC_FREE(dl->mh); +} + +static void wait_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval t, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void do_lock_wait_time(struct do_lock_context *dl, + unsigned long wait_time) +{ + struct tevent_timer *tt; + bool done = false; + + tt = tevent_add_timer(dl->ctdb->ev, + dl, + tevent_timeval_current_ofs(wait_time, 0), + wait_handler, + &done); + assert(tt != NULL); + + while (!done && dl->mh != NULL) { + tevent_loop_once(dl->ctdb->ev); + } +} + +/* + * Testcases + */ + +static void test_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* CONTENTION */ + do_lock(dl2, mutex_string); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_unlock_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); +} + +static void test_lock_cancel_check(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* NOLOCK */ + do_lock_check(dl); + assert(dl->mh == NULL); +} + +static void test_lock_cancel_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + do_lock_take(dl, mutex_string); + assert(dl->mh != NULL); + + /* CANCEL */ + do_unlock(dl); + assert(dl->mh != NULL); + + do_lock_wait_done(dl); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_wait_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* Wait for twice as long as the PPID timeout */ + do_lock_wait_time(dl, 2 * 5); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void fd_done_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +static void test_lock_ppid_gone_lock_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string) +{ + struct do_lock_context *dl; + struct tevent_fd *fde; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nread; + bool done; + + /* + * Do this in the parent - debugging aborts of the child is + * trickier + */ + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* + * Note that we never see corresponding LOST. That + * would come from this process, but it is killed + * below. + */ + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(999); + exit(1); + } + + close(pipefd[1]); + + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + /* + * pipefd[1] is leaked into the helper, so there will be an + * event generated when the helper exits + */ + done = false; + fde = tevent_add_fd(ctdb->ev, + ctdb, + pipefd[0], + TEVENT_FD_READ, + fd_done_handler, + &done); + assert(fde != NULL); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + while (! done) { + tevent_loop_once(ctdb->ev); + } + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed_no_recheck(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl1; + struct do_lock_context *dl2; + int ret; + + dl1 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl1 != NULL); + dl1->ctdb = ctdb; + + dl2 = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl2 != NULL); + dl2->ctdb = ctdb; + + /* LOCK */ + do_lock(dl1, mutex_string); + assert(dl1->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + /* LOCK */ + do_lock(dl2, mutex_string); + assert(dl2->mh != NULL); + + /* UNLOCK */ + do_unlock(dl2); + assert(dl2->mh == NULL); + + /* UNLOCK */ + do_unlock(dl1); + assert(dl1->mh == NULL); +} + +static void test_lock_file_wait_recheck_unlock(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + unsigned long wait_time) +{ + struct do_lock_context *dl; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + do_lock_wait_time(dl, wait_time); + assert(dl->mh != NULL); + + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); +} + +static void test_lock_file_removed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + ret = unlink(lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_file_changed(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file) +{ + struct do_lock_context *dl; + char *t; + int fd; + int ret; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + t = talloc_asprintf(ctdb, "%s.new", lock_file); + assert(t != NULL); + + fd = open(t, O_RDWR|O_CREAT, 0600); + assert(fd != -1); + close(fd); + + ret = rename(t, lock_file); + assert(ret == 0); + + while (dl->mh != NULL) { + /* LOST */ + tevent_loop_once(ctdb->ev); + } +} + +static void test_lock_io_timeout(TALLOC_CTX *mem_ctx, + struct ctdb_context *ctdb, + const char *mutex_string, + const char *lock_file, + unsigned long block_wait, + unsigned long block_time) +{ + struct do_lock_context *dl; + int pipefd[2]; + int ret; + pid_t pid, pid2; + ssize_t nwritten; + + dl = talloc_zero(mem_ctx, struct do_lock_context); + assert(dl != NULL); + dl->ctdb = ctdb; + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + static struct flock lock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 1, + .l_len = 1, + .l_pid = 0, + }; + ssize_t nread; + int fd; + + close(pipefd[1]); + + /* Only continue when the parent is ready */ + nread = read(pipefd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + sleep(block_wait); + + fd = open(lock_file, O_RDWR, 0600); + assert(fd != -1); + + ret = fcntl(fd, F_SETLKW, &lock); + assert(ret == 0); + + sleep(block_time); + + close(fd); + + sleep(999); + + _exit(0); + } + + close(pipefd[0]); + + /* LOCK */ + do_lock(dl, mutex_string); + assert(dl->mh != NULL); + + nwritten = write(pipefd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + do_lock_wait_time(dl, block_wait + block_time * 2); + if (dl->mh != NULL) { + /* UNLOCK */ + do_unlock(dl); + assert(dl->mh == NULL); + } + + ret = kill(pid, SIGKILL); + assert(ret == 0); + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); +} + +/* + * Main + */ + +static const char *prog; + +static void usage(void) +{ + fprintf(stderr, "usage: %s <test> <mutex-string> [<arg>...]\n", prog); + exit(1); +} + +static void alarm_handler(int sig) +{ + abort(); +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_context *ctdb; + const char *mutex_string; + const char *test; + struct sigaction sa = { .sa_handler = NULL, }; + int ret; + const char *lock_file; + unsigned int wait_time; + + prog = argv[0]; + + if (argc < 3) { + usage(); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ctdb = talloc_zero(mem_ctx, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(ctdb); + assert(ctdb->ev != NULL); + + /* Add a 60s timeout for the whole test */ + sa.sa_handler = alarm_handler; + sigemptyset(&sa.sa_mask); + ret = sigaction(SIGALRM, &sa, NULL); + assert(ret == 0); + alarm(60); + + test_backtrace_setup(); + + test = argv[1]; + mutex_string = argv[2]; + + if (strcmp(test, "lock-unlock") == 0) { + test_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-lock-unlock") == 0) { + test_lock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-unlock-lock-unlock") == 0) { + test_lock_unlock_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-check") == 0) { + test_lock_cancel_check(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-cancel-unlock") == 0) { + test_lock_cancel_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-wait-unlock") == 0) { + test_lock_wait_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-ppid-gone-lock-unlock") == 0) { + test_lock_ppid_gone_lock_unlock(mem_ctx, ctdb, mutex_string); + } else if (strcmp(test, "lock-file-removed-no-recheck") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed_no_recheck(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-wait-recheck-unlock") == 0) { + if (argc != 4) { + usage(); + } + + wait_time = smb_strtoul(argv[3], + NULL, + 10, + &ret, + SMB_STR_STANDARD); + if (ret != 0) { + usage(); + } + + test_lock_file_wait_recheck_unlock(mem_ctx, + ctdb, + mutex_string, + wait_time); + } else if (strcmp(test, "lock-file-removed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_removed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-file-changed") == 0) { + if (argc != 4) { + usage(); + } + + lock_file = argv[3]; + + test_lock_file_changed(mem_ctx, + ctdb, + mutex_string, + lock_file); + } else if (strcmp(test, "lock-io-timeout") == 0) { + unsigned long block_wait; + unsigned long block_time; + + if (argc != 6) { + usage(); + } + + lock_file = argv[3]; + block_wait = (unsigned long)atol(argv[4]); + block_time = (unsigned long)atol(argv[5]); + + test_lock_io_timeout(mem_ctx, + ctdb, + mutex_string, + lock_file, + block_wait, + block_time); + } else { + fprintf(stderr, "Unknown test\n"); + exit(1); + } + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/cluster_wait.c b/ctdb/tests/src/cluster_wait.c new file mode 100644 index 0000000..d411591 --- /dev/null +++ b/ctdb/tests/src/cluster_wait.c @@ -0,0 +1,346 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" + +#include "client/client.h" + +#include "tests/src/cluster_wait.h" + +#define MSG_ID_JOIN (CTDB_SRVID_TEST_RANGE | 0x1) +#define MSG_ID_SYNC (CTDB_SRVID_TEST_RANGE | 0x2) + +/* Wait for all the clients to initialize */ + +struct cluster_wait_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t num_nodes; + bool *ready; + bool join_done; +}; + +static void cluster_wait_join_registered(struct tevent_req *subreq); +static void cluster_wait_sync_registered(struct tevent_req *subreq); +static void cluster_wait_join(struct tevent_req *subreq); +static void cluster_wait_join_sent(struct tevent_req *subreq); +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_join_unregistered(struct tevent_req *subreq); +static void cluster_wait_sync_sent(struct tevent_req *subreq); +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void cluster_wait_sync_unregistered(struct tevent_req *subreq); + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes) +{ + struct tevent_req *req, *subreq; + struct cluster_wait_state *state; + bool ok; + + req = tevent_req_create(mem_ctx, &state, struct cluster_wait_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + + state->join_done = false; + + if (ctdb_client_pnn(client) == 0) { + state->ready = talloc_zero_array(state, bool, num_nodes); + if (tevent_req_nomem(state->ready, req)) { + return tevent_req_post(req, ev); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_JOIN, + cluster_wait_join_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_join_registered, + req); + } + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_SYNC, + cluster_wait_sync_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, cluster_wait_sync_registered, req); + + /* If cluster is not synchronized within 30 seconds, time out */ + ok = tevent_req_set_endtime( + req, + ev, + tevent_timeval_current_ofs(30, 0)); + if (!ok) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void cluster_wait_join_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + printf("Waiting for cluster\n"); + fflush(stdout); +} + +static void cluster_wait_sync_registered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + uint32_t pnn; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + + msg.srvid = MSG_ID_JOIN; + msg.data.data.dsize = sizeof(pnn); + msg.data.data.dptr = (uint8_t *)&pnn; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + 0, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_sent, req); +} + +static void cluster_wait_join_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join, req); +} + +static void cluster_wait_join_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + uint32_t pnn; + uint32_t i; + + if (srvid != MSG_ID_JOIN) { + return; + } + + if (data.dsize != sizeof(uint32_t)) { + return; + } + + pnn = *(uint32_t *)data.dptr; + + if (pnn > state->num_nodes) { + return; + } + + state->ready[pnn] = true; + + for (i=0; i<state->num_nodes; i++) { + if (! state->ready[i]) { + return; + } + } + + if (state->join_done) { + return; + } + + state->join_done = true; + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_JOIN, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_join_unregistered, req); +} + +static void cluster_wait_join_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct ctdb_req_message msg; + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + if (! status) { + tevent_req_error(req, ret); + return; + } + + msg.srvid = MSG_ID_SYNC; + msg.data.data = tdb_null; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + CTDB_BROADCAST_CONNECTED, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_sent, req); +} + +static void cluster_wait_sync_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } +} + +static void cluster_wait_sync_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct cluster_wait_state *state = tevent_req_data( + req, struct cluster_wait_state); + struct tevent_req *subreq; + + if (srvid != MSG_ID_SYNC) { + return; + } + + subreq = ctdb_client_remove_message_handler_send( + state, state->ev, state->client, + MSG_ID_SYNC, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, cluster_wait_sync_unregistered, req); +} + +static void cluster_wait_sync_unregistered(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_remove_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +bool cluster_wait_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} diff --git a/ctdb/tests/src/cluster_wait.h b/ctdb/tests/src/cluster_wait.h new file mode 100644 index 0000000..e0c64cc --- /dev/null +++ b/ctdb/tests/src/cluster_wait.h @@ -0,0 +1,30 @@ +/* + Cluster wide synchronization + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CLUSTER_WAIT_H__ +#define __CLUSTER_WAIT_H__ + +struct tevent_req *cluster_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t num_nodes); + +bool cluster_wait_recv(struct tevent_req *req, int *perr); + +#endif /* __CLUSTER_WAIT_H__ */ diff --git a/ctdb/tests/src/cmdline_test.c b/ctdb/tests/src/cmdline_test.c new file mode 100644 index 0000000..916d820 --- /dev/null +++ b/ctdb/tests/src/cmdline_test.c @@ -0,0 +1,480 @@ +/* + Command line processing tests + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> + +#include <assert.h> + +#include "common/cmdline.c" + +static int dummy_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + return 0; +} + +static struct poptOption dummy_options[] = { + POPT_TABLEEND +}; + +static struct cmdline_command dummy_commands[] = { + CMDLINE_TABLEEND +}; + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, NULL, NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, "test1", NULL, NULL, NULL, &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test1", + dummy_options, + NULL, + NULL, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static struct cmdline_command test2_nofunc[] = { + { "nofunc", NULL, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_nohelp[] = { + { "nohelp", dummy_func, NULL, NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_long[] = { + { "really really long command with lots of words", + dummy_func, "long command help", + "<and lots of really long long arguments>" }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_longhelp[] = { + { "longhelp", dummy_func, + "this is a really really really long help message" \ + "with lots of words and lots of description", + NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test2_twowords[] = { + { "multiple words", dummy_func, "multiple words help", NULL }, + CMDLINE_TABLEEND +}; + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nofunc, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_nohelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_long, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_longhelp, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test2", + NULL, + NULL, + test2_twowords, + &cmdline); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +struct { + const char *str; +} test3_data; + +static struct poptOption test3_noname[] = { + { NULL, 'o', POPT_ARG_STRING, &test3_data.str, 0, + "Noname option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_notype[] = { + { "debug", 'd', POPT_ARG_NONE, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static struct poptOption test3_noarg[] = { + { "debug", 'd', POPT_ARG_STRING, NULL, 0, + "No argument option", NULL }, + POPT_TABLEEND +}; + +static void test3(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noname, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_notype, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + ret = cmdline_init(mem_ctx, + "test3", + test3_noarg, + NULL, + dummy_commands, + &cmdline); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static int test4_count; +static int test4_value; + +static struct poptOption test4_options[] = { + { "count", 'c', POPT_ARG_INT, &test4_count, 0, + "Option help of length thirty.", NULL }, + { "value", 'v', POPT_ARG_INT, &test4_value, 0, + "Short description", "Value help of length 23" }, + POPT_TABLEEND +}; + +static struct cmdline_command test4_commands[] = { + { "A really really long command", dummy_func, + "This is a really long help message", + "<a long arguments message>" }, + { "short command", dummy_func, + "short msg for short command", "<short arg msg>" }, + CMDLINE_TABLEEND +}; + +static void test4(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test4", + test4_options, + NULL, + test4_commands, + &cmdline); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + cmdline_usage(cmdline, "short command"); + + talloc_free(mem_ctx); +} + +static int action_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + if (argc != 1) { + return 100; + } + + printf("%s\n", argv[0]); + return 200; +} + +static struct cmdline_command action_commands[] = { + { "action one", dummy_func, "action one help", NULL }, + { "action two", action_func, "action two help", NULL }, + CMDLINE_TABLEEND +}; + +static void test5(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "test5", "--help" }; + const char *argv2[] = { "test5", "action" }; + const char *argv3[] = { "test5", "action", "--help" }; + const char *argv4[] = { "test5", "action", "one" }; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test5", + NULL, + "Action", + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 2, argv2, true); + assert(ret == ENOENT); + + ret = cmdline_parse(cmdline, 3, argv3, true); + assert(ret == EAGAIN); + + ret = cmdline_parse(cmdline, 3, argv4, true); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "action", "two" }; + const char *argv2[] = { "action", "two", "arg1" }; + const char *argv3[] = { "action", "two", "arg1", "arg2" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test6", + NULL, + NULL, + action_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + ret = cmdline_parse(cmdline, 3, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 200); + + ret = cmdline_parse(cmdline, 4, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 100); + + talloc_free(mem_ctx); +} + +static int test7_func(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + assert(argc == 1); + + printf("%s\n", argv[0]); + + return 0; +} + +static struct cmdline_command test7_basic_commands[] = { + { "cmd1", test7_func, "command one help", NULL }, + { "cmd2", test7_func, "command two help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_advanced_commands[] = { + { "cmd3", test7_func, "command three help", NULL }, + { "cmd4", test7_func, "command four help", NULL }, + CMDLINE_TABLEEND +}; + +static struct cmdline_command test7_ultimate_commands[] = { + { "cmd5", test7_func, "command five help", NULL }, + { "cmd6", test7_func, "command six help", NULL }, + CMDLINE_TABLEEND +}; + +static void test7(void) +{ + TALLOC_CTX *mem_ctx; + struct cmdline_context *cmdline; + const char *argv1[] = { "cmd1", "one" }; + const char *argv2[] = { "cmd3", "three" }; + const char *argv3[] = { "cmd6", "six" }; + int ret, result; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = cmdline_init(mem_ctx, + "test7", + NULL, + "Basic", + test7_basic_commands, + &cmdline); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Advanced", test7_advanced_commands); + assert(ret == 0); + + ret = cmdline_add(cmdline, "Ultimate", test7_ultimate_commands); + assert(ret == 0); + + cmdline_usage(cmdline, NULL); + + printf("\n"); + + ret = cmdline_parse(cmdline, 2, argv1, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv2, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + ret = cmdline_parse(cmdline, 2, argv3, false); + assert(ret == 0); + + ret = cmdline_run(cmdline, NULL, &result); + assert(ret == 0); + assert(result == 0); + + talloc_free(mem_ctx); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage %s <testnum>\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/comm_client_test.c b/ctdb/tests/src/comm_client_test.c new file mode 100644 index 0000000..41ed5f7 --- /dev/null +++ b/ctdb/tests/src/comm_client_test.c @@ -0,0 +1,217 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + + +struct writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + size_t count, id; +}; + +static void writer_done(struct tevent_req *subreq); +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void dead_handler(void *private_data); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, size_t *pkt_size, + size_t count) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + size_t max_size = 0, buflen; + size_t i; + int ret; + + for (i=0; i<count; i++) { + if (pkt_size[i] > max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + ret = comm_setup(state, ev, fd, read_handler, req, + dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; i<max_size; i++) { + state->buf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, writer_done, req); + + return req; +} + +static void writer_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool ret; + int err; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + struct tevent_req *subreq; + + if (buflen != state->pkt_size[state->id]) { + tevent_req_error(req, EIO); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, writer_done, req); +} + +static void dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_error(req, EPIPE); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret, i; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + for (i=0; i<5; i++) { + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret == 0) { + break; + } + sleep(1); + } + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + int err; + + if (argc != 2) { + printf("Usage: %s <sockpath>\n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + + req = writer_send(mem_ctx, ev, fd, pkt_size, 13); + assert(req != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + exit(0); +} diff --git a/ctdb/tests/src/comm_server_test.c b/ctdb/tests/src/comm_server_test.c new file mode 100644 index 0000000..86b5658 --- /dev/null +++ b/ctdb/tests/src/comm_server_test.c @@ -0,0 +1,292 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/async_req/async_sock.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +struct echo_state { + struct tevent_context *ev; + int fd; + struct comm_context *comm; + uint8_t *data; +}; + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data); +static void read_failed(void *private_data); +static void write_done(struct tevent_req *subreq); + +static struct tevent_req *echo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, int fd) +{ + struct tevent_req *req; + struct echo_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct echo_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + ret = comm_setup(state, ev, fd, read_handler, req, + read_failed, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static void read_handler(uint8_t *buf, size_t buflen, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + struct tevent_req *subreq; + + state->data = talloc_memdup(state, buf, buflen); + if (tevent_req_nomem(state->data, req)) { + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, + state->data, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, write_done, req); +} + +static void read_failed(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct echo_state *state = tevent_req_data( + req, struct echo_state); + bool ret; + int err; + + TALLOC_FREE(state->data); + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } +} + +static bool echo_recv(struct tevent_req *req, int *perr) +{ + struct echo_state *state = tevent_req_data( + req, struct echo_state); + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + close(state->fd); + return true; +} + + +struct socket_process_state { + struct tevent_context *ev; + int fd; + int max_clients; + int num_clients; +}; + +static void socket_process_client(struct tevent_req *subreq); +static void socket_process_client_done(struct tevent_req *subreq); + +static struct tevent_req *socket_process_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, int max_clients) +{ + struct tevent_req *req, *subreq; + struct socket_process_state *state; + + req = tevent_req_create(mem_ctx, &state, struct socket_process_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->max_clients = max_clients; + state->num_clients = 0; + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, socket_process_client, req); + + return req; +} + +static void socket_process_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + int client_fd; + int err = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &err); + TALLOC_FREE(subreq); + + state->num_clients++; + + if (client_fd == -1) { + tevent_req_error(req, err); + return; + } + + subreq = echo_send(state, state->ev, client_fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client_done, req); + + if (state->num_clients == state->max_clients) { + /* Stop accepting any more clients */ + return; + } + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, socket_process_client, req); +} + +static void socket_process_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct socket_process_state *state = tevent_req_data( + req, struct socket_process_state); + bool ret; + int err = 0; + + ret = echo_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, EIO); + return; + } + + if (state->num_clients == state->max_clients) { + tevent_req_done(req); + } +} + +static void socket_process_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + } +} + +static int socket_init(char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd, err = 0; + int num_clients; + + if (argc != 3) { + printf("Usage: %s <sockpath> <num_clients>\n", argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + fd = socket_init(argv[1]); + num_clients = atoi(argv[2]); + assert(num_clients > 0); + + req = socket_process_send(mem_ctx, ev, fd, num_clients); + assert(req != NULL); + + tevent_req_poll(req, ev); + + socket_process_recv(req, &err); + return err; +} diff --git a/ctdb/tests/src/comm_test.c b/ctdb/tests/src/comm_test.c new file mode 100644 index 0000000..4595928 --- /dev/null +++ b/ctdb/tests/src/comm_test.c @@ -0,0 +1,501 @@ +/* + comm tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" + +/* + * Test read_handler and dead_handler + */ + +static void test1_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + int *result = (int *)private_data; + + *result = -1; +} + +static void test1_dead_handler(void *private_data) +{ + int *result = (int *)private_data; + + *result = 1; +} + +static void test1(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + int fd[2]; + int result = 0; + uint32_t data[2]; + int ret; + ssize_t n; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test1_read_handler, &result, + test1_dead_handler, &result, &comm); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + n = write(fd[1], (void *)&data, data[0]); + assert(n == data[0]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == -1); + + result = 0; + close(fd[1]); + + while (result == 0) { + tevent_loop_once(ev); + } + + assert(result == 1); + + talloc_free(mem_ctx); +} + +/* + * Test that the tevent_req returned by comm_write_send() can be free'd. + */ + +struct test2_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void test2_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + TALLOC_FREE(state->mem_ctx); +} + +static void test2_dead_handler(void *private_data) +{ + abort(); +} + +struct test2_write_state { + int count; +}; + +static void test2_write_done(struct tevent_req *subreq); + +static struct tevent_req *test2_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct test2_write_state *state; + int i; + + req = tevent_req_create(mem_ctx, &state, struct test2_write_state); + if (req == NULL) { + return NULL; + } + + state->count = 0; + + for (i=0; i<10; i++) { + subreq = comm_write_send(state, ev, comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test2_write_done, req); + } + + return req; +} + +static void test2_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test2_write_state *state = tevent_req_data( + req, struct test2_write_state); + bool status; + int ret; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->count += 1; + + if (state->count == 10) { + tevent_req_done(req); + } +} + +static void test2_timer_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval cur_time, + void *private_data) +{ + struct test2_state *state = (struct test2_state *)private_data; + + state->done = true; +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm_reader, *comm_writer; + struct test2_state test2_state; + struct tevent_req *req; + struct tevent_timer *te; + int fd[2]; + uint32_t data[2]; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test2_state.mem_ctx = talloc_new(mem_ctx); + assert(test2_state.mem_ctx != NULL); + + test2_state.done = false; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[0], test2_read_handler, &test2_state, + test2_dead_handler, NULL, &comm_reader); + assert(ret == 0); + + ret = comm_setup(ev, ev, fd[1], NULL, NULL, test2_dead_handler, NULL, + &comm_writer); + assert(ret == 0); + + data[0] = 2 * sizeof(uint32_t); + data[1] = 0; + + req = test2_write_send(test2_state.mem_ctx, ev, comm_writer, + (uint8_t *)data, data[0]); + assert(req != NULL); + + te = tevent_add_timer(ev, ev, tevent_timeval_current_ofs(5,0), + test2_timer_handler, &test2_state); + assert(te != NULL); + + while (! test2_state.done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +/* + * Test that data is written and read correctly. + */ + +static void test3_dead_handler(void *private_data) +{ + int dead_data = *(int *)private_data; + + assert(dead_data == 1 || dead_data == 2); + + if (dead_data == 1) { + /* reader */ + fprintf(stderr, "writer closed pipe\n"); + } else { + /* writer */ + fprintf(stderr, "reader closed pipe\n"); + } +} + +struct test3_writer_state { + struct tevent_context *ev; + struct comm_context *comm; + uint8_t *buf; + size_t *pkt_size; + int count, id; +}; + +static void test3_writer_next(struct tevent_req *subreq); + +static struct tevent_req *test3_writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct comm_context *comm, + size_t *pkt_size, size_t count) +{ + struct tevent_req *req, *subreq; + struct test3_writer_state *state; + size_t max_size = 0, buflen; + size_t i; + + for (i=0; i<count; i++) { + if (pkt_size[i] > max_size) { + max_size = pkt_size[i]; + } + } + + req = tevent_req_create(mem_ctx, &state, struct test3_writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->comm = comm; + state->pkt_size = pkt_size; + state->count = count; + state->id = 0; + + state->buf = talloc_array(state, uint8_t, max_size); + if (state->buf == NULL) { + talloc_free(req); + return NULL; + } + for (i=0; i<max_size; i++) { + state->buf[i] = i%256; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, test3_writer_next, req); + return req; +} + +static void test3_writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test3_writer_state *state = tevent_req_data( + req, struct test3_writer_state); + bool ret; + int err; + size_t buflen; + + ret = comm_write_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!ret) { + tevent_req_error(req, err); + return; + } + + state->id++; + if (state->id >= state->count) { + tevent_req_done(req); + return; + } + + buflen = state->pkt_size[state->id]; + *(uint32_t *)state->buf = buflen; + subreq = comm_write_send(state, state->ev, state->comm, + state->buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + tevent_req_set_callback(subreq, test3_writer_next, req); +} + +static void test3_writer_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return; + } + *perr = 0; +} + +static void test3_writer(int fd, size_t *pkt_size, size_t count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct tevent_req *req; + int dead_data = 2; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + err = comm_setup(mem_ctx, ev, fd, NULL, NULL, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + req = test3_writer_send(mem_ctx, ev, comm, pkt_size, count); + assert(req != NULL); + + tevent_req_poll(req, ev); + + test3_writer_recv(req, &err); + assert(err == 0); + + talloc_free(mem_ctx); +} + +struct test3_reader_state { + size_t *pkt_size; + int count, received; + bool done; +}; + +static void test3_reader_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test3_reader_state *state = talloc_get_type_abort( + private_data, struct test3_reader_state); + + assert(buflen == state->pkt_size[state->received]); + printf("%zi ", buflen); + state->received++; + + if (state->received == state->count) { + printf("\n"); + state->done = true; + } +} + +static void test3_reader(int fd, size_t *pkt_size, int count) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct comm_context *comm; + struct test3_reader_state *state; + int dead_data = 1; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state = talloc_zero(mem_ctx, struct test3_reader_state); + assert(state != NULL); + + state->pkt_size = pkt_size; + state->count = count; + state->received = 0; + state->done = false; + + err = comm_setup(mem_ctx, ev, fd, test3_reader_handler, state, + test3_dead_handler, &dead_data, &comm); + assert(err == 0); + assert(comm != NULL); + + while (!state->done) { + tevent_loop_once(ev); + } + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + int fd[2]; + int ret; + pid_t pid; + size_t pkt_size[13] = { 100, 2048, 500, 4096, 1024, 8192, + 200, 16384, 300, 32768, 400, 65536, + 1024*1024 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + test3_writer(fd[1], pkt_size, 13); + close(fd[1]); + exit(0); + } + + close(fd[1]); + test3_reader(fd[0], pkt_size, 13); + close(fd[0]); +} + + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s <testnum>\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/conf_test.c b/ctdb/tests/src/conf_test.c new file mode 100644 index 0000000..9b3bd8f --- /dev/null +++ b/ctdb/tests/src/conf_test.c @@ -0,0 +1,513 @@ +/* + Configuration file handling on top of tini + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/conf.c" + +static void test1(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_section(conf, NULL, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test2(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_string(conf, "section1", "key1", "default", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test3(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test4(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", NULL, NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_integer(conf, "section1", "key1", 10, NULL); + status = conf_valid(conf); + assert(status == false); + + talloc_free(mem_ctx); +} + +static void test5(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + enum conf_type type; + int ret; + bool status; + const char *s_val; + int i_val; + bool b_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + status = conf_query(conf, "section1", "key1", &type); + assert(status == true); + assert(type == CONF_STRING); + + status = conf_query(conf, "section1", "key2", &type); + assert(status == true); + assert(type == CONF_INTEGER); + + status = conf_query(conf, "section1", "key3", &type); + assert(status == true); + assert(type == CONF_BOOLEAN); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "value1") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static void test6(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val, is_default; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + is_default = false; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "default") == 0); + assert(is_default == true); + + is_default = false; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 10); + assert(is_default == true); + + is_default = false; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == true); + assert(is_default == true); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == 0); + + assert(strcmp(s_val, "foobar") == 0); + assert(i_val == 20); + assert(b_val == false); + + is_default = true; + ret = conf_get_string(conf, "section1", "key1", &s2_val, &is_default); + assert(ret == 0); + assert(strcmp(s2_val, "foobar") == 0); + assert(is_default == false); + + is_default = true; + ret = conf_get_integer(conf, "section1", "key2", &i2_val, &is_default); + assert(ret == 0); + assert(i2_val == 20); + assert(is_default == false); + + is_default = true; + ret = conf_get_boolean(conf, "section1", "key3", &b2_val, &is_default); + assert(ret == 0); + assert(b2_val == false); + assert(is_default == false); + + conf_dump(conf, stdout); + + conf_set_defaults(conf); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + talloc_free(mem_ctx); +} + +static bool test7_validate_string(const char *key, + const char *old_value, const char *new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_integer(const char *key, + int old_value, int new_value, + enum conf_update_mode mode) +{ + return false; +} + +static bool test7_validate_boolean(const char *key, + bool old_value, bool new_value, + enum conf_update_mode mode) +{ + return false; +} + +static void test7(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + const char *s_val, *s2_val; + int i_val, i2_val; + bool b_val, b2_val; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", + test7_validate_string); + conf_define_integer(conf, "section1", "key2", 10, + test7_validate_integer); + conf_define_boolean(conf, "section1", "key3", true, + test7_validate_boolean); + + conf_assign_string_pointer(conf, "section1", "key1", &s_val); + conf_assign_integer_pointer(conf, "section1", "key2", &i_val); + conf_assign_boolean_pointer(conf, "section1", "key3", &b_val); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_set_string(conf, "section1", "key1", "default"); + assert(ret == 0); + + ret = conf_set_string(conf, "section1", "key1", "foobar"); + assert(ret == EINVAL); + + ret = conf_set_integer(conf, "section1", "key2", 10); + assert(ret == 0); + + ret = conf_set_integer(conf, "section1", "key2", 20); + assert(ret == EINVAL); + + ret = conf_set_boolean(conf, "section1", "key3", true); + assert(ret == 0); + + ret = conf_set_boolean(conf, "section1", "key3", false); + assert(ret == EINVAL); + + assert(strcmp(s_val, "default") == 0); + assert(i_val == 10); + assert(b_val == true); + + ret = conf_get_string(conf, "section1", "key2", &s2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_integer(conf, "section1", "key3", &i2_val, NULL); + assert(ret == EINVAL); + + ret = conf_get_boolean(conf, "section1", "key1", &b2_val, NULL); + assert(ret == EINVAL); + + talloc_free(mem_ctx); +} + +static bool test8_validate(struct conf_context *conf, + const char *section, + enum conf_update_mode mode) +{ + return false; +} + +static void test8(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", test8_validate); + status = conf_valid(conf); + assert(status == true); + + conf_define_string(conf, "section1", "key1", "default", NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, true); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test9(const char *filename, bool ignore_unknown) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct conf_context *conf; + int ret; + bool status; + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + conf_set_boolean(conf, "section1", "key3", false); + + ret = conf_load(conf, filename, ignore_unknown); + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +static void test11(const char *filename) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char reload[PATH_MAX]; + struct conf_context *conf; + int ret; + bool status; + + ret = snprintf(reload, sizeof(reload), "%s.reload", filename); + assert((size_t)ret < sizeof(reload)); + + ret = conf_init(mem_ctx, &conf); + assert(ret == 0); + assert(conf != NULL); + + conf_define_section(conf, "section1", NULL); + + conf_define_string(conf, "section1", "key1", "value1", NULL); + conf_define_integer(conf, "section1", "key2", 10, NULL); + conf_define_boolean(conf, "section1", "key3", true, NULL); + + status = conf_valid(conf); + assert(status == true); + + ret = conf_load(conf, filename, false); + assert(ret == 0); + + ret = rename(reload, filename); + assert(ret == 0); + + ret = conf_reload(conf); + assert(ret == 0); + + conf_dump(conf, stdout); + + talloc_free(mem_ctx); + exit(ret); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]); + exit(1); + } + + num = atoi(argv[1]); + if (num > 7 && argc != 3) { + fprintf(stderr, "Usage: %s <testnum> [<config>]\n", argv[0]); + exit(1); + } + + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + case 5: + test5(); + break; + + case 6: + test6(); + break; + + case 7: + test7(); + break; + + case 8: + test8(argv[2]); + break; + + case 9: + test9(argv[2], true); + break; + + case 10: + test9(argv[2], false); + break; + + case 11: + test11(argv[2]); + break; + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_io_test.c b/ctdb/tests/src/ctdb_io_test.c new file mode 100644 index 0000000..d8f2216 --- /dev/null +++ b/ctdb/tests/src/ctdb_io_test.c @@ -0,0 +1,356 @@ +/* + ctdb_io tests + + Copyright (C) Christof Schmitt 2019 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "common/ctdb_io.c" + +void ctdb_set_error(struct ctdb_context *ctdb, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + assert(false); +} + +static void test_setup(ctdb_queue_cb_fn_t cb, + int *pfd, + struct ctdb_context **pctdb, + struct ctdb_queue **pqueue) +{ + int pipefd[2], ret; + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + + ret = pipe(pipefd); + assert(ret == 0); + + ctdb = talloc_zero(NULL, struct ctdb_context); + assert(ctdb != NULL); + + ctdb->ev = tevent_context_init(NULL); + + queue = ctdb_queue_setup(ctdb, ctdb, pipefd[0], 0, cb, + NULL, "test queue"); + assert(queue != NULL); + + *pctdb = ctdb; + *pfd = pipefd[1]; + if (pqueue != NULL) { + *pqueue = queue; + } +} + +static const size_t test1_req_len = 8; +static const char *test1_req = "abcdefgh"; + +static void test1_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test1_req_len); + + assert(length == sizeof(uint32_t) + test1_req_len); + assert(memcmp(data + sizeof(len), test1_req, test1_req_len) == 0); +} + +static void test1(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + uint32_t pkt_size; + + test_setup(test1_callback, &fd, &ctdb, NULL); + + pkt_size = sizeof(uint32_t) + test1_req_len; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, test1_req, test1_req_len); + assert(ret != -1 && (size_t)ret == test1_req_len); + + tevent_loop_once(ctdb->ev); + + TALLOC_FREE(ctdb); +} + +static const size_t test2_req_len[] = { 900, 24, 600 }; + +static int test2_cb_num = 0; + +static void test2_callback(uint8_t *data, size_t length, void *private_data) +{ + uint32_t len; + + len = *(uint32_t *)data; + assert(len == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + assert(length == sizeof(uint32_t) + test2_req_len[test2_cb_num]); + + test2_cb_num++; +} + +static void test2(void) +{ + struct ctdb_context *ctdb; + int fd; + ssize_t ret; + size_t i; + uint32_t pkt_size; + char req[1024] = { 0 }; + + for (i = 0; i < sizeof(req); i++) { + req[i] = i % CHAR_MAX; + } + + test_setup(test2_callback, &fd, &ctdb, NULL); + + /* + * request 0 + */ + + pkt_size = sizeof(uint32_t) + test2_req_len[0]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[0]); + assert(ret != -1 && (size_t)ret == test2_req_len[0]); + + /* + * request 1 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[1]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + /* + * Omit the last byte to avoid buffer processing. + */ + ret = write(fd, req, test2_req_len[1] - 1); + assert(ret != -1 && (size_t)ret == test2_req_len[1] - 1); + + tevent_loop_once(ctdb->ev); + + /* + * Write the missing byte now. + */ + ret = write(fd, &req[test2_req_len[1] - 1], 1); + assert(ret != -1 && (size_t)ret == 1); + + /* + * request 2 + */ + pkt_size = sizeof(uint32_t) + test2_req_len[2]; + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, req, test2_req_len[2]); + assert(ret != -1 && (size_t)ret == test2_req_len[2]); + + tevent_loop_once(ctdb->ev); + tevent_loop_once(ctdb->ev); + + assert(test2_cb_num == 2); + + TALLOC_FREE(ctdb); +} + +static void test_cb(uint8_t *data, size_t length, void *private_data) +{ + /* dummy handler, not verifying anything */ + TALLOC_FREE(data); +} + +static void test3(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + request = talloc_zero_size(queue, queue->buffer_size); + + /* + * calculate a request length which will fit into the buffer + * but not twice. Because we need to write the size integer + * as well (4-bytes) we're guaranteed that no 2 packets will fit. + */ + req_len = queue->buffer_size >> 1; + + /* writing first packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len); + assert(ret != -1 && (size_t)ret == req_len); + + /* writing second, incomplete packet */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, req_len >> 1); + assert(ret != -1 && (size_t)ret == req_len >> 1); + + /* process...only 1st packet can be processed */ + tevent_loop_once(ctdb->ev); + + /* we should see a progressed offset of req_len + sizeof(pkt_size) */ + assert(queue->buffer.offset == req_len + sizeof(pkt_size)); + + /* writing another few bytes of the still incomplete packet */ + ret = write(fd, request, (req_len >> 1) - 1); + assert(ret != -1 && (size_t)ret == (req_len >> 1) - 1); + + /* + * the packet is still incomplete and connot be processed + * but the packet data had to be moved in the buffer in order + * to fetch the new 199 bytes -> offset must be 0 now. + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + assert(queue->buffer.offset == 0); + + TALLOC_FREE(ctdb); +} + +static void test4(void) +{ + struct ctdb_context *ctdb; + struct ctdb_queue *queue; + uint32_t pkt_size; + char *request; + size_t req_len, half_buf_size; + int fd; + ssize_t ret; + + test_setup(test_cb, &fd, &ctdb, &queue); + + req_len = queue->buffer_size << 1; /* double the buffer size */ + request = talloc_zero_size(queue, req_len); + + /* writing first part of packet exceeding standard buffer size */ + pkt_size = sizeof(uint32_t) + req_len; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + half_buf_size = queue->buffer_size >> 1; + + ret = write(fd, request, req_len - half_buf_size); + assert(ret != -1 && (size_t)ret == req_len - half_buf_size); + + /* + * process... + * this needs to be done to have things changed + */ + tevent_loop_once(ctdb->ev); + /* + * needs to be called twice as an initial incomplete packet + * does not trigger a schedule_immediate + */ + tevent_loop_once(ctdb->ev); + + /* the buffer should be resized to packet size now */ + assert(queue->buffer.size == pkt_size); + + /* writing remaining data */ + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* + * the buffer was increased beyond its standard size. + * once packet got processed, the buffer has to be free'd + * and will be re-allocated with standard size on new request arrival. + */ + + assert(queue->buffer.size == 0); + + /* writing new packet to verify standard buffer size */ + pkt_size = sizeof(uint32_t) + half_buf_size; + + ret = write(fd, &pkt_size, sizeof(pkt_size)); + assert(ret != -1 && (size_t)ret == sizeof(pkt_size)); + + ret = write(fd, request, half_buf_size); + assert(ret != -1 && (size_t)ret == half_buf_size); + + /* process... */ + tevent_loop_once(ctdb->ev); + + /* back to standard buffer size */ + assert(queue->buffer.size == queue->buffer_size); + + TALLOC_FREE(ctdb); +} + +int main(int argc, const char **argv) +{ + int num; + + if (argc != 2) { + fprintf(stderr, "%s <testnum>\n", argv[0]); + exit(1); + } + + + num = atoi(argv[1]); + switch (num) { + case 1: + test1(); + break; + + case 2: + test2(); + break; + + case 3: + test3(); + break; + + case 4: + test4(); + break; + + default: + fprintf(stderr, "Unknown test number %s\n", argv[1]); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_packet_parse.c b/ctdb/tests/src/ctdb_packet_parse.c new file mode 100644 index 0000000..0b99b34 --- /dev/null +++ b/ctdb/tests/src/ctdb_packet_parse.c @@ -0,0 +1,136 @@ +/* + CTDB protocol parser + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/locale.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" + +static TDB_DATA strace_parser(char *buf, TALLOC_CTX *mem_ctx) +{ + TDB_DATA data; + size_t i = 0, j = 0; + + data.dptr = talloc_size(mem_ctx, strlen(buf)); + if (data.dptr == NULL) { + return tdb_null; + } + + while (i < strlen(buf)) { + if (buf[i] == '\\') { + /* first char after '\' is a digit or other escape */ + if (isdigit(buf[i+1])) { + char tmp[4] = { '\0', '\0', '\0', '\0' }; + + tmp[0] = buf[i+1]; + if (isdigit(buf[i+2])) { + tmp[1] = buf[i+2]; + if (isdigit(buf[i+3])) { + tmp[2] = buf[i+3]; + i += 4; + } else { + i += 3; + } + } else { + i += 2; + } + data.dptr[j] = strtol(tmp, NULL, 8); + } else if (buf[i+1] == 'a') { + data.dptr[j] = 7; + i += 2; + } else if (buf[i+1] == 'b') { + data.dptr[j] = 8; + i += 2; + } else if (buf[i+1] == 't') { + data.dptr[j] = 9; + i += 2; + } else if (buf[i+1] == 'n') { + data.dptr[j] = 10; + i += 2; + } else if (buf[i+1] == 'v') { + data.dptr[j] = 11; + i += 2; + } else if (buf[i+1] == 'f') { + data.dptr[j] = 12; + i += 2; + } else if (buf[i+1] == 'r') { + data.dptr[j] = 13; + i += 2; + } else { + fprintf(stderr, + "Unknown escape \\%c\n", + buf[i+1]); + data.dptr[j] = 0; + } + + j += 1; + } else if (buf[i] == '\n') { + i += 1; + } else if (buf[i] == '\0') { + break; + } else { + data.dptr[j] = buf[i]; + i += 1; + j += 1; + } + } + + data.dsize = j; + + return data; +} + +int main(int argc, char *argv[]) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char line[1024]; + char *ptr; + TDB_DATA (*parser)(char *, TALLOC_CTX *); + + if (argc != 2) { + fprintf(stderr, "Usage: %s strace\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "strace") == 0) { + parser = strace_parser; + } else { + fprintf(stderr, "Unknown input format - %s\n", argv[1]); + exit(1); + } + + while ((ptr = fgets(line, sizeof(line), stdin)) != NULL) { + TDB_DATA data; + + data = parser(ptr, mem_ctx); + if (data.dptr == NULL) { + continue; + } + + ctdb_packet_print(data.dptr, data.dsize, stdout); + TALLOC_FREE(data.dptr); + } + + return 0; +} diff --git a/ctdb/tests/src/ctdb_takeover_tests.c b/ctdb/tests/src/ctdb_takeover_tests.c new file mode 100644 index 0000000..cc6b441 --- /dev/null +++ b/ctdb/tests/src/ctdb_takeover_tests.c @@ -0,0 +1,278 @@ +/* + Tests for ctdb_takeover.c + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" +#include "common/system.h" + +#include "server/ipalloc.h" + +#include "ipalloc_read_known_ips.h" + +static void print_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct public_ip_list * ips) +{ + while (ips) { + printf("%s %d\n", + ctdb_sock_addr_to_string(mem_ctx, &(ips->addr), false), + ips->pnn); + ips = ips->next; + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable); +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes); + +static void read_ctdb_public_ip_info(TALLOC_CTX *ctx, + int numnodes, + bool multi, + struct ctdb_public_ip_list ** known, + struct ctdb_public_ip_list ** avail) +{ + int n; + enum ctdb_runstate *runstate; + + *known = ipalloc_read_known_ips(ctx, numnodes, multi); + assert(*known != NULL); + + *avail = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + assert(*avail != NULL); + + runstate = get_runstate(ctx, numnodes); + for (n = 0; n < numnodes; n++) { + if (runstate[n] == CTDB_RUNSTATE_RUNNING) { + (*avail)[n] = (*known)[n]; + } + } +} + +static uint32_t *get_tunable_values(TALLOC_CTX *tmp_ctx, + int numnodes, + const char *tunable) +{ + int i; + char *tok; + uint32_t *tvals = talloc_zero_array(tmp_ctx, uint32_t, numnodes); + char *t = getenv(tunable); + + if (t) { + if (strcmp(t, "1") == 0) { + for (i=0; i<numnodes; i++) { + tvals[i] = 1; + } + } else { + tok = strtok(t, ","); + i = 0; + while (tok != NULL) { + tvals[i] = + (uint32_t) strtol(tok, NULL, 0); + i++; + tok = strtok(NULL, ","); + } + if (i != numnodes) { + fprintf(stderr, "ERROR: Wrong number of values in %s\n", tunable); + exit(1); + } + } + } + + return tvals; +} + +static enum ctdb_runstate *get_runstate(TALLOC_CTX *tmp_ctx, + int numnodes) +{ + int i; + uint32_t *tvals; + enum ctdb_runstate *runstate = + talloc_zero_array(tmp_ctx, enum ctdb_runstate, numnodes); + char *t = getenv("CTDB_TEST_RUNSTATE"); + + if (t == NULL) { + for (i=0; i<numnodes; i++) { + runstate[i] = CTDB_RUNSTATE_RUNNING; + } + } else { + tvals = get_tunable_values(tmp_ctx, numnodes, "CTDB_TEST_RUNSTATE"); + for (i=0; i<numnodes; i++) { + runstate[i] = (enum ctdb_runstate) tvals[i]; + } + talloc_free(tvals); + } + + return runstate; +} + +/* Fake up enough CTDB state to be able to run the IP allocation + * algorithm. Usually this sets up some standard state, sets the node + * states from the command-line and reads the current IP layout from + * stdin. + * + * However, if read_ips_for_multiple_nodes is true then each node's + * idea of the IP layout is read separately from stdin. In this mode + * is doesn't make much sense to use read_ctdb_public_ip_info's + * optional ALLOWED_PNN,... list in the input, since each node is + * being handled separately anyway. IPs for each node are separated + * by a blank line. This mode is for testing weird behaviours where + * the IP layouts differs across nodes and we want to improve + * create_merged_ip_list(), so should only be used in tests of + * ipalloc(). Yes, it is a hack... :-) + */ +static void ctdb_test_init(TALLOC_CTX *mem_ctx, + const char nodestates[], + struct ipalloc_state **ipalloc_state, + bool read_ips_for_multiple_nodes) +{ + struct ctdb_public_ip_list *known; + struct ctdb_public_ip_list *avail; + char *tok, *ns; + const char *t; + struct ctdb_node_map *nodemap; + uint32_t noiptakeover; + ctdb_sock_addr sa_zero = { .ip = { 0 } }; + enum ipalloc_algorithm algorithm; + uint32_t n; + + /* Avoid that const */ + ns = talloc_strdup(mem_ctx, nodestates); + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + assert(nodemap != NULL); + nodemap->num = 0; + tok = strtok(ns, ","); + while (tok != NULL) { + n = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, n+1); + nodemap->node[n].pnn = n; + nodemap->node[n].flags = (uint32_t) strtol(tok, NULL, 0); + nodemap->node[n].addr = sa_zero; + nodemap->num++; + tok = strtok(NULL, ","); + } + + algorithm = IPALLOC_LCP2; + if ((t = getenv("CTDB_IP_ALGORITHM"))) { + if (strcmp(t, "lcp2") == 0) { + algorithm = IPALLOC_LCP2; + } else if (strcmp(t, "nondet") == 0) { + algorithm = IPALLOC_NONDETERMINISTIC; + } else if (strcmp(t, "det") == 0) { + algorithm = IPALLOC_DETERMINISTIC; + } else { + DEBUG(DEBUG_ERR, + ("ERROR: unknown IP algorithm %s\n", t)); + exit(1); + } + } + + t = getenv("CTDB_SET_NoIPTakeover"); + if (t != NULL) { + noiptakeover = (uint32_t) strtol(t, NULL, 0); + } else { + noiptakeover = 0; + } + + *ipalloc_state = ipalloc_state_init(mem_ctx, nodemap->num, + algorithm, + (noiptakeover != 0), + false, + NULL); + assert(*ipalloc_state != NULL); + + read_ctdb_public_ip_info(mem_ctx, nodemap->num, + read_ips_for_multiple_nodes, + &known, &avail); + + /* Drop available IPs for INACTIVE/DISABLED nodes */ + for (n = 0; n < nodemap->num; n++) { + uint32_t flags = nodemap->node[n].flags; + if ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0) { + avail[n].num = 0; + } + } + + ipalloc_set_public_ips(*ipalloc_state, known, avail); +} + +/* IP layout is read from stdin. See comment for ctdb_test_init() for + * explanation of read_ips_for_multiple_nodes. + */ +static void ctdb_test_ipalloc(const char nodestates[], + bool read_ips_for_multiple_nodes) +{ + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + struct ipalloc_state *ipalloc_state; + + ctdb_test_init(tmp_ctx, nodestates, &ipalloc_state, + read_ips_for_multiple_nodes); + + print_ctdb_public_ip_list(tmp_ctx, ipalloc(ipalloc_state)); + + talloc_free(tmp_ctx); +} + +static void usage(void) +{ + fprintf(stderr, "usage: ctdb_takeover_tests <op>\n"); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int loglevel; + const char *debuglevelstr = getenv("CTDB_TEST_LOGLEVEL"); + + setup_logging("ctdb_takeover_tests", DEBUG_STDERR); + + if (! debug_level_parse(debuglevelstr, &loglevel)) { + loglevel = DEBUG_DEBUG; + } + debuglevel_set(loglevel); + + if (argc < 2) { + usage(); + } + + if (argc == 3 && + strcmp(argv[1], "ipalloc") == 0) { + ctdb_test_ipalloc(argv[2], false); + } else if (argc == 4 && + strcmp(argv[1], "ipalloc") == 0 && + strcmp(argv[3], "multi") == 0) { + ctdb_test_ipalloc(argv[2], true); + } else { + usage(); + } + + return 0; +} diff --git a/ctdb/tests/src/db_hash_test.c b/ctdb/tests/src/db_hash_test.c new file mode 100644 index 0000000..31aa501 --- /dev/null +++ b/ctdb/tests/src/db_hash_test.c @@ -0,0 +1,138 @@ +/* + db_hash tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" + +static int record_parser(uint8_t *keybuf, size_t keylen, + uint8_t *databuf, size_t datalen, + void *private_data) +{ + int *count = (int *)private_data; + + (*count) += 1; + return 0; +} + +static void do_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + uint8_t key[] = "This is a long key"; + uint8_t value[] = "This is a long value"; + int ret; + int count = 0; + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EINVAL); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_fetch(dh, key, sizeof(key), NULL, NULL); + assert(ret == EINVAL); + + ret = db_hash_fetch(dh, key, sizeof(key), record_parser, &count); + assert(ret == 0); + assert(count == 1); + + ret = db_hash_insert(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == EEXIST); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_exists(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_delete(dh, key, sizeof(key)); + assert(ret == ENOENT); + + ret = db_hash_add(dh, key, sizeof(key), key, sizeof(key)); + assert(ret == 0); + + ret = db_hash_add(dh, key, sizeof(key), value, sizeof(value)); + assert(ret == 0); + + talloc_free(dh); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void do_traverse_test(enum db_hash_type type) +{ + struct db_hash_context *dh = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + char key[16] = "keyXXXX"; + char value[] = "This is some test value"; + int count, ret, i; + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == EINVAL); + + ret = db_hash_init(mem_ctx, "foobar", 1024, type, &dh); + assert(ret == 0); + + for (i=0; i<2000; i++) { + sprintf(key, "key%04d", i); + ret = db_hash_insert(dh, (uint8_t *)key, sizeof(key), + (uint8_t *)value, sizeof(value)); + assert(ret == 0); + } + + ret = db_hash_traverse(dh, NULL, NULL, &count); + assert(ret == 0); + assert(count == 2000); + + ret = db_hash_traverse(dh, record_parser, &count, NULL); + assert(ret == 0); + assert(count == 4000); + + talloc_free(dh); + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test(DB_HASH_SIMPLE); + do_test(DB_HASH_COMPLEX); + do_traverse_test(DB_HASH_SIMPLE); + do_traverse_test(DB_HASH_COMPLEX); + return 0; +} diff --git a/ctdb/tests/src/db_test_tool.c b/ctdb/tests/src/db_test_tool.c new file mode 100644 index 0000000..7ac6b03 --- /dev/null +++ b/ctdb/tests/src/db_test_tool.c @@ -0,0 +1,792 @@ +/* + CTDB DB test tool + + Copyright (C) Martin Schwenke 2019 + + Parts based on ctdb.c, event_tool.c: + + Copyright (C) Amitay Isaacs 2015, 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/time.h" + +#include <ctype.h> +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/sys_rw.h" +#include "lib/util/util.h" +#include "lib/util/smb_strtox.h" +#include "lib/tdb_wrap/tdb_wrap.h" + +#include "common/cmdline.h" +#include "common/logging.h" +#include "common/path.h" +#include "common/event_script.h" +#include "common/system_socket.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" + +#include "client/client.h" +#include "client/client_sync.h" + +struct tdb_context *client_db_tdb(struct ctdb_db_context *db); + +#define TIMEOUT() tevent_timeval_current_ofs(ctx->timelimit, 0) + +struct db_test_tool_context { + struct cmdline_context *cmdline; + struct tevent_context *ev; + struct ctdb_client_context *client; + uint32_t destnode; + uint32_t timelimit; +}; + +/* + * If this is ever consolodated into a larger test tool then these + * forward declarations can be moved to an include file + */ +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result); +int db_test_tool_run(struct db_test_tool_context *ctx, int *result); + +static int db_test_get_lmaster(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_vnn_map *vnnmap; + TDB_DATA key; + uint32_t idx, lmaster; + unsigned int hash; + int ret = 0; + + if (argc != 1) { + cmdline_usage(ctx->cmdline, "get-lmaster"); + return 1; + } + + ret = ctdb_ctrl_getvnnmap(mem_ctx, + ctx->ev, + ctx->client, + CTDB_CURRENT_NODE, + TIMEOUT(), + &vnnmap); + if (ret != 0) { + D_ERR("Control GETVNN_MAP failed, ret=%d\n", ret); + return ret; + } + + key.dsize = strlen(argv[0]); + key.dptr = (uint8_t *)discard_const(argv[0]); + + hash = tdb_jenkins_hash(&key); + idx = hash % vnnmap->size; + lmaster = vnnmap->map[idx]; + + printf("%"PRId32"\n", lmaster); + + return 0; +} + +static struct ctdb_dbid *db_find(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + struct ctdb_dbid_map *dbmap, + const char *db_name) +{ + struct ctdb_dbid *db = NULL; + const char *name; + unsigned int i; + int ret; + + for (i=0; i<dbmap->num; i++) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + dbmap->dbs[i].db_id, + &name); + if (ret != 0) { + return NULL; + } + + if (strcmp(db_name, name) == 0) { + talloc_free(discard_const(name)); + db = &dbmap->dbs[i]; + break; + } + } + + return db; +} + +static bool db_exists(TALLOC_CTX *mem_ctx, + struct db_test_tool_context *ctx, + const char *db_arg, + uint32_t *db_id, + const char **db_name, + uint8_t *db_flags) +{ + struct ctdb_dbid_map *dbmap; + struct ctdb_dbid *db = NULL; + uint32_t id = 0; + const char *name = NULL; + unsigned int i; + int ret = 0; + + ret = ctdb_ctrl_get_dbmap(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &dbmap); + if (ret != 0) { + return false; + } + + if (strncmp(db_arg, "0x", 2) == 0) { + id = smb_strtoul(db_arg, NULL, 0, &ret, SMB_STR_STANDARD); + if (ret != 0) { + return false; + } + for (i=0; i<dbmap->num; i++) { + if (id == dbmap->dbs[i].db_id) { + db = &dbmap->dbs[i]; + break; + } + } + } else { + name = db_arg; + db = db_find(mem_ctx, ctx, dbmap, name); + } + + if (db == NULL) { + fprintf(stderr, "No database matching '%s' found\n", db_arg); + return false; + } + + if (name == NULL) { + ret = ctdb_ctrl_get_dbname(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + id, + &name); + if (ret != 0) { + return false; + } + } + + if (db_id != NULL) { + *db_id = db->db_id; + } + if (db_name != NULL) { + *db_name = talloc_strdup(mem_ctx, name); + } + if (db_flags != NULL) { + *db_flags = db->flags; + } + return true; +} + +static int db_test_fetch_local_delete(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db = NULL; + struct ctdb_record_handle *h = NULL; + struct tdb_context *tdb; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t len; + uint8_t *buf; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "fetch-local-delete"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = ctdb_fetch_lock(mem_ctx, + ctx->ev, + ctx->client, + db, + key, + false, + &h, + &header, + NULL); + if (ret != 0) { + D_ERR("Failed to fetch record for key %s\n", argv[1]); + goto done; + } + + len = ctdb_ltdb_header_len(&header); + buf = talloc_size(mem_ctx, len); + if (buf == NULL) { + D_ERR("Memory allocation error\n"); + ret = ENOMEM; + goto done; + } + + ctdb_ltdb_header_push(&header, buf, &np); + + data.dsize = np; + data.dptr = buf; + + tdb = client_db_tdb(db); + + ret = tdb_store(tdb, key, data, TDB_REPLACE); + TALLOC_FREE(buf); + if (ret != 0) { + D_ERR("fetch_lock delete: %s tdb_store failed, %s\n", + db_name, + tdb_errorstr(tdb)); + } + +done: + TALLOC_FREE(h); + + return ret; +} + +#define ISASCII(x) (isprint(x) && ! strchr("\"\\", (x))) + +static void dump(const char *name, uint8_t *dptr, size_t dsize) +{ + size_t i; + + fprintf(stdout, "%s(%zu) = \"", name, dsize); + for (i = 0; i < dsize; i++) { + if (ISASCII(dptr[i])) { + fprintf(stdout, "%c", dptr[i]); + } else { + fprintf(stdout, "\\%02X", dptr[i]); + } + } + fprintf(stdout, "\"\n"); +} + +static void dump_ltdb_header(struct ctdb_ltdb_header *header) +{ + fprintf(stdout, "dmaster: %u\n", header->dmaster); + fprintf(stdout, "rsn: %" PRIu64 "\n", header->rsn); + fprintf(stdout, "flags: 0x%08x", header->flags); + if (header->flags & CTDB_REC_FLAG_MIGRATED_WITH_DATA) { + fprintf(stdout, " MIGRATED_WITH_DATA"); + } + if (header->flags & CTDB_REC_FLAG_VACUUM_MIGRATED) { + fprintf(stdout, " VACUUM_MIGRATED"); + } + if (header->flags & CTDB_REC_FLAG_AUTOMATIC) { + fprintf(stdout, " AUTOMATIC"); + } + if (header->flags & CTDB_REC_RO_HAVE_DELEGATIONS) { + fprintf(stdout, " RO_HAVE_DELEGATIONS"); + } + if (header->flags & CTDB_REC_RO_HAVE_READONLY) { + fprintf(stdout, " RO_HAVE_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKING_READONLY) { + fprintf(stdout, " RO_REVOKING_READONLY"); + } + if (header->flags & CTDB_REC_RO_REVOKE_COMPLETE) { + fprintf(stdout, " RO_REVOKE_COMPLETE"); + } + fprintf(stdout, "\n"); + +} + +static int db_test_local_lock(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + const char *db_name; + int pipefd[2]; + TDB_DATA key; + uint32_t db_id; + uint8_t db_flags; + pid_t pid; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-lock"); + return 1; + } + + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + D_ERR("DB %s not attached\n", db_name); + return 1; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return 1; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return 1; + } + + ret = pipe(pipefd); + if (ret != 0) { + DBG_ERR("Failed to create pipe\n"); + return 1; + } + + pid = fork(); + if (pid < 0) { + DBG_ERR("Failed to fork()\n"); + return 1; + } + + if (pid != 0) { + ssize_t nread; + int status; + + close(pipefd[1]); + + nread = sys_read(pipefd[0], &status, sizeof(status)); + if (nread < 0 || (size_t)nread != sizeof(status)) { + status = EINVAL; + } + + if (status == 0) { + printf("OK %d\n", pid); + } else { + printf("FAIL %d\n", status); + } + fflush(stdout); + + return status; + } + + close(pipefd[0]); + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + ret = tdb_chainlock(client_db_tdb(db), key); + if (ret != 0) { + D_ERR("Failed to lock chain for key %s\n", argv[1]); + goto fail; + } + + sys_write(pipefd[1], &ret, sizeof(ret)); + + fclose(stdin); + fclose(stdout); + fclose(stderr); + + /* Hold the lock- the caller should SIGTERM to release the lock */ + sleep(120); + exit(1); + +fail: + sys_write(pipefd[1], &ret, sizeof(ret)); + return ret; +} + +static int db_test_local_read(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_context *db; + struct ctdb_ltdb_header header; + const char *db_name; + TDB_DATA key, data; + uint32_t db_id; + uint8_t db_flags; + size_t np; + int ret; + + if (argc != 2) { + cmdline_usage(ctx->cmdline, "local-read"); + return 1; + } + + if (! db_exists(mem_ctx, ctx, argv[0], &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + ret = ctdb_attach(ctx->ev, + ctx->client, + TIMEOUT(), + db_name, + db_flags, + &db); + if (ret != 0) { + D_ERR("Failed to attach to DB %s\n", db_name); + return ret; + } + + key.dsize = strlen(argv[1]); + key.dptr = (uint8_t *)discard_const(argv[1]); + + data = tdb_fetch(client_db_tdb(db), key); + + if (data.dptr == NULL) { + D_ERR("No record for key %s\n", argv[1]); + return 1; + } + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + D_ERR("Invalid record for key %s\n", argv[1]); + free(data.dptr); + return 1; + } + + ret = ctdb_ltdb_header_pull(data.dptr, data.dsize, &header, &np); + if (ret != 0) { + D_ERR("Failed to parse header from data\n"); + free(data.dptr); + return 1; + } + + dump_ltdb_header(&header); + dump("data", data.dptr + np, data.dsize - np); + + free(data.dptr); + + return 0; +} + +static int db_test_vacuum(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + void *private_data) +{ + struct db_test_tool_context *ctx = talloc_get_type_abort( + private_data, struct db_test_tool_context); + struct ctdb_db_vacuum db_vacuum; + struct ctdb_req_control request; + struct ctdb_reply_control *reply; + const char *db_arg; + uint32_t db_id; + const char *db_name; + uint8_t db_flags; + int ret = 0; + + if (argc != 1 && argc != 2) { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + + db_arg = argv[0]; + + db_vacuum.full_vacuum_run = false; + if (argc == 2) { + if (strcmp(argv[1], "full") == 0) { + db_vacuum.full_vacuum_run = true; + } else { + cmdline_usage(ctx->cmdline, "vacuum"); + return 1; + } + } + + if (! db_exists(mem_ctx, ctx, db_arg, &db_id, &db_name, &db_flags)) { + return ENOENT; + } + + if (db_flags & (CTDB_DB_FLAGS_PERSISTENT | CTDB_DB_FLAGS_REPLICATED)) { + D_ERR("DB %s is not a volatile database\n", db_name); + return EINVAL; + } + + db_vacuum.db_id = db_id; + + ctdb_req_control_db_vacuum(&request, &db_vacuum); + + ret = ctdb_client_control(mem_ctx, + ctx->ev, + ctx->client, + ctx->destnode, + TIMEOUT(), + &request, + &reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed to node %u, ret=%d\n", + ctx->destnode, + ret); + return ret; + } + + + ret = ctdb_reply_control_db_vacuum(reply); + if (ret != 0) { + D_ERR("Control DB_VACUUM failed, ret=%d\n", ret); + return ret; + } + + return 0; +} + +struct cmdline_command db_test_commands[] = { + { + .name = "get-lmaster", + .fn = db_test_get_lmaster, + .msg_help = "Print lmaster for key", + .msg_args = "<key>" + }, + { + .name = "fetch-local-delete", + .fn = db_test_fetch_local_delete, + .msg_help = "Fetch record and delete from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-lock", + .fn = db_test_local_lock, + .msg_help = "Lock a record in a local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "local-read", + .fn = db_test_local_read, + .msg_help = "Read a record from local database", + .msg_args = "<dbname|dbid> <key>" + }, + { + .name = "vacuum", + .fn = db_test_vacuum, + .msg_help = "Vacuum a database", + .msg_args = "<dbname|dbid> [full]" + }, + CMDLINE_TABLEEND +}; + +int db_test_tool_init(TALLOC_CTX *mem_ctx, + const char *prog, + struct poptOption *options, + int argc, + const char **argv, + bool parse_options, + struct db_test_tool_context **result) +{ + struct db_test_tool_context *ctx; + int ret; + + ctx = talloc_zero(mem_ctx, struct db_test_tool_context); + if (ctx == NULL) { + D_ERR("Memory allocation error\n"); + return ENOMEM; + } + + ret = cmdline_init(mem_ctx, + prog, + options, + NULL, + db_test_commands, + &ctx->cmdline); + if (ret != 0) { + D_ERR("Failed to initialize cmdline, ret=%d\n", ret); + talloc_free(ctx); + return ret; + } + + ret = cmdline_parse(ctx->cmdline, argc, argv, parse_options); + if (ret != 0) { + cmdline_usage(ctx->cmdline, NULL); + talloc_free(ctx); + return ret; + } + + *result = ctx; + return 0; +} + +int db_test_tool_run(struct db_test_tool_context *ctx, int *result) +{ + char *ctdb_socket; + int ret; + + ctx->ev = tevent_context_init(ctx); + if (ctx->ev == NULL) { + D_ERR("Failed to initialize tevent\n"); + return ENOMEM; + } + + ctdb_socket = path_socket(ctx, "ctdbd"); + if (ctdb_socket == NULL) { + fprintf(stderr, "Memory allocation error\n"); + return ENOMEM; + } + + ret = ctdb_client_init(ctx, ctx->ev, ctdb_socket, &ctx->client); + if (ret != 0) { + D_ERR("Failed to connect to CTDB daemon (%s)\n", ctdb_socket); + return ret; + } + + ret = cmdline_run(ctx->cmdline, ctx, result); + return ret; +} + +#ifdef CTDB_DB_TEST_TOOL + +static struct { + const char *debug; + int destnode; + int timelimit; +} db_test_data = { + .debug = "ERROR", + .destnode = CTDB_CURRENT_NODE, + .timelimit = 60, +}; + +struct poptOption db_test_options[] = { + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &db_test_data.debug, + .val = 0, + .descrip = "debug level", + .argDescrip = "ERROR|WARNING|NOTICE|INFO|DEBUG" + }, + { + .longName = "node", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.destnode, + .val = 0, + .descrip = "node number", + .argDescrip = "NUM" + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &db_test_data.timelimit, + .val = 0, + .descrip = "control time limit", + .argDescrip = "SECONDS" + }, + POPT_TABLEEND +}; + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct db_test_tool_context *ctx; + int ret, result = 0; + int level; + bool ok; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = db_test_tool_init(mem_ctx, + "ctdb-db-test", + db_test_options, + argc, + argv, + true, + &ctx); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + setup_logging("ctdb-db-test", DEBUG_STDERR); + ok = debug_level_parse(db_test_data.debug, &level); + if (!ok) { + level = DEBUG_ERR; + } + debuglevel_set(level); + + ctx->destnode = db_test_data.destnode; + ctx->timelimit = db_test_data.timelimit; + + ret = db_test_tool_run(ctx, &result); + if (ret != 0) { + result = ret; + } + + talloc_free(mem_ctx); + exit(result); +} + +#endif /* CTDB_DB_TEST_TOOL */ diff --git a/ctdb/tests/src/dummy_client.c b/ctdb/tests/src/dummy_client.c new file mode 100644 index 0000000..13e0691 --- /dev/null +++ b/ctdb/tests/src/dummy_client.c @@ -0,0 +1,163 @@ +/* + Dummy CTDB client for testing + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> + +#include "common/logging.h" +#include "common/path.h" + +#include "client/client.h" + +static struct { + const char *sockpath; + const char *debuglevel; + int num_connections; + int timelimit; + const char *srvidstr; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + { "nconn", 'n', POPT_ARG_INT, &options.num_connections, 0, + "number of connections", "" }, + { "timelimit", 't', POPT_ARG_INT, &options.timelimit, 0, + "time limit", "seconds" }, + { "srvid", 'S', POPT_ARG_STRING, &options.srvidstr, 0, + "srvid to register", "srvid" }, + POPT_TABLEEND +}; + +static void dummy_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + bool *done = (bool *)private_data; + + *done = true; +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context **client; + struct ctdb_client_context *last_client; + poptContext pc; + int opt, ret, i; + int log_level; + bool status, done; + + /* Set default options */ + options.sockpath = NULL; + options.debuglevel = "ERR"; + options.num_connections = 1; + options.timelimit = 60; + options.srvidstr = NULL; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + status = debug_level_parse(options.debuglevel, &log_level); + if (! status) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + setup_logging("dummy_client", DEBUG_STDERR); + debuglevel_set(log_level); + + if (options.sockpath == NULL) { + options.sockpath = path_socket(mem_ctx, "ctdbd"); + if (options.sockpath == NULL) { + D_ERR("Memory allocation error\n"); + exit(1); + } + } + + client = talloc_array(mem_ctx, struct ctdb_client_context *, + options.num_connections); + if (client == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + for (i=0; i<options.num_connections; i++) { + ret = ctdb_client_init(client, ev, options.sockpath, + &client[i]); + if (ret != 0) { + D_ERR("Failed to initialize client %d, ret=%d\n", + i, ret); + exit(1); + } + } + + last_client = client[options.num_connections-1]; + + done = false; + if (options.srvidstr != NULL) { + uint64_t srvid; + + srvid = strtoull(options.srvidstr, NULL, 0); + + ret = ctdb_client_set_message_handler(ev, last_client, srvid, + dummy_handler, &done); + if (ret != 0) { + D_ERR("Failed to register srvid, ret=%d\n", ret); + talloc_free(client); + exit(1); + } + + D_INFO("Registered SRVID 0x%"PRIx64"\n", srvid); + } + + ret = ctdb_client_wait_timeout(ev, &done, + tevent_timeval_current_ofs(options.timelimit, 0)); + if (ret != 0 && ret == ETIMEDOUT) { + D_ERR("client_wait_timeout() failed, ret=%d\n", ret); + talloc_free(client); + exit(1); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/tests/src/errcode.c b/ctdb/tests/src/errcode.c new file mode 100644 index 0000000..7343e81 --- /dev/null +++ b/ctdb/tests/src/errcode.c @@ -0,0 +1,189 @@ +/* + Portability layer for error codes + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * These errors are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + * + * Error codes marked obsolete are removed (ENODATA, ENOSR, ENOSTR, ETIME) + */ + +#include "replace.h" + +struct { + const char *label; + int code; +} err_codes[] = { + { "E2BIG", E2BIG }, + + { "EACCES", EACCES }, + { "EADDRINUSE", EADDRINUSE }, + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, + { "EAFNOSUPPORT", EAFNOSUPPORT }, + { "EAGAIN", EAGAIN }, + { "EALREADY", EALREADY }, + + { "EBADF", EBADF }, + { "EBADMSG", EBADMSG }, + { "EBUSY", EBUSY }, + + { "ECANCELED", ECANCELED }, + { "ECHILD", ECHILD }, + { "ECONNABORTED", ECONNABORTED }, + { "ECONNREFUSED", ECONNREFUSED }, + { "ECONNRESET", ECONNRESET }, + + { "EDEADLK", EDEADLK }, + { "EDESTADDRREQ", EDESTADDRREQ }, + { "EDOM", EDOM }, + { "EDQUOT", EDQUOT }, + + { "EEXIST", EEXIST }, + + { "EFAULT", EFAULT }, + { "EFBIG", EFBIG }, + + { "EHOSTUNREACH", EHOSTUNREACH }, + + { "EIDRM", EIDRM }, + { "EILSEQ", EILSEQ }, + { "EINPROGRESS", EINPROGRESS }, + { "EINTR", EINTR }, + { "EINVAL", EINVAL }, + { "EIO", EIO }, + { "EISCONN", EISCONN }, + { "EISDIR", EISDIR }, + + { "ELOOP", ELOOP }, + + { "EMFILE", EMFILE }, + { "EMLINK", EMLINK }, + { "EMSGSIZE", EMSGSIZE }, + { "EMULTIHOP", EMULTIHOP }, + + { "ENAMETOOLONG", ENAMETOOLONG }, + { "ENETDOWN", ENETDOWN }, + { "ENETRESET", ENETRESET }, + { "ENETUNREACH", ENETUNREACH }, + { "ENFILE", ENFILE }, + { "ENOBUFS", ENOBUFS }, + { "ENODEV", ENODEV }, + { "ENOENT", ENOENT }, + { "ENOEXEC", ENOEXEC }, + { "ENOLCK", ENOLCK }, + { "ENOLINK", ENOLINK }, + { "ENOMEM", ENOMEM }, + { "ENOMSG", ENOMSG }, + { "ENOPROTOOPT", ENOPROTOOPT }, + { "ENOSPC", ENOSPC }, + { "ENOSYS", ENOSYS }, + { "ENOTCONN", ENOTCONN }, + { "ENOTDIR", ENOTDIR }, + { "ENOTEMPTY", ENOTEMPTY }, + { "ENOTSOCK", ENOTSOCK }, + { "ENOTSUP", ENOTSUP }, + { "ENOTTY", ENOTTY }, + { "ENXIO", ENXIO }, + + { "EOPNOTSUPP", EOPNOTSUPP }, + { "EOVERFLOW", EOVERFLOW }, + + { "EPERM", EPERM }, + { "EPIPE", EPIPE }, + { "EPROTO", EPROTO }, + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, + { "EPROTOTYPE", EPROTOTYPE }, + + { "ERANGE", ERANGE }, + { "EROFS", EROFS }, + + { "ESPIPE", ESPIPE }, + { "ESRCH", ESRCH }, + { "ESTALE", ESTALE }, + + { "ETIMEDOUT", ETIMEDOUT }, + { "ETXTBSY", ETXTBSY }, + + { "EWOULDBLOCK", EWOULDBLOCK }, + + { "EXDEV", EXDEV }, +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + printf("%s %d\n", err_codes[i].label, err_codes[i].code); + } +} + +static void match_label(const char *str) +{ + int code = -1; + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + if (strcasecmp(err_codes[i].label, str) == 0) { + code = err_codes[i].code; + break; + } + } + + printf("%d\n", code); +} + +static void match_code(int code) +{ + const char *label = "UNKNOWN"; + size_t i; + + for (i=0; i<ARRAY_SIZE(err_codes); i++) { + if (err_codes[i].code == code) { + label = err_codes[i].label; + break; + } + } + + printf("%s\n", label); +} + +int main(int argc, const char **argv) +{ + long int code; + char *endptr; + + if (argc != 2) { + fprintf(stderr, "Usage: %s dump|<errcode>\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/event_script_test.c b/ctdb/tests/src/event_script_test.c new file mode 100644 index 0000000..a78e400 --- /dev/null +++ b/ctdb/tests/src/event_script_test.c @@ -0,0 +1,120 @@ +/* + Low level event script handling tests + + Copyright (C) Martin Schwenke 2018 + + Based on run_event_test.c: + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <popt.h> +#include <talloc.h> + +#include <assert.h> + +#include "common/event_script.c" + +static void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s list <scriptdir>\n", + prog); + fprintf(stderr, + " %s chmod enable <scriptdir> <scriptname>\n", + prog); + fprintf(stderr, + " %s chmod diable <scriptdir> <scriptname>\n", + prog); +} + +static void do_list(TALLOC_CTX *mem_ctx, int argc, const char **argv) +{ + struct event_script_list *script_list = NULL; + unsigned int i; + int ret; + + if (argc != 3) { + usage(argv[0]); + exit(1); + } + + ret = event_script_get_list(mem_ctx, argv[2], &script_list); + if (ret != 0) { + printf("Script list %s failed with result=%d\n", argv[2], ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No scripts found\n"); + return; + } + + for (i=0; i < script_list->num_scripts; i++) { + struct event_script *s = script_list->script[i]; + printf("%s\n", s->name); + } +} + +static void do_chmod(TALLOC_CTX *mem_ctx, + int argc, + const char **argv, + bool enable) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = event_script_chmod(argv[2], argv[3], enable); + + printf("Script %s %s %s completed with result=%d\n", + argv[1], argv[2], argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + if (strcmp(argv[1], "list") == 0) { + do_list(mem_ctx, argc, argv); + } else if (strcmp(argv[1], "enable") == 0) { + do_chmod(mem_ctx, argc, argv, true); + } else if (strcmp(argv[1], "disable") == 0) { + do_chmod(mem_ctx, argc, argv, false); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} diff --git a/ctdb/tests/src/fake_ctdbd.c b/ctdb/tests/src/fake_ctdbd.c new file mode 100644 index 0000000..a04ad85 --- /dev/null +++ b/ctdb/tests/src/fake_ctdbd.c @@ -0,0 +1,4781 @@ +/* + Fake CTDB server for testing + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" +#include "system/time.h" +#include "system/filesys.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> +#include <tdb.h> + +#include "lib/util/dlinklist.h" +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" +#include "lib/util/samba_util.h" +#include "lib/async_req/async_sock.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_api.h" +#include "protocol/protocol_util.h" +#include "protocol/protocol_private.h" + +#include "common/comm.h" +#include "common/logging.h" +#include "common/tunable.h" +#include "common/srvid.h" +#include "common/system.h" + +#include "ipalloc_read_known_ips.h" + + +#define CTDB_PORT 4379 + +/* A fake flag that is only supported by some functions */ +#define NODE_FLAGS_FAKE_TIMEOUT 0x80000000 + +struct node { + ctdb_sock_addr addr; + uint32_t pnn; + uint32_t flags; + uint32_t capabilities; + bool recovery_disabled; + void *recovery_substate; +}; + +struct node_map { + uint32_t num_nodes; + struct node *node; + uint32_t pnn; + uint32_t recmaster; +}; + +struct interface { + const char *name; + bool link_up; + uint32_t references; +}; + +struct interface_map { + int num; + struct interface *iface; +}; + +struct vnn_map { + uint32_t recmode; + uint32_t generation; + uint32_t size; + uint32_t *map; +}; + +struct database { + struct database *prev, *next; + const char *name; + const char *path; + struct tdb_context *tdb; + uint32_t id; + uint8_t flags; + uint64_t seq_num; +}; + +struct database_map { + struct database *db; + const char *dbdir; +}; + +struct fake_control_failure { + struct fake_control_failure *prev, *next; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; +}; + +struct ctdb_client { + struct ctdb_client *prev, *next; + struct ctdbd_context *ctdb; + pid_t pid; + void *state; +}; + +struct ctdbd_context { + struct node_map *node_map; + struct interface_map *iface_map; + struct vnn_map *vnn_map; + struct database_map *db_map; + struct srvid_context *srv; + int num_clients; + struct timeval start_time; + struct timeval recovery_start_time; + struct timeval recovery_end_time; + bool takeover_disabled; + int log_level; + enum ctdb_runstate runstate; + struct ctdb_tunable_list tun_list; + char *reclock; + struct ctdb_public_ip_list *known_ips; + struct fake_control_failure *control_failures; + struct ctdb_client *client_list; +}; + +/* + * Parse routines + */ + +static struct node_map *nodemap_init(TALLOC_CTX *mem_ctx) +{ + struct node_map *node_map; + + node_map = talloc_zero(mem_ctx, struct node_map); + if (node_map == NULL) { + return NULL; + } + + node_map->pnn = CTDB_UNKNOWN_PNN; + node_map->recmaster = CTDB_UNKNOWN_PNN; + + return node_map; +} + +/* Read a nodemap from stdin. Each line looks like: + * <PNN> <FLAGS> [RECMASTER] [CURRENT] [CAPABILITIES] + * EOF or a blank line terminates input. + * + * By default, capablities for each node are + * CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER. These 2 + * capabilities can be faked off by adding, for example, + * -CTDB_CAP_RECMASTER. + */ + +static bool nodemap_parse(struct node_map *node_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t pnn, flags, capabilities; + char *tok, *t; + char *ip; + ctdb_sock_addr saddr; + struct node *node; + int ret; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get PNN */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get IP */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing IP\n", line); + continue; + } + ret = ctdb_sock_addr_from_string(tok, &saddr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&saddr, CTDB_PORT); + ip = talloc_strdup(node_map, tok); + if (ip == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing flags\n", + line); + continue; + } + flags = (uint32_t)strtoul(tok, NULL, 0); + /* Handle deleted nodes */ + if (flags & NODE_FLAGS_DELETED) { + talloc_free(ip); + ip = talloc_strdup(node_map, "0.0.0.0"); + if (ip == NULL) { + goto fail; + } + } + capabilities = CTDB_CAP_RECMASTER|CTDB_CAP_LMASTER; + + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "CURRENT") == 0) { + node_map->pnn = pnn; + } else if (strcmp(tok, "RECMASTER") == 0) { + node_map->recmaster = pnn; + } else if (strcmp(tok, "-CTDB_CAP_RECMASTER") == 0) { + capabilities &= ~CTDB_CAP_RECMASTER; + } else if (strcmp(tok, "-CTDB_CAP_LMASTER") == 0) { + capabilities &= ~CTDB_CAP_LMASTER; + } else if (strcmp(tok, "TIMEOUT") == 0) { + /* This can be done with just a flag + * value but it is probably clearer + * and less error-prone to fake this + * with an explicit token */ + flags |= NODE_FLAGS_FAKE_TIMEOUT; + } + tok = strtok(NULL, " \t"); + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes + 1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + ret = ctdb_sock_addr_from_string(ip, &node->addr, false); + if (ret != 0) { + fprintf(stderr, "bad line (%s) - invalid IP\n", line); + continue; + } + ctdb_sock_addr_set_port(&node->addr, CTDB_PORT); + node->pnn = pnn; + node->flags = flags; + node->capabilities = capabilities; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + if (node_map->num_nodes == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing nodemap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing nodemap failed\n")); + return false; + +} + +/* Append a node to a node map with given address and flags */ +static bool node_map_add(struct ctdb_node_map *nodemap, + const char *nstr, uint32_t flags) +{ + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_node_and_flags *n; + int ret; + + ret = ctdb_sock_addr_from_string(nstr, &addr, false); + if (ret != 0) { + fprintf(stderr, "Invalid IP address %s\n", nstr); + return false; + } + ctdb_sock_addr_set_port(&addr, CTDB_PORT); + + num = nodemap->num; + nodemap->node = talloc_realloc(nodemap, nodemap->node, + struct ctdb_node_and_flags, num+1); + if (nodemap->node == NULL) { + return false; + } + + n = &nodemap->node[num]; + n->addr = addr; + n->pnn = num; + n->flags = flags; + + nodemap->num = num+1; + return true; +} + +/* Read a nodes file into a node map */ +static struct ctdb_node_map *ctdb_read_nodes_file(TALLOC_CTX *mem_ctx, + const char *nlist) +{ + char **lines; + int nlines; + int i; + struct ctdb_node_map *nodemap; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + return NULL; + } + + lines = file_lines_load(nlist, &nlines, 0, mem_ctx); + if (lines == NULL) { + return NULL; + } + + while (nlines > 0 && strcmp(lines[nlines-1], "") == 0) { + nlines--; + } + + for (i=0; i<nlines; i++) { + char *node; + uint32_t flags; + size_t len; + + node = lines[i]; + /* strip leading spaces */ + while((*node == ' ') || (*node == '\t')) { + node++; + } + + len = strlen(node); + + /* strip trailing spaces */ + while ((len > 1) && + ((node[len-1] == ' ') || (node[len-1] == '\t'))) + { + node[len-1] = '\0'; + len--; + } + + if (len == 0) { + continue; + } + if (*node == '#') { + /* A "deleted" node is a node that is + commented out in the nodes file. This is + used instead of removing a line, which + would cause subsequent nodes to change + their PNN. */ + flags = NODE_FLAGS_DELETED; + node = discard_const("0.0.0.0"); + } else { + flags = 0; + } + if (! node_map_add(nodemap, node, flags)) { + talloc_free(lines); + TALLOC_FREE(nodemap); + return NULL; + } + } + + talloc_free(lines); + return nodemap; +} + +static struct ctdb_node_map *read_nodes_file(TALLOC_CTX *mem_ctx, + uint32_t pnn) +{ + struct ctdb_node_map *nodemap; + char nodes_list[PATH_MAX]; + const char *ctdb_base; + int num; + + ctdb_base = getenv("CTDB_BASE"); + if (ctdb_base == NULL) { + D_ERR("CTDB_BASE is not set\n"); + return NULL; + } + + /* read optional node-specific nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), + "%s/nodes.%d", ctdb_base, pnn); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + /* Fake a load failure for an empty nodemap */ + if (nodemap->num == 0) { + talloc_free(nodemap); + + D_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; + } + + return nodemap; + } + + /* read normal nodes file */ + num = snprintf(nodes_list, sizeof(nodes_list), "%s/nodes", ctdb_base); + if (num == sizeof(nodes_list)) { + D_ERR("nodes file path too long\n"); + return NULL; + } + nodemap = ctdb_read_nodes_file(mem_ctx, nodes_list); + if (nodemap != NULL) { + return nodemap; + } + + DBG_ERR("Failed to read nodes file \"%s\"\n", nodes_list); + return NULL; +} + +static struct interface_map *interfaces_init(TALLOC_CTX *mem_ctx) +{ + struct interface_map *iface_map; + + iface_map = talloc_zero(mem_ctx, struct interface_map); + if (iface_map == NULL) { + return NULL; + } + + return iface_map; +} + +/* Read interfaces information. Same format as "ctdb ifaces -Y" + * output: + * :Name:LinkStatus:References: + * :eth2:1:4294967294 + * :eth1:1:4294967292 + */ + +static bool interfaces_parse(struct interface_map *iface_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint16_t link_state; + uint32_t references; + char *tok, *t, *name; + struct interface *iface; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, ":Name:LinkStatus:References:") == 0) { + continue; + } + + /* Leading colon... */ + // tok = strtok(line, ":"); + + /* name */ + tok = strtok(line, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing name\n", line); + continue; + } + name = tok; + + /* link_state */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing link state\n", + line); + continue; + } + link_state = (uint16_t)strtoul(tok, NULL, 0); + + /* references... */ + tok = strtok(NULL, ":"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing references\n", + line); + continue; + } + references = (uint32_t)strtoul(tok, NULL, 0); + + iface_map->iface = talloc_realloc(iface_map, iface_map->iface, + struct interface, + iface_map->num + 1); + if (iface_map->iface == NULL) { + goto fail; + } + + iface = &iface_map->iface[iface_map->num]; + + iface->name = talloc_strdup(iface_map, name); + if (iface->name == NULL) { + goto fail; + } + iface->link_up = link_state; + iface->references = references; + + iface_map->num += 1; + } + + if (iface_map->num == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing interfaces done\n")); + return true; + +fail: + fprintf(stderr, "Parsing interfaces failed\n"); + return false; +} + +static struct vnn_map *vnnmap_init(TALLOC_CTX *mem_ctx) +{ + struct vnn_map *vnn_map; + + vnn_map = talloc_zero(mem_ctx, struct vnn_map); + if (vnn_map == NULL) { + fprintf(stderr, "Memory error\n"); + return NULL; + } + vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + vnn_map->generation = INVALID_GENERATION; + + return vnn_map; +} + +/* Read vnn map. + * output: + * <GENERATION> + * <LMASTER0> + * <LMASTER1> + * ... + */ + +static bool vnnmap_parse(struct vnn_map *vnn_map) +{ + char line[1024]; + + while (fgets(line, sizeof(line), stdin) != NULL) { + uint32_t n; + char *t; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + n = (uint32_t) strtol(line, NULL, 0); + + /* generation */ + if (vnn_map->generation == INVALID_GENERATION) { + vnn_map->generation = n; + continue; + } + + vnn_map->map = talloc_realloc(vnn_map, vnn_map->map, uint32_t, + vnn_map->size + 1); + if (vnn_map->map == NULL) { + fprintf(stderr, "Memory error\n"); + goto fail; + } + + vnn_map->map[vnn_map->size] = n; + vnn_map->size += 1; + } + + if (vnn_map->size == 0) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing vnnmap done\n")); + return true; + +fail: + fprintf(stderr, "Parsing vnnmap failed\n"); + return false; +} + +static bool reclock_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->reclock = talloc_strdup(ctdb, line); + if (ctdb->reclock == NULL) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + DEBUG(DEBUG_INFO, ("Parsing reclock done\n")); + return true; + +fail: + fprintf(stderr, "Parsing reclock failed\n"); + return false; +} + +static struct database_map *dbmap_init(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct database_map *db_map; + + db_map = talloc_zero(mem_ctx, struct database_map); + if (db_map == NULL) { + return NULL; + } + + db_map->dbdir = talloc_strdup(db_map, dbdir); + if (db_map->dbdir == NULL) { + talloc_free(db_map); + return NULL; + } + + return db_map; +} + +/* Read a database map from stdin. Each line looks like: + * <ID> <NAME> [FLAGS] [SEQ_NUM] + * EOF or a blank line terminates input. + * + * By default, flags and seq_num are 0 + */ + +static bool dbmap_parse(struct database_map *db_map) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + uint32_t id; + uint8_t flags = 0; + uint32_t seq_num = 0; + char *tok, *t; + char *name; + struct database *db; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get ID */ + tok = strtok(line, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing ID\n", line); + continue; + } + id = (uint32_t)strtoul(tok, NULL, 0); + + /* Get NAME */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + fprintf(stderr, "bad line (%s) - missing NAME\n", line); + continue; + } + name = talloc_strdup(db_map, tok); + if (name == NULL) { + goto fail; + } + + /* Get flags */ + tok = strtok(NULL, " \t"); + while (tok != NULL) { + if (strcmp(tok, "PERSISTENT") == 0) { + flags |= CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(tok, "STICKY") == 0) { + flags |= CTDB_DB_FLAGS_STICKY; + } else if (strcmp(tok, "READONLY") == 0) { + flags |= CTDB_DB_FLAGS_READONLY; + } else if (strcmp(tok, "REPLICATED") == 0) { + flags |= CTDB_DB_FLAGS_REPLICATED; + } else if (tok[0] >= '0'&& tok[0] <= '9') { + uint8_t nv = CTDB_DB_FLAGS_PERSISTENT | + CTDB_DB_FLAGS_REPLICATED; + + if ((flags & nv) == 0) { + fprintf(stderr, + "seq_num for volatile db\n"); + goto fail; + } + seq_num = (uint64_t)strtoull(tok, NULL, 0); + } + + tok = strtok(NULL, " \t"); + } + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + goto fail; + } + + db->id = id; + db->name = talloc_steal(db, name); + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + talloc_free(db); + goto fail; + } + db->flags = flags; + db->seq_num = seq_num; + + DLIST_ADD_END(db_map->db, db); + } + + if (db_map->db == NULL) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("Parsing dbmap done\n")); + return true; + +fail: + DEBUG(DEBUG_INFO, ("Parsing dbmap failed\n")); + return false; + +} + +static struct database *database_find(struct database_map *db_map, + uint32_t db_id) +{ + struct database *db; + + for (db = db_map->db; db != NULL; db = db->next) { + if (db->id == db_id) { + return db; + } + } + + return NULL; +} + +static int database_count(struct database_map *db_map) +{ + struct database *db; + int count = 0; + + for (db = db_map->db; db != NULL; db = db->next) { + count += 1; + } + + return count; +} + +static int database_flags(uint8_t db_flags) +{ + int tdb_flags = 0; + + if (db_flags & CTDB_DB_FLAGS_PERSISTENT) { + tdb_flags = TDB_DEFAULT; + } else { + /* volatile and replicated use the same flags */ + tdb_flags = TDB_NOSYNC | + TDB_CLEAR_IF_FIRST | + TDB_INCOMPATIBLE_HASH; + } + + tdb_flags |= TDB_DISALLOW_NESTING; + + return tdb_flags; +} + +static struct database *database_new(struct database_map *db_map, + const char *name, uint8_t flags) +{ + struct database *db; + TDB_DATA key; + int tdb_flags; + + db = talloc_zero(db_map, struct database); + if (db == NULL) { + return NULL; + } + + db->name = talloc_strdup(db, name); + if (db->name == NULL) { + goto fail; + } + + db->path = talloc_asprintf(db, "%s/%s", db_map->dbdir, name); + if (db->path == NULL) { + goto fail; + } + + key.dsize = strlen(db->name) + 1; + key.dptr = discard_const(db->name); + + db->id = tdb_jenkins_hash(&key); + db->flags = flags; + + tdb_flags = database_flags(flags); + + db->tdb = tdb_open(db->path, 8192, tdb_flags, O_CREAT|O_RDWR, 0644); + if (db->tdb == NULL) { + DBG_ERR("tdb_open\n"); + goto fail; + } + + DLIST_ADD_END(db_map->db, db); + return db; + +fail: + DBG_ERR("Memory error\n"); + talloc_free(db); + return NULL; + +} + +static int ltdb_store(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, TDB_DATA data) +{ + int ret; + bool db_volatile = true; + bool keep = false; + + if (db->tdb == NULL) { + return EINVAL; + } + + if ((db->flags & CTDB_DB_FLAGS_PERSISTENT) || + (db->flags & CTDB_DB_FLAGS_REPLICATED)) { + db_volatile = false; + } + + if (data.dsize > 0) { + keep = true; + } else { + if (db_volatile && header->rsn == 0) { + keep = true; + } + } + + if (keep) { + TDB_DATA rec[2]; + + rec[0].dsize = ctdb_ltdb_header_len(header); + rec[0].dptr = (uint8_t *)header; + + rec[1].dsize = data.dsize; + rec[1].dptr = data.dptr; + + ret = tdb_storev(db->tdb, key, rec, 2, TDB_REPLACE); + } else { + if (header->rsn > 0) { + ret = tdb_delete(db->tdb, key); + } else { + ret = 0; + } + } + + return ret; +} + +static int ltdb_fetch(struct database *db, TDB_DATA key, + struct ctdb_ltdb_header *header, + TALLOC_CTX *mem_ctx, TDB_DATA *data) +{ + TDB_DATA rec; + size_t np; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + rec = tdb_fetch(db->tdb, key); + ret = ctdb_ltdb_header_pull(rec.dptr, rec.dsize, header, &np); + if (ret != 0) { + if (rec.dptr != NULL) { + free(rec.dptr); + } + + *header = (struct ctdb_ltdb_header) { + .rsn = 0, + .dmaster = 0, + .flags = 0, + }; + + ret = ltdb_store(db, key, header, tdb_null); + if (ret != 0) { + return ret; + } + + *data = tdb_null; + return 0; + } + + data->dsize = rec.dsize - ctdb_ltdb_header_len(header); + data->dptr = talloc_memdup(mem_ctx, + rec.dptr + ctdb_ltdb_header_len(header), + data->dsize); + + free(rec.dptr); + + if (data->dptr == NULL) { + return ENOMEM; + } + + return 0; +} + +static int database_seqnum(struct database *db, uint64_t *seqnum) +{ + const char *keyname = CTDB_DB_SEQNUM_KEY; + TDB_DATA key, data; + struct ctdb_ltdb_header header; + size_t np; + int ret; + + if (db->tdb == NULL) { + *seqnum = db->seq_num; + return 0; + } + + key.dptr = discard_const(keyname); + key.dsize = strlen(keyname) + 1; + + ret = ltdb_fetch(db, key, &header, db, &data); + if (ret != 0) { + return ret; + } + + if (data.dsize == 0) { + *seqnum = 0; + return 0; + } + + ret = ctdb_uint64_pull(data.dptr, data.dsize, seqnum, &np); + talloc_free(data.dptr); + if (ret != 0) { + *seqnum = 0; + } + + return ret; +} + +static int ltdb_transaction_update(uint32_t reqid, + struct ctdb_ltdb_header *no_header, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct database *db = (struct database *)private_data; + TALLOC_CTX *tmp_ctx = talloc_new(db); + struct ctdb_ltdb_header header = { 0 }, oldheader; + TDB_DATA olddata; + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = ctdb_ltdb_header_extract(&data, &header); + if (ret != 0) { + return ret; + } + + ret = ltdb_fetch(db, key, &oldheader, tmp_ctx, &olddata); + if (ret != 0) { + return ret; + } + + if (olddata.dsize > 0) { + if (oldheader.rsn > header.rsn || + (oldheader.rsn == header.rsn && + olddata.dsize != data.dsize)) { + return -1; + } + } + + talloc_free(tmp_ctx); + + ret = ltdb_store(db, key, &header, data); + return ret; +} + +static int ltdb_transaction(struct database *db, + struct ctdb_rec_buffer *recbuf) +{ + int ret; + + if (db->tdb == NULL) { + return EINVAL; + } + + ret = tdb_transaction_start(db->tdb); + if (ret == -1) { + return ret; + } + + ret = ctdb_rec_buffer_traverse(recbuf, ltdb_transaction_update, db); + if (ret != 0) { + tdb_transaction_cancel(db->tdb); + } + + ret = tdb_transaction_commit(db->tdb); + return ret; +} + +static bool public_ips_parse(struct ctdbd_context *ctdb, + uint32_t numnodes) +{ + bool status; + + if (numnodes == 0) { + D_ERR("Must initialise nodemap before public IPs\n"); + return false; + } + + ctdb->known_ips = ipalloc_read_known_ips(ctdb, numnodes, false); + + status = (ctdb->known_ips != NULL && ctdb->known_ips->num != 0); + + if (status) { + D_INFO("Parsing public IPs done\n"); + } else { + D_INFO("Parsing public IPs failed\n"); + } + + return status; +} + +/* Read information about controls to fail. Format is: + * <opcode> <pnn> {ERROR|TIMEOUT} <comment> + */ +static bool control_failures_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + + while ((fgets(line, sizeof(line), stdin) != NULL)) { + char *tok, *t; + enum ctdb_controls opcode; + uint32_t pnn; + const char *error; + const char *comment; + struct fake_control_failure *failure = NULL; + + if (line[0] == '\n') { + break; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Get opcode */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing opcode\n", line); + continue; + } + opcode = (enum ctdb_controls)strtoul(tok, NULL, 0); + + /* Get PNN */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing PNN\n", line); + continue; + } + pnn = (uint32_t)strtoul(tok, NULL, 0); + + /* Get error */ + tok = strtok(NULL, " \t"); + if (tok == NULL) { + D_ERR("bad line (%s) - missing errno\n", line); + continue; + } + error = talloc_strdup(ctdb, tok); + if (error == NULL) { + goto fail; + } + if (strcmp(error, "ERROR") != 0 && + strcmp(error, "TIMEOUT") != 0) { + D_ERR("bad line (%s) " + "- error must be \"ERROR\" or \"TIMEOUT\"\n", + line); + goto fail; + } + + /* Get comment */ + tok = strtok(NULL, "\n"); /* rest of line */ + if (tok == NULL) { + D_ERR("bad line (%s) - missing comment\n", line); + continue; + } + comment = talloc_strdup(ctdb, tok); + if (comment == NULL) { + goto fail; + } + + failure = talloc_zero(ctdb, struct fake_control_failure); + if (failure == NULL) { + goto fail; + } + + failure->opcode = opcode; + failure->pnn = pnn; + failure->error = error; + failure->comment = comment; + + DLIST_ADD(ctdb->control_failures, failure); + } + + if (ctdb->control_failures == NULL) { + goto fail; + } + + D_INFO("Parsing fake control failures done\n"); + return true; + +fail: + D_INFO("Parsing fake control failures failed\n"); + return false; +} + +static bool runstate_parse(struct ctdbd_context *ctdb) +{ + char line[1024]; + char *t; + + if (fgets(line, sizeof(line), stdin) == NULL) { + goto fail; + } + + if (line[0] == '\n') { + goto fail; + } + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + ctdb->runstate = ctdb_runstate_from_string(line); + if (ctdb->runstate == CTDB_RUNSTATE_UNKNOWN) { + goto fail; + } + + /* Swallow possible blank line following section. Picky + * compiler settings don't allow the return value to be + * ignored, so make the compiler happy. + */ + if (fgets(line, sizeof(line), stdin) == NULL) { + ; + } + D_INFO("Parsing runstate done\n"); + return true; + +fail: + D_ERR("Parsing runstate failed\n"); + return false; +} + +/* + * Manage clients + */ + +static int ctdb_client_destructor(struct ctdb_client *client) +{ + DLIST_REMOVE(client->ctdb->client_list, client); + return 0; +} + +static int client_add(struct ctdbd_context *ctdb, pid_t client_pid, + void *client_state) +{ + struct ctdb_client *client; + + client = talloc_zero(client_state, struct ctdb_client); + if (client == NULL) { + return ENOMEM; + } + + client->ctdb = ctdb; + client->pid = client_pid; + client->state = client_state; + + DLIST_ADD(ctdb->client_list, client); + talloc_set_destructor(client, ctdb_client_destructor); + return 0; +} + +static void *client_find(struct ctdbd_context *ctdb, pid_t client_pid) +{ + struct ctdb_client *client; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == client_pid) { + return client->state; + } + } + + return NULL; +} + +/* + * CTDB context setup + */ + +static uint32_t new_generation(uint32_t old_generation) +{ + uint32_t generation; + + while (1) { + generation = random(); + if (generation != INVALID_GENERATION && + generation != old_generation) { + break; + } + } + + return generation; +} + +static struct ctdbd_context *ctdbd_setup(TALLOC_CTX *mem_ctx, + const char *dbdir) +{ + struct ctdbd_context *ctdb; + char line[1024]; + bool status; + int ret; + + ctdb = talloc_zero(mem_ctx, struct ctdbd_context); + if (ctdb == NULL) { + return NULL; + } + + ctdb->node_map = nodemap_init(ctdb); + if (ctdb->node_map == NULL) { + goto fail; + } + + ctdb->iface_map = interfaces_init(ctdb); + if (ctdb->iface_map == NULL) { + goto fail; + } + + ctdb->vnn_map = vnnmap_init(ctdb); + if (ctdb->vnn_map == NULL) { + goto fail; + } + + ctdb->db_map = dbmap_init(ctdb, dbdir); + if (ctdb->db_map == NULL) { + goto fail; + } + + ret = srvid_init(ctdb, &ctdb->srv); + if (ret != 0) { + goto fail; + } + + ctdb->runstate = CTDB_RUNSTATE_RUNNING; + + while (fgets(line, sizeof(line), stdin) != NULL) { + char *t; + + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + if (strcmp(line, "NODEMAP") == 0) { + status = nodemap_parse(ctdb->node_map); + } else if (strcmp(line, "IFACES") == 0) { + status = interfaces_parse(ctdb->iface_map); + } else if (strcmp(line, "VNNMAP") == 0) { + status = vnnmap_parse(ctdb->vnn_map); + } else if (strcmp(line, "DBMAP") == 0) { + status = dbmap_parse(ctdb->db_map); + } else if (strcmp(line, "PUBLICIPS") == 0) { + status = public_ips_parse(ctdb, + ctdb->node_map->num_nodes); + } else if (strcmp(line, "RECLOCK") == 0) { + status = reclock_parse(ctdb); + } else if (strcmp(line, "CONTROLFAILS") == 0) { + status = control_failures_parse(ctdb); + } else if (strcmp(line, "RUNSTATE") == 0) { + status = runstate_parse(ctdb); + } else { + fprintf(stderr, "Unknown line %s\n", line); + status = false; + } + + if (! status) { + goto fail; + } + } + + ctdb->start_time = tevent_timeval_current(); + ctdb->recovery_start_time = tevent_timeval_current(); + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + if (ctdb->vnn_map->generation == INVALID_GENERATION) { + ctdb->vnn_map->generation = + new_generation(ctdb->vnn_map->generation); + } + ctdb->recovery_end_time = tevent_timeval_current(); + + ctdb->log_level = DEBUG_ERR; + + ctdb_tunable_set_defaults(&ctdb->tun_list); + + return ctdb; + +fail: + TALLOC_FREE(ctdb); + return NULL; +} + +static bool ctdbd_verify(struct ctdbd_context *ctdb) +{ + struct node *node; + unsigned int i; + + if (ctdb->node_map->num_nodes == 0) { + return true; + } + + /* Make sure all the nodes are in order */ + for (i=0; i<ctdb->node_map->num_nodes; i++) { + node = &ctdb->node_map->node[i]; + if (node->pnn != i) { + fprintf(stderr, "Expected node %u, found %u\n", + i, node->pnn); + return false; + } + } + + node = &ctdb->node_map->node[ctdb->node_map->pnn]; + if (node->flags & NODE_FLAGS_DISCONNECTED) { + DEBUG(DEBUG_INFO, ("Node disconnected, exiting\n")); + exit(0); + } + + return true; +} + +/* + * Doing a recovery + */ + +struct recover_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; +}; + +static int recover_check(struct tevent_req *req); +static void recover_wait_done(struct tevent_req *subreq); +static void recover_done(struct tevent_req *subreq); + +static struct tevent_req *recover_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb) +{ + struct tevent_req *req; + struct recover_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct recover_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + return req; +} + +static int recover_check(struct tevent_req *req) +{ + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + bool recovery_disabled; + unsigned int i; + + recovery_disabled = false; + for (i=0; i<ctdb->node_map->num_nodes; i++) { + if (ctdb->node_map->node[i].recovery_disabled) { + recovery_disabled = true; + break; + } + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (subreq == NULL) { + return ENOMEM; + } + + if (recovery_disabled) { + tevent_req_set_callback(subreq, recover_wait_done, req); + } else { + ctdb->recovery_start_time = tevent_timeval_current(); + tevent_req_set_callback(subreq, recover_done, req); + } + + return 0; +} + +static void recover_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ret = recover_check(req); + if (ret != 0) { + tevent_req_error(req, ret); + } +} + +static void recover_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct recover_state *state = tevent_req_data( + req, struct recover_state); + struct ctdbd_context *ctdb = state->ctdb; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + ctdb->vnn_map->recmode = CTDB_RECOVERY_NORMAL; + ctdb->recovery_end_time = tevent_timeval_current(); + ctdb->vnn_map->generation = new_generation(ctdb->vnn_map->generation); + + tevent_req_done(req); +} + +static bool recover_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + + return true; +} + +/* + * Routines for ctdb_req_header + */ + +static void header_fix_pnn(struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + if (header->srcnode == CTDB_CURRENT_NODE) { + header->srcnode = ctdb->node_map->pnn; + } + + if (header->destnode == CTDB_CURRENT_NODE) { + header->destnode = ctdb->node_map->pnn; + } +} + +static struct ctdb_req_header header_reply_call( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CALL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_control( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REPLY_CONTROL, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = header->reqid, + }; + + return reply_header; +} + +static struct ctdb_req_header header_reply_message( + struct ctdb_req_header *header, + struct ctdbd_context *ctdb) +{ + struct ctdb_req_header reply_header; + + reply_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = header->srcnode, + .srcnode = header->destnode, + .reqid = 0, + }; + + return reply_header; +} + +/* + * Client state + */ + +struct client_state { + struct tevent_context *ev; + int fd; + struct ctdbd_context *ctdb; + int pnn; + pid_t pid; + struct comm_context *comm; + struct srvid_register_state *rstate; + int status; +}; + +/* + * Send replies to call, controls and messages + */ + +static void client_reply_done(struct tevent_req *subreq); + +static void client_send_call(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_call *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_call(header, ctdb); + + datalen = ctdb_reply_call_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_call_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_message(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message_data *message) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_message(header, ctdb); + + datalen = ctdb_req_message_data_len(&reply_header, message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_req_message_data_push(&reply_header, message, + buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("message srvid = 0x%"PRIx64"\n", message->srvid)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_send_control(struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_reply_control *reply) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header reply_header; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + reply_header = header_reply_control(header, ctdb); + + datalen = ctdb_reply_control_len(&reply_header, reply); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_push(&reply_header, reply, buf, &buflen); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + DEBUG(DEBUG_INFO, ("reply opcode = %u\n", reply->rdata.opcode)); + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, client_reply_done, req); + + talloc_steal(subreq, buf); +} + +static void client_reply_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +/* + * Handling protocol - controls + */ + +static void control_process_exists(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct client_state *cstate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + cstate = client_find(ctdb, request->rdata.data.pid); + if (cstate == NULL) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else { + reply.status = kill(request->rdata.data.pid, 0); + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_ping(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->num_clients; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_getdbpath(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_path = + talloc_strdup(mem_ctx, db->path); + if (reply.rdata.data.db_path == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_getvnnmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_vnn_map *vnnmap; + + reply.rdata.opcode = request->opcode; + + vnnmap = talloc_zero(mem_ctx, struct ctdb_vnn_map); + if (vnnmap == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + vnnmap->generation = ctdb->vnn_map->generation; + vnnmap->size = ctdb->vnn_map->size; + vnnmap->map = ctdb->vnn_map->map; + + reply.rdata.data.vnnmap = vnnmap; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_get_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.loglevel = (uint32_t)ctdb->log_level; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_set_debug(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + ctdb->log_level = (int)request->rdata.data.loglevel; + + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_dbmap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_dbid_map *dbmap; + struct database *db; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + dbmap = talloc_zero(mem_ctx, struct ctdb_dbid_map); + if (dbmap == NULL) { + goto fail; + } + + dbmap->num = database_count(ctdb->db_map); + dbmap->dbs = talloc_zero_array(dbmap, struct ctdb_dbid, dbmap->num); + if (dbmap->dbs == NULL) { + goto fail; + } + + db = ctdb->db_map->db; + for (i = 0; i < dbmap->num; i++) { + dbmap->dbs[i] = (struct ctdb_dbid) { + .db_id = db->id, + .flags = db->flags, + }; + + db = db->next; + } + + reply.rdata.data.dbmap = dbmap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = ctdb->vnn_map->recmode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +struct set_recmode_state { + struct tevent_req *req; + struct ctdbd_context *ctdb; + struct ctdb_req_header header; + struct ctdb_reply_control reply; +}; + +static void set_recmode_callback(struct tevent_req *subreq) +{ + struct set_recmode_state *substate = tevent_req_callback_data( + subreq, struct set_recmode_state); + bool status; + int ret; + + status = recover_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + substate->reply.status = ret; + substate->reply.errmsg = "recovery failed"; + } else { + substate->reply.status = 0; + substate->reply.errmsg = NULL; + } + + client_send_control(substate->req, &substate->header, &substate->reply); + talloc_free(substate); +} + +static void control_set_recmode(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct set_recmode_state *substate; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (request->rdata.data.recmode == CTDB_RECOVERY_NORMAL) { + reply.status = -1; + reply.errmsg = "Client cannot set recmode to NORMAL"; + goto fail; + } + + substate = talloc_zero(ctdb, struct set_recmode_state); + if (substate == NULL) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + substate->req = req; + substate->ctdb = ctdb; + substate->header = *header; + substate->reply.rdata.opcode = request->opcode; + + subreq = recover_send(substate, state->ev, state->ctdb); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, set_recmode_callback, substate); + + ctdb->vnn_map->recmode = CTDB_RECOVERY_ACTIVE; + return; + +fail: + client_send_control(req, header, &reply); + +} + +static void control_db_attach(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, 0); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void srvid_handler_done(struct tevent_req *subreq); + +static void srvid_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + struct client_state *state = talloc_get_type_abort( + private_data, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct tevent_req *subreq; + struct ctdb_req_header request_header; + struct ctdb_req_message_data message; + uint8_t *buf; + size_t datalen, buflen; + int ret; + + request_header = (struct ctdb_req_header) { + .ctdb_magic = CTDB_MAGIC, + .ctdb_version = CTDB_PROTOCOL, + .generation = ctdb->vnn_map->generation, + .operation = CTDB_REQ_MESSAGE, + .destnode = state->pnn, + .srcnode = ctdb->node_map->recmaster, + .reqid = 0, + }; + + message = (struct ctdb_req_message_data) { + .srvid = srvid, + .data = data, + }; + + datalen = ctdb_req_message_data_len(&request_header, &message); + ret = ctdb_allocate_pkt(state, datalen, &buf, &buflen); + if (ret != 0) { + return; + } + + ret = ctdb_req_message_data_push(&request_header, + &message, + buf, + &buflen); + if (ret != 0) { + talloc_free(buf); + return; + } + + subreq = comm_write_send(state, state->ev, state->comm, buf, buflen); + if (subreq == NULL) { + talloc_free(buf); + return; + } + tevent_req_set_callback(subreq, srvid_handler_done, state); + + talloc_steal(subreq, buf); +} + +static void srvid_handler_done(struct tevent_req *subreq) +{ + struct client_state *state = tevent_req_callback_data( + subreq, struct client_state); + int ret; + bool ok; + + ok = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (!ok) { + DEBUG(DEBUG_ERR, + ("Failed to dispatch message to client pid=%u, ret=%d\n", + state->pid, + ret)); + } +} + +static void control_register_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_register(ctdb->srv, state, request->srvid, + srvid_handler, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Register srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_deregister_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + int ret; + + reply.rdata.opcode = request->opcode; + + ret = srvid_deregister(ctdb->srv, request->srvid, state); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "srvid not registered"; + goto fail; + } + + DEBUG(DEBUG_INFO, ("Deregister srvid 0x%"PRIx64"\n", request->srvid)); + + reply.status = 0; + reply.errmsg = NULL; + +fail: + client_send_control(req, header, &reply); +} + +static void control_get_dbname(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.db_name = talloc_strdup(mem_ctx, db->name); + if (reply.rdata.data.db_name == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + + client_send_control(req, header, &reply); +} + +static void control_get_pid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = getpid(); + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_pnn(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.status = header->destnode; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_shutdown(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *hdr, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + + state->status = 99; +} + +static void control_set_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + bool ret, obsolete; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_set_value(&ctdb->tun_list, + request->rdata.data.tunable->name, + request->rdata.data.tunable->value, + &obsolete); + if (! ret) { + reply.status = -1; + } else if (obsolete) { + reply.status = 1; + } else { + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_get_tunable(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + uint32_t value; + bool ret; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + ret = ctdb_tunable_get_value(&ctdb->tun_list, + request->rdata.data.tun_var, &value); + if (! ret) { + reply.status = -1; + } else { + reply.rdata.data.tun_value = value; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_list_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_var_list *var_list; + + reply.rdata.opcode = request->opcode; + reply.errmsg = NULL; + + var_list = ctdb_tunable_names(mem_ctx); + if (var_list == NULL) { + reply.status = -1; + } else { + reply.rdata.data.tun_var_list = var_list; + reply.status = 0; + } + + client_send_control(req, header, &reply); +} + +static void control_modify_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_node_flag_change *change = request->rdata.data.flag_change; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if ((change->old_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) || + (change->new_flags & ~NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO, + ("MODIFY_FLAGS control not for PERMANENTLY_DISABLED\n")); + reply.status = EINVAL; + reply.errmsg = "Failed to MODIFY_FLAGS"; + client_send_control(req, header, &reply); + return; + } + + /* There's all sorts of broadcast weirdness here. Only change + * the specified node, not the destination node of the + * control. */ + node = &ctdb->node_map->node[change->pnn]; + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0) { + DEBUG(DEBUG_INFO,("Disabling node %d\n", header->destnode)); + node->flags |= NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + if ((node->flags & + change->old_flags & NODE_FLAGS_PERMANENTLY_DISABLED) != 0 && + (change->new_flags & NODE_FLAGS_PERMANENTLY_DISABLED) == 0) { + DEBUG(DEBUG_INFO,("Enabling node %d\n", header->destnode)); + node->flags &= ~NODE_FLAGS_PERMANENTLY_DISABLED; + goto done; + } + + DEBUG(DEBUG_INFO, ("Flags unchanged for node %d\n", header->destnode)); + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_all_tunables(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.tun_list = &ctdb->tun_list; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_db_attach_persistent(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_PERSISTENT); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_uptime(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_uptime *uptime;; + + reply.rdata.opcode = request->opcode; + + uptime = talloc_zero(mem_ctx, struct ctdb_uptime); + if (uptime == NULL) { + goto fail; + } + + uptime->current_time = tevent_timeval_current(); + uptime->ctdbd_start_time = ctdb->start_time; + uptime->last_recovery_started = ctdb->recovery_start_time; + uptime->last_recovery_finished = ctdb->recovery_end_time; + + reply.rdata.data.uptime = uptime; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_reload_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node_map *node_map = ctdb->node_map; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + for (i=0; i<nodemap->num; i++) { + struct node *node; + + if (i < node_map->num_nodes && + ctdb_sock_addr_same(&nodemap->node[i].addr, + &node_map->node[i].addr)) { + continue; + } + + if (nodemap->node[i].flags & NODE_FLAGS_DELETED) { + int ret; + + node = &node_map->node[i]; + + node->flags |= NODE_FLAGS_DELETED; + ret = ctdb_sock_addr_from_string("0.0.0.0", &node->addr, + false); + if (ret != 0) { + /* Can't happen, but Coverity... */ + goto fail; + } + + continue; + } + + if (i < node_map->num_nodes && + node_map->node[i].flags & NODE_FLAGS_DELETED) { + node = &node_map->node[i]; + + node->flags &= ~NODE_FLAGS_DELETED; + node->addr = nodemap->node[i].addr; + + continue; + } + + node_map->node = talloc_realloc(node_map, node_map->node, + struct node, + node_map->num_nodes+1); + if (node_map->node == NULL) { + goto fail; + } + node = &node_map->node[node_map->num_nodes]; + + node->addr = nodemap->node[i].addr; + node->pnn = nodemap->node[i].pnn; + node->flags = 0; + node->capabilities = CTDB_CAP_DEFAULT; + node->recovery_disabled = false; + node->recovery_substate = NULL; + + node_map->num_nodes += 1; + } + + talloc_free(nodemap); + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_capabilities(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct node *node; + uint32_t caps = 0; + + reply.rdata.opcode = request->opcode; + + node = &ctdb->node_map->node[header->destnode]; + caps = node->capabilities; + + if (node->flags & NODE_FLAGS_FAKE_TIMEOUT) { + /* Don't send reply */ + return; + } + + reply.rdata.data.caps = caps; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_release_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("RELEASE_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn != header->destnode) { + if (header->destnode == ip->pnn) { + D_ERR("error: RELEASE_IP %s - to TAKE_IP node %d\n", + ctdb_sock_addr_to_string(mem_ctx, + &ip->addr, false), + ip->pnn); + reply.status = -1; + reply.errmsg = "RELEASE_IP to TAKE_IP node"; + client_send_control(req, header, &reply); + return; + } + + D_INFO("RELEASE_IP %s - to node %d - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } else { + D_NOTICE("RELEASE_IP %s - to node %d\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false), + ip->pnn); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_takeover_ip(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_public_ip *ip = request->rdata.data.pubip; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + struct ctdb_public_ip *t = NULL; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + ips = &ctdb->known_ips[header->destnode]; + + t = NULL; + for (i = 0; i < ips->num; i++) { + if (ctdb_sock_addr_same_ip(&ips->ip[i].addr, &ip->addr)) { + t = &ips->ip[i]; + break; + } + } + if (t == NULL) { + D_INFO("TAKEOVER_IP %s - not a public IP\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + goto done; + } + + if (t->pnn == header->destnode) { + D_INFO("TAKEOVER_IP %s - redundant\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + } else { + D_NOTICE("TAKEOVER_IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, &ip->addr, false)); + t->pnn = ip->pnn; + } + +done: + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_public_ips(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_public_ip_list *ips = NULL; + + reply.rdata.opcode = request->opcode; + + if (ctdb->known_ips == NULL) { + /* No IPs defined so create a dummy empty struct and ship it */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + goto ok; + } + + ips = &ctdb->known_ips[header->destnode]; + + if (request->flags & CTDB_PUBLIC_IP_FLAGS_ONLY_AVAILABLE) { + /* If runstate is not RUNNING or a node is then return + * no available IPs. Don't worry about interface + * states here - we're not faking down to that level. + */ + uint32_t flags = ctdb->node_map->node[header->destnode].flags; + if (ctdb->runstate != CTDB_RUNSTATE_RUNNING || + ((flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) != 0)) { + /* No available IPs: return dummy empty struct */ + ips = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (ips == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + } + +ok: + reply.rdata.data.pubip_list = ips; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_nodemap(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + struct node *node; + unsigned int i; + + reply.rdata.opcode = request->opcode; + + nodemap = talloc_zero(mem_ctx, struct ctdb_node_map); + if (nodemap == NULL) { + goto fail; + } + + nodemap->num = ctdb->node_map->num_nodes; + nodemap->node = talloc_array(nodemap, struct ctdb_node_and_flags, + nodemap->num); + if (nodemap->node == NULL) { + goto fail; + } + + for (i=0; i<nodemap->num; i++) { + node = &ctdb->node_map->node[i]; + nodemap->node[i] = (struct ctdb_node_and_flags) { + .pnn = node->pnn, + .flags = node->flags, + .addr = node->addr, + }; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_get_reclock_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + if (ctdb->reclock != NULL) { + reply.rdata.data.reclock_file = + talloc_strdup(mem_ctx, ctdb->reclock); + if (reply.rdata.data.reclock_file == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } else { + reply.rdata.data.reclock_file = NULL; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_stop_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Stopping node\n")); + ctdb->node_map->node[header->destnode].flags |= NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_continue_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Continue node\n")); + ctdb->node_map->node[header->destnode].flags &= ~NODE_FLAGS_STOPPED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void set_ban_state_callback(struct tevent_req *subreq) +{ + struct node *node = tevent_req_callback_data( + subreq, struct node); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + node->flags &= ~NODE_FLAGS_BANNED; +} + +static void control_set_ban_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_ban_state *ban = request->rdata.data.ban_state; + struct ctdb_reply_control reply; + struct node *node; + + reply.rdata.opcode = request->opcode; + + if (ban->pnn != header->destnode) { + DEBUG(DEBUG_INFO, + ("SET_BAN_STATE control for PNN %d rejected\n", + ban->pnn)); + reply.status = EINVAL; + goto fail; + } + + node = &ctdb->node_map->node[header->destnode]; + + if (ban->time == 0) { + DEBUG(DEBUG_INFO,("Unbanning this node\n")); + node->flags &= ~NODE_FLAGS_BANNED; + goto done; + } + + subreq = tevent_wakeup_send(ctdb->node_map, state->ev, + tevent_timeval_current_ofs( + ban->time, 0)); + if (subreq == NULL) { + reply.status = ENOMEM; + goto fail; + } + tevent_req_set_callback(subreq, set_ban_state_callback, node); + + DEBUG(DEBUG_INFO, ("Banning this node for %d seconds\n", ban->time)); + node->flags |= NODE_FLAGS_BANNED; + ctdb->vnn_map->generation = INVALID_GENERATION; + +done: + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; + +fail: + reply.errmsg = "Failed to ban node"; +} + +static void control_trans3_commit(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.recbuf->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + if (! (db->flags & + (CTDB_DB_FLAGS_PERSISTENT|CTDB_DB_FLAGS_REPLICATED))) { + reply.status = -1; + reply.errmsg = "Transactions on volatile database"; + client_send_control(req, header, &reply); + return; + } + + ret = ltdb_transaction(db, request->rdata.data.recbuf); + if (ret != 0) { + reply.status = -1; + reply.errmsg = "Transaction failed"; + client_send_control(req, header, &reply); + return; + } + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_get_db_seqnum(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + int ret; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + uint64_t seqnum; + + ret = database_seqnum(db, &seqnum); + if (ret == 0) { + reply.rdata.data.seqnum = seqnum; + reply.status = 0; + reply.errmsg = NULL; + } else { + reply.status = ret; + reply.errmsg = "Failed to get seqnum"; + } + } + + client_send_control(req, header, &reply); +} + +static void control_db_get_health(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.reason = NULL; + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static struct ctdb_iface_list *get_ctdb_iface_list(TALLOC_CTX *mem_ctx, + struct ctdbd_context *ctdb) +{ + struct ctdb_iface_list *iface_list; + struct interface *iface; + unsigned int i; + + iface_list = talloc_zero(mem_ctx, struct ctdb_iface_list); + if (iface_list == NULL) { + goto done; + } + + iface_list->num = ctdb->iface_map->num; + iface_list->iface = talloc_array(iface_list, struct ctdb_iface, + iface_list->num); + if (iface_list->iface == NULL) { + TALLOC_FREE(iface_list); + goto done; + } + + for (i=0; i<iface_list->num; i++) { + iface = &ctdb->iface_map->iface[i]; + iface_list->iface[i] = (struct ctdb_iface) { + .link_state = iface->link_up, + .references = iface->references, + }; + strlcpy(iface_list->iface[i].name, iface->name, + sizeof(iface_list->iface[i].name)); + } + +done: + return iface_list; +} + +static void control_get_public_ip_info(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + ctdb_sock_addr *addr = request->rdata.data.addr; + struct ctdb_public_ip_list *known = NULL; + struct ctdb_public_ip_info *info = NULL; + unsigned i; + + reply.rdata.opcode = request->opcode; + + info = talloc_zero(mem_ctx, struct ctdb_public_ip_info); + if (info == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.rdata.data.ipinfo = info; + + if (ctdb->known_ips != NULL) { + known = &ctdb->known_ips[header->destnode]; + } else { + /* No IPs defined so create a dummy empty struct and + * fall through. The given IP won't be matched + * below... + */ + known = talloc_zero(mem_ctx, struct ctdb_public_ip_list);; + if (known == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + } + + for (i = 0; i < known->num; i++) { + if (ctdb_sock_addr_same_ip(&known->ip[i].addr, + addr)) { + break; + } + } + + if (i == known->num) { + D_ERR("GET_PUBLIC_IP_INFO: not known public IP %s\n", + ctdb_sock_addr_to_string(mem_ctx, addr, false)); + reply.status = -1; + reply.errmsg = "Unknown address"; + goto done; + } + + info->ip = known->ip[i]; + + /* The fake PUBLICIPS stanza and resulting known_ips data + * don't know anything about interfaces, so completely fake + * this. + */ + info->active_idx = 0; + + info->ifaces = get_ctdb_iface_list(mem_ctx, ctdb); + if (info->ifaces == NULL) { + reply.status = ENOMEM; + reply.errmsg = "Memory error"; + goto done; + } + + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_get_ifaces(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface_list *iface_list; + + reply.rdata.opcode = request->opcode; + + iface_list = get_ctdb_iface_list(mem_ctx, ctdb); + if (iface_list == NULL) { + goto fail; + } + + reply.rdata.data.iface_list = iface_list; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); +} + +static void control_set_iface_link_state(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct ctdb_iface *in_iface; + struct interface *iface = NULL; + bool link_up = false; + int i; + + reply.rdata.opcode = request->opcode; + + in_iface = request->rdata.data.iface; + + if (in_iface->name[CTDB_IFACE_SIZE] != '\0') { + reply.errmsg = "interface name not terminated"; + goto fail; + } + + switch (in_iface->link_state) { + case 0: + link_up = false; + break; + + case 1: + link_up = true; + break; + + default: + reply.errmsg = "invalid link state"; + goto fail; + } + + if (in_iface->references != 0) { + reply.errmsg = "references should be 0"; + goto fail; + } + + for (i=0; i<ctdb->iface_map->num; i++) { + if (strcmp(ctdb->iface_map->iface[i].name, + in_iface->name) == 0) { + iface = &ctdb->iface_map->iface[i]; + break; + } + } + + if (iface == NULL) { + reply.errmsg = "interface not found"; + goto fail; + } + + iface->link_up = link_up; + + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + client_send_control(req, header, &reply); +} + +static void control_set_db_readonly(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set READONLY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_READONLY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +struct traverse_start_ext_state { + struct tevent_req *req; + struct ctdb_req_header *header; + uint32_t reqid; + uint64_t srvid; + bool withemptyrecords; + int status; +}; + +static int traverse_start_ext_handler(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct traverse_start_ext_state *state = + (struct traverse_start_ext_state *)private_data; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + size_t np; + + if (data.dsize < sizeof(struct ctdb_ltdb_header)) { + return 0; + } + + if ((data.dsize == sizeof(struct ctdb_ltdb_header)) && + (!state->withemptyrecords)) { + return 0; + } + + rec = (struct ctdb_rec_data) { + .reqid = state->reqid, + .header = NULL, + .key = key, + .data = data, + }; + + message.srvid = state->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + message.data.dptr = talloc_size(state->req, message.data.dsize); + if (message.data.dptr == NULL) { + state->status = ENOMEM; + return 1; + } + + ctdb_rec_data_push(&rec, message.data.dptr, &np); + client_send_message(state->req, state->header, &message); + + talloc_free(message.data.dptr); + + return 0; +} + +static void control_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + struct ctdb_traverse_start_ext *ext; + struct traverse_start_ext_state t_state; + struct ctdb_rec_data rec; + struct ctdb_req_message_data message; + uint8_t buffer[32]; + size_t np; + int ret; + + reply.rdata.opcode = request->opcode; + + ext = request->rdata.data.traverse_start_ext; + + db = database_find(ctdb->db_map, ext->db_id); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Unknown database"; + client_send_control(req, header, &reply); + return; + } + + t_state = (struct traverse_start_ext_state) { + .req = req, + .header = header, + .reqid = ext->reqid, + .srvid = ext->srvid, + .withemptyrecords = ext->withemptyrecords, + }; + + ret = tdb_traverse_read(db->tdb, traverse_start_ext_handler, &t_state); + DEBUG(DEBUG_INFO, ("traversed %d records\n", ret)); + if (t_state.status != 0) { + reply.status = -1; + reply.errmsg = "Memory error"; + client_send_control(req, header, &reply); + } + + reply.status = 0; + client_send_control(req, header, &reply); + + rec = (struct ctdb_rec_data) { + .reqid = ext->reqid, + .header = NULL, + .key = tdb_null, + .data = tdb_null, + }; + + message.srvid = ext->srvid; + message.data.dsize = ctdb_rec_data_len(&rec); + ctdb_rec_data_push(&rec, buffer, &np); + message.data.dptr = buffer; + client_send_message(req, header, &message); +} + +static void control_set_db_sticky(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + goto done; + } + + if (db->flags & CTDB_DB_FLAGS_PERSISTENT) { + reply.status = EINVAL; + reply.errmsg = "Can not set STICKY on persistent db"; + goto done; + } + + db->flags |= CTDB_DB_FLAGS_STICKY; + reply.status = 0; + reply.errmsg = NULL; + +done: + client_send_control(req, header, &reply); +} + +static void control_ipreallocated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + /* Always succeed */ + reply.rdata.opcode = request->opcode; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_runstate(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + reply.rdata.data.runstate = ctdb->runstate; + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); +} + +static void control_get_nodes_file(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + struct ctdb_node_map *nodemap; + + reply.rdata.opcode = request->opcode; + + nodemap = read_nodes_file(mem_ctx, header->destnode); + if (nodemap == NULL) { + goto fail; + } + + reply.rdata.data.nodemap = nodemap; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); + return; + +fail: + reply.status = -1; + reply.errmsg = "Failed to read nodes file"; + client_send_control(req, header, &reply); +} + +static void control_db_open_flags(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + db = database_find(ctdb->db_map, request->rdata.data.db_id); + if (db == NULL) { + reply.status = ENOENT; + reply.errmsg = "Database not found"; + } else { + reply.rdata.data.tdb_flags = database_flags(db->flags); + reply.status = 0; + reply.errmsg = NULL; + } + + client_send_control(req, header, &reply); +} + +static void control_db_attach_replicated(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct database *db; + + reply.rdata.opcode = request->opcode; + + for (db = ctdb->db_map->db; db != NULL; db = db->next) { + if (strcmp(db->name, request->rdata.data.db_name) == 0) { + goto done; + } + } + + db = database_new(ctdb->db_map, request->rdata.data.db_name, + CTDB_DB_FLAGS_REPLICATED); + if (db == NULL) { + reply.status = -1; + reply.errmsg = "Failed to attach database"; + client_send_control(req, header, &reply); + return; + } + +done: + reply.rdata.data.db_id = db->id; + reply.status = 0; + reply.errmsg = NULL; + client_send_control(req, header, &reply); +} + +static void control_check_pid_srvid(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_client *client; + struct client_state *cstate; + struct ctdb_reply_control reply; + bool pid_found, srvid_found; + int ret; + + reply.rdata.opcode = request->opcode; + + pid_found = false; + srvid_found = false; + + for (client=ctdb->client_list; client != NULL; client=client->next) { + if (client->pid == request->rdata.data.pid_srvid->pid) { + pid_found = true; + cstate = (struct client_state *)client->state; + ret = srvid_exists(ctdb->srv, + request->rdata.data.pid_srvid->srvid, + cstate); + if (ret == 0) { + srvid_found = true; + ret = kill(cstate->pid, 0); + if (ret != 0) { + reply.status = ret; + reply.errmsg = strerror(errno); + } else { + reply.status = 0; + reply.errmsg = NULL; + } + } + } + } + + if (! pid_found) { + reply.status = -1; + reply.errmsg = "No client for PID"; + } else if (! srvid_found) { + reply.status = -1; + reply.errmsg = "No client for PID and SRVID"; + } + + client_send_control(req, header, &reply); +} + +static void control_disable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Disabling node\n")); + ctdb->node_map->node[header->destnode].flags |= + NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static void control_enable_node(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + + reply.rdata.opcode = request->opcode; + + DEBUG(DEBUG_INFO, ("Enable node\n")); + ctdb->node_map->node[header->destnode].flags &= + ~NODE_FLAGS_PERMANENTLY_DISABLED; + + reply.status = 0; + reply.errmsg = NULL; + + client_send_control(req, header, &reply); + return; +} + +static bool fake_control_failure(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_reply_control reply; + struct fake_control_failure *f = NULL; + + D_DEBUG("Checking fake control failure for control %u on node %u\n", + request->opcode, header->destnode); + for (f = ctdb->control_failures; f != NULL; f = f->next) { + if (f->opcode == request->opcode && + (f->pnn == header->destnode || + f->pnn == CTDB_UNKNOWN_PNN)) { + + reply.rdata.opcode = request->opcode; + if (strcmp(f->error, "TIMEOUT") == 0) { + /* Causes no reply */ + D_ERR("Control %u fake timeout on node %u\n", + request->opcode, header->destnode); + return true; + } else if (strcmp(f->error, "ERROR") == 0) { + D_ERR("Control %u fake error on node %u\n", + request->opcode, header->destnode); + reply.status = -1; + reply.errmsg = f->comment; + client_send_control(req, header, &reply); + return true; + } + } + } + + return false; +} + +static void control_error(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_control *request) +{ + struct ctdb_reply_control reply; + + D_DEBUG("Control %u not implemented\n", request->opcode); + + reply.rdata.opcode = request->opcode; + reply.status = -1; + reply.errmsg = "Not implemented"; + + client_send_control(req, header, &reply); +} + +/* + * Handling protocol - messages + */ + +struct disable_recoveries_state { + struct node *node; +}; + +static void disable_recoveries_callback(struct tevent_req *subreq) +{ + struct disable_recoveries_state *substate = tevent_req_callback_data( + subreq, struct disable_recoveries_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + DEBUG(DEBUG_INFO, ("tevent_wakeup_recv failed\n")); + } + + substate->node->recovery_disabled = false; + TALLOC_FREE(substate->node->recovery_substate); +} + +static void message_disable_recoveries(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct tevent_req *subreq; + struct ctdbd_context *ctdb = state->ctdb; + struct disable_recoveries_state *substate; + struct ctdb_disable_message *disable = request->data.disable; + struct ctdb_req_message_data reply; + struct node *node; + int ret = -1; + TDB_DATA data; + + node = &ctdb->node_map->node[header->destnode]; + + if (disable->timeout == 0) { + TALLOC_FREE(node->recovery_substate); + node->recovery_disabled = false; + DEBUG(DEBUG_INFO, ("Enabled recoveries on node %u\n", + header->destnode)); + goto done; + } + + substate = talloc_zero(ctdb->node_map, + struct disable_recoveries_state); + if (substate == NULL) { + goto fail; + } + + substate->node = node; + + subreq = tevent_wakeup_send(substate, state->ev, + tevent_timeval_current_ofs( + disable->timeout, 0)); + if (subreq == NULL) { + talloc_free(substate); + goto fail; + } + tevent_req_set_callback(subreq, disable_recoveries_callback, substate); + + DEBUG(DEBUG_INFO, ("Disabled recoveries for %d seconds on node %u\n", + disable->timeout, header->destnode)); + node->recovery_substate = substate; + node->recovery_disabled = true; + +done: + ret = header->destnode; + +fail: + reply.srvid = disable->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +static void message_takeover_run(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + struct ctdb_req_header *header, + struct ctdb_req_message *request) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_srvid_message *srvid = request->data.msg; + struct ctdb_req_message_data reply; + int ret = -1; + TDB_DATA data; + + if (header->destnode != ctdb->node_map->recmaster) { + /* No reply! Only recmaster replies... */ + return; + } + + DEBUG(DEBUG_INFO, ("IP takover run on node %u\n", + header->destnode)); + ret = header->destnode; + + reply.srvid = srvid->srvid; + data.dptr = (uint8_t *)&ret; + data.dsize = sizeof(int); + reply.data = data; + + client_send_message(req, header, &reply); +} + +/* + * Handle a single client + */ + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data); +static void client_dead_handler(void *private_data); +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen); +static void client_reply_done(struct tevent_req *subreq); + +static struct tevent_req *client_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, struct ctdbd_context *ctdb, + int pnn) +{ + struct tevent_req *req; + struct client_state *state; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct client_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->ctdb = ctdb; + state->pnn = pnn; + + (void) ctdb_get_peer_pid(fd, &state->pid); + + ret = comm_setup(state, ev, fd, client_read_handler, req, + client_dead_handler, req, &state->comm); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ret = client_add(ctdb, state->pid, state); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + DEBUG(DEBUG_INFO, ("New client fd=%d\n", fd)); + + return req; +} + +static void client_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + struct ctdb_req_header header; + size_t np; + unsigned int i; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + if (buflen != header.length) { + return; + } + + ret = ctdb_req_header_verify(&header, 0); + if (ret != 0) { + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode == CTDB_BROADCAST_ALL) { + for (i=0; i<ctdb->node_map->num_nodes; i++) { + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode == CTDB_BROADCAST_CONNECTED) { + for (i=0; i<ctdb->node_map->num_nodes; i++) { + if (ctdb->node_map->node[i].flags & + NODE_FLAGS_DISCONNECTED) { + continue; + } + + header.destnode = i; + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); + } + return; + } + + if (header.destnode > ctdb->node_map->num_nodes) { + fprintf(stderr, "Invalid destination pnn 0x%x\n", + header.destnode); + return; + } + + + if (ctdb->node_map->node[header.destnode].flags & NODE_FLAGS_DISCONNECTED) { + fprintf(stderr, "Packet for disconnected node pnn %u\n", + header.destnode); + return; + } + + ctdb_req_header_push(&header, buf, &np); + client_process_packet(req, buf, buflen); +} + +static void client_dead_handler(void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + + tevent_req_done(req); +} + +static void client_process_packet(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct ctdb_req_header header; + size_t np; + int ret; + + ret = ctdb_req_header_pull(buf, buflen, &header, &np); + if (ret != 0) { + return; + } + + switch (header.operation) { + case CTDB_REQ_CALL: + client_process_call(req, buf, buflen); + break; + + case CTDB_REQ_MESSAGE: + client_process_message(req, buf, buflen); + break; + + case CTDB_REQ_CONTROL: + client_process_control(req, buf, buflen); + break; + + default: + break; + } +} + +static void client_process_call(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_call request; + struct ctdb_reply_call reply; + struct database *db; + struct ctdb_ltdb_header hdr; + TDB_DATA data; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_call_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + goto fail; + } + + DEBUG(DEBUG_INFO, ("call db_id = %u\n", request.db_id)); + + db = database_find(ctdb->db_map, request.db_id); + if (db == NULL) { + goto fail; + } + + ret = ltdb_fetch(db, request.key, &hdr, mem_ctx, &data); + if (ret != 0) { + goto fail; + } + + /* Fake migration */ + if (hdr.dmaster != ctdb->node_map->pnn) { + hdr.dmaster = ctdb->node_map->pnn; + + ret = ltdb_store(db, request.key, &hdr, data); + if (ret != 0) { + goto fail; + } + } + + talloc_free(mem_ctx); + + reply.status = 0; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); + return; + +fail: + talloc_free(mem_ctx); + reply.status = -1; + reply.data = tdb_null; + + client_send_call(req, &header, &reply); +} + +static void client_process_message(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_message request; + uint64_t srvid; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_message_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + /* Many messages are not replied to, so just behave as + * though this message was not received */ + fprintf(stderr, "Invalid node %d\n", header.destnode); + talloc_free(mem_ctx); + return; + } + + srvid = request.srvid; + DEBUG(DEBUG_INFO, ("request srvid = 0x%"PRIx64"\n", srvid)); + + if (srvid == CTDB_SRVID_DISABLE_RECOVERIES) { + message_disable_recoveries(mem_ctx, req, &header, &request); + } else if (srvid == CTDB_SRVID_TAKEOVER_RUN) { + message_takeover_run(mem_ctx, req, &header, &request); + } else { + D_DEBUG("Message id 0x%"PRIx64" not implemented\n", srvid); + } + + /* check srvid */ + talloc_free(mem_ctx); +} + +static void client_process_control(struct tevent_req *req, + uint8_t *buf, size_t buflen) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + struct ctdbd_context *ctdb = state->ctdb; + TALLOC_CTX *mem_ctx; + struct ctdb_req_header header; + struct ctdb_req_control request; + int ret; + + mem_ctx = talloc_new(state); + if (tevent_req_nomem(mem_ctx, req)) { + return; + } + + ret = ctdb_req_control_pull(buf, buflen, &header, mem_ctx, &request); + if (ret != 0) { + talloc_free(mem_ctx); + tevent_req_error(req, ret); + return; + } + + header_fix_pnn(&header, ctdb); + + if (header.destnode >= ctdb->node_map->num_nodes) { + struct ctdb_reply_control reply; + + reply.rdata.opcode = request.opcode; + reply.errmsg = "Invalid node"; + reply.status = -1; + client_send_control(req, &header, &reply); + return; + } + + DEBUG(DEBUG_INFO, ("request opcode = %u, reqid = %u\n", + request.opcode, header.reqid)); + + if (fake_control_failure(mem_ctx, req, &header, &request)) { + goto done; + } + + switch (request.opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + control_process_exists(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_PING: + control_ping(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETDBPATH: + control_getdbpath(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GETVNNMAP: + control_getvnnmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DEBUG: + control_get_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DEBUG: + control_set_debug(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBMAP: + control_get_dbmap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECMODE: + control_get_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_RECMODE: + control_set_recmode(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH: + control_db_attach(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + control_register_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + control_deregister_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DBNAME: + control_get_dbname(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PID: + control_get_pid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PNN: + control_get_pnn(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SHUTDOWN: + control_shutdown(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_TUNABLE: + control_set_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_TUNABLE: + control_get_tunable(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + control_list_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + control_modify_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + control_get_all_tunables(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + control_db_attach_persistent(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_UPTIME: + control_uptime(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + control_reload_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + control_get_capabilities(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_RELEASE_IP: + control_release_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + control_takeover_ip(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + control_get_public_ips(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODEMAP: + control_get_nodemap(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + control_get_reclock_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_STOP_NODE: + control_stop_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CONTINUE_NODE: + control_continue_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + control_set_ban_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + control_trans3_commit(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + control_get_db_seqnum(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + control_db_get_health(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + control_get_public_ip_info(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_IFACES: + control_get_ifaces(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + control_set_iface_link_state(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + control_set_db_readonly(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + control_traverse_start_ext(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + control_set_db_sticky(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_IPREALLOCATED: + control_ipreallocated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_RUNSTATE: + control_get_runstate(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + control_get_nodes_file(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + control_db_open_flags(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + control_db_attach_replicated(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + control_check_pid_srvid(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_DISABLE_NODE: + control_disable_node(mem_ctx, req, &header, &request); + break; + + case CTDB_CONTROL_ENABLE_NODE: + control_enable_node(mem_ctx, req, &header, &request); + break; + + default: + if (! (request.flags & CTDB_CTRL_FLAG_NOREPLY)) { + control_error(mem_ctx, req, &header, &request); + } + break; + } + +done: + talloc_free(mem_ctx); +} + +static int client_recv(struct tevent_req *req, int *perr) +{ + struct client_state *state = tevent_req_data( + req, struct client_state); + int err; + + DEBUG(DEBUG_INFO, ("Client done fd=%d\n", state->fd)); + close(state->fd); + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return -1; + } + + return state->status; +} + +/* + * Fake CTDB server + */ + +struct server_state { + struct tevent_context *ev; + struct ctdbd_context *ctdb; + struct tevent_timer *leader_broadcast_te; + int fd; +}; + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data); +static void server_new_client(struct tevent_req *subreq); +static void server_client_done(struct tevent_req *subreq); + +static struct tevent_req *server_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdbd_context *ctdb, + int fd) +{ + struct tevent_req *req, *subreq; + struct server_state *state; + + req = tevent_req_create(mem_ctx, &state, struct server_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->ctdb = ctdb; + state->fd = fd; + + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(0, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } + + subreq = accept_send(state, ev, fd); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, server_new_client, req); + + return req; +} + +static void server_leader_broadcast(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct server_state *state = talloc_get_type_abort( + private_data, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + uint32_t leader = ctdb->node_map->recmaster; + TDB_DATA data; + int ret; + + if (leader == CTDB_UNKNOWN_PNN) { + goto done; + } + + data.dptr = (uint8_t *)&leader; + data.dsize = sizeof(leader); + + ret = srvid_dispatch(ctdb->srv, CTDB_SRVID_LEADER, 0, data); + if (ret != 0) { + DBG_WARNING("Failed to send leader broadcast, ret=%d\n", ret); + } + +done: + state->leader_broadcast_te = tevent_add_timer(state->ev, + state, + timeval_current_ofs(1, 0), + server_leader_broadcast, + state); + if (state->leader_broadcast_te == NULL) { + DBG_WARNING("Failed to set up leader broadcast\n"); + } +} + +static void server_new_client(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int client_fd; + int ret = 0; + + client_fd = accept_recv(subreq, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (client_fd == -1) { + tevent_req_error(req, ret); + return; + } + + subreq = client_send(state, state->ev, client_fd, + ctdb, ctdb->node_map->pnn); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_client_done, req); + + ctdb->num_clients += 1; + + subreq = accept_send(state, state->ev, state->fd); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, server_new_client, req); +} + +static void server_client_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct server_state *state = tevent_req_data( + req, struct server_state); + struct ctdbd_context *ctdb = state->ctdb; + int ret = 0; + int status; + + status = client_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (status < 0) { + tevent_req_error(req, ret); + return; + } + + ctdb->num_clients -= 1; + + if (status == 99) { + /* Special status, to shutdown server */ + DEBUG(DEBUG_INFO, ("Shutting down server\n")); + tevent_req_done(req); + } +} + +static bool server_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +/* + * Main functions + */ + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + size_t len; + int ret, fd; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + fprintf(stderr, "path too long: %s\n", sockpath); + return -1; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + fprintf(stderr, "socket failed - %s\n", sockpath); + return -1; + } + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret != 0) { + fprintf(stderr, "bind failed - %s\n", sockpath); + goto fail; + } + + ret = listen(fd, 10); + if (ret != 0) { + fprintf(stderr, "listen failed\n"); + goto fail; + } + + DEBUG(DEBUG_INFO, ("Socket init done\n")); + + return fd; + +fail: + if (fd != -1) { + close(fd); + } + return -1; +} + +static struct options { + const char *dbdir; + const char *sockpath; + const char *pidfile; + const char *debuglevel; +} options; + +static struct poptOption cmdline_options[] = { + POPT_AUTOHELP + { "dbdir", 'D', POPT_ARG_STRING, &options.dbdir, 0, + "Database directory", "directory" }, + { "socket", 's', POPT_ARG_STRING, &options.sockpath, 0, + "Unix domain socket path", "filename" }, + { "pidfile", 'p', POPT_ARG_STRING, &options.pidfile, 0, + "pid file", "filename" } , + { "debug", 'd', POPT_ARG_STRING, &options.debuglevel, 0, + "debug level", "ERR|WARNING|NOTICE|INFO|DEBUG" } , + POPT_TABLEEND +}; + +static void cleanup(void) +{ + unlink(options.sockpath); + unlink(options.pidfile); +} + +static void signal_handler(int sig) +{ + cleanup(); + exit(0); +} + +static void start_server(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdbd_context *ctdb, int fd, int pfd) +{ + struct tevent_req *req; + int ret = 0; + ssize_t len; + + atexit(cleanup); + signal(SIGTERM, signal_handler); + + req = server_send(mem_ctx, ev, ctdb, fd); + if (req == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + len = write(pfd, &ret, sizeof(ret)); + if (len != sizeof(ret)) { + fprintf(stderr, "Failed to send message to parent\n"); + exit(1); + } + close(pfd); + + tevent_req_poll(req, ev); + + server_recv(req, &ret); + if (ret != 0) { + exit(1); + } +} + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *mem_ctx; + struct ctdbd_context *ctdb; + struct tevent_context *ev; + poptContext pc; + int opt, fd, ret, pfd[2]; + ssize_t len; + pid_t pid; + FILE *fp; + + pc = poptGetContext(argv[0], argc, argv, cmdline_options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s\n", poptBadOption(pc, 0)); + exit(1); + } + + if (options.dbdir == NULL) { + fprintf(stderr, "Please specify database directory\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.sockpath == NULL) { + fprintf(stderr, "Please specify socket path\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + if (options.pidfile == NULL) { + fprintf(stderr, "Please specify pid file\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + ret = logging_init(mem_ctx, "file:", options.debuglevel, "fake-ctdbd"); + if (ret != 0) { + fprintf(stderr, "Invalid debug level\n"); + poptPrintHelp(pc, stdout, 0); + exit(1); + } + + ctdb = ctdbd_setup(mem_ctx, options.dbdir); + if (ctdb == NULL) { + exit(1); + } + + if (! ctdbd_verify(ctdb)) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + + fd = socket_init(options.sockpath); + if (fd == -1) { + exit(1); + } + + ret = pipe(pfd); + if (ret != 0) { + fprintf(stderr, "Failed to create pipe\n"); + cleanup(); + exit(1); + } + + pid = fork(); + if (pid == -1) { + fprintf(stderr, "Failed to fork\n"); + cleanup(); + exit(1); + } + + if (pid == 0) { + /* Child */ + close(pfd[0]); + start_server(mem_ctx, ev, ctdb, fd, pfd[1]); + exit(1); + } + + /* Parent */ + close(pfd[1]); + + len = read(pfd[0], &ret, sizeof(ret)); + close(pfd[0]); + if (len != sizeof(ret)) { + fprintf(stderr, "len = %zi\n", len); + fprintf(stderr, "Failed to get message from child\n"); + kill(pid, SIGTERM); + exit(1); + } + + fp = fopen(options.pidfile, "w"); + if (fp == NULL) { + fprintf(stderr, "Failed to open pid file %s\n", + options.pidfile); + kill(pid, SIGTERM); + exit(1); + } + fprintf(fp, "%d\n", pid); + fclose(fp); + + return 0; +} diff --git a/ctdb/tests/src/fetch_loop.c b/ctdb/tests/src/fetch_loop.c new file mode 100644 index 0000000..0e1d9da --- /dev/null +++ b/ctdb/tests/src/fetch_loop.c @@ -0,0 +1,288 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_loop_key.c b/ctdb/tests/src/fetch_loop_key.c new file mode 100644 index 0000000..3f41ca7 --- /dev/null +++ b/ctdb/tests/src/fetch_loop_key.c @@ -0,0 +1,217 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_next(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->timelimit = timelimit; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, ctdb_db, + state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + return req; +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize == sizeof(uint32_t)) { + state->locks_count = *(uint32_t *)data.dptr; + } + TALLOC_FREE(data.dptr); + + state->locks_count += 1; + data.dsize = sizeof(uint32_t); + data.dptr = (uint8_t *)&state->locks_count; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + talloc_free(h); + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +static struct tevent_req *global_req; + +static void alarm_handler(int sig) +{ + struct fetch_loop_state *state = tevent_req_data( + global_req, struct fetch_loop_state); + static int time_passed = 0; + + time_passed += 1; + + printf("Locks:%d\n", state->locks_count); + fflush(stdout); + + if (time_passed >= state->timelimit) { + tevent_req_done(global_req); + } + + alarm(1); +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int ret; + bool status; + + setup_logging("fetch_loop_key", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + global_req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (global_req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + signal(SIGALRM, alarm_handler); + alarm(1); + + tevent_req_poll(global_req, ev); + + status = fetch_loop_recv(global_req, &ret); + if (! status) { + fprintf(stderr, "fetch loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly.c b/ctdb/tests/src/fetch_readonly.c new file mode 100644 index 0000000..ff126bd --- /dev/null +++ b/ctdb/tests/src/fetch_readonly.c @@ -0,0 +1,166 @@ +/* + Fetch a single record using readonly + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + + +struct fetch_readonly_state { + struct tevent_context *ev; +}; + +static void fetch_readonly_done(struct tevent_req *subreq); + +static struct tevent_req *fetch_readonly_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_readonly_state *state; + TDB_DATA key; + + req = tevent_req_create(mem_ctx, &state, struct fetch_readonly_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + + key.dptr = (uint8_t *)discard_const(keystr); + key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, ev, client, db, key, true); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_readonly_done, req); + + return req; +} + +static void fetch_readonly_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_readonly_state *state = tevent_req_data( + req, struct fetch_readonly_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + tevent_req_done(req); +} + +static bool fetch_readonly_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, %s\n", + strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_readonly_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_readonly_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_readonly_loop.c b/ctdb/tests/src/fetch_readonly_loop.c new file mode 100644 index 0000000..08cf476 --- /dev/null +++ b/ctdb/tests/src/fetch_readonly_loop.c @@ -0,0 +1,272 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTDB "fetch_readonly_loop.tdb" +#define TESTKEY "testkey" + +struct fetch_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + TDB_DATA key; + int locks_count; +}; + +static void fetch_loop_start(struct tevent_req *subreq); +static void fetch_loop_next(struct tevent_req *subreq); +static void fetch_loop_each_second(struct tevent_req *subreq); +static void fetch_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *fetch_loop_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct fetch_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->key.dptr = discard_const(TESTKEY); + state->key.dsize = strlen(TESTKEY); + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_loop_start, req); + + return req; +} + +static void fetch_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_finish, req); +} + +static void fetch_loop_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + struct ctdb_record_handle *h; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + state->locks_count += 1; + talloc_free(h); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, true); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_next, req); +} + +static void fetch_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\r", state->locks_count); + fflush(stdout); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_loop_each_second, req); +} + +static void fetch_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_loop_state *state = tevent_req_data( + req, struct fetch_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("Locks:%d\n", state->locks_count); + + tevent_req_done(req); +} + +static bool fetch_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_readonly_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), TESTDB, 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", TESTDB); + exit(1); + } + + req = fetch_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "fetch readonly loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/fetch_ring.c b/ctdb/tests/src/fetch_ring.c new file mode 100644 index 0000000..f1786ef --- /dev/null +++ b/ctdb/tests/src/fetch_ring.c @@ -0,0 +1,398 @@ +/* + simple ctdb benchmark + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_FETCH 0 + +static uint32_t next_node(struct ctdb_client_context *client, uint32_t num_nodes) +{ + return (ctdb_client_pnn(client) + 1) % num_nodes; +} + +struct fetch_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + uint32_t num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + int msg_count; + struct timeval start_time; +}; + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void fetch_ring_wait(struct tevent_req *subreq); +static void fetch_ring_start(struct tevent_req *subreq); +static void fetch_ring_update(struct tevent_req *subreq); +static void fetch_ring_msg_sent(struct tevent_req *subreq); +static void fetch_ring_finish(struct tevent_req *subreq); +static void fetch_ring_final_read(struct tevent_req *subreq); + +static struct tevent_req *fetch_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + const char *keystr, + uint32_t num_nodes, + int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct fetch_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct fetch_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_client_set_message_handler_send( + state, ev, client, MSG_ID_FETCH, + fetch_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, fetch_ring_wait, req); + + return req; +} + +static void fetch_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct tevent_req *subreq; + + state->msg_count += 1; + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); +} + +static void fetch_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_start, req); +} + +static void fetch_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == state->num_nodes-1) { + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_update, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_finish, req); + +} + +static void fetch_ring_update(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + struct ctdb_req_message msg; + TDB_DATA data; + uint32_t pnn; + int ret; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + if (data.dsize > 1000) { + TALLOC_FREE(data.dptr); + data.dsize = 0; + } + + if (data.dsize == 0) { + data.dptr = (uint8_t *)talloc_asprintf(state, "Test data\n"); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + data.dptr = (uint8_t *)talloc_asprintf_append( + (char *)data.dptr, + "msg_count=%d on node %d\n", + state->msg_count, + ctdb_client_pnn(state->client)); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + + data.dsize = strlen((const char *)data.dptr) + 1; + + ret = ctdb_store_record(h, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(data.dptr); + talloc_free(h); + + msg.srvid = MSG_ID_FETCH; + msg.data.data = tdb_null; + + pnn = next_node(state->client, state->num_nodes); + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_msg_sent, req); +} + +static void fetch_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void fetch_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Fetch[%u]: %.2f msgs/sec\n", ctdb_client_pnn(state->client), + state->msg_count / t); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->ctdb_db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, fetch_ring_final_read, req); +} + +static void fetch_ring_final_read(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct fetch_ring_state *state = tevent_req_data( + req, struct fetch_ring_state); + struct ctdb_record_handle *h; + TDB_DATA data; + int err; + + h = ctdb_fetch_lock_recv(subreq, NULL, state, &data, &err); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, err); + return; + } + + if (state->interactive == 1) { + printf("DATA:\n%s\n", (char *)data.dptr); + } + talloc_free(data.dptr); + talloc_free(h); + + tevent_req_done(req); +} + +static bool fetch_ring_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("fetch_ring", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, + client, + tevent_timeval_zero(), + opts->dbname, + 0, + &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to DB %s\n", opts->dbname); + exit(1); + } + + req = fetch_ring_send(mem_ctx, + ev, + client, + ctdb_db, + opts->keystr, + opts->num_nodes, + opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = fetch_ring_recv(req, NULL); + if (! status) { + fprintf(stderr, "fetch ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/g_lock_loop.c b/ctdb/tests/src/g_lock_loop.c new file mode 100644 index 0000000..3b84241 --- /dev/null +++ b/ctdb/tests/src/g_lock_loop.c @@ -0,0 +1,270 @@ +/* + simple ctdb benchmark for g_lock operations + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/tevent_unix.h" +#include "lib/util/debug.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define TESTKEY "testkey" + +struct glock_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int num_nodes; + int timelimit; + uint32_t pnn; + uint32_t counter; + struct ctdb_server_id sid; + const char *key; +}; + +static void glock_loop_start(struct tevent_req *subreq); +static void glock_loop_locked(struct tevent_req *subreq); +static void glock_loop_unlocked(struct tevent_req *subreq); +static void glock_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *glock_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct glock_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct glock_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->counter = 0; + state->sid = ctdb_client_get_server_id(client, 1); + state->key = TESTKEY; + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, glock_loop_start, req); + + return req; +} + +static void glock_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_finish, req); +} + +static void glock_loop_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_lock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_lock failed\n"); + tevent_req_error(req, ret); + return; + } + + state->counter += 1; + + subreq = ctdb_g_lock_unlock_send(state, state->ev, state->client, + state->db, state->key, state->sid); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_unlocked, req); +} + +static void glock_loop_unlocked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + int ret; + bool status; + + status = ctdb_g_lock_unlock_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "g_lock_unlock failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_g_lock_lock_send(state, state->ev, state->client, + state->db, state->key, &state->sid, + false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, glock_loop_locked, req); +} + +static void glock_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct glock_loop_state *state = tevent_req_data( + req, struct glock_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("PNN:%u counter:%u\n", state->pnn, state->counter); + + tevent_req_done(req); +} + +static bool glock_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("glock_loop", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), "g_lock.tdb", + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to g_lock.tdb\n"); + exit(1); + } + + req = glock_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = glock_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "g_lock loop test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/hash_count_test.c b/ctdb/tests/src/hash_count_test.c new file mode 100644 index 0000000..6ddde08 --- /dev/null +++ b/ctdb/tests/src/hash_count_test.c @@ -0,0 +1,132 @@ +/* + hash_count tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" +#include "common/hash_count.c" + +#define KEY "this_is_a_test_key" + +static void test1_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + int *count = (int *)private_data; + + assert(key.dsize == strlen(KEY)); + assert(strcmp((char *)key.dptr, KEY) == 0); + assert(counter > 0); + + (*count) += 1; +} + +static void do_test1(void) +{ + struct hash_count_context *hc = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + int count = 0; + int ret, i; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_increment(hc, key); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, NULL, NULL, &hc); + assert(ret == EINVAL); + + ret = hash_count_init(mem_ctx, interval, test1_handler, &count, &hc); + assert(ret == 0); + assert(hc != NULL); + + for (i=0; i<10; i++) { + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == i+1); + } + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +static void test2_handler(TDB_DATA key, uint64_t counter, void *private_data) +{ + uint64_t *count = (uint64_t *)private_data; + + *count = counter; +} + +static void do_test2(void) +{ + struct hash_count_context *hc; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct timeval interval = {1, 0}; + TDB_DATA key; + uint64_t count = 0; + int ret; + + key.dptr = (uint8_t *)discard_const(KEY); + key.dsize = strlen(KEY); + + ret = hash_count_init(mem_ctx, interval, test2_handler, &count, &hc); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + hash_count_expire(hc, &ret); + assert(ret == 0); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 2); + + sleep(2); + + ret = hash_count_increment(hc, key); + assert(ret == 0); + assert(count == 1); + + sleep(2); + + hash_count_expire(hc, &ret); + assert(ret == 1); + + talloc_free(hc); + ret = talloc_get_size(mem_ctx); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +int main(void) +{ + do_test1(); + do_test2(); + + return 0; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.c b/ctdb/tests/src/ipalloc_read_known_ips.c new file mode 100644 index 0000000..33d0f94 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.c @@ -0,0 +1,179 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "protocol/protocol.h" +#include "protocol/protocol_util.h" +#include "common/logging.h" + +#include "ipalloc_read_known_ips.h" + +static bool add_ip(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *l, + ctdb_sock_addr *addr, + uint32_t pnn) +{ + + l->ip = talloc_realloc(mem_ctx, l->ip, + struct ctdb_public_ip, l->num + 1); + if (l->ip == NULL) { + D_ERR(__location__ " out of memory\n"); + return false; + } + + l->ip[l->num].addr = *addr; + l->ip[l->num].pnn = pnn; + l->num++; + + return true; +} + +/* Format of each line is "IP CURRENT_PNN [ALLOWED_PNN,...]". + * If multi is true then ALLOWED_PNNs are not allowed. */ +static bool read_ctdb_public_ip_info_node(bool multi, + int numnodes, + struct ctdb_public_ip_list **k, + struct ctdb_public_ip_list *known) +{ + char line[1024]; + ctdb_sock_addr addr; + char *t, *tok; + int pnn, n; + + /* Known public IPs */ + *k = talloc_zero(known, struct ctdb_public_ip_list); + if (*k == NULL) { + goto fail; + } + + while (fgets(line, sizeof(line), stdin) != NULL) { + int ret; + + /* Get rid of pesky newline */ + if ((t = strchr(line, '\n')) != NULL) { + *t = '\0'; + } + + /* Exit on an empty line */ + if (line[0] == '\0') { + break; + } + + /* Get the IP address */ + tok = strtok(line, " \t"); + if (tok == NULL) { + D_WARNING("WARNING, bad line ignored :%s\n", line); + continue; + } + + ret = ctdb_sock_addr_from_string(tok, &addr, false); + if (ret != 0) { + D_ERR("ERROR, bad address :%s\n", tok); + continue; + } + + /* Get the PNN */ + pnn = -1; + tok = strtok(NULL, " \t"); + if (tok != NULL) { + pnn = (int) strtol(tok, (char **) NULL, 10); + } + + if (! add_ip(*k, *k, &addr, pnn)) { + goto fail; + } + + tok = strtok(NULL, " \t#"); + if (tok == NULL) { + if (! multi) { + for (n = 0; n < numnodes; n++) { + if (! add_ip(known, &known[n], + &addr, pnn)) { + goto fail; + } + } + } + continue; + } + + /* Handle allowed nodes for addr */ + if (multi) { + D_ERR("ERROR, bad token\n"); + goto fail; + } + t = strtok(tok, ","); + while (t != NULL) { + n = (int) strtol(t, (char **) NULL, 10); + if (! add_ip(known, &known[n], &addr, pnn)) { + goto fail; + } + t = strtok(NULL, ","); + } + } + + return true; + +fail: + TALLOC_FREE(*k); + return false; +} + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi) +{ + int n; + struct ctdb_public_ip_list *k; + struct ctdb_public_ip_list *known; + + known = talloc_zero_array(ctx, struct ctdb_public_ip_list, + numnodes); + if (known == NULL) { + D_ERR(__location__ " out of memory\n"); + goto fail; + } + + if (multi) { + for (n = 0; n < numnodes; n++) { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + + known[n] = *k; + } + } else { + if (! read_ctdb_public_ip_info_node(multi, numnodes, + &k, known)) { + goto fail; + } + } + + return known; + +fail: + talloc_free(known); + return NULL; +} diff --git a/ctdb/tests/src/ipalloc_read_known_ips.h b/ctdb/tests/src/ipalloc_read_known_ips.h new file mode 100644 index 0000000..aa6d154 --- /dev/null +++ b/ctdb/tests/src/ipalloc_read_known_ips.h @@ -0,0 +1,32 @@ +/* + Tests support for CTDB IP allocation + + Copyright (C) Martin Schwenke 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __IPALLOC_READ_KNOWN_IPS_H__ +#define __IPALLOC_READ_KNOWN_IPS_H__ + +#include <stdbool.h> +#include <talloc.h> + +#include "protocol/protocol.h" + +struct ctdb_public_ip_list * ipalloc_read_known_ips(TALLOC_CTX *ctx, + int numnodes, + bool multi); + +#endif /* __IPALLOC_READ_KNOWN_IPS_H__ */ diff --git a/ctdb/tests/src/line_test.c b/ctdb/tests/src/line_test.c new file mode 100644 index 0000000..806d883 --- /dev/null +++ b/ctdb/tests/src/line_test.c @@ -0,0 +1,102 @@ +/* + Test code for line based I/O over fds + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/line.c" + +static int line_print(char *line, void *private_data) +{ + printf("%s\n", line); + fflush(stdout); + + return 0; +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + size_t hint = 32; + pid_t pid; + int ret, lines = 0; + int pipefd[2]; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "Usage: %s <filename> [<hint>]\n", argv[0]); + exit(1); + } + + if (argc == 3) { + long value; + + value = atol(argv[2]); + assert(value > 0); + hint = value; + } + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + char buffer[16]; + ssize_t n, n2; + int fd; + + close(pipefd[0]); + + fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + while (1) { + n = read(fd, buffer, sizeof(buffer)); + assert(n >= 0 && (size_t)n <= sizeof(buffer)); + + if (n == 0) { + break; + } + + n2 = write(pipefd[1], buffer, n); + assert(n2 == n); + } + + close(pipefd[1]); + close(fd); + + exit(0); + } + + close(pipefd[1]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ret = line_read(pipefd[0], hint, NULL, line_print, NULL, &lines); + assert(ret == 0); + + talloc_free(mem_ctx); + + return lines; +} diff --git a/ctdb/tests/src/lock_tdb.c b/ctdb/tests/src/lock_tdb.c new file mode 100644 index 0000000..c37f846 --- /dev/null +++ b/ctdb/tests/src/lock_tdb.c @@ -0,0 +1,60 @@ +/* + Lock a tdb and sleep + + Copyright (C) Amitay Isaacs 2012 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <tdb.h> + +const char *tdb_file; +TDB_CONTEXT *tdb; + +static void signal_handler(int signum) +{ + tdb_close(tdb); +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + printf("Usage: %s <tdb file>\n", argv[0]); + exit(1); + } + + tdb_file = argv[1]; + + tdb = tdb_open(tdb_file, 0, 0, O_RDWR, 0); + if (tdb == NULL) { + fprintf(stderr, "Failed to open TDB file %s\n", tdb_file); + exit(1); + } + + signal(SIGINT, signal_handler); + + if (tdb_lockall(tdb) != 0) { + fprintf(stderr, "Failed to lock database %s\n", tdb_file); + tdb_close(tdb); + exit(1); + } + + sleep(999999); + + return 0; +} diff --git a/ctdb/tests/src/message_ring.c b/ctdb/tests/src/message_ring.c new file mode 100644 index 0000000..d1fcee4 --- /dev/null +++ b/ctdb/tests/src/message_ring.c @@ -0,0 +1,369 @@ +/* + simple ctdb benchmark - send messages in a ring around cluster + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +#define MSG_ID_BENCH 0 + +struct message_ring_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + int interactive; + int msg_count; + int msg_plus, msg_minus; + struct timeval start_time; +}; + +static void message_ring_wait(struct tevent_req *subreq); +static void message_ring_start(struct tevent_req *subreq); +static void message_ring_each_second(struct tevent_req *subreq); +static void message_ring_msg_sent(struct tevent_req *subreq); +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data); +static void message_ring_finish(struct tevent_req *subreq); + +static struct tevent_req *message_ring_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit, + int interactive) +{ + struct tevent_req *req, *subreq; + struct message_ring_state *state; + + req = tevent_req_create(mem_ctx, &state, struct message_ring_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + + subreq = ctdb_client_set_message_handler_send( + state, state->ev, state->client, + MSG_ID_BENCH, + message_ring_msg_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, message_ring_wait, req); + + return req; +} + +static void message_ring_wait(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = ctdb_client_set_message_handler_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_start, req); +} + +static void message_ring_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->start_time = tevent_timeval_current(); + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_finish, req); +} + +static uint32_t next_node(struct ctdb_client_context *client, + int num_nodes, int incr) +{ + return (ctdb_client_pnn(client) + num_nodes + incr) % num_nodes; +} + +static void message_ring_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + uint32_t pnn; + int incr; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + pnn = ctdb_client_pnn(state->client); + if (pnn == 0 && state->interactive == 1) { + double t; + + t = timeval_elapsed(&state->start_time); + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + pnn, state->msg_count / t, + state->msg_plus, state->msg_minus); + fflush(stdout); + } + + if (state->msg_plus == 0) { + incr = 1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + if (state->msg_minus == 0) { + incr = -1; + + msg.srvid = 0; + msg.data.data.dptr = (uint8_t *)&incr; + msg.data.data.dsize = sizeof(incr); + + pnn = next_node(state->client, state->num_nodes, incr); + + subreq = ctdb_client_message_send(state, state->ev, + state->client, pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_each_second, req); +} + +static void message_ring_msg_sent(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int ret; + + status = ctdb_client_message_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + } +} + +static void message_ring_msg_handler(uint64_t srvid, TDB_DATA data, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + struct ctdb_req_message msg; + struct tevent_req *subreq; + int incr; + uint32_t pnn; + + if (srvid != MSG_ID_BENCH) { + return; + } + + if (data.dsize != sizeof(int)) { + return; + } + incr = *(int *)data.dptr; + + state->msg_count += 1; + if (incr == 1) { + state->msg_plus += 1; + } else { + state->msg_minus += 1; + } + + pnn = next_node(state->client, state->num_nodes, incr); + + msg.srvid = srvid; + msg.data.data = data; + + subreq = ctdb_client_message_send(state, state->ev, state->client, + pnn, &msg); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, message_ring_msg_sent, req); +} + +static void message_ring_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct message_ring_state *state = tevent_req_data( + req, struct message_ring_state); + bool status; + double t; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + t = timeval_elapsed(&state->start_time); + + printf("Ring[%u]: %.2f msgs/sec (+ve=%d -ve=%d)\n", + ctdb_client_pnn(state->client), state->msg_count / t, + state->msg_plus, state->msg_minus); + + tevent_req_done(req); +} + +static bool message_ring_recv(struct tevent_req *req) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("message_ring", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Failed to wait for recovery\n"); + exit(1); + } + + req = message_ring_send(mem_ctx, ev, client, + opts->num_nodes, opts->timelimit, + opts->interactive); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = message_ring_recv(req); + if (! status) { + fprintf(stderr, "message ring test failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/pidfile_test.c b/ctdb/tests/src/pidfile_test.c new file mode 100644 index 0000000..592fc2b --- /dev/null +++ b/ctdb/tests/src/pidfile_test.c @@ -0,0 +1,242 @@ +/* + pidfile tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/pidfile.c" + + +/* create pid file, check pid file exists, check pid and remove pid file */ +static void test1(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + int ret; + struct stat st; + FILE *fp; + pid_t pid; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid); + assert(ret == 1); + assert(pid == getpid()); + fclose(fp); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file in two processes */ +static void test2(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + FILE *fp; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(10); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + fp = fopen(pidfile, "r"); + assert(fp != NULL); + ret = fscanf(fp, "%d", &pid2); + assert(ret == 1); + assert(pid == pid2); + fclose(fp); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret != 0); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, fork, try to remove pid file in separate process */ +static void test3(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + TALLOC_FREE(pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); + + ret = stat(pidfile, &st); + assert(ret == -1); +} + +/* create pid file, kill process, overwrite pid file in different process */ +static void test4(const char *pidfile) +{ + struct pidfile_context *pid_ctx; + pid_t pid, pid2; + int fd[2]; + int ret; + size_t nread; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + ssize_t nwritten; + + close(fd[0]); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + + nwritten = write(fd[1], &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + sleep(99); + exit(1); + } + + close(fd[1]); + + nread = read(fd[0], &ret, sizeof(ret)); + assert(nread == sizeof(ret)); + assert(ret == 0); + + ret = stat(pidfile, &st); + assert(ret == 0); + + ret = kill(pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + + ret = pidfile_context_create(NULL, pidfile, &pid_ctx); + assert(ret == 0); + assert(pid_ctx != NULL); + + ret = stat(pidfile, &st); + assert(ret == 0); + + TALLOC_FREE(pid_ctx); +} + +int main(int argc, const char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s <pidfile>\n", argv[0]); + exit(1); + } + + test1(argv[1]); + test2(argv[1]); + test3(argv[1]); + test4(argv[1]); + + return 0; +} diff --git a/ctdb/tests/src/pkt_read_test.c b/ctdb/tests/src/pkt_read_test.c new file mode 100644 index 0000000..a3ebe0a --- /dev/null +++ b/ctdb/tests/src/pkt_read_test.c @@ -0,0 +1,249 @@ +/* + packet read tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" + +static void writer(int fd) +{ + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, j; + int ret; + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<1000; i++) { + for (j=0; j<4; j++) { + buflen = pkt_size[j]; + memcpy(buf, &buflen, sizeof(buflen)); + + ret = write(fd, buf, buflen); + if (ret < 0) { + printf("write error: %s\n", strerror(errno)); + assert(ret > 0); + } + } + } + + close(fd); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, + size_t buflen) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, state->buflen, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd, bool fixed) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + uint8_t *buf = NULL; + size_t buflen = 0; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + if (fixed) { + buflen = 1024; + buf = talloc_size(mem_ctx, buflen); + assert(buf != NULL); + } + + req = reader_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +static void reader_test(bool fixed) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0], fixed); +} + +int main(void) +{ + reader_test(true); + reader_test(false); + + return 0; +} diff --git a/ctdb/tests/src/pkt_write_test.c b/ctdb/tests/src/pkt_write_test.c new file mode 100644 index 0000000..dae92a5 --- /dev/null +++ b/ctdb/tests/src/pkt_write_test.c @@ -0,0 +1,359 @@ +/* + packet write tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "lib/util/blocking.h" + +#include "common/pkt_read.c" +#include "common/pkt_write.c" + +struct writer_state { + struct tevent_context *ev; + int fd; + uint8_t *buf; + size_t buflen; + int count; + struct tevent_req *subreq; +}; + +static void writer_next(struct tevent_req *subreq); + +static struct tevent_req *writer_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, uint8_t *buf, size_t buflen) +{ + struct tevent_req *req, *subreq; + struct writer_state *state; + + req = tevent_req_create(mem_ctx, &state, struct writer_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + state->buf = buf; + state->buflen = buflen; + state->count = 0; + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); + return req; +} + +static void writer_next(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + ssize_t nwritten; + int err = 0; + + nwritten = pkt_write_recv(subreq, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nwritten == -1) { + tevent_req_error(req, err); + return; + } + + if ((size_t)nwritten != state->buflen) { + tevent_req_error(req, EIO); + return; + } + + state->count++; + if (state->count >= 1000) { + tevent_req_done(req); + return; + } + + subreq = pkt_write_send(state, state->ev, state->fd, + state->buf, state->buflen); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, writer_next, req); +} + +static void writer_recv(struct tevent_req *req, int *perr) +{ + struct writer_state *state = tevent_req_data( + req, struct writer_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + return; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void writer_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct writer_state *state = tevent_req_data( + req, struct writer_state); + + assert(state->subreq != NULL); + pkt_write_handler(ev, fde, flags, state->subreq); +} + +static void writer(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + uint8_t buf[1024*1024]; + size_t buflen; + size_t pkt_size[4] = { 100, 500, 1024, 1024*1024 }; + int i, err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + for (i=0; i<1024*1024; i++) { + buf[i] = i%256; + } + + for (i=0; i<4; i++) { + buflen = pkt_size[i]; + memcpy(buf, &buflen, sizeof(buflen)); + + req = writer_send(mem_ctx, ev, fd, buf, buflen); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_WRITE, + writer_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + writer_recv(req, &err); + assert(err == 0); + + talloc_free(fde); + talloc_free(req); + } + + close(fd); + + talloc_free(mem_ctx); +} + +struct reader_state { + struct tevent_context *ev; + int fd; + uint8_t buf[1024]; + struct tevent_req *subreq; +}; + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data); +static void reader_done(struct tevent_req *subreq); + +static struct tevent_req *reader_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd) +{ + struct tevent_req *req, *subreq; + struct reader_state *state; + + req = tevent_req_create(mem_ctx, &state, struct reader_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->fd = fd; + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + tevent_req_post(req, ev); + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); + return req; +} + +static ssize_t reader_more(uint8_t *buf, size_t buflen, void *private_data) +{ + uint32_t pkt_len; + + if (buflen < sizeof(pkt_len)) { + return sizeof(pkt_len) - buflen; + } + + pkt_len = *(uint32_t *)buf; + return pkt_len - buflen; +} + +static void reader_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + ssize_t nread; + uint8_t *buf; + bool free_buf; + int err = 0; + + nread = pkt_read_recv(subreq, state, &buf, &free_buf, &err); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (nread == -1) { + if (err == EPIPE) { + tevent_req_done(req); + } else { + tevent_req_error(req, err); + } + return; + } + + if (free_buf) { + talloc_free(buf); + } + + subreq = pkt_read_send(state, state->ev, state->fd, 4, + state->buf, 1024, reader_more, NULL); + if (tevent_req_nomem(subreq, req)) { + return; + } + + state->subreq = subreq; + tevent_req_set_callback(subreq, reader_done, req); +} + +static void reader_recv(struct tevent_req *req, int *perr) +{ + struct reader_state *state = tevent_req_data( + req, struct reader_state); + int err = 0; + + if (state->subreq != NULL) { + *perr = -1; + } + + if (tevent_req_is_unix_error(req, &err)) { + *perr = err; + return; + } + + *perr = 0; +} + +static void reader_handler(struct tevent_context *ev, struct tevent_fd *fde, + uint16_t flags, void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct reader_state *state = tevent_req_data( + req, struct reader_state); + + assert(state->subreq != NULL); + pkt_read_handler(ev, fde, flags, state->subreq); +} + +static void reader(int fd) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_fd *fde; + struct tevent_req *req; + int err; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + req = reader_send(mem_ctx, ev, fd); + assert(req != NULL); + + fde = tevent_add_fd(ev, mem_ctx, fd, TEVENT_FD_READ, + reader_handler, req); + assert(fde != NULL); + + tevent_req_poll(req, ev); + + reader_recv(req, &err); + assert(err == 0); + + close(fd); + + talloc_free(mem_ctx); +} + +int main(void) +{ + int fd[2]; + int ret; + pid_t pid; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Child process */ + close(fd[0]); + writer(fd[1]); + exit(0); + } + + close(fd[1]); + ret = set_blocking(fd[0], false); + if (ret == -1) { + exit(1); + } + + reader(fd[0]); + + return 0; +} diff --git a/ctdb/tests/src/porting_tests.c b/ctdb/tests/src/porting_tests.c new file mode 100644 index 0000000..00618d2 --- /dev/null +++ b/ctdb/tests/src/porting_tests.c @@ -0,0 +1,262 @@ +/* + Test porting lib (common/system_*.c) + + Copyright (C) Mathieu Parent 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" + +#include <popt.h> +#include <talloc.h> +#include <tevent.h> +#include <tdb.h> +#include <assert.h> + +#include "lib/util/debug.h" +#include "lib/util/blocking.h" + +#include "protocol/protocol.h" +#include "common/system.h" +#include "common/logging.h" + + +static struct { + const char *socketname; + const char *debuglevel; + pid_t helper_pid; + int socket; +} globals = { + .socketname = "/tmp/test.sock" +}; + + + +/* + Socket functions +*/ +/* + create a unix domain socket and bind it + return a file descriptor open on the socket +*/ +static int socket_server_create(void) +{ + struct sockaddr_un addr; + int ret; + + globals.socket = socket(AF_UNIX, SOCK_STREAM, 0); + assert(globals.socket != -1); + + set_close_on_exec(globals.socket); + //set_blocking(globals.socket, false); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = bind(globals.socket, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + ret = chown(globals.socketname, geteuid(), getegid()); + assert(ret == 0); + + ret = chmod(globals.socketname, 0700); + assert(ret == 0); + + ret = listen(globals.socket, 100); + assert(ret == 0); + + return 0; +} + +static int socket_server_wait_peer(void) +{ + struct sockaddr_un addr; + socklen_t len; + int fd; + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + fd = accept(globals.socket, (struct sockaddr *)&addr, &len); + assert(fd != -1); + + //set_blocking(fd, false); + set_close_on_exec(fd); + return fd; +} + +static int socket_server_close(void) +{ + int ret; + + ret = close(globals.socket); + assert(ret == 0); + + ret = unlink(globals.socketname); + assert(ret == 0); + + return 0; +} + +static int socket_client_connect(void) +{ + struct sockaddr_un addr; + int client = 0; + int ret; + + client = socket(AF_UNIX, SOCK_STREAM, 0); + assert(client != -1); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, globals.socketname, sizeof(addr.sun_path)-1); + + ret = connect(client, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret == 0); + + return client; +} + +static int socket_client_close(int client) +{ + int ret; + + ret = close(client); + assert(ret == 0); + + return 0; +} + +/* + forked program +*/ +static int fork_helper(void) +{ + pid_t pid; + int client; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { // Child + pid = getppid(); + client = socket_client_connect(); + while (kill(pid, 0) == 0) { + sleep(1); + } + socket_client_close(client); + exit(0); + } else { + globals.helper_pid = pid; + } + return 0; +} + +/* + tests +*/ +static int test_ctdb_sys_check_iface_exists(void) +{ + bool test1, test2; + + test1 = ctdb_sys_check_iface_exists("unlikely123xyz"); + assert(!test1); + + /* Linux and others */ + test1 = ctdb_sys_check_iface_exists("lo"); + /* FreeBSD */ + test2 = ctdb_sys_check_iface_exists("lo0"); + assert(test1 || test2); + + return 0; +} + +static int test_ctdb_get_peer_pid(void) +{ + int ret; + int fd; + pid_t peer_pid = 0; + + fd = socket_server_wait_peer(); + + ret = ctdb_get_peer_pid(fd, &peer_pid); + assert(ret == 0 || ret == ENOSYS); + + if (ret == 0) { + assert(peer_pid == globals.helper_pid); + + kill(peer_pid, SIGTERM); + } else { + kill(globals.helper_pid, SIGTERM); + } + + close(fd); + return 0; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + struct poptOption popt_options[] = { + POPT_AUTOHELP + { "socket", 0, POPT_ARG_STRING, &globals.socketname, 0, "local socket name", "filename" }, + POPT_TABLEEND + }; + int opt, ret; + const char **extra_argv; + int extra_argc = 0; + poptContext pc; + + pc = poptGetContext(argv[0], argc, argv, popt_options, POPT_CONTEXT_KEEP_FIRST); + + while ((opt = poptGetNextOpt(pc)) != -1) { + switch (opt) { + default: + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + exit(1); + } + } + + /* setup the remaining options for the main program to use */ + extra_argv = poptGetArgs(pc); + if (extra_argv) { + extra_argv++; + while (extra_argv[extra_argc]) extra_argc++; + } + + assert(globals.socketname != NULL); + + ret = socket_server_create(); + assert(ret == 0); + + /* FIXME: Test tcp_checksum6, tcp_checksum */ + /* FIXME: Test ctdb_sys_send_arp, ctdb_sys_send_tcp */ + /* FIXME: Test ctdb_sys_{open,close}_capture_socket, ctdb_sys_read_tcp_packet */ + test_ctdb_sys_check_iface_exists(); + + ret = fork_helper(); + assert(ret == 0); + test_ctdb_get_peer_pid(); + + ret = socket_server_close(); + assert(ret == 0); + + return 0; +} diff --git a/ctdb/tests/src/protocol_basic_test.c b/ctdb/tests/src/protocol_basic_test.c new file mode 100644 index 0000000..7046718 --- /dev/null +++ b/ctdb/tests/src/protocol_basic_test.c @@ -0,0 +1,106 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "protocol/protocol_basic.c" + +#include "tests/src/protocol_common_basic.h" + +PROTOCOL_TYPE1_TEST(uint8_t, ctdb_uint8); +PROTOCOL_TYPE1_TEST(uint16_t, ctdb_uint16); +PROTOCOL_TYPE1_TEST(int32_t, ctdb_int32); +PROTOCOL_TYPE1_TEST(uint32_t, ctdb_uint32); +PROTOCOL_TYPE1_TEST(uint64_t, ctdb_uint64); +PROTOCOL_TYPE1_TEST(double, ctdb_double); +PROTOCOL_TYPE1_TEST(bool, ctdb_bool); + +static void test_ctdb_chararray(void) +{ + size_t len = rand_int(1000) + 1; + char p1[len], p2[len]; + size_t buflen, np = 0; + size_t i; + int ret; + + for (i=0; i<len-1; i++) { + p1[i] = 'A' + rand_int(26); + } + p1[len-1] = '\0'; + buflen = ctdb_chararray_len(p1, len); + assert(buflen < sizeof(BUFFER)); + ctdb_chararray_push(p1, len, BUFFER, &np); + assert(np == buflen); + np = 0; + ret = ctdb_chararray_pull(BUFFER, buflen, p2, len, &np); + assert(ret == 0); + assert(np == buflen); + assert(strncmp(p1, p2, len) == 0); +} + +PROTOCOL_TYPE2_TEST(const char *, ctdb_string); +PROTOCOL_TYPE2_TEST(const char *, ctdb_stringn); + +PROTOCOL_TYPE1_TEST(pid_t, ctdb_pid); +PROTOCOL_TYPE1_TEST(struct timeval, ctdb_timeval); + +static void test_ctdb_padding(void) +{ + int padding; + size_t buflen, np = 0; + int ret; + + padding = rand_int(8); + + buflen = ctdb_padding_len(padding); + assert(buflen < sizeof(BUFFER)); + ctdb_padding_push(padding, BUFFER, &np); + assert(np == buflen); + np = 0; + ret = ctdb_padding_pull(BUFFER, buflen, padding, &np); + assert(ret == 0); + assert(np == buflen); +} + +static void protocol_basic_test(void) +{ + TEST_FUNC(ctdb_uint8)(); + TEST_FUNC(ctdb_uint16)(); + TEST_FUNC(ctdb_int32)(); + TEST_FUNC(ctdb_uint32)(); + TEST_FUNC(ctdb_uint64)(); + TEST_FUNC(ctdb_double)(); + TEST_FUNC(ctdb_bool)(); + + test_ctdb_chararray(); + + TEST_FUNC(ctdb_string)(); + TEST_FUNC(ctdb_stringn)(); + + TEST_FUNC(ctdb_pid)(); + TEST_FUNC(ctdb_timeval)(); + + test_ctdb_padding(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_basic_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_common.c b/ctdb/tests/src/protocol_common.c new file mode 100644 index 0000000..212c23c --- /dev/null +++ b/ctdb/tests/src/protocol_common.c @@ -0,0 +1,1260 @@ +/* + protocol tests - common functions + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "protocol/protocol_api.h" + +#include "tests/src/protocol_common_basic.h" +#include "tests/src/protocol_common.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + p->dsize = rand_int(1024) + 1; + p->dptr = talloc_array(mem_ctx, uint8_t, p->dsize); + assert(p->dptr != NULL); + fill_buffer(p->dptr, p->dsize); +} + +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + if (rand_int(5) == 0) { + p->dsize = 0; + p->dptr = NULL; + } else { + fill_tdb_data_nonnull(mem_ctx, p); + } +} + +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + assert(p1->dsize == p2->dsize); + verify_buffer(p1->dptr, p2->dptr, p1->dsize); +} + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p) +{ + fill_tdb_data(mem_ctx, p); +} + +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2) +{ + verify_tdb_data(p1, p2); +} + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p) +{ + p->num = rand32i(); + p->min = rand_double(); + p->max = rand_double(); + p->total = rand_double(); +} + +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2) +{ + assert(p1->num == p2->num); + assert(p1->min == p2->min); + assert(p1->max == p2->max); + assert(p1->total == p2->total); +} + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p) +{ + int i; + + p->num_clients = rand32(); + p->frozen = rand32(); + p->recovering = rand32(); + p->client_packets_sent = rand32(); + p->client_packets_recv = rand32(); + p->node_packets_sent = rand32(); + p->node_packets_recv = rand32(); + p->keepalive_packets_sent = rand32(); + p->keepalive_packets_recv = rand32(); + + p->node.req_call = rand32(); + p->node.reply_call = rand32(); + p->node.req_dmaster = rand32(); + p->node.reply_dmaster = rand32(); + p->node.reply_error = rand32(); + p->node.req_message = rand32(); + p->node.req_control = rand32(); + p->node.reply_control = rand32(); + + p->client.req_call = rand32(); + p->client.req_message = rand32(); + p->client.req_control = rand32(); + + p->timeouts.call = rand32(); + p->timeouts.control = rand32(); + p->timeouts.traverse = rand32(); + + fill_ctdb_latency_counter(&p->reclock.ctdbd); + fill_ctdb_latency_counter(&p->reclock.recd); + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->locks.buckets[i] = rand32(); + } + + p->total_calls = rand32(); + p->pending_calls = rand32(); + p->childwrite_calls = rand32(); + p->pending_childwrite_calls = rand32(); + p->memory_used = rand32(); + p->__last_counter = rand32(); + p->max_hop_count = rand32(); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->hop_count_bucket[i] = rand32(); + } + fill_ctdb_latency_counter(&p->call_latency); + fill_ctdb_latency_counter(&p->childwrite_latency); + p->num_recoveries = rand32(); + fill_ctdb_timeval(&p->statistics_start_time); + fill_ctdb_timeval(&p->statistics_current_time); + p->total_ro_delegations = rand32(); + p->total_ro_revokes = rand32(); +} + +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2) +{ + int i; + + assert(p1->num_clients == p2->num_clients); + assert(p1->frozen == p2->frozen); + assert(p1->recovering == p2->recovering); + assert(p1->client_packets_sent == p2->client_packets_sent); + assert(p1->client_packets_recv == p2->client_packets_recv); + assert(p1->node_packets_sent == p2->node_packets_sent); + assert(p1->node_packets_recv == p2->node_packets_recv); + assert(p1->keepalive_packets_sent == p2->keepalive_packets_sent); + assert(p1->keepalive_packets_recv == p2->keepalive_packets_recv); + + assert(p1->node.req_call == p2->node.req_call); + assert(p1->node.reply_call == p2->node.reply_call); + assert(p1->node.req_dmaster == p2->node.req_dmaster); + assert(p1->node.reply_dmaster == p2->node.reply_dmaster); + assert(p1->node.reply_error == p2->node.reply_error); + assert(p1->node.req_message == p2->node.req_message); + assert(p1->node.req_control == p2->node.req_control); + assert(p1->node.reply_control == p2->node.reply_control); + + assert(p1->client.req_call == p2->client.req_call); + assert(p1->client.req_message == p2->client.req_message); + assert(p1->client.req_control == p2->client.req_control); + + assert(p1->timeouts.call == p2->timeouts.call); + assert(p1->timeouts.control == p2->timeouts.control); + assert(p1->timeouts.traverse == p2->timeouts.traverse); + + verify_ctdb_latency_counter(&p1->reclock.ctdbd, &p2->reclock.ctdbd); + verify_ctdb_latency_counter(&p1->reclock.recd, &p2->reclock.recd); + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->locks.buckets[i] == p2->locks.buckets[i]); + } + + assert(p1->total_calls == p2->total_calls); + assert(p1->pending_calls == p2->pending_calls); + assert(p1->childwrite_calls == p2->childwrite_calls); + assert(p1->pending_childwrite_calls == p2->pending_childwrite_calls); + assert(p1->memory_used == p2->memory_used); + assert(p1->__last_counter == p2->__last_counter); + assert(p1->max_hop_count == p2->max_hop_count); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]); + } + verify_ctdb_latency_counter(&p1->call_latency, &p2->call_latency); + verify_ctdb_latency_counter(&p1->childwrite_latency, + &p2->childwrite_latency); + assert(p1->num_recoveries == p2->num_recoveries); + verify_ctdb_timeval(&p1->statistics_start_time, + &p2->statistics_start_time); + verify_ctdb_timeval(&p1->statistics_current_time, + &p2->statistics_current_time); + assert(p1->total_ro_delegations == p2->total_ro_delegations); + assert(p1->total_ro_revokes == p2->total_ro_revokes); +} + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p) +{ + unsigned int i; + + p->generation = rand32(); + p->size = rand_int(20); + if (p->size > 0) { + p->map = talloc_array(mem_ctx, uint32_t, p->size); + assert(p->map != NULL); + + for (i=0; i<p->size; i++) { + p->map[i] = rand32(); + } + } else { + p->map = NULL; + } +} + +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2) +{ + unsigned int i; + + assert(p1->generation == p2->generation); + assert(p1->size == p2->size); + for (i=0; i<p1->size; i++) { + assert(p1->map[i] == p2->map[i]); + } +} + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p) +{ + p->db_id = rand32(); + p->flags = rand8(); +} + +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p) +{ + unsigned int i; + + p->num = rand_int(40); + if (p->num > 0) { + p->dbs = talloc_zero_array(mem_ctx, struct ctdb_dbid, p->num); + assert(p->dbs != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_dbid(mem_ctx, &p->dbs[i]); + } + } else { + p->dbs = NULL; + } +} + +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_dbid(&p1->dbs[i], &p2->dbs[i]); + } +} + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); +} + +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); +} + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p) +{ + p->db_id = rand32(); + p->lmaster = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->lmaster == p2->lmaster); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p) +{ + fill_ctdb_uint32(&p->db_id); + fill_ctdb_bool(&p->full_vacuum_run); +} + +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2) +{ + verify_ctdb_uint32(&p1->db_id, &p2->db_id); + verify_ctdb_bool(&p1->full_vacuum_run, &p2->full_vacuum_run); +} + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p) +{ + fill_ctdb_uint32(&p->timeout); + fill_tdb_data(mem_ctx, &p->buf); +} + +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2) +{ + verify_ctdb_uint32(&p1->timeout, &p2->timeout); + verify_tdb_data(&p1->buf, &p2->buf); +} + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p) +{ + p->rsn = rand64(); + p->dmaster = rand32(); + p->reserved1 = rand32(); + p->flags = rand32(); +} + +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2) +{ + assert(p1->rsn == p2->rsn); + assert(p1->dmaster == p2->dmaster); + assert(p1->reserved1 == p2->reserved1); + assert(p1->flags == p2->flags); +} + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p) +{ + p->reqid = rand32(); + if (p->reqid % 5 == 0) { + p->header = talloc(mem_ctx, struct ctdb_ltdb_header); + assert(p->header != NULL); + fill_ctdb_ltdb_header(p->header); + } else { + p->header = NULL; + } + fill_tdb_data_nonnull(mem_ctx, &p->key); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2) +{ + struct ctdb_ltdb_header header; + + assert(p1->reqid == p2->reqid); + if (p1->header != NULL) { + assert(ctdb_ltdb_header_extract(&p2->data, &header) == 0); + verify_ctdb_ltdb_header(p1->header, &header); + } + verify_tdb_data(&p1->key, &p2->key); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p) +{ + struct ctdb_rec_data rec; + int ret, i; + int count; + + p->db_id = rand32(); + p->count = 0; + p->buf = NULL; + p->buflen = 0; + + count = rand_int(100); + if (count > 0) { + for (i=0; i<count; i++) { + fill_ctdb_rec_data(mem_ctx, &rec); + ret = ctdb_rec_buffer_add(mem_ctx, p, rec.reqid, + rec.header, + rec.key, rec.data); + assert(ret == 0); + } + } +} + +void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1, + struct ctdb_rec_buffer *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->count == p2->count); + assert(p1->buflen == p2->buflen); + verify_buffer(p1->buf, p2->buf, p1->buflen); +} + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p) +{ + p->db_id = rand32(); + p->reqid = rand32(); + p->pnn = rand32(); + p->client_reqid = rand32(); + p->srvid = rand64(); + p->withemptyrecords = rand_int(2); +} + +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->reqid == p2->reqid); + assert(p1->pnn == p2->pnn); + assert(p1->client_reqid == p2->client_reqid); + assert(p1->srvid == p2->srvid); + assert(p1->withemptyrecords == p2->withemptyrecords); +} + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p) +{ + if (rand_int(2) == 0) { + p->ip.sin_family = AF_INET; + p->ip.sin_port = rand_int(65535); + fill_buffer(&p->ip.sin_addr, sizeof(struct in_addr)); + } else { + p->ip6.sin6_family = AF_INET6; + p->ip6.sin6_port = rand_int(65535); + fill_buffer(&p->ip6.sin6_addr, sizeof(struct in6_addr)); + } +} + +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2) +{ + assert(p1->sa.sa_family == p2->sa.sa_family); + if (p1->sa.sa_family == AF_INET) { + assert(p1->ip.sin_port == p2->ip.sin_port); + verify_buffer(&p1->ip.sin_addr, &p2->ip.sin_addr, + sizeof(struct in_addr)); + } else { + assert(p1->ip6.sin6_port == p2->ip6.sin6_port); + verify_buffer(&p1->ip6.sin6_addr, &p2->ip6.sin6_addr, + sizeof(struct in6_addr)); + } +} + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->src); + fill_ctdb_sock_addr(mem_ctx, &p->dst); +} + +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2) +{ + verify_ctdb_sock_addr(&p1->src, &p2->src); + verify_ctdb_sock_addr(&p1->dst, &p2->dst); +} + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p) +{ + uint32_t i; + + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2) +{ + uint32_t i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p) +{ + fill_ctdb_string(mem_ctx, &p->name); + p->value = rand32(); +} + +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2) +{ + verify_ctdb_string(&p1->name, &p2->name); + assert(p1->value == p2->value); +} + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p) +{ + p->pnn = rand32(); + p->new_flags = rand32(); + p->old_flags = rand32(); +} + +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->new_flags == p2->new_flags); + assert(p1->old_flags == p2->old_flags); +} + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p) +{ + int i; + + p->count = rand_int(100) + 1; + p->var = talloc_array(mem_ctx, const char *, p->count); + for (i=0; i<p->count; i++) { + fill_ctdb_string(p->var, &p->var[i]); + } +} + +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2) +{ + int i; + + assert(p1->count == p2->count); + for (i=0; i<p1->count; i++) { + verify_ctdb_string(&p1->var[i], &p2->var[i]); + } +} + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p) +{ + p->max_redirect_count = rand32(); + p->seqnum_interval = rand32(); + p->control_timeout = rand32(); + p->traverse_timeout = rand32(); + p->keepalive_interval = rand32(); + p->keepalive_limit = rand32(); + p->recover_timeout = rand32(); + p->recover_interval = rand32(); + p->election_timeout = rand32(); + p->takeover_timeout = rand32(); + p->monitor_interval = rand32(); + p->tickle_update_interval = rand32(); + p->script_timeout = rand32(); + p->monitor_timeout_count = rand32(); + p->script_unhealthy_on_timeout = rand32(); + p->recovery_grace_period = rand32(); + p->recovery_ban_period = rand32(); + p->database_hash_size = rand32(); + p->database_max_dead = rand32(); + p->rerecovery_timeout = rand32(); + p->enable_bans = rand32(); + p->deterministic_public_ips = rand32(); + p->reclock_ping_period = rand32(); + p->no_ip_failback = rand32(); + p->disable_ip_failover = rand32(); + p->verbose_memory_names = rand32(); + p->recd_ping_timeout = rand32(); + p->recd_ping_failcount = rand32(); + p->log_latency_ms = rand32(); + p->reclock_latency_ms = rand32(); + p->recovery_drop_all_ips = rand32(); + p->verify_recovery_lock = rand32(); + p->vacuum_interval = rand32(); + p->vacuum_max_run_time = rand32(); + p->repack_limit = rand32(); + p->vacuum_limit = rand32(); + p->max_queue_depth_drop_msg = rand32(); + p->allow_unhealthy_db_read = rand32(); + p->stat_history_interval = rand32(); + p->deferred_attach_timeout = rand32(); + p->vacuum_fast_path_count = rand32(); + p->lcp2_public_ip_assignment = rand32(); + p->allow_client_db_attach = rand32(); + p->recover_pdb_by_seqnum = rand32(); + p->deferred_rebalance_on_node_add = rand32(); + p->fetch_collapse = rand32(); + p->hopcount_make_sticky = rand32(); + p->sticky_duration = rand32(); + p->sticky_pindown = rand32(); + p->no_ip_takeover = rand32(); + p->db_record_count_warn = rand32(); + p->db_record_size_warn = rand32(); + p->db_size_warn = rand32(); + p->pulldb_preallocation_size = rand32(); + p->no_ip_host_on_all_disabled = rand32(); + p->samba3_hack = rand32(); + p->mutex_enabled = rand32(); + p->lock_processes_per_db = rand32(); + p->rec_buffer_size_limit = rand32(); + p->queue_buffer_size = rand32(); + p->ip_alloc_algorithm = rand32(); + p->allow_mixed_versions = rand32(); +} + +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2) +{ + assert(p1->max_redirect_count == p2->max_redirect_count); + assert(p1->seqnum_interval == p2->seqnum_interval); + assert(p1->control_timeout == p2->control_timeout); + assert(p1->traverse_timeout == p2->traverse_timeout); + assert(p1->keepalive_interval == p2->keepalive_interval); + assert(p1->keepalive_limit == p2->keepalive_limit); + assert(p1->recover_timeout == p2->recover_timeout); + assert(p1->recover_interval == p2->recover_interval); + assert(p1->election_timeout == p2->election_timeout); + assert(p1->takeover_timeout == p2->takeover_timeout); + assert(p1->monitor_interval == p2->monitor_interval); + assert(p1->tickle_update_interval == p2->tickle_update_interval); + assert(p1->script_timeout == p2->script_timeout); + assert(p1->monitor_timeout_count == p2->monitor_timeout_count); + assert(p1->script_unhealthy_on_timeout == p2->script_unhealthy_on_timeout); + assert(p1->recovery_grace_period == p2->recovery_grace_period); + assert(p1->recovery_ban_period == p2->recovery_ban_period); + assert(p1->database_hash_size == p2->database_hash_size); + assert(p1->database_max_dead == p2->database_max_dead); + assert(p1->rerecovery_timeout == p2->rerecovery_timeout); + assert(p1->enable_bans == p2->enable_bans); + assert(p1->deterministic_public_ips == p2->deterministic_public_ips); + assert(p1->reclock_ping_period == p2->reclock_ping_period); + assert(p1->no_ip_failback == p2->no_ip_failback); + assert(p1->disable_ip_failover == p2->disable_ip_failover); + assert(p1->verbose_memory_names == p2->verbose_memory_names); + assert(p1->recd_ping_timeout == p2->recd_ping_timeout); + assert(p1->recd_ping_failcount == p2->recd_ping_failcount); + assert(p1->log_latency_ms == p2->log_latency_ms); + assert(p1->reclock_latency_ms == p2->reclock_latency_ms); + assert(p1->recovery_drop_all_ips == p2->recovery_drop_all_ips); + assert(p1->verify_recovery_lock == p2->verify_recovery_lock); + assert(p1->vacuum_interval == p2->vacuum_interval); + assert(p1->vacuum_max_run_time == p2->vacuum_max_run_time); + assert(p1->repack_limit == p2->repack_limit); + assert(p1->vacuum_limit == p2->vacuum_limit); + assert(p1->max_queue_depth_drop_msg == p2->max_queue_depth_drop_msg); + assert(p1->allow_unhealthy_db_read == p2->allow_unhealthy_db_read); + assert(p1->stat_history_interval == p2->stat_history_interval); + assert(p1->deferred_attach_timeout == p2->deferred_attach_timeout); + assert(p1->vacuum_fast_path_count == p2->vacuum_fast_path_count); + assert(p1->lcp2_public_ip_assignment == p2->lcp2_public_ip_assignment); + assert(p1->allow_client_db_attach == p2->allow_client_db_attach); + assert(p1->recover_pdb_by_seqnum == p2->recover_pdb_by_seqnum); + assert(p1->deferred_rebalance_on_node_add == p2->deferred_rebalance_on_node_add); + assert(p1->fetch_collapse == p2->fetch_collapse); + assert(p1->hopcount_make_sticky == p2->hopcount_make_sticky); + assert(p1->sticky_duration == p2->sticky_duration); + assert(p1->sticky_pindown == p2->sticky_pindown); + assert(p1->no_ip_takeover == p2->no_ip_takeover); + assert(p1->db_record_count_warn == p2->db_record_count_warn); + assert(p1->db_record_size_warn == p2->db_record_size_warn); + assert(p1->db_size_warn == p2->db_size_warn); + assert(p1->pulldb_preallocation_size == p2->pulldb_preallocation_size); + assert(p1->no_ip_host_on_all_disabled == p2->no_ip_host_on_all_disabled); + assert(p1->samba3_hack == p2->samba3_hack); + assert(p1->mutex_enabled == p2->mutex_enabled); + assert(p1->lock_processes_per_db == p2->lock_processes_per_db); + assert(p1->rec_buffer_size_limit == p2->rec_buffer_size_limit); + assert(p1->queue_buffer_size == p2->queue_buffer_size); + assert(p1->ip_alloc_algorithm == p2->ip_alloc_algorithm); + assert(p1->allow_mixed_versions == p2->allow_mixed_versions); +} + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p) +{ + unsigned int i; + + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->num = rand_int(1000); + if (p->num > 0) { + p->conn = talloc_array(mem_ctx, struct ctdb_connection, p->num); + assert(p->conn != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_connection(mem_ctx, &p->conn[i]); + } + } else { + p->conn = NULL; + } +} + +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2) +{ + unsigned int i; + + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_connection(&p1->conn[i], &p2->conn[i]); + } +} + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p) +{ + fill_ctdb_sock_addr(mem_ctx, &p->addr); + p->mask = rand_int(33); + if (rand_int(2) == 0) { + p->iface = NULL; + } else { + fill_ctdb_string(mem_ctx, &p->iface); + } +} + +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2) +{ + verify_ctdb_sock_addr(&p1->addr, &p2->addr); + assert(p1->mask == p2->mask); + verify_ctdb_string(&p1->iface, &p2->iface); +} + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p) +{ + p->db_id = rand32(); + p->tid = rand32(); +} + +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2) +{ + assert(p1->db_id == p2->db_id); + assert(p1->tid == p2->tid); +} + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p) +{ + fill_ctdb_timeval(&p->current_time); + fill_ctdb_timeval(&p->ctdbd_start_time); + fill_ctdb_timeval(&p->last_recovery_started); + fill_ctdb_timeval(&p->last_recovery_finished); +} + +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2) +{ + verify_ctdb_timeval(&p1->current_time, &p2->current_time); + verify_ctdb_timeval(&p1->ctdbd_start_time, &p2->ctdbd_start_time); + verify_ctdb_timeval(&p1->last_recovery_started, + &p2->last_recovery_started); + verify_ctdb_timeval(&p1->last_recovery_finished, + &p2->last_recovery_finished); +} + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p) +{ + p->pnn = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2) +{ + assert(p1->pnn == p2->pnn); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->ip = talloc_array(mem_ctx, struct ctdb_public_ip, p->num); + assert(p->ip != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_public_ip(mem_ctx, &p->ip[i]); + } + } else { + p->ip = NULL; + } +} + +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_public_ip(&p1->ip[i], &p2->ip[i]); + } +} + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p) +{ + p->pnn = rand32(); + p->flags = rand32(); + fill_ctdb_sock_addr(mem_ctx, &p->addr); +} + +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->flags == p2->flags); + verify_ctdb_sock_addr(&p1->addr, &p2->addr); +} + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->node = talloc_array(mem_ctx, struct ctdb_node_and_flags, + p->num); + assert(p->node != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_node_and_flags(mem_ctx, &p->node[i]); + } + } else { + p->node = NULL; + } +} + +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_node_and_flags(&p1->node[i], &p2->node[i]); + } +} + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p) +{ + fill_string(p->name, MAX_SCRIPT_NAME+1); + fill_ctdb_timeval(&p->start); + fill_ctdb_timeval(&p->finished); + p->status = rand32i(); + fill_string(p->output, MAX_SCRIPT_OUTPUT+1); +} + +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2) +{ + verify_string(p1->name, p2->name); + verify_ctdb_timeval(&p1->start, &p2->start); + verify_ctdb_timeval(&p1->finished, &p2->finished); + assert(p1->status == p2->status); + verify_string(p1->output, p2->output); +} + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p) +{ + unsigned int i; + + p->num_scripts = rand_int(32); + if (p->num_scripts > 0) { + p->script = talloc_zero_array(mem_ctx, struct ctdb_script, + p->num_scripts); + assert(p->script != NULL); + for (i=0; i<p->num_scripts; i++) { + fill_ctdb_script(mem_ctx, &p->script[i]); + } + } else { + p->script = NULL; + } +} + +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2) +{ + unsigned int i; + + assert(p1->num_scripts == p2->num_scripts); + for (i=0; i<p1->num_scripts; i++) { + verify_ctdb_script(&p1->script[i], &p2->script[i]); + } +} + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p) +{ + p->pnn = rand32(); + p->time = rand32(); +} + +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->time == p2->time); +} + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p) +{ + p->srvid = rand64(); + fill_tdb_data(mem_ctx, &p->data); +} + +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2) +{ + assert(p1->srvid == p2->srvid); + verify_tdb_data(&p1->data, &p2->data); +} + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p) +{ + fill_string(p->name, CTDB_IFACE_SIZE+2); + p->link_state = rand16(); + p->references = rand32(); +} + +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2) +{ + verify_string(p1->name, p2->name); + assert(p1->link_state == p2->link_state); + assert(p1->references == p2->references); +} + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p) +{ + unsigned int i; + + p->num = rand_int(32); + if (p->num > 0) { + p->iface = talloc_array(mem_ctx, struct ctdb_iface, p->num); + assert(p->iface != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_iface(mem_ctx, &p->iface[i]); + } + } else { + p->iface = NULL; + } +} + +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_iface(&p1->iface[i], &p2->iface[i]); + } +} + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p) +{ + fill_ctdb_public_ip(mem_ctx, &p->ip); + p->active_idx = rand_int(32) + 1; + p->ifaces = talloc(mem_ctx, struct ctdb_iface_list); + assert(p->ifaces != NULL); + fill_ctdb_iface_list(mem_ctx, p->ifaces); +} + +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2) +{ + verify_ctdb_public_ip(&p1->ip, &p2->ip); + assert(p1->active_idx == p2->active_idx); + verify_ctdb_iface_list(p1->ifaces, p2->ifaces); +} + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p) +{ + int i; + + p->num = rand_int(10); + if (p->num > 0) { + p->stats = talloc_zero_array(mem_ctx, struct ctdb_statistics, + p->num); + assert(p->stats != NULL); + + for (i=0; i<p->num; i++) { + fill_ctdb_statistics(mem_ctx, &p->stats[i]); + } + } else { + p->stats = NULL; + } +} + +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2) +{ + int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_statistics(&p1->stats[i], &p2->stats[i]); + } +} + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p) +{ + p->db_id = rand32(); + fill_ctdb_ltdb_header(&p->header); + fill_tdb_data_nonnull(mem_ctx, &p->key); +} + +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2) +{ + assert(p1->db_id == p2->db_id); + verify_ctdb_ltdb_header(&p1->header, &p2->header); + verify_tdb_data(&p1->key, &p2->key); +} + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p) +{ + unsigned int i; + + p->locks.num_calls = rand32(); + p->locks.num_current = rand32(); + p->locks.num_pending = rand32(); + p->locks.num_failed = rand32(); + fill_ctdb_latency_counter(&p->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->locks.buckets[i] = rand32(); + } + + fill_ctdb_latency_counter(&p->vacuum.latency); + + p->db_ro_delegations = rand32(); + p->db_ro_revokes = rand32(); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + p->hop_count_bucket[i] = rand32(); + } + + p->num_hot_keys = MAX_HOT_KEYS; + for (i=0; i<p->num_hot_keys; i++) { + p->hot_keys[i].count = rand32(); + fill_tdb_data(mem_ctx, &p->hot_keys[i].key); + } +} + +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2) +{ + unsigned int i; + + assert(p1->locks.num_calls == p2->locks.num_calls); + assert(p1->locks.num_current == p2->locks.num_current); + assert(p1->locks.num_pending == p2->locks.num_pending); + assert(p1->locks.num_failed == p2->locks.num_failed); + verify_ctdb_latency_counter(&p1->locks.latency, &p2->locks.latency); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->locks.buckets[i] == p2->locks.buckets[i]); + } + + verify_ctdb_latency_counter(&p1->vacuum.latency, &p2->vacuum.latency); + + assert(p1->db_ro_delegations == p2->db_ro_delegations); + assert(p1->db_ro_revokes == p2->db_ro_revokes); + for (i=0; i<MAX_COUNT_BUCKETS; i++) { + assert(p1->hop_count_bucket[i] == p2->hop_count_bucket[i]); + } + + assert(p1->num_hot_keys == p2->num_hot_keys); + for (i=0; i<p1->num_hot_keys; i++) { + assert(p1->hot_keys[i].count == p2->hot_keys[i].count); + verify_tdb_data(&p1->hot_keys[i].key, &p2->hot_keys[i].key); + } +} + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p) +{ + p->pid = rand32(); + p->srvid = rand64(); +} + +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p) +{ + p->num_connected = rand_int(32); + fill_ctdb_timeval(&p->priority_time); + p->pnn = rand_int(32); + p->node_flags = rand32(); +} + +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2) +{ + assert(p1->num_connected == p2->num_connected); + verify_ctdb_timeval(&p1->priority_time, &p2->priority_time); + assert(p1->pnn == p2->pnn); + assert(p1->node_flags == p2->node_flags); +} + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); +} + +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); +} + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p) +{ + p->pnn = rand_int(32); + p->srvid = rand64(); + p->timeout = rand32(); +} + +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2) +{ + assert(p1->pnn == p2->pnn); + assert(p1->srvid == p2->srvid); + assert(p1->timeout == p2->timeout); +} + +void fill_ctdb_server_id(struct ctdb_server_id *p) +{ + p->pid = rand64(); + p->task_id = rand32(); + p->vnn = rand_int(32); + p->unique_id = rand64(); +} + +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2) +{ + assert(p1->pid == p2->pid); + assert(p1->task_id == p2->task_id); + assert(p1->vnn == p2->vnn); + assert(p1->unique_id == p2->unique_id); +} + +void fill_ctdb_g_lock(struct ctdb_g_lock *p) +{ + p->type = rand_int(2); + fill_ctdb_server_id(&p->sid); +} + +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2) +{ + assert(p1->type == p2->type); + verify_ctdb_server_id(&p1->sid, &p2->sid); +} + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p) +{ + unsigned int i; + + p->num = rand_int(20) + 1; + p->lock = talloc_zero_array(mem_ctx, struct ctdb_g_lock, p->num); + assert(p->lock != NULL); + for (i=0; i<p->num; i++) { + fill_ctdb_g_lock(&p->lock[i]); + } +} + +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2) +{ + unsigned int i; + + assert(p1->num == p2->num); + for (i=0; i<p1->num; i++) { + verify_ctdb_g_lock(&p1->lock[i], &p2->lock[i]); + } +} + +void fill_sock_packet_header(struct sock_packet_header *p) +{ + p->length = rand32(); + p->reqid = rand32(); +} + +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2) +{ + assert(p1->length == p2->length); + assert(p1->reqid == p2->reqid); +} diff --git a/ctdb/tests/src/protocol_common.h b/ctdb/tests/src/protocol_common.h new file mode 100644 index 0000000..171b19b --- /dev/null +++ b/ctdb/tests/src/protocol_common.h @@ -0,0 +1,238 @@ +/* + protocol tests - common functions + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_H__ +#define __CTDB_PROTOCOL_COMMON_H__ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" + +#include "tests/src/protocol_common_basic.h" + +void fill_tdb_data_nonnull(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void fill_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_data(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_data(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_tdb_datan(TALLOC_CTX *mem_ctx, TDB_DATA *p); +void verify_ctdb_tdb_datan(TDB_DATA *p1, TDB_DATA *p2); + +void fill_ctdb_latency_counter(struct ctdb_latency_counter *p); +void verify_ctdb_latency_counter(struct ctdb_latency_counter *p1, + struct ctdb_latency_counter *p2); + +void fill_ctdb_statistics(TALLOC_CTX *mem_ctx, struct ctdb_statistics *p); +void verify_ctdb_statistics(struct ctdb_statistics *p1, + struct ctdb_statistics *p2); + +void fill_ctdb_vnn_map(TALLOC_CTX *mem_ctx, struct ctdb_vnn_map *p); +void verify_ctdb_vnn_map(struct ctdb_vnn_map *p1, struct ctdb_vnn_map *p2); + +void fill_ctdb_dbid(TALLOC_CTX *mem_ctx, struct ctdb_dbid *p); +void verify_ctdb_dbid(struct ctdb_dbid *p1, struct ctdb_dbid *p2); + +void fill_ctdb_dbid_map(TALLOC_CTX *mem_ctx, struct ctdb_dbid_map *p); +void verify_ctdb_dbid_map(struct ctdb_dbid_map *p1, struct ctdb_dbid_map *p2); + +void fill_ctdb_pulldb(TALLOC_CTX *mem_ctx, struct ctdb_pulldb *p); +void verify_ctdb_pulldb(struct ctdb_pulldb *p1, struct ctdb_pulldb *p2); + +void fill_ctdb_pulldb_ext(TALLOC_CTX *mem_ctx, struct ctdb_pulldb_ext *p); +void verify_ctdb_pulldb_ext(struct ctdb_pulldb_ext *p1, + struct ctdb_pulldb_ext *p2); + +void fill_ctdb_db_vacuum(TALLOC_CTX *mem_ctx, struct ctdb_db_vacuum *p); +void verify_ctdb_db_vacuum(struct ctdb_db_vacuum *p1, + struct ctdb_db_vacuum *p2); + +void fill_ctdb_echo_data(TALLOC_CTX *mem_ctx, struct ctdb_echo_data *p); +void verify_ctdb_echo_data(struct ctdb_echo_data *p1, + struct ctdb_echo_data *p2); + +void fill_ctdb_ltdb_header(struct ctdb_ltdb_header *p); +void verify_ctdb_ltdb_header(struct ctdb_ltdb_header *p1, + struct ctdb_ltdb_header *p2); + +void fill_ctdb_rec_data(TALLOC_CTX *mem_ctx, struct ctdb_rec_data *p); +void verify_ctdb_rec_data(struct ctdb_rec_data *p1, struct ctdb_rec_data *p2); + +void fill_ctdb_rec_buffer(TALLOC_CTX *mem_ctx, struct ctdb_rec_buffer *p); +void verify_ctdb_rec_buffer(struct ctdb_rec_buffer *p1, + struct ctdb_rec_buffer *p2); + +void fill_ctdb_traverse_start(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start *p); +void verify_ctdb_traverse_start(struct ctdb_traverse_start *p1, + struct ctdb_traverse_start *p2); + +void fill_ctdb_traverse_all(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all *p); +void verify_ctdb_traverse_all(struct ctdb_traverse_all *p1, + struct ctdb_traverse_all *p2); + +void fill_ctdb_traverse_start_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext *p); +void verify_ctdb_traverse_start_ext(struct ctdb_traverse_start_ext *p1, + struct ctdb_traverse_start_ext *p2); + +void fill_ctdb_traverse_all_ext(TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext *p); +void verify_ctdb_traverse_all_ext(struct ctdb_traverse_all_ext *p1, + struct ctdb_traverse_all_ext *p2); + +void fill_ctdb_sock_addr(TALLOC_CTX *mem_ctx, ctdb_sock_addr *p); +void verify_ctdb_sock_addr(ctdb_sock_addr *p1, ctdb_sock_addr *p2); + +void fill_ctdb_connection(TALLOC_CTX *mem_ctx, struct ctdb_connection *p); +void verify_ctdb_connection(struct ctdb_connection *p1, + struct ctdb_connection *p2); + +void fill_ctdb_connection_list(TALLOC_CTX *mem_ctx, + struct ctdb_connection_list *p); +void verify_ctdb_connection_list(struct ctdb_connection_list *p1, + struct ctdb_connection_list *p2); + +void fill_ctdb_tunable(TALLOC_CTX *mem_ctx, struct ctdb_tunable *p); +void verify_ctdb_tunable(struct ctdb_tunable *p1, struct ctdb_tunable *p2); + +void fill_ctdb_node_flag_change(TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change *p); +void verify_ctdb_node_flag_change(struct ctdb_node_flag_change *p1, + struct ctdb_node_flag_change *p2); + +void fill_ctdb_var_list(TALLOC_CTX *mem_ctx, struct ctdb_var_list *p); +void verify_ctdb_var_list(struct ctdb_var_list *p1, struct ctdb_var_list *p2); + +void fill_ctdb_tunable_list(TALLOC_CTX *mem_ctx, struct ctdb_tunable_list *p); +void verify_ctdb_tunable_list(struct ctdb_tunable_list *p1, + struct ctdb_tunable_list *p2); + +void fill_ctdb_tickle_list(TALLOC_CTX *mem_ctx, struct ctdb_tickle_list *p); +void verify_ctdb_tickle_list(struct ctdb_tickle_list *p1, + struct ctdb_tickle_list *p2); + +void fill_ctdb_addr_info(TALLOC_CTX *mem_ctx, struct ctdb_addr_info *p); +void verify_ctdb_addr_info(struct ctdb_addr_info *p1, + struct ctdb_addr_info *p2); + +void fill_ctdb_transdb(TALLOC_CTX *mem_ctx, struct ctdb_transdb *p); +void verify_ctdb_transdb(struct ctdb_transdb *p1, struct ctdb_transdb *p2); + +void fill_ctdb_uptime(TALLOC_CTX *mem_ctx, struct ctdb_uptime *p); +void verify_ctdb_uptime(struct ctdb_uptime *p1, struct ctdb_uptime *p2); + +void fill_ctdb_public_ip(TALLOC_CTX *mem_ctx, struct ctdb_public_ip *p); +void verify_ctdb_public_ip(struct ctdb_public_ip *p1, + struct ctdb_public_ip *p2); + +void fill_ctdb_public_ip_list(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list *p); +void verify_ctdb_public_ip_list(struct ctdb_public_ip_list *p1, + struct ctdb_public_ip_list *p2); + +void fill_ctdb_node_and_flags(TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags *p); +void verify_ctdb_node_and_flags(struct ctdb_node_and_flags *p1, + struct ctdb_node_and_flags *p2); + +void fill_ctdb_node_map(TALLOC_CTX *mem_ctx, struct ctdb_node_map *p); +void verify_ctdb_node_map(struct ctdb_node_map *p1, struct ctdb_node_map *p2); + +void fill_ctdb_script(TALLOC_CTX *mem_ctx, struct ctdb_script *p); +void verify_ctdb_script(struct ctdb_script *p1, struct ctdb_script *p2); + +void fill_ctdb_script_list(TALLOC_CTX *mem_ctx, struct ctdb_script_list *p); +void verify_ctdb_script_list(struct ctdb_script_list *p1, + struct ctdb_script_list *p2); + +void fill_ctdb_ban_state(TALLOC_CTX *mem_ctx, struct ctdb_ban_state *p); +void verify_ctdb_ban_state(struct ctdb_ban_state *p1, + struct ctdb_ban_state *p2); + +void fill_ctdb_notify_data(TALLOC_CTX *mem_ctx, struct ctdb_notify_data *p); +void verify_ctdb_notify_data(struct ctdb_notify_data *p1, + struct ctdb_notify_data *p2); + +void fill_ctdb_iface(TALLOC_CTX *mem_ctx, struct ctdb_iface *p); +void verify_ctdb_iface(struct ctdb_iface *p1, struct ctdb_iface *p2); + +void fill_ctdb_iface_list(TALLOC_CTX *mem_ctx, struct ctdb_iface_list *p); +void verify_ctdb_iface_list(struct ctdb_iface_list *p1, + struct ctdb_iface_list *p2); + +void fill_ctdb_public_ip_info(TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info *p); +void verify_ctdb_public_ip_info(struct ctdb_public_ip_info *p1, + struct ctdb_public_ip_info *p2); + +void fill_ctdb_statistics_list(TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list *p); +void verify_ctdb_statistics_list(struct ctdb_statistics_list *p1, + struct ctdb_statistics_list *p2); + +void fill_ctdb_key_data(TALLOC_CTX *mem_ctx, struct ctdb_key_data *p); +void verify_ctdb_key_data(struct ctdb_key_data *p1, struct ctdb_key_data *p2); + +void fill_ctdb_db_statistics(TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics *p); +void verify_ctdb_db_statistics(struct ctdb_db_statistics *p1, + struct ctdb_db_statistics *p2); + +void fill_ctdb_pid_srvid(TALLOC_CTX *mem_ctx, struct ctdb_pid_srvid *p); +void verify_ctdb_pid_srvid(struct ctdb_pid_srvid *p1, + struct ctdb_pid_srvid *p2); + +void fill_ctdb_election_message(TALLOC_CTX *mem_ctx, + struct ctdb_election_message *p); +void verify_ctdb_election_message(struct ctdb_election_message *p1, + struct ctdb_election_message *p2); + +void fill_ctdb_srvid_message(TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message *p); +void verify_ctdb_srvid_message(struct ctdb_srvid_message *p1, + struct ctdb_srvid_message *p2); + +void fill_ctdb_disable_message(TALLOC_CTX *mem_ctx, + struct ctdb_disable_message *p); +void verify_ctdb_disable_message(struct ctdb_disable_message *p1, + struct ctdb_disable_message *p2); + +void fill_ctdb_server_id(struct ctdb_server_id *p); +void verify_ctdb_server_id(struct ctdb_server_id *p1, + struct ctdb_server_id *p2); + +void fill_ctdb_g_lock(struct ctdb_g_lock *p); +void verify_ctdb_g_lock(struct ctdb_g_lock *p1, struct ctdb_g_lock *p2); + +void fill_ctdb_g_lock_list(TALLOC_CTX *mem_ctx, struct ctdb_g_lock_list *p); +void verify_ctdb_g_lock_list(struct ctdb_g_lock_list *p1, + struct ctdb_g_lock_list *p2); + +void fill_sock_packet_header(struct sock_packet_header *p); +void verify_sock_packet_header(struct sock_packet_header *p1, + struct sock_packet_header *p2); + +#endif /* __CTDB_PROTOCOL_COMMON_H__ */ diff --git a/ctdb/tests/src/protocol_common_basic.c b/ctdb/tests/src/protocol_common_basic.c new file mode 100644 index 0000000..7567f7b --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.c @@ -0,0 +1,305 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/wait.h" + +#include <assert.h> + +#include "lib/util/fault.h" + +#include "tests/src/protocol_common_basic.h" + +uint8_t BUFFER[1024*1024]; + +/* + * Functions to generation random data + */ + +int rand_int(int max) +{ + return random() % max; +} + +uint8_t rand8(void) +{ + uint8_t val = rand_int(256) & 0xff; + return val; +} + +uint16_t rand16(void) +{ + uint16_t val = rand_int(0xffff) & 0xffff; + return val; +} + +int32_t rand32i(void) +{ + return INT_MIN + random(); +} + +uint32_t rand32(void) +{ + return random(); +} + +uint64_t rand64(void) +{ + uint64_t t = random(); + t = (t << 32) | random(); + return t; +} + +double rand_double(void) +{ + return 1.0 / rand64(); +} + +void fill_buffer(void *p, size_t len) +{ + size_t i; + uint8_t *ptr = p; + + for (i=0; i<len; i++) { + ptr[i] = rand8(); + } +} + +void verify_buffer(void *p1, void *p2, size_t len) +{ + if (len > 0) { + assert(memcmp(p1, p2, len) == 0); + } +} + +void fill_string(char *p, size_t len) +{ + size_t i; + + for (i=0; i<len-1; i++) { + p[i] = 'A' + rand_int(26); + } + p[len-1] = '\0'; +} + +void verify_string(const char *p1, const char *p2) +{ + assert(strlen(p1) == strlen(p2)); + assert(strcmp(p1, p2) == 0); +} + +void fill_ctdb_uint8(uint8_t *p) +{ + *p = rand8(); +} + +void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint16(uint16_t *p) +{ + *p = rand16(); +} + +void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_int32(int32_t *p) +{ + *p = rand32i(); +} + +void verify_ctdb_int32(int32_t *p1, int32_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint32(uint32_t *p) +{ + *p = rand32(); +} + +void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_uint64(uint64_t *p) +{ + *p = rand64(); +} + +void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_double(double *p) +{ + *p = rand_double(); +} + +void verify_ctdb_double(double *p1, double *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_bool(bool *p) +{ + if (rand_int(2) == 0) { + *p = true; + } else { + *p = false; + } +} + +void verify_ctdb_bool(bool *p1, bool *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p) +{ + char *str; + int len; + + len = rand_int(1024) + 2; + str = talloc_size(mem_ctx, len+1); + assert(str != NULL); + + fill_string(str, len); + *p = str; +} + +void verify_ctdb_string(const char **p1, const char **p2) +{ + if (*p1 == NULL || *p2 == NULL) { + assert(*p1 == *p2); + } else { + verify_string(*p1, *p2); + } +} + +void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p) +{ + fill_ctdb_string(mem_ctx, p); +} + +void verify_ctdb_stringn(const char **p1, const char **p2) +{ + verify_ctdb_string(p1, p2); +} + +void fill_ctdb_pid(pid_t *p) +{ + *p = rand32(); +} + +void verify_ctdb_pid(pid_t *p1, pid_t *p2) +{ + assert(*p1 == *p2); +} + +void fill_ctdb_timeval(struct timeval *p) +{ + p->tv_sec = rand32(); + p->tv_usec = rand_int(1000000); +} + +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2) +{ + assert(p1->tv_sec == p2->tv_sec); + assert(p1->tv_usec == p2->tv_usec); +} + +static unsigned int seed; +static char protocol_test_iterate_buf[1024]; + +static void protocol_test_iterate_abort_handler(int sig) +{ + struct sigaction act = { + .sa_handler = SIG_DFL, + }; + + fprintf(stderr, "Failed with seed: %d\n", seed); + if (protocol_test_iterate_buf[0] != '\0') { + fprintf(stderr, " tag: %s\n", protocol_test_iterate_buf); + } + log_stack_trace(); + sigaction(SIGABRT, &act, NULL); + abort(); +} + +void protocol_test_iterate_tag(const char *fmt, ...) +{ + va_list ap; + int count; + + va_start(ap,fmt); + count = vsnprintf(protocol_test_iterate_buf, + sizeof(protocol_test_iterate_buf), + fmt, + ap); + va_end(ap); + + assert(count >= 0); + protocol_test_iterate_buf[sizeof(protocol_test_iterate_buf) - 1] = '\0'; +} + +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)) +{ + struct sigaction act = { + .sa_handler = protocol_test_iterate_abort_handler, + }; + unsigned int min, max; + + if (argc == 2 || argc == 3) { + min = atoi(argv[1]); + + if (argc == 3) { + max = atoi(argv[2]); + if (min >= max) { + fprintf(stderr, + "%s: min must be less than max\n", + argv[0]); + exit(1); + } + + } else { + max = min; + } + } else { + fprintf(stderr, "usage: %s min [max]\n", argv[0]); + exit(1); + } + + sigaction(SIGABRT, &act, NULL); + + for (seed = min; seed <= max ; seed++) { + srandom(seed); + + test_func(); + } +} diff --git a/ctdb/tests/src/protocol_common_basic.h b/ctdb/tests/src/protocol_common_basic.h new file mode 100644 index 0000000..22a11b3 --- /dev/null +++ b/ctdb/tests/src/protocol_common_basic.h @@ -0,0 +1,175 @@ +/* + protocol tests - common functions - basic types + + Copyright (C) Amitay Isaacs 2015-2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_BASIC_H__ +#define __CTDB_PROTOCOL_COMMON_BASIC_H__ + +#include "replace.h" + +#include <talloc.h> + +/* + * Generate test routines + */ + +#define TEST_FUNC(NAME) test_ ##NAME +#define FILL_FUNC(NAME) fill_ ##NAME +#define VERIFY_FUNC(NAME) verify_ ##NAME +#define LEN_FUNC(NAME) NAME## _len +#define PUSH_FUNC(NAME) NAME## _push +#define PULL_FUNC(NAME) NAME## _pull + +/* + * Test for basic data types that do not need memory allocation + * For example - int32_t, uint32_t, uint64_t + */ +#define PROTOCOL_TYPE1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + FILL_FUNC(NAME)(&p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ +} + +/* + * Test for container data types that need memory allocation for sub-elements + * For example - TDB_DATA + */ +#define PROTOCOL_TYPE2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE p1; \ + TYPE p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &p1); \ + buflen = LEN_FUNC(NAME)(&p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(&p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +/* + * Test for derived data types that need memory allocation + * For example - most ctdb structures + */ +#define PROTOCOL_TYPE3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE *p1, *p2; \ + size_t buflen, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p1 = talloc_zero(mem_ctx, TYPE); \ + assert(p1 != NULL); \ + FILL_FUNC(NAME)(p1, p1); \ + buflen = LEN_FUNC(NAME)(p1); \ + assert(buflen < sizeof(BUFFER)); \ + PUSH_FUNC(NAME)(p1, BUFFER, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(BUFFER, buflen, mem_ctx, &p2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + +extern uint8_t BUFFER[1024*1024]; + +int rand_int(int max); +uint8_t rand8(void); +uint16_t rand16(void); +int32_t rand32i(void); +uint32_t rand32(void); +uint64_t rand64(void); +double rand_double(void); + +void fill_buffer(void *p, size_t len); +void verify_buffer(void *p1, void *p2, size_t len); + +void fill_string(char *p, size_t len); +void verify_string(const char *p1, const char *p2); + +void fill_ctdb_uint8(uint8_t *p); +void verify_ctdb_uint8(uint8_t *p1, uint8_t *p2); + +void fill_ctdb_uint16(uint16_t *p); +void verify_ctdb_uint16(uint16_t *p1, uint16_t *p2); + +void fill_ctdb_int32(int32_t *p); +void verify_ctdb_int32(int32_t *p1, int32_t *p2); + +void fill_ctdb_uint32(uint32_t *p); +void verify_ctdb_uint32(uint32_t *p1, uint32_t *p2); + +void fill_ctdb_uint64(uint64_t *p); +void verify_ctdb_uint64(uint64_t *p1, uint64_t *p2); + +void fill_ctdb_double(double *p); +void verify_ctdb_double(double *p1, double *p2); + +void fill_ctdb_bool(bool *p); +void verify_ctdb_bool(bool *p1, bool *p2); + +void fill_ctdb_string(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_string(const char **p1, const char **p2); + +void fill_ctdb_stringn(TALLOC_CTX *mem_ctx, const char **p); +void verify_ctdb_stringn(const char **p1, const char **p2); + +void fill_ctdb_pid(pid_t *p); +void verify_ctdb_pid(pid_t *p1, pid_t *p2); + +void fill_ctdb_timeval(struct timeval *p); +void verify_ctdb_timeval(struct timeval *p1, struct timeval *p2); + +void protocol_test_iterate_tag(const char *fmt, ...) PRINTF_ATTRIBUTE(1,0); +void protocol_test_iterate(int argc, + const char *argv[], + void (*test_func)(void)); + +#endif /* __CTDB_PROTOCOL_COMMON_BASIC_H__ */ + + diff --git a/ctdb/tests/src/protocol_common_ctdb.c b/ctdb/tests/src/protocol_common_ctdb.c new file mode 100644 index 0000000..3840768 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.c @@ -0,0 +1,1934 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to fill and verify protocol structures + */ + +void fill_ctdb_req_header(struct ctdb_req_header *h) +{ + h->length = rand32(); + h->ctdb_magic = rand32(); + h->ctdb_version = rand32(); + h->generation = rand32(); + h->operation = rand32(); + h->destnode = rand32(); + h->srcnode = rand32(); + h->reqid = rand32(); +} + +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2) +{ + assert(h->length == h2->length); + assert(h->ctdb_magic == h2->ctdb_magic); + assert(h->ctdb_version == h2->ctdb_version); + assert(h->generation == h2->generation); + assert(h->operation == h2->operation); + assert(h->destnode == h2->destnode); + assert(h->srcnode == h2->srcnode); + assert(h->reqid == h2->reqid); +} + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c) +{ + c->flags = rand32(); + c->db_id = rand32(); + c->callid = rand32(); + c->hopcount = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->calldata); +} + +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2) +{ + assert(c->flags == c2->flags); + assert(c->db_id == c2->db_id); + assert(c->callid == c2->callid); + assert(c->hopcount == c2->hopcount); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->calldata, &c2->calldata); +} + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c) +{ + c->status = rand32(); + fill_tdb_data(mem_ctx, &c->msg); +} + +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2) +{ + assert(c->status == c2->status); + verify_tdb_data(&c->msg, &c2->msg); +} + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + c->dmaster = rand32(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + assert(c->dmaster == c2->dmaster); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + c->db_id = rand32(); + c->rsn = rand64(); + fill_tdb_data_nonnull(mem_ctx, &c->key); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2) +{ + assert(c->db_id == c2->db_id); + assert(c->rsn == c2->rsn); + verify_tdb_data(&c->key, &c2->key); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + cd->data.pid = rand32(); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + cd->data.recmode = rand_int(2); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_TRAVERSE_START: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + cd->data.traverse_all = talloc(mem_ctx, struct ctdb_traverse_all); + assert(cd->data.traverse_all != NULL); + fill_ctdb_traverse_all(mem_ctx, cd->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + cd->data.rec_data = talloc(mem_ctx, struct ctdb_rec_data); + assert(cd->data.rec_data != NULL); + fill_ctdb_rec_data(mem_ctx, cd->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + cd->data.tunable = talloc(mem_ctx, struct ctdb_tunable); + assert(cd->data.tunable != NULL); + fill_ctdb_tunable(mem_ctx, cd->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + fill_ctdb_string(mem_ctx, &cd->data.tun_var); + assert(cd->data.tun_var != NULL); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + cd->data.flag_change = talloc(mem_ctx, struct ctdb_node_flag_change); + assert(cd->data.flag_change != NULL); + fill_ctdb_node_flag_change(mem_ctx, cd->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + cd->data.addr_info = talloc(mem_ctx, struct ctdb_addr_info); + assert(cd->data.addr_info != NULL); + fill_ctdb_addr_info(mem_ctx, cd->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + cd->data.pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(cd->data.pubip != NULL); + fill_ctdb_public_ip(mem_ctx, cd->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + cd->data.traverse_start = talloc(mem_ctx, struct ctdb_traverse_start); + assert(cd->data.traverse_start != NULL); + fill_ctdb_traverse_start(mem_ctx, cd->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + cd->data.reclock_latency = rand_double(); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + cd->data.role = rand_int(2); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + cd->data.notify = talloc(mem_ctx, struct ctdb_notify_data); + assert(cd->data.notify != NULL); + fill_ctdb_notify_data(mem_ctx, cd->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + cd->data.srvid = rand64(); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.addr = talloc(mem_ctx, ctdb_sock_addr); + assert(cd->data.addr != NULL); + fill_ctdb_sock_addr(mem_ctx, cd->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + cd->data.iface = talloc(mem_ctx, struct ctdb_iface); + assert(cd->data.iface != NULL); + fill_ctdb_iface(mem_ctx, cd->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + cd->data.conn = talloc(mem_ctx, struct ctdb_connection); + assert(cd->data.conn != NULL); + fill_ctdb_connection(mem_ctx, cd->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + cd->data.key = talloc(mem_ctx, struct ctdb_key_data); + assert(cd->data.key != NULL); + fill_ctdb_key_data(mem_ctx, cd->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + cd->data.traverse_start_ext = talloc(mem_ctx, struct ctdb_traverse_start_ext); + assert(cd->data.traverse_start_ext != NULL); + fill_ctdb_traverse_start_ext(mem_ctx, cd->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + cd->data.traverse_all_ext = talloc(mem_ctx, struct ctdb_traverse_all_ext); + assert(cd->data.traverse_all_ext != NULL); + fill_ctdb_traverse_all_ext(mem_ctx, cd->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_THAW: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + cd->data.transdb = talloc(mem_ctx, struct ctdb_transdb); + assert(cd->data.transdb != NULL); + fill_ctdb_transdb(mem_ctx, cd->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + cd->data.pulldb_ext = talloc(mem_ctx, struct ctdb_pulldb_ext); + assert(cd->data.pulldb_ext != NULL); + fill_ctdb_pulldb_ext(mem_ctx, cd->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name != NULL); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + cd->data.pid_srvid = talloc(mem_ctx, struct ctdb_pid_srvid); + assert(cd->data.pid_srvid != NULL); + fill_ctdb_pid_srvid(mem_ctx, cd->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + cd->data.db_vacuum = talloc(mem_ctx, struct ctdb_db_vacuum); + assert(cd->data.db_vacuum != NULL); + fill_ctdb_db_vacuum(mem_ctx, cd->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + } +} + +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + assert(cd->data.pid == cd2->data.pid); + break; + + case CTDB_CONTROL_STATISTICS: + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GETVNNMAP: + break; + + case CTDB_CONTROL_SETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_GET_DEBUG: + break; + + case CTDB_CONTROL_SET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_GET_DBMAP: + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + assert(cd->data.recmode == cd2->data.recmode); + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_TRAVERSE_START: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + verify_ctdb_traverse_all(cd->data.traverse_all, + cd2->data.traverse_all); + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + verify_ctdb_rec_data(cd->data.rec_data, cd2->data.rec_data); + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DUMP_MEMORY: + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_ADD: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_TCP_REMOVE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + verify_ctdb_tunable(cd->data.tunable, cd2->data.tunable); + break; + + case CTDB_CONTROL_GET_TUNABLE: + verify_ctdb_string(&cd->data.tun_var, &cd2->data.tun_var); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + verify_ctdb_node_flag_change(cd->data.flag_change, + cd2->data.flag_change); + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_WIPE_DATABASE: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_UPTIME: + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + verify_ctdb_addr_info(cd->data.addr_info, cd2->data.addr_info); + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_TAKEOVER_IP: + verify_ctdb_public_ip(cd->data.pubip, cd2->data.pubip); + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + break; + + case CTDB_CONTROL_GET_NODEMAP: + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + verify_ctdb_traverse_start(cd->data.traverse_start, + cd2->data.traverse_start); + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + assert(cd->data.reclock_latency == cd2->data.reclock_latency); + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + assert(cd->data.role == cd2->data.role); + break; + + case CTDB_CONTROL_SET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_GET_BAN_STATE: + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + verify_ctdb_notify_data(cd->data.notify, cd2->data.notify); + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + assert(cd->data.srvid == cd2->data.srvid); + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_sock_addr(cd->data.addr, cd2->data.addr); + break; + + case CTDB_CONTROL_GET_IFACES: + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + verify_ctdb_iface(cd->data.iface, cd2->data.iface); + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + verify_ctdb_connection(cd->data.conn, cd2->data.conn); + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + verify_ctdb_key_data(cd->data.key, cd2->data.key); + break; + + case CTDB_CONTROL_SET_DB_READONLY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + verify_ctdb_traverse_start_ext(cd->data.traverse_start_ext, + cd2->data.traverse_start_ext); + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + verify_ctdb_traverse_all_ext(cd->data.traverse_all_ext, + cd2->data.traverse_all_ext); + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + break; + + case CTDB_CONTROL_DB_DETACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_GET_NODES_FILE: + break; + + case CTDB_CONTROL_DB_FREEZE: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_THAW: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_TRANSACTION_START: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_COMMIT: + verify_ctdb_transdb(cd->data.transdb, cd2->data.transdb); + break; + + case CTDB_CONTROL_DB_TRANSACTION_CANCEL: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_PULL: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_START: + verify_ctdb_pulldb_ext(cd->data.pulldb_ext, + cd2->data.pulldb_ext); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + verify_ctdb_pid_srvid(cd->data.pid_srvid, cd2->data.pid_srvid); + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_DB_VACUUM: + verify_ctdb_db_vacuum(cd->data.db_vacuum, cd2->data.db_vacuum); + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + } +} + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode) +{ + c->opcode = opcode; + c->pad = rand32(); + c->srvid = rand64(); + c->client_id = rand32(); + c->flags = rand32(); + + fill_ctdb_req_control_data(mem_ctx, &c->rdata, opcode); +} + +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2) +{ + assert(c->opcode == c2->opcode); + assert(c->pad == c2->pad); + assert(c->srvid == c2->srvid); + assert(c->client_id == c2->client_id); + assert(c->flags == c2->flags); + + verify_ctdb_req_control_data(&c->rdata, &c2->rdata); +} + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode) +{ + cd->opcode = opcode; + + switch (opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + cd->data.stats = talloc(mem_ctx, struct ctdb_statistics); + assert(cd->data.stats != NULL); + fill_ctdb_statistics(mem_ctx, cd->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + fill_ctdb_string(mem_ctx, &cd->data.db_path); + assert(cd->data.db_path != NULL); + break; + + case CTDB_CONTROL_GETVNNMAP: + cd->data.vnnmap = talloc(mem_ctx, struct ctdb_vnn_map); + assert(cd->data.vnnmap != NULL); + fill_ctdb_vnn_map(mem_ctx, cd->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + cd->data.loglevel = rand_int(5); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + cd->data.dbmap = talloc(mem_ctx, struct ctdb_dbid_map); + assert(cd->data.dbmap != NULL); + fill_ctdb_dbid_map(mem_ctx, cd->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + fill_ctdb_string(mem_ctx, &cd->data.db_name); + assert(cd->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + fill_ctdb_string(mem_ctx, &cd->data.mem_str); + assert(cd->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + cd->data.tun_value = rand32(); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + cd->data.tun_var_list = talloc(mem_ctx, struct ctdb_var_list); + assert(cd->data.tun_var_list != NULL); + fill_ctdb_var_list(mem_ctx, cd->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + cd->data.tun_list = talloc(mem_ctx, struct ctdb_tunable_list); + assert(cd->data.tun_list != NULL); + fill_ctdb_tunable_list(mem_ctx, cd->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + cd->data.tickles = talloc(mem_ctx, struct ctdb_tickle_list); + assert(cd->data.tickles != NULL); + fill_ctdb_tickle_list(mem_ctx, cd->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + cd->data.uptime = talloc(mem_ctx, struct ctdb_uptime); + assert(cd->data.uptime != NULL); + fill_ctdb_uptime(mem_ctx, cd->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + cd->data.recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(cd->data.recbuf != NULL); + fill_ctdb_rec_buffer(mem_ctx, cd->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + cd->data.caps = rand32(); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + cd->data.pubip_list = talloc(mem_ctx, struct ctdb_public_ip_list); + assert(cd->data.pubip_list != NULL); + fill_ctdb_public_ip_list(mem_ctx, cd->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + fill_ctdb_string(mem_ctx, &cd->data.reclock_file); + assert(cd->data.reclock_file != NULL); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + cd->data.ban_state = talloc(mem_ctx, struct ctdb_ban_state); + assert(cd->data.ban_state != NULL); + fill_ctdb_ban_state(mem_ctx, cd->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + cd->data.seqnum = rand64(); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + fill_ctdb_string(mem_ctx, &cd->data.reason); + assert(cd->data.reason != NULL); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + cd->data.ipinfo = talloc(mem_ctx, struct ctdb_public_ip_info); + assert(cd->data.ipinfo != NULL); + fill_ctdb_public_ip_info(mem_ctx, cd->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + cd->data.iface_list = talloc(mem_ctx, struct ctdb_iface_list); + assert(cd->data.iface_list != NULL); + fill_ctdb_iface_list(mem_ctx, cd->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + cd->data.stats_list = talloc(mem_ctx, struct ctdb_statistics_list); + assert(cd->data.stats_list != NULL); + fill_ctdb_statistics_list(mem_ctx, cd->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + cd->data.dbstats = talloc(mem_ctx, struct ctdb_db_statistics); + assert(cd->data.dbstats != NULL); + fill_ctdb_db_statistics(mem_ctx, cd->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + cd->data.runstate = rand32(); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + cd->data.nodemap = talloc(mem_ctx, struct ctdb_node_map); + assert(cd->data.nodemap != NULL); + fill_ctdb_node_map(mem_ctx, cd->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + cd->data.num_records = rand32(); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + cd->data.tdb_flags = rand32(); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + cd->data.db_id = rand32(); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + cd->data.echo_data = talloc(mem_ctx, struct ctdb_echo_data); + assert(cd->data.echo_data != NULL); + fill_ctdb_echo_data(mem_ctx, cd->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + } +} + +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2) +{ + assert(cd->opcode == cd2->opcode); + + switch (cd->opcode) { + case CTDB_CONTROL_PROCESS_EXISTS: + break; + + case CTDB_CONTROL_STATISTICS: + verify_ctdb_statistics(cd->data.stats, cd2->data.stats); + break; + + case CTDB_CONTROL_PING: + break; + + case CTDB_CONTROL_GETDBPATH: + verify_ctdb_string(&cd->data.db_path, &cd2->data.db_path); + break; + + case CTDB_CONTROL_GETVNNMAP: + verify_ctdb_vnn_map(cd->data.vnnmap, cd2->data.vnnmap); + break; + + case CTDB_CONTROL_SETVNNMAP: + break; + + case CTDB_CONTROL_GET_DEBUG: + assert(cd->data.loglevel == cd2->data.loglevel); + break; + + case CTDB_CONTROL_SET_DEBUG: + break; + + case CTDB_CONTROL_GET_DBMAP: + verify_ctdb_dbid_map(cd->data.dbmap, cd2->data.dbmap); + break; + + case CTDB_CONTROL_GET_RECMODE: + break; + + case CTDB_CONTROL_SET_RECMODE: + break; + + case CTDB_CONTROL_STATISTICS_RESET: + break; + + case CTDB_CONTROL_DB_ATTACH: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_TRAVERSE_START: + break; + + case CTDB_CONTROL_TRAVERSE_ALL: + break; + + case CTDB_CONTROL_TRAVERSE_DATA: + break; + + case CTDB_CONTROL_REGISTER_SRVID: + break; + + case CTDB_CONTROL_DEREGISTER_SRVID: + break; + + case CTDB_CONTROL_GET_DBNAME: + verify_ctdb_string(&cd->data.db_name, &cd2->data.db_name); + break; + + case CTDB_CONTROL_ENABLE_SEQNUM: + break; + + case CTDB_CONTROL_UPDATE_SEQNUM: + break; + + case CTDB_CONTROL_DUMP_MEMORY: + verify_ctdb_string(&cd->data.mem_str, &cd2->data.mem_str); + break; + + case CTDB_CONTROL_GET_PID: + break; + + case CTDB_CONTROL_FREEZE: + break; + + case CTDB_CONTROL_GET_PNN: + break; + + case CTDB_CONTROL_SHUTDOWN: + break; + + case CTDB_CONTROL_TCP_CLIENT: + break; + + case CTDB_CONTROL_TCP_ADD: + break; + + case CTDB_CONTROL_TCP_REMOVE: + break; + + case CTDB_CONTROL_STARTUP: + break; + + case CTDB_CONTROL_SET_TUNABLE: + break; + + case CTDB_CONTROL_GET_TUNABLE: + assert(cd->data.tun_value == cd2->data.tun_value); + break; + + case CTDB_CONTROL_LIST_TUNABLES: + verify_ctdb_var_list(cd->data.tun_var_list, + cd2->data.tun_var_list); + break; + + case CTDB_CONTROL_MODIFY_FLAGS: + break; + + case CTDB_CONTROL_GET_ALL_TUNABLES: + verify_ctdb_tunable_list(cd->data.tun_list, cd2->data.tun_list); + break; + + case CTDB_CONTROL_GET_TCP_TICKLE_LIST: + verify_ctdb_tickle_list(cd->data.tickles, cd2->data.tickles); + break; + + case CTDB_CONTROL_SET_TCP_TICKLE_LIST: + break; + + case CTDB_CONTROL_DB_ATTACH_PERSISTENT: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_UPDATE_RECORD: + break; + + case CTDB_CONTROL_SEND_GRATUITOUS_ARP: + break; + + case CTDB_CONTROL_WIPE_DATABASE: + break; + + case CTDB_CONTROL_UPTIME: + verify_ctdb_uptime(cd->data.uptime, cd2->data.uptime); + break; + + case CTDB_CONTROL_START_RECOVERY: + break; + + case CTDB_CONTROL_END_RECOVERY: + break; + + case CTDB_CONTROL_RELOAD_NODES_FILE: + break; + + case CTDB_CONTROL_TRY_DELETE_RECORDS: + verify_ctdb_rec_buffer(cd->data.recbuf, cd2->data.recbuf); + break; + + case CTDB_CONTROL_ADD_PUBLIC_IP: + break; + + case CTDB_CONTROL_DEL_PUBLIC_IP: + break; + + case CTDB_CONTROL_GET_CAPABILITIES: + assert(cd->data.caps == cd2->data.caps); + break; + + case CTDB_CONTROL_RECD_PING: + break; + + case CTDB_CONTROL_RELEASE_IP: + break; + + case CTDB_CONTROL_TAKEOVER_IP: + break; + + case CTDB_CONTROL_GET_PUBLIC_IPS: + verify_ctdb_public_ip_list(cd->data.pubip_list, + cd2->data.pubip_list); + break; + + case CTDB_CONTROL_GET_NODEMAP: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_TRAVERSE_KILL: + break; + + case CTDB_CONTROL_RECD_RECLOCK_LATENCY: + break; + + case CTDB_CONTROL_GET_RECLOCK_FILE: + verify_ctdb_string(&cd->data.reclock_file, + &cd2->data.reclock_file); + break; + + case CTDB_CONTROL_STOP_NODE: + break; + + case CTDB_CONTROL_CONTINUE_NODE: + break; + + case CTDB_CONTROL_SET_LMASTERROLE: + break; + + case CTDB_CONTROL_SET_RECMASTERROLE: + break; + + case CTDB_CONTROL_SET_BAN_STATE: + break; + + case CTDB_CONTROL_GET_BAN_STATE: + verify_ctdb_ban_state(cd->data.ban_state, cd2->data.ban_state); + break; + + case CTDB_CONTROL_REGISTER_NOTIFY: + break; + + case CTDB_CONTROL_DEREGISTER_NOTIFY: + break; + + case CTDB_CONTROL_TRANS3_COMMIT: + break; + + case CTDB_CONTROL_GET_DB_SEQNUM: + assert(cd->data.seqnum == cd2->data.seqnum); + break; + + case CTDB_CONTROL_DB_SET_HEALTHY: + break; + + case CTDB_CONTROL_DB_GET_HEALTH: + verify_ctdb_string(&cd->data.reason, &cd2->data.reason); + break; + + case CTDB_CONTROL_GET_PUBLIC_IP_INFO: + verify_ctdb_public_ip_info(cd->data.ipinfo, cd2->data.ipinfo); + break; + + case CTDB_CONTROL_GET_IFACES: + verify_ctdb_iface_list(cd->data.iface_list, + cd2->data.iface_list); + break; + + case CTDB_CONTROL_SET_IFACE_LINK_STATE: + break; + + case CTDB_CONTROL_TCP_ADD_DELAYED_UPDATE: + break; + + case CTDB_CONTROL_GET_STAT_HISTORY: + verify_ctdb_statistics_list(cd->data.stats_list, + cd2->data.stats_list); + break; + + case CTDB_CONTROL_SCHEDULE_FOR_DELETION: + break; + + case CTDB_CONTROL_SET_DB_READONLY: + break; + + case CTDB_CONTROL_TRAVERSE_START_EXT: + break; + + case CTDB_CONTROL_GET_DB_STATISTICS: + verify_ctdb_db_statistics(cd->data.dbstats, cd2->data.dbstats); + break; + + case CTDB_CONTROL_SET_DB_STICKY: + break; + + case CTDB_CONTROL_RELOAD_PUBLIC_IPS: + break; + + case CTDB_CONTROL_TRAVERSE_ALL_EXT: + break; + + case CTDB_CONTROL_IPREALLOCATED: + break; + + case CTDB_CONTROL_GET_RUNSTATE: + assert(cd->data.runstate == cd2->data.runstate); + break; + + case CTDB_CONTROL_DB_DETACH: + break; + + case CTDB_CONTROL_GET_NODES_FILE: + verify_ctdb_node_map(cd->data.nodemap, cd2->data.nodemap); + break; + + case CTDB_CONTROL_DB_PULL: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_PUSH_CONFIRM: + assert(cd->data.num_records == cd2->data.num_records); + break; + + case CTDB_CONTROL_DB_OPEN_FLAGS: + assert(cd->data.tdb_flags == cd2->data.tdb_flags); + break; + + case CTDB_CONTROL_DB_ATTACH_REPLICATED: + assert(cd->data.db_id == cd2->data.db_id); + break; + + case CTDB_CONTROL_CHECK_PID_SRVID: + break; + + case CTDB_CONTROL_TUNNEL_REGISTER: + break; + + case CTDB_CONTROL_TUNNEL_DEREGISTER: + break; + + case CTDB_CONTROL_VACUUM_FETCH: + break; + + case CTDB_CONTROL_DB_VACUUM: + break; + + case CTDB_CONTROL_ECHO_DATA: + verify_ctdb_echo_data(cd->data.echo_data, cd2->data.echo_data); + break; + + case CTDB_CONTROL_DISABLE_NODE: + break; + + case CTDB_CONTROL_ENABLE_NODE: + break; + } +} + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode) +{ + c->status = -rand_int(2); + if (c->status == 0) { + c->errmsg = NULL; + fill_ctdb_reply_control_data(mem_ctx, &c->rdata, opcode); + } else { + fill_ctdb_string(mem_ctx, &c->errmsg); + } +} + +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2) +{ + assert(c->status == c2->status); + verify_ctdb_string(&c->errmsg, &c2->errmsg); + if (c->status == 0) { + verify_ctdb_reply_control_data(&c->rdata, &c2->rdata); + } +} + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + md->election = talloc(mem_ctx, struct ctdb_election_message); + assert(md->election != NULL); + fill_ctdb_election_message(md->election, md->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + fill_ctdb_string(mem_ctx, &md->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + md->flag_change = talloc(mem_ctx, + struct ctdb_node_flag_change); + assert(md->flag_change != NULL); + fill_ctdb_node_flag_change(md->flag_change, md->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + md->pubip = talloc(mem_ctx, struct ctdb_public_ip); + assert(md->pubip != NULL); + fill_ctdb_public_ip(md->pubip, md->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + md->recbuf = talloc(mem_ctx, struct ctdb_rec_buffer); + assert(md->recbuf != NULL); + fill_ctdb_rec_buffer(md->recbuf, md->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + md->db_id = rand32(); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + md->msg = talloc(mem_ctx, struct ctdb_srvid_message); + assert(md->msg != NULL); + fill_ctdb_srvid_message(md->msg, md->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + md->pnn = rand32(); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + md->disable = talloc(mem_ctx, struct ctdb_disable_message); + assert(md->disable != NULL); + fill_ctdb_disable_message(md->disable, md->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + md->timeout = rand32(); + break; + + default: + abort(); + } +} + +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid) +{ + switch (srvid) { + case CTDB_SRVID_RECONFIGURE: + case CTDB_SRVID_GETLOG: + case CTDB_SRVID_CLEARLOG: + case CTDB_SRVID_RELOAD_NODES: + break; + + case CTDB_SRVID_ELECTION: + verify_ctdb_election_message(md->election, md2->election); + break; + + case CTDB_SRVID_RELEASE_IP: + case CTDB_SRVID_TAKE_IP: + verify_ctdb_string(&md->ipaddr, &md2->ipaddr); + break; + + case CTDB_SRVID_SET_NODE_FLAGS: + case CTDB_SRVID_PUSH_NODE_FLAGS: + verify_ctdb_node_flag_change(md->flag_change, + md2->flag_change); + break; + + case CTDB_SRVID_RECD_UPDATE_IP: + verify_ctdb_public_ip(md->pubip, md2->pubip); + break; + + case CTDB_SRVID_VACUUM_FETCH: + verify_ctdb_rec_buffer(md->recbuf, md2->recbuf); + break; + + case CTDB_SRVID_DETACH_DATABASE: + assert(md->db_id == md2->db_id); + break; + + case CTDB_SRVID_MEM_DUMP: + case CTDB_SRVID_TAKEOVER_RUN: + verify_ctdb_srvid_message(md->msg, md2->msg); + break; + + case CTDB_SRVID_LEADER: + case CTDB_SRVID_BANNING: + case CTDB_SRVID_REBALANCE_NODE: + assert(md->pnn == md2->pnn); + break; + + case CTDB_SRVID_DISABLE_TAKEOVER_RUNS: + case CTDB_SRVID_DISABLE_RECOVERIES: + verify_ctdb_disable_message(md->disable, md2->disable); + break; + + case CTDB_SRVID_DISABLE_IP_CHECK: + assert(md->timeout == md2->timeout); + break; + + default: + abort(); + } +} + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid) +{ + c->srvid = srvid; + fill_ctdb_message_data(mem_ctx, &c->data, srvid); +} + +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2) +{ + assert(c->srvid == c2->srvid); + verify_ctdb_message_data(&c->data, &c2->data, c->srvid); +} + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + c->srvid = rand64(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2) +{ + assert(c->srvid == c2->srvid); + verify_tdb_data(&c->data, &c2->data); +} + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + c->version = rand32(); + c->uptime = rand32(); +} + +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2) +{ + assert(c->version == c2->version); + assert(c->uptime == c2->uptime); +} + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c) +{ + c->tunnel_id = rand64(); + c->flags = rand32(); + fill_tdb_data(mem_ctx, &c->data); +} + +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2) +{ + assert(c->tunnel_id == c2->tunnel_id); + assert(c->flags == c2->flags); + verify_tdb_data(&c->data, &c2->data); +} diff --git a/ctdb/tests/src/protocol_common_ctdb.h b/ctdb/tests/src/protocol_common_ctdb.h new file mode 100644 index 0000000..0681089 --- /dev/null +++ b/ctdb/tests/src/protocol_common_ctdb.h @@ -0,0 +1,101 @@ +/* + protocol tests - ctdb protocol + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_PROTOCOL_COMMON_CTDB_H__ +#define __CTDB_PROTOCOL_COMMON_CTDB_H__ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tdb.h> + +#include "protocol/protocol.h" + +void fill_ctdb_req_header(struct ctdb_req_header *h); +void verify_ctdb_req_header(struct ctdb_req_header *h, + struct ctdb_req_header *h2); + +void fill_ctdb_req_call(TALLOC_CTX *mem_ctx, struct ctdb_req_call *c); +void verify_ctdb_req_call(struct ctdb_req_call *c, struct ctdb_req_call *c2); + +void fill_ctdb_reply_call(TALLOC_CTX *mem_ctx, struct ctdb_reply_call *c); +void verify_ctdb_reply_call(struct ctdb_reply_call *c, + struct ctdb_reply_call *c2); + +void fill_ctdb_reply_error(TALLOC_CTX *mem_ctx, struct ctdb_reply_error *c); +void verify_ctdb_reply_error(struct ctdb_reply_error *c, + struct ctdb_reply_error *c2); + +void fill_ctdb_req_dmaster(TALLOC_CTX *mem_ctx, struct ctdb_req_dmaster *c); +void verify_ctdb_req_dmaster(struct ctdb_req_dmaster *c, + struct ctdb_req_dmaster *c2); + +void fill_ctdb_reply_dmaster(TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c); +void verify_ctdb_reply_dmaster(struct ctdb_reply_dmaster *c, + struct ctdb_reply_dmaster *c2); + +void fill_ctdb_req_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_control_data *cd, + uint32_t opcode); +void verify_ctdb_req_control_data(struct ctdb_req_control_data *cd, + struct ctdb_req_control_data *cd2); + +void fill_ctdb_req_control(TALLOC_CTX *mem_ctx, struct ctdb_req_control *c, + uint32_t opcode); +void verify_ctdb_req_control(struct ctdb_req_control *c, + struct ctdb_req_control *c2); + +void fill_ctdb_reply_control_data(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control_data *cd, + uint32_t opcode); +void verify_ctdb_reply_control_data(struct ctdb_reply_control_data *cd, + struct ctdb_reply_control_data *cd2); + +void fill_ctdb_reply_control(TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c, uint32_t opcode); +void verify_ctdb_reply_control(struct ctdb_reply_control *c, + struct ctdb_reply_control *c2); + +void fill_ctdb_message_data(TALLOC_CTX *mem_ctx, union ctdb_message_data *md, + uint64_t srvid); +void verify_ctdb_message_data(union ctdb_message_data *md, + union ctdb_message_data *md2, uint64_t srvid); + +void fill_ctdb_req_message(TALLOC_CTX *mem_ctx, struct ctdb_req_message *c, + uint64_t srvid); +void verify_ctdb_req_message(struct ctdb_req_message *c, + struct ctdb_req_message *c2); + +void fill_ctdb_req_message_data(TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c); +void verify_ctdb_req_message_data(struct ctdb_req_message_data *c, + struct ctdb_req_message_data *c2); + +void fill_ctdb_req_keepalive(TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c); +void verify_ctdb_req_keepalive(struct ctdb_req_keepalive *c, + struct ctdb_req_keepalive *c2); + +void fill_ctdb_req_tunnel(TALLOC_CTX *mem_ctx, struct ctdb_req_tunnel *c); +void verify_ctdb_req_tunnel(struct ctdb_req_tunnel *c, + struct ctdb_req_tunnel *c2); + +#endif /* __CTDB_PROTOCOL_COMMON_CTDB_H__ */ diff --git a/ctdb/tests/src/protocol_ctdb_compat_test.c b/ctdb/tests/src/protocol_ctdb_compat_test.c new file mode 100644 index 0000000..fc9f82e --- /dev/null +++ b/ctdb/tests/src/protocol_ctdb_compat_test.c @@ -0,0 +1,1270 @@ +/* + ctdb protocol backward compatibility test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_CTDB1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB4_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB5_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB6_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, opcode); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, opcode, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, opcode, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_CTDB7_TEST(TYPE, NAME, OPER) \ +static void COMPAT_TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + struct ctdb_req_header h, h1, h2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h); \ + FILL_FUNC(NAME)(mem_ctx, &p, srvid); \ + buflen1 = LEN_FUNC(NAME)(&h, &p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&h, &p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + ret = PUSH_FUNC(NAME)(&h, &p, buf1, &buflen1); \ + assert(ret == 0); \ + ret = OLD_PUSH_FUNC(NAME)(&h, &p, buf2, &buflen2); \ + assert(ret == 0); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &h1, mem_ctx, &p1); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &h2, mem_ctx, &p2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_req_header_len_old(struct ctdb_req_header *in) +{ + return sizeof(struct ctdb_req_header); +} + +static void ctdb_req_header_push_old(struct ctdb_req_header *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_req_header)); +} + +static int ctdb_req_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *out) +{ + if (buflen < sizeof(struct ctdb_req_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_req_header)); + return 0; +} + +struct ctdb_req_call_wire { + struct ctdb_req_header hdr; + uint32_t flags; + uint32_t db_id; + uint32_t callid; + uint32_t hopcount; + uint32_t keylen; + uint32_t calldatalen; + uint8_t data[1]; /* key[] followed by calldata[] */ +}; + +static size_t ctdb_req_call_len_old(struct ctdb_req_header *h, + struct ctdb_req_call *c) +{ + return offsetof(struct ctdb_req_call_wire, data) + + ctdb_tdb_data_len(&c->key) + + ctdb_tdb_data_len(&c->calldata); +} + +static int ctdb_req_call_push_old(struct ctdb_req_header *h, + struct ctdb_req_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + + if (c->key.dsize == 0) { + return EINVAL; + } + + length = ctdb_req_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->flags = c->flags; + wire->db_id = c->db_id; + wire->callid = c->callid; + wire->hopcount = c->hopcount; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->calldatalen = ctdb_tdb_data_len(&c->calldata); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->calldata, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_call *c) +{ + struct ctdb_req_call_wire *wire = + (struct ctdb_req_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->calldatalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->calldatalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->calldatalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->flags = wire->flags; + c->db_id = wire->db_id; + c->callid = wire->callid; + c->hopcount = wire->hopcount; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->calldatalen, + mem_ctx, &c->calldata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_call_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_call_len_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c) +{ + return offsetof(struct ctdb_reply_call_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_call_push_old(struct ctdb_req_header *h, + struct ctdb_reply_call *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + + length = ctdb_reply_call_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_reply_call_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_call *c) +{ + struct ctdb_reply_call_wire *wire = + (struct ctdb_reply_call_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_call_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_error_wire { + struct ctdb_req_header hdr; + uint32_t status; + uint32_t msglen; + uint8_t msg[1]; +}; + +static size_t ctdb_reply_error_len_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c) +{ + return offsetof(struct ctdb_reply_error_wire, msg) + + ctdb_tdb_data_len(&c->msg); +} + +static int ctdb_reply_error_push_old(struct ctdb_req_header *h, + struct ctdb_reply_error *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + + length = ctdb_reply_error_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + wire->msglen = ctdb_tdb_data_len(&c->msg); + ctdb_tdb_data_push(&c->msg, wire->msg, &np); + + return 0; +} + +static int ctdb_reply_error_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_error *c) +{ + struct ctdb_reply_error_wire *wire = + (struct ctdb_reply_error_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_error_wire, msg); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->msglen > buflen) { + return EMSGSIZE; + } + if (length + wire->msglen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->msglen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + ret = ctdb_tdb_data_pull(wire->msg, wire->msglen, mem_ctx, &c->msg, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t dmaster; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c) +{ + return offsetof(struct ctdb_req_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_req_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_req_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->dmaster = c->dmaster; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_req_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_dmaster *c) +{ + struct ctdb_req_dmaster_wire *wire = + (struct ctdb_req_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + c->dmaster = wire->dmaster; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_dmaster_wire { + struct ctdb_req_header hdr; + uint32_t db_id; + uint64_t rsn; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_dmaster_len_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c) +{ + return offsetof(struct ctdb_reply_dmaster_wire, data) + + ctdb_tdb_data_len(&c->key) + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_reply_dmaster_push_old(struct ctdb_req_header *h, + struct ctdb_reply_dmaster *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + + length = ctdb_reply_dmaster_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->db_id = c->db_id; + wire->rsn = c->rsn; + wire->keylen = ctdb_tdb_data_len(&c->key); + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->key, wire->data, &np); + ctdb_tdb_data_push(&c->data, wire->data + wire->keylen, &np); + + return 0; +} + +static int ctdb_reply_dmaster_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_dmaster *c) +{ + struct ctdb_reply_dmaster_wire *wire = + (struct ctdb_reply_dmaster_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_dmaster_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->keylen < length) { + return EMSGSIZE; + } + if (length + wire->keylen + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->db_id = wire->db_id; + c->rsn = wire->rsn; + + ret = ctdb_tdb_data_pull(wire->data, wire->keylen, mem_ctx, &c->key, + &np); + if (ret != 0) { + return ret; + } + + ret = ctdb_tdb_data_pull(wire->data + wire->keylen, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_control_wire { + struct ctdb_req_header hdr; + uint32_t opcode; + uint32_t pad; + uint64_t srvid; + uint32_t client_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_control_len_old(struct ctdb_req_header *h, + struct ctdb_req_control *c) +{ + return offsetof(struct ctdb_req_control_wire, data) + + ctdb_req_control_data_len(&c->rdata); +} + +static int ctdb_req_control_push_old(struct ctdb_req_header *h, + struct ctdb_req_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + + length = ctdb_req_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->opcode = c->opcode; + wire->pad = c->pad; + wire->srvid = c->srvid; + wire->client_id = c->client_id; + wire->flags = c->flags; + + wire->datalen = ctdb_req_control_data_len(&c->rdata); + ctdb_req_control_data_push(&c->rdata, wire->data, &np); + + return 0; +} + +static int ctdb_req_control_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_control *c) +{ + struct ctdb_req_control_wire *wire = + (struct ctdb_req_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->opcode = wire->opcode; + c->pad = wire->pad; + c->srvid = wire->srvid; + c->client_id = wire->client_id; + c->flags = wire->flags; + + ret = ctdb_req_control_data_pull(wire->data, wire->datalen, + c->opcode, mem_ctx, &c->rdata, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_reply_control_wire { + struct ctdb_req_header hdr; + int32_t status; + uint32_t datalen; + uint32_t errorlen; + uint8_t data[1]; +}; + +static size_t ctdb_reply_control_len_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c) +{ + return offsetof(struct ctdb_reply_control_wire, data) + + (c->status == 0 ? + ctdb_reply_control_data_len(&c->rdata) : + ctdb_string_len(&c->errmsg)); +} + +static int ctdb_reply_control_push_old(struct ctdb_req_header *h, + struct ctdb_reply_control *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + + length = ctdb_reply_control_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->status = c->status; + + if (c->status == 0) { + wire->datalen = ctdb_reply_control_data_len(&c->rdata); + wire->errorlen = 0; + ctdb_reply_control_data_push(&c->rdata, wire->data, &np); + } else { + wire->datalen = 0; + wire->errorlen = ctdb_string_len(&c->errmsg); + ctdb_string_push(&c->errmsg, wire->data + wire->datalen, &np); + } + + return 0; +} + +static int ctdb_reply_control_pull_old(uint8_t *buf, size_t buflen, + uint32_t opcode, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_reply_control *c) +{ + struct ctdb_reply_control_wire *wire = + (struct ctdb_reply_control_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_reply_control_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen || wire->errorlen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (length + wire->datalen + wire->errorlen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen + wire->errorlen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->status = wire->status; + + if (c->status != -1) { + ret = ctdb_reply_control_data_pull(wire->data, wire->datalen, + opcode, mem_ctx, + &c->rdata, &np); + if (ret != 0) { + return ret; + } + } + + ret = ctdb_string_pull(wire->data + wire->datalen, wire->errorlen, + mem_ctx, &c->errmsg, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_message_wire { + struct ctdb_req_header hdr; + uint64_t srvid; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_message_len_old(struct ctdb_req_header *h, + struct ctdb_req_message *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_message_data_len(&c->data, c->srvid); +} + +static int ctdb_req_message_push_old(struct ctdb_req_header *h, + struct ctdb_req_message *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->srvid = c->srvid; + wire->datalen = ctdb_message_data_len(&c->data, c->srvid); + ctdb_message_data_push(&c->data, c->srvid, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + ret = ctdb_message_data_pull(wire->data, wire->datalen, wire->srvid, + mem_ctx, &c->data, &np); + return ret; +} + +static size_t ctdb_req_message_data_len_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c) +{ + return offsetof(struct ctdb_req_message_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_message_data_push_old(struct ctdb_req_header *h, + struct ctdb_req_message_data *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + + length = ctdb_req_message_data_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push(h, (uint8_t *)&wire->hdr, &np); + + wire->srvid = c->srvid; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_message_data_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_message_data *c) +{ + struct ctdb_req_message_wire *wire = + (struct ctdb_req_message_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_message_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->srvid = wire->srvid; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, + mem_ctx, &c->data, &np); + if (ret != 0) { + return ret; + } + + return 0; +} + +struct ctdb_req_keepalive_wire { + struct ctdb_req_header hdr; + uint32_t version; + uint32_t uptime; +}; + +static size_t ctdb_req_keepalive_len_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c) +{ + return sizeof(struct ctdb_req_keepalive_wire); +} + +static int ctdb_req_keepalive_push_old(struct ctdb_req_header *h, + struct ctdb_req_keepalive *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + + length = ctdb_req_keepalive_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->version = c->version; + wire->uptime = c->uptime; + + return 0; +} + +static int ctdb_req_keepalive_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_keepalive *c) +{ + struct ctdb_req_keepalive_wire *wire = + (struct ctdb_req_keepalive_wire *)buf; + size_t length; + int ret; + + length = sizeof(struct ctdb_req_keepalive_wire); + if (buflen < length) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->version = wire->version; + c->uptime = wire->uptime; + + return 0; +} + +struct ctdb_req_tunnel_wire { + struct ctdb_req_header hdr; + uint64_t tunnel_id; + uint32_t flags; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_req_tunnel_len_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c) +{ + return offsetof(struct ctdb_req_tunnel_wire, data) + + ctdb_tdb_data_len(&c->data); +} + +static int ctdb_req_tunnel_push_old(struct ctdb_req_header *h, + struct ctdb_req_tunnel *c, + uint8_t *buf, size_t *buflen) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + + length = ctdb_req_tunnel_len_old(h, c); + if (*buflen < length) { + *buflen = length; + return EMSGSIZE; + } + + h->length = *buflen; + ctdb_req_header_push_old(h, (uint8_t *)&wire->hdr); + + wire->tunnel_id = c->tunnel_id; + wire->flags = c->flags; + wire->datalen = ctdb_tdb_data_len(&c->data); + ctdb_tdb_data_push(&c->data, wire->data, &np); + + return 0; +} + +static int ctdb_req_tunnel_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_req_header *h, + TALLOC_CTX *mem_ctx, + struct ctdb_req_tunnel *c) +{ + struct ctdb_req_tunnel_wire *wire = + (struct ctdb_req_tunnel_wire *)buf; + size_t length, np; + int ret; + + length = offsetof(struct ctdb_req_tunnel_wire, data); + if (buflen < length) { + return EMSGSIZE; + } + if (wire->datalen > buflen) { + return EMSGSIZE; + } + if (length + wire->datalen < length) { + return EMSGSIZE; + } + if (buflen < length + wire->datalen) { + return EMSGSIZE; + } + + if (h != NULL) { + ret = ctdb_req_header_pull_old((uint8_t *)&wire->hdr, buflen, + h); + if (ret != 0) { + return ret; + } + } + + c->tunnel_id = wire->tunnel_id; + c->flags = wire->flags; + + ret = ctdb_tdb_data_pull(wire->data, wire->datalen, mem_ctx, &c->data, + &np); + if (ret != 0) { + return ret; + } + + return 0; +} + + +COMPAT_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +COMPAT_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +COMPAT_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, CTDB_REPLY_ERROR); +COMPAT_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, CTDB_REQ_DMASTER); +COMPAT_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, CTDB_REPLY_DMASTER); + +COMPAT_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, CTDB_REQ_CONTROL); +COMPAT_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, CTDB_REPLY_CONTROL); + +COMPAT_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, CTDB_REQ_MESSAGE); +COMPAT_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, CTDB_REQ_MESSAGE); + +COMPAT_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, CTDB_REQ_KEEPALIVE); +COMPAT_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +#define NUM_CONTROLS 151 + +static void protocol_ctdb_compat_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + unsigned int i; + + COMPAT_TEST_FUNC(ctdb_req_header)(); + + COMPAT_TEST_FUNC(ctdb_req_call)(); + COMPAT_TEST_FUNC(ctdb_reply_call)(); + COMPAT_TEST_FUNC(ctdb_reply_error)(); + COMPAT_TEST_FUNC(ctdb_req_dmaster)(); + COMPAT_TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + COMPAT_TEST_FUNC(ctdb_req_control)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + COMPAT_TEST_FUNC(ctdb_reply_control)(opcode); + } + + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + COMPAT_TEST_FUNC(ctdb_req_message)(test_srvid[i]); + } + COMPAT_TEST_FUNC(ctdb_req_message_data)(); + + COMPAT_TEST_FUNC(ctdb_req_keepalive)(); + COMPAT_TEST_FUNC(ctdb_req_tunnel)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_ctdb_compat_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_ctdb_test.c b/ctdb/tests/src/protocol_ctdb_test.c new file mode 100644 index 0000000..f6fb513 --- /dev/null +++ b/ctdb/tests/src/protocol_ctdb_test.c @@ -0,0 +1,365 @@ +/* + protocol tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_header.c" +#include "protocol/protocol_call.c" +#include "protocol/protocol_control.c" +#include "protocol/protocol_message.c" +#include "protocol/protocol_keepalive.c" +#include "protocol/protocol_tunnel.c" +#include "protocol/protocol_packet.c" + +#include "tests/src/protocol_common.h" +#include "tests/src/protocol_common_ctdb.h" + +/* + * Functions to test marshalling + */ + +/* for ctdb_req_header */ +#define PROTOCOL_CTDB1_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&c1); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control_data, ctdb_reply_control_data */ +#define PROTOCOL_CTDB2_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_message_data */ +#define PROTOCOL_CTDB3_TEST(TYPE, NAME) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, np; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&c1, srvid); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + np = 0; \ + PUSH_FUNC(NAME)(&c1, srvid, pkt, &np); \ + assert(np == buflen); \ + np = 0; \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, srvid, mem_ctx, &c2, &np); \ + assert(ret == 0); \ + assert(np == buflen); \ + VERIFY_FUNC(NAME)(&c1, &c2, srvid); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_call, ctdb_reply_call, etc. */ +#define PROTOCOL_CTDB4_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s\n", #NAME); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_control */ +#define PROTOCOL_CTDB5_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_reply_control */ +#define PROTOCOL_CTDB6_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint32_t opcode) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %u\n", #NAME, opcode); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, opcode); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, opcode, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +/* for ctdb_req_message */ +#define PROTOCOL_CTDB7_TEST(TYPE, NAME, OPER) \ +static void TEST_FUNC(NAME)(uint64_t srvid) \ +{ \ + TALLOC_CTX *mem_ctx; \ + struct ctdb_req_header h1, h2; \ + TYPE c1, c2; \ + uint8_t *pkt; \ + size_t pkt_len, buflen, len; \ + int ret; \ +\ + protocol_test_iterate_tag("%s %"PRIx64"\n", #NAME, srvid); \ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + fill_ctdb_req_header(&h1); \ + FILL_FUNC(NAME)(mem_ctx, &c1, srvid); \ + buflen = LEN_FUNC(NAME)(&h1, &c1); \ + ret = ctdb_allocate_pkt(mem_ctx, buflen, &pkt, &pkt_len); \ + assert(ret == 0); \ + assert(pkt != NULL); \ + assert(pkt_len >= buflen); \ + len = 0; \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &len); \ + assert(ret == EMSGSIZE); \ + assert(len == buflen); \ + ret = PUSH_FUNC(NAME)(&h1, &c1, pkt, &pkt_len); \ + assert(ret == 0); \ + ret = PULL_FUNC(NAME)(pkt, pkt_len, &h2, mem_ctx, &c2); \ + assert(ret == 0); \ + verify_ctdb_req_header(&h1, &h2); \ + assert(h2.length == pkt_len); \ + VERIFY_FUNC(NAME)(&c1, &c2); \ + talloc_free(mem_ctx); \ +} + +PROTOCOL_CTDB1_TEST(struct ctdb_req_header, ctdb_req_header); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_call, ctdb_req_call, CTDB_REQ_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_call, ctdb_reply_call, CTDB_REPLY_CALL); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_error, ctdb_reply_error, + CTDB_REPLY_ERROR); +PROTOCOL_CTDB4_TEST(struct ctdb_req_dmaster, ctdb_req_dmaster, + CTDB_REQ_DMASTER); +PROTOCOL_CTDB4_TEST(struct ctdb_reply_dmaster, ctdb_reply_dmaster, + CTDB_REPLY_DMASTER); + +#define NUM_CONTROLS 159 + +PROTOCOL_CTDB2_TEST(struct ctdb_req_control_data, ctdb_req_control_data); +PROTOCOL_CTDB2_TEST(struct ctdb_reply_control_data, ctdb_reply_control_data); + +PROTOCOL_CTDB5_TEST(struct ctdb_req_control, ctdb_req_control, + CTDB_REQ_CONTROL); +PROTOCOL_CTDB6_TEST(struct ctdb_reply_control, ctdb_reply_control, + CTDB_REPLY_CONTROL); + +PROTOCOL_CTDB3_TEST(union ctdb_message_data, ctdb_message_data); +PROTOCOL_CTDB7_TEST(struct ctdb_req_message, ctdb_req_message, + CTDB_REQ_MESSAGE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_message_data, ctdb_req_message_data, + CTDB_REQ_MESSAGE); + +PROTOCOL_CTDB4_TEST(struct ctdb_req_keepalive, ctdb_req_keepalive, + CTDB_REQ_KEEPALIVE); +PROTOCOL_CTDB4_TEST(struct ctdb_req_tunnel, ctdb_req_tunnel, CTDB_REQ_TUNNEL); + +static void protocol_ctdb_test(void) +{ + uint32_t opcode; + uint64_t test_srvid[] = { + CTDB_SRVID_BANNING, + CTDB_SRVID_ELECTION, + CTDB_SRVID_LEADER, + CTDB_SRVID_RECONFIGURE, + CTDB_SRVID_RELEASE_IP, + CTDB_SRVID_TAKE_IP, + CTDB_SRVID_SET_NODE_FLAGS, + CTDB_SRVID_RECD_UPDATE_IP, + CTDB_SRVID_VACUUM_FETCH, + CTDB_SRVID_DETACH_DATABASE, + CTDB_SRVID_MEM_DUMP, + CTDB_SRVID_GETLOG, + CTDB_SRVID_CLEARLOG, + CTDB_SRVID_PUSH_NODE_FLAGS, + CTDB_SRVID_RELOAD_NODES, + CTDB_SRVID_TAKEOVER_RUN, + CTDB_SRVID_REBALANCE_NODE, + CTDB_SRVID_DISABLE_TAKEOVER_RUNS, + CTDB_SRVID_DISABLE_RECOVERIES, + CTDB_SRVID_DISABLE_IP_CHECK, + }; + size_t i; + + TEST_FUNC(ctdb_req_header)(); + + TEST_FUNC(ctdb_req_call)(); + TEST_FUNC(ctdb_reply_call)(); + TEST_FUNC(ctdb_reply_error)(); + TEST_FUNC(ctdb_req_dmaster)(); + TEST_FUNC(ctdb_reply_dmaster)(); + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_req_control_data)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_reply_control_data)(opcode); + } + + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_req_control)(opcode); + } + for (opcode=0; opcode<NUM_CONTROLS; opcode++) { + TEST_FUNC(ctdb_reply_control)(opcode); + } + + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + TEST_FUNC(ctdb_message_data)(test_srvid[i]); + } + for (i=0; i<ARRAY_SIZE(test_srvid); i++) { + TEST_FUNC(ctdb_req_message)(test_srvid[i]); + } + TEST_FUNC(ctdb_req_message_data)(); + + TEST_FUNC(ctdb_req_keepalive)(); + TEST_FUNC(ctdb_req_tunnel)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_ctdb_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_types_compat_test.c b/ctdb/tests/src/protocol_types_compat_test.c new file mode 100644 index 0000000..140ea86 --- /dev/null +++ b/ctdb/tests/src/protocol_types_compat_test.c @@ -0,0 +1,2371 @@ +/* + protocol types backward compatibility test + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" + +#include "tests/src/protocol_common.h" + +#define COMPAT_TEST_FUNC(NAME) test_ ##NAME## _compat +#define OLD_LEN_FUNC(NAME) NAME## _len_old +#define OLD_PUSH_FUNC(NAME) NAME## _push_old +#define OLD_PULL_FUNC(NAME) NAME## _pull_old + +#define COMPAT_TYPE1_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE p = { 0 }, p1, p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + FILL_FUNC(NAME)(&p); \ + buflen1 = LEN_FUNC(NAME)(&p); \ + buflen2 = OLD_LEN_FUNC(NAME)(&p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(&p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(&p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(&p1, &p2); \ + talloc_free(mem_ctx); \ +} + +#define COMPAT_TYPE3_TEST(TYPE, NAME) \ +static void COMPAT_TEST_FUNC(NAME)(void) \ +{ \ + TALLOC_CTX *mem_ctx; \ + uint8_t *buf1, *buf2; \ + TYPE *p, *p1, *p2; \ + size_t buflen1, buflen2, np = 0; \ + int ret; \ +\ + mem_ctx = talloc_new(NULL); \ + assert(mem_ctx != NULL); \ + p = talloc_zero(mem_ctx, TYPE); \ + assert(p != NULL); \ + FILL_FUNC(NAME)(p, p); \ + buflen1 = LEN_FUNC(NAME)(p); \ + buflen2 = OLD_LEN_FUNC(NAME)(p); \ + assert(buflen1 == buflen2); \ + buf1 = talloc_zero_size(mem_ctx, buflen1); \ + assert(buf1 != NULL); \ + buf2 = talloc_zero_size(mem_ctx, buflen2); \ + assert(buf2 != NULL); \ + PUSH_FUNC(NAME)(p, buf1, &np); \ + OLD_PUSH_FUNC(NAME)(p, buf2); \ + assert(memcmp(buf1, buf2, buflen1) == 0); \ + ret = PULL_FUNC(NAME)(buf1, buflen1, mem_ctx, &p1, &np); \ + assert(ret == 0); \ + ret = OLD_PULL_FUNC(NAME)(buf2, buflen2, mem_ctx, &p2); \ + assert(ret == 0); \ + VERIFY_FUNC(NAME)(p1, p2); \ + talloc_free(mem_ctx); \ +} + + +static size_t ctdb_statistics_len_old(struct ctdb_statistics *in) +{ + return sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_push_old(struct ctdb_statistics *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics **out) +{ + struct ctdb_statistics *val; + + if (buflen < sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, buf, sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_vnn_map_wire { + uint32_t generation; + uint32_t size; + uint32_t map[1]; +}; + +static size_t ctdb_vnn_map_len_old(struct ctdb_vnn_map *in) +{ + return offsetof(struct ctdb_vnn_map, map) + + in->size * sizeof(uint32_t); +} + +static void ctdb_vnn_map_push_old(struct ctdb_vnn_map *in, uint8_t *buf) +{ + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_vnn_map, map)); + memcpy(wire->map, in->map, in->size * sizeof(uint32_t)); +} + +static int ctdb_vnn_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_vnn_map **out) +{ + struct ctdb_vnn_map *val; + struct ctdb_vnn_map_wire *wire = (struct ctdb_vnn_map_wire *)buf; + + if (buflen < offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (wire->size > buflen / sizeof(uint32_t)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t) < + offsetof(struct ctdb_vnn_map_wire, map)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_vnn_map_wire, map) + + wire->size * sizeof(uint32_t)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_vnn_map); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_vnn_map, map)); + + val->map = talloc_memdup(val, wire->map, + wire->size * sizeof(uint32_t)); + if (val->map == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_dbid_map_wire { + uint32_t num; + struct ctdb_dbid dbs[1]; +}; + +static size_t ctdb_dbid_map_len_old(struct ctdb_dbid_map *in) +{ + return sizeof(uint32_t) + in->num * sizeof(struct ctdb_dbid); +} + +static void ctdb_dbid_map_push_old(struct ctdb_dbid_map *in, uint8_t *buf) +{ + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + wire->num = in->num; + memcpy(wire->dbs, in->dbs, in->num * sizeof(struct ctdb_dbid)); +} + +static int ctdb_dbid_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_dbid_map **out) +{ + struct ctdb_dbid_map *val; + struct ctdb_dbid_map_wire *wire = (struct ctdb_dbid_map_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_dbid)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_dbid_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->dbs = talloc_memdup(val, wire->dbs, + wire->num * sizeof(struct ctdb_dbid)); + if (val->dbs == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_len_old(struct ctdb_pulldb *in) +{ + return sizeof(struct ctdb_pulldb); +} + +static void ctdb_pulldb_push_old(struct ctdb_pulldb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb)); +} + +static int ctdb_pulldb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_pulldb **out) +{ + struct ctdb_pulldb *val; + + if (buflen < sizeof(struct ctdb_pulldb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_pulldb_ext_len_old(struct ctdb_pulldb_ext *in) +{ + return sizeof(struct ctdb_pulldb_ext); +} + +static void ctdb_pulldb_ext_push_old(struct ctdb_pulldb_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_pulldb_ext)); +} + +static int ctdb_pulldb_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_pulldb_ext **out) +{ + struct ctdb_pulldb_ext *val; + + if (buflen < sizeof(struct ctdb_pulldb_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_pulldb_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_ltdb_header_len_old(struct ctdb_ltdb_header *in) +{ + return sizeof(struct ctdb_ltdb_header); +} + +static void ctdb_ltdb_header_push_old(struct ctdb_ltdb_header *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ltdb_header)); +} + +static int ctdb_ltdb_header_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_ltdb_header *out) +{ + if (buflen < sizeof(struct ctdb_ltdb_header)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_ltdb_header)); + return 0; +} + +struct ctdb_rec_data_wire { + uint32_t length; + uint32_t reqid; + uint32_t keylen; + uint32_t datalen; + uint8_t data[1]; +}; + +static size_t ctdb_rec_data_len_old(struct ctdb_rec_data *in) +{ + return offsetof(struct ctdb_rec_data_wire, data) + + in->key.dsize + in->data.dsize + + (in->header == NULL ? 0 : sizeof(struct ctdb_ltdb_header)); +} + +static void ctdb_rec_data_push_old(struct ctdb_rec_data *in, uint8_t *buf) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + wire->length = ctdb_rec_data_len(in); + wire->reqid = in->reqid; + wire->keylen = in->key.dsize; + wire->datalen = in->data.dsize; + if (in->header != NULL) { + wire->datalen += sizeof(struct ctdb_ltdb_header); + } + + memcpy(wire->data, in->key.dptr, in->key.dsize); + offset = in->key.dsize; + if (in->header != NULL) { + memcpy(&wire->data[offset], in->header, + sizeof(struct ctdb_ltdb_header)); + offset += sizeof(struct ctdb_ltdb_header); + } + if (in->data.dsize > 0) { + memcpy(&wire->data[offset], in->data.dptr, in->data.dsize); + } +} + +static int ctdb_rec_data_pull_data_old(uint8_t *buf, size_t buflen, + uint32_t *reqid, + struct ctdb_ltdb_header **header, + TDB_DATA *key, TDB_DATA *data, + size_t *reclen) +{ + struct ctdb_rec_data_wire *wire = (struct ctdb_rec_data_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (wire->keylen > buflen || wire->datalen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + wire->keylen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen < + offsetof(struct ctdb_rec_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen) { + return EMSGSIZE; + } + + *reqid = wire->reqid; + + key->dsize = wire->keylen; + key->dptr = wire->data; + offset = wire->keylen; + + /* Always set header to NULL. If it is required, exact it using + * ctdb_rec_data_extract_header() + */ + *header = NULL; + + data->dsize = wire->datalen; + data->dptr = &wire->data[offset]; + + *reclen = offsetof(struct ctdb_rec_data_wire, data) + + wire->keylen + wire->datalen; + + return 0; +} + +static int ctdb_rec_data_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data *out) +{ + uint32_t reqid; + struct ctdb_ltdb_header *header; + TDB_DATA key, data; + size_t reclen; + int ret; + + ret = ctdb_rec_data_pull_data_old(buf, buflen, &reqid, &header, + &key, &data, &reclen); + if (ret != 0) { + return ret; + } + + out->reqid = reqid; + out->header = NULL; + + out->key.dsize = key.dsize; + if (key.dsize > 0) { + out->key.dptr = talloc_memdup(mem_ctx, key.dptr, key.dsize); + if (out->key.dptr == NULL) { + return ENOMEM; + } + } + + out->data.dsize = data.dsize; + if (data.dsize > 0) { + out->data.dptr = talloc_memdup(mem_ctx, data.dptr, data.dsize); + if (out->data.dptr == NULL) { + return ENOMEM; + } + } + + return 0; +} + +static int ctdb_rec_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_data **out) +{ + struct ctdb_rec_data *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_rec_data); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_rec_data_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_rec_buffer_wire { + uint32_t db_id; + uint32_t count; + uint8_t data[1]; +}; + +static size_t ctdb_rec_buffer_len_old(struct ctdb_rec_buffer *in) +{ + return offsetof(struct ctdb_rec_buffer_wire, data) + in->buflen; +} + +static void ctdb_rec_buffer_push_old(struct ctdb_rec_buffer *in, uint8_t *buf) +{ + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + + wire->db_id = in->db_id; + wire->count = in->count; + if (in->buflen > 0) { + memcpy(wire->data, in->buf, in->buflen); + } +} + +static int ctdb_rec_buffer_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_rec_buffer **out) +{ + struct ctdb_rec_buffer *val; + struct ctdb_rec_buffer_wire *wire = (struct ctdb_rec_buffer_wire *)buf; + size_t offset; + + if (buflen < offsetof(struct ctdb_rec_buffer_wire, data)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_rec_buffer); + if (val == NULL) { + return ENOMEM; + } + + val->db_id = wire->db_id; + val->count = wire->count; + + offset = offsetof(struct ctdb_rec_buffer_wire, data); + val->buflen = buflen - offset; + val->buf = talloc_memdup(val, wire->data, val->buflen); + if (val->buf == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_len_old(struct ctdb_traverse_start *in) +{ + return sizeof(struct ctdb_traverse_start); +} + +static void ctdb_traverse_start_push_old(struct ctdb_traverse_start *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start)); +} + +static int ctdb_traverse_start_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start **out) +{ + struct ctdb_traverse_start *val; + + if (buflen < sizeof(struct ctdb_traverse_start)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_start)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_len_old(struct ctdb_traverse_all *in) +{ + return sizeof(struct ctdb_traverse_all); +} + +static void ctdb_traverse_all_push_old(struct ctdb_traverse_all *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all)); +} + +static int ctdb_traverse_all_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all **out) +{ + struct ctdb_traverse_all *val; + + if (buflen < sizeof(struct ctdb_traverse_all)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_traverse_all)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_start_ext_len_old( + struct ctdb_traverse_start_ext *in) +{ + return sizeof(struct ctdb_traverse_start_ext); +} + +static void ctdb_traverse_start_ext_push_old( + struct ctdb_traverse_start_ext *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_start_ext)); +} + +static int ctdb_traverse_start_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_start_ext **out) +{ + struct ctdb_traverse_start_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_start_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_start_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_traverse_all_ext_len_old(struct ctdb_traverse_all_ext *in) +{ + return sizeof(struct ctdb_traverse_all_ext); +} + +static void ctdb_traverse_all_ext_push_old(struct ctdb_traverse_all_ext *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_traverse_all_ext)); +} + +static int ctdb_traverse_all_ext_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_traverse_all_ext **out) +{ + struct ctdb_traverse_all_ext *val; + + if (buflen < sizeof(struct ctdb_traverse_all_ext)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_traverse_all_ext)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_sock_addr_len_old(ctdb_sock_addr *in) +{ + return sizeof(ctdb_sock_addr); +} + +static void ctdb_sock_addr_push_old(ctdb_sock_addr *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(ctdb_sock_addr)); +} + +static int ctdb_sock_addr_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + ctdb_sock_addr *out) +{ + if (buflen < sizeof(ctdb_sock_addr)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(ctdb_sock_addr)); + + return 0; +} + +static int ctdb_sock_addr_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, ctdb_sock_addr **out) +{ + ctdb_sock_addr *val; + int ret; + + val = talloc(mem_ctx, ctdb_sock_addr); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_sock_addr_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +static size_t ctdb_connection_len_old(struct ctdb_connection *in) +{ + return sizeof(struct ctdb_connection); +} + +static void ctdb_connection_push_old(struct ctdb_connection *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_connection)); +} + +static int ctdb_connection_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection *out) +{ + if (buflen < sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_connection)); + + return 0; +} + +static int ctdb_connection_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_connection **out) +{ + struct ctdb_connection *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_connection); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_connection_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_tunable_wire { + uint32_t value; + uint32_t length; + uint8_t name[1]; +}; + +static size_t ctdb_tunable_len_old(struct ctdb_tunable *in) +{ + return offsetof(struct ctdb_tunable_wire, name) + + strlen(in->name) + 1; +} + +static void ctdb_tunable_push_old(struct ctdb_tunable *in, uint8_t *buf) +{ + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + wire->value = in->value; + wire->length = strlen(in->name) + 1; + memcpy(wire->name, in->name, wire->length); +} + +static int ctdb_tunable_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable **out) +{ + struct ctdb_tunable *val; + struct ctdb_tunable_wire *wire = (struct ctdb_tunable_wire *)buf; + + if (buflen < offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tunable_wire, name) + wire->length < + offsetof(struct ctdb_tunable_wire, name)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tunable_wire, name) + wire->length) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tunable); + if (val == NULL) { + return ENOMEM; + } + + val->value = wire->value; + val->name = talloc_memdup(val, wire->name, wire->length); + if (val->name == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_node_flag_change_len_old(struct ctdb_node_flag_change *in) +{ + return sizeof(struct ctdb_node_flag_change); +} + +static void ctdb_node_flag_change_push_old(struct ctdb_node_flag_change *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_flag_change)); +} + +static int ctdb_node_flag_change_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_flag_change **out) +{ + struct ctdb_node_flag_change *val; + + if (buflen < sizeof(struct ctdb_node_flag_change)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_node_flag_change)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_var_list_wire { + uint32_t length; + char list_str[1]; +}; + +static size_t ctdb_var_list_len_old(struct ctdb_var_list *in) +{ + int i; + size_t len = sizeof(uint32_t); + + for (i=0; i<in->count; i++) { + assert(in->var[i] != NULL); + len += strlen(in->var[i]) + 1; + } + return len; +} + +static void ctdb_var_list_push_old(struct ctdb_var_list *in, uint8_t *buf) +{ + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + int i, n; + size_t offset = 0; + + if (in->count > 0) { + n = sprintf(wire->list_str, "%s", in->var[0]); + offset += n; + } + for (i=1; i<in->count; i++) { + n = sprintf(&wire->list_str[offset], ":%s", in->var[i]); + offset += n; + } + wire->length = offset + 1; +} + +static int ctdb_var_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_var_list **out) +{ + struct ctdb_var_list *val = NULL; + struct ctdb_var_list_wire *wire = (struct ctdb_var_list_wire *)buf; + char *str, *s, *tok, *ptr; + const char **list; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->length > buflen) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->length < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->length) { + return EMSGSIZE; + } + + str = talloc_strndup(mem_ctx, (char *)wire->list_str, wire->length); + if (str == NULL) { + return ENOMEM; + } + + val = talloc_zero(mem_ctx, struct ctdb_var_list); + if (val == NULL) { + goto fail; + } + + s = str; + while ((tok = strtok_r(s, ":", &ptr)) != NULL) { + s = NULL; + list = talloc_realloc(val, val->var, const char *, + val->count+1); + if (list == NULL) { + goto fail; + } + + val->var = list; + val->var[val->count] = talloc_strdup(val, tok); + if (val->var[val->count] == NULL) { + goto fail; + } + val->count++; + } + + talloc_free(str); + *out = val; + return 0; + +fail: + talloc_free(str); + talloc_free(val); + return ENOMEM; +} + +static size_t ctdb_tunable_list_len_old(struct ctdb_tunable_list *in) +{ + return sizeof(struct ctdb_tunable_list); +} + +static void ctdb_tunable_list_push_old(struct ctdb_tunable_list *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_tunable_list)); +} + +static int ctdb_tunable_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tunable_list **out) +{ + struct ctdb_tunable_list *val; + + if (buflen < sizeof(struct ctdb_tunable_list)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_tunable_list)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_tickle_list_wire { + ctdb_sock_addr addr; + uint32_t num; + struct ctdb_connection conn[1]; +}; + +static size_t ctdb_tickle_list_len_old(struct ctdb_tickle_list *in) +{ + return offsetof(struct ctdb_tickle_list, conn) + + in->num * sizeof(struct ctdb_connection); +} + +static void ctdb_tickle_list_push_old(struct ctdb_tickle_list *in, + uint8_t *buf) +{ + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + + memcpy(&wire->addr, &in->addr, sizeof(ctdb_sock_addr)); + wire->num = in->num; + + offset = offsetof(struct ctdb_tickle_list_wire, conn); + for (i=0; i<in->num; i++) { + ctdb_connection_push_old(&in->conn[i], &buf[offset]); + offset += ctdb_connection_len_old(&in->conn[i]); + } +} + +static int ctdb_tickle_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_tickle_list **out) +{ + struct ctdb_tickle_list *val; + struct ctdb_tickle_list_wire *wire = + (struct ctdb_tickle_list_wire *)buf; + size_t offset; + unsigned int i; + int ret; + + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection) < + offsetof(struct ctdb_tickle_list_wire, conn)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_tickle_list_wire, conn) + + wire->num * sizeof(struct ctdb_connection)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_tickle_list); + if (val == NULL) { + return ENOMEM; + } + + offset = offsetof(struct ctdb_tickle_list, conn); + memcpy(val, wire, offset); + + val->conn = talloc_array(val, struct ctdb_connection, wire->num); + if (val->conn == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; i<wire->num; i++) { + ret = ctdb_connection_pull_elems_old(&buf[offset], + buflen-offset, + val->conn, + &val->conn[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_connection_len_old(&val->conn[i]); + } + + *out = val; + return 0; +} + +struct ctdb_addr_info_wire { + ctdb_sock_addr addr; + uint32_t mask; + uint32_t len; + char iface[1]; +}; + +static size_t ctdb_addr_info_len_old(struct ctdb_addr_info *in) +{ + uint32_t len; + + len = offsetof(struct ctdb_addr_info_wire, iface); + if (in->iface != NULL) { + len += strlen(in->iface)+1; + } + + return len; +} + +static void ctdb_addr_info_push_old(struct ctdb_addr_info *in, uint8_t *buf) +{ + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + wire->addr = in->addr; + wire->mask = in->mask; + if (in->iface == NULL) { + wire->len = 0; + } else { + wire->len = strlen(in->iface)+1; + memcpy(wire->iface, in->iface, wire->len); + } +} + +static int ctdb_addr_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_addr_info **out) +{ + struct ctdb_addr_info *val; + struct ctdb_addr_info_wire *wire = (struct ctdb_addr_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_addr_info_wire, iface) + wire->len < + offsetof(struct ctdb_addr_info_wire, iface)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_addr_info_wire, iface) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_addr_info); + if (val == NULL) { + return ENOMEM; + } + + val->addr = wire->addr; + val->mask = wire->mask; + + if (wire->len == 0) { + val->iface = NULL; + } else { + val->iface = talloc_strndup(val, wire->iface, wire->len); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + } + + *out = val; + return 0; +} + +static size_t ctdb_transdb_len_old(struct ctdb_transdb *in) +{ + return sizeof(struct ctdb_transdb); +} + +static void ctdb_transdb_push_old(struct ctdb_transdb *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_transdb)); +} + +static int ctdb_transdb_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_transdb **out) +{ + struct ctdb_transdb *val; + + if (buflen < sizeof(struct ctdb_transdb)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_transdb)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_uptime_len_old(struct ctdb_uptime *in) +{ + return sizeof(struct ctdb_uptime); +} + +static void ctdb_uptime_push_old(struct ctdb_uptime *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_uptime)); +} + +static int ctdb_uptime_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_uptime **out) +{ + struct ctdb_uptime *val; + + if (buflen < sizeof(struct ctdb_uptime)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_uptime)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_public_ip_len_old(struct ctdb_public_ip *in) +{ + return sizeof(struct ctdb_public_ip); +} + +static void ctdb_public_ip_push_old(struct ctdb_public_ip *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_public_ip)); +} + +static int ctdb_public_ip_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip *out) +{ + if (buflen < sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_public_ip)); + + return 0; +} + +static int ctdb_public_ip_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip **out) +{ + struct ctdb_public_ip *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_public_ip); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_public_ip_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_public_ip_list_wire { + uint32_t num; + struct ctdb_public_ip ip[1]; +}; + +static size_t ctdb_public_ip_list_len_old(struct ctdb_public_ip_list *in) +{ + unsigned int i; + size_t len; + + len = sizeof(uint32_t); + for (i=0; i<in->num; i++) { + len += ctdb_public_ip_len_old(&in->ip[i]); + } + return len; +} + +static void ctdb_public_ip_list_push_old(struct ctdb_public_ip_list *in, + uint8_t *buf) +{ + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; i<in->num; i++) { + ctdb_public_ip_push_old(&in->ip[i], &buf[offset]); + offset += ctdb_public_ip_len_old(&in->ip[i]); + } +} + +static int ctdb_public_ip_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_list **out) +{ + struct ctdb_public_ip_list *val; + struct ctdb_public_ip_list_wire *wire = + (struct ctdb_public_ip_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_public_ip) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_public_ip)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + if (wire->num == 0) { + val->ip = NULL; + *out = val; + return 0; + } + val->ip = talloc_array(val, struct ctdb_public_ip, wire->num); + if (val->ip == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_public_ip_list_wire, ip); + for (i=0; i<wire->num; i++) { + ret = ctdb_public_ip_pull_elems_old(&buf[offset], + buflen-offset, + val->ip, + &val->ip[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_public_ip_len_old(&val->ip[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_node_and_flags_len_old(struct ctdb_node_and_flags *in) +{ + return sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_and_flags_push_old(struct ctdb_node_and_flags *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_node_and_flags)); +} + +static int ctdb_node_and_flags_pull_elems_old(TALLOC_CTX *mem_ctx, + uint8_t *buf, size_t buflen, + struct ctdb_node_and_flags *out) +{ + if (buflen < sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_node_and_flags)); + + return 0; +} + +static int ctdb_node_and_flags_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_and_flags **out) +{ + struct ctdb_node_and_flags *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_node_and_flags); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_node_and_flags_pull_elems_old(val, buf, buflen, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_node_map_wire { + uint32_t num; + struct ctdb_node_and_flags node[1]; +}; + +static size_t ctdb_node_map_len_old(struct ctdb_node_map *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_node_and_flags); +} + +static void ctdb_node_map_push_old(struct ctdb_node_map *in, uint8_t *buf) +{ + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + + wire->num = in->num; + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; i<in->num; i++) { + ctdb_node_and_flags_push_old(&in->node[i], &buf[offset]); + offset += ctdb_node_and_flags_len_old(&in->node[i]); + } +} + +static int ctdb_node_map_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_node_map **out) +{ + struct ctdb_node_map *val; + struct ctdb_node_map_wire *wire = (struct ctdb_node_map_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_node_and_flags) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + + wire->num * sizeof(struct ctdb_node_and_flags)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_node_map); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->node = talloc_array(val, struct ctdb_node_and_flags, wire->num); + if (val->node == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = offsetof(struct ctdb_node_map_wire, node); + for (i=0; i<wire->num; i++) { + ret = ctdb_node_and_flags_pull_elems_old(val->node, + &buf[offset], + buflen-offset, + &val->node[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_node_and_flags_len_old(&val->node[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_script_len_old(struct ctdb_script *in) +{ + return sizeof(struct ctdb_script); +} + +static void ctdb_script_push_old(struct ctdb_script *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_script)); +} + +static int ctdb_script_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script *out) +{ + if (buflen < sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_script)); + + return 0; +} + +static int ctdb_script_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_script **out) +{ + struct ctdb_script *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_script); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_script_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_script_list_wire { + uint32_t num_scripts; + struct ctdb_script script[1]; +}; + +static size_t ctdb_script_list_len_old(struct ctdb_script_list *in) +{ + unsigned int i; + size_t len; + + if (in == NULL) { + return 0; + } + + len = offsetof(struct ctdb_script_list_wire, script); + for (i=0; i<in->num_scripts; i++) { + len += ctdb_script_len_old(&in->script[i]); + } + return len; +} + +static void ctdb_script_list_push_old(struct ctdb_script_list *in, + uint8_t *buf) +{ + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + + if (in == NULL) { + return; + } + + wire->num_scripts = in->num_scripts; + + offset = offsetof(struct ctdb_script_list_wire, script); + for (i=0; i<in->num_scripts; i++) { + ctdb_script_push_old(&in->script[i], &buf[offset]); + offset += ctdb_script_len_old(&in->script[i]); + } +} + +static int ctdb_script_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_script_list **out) +{ + struct ctdb_script_list *val; + struct ctdb_script_list_wire *wire = + (struct ctdb_script_list_wire *)buf; + size_t offset; + unsigned int i; + bool ret; + + /* If event scripts have never been run, the result will be NULL */ + if (buflen == 0) { + *out = NULL; + return 0; + } + + offset = offsetof(struct ctdb_script_list_wire, script); + + if (buflen < offset) { + return EMSGSIZE; + } + if (wire->num_scripts > buflen / sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + if (offset + wire->num_scripts * sizeof(struct ctdb_script) < offset) { + return EMSGSIZE; + } + if (buflen < offset + wire->num_scripts * sizeof(struct ctdb_script)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_script_list); + if (val == NULL) { + return ENOMEM; + + } + + val->num_scripts = wire->num_scripts; + val->script = talloc_array(val, struct ctdb_script, wire->num_scripts); + if (val->script == NULL) { + talloc_free(val); + return ENOMEM; + } + + for (i=0; i<wire->num_scripts; i++) { + ret = ctdb_script_pull_elems_old(&buf[offset], buflen-offset, + val->script, + &val->script[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += ctdb_script_len_old(&val->script[i]); + } + + *out = val; + return 0; +} + +static size_t ctdb_ban_state_len_old(struct ctdb_ban_state *in) +{ + return sizeof(struct ctdb_ban_state); +} + +static void ctdb_ban_state_push_old(struct ctdb_ban_state *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_ban_state)); +} + +static int ctdb_ban_state_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_ban_state **out) +{ + struct ctdb_ban_state *val; + + if (buflen < sizeof(struct ctdb_ban_state)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_ban_state)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_notify_data_wire { + uint64_t srvid; + uint32_t len; + uint8_t data[1]; +}; + +static size_t ctdb_notify_data_len_old(struct ctdb_notify_data *in) +{ + return offsetof(struct ctdb_notify_data_wire, data) + + in->data.dsize; +} + +static void ctdb_notify_data_push_old(struct ctdb_notify_data *in, + uint8_t *buf) +{ + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + wire->srvid = in->srvid; + wire->len = in->data.dsize; + memcpy(wire->data, in->data.dptr, in->data.dsize); +} + +static int ctdb_notify_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_notify_data **out) +{ + struct ctdb_notify_data *val; + struct ctdb_notify_data_wire *wire = + (struct ctdb_notify_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (wire->len > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_notify_data_wire, data) + wire->len < + offsetof(struct ctdb_notify_data_wire, data)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_notify_data_wire, data) + wire->len) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_notify_data); + if (val == NULL) { + return ENOMEM; + } + + val->srvid = wire->srvid; + val->data.dsize = wire->len; + val->data.dptr = talloc_memdup(val, wire->data, wire->len); + if (val->data.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_iface_len_old(struct ctdb_iface *in) +{ + return sizeof(struct ctdb_iface); +} + +static void ctdb_iface_push_old(struct ctdb_iface *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_pull_elems_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface *out) +{ + if (buflen < sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_iface)); + + return 0; +} + +static int ctdb_iface_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, struct ctdb_iface **out) +{ + struct ctdb_iface *val; + int ret; + + val = talloc(mem_ctx, struct ctdb_iface); + if (val == NULL) { + return ENOMEM; + } + + ret = ctdb_iface_pull_elems_old(buf, buflen, val, val); + if (ret != 0) { + TALLOC_FREE(val); + return ret; + } + + *out = val; + return ret; +} + +struct ctdb_iface_list_wire { + uint32_t num; + struct ctdb_iface iface[1]; +}; + +static size_t ctdb_iface_list_len_old(struct ctdb_iface_list *in) +{ + return sizeof(uint32_t) + + in->num * sizeof(struct ctdb_iface); +} + +static void ctdb_iface_list_push_old(struct ctdb_iface_list *in, uint8_t *buf) +{ + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->iface, in->iface, in->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_iface_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_iface_list **out) +{ + struct ctdb_iface_list *val; + struct ctdb_iface_list_wire *wire = + (struct ctdb_iface_list_wire *)buf; + + if (buflen < sizeof(uint32_t)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface) < + sizeof(uint32_t)) { + return EMSGSIZE; + } + if (buflen < sizeof(uint32_t) + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_iface_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + val->iface = talloc_array(val, struct ctdb_iface, wire->num); + if (val->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->iface, wire->iface, wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_public_ip_info_wire { + struct ctdb_public_ip ip; + uint32_t active_idx; + uint32_t num; + struct ctdb_iface ifaces[1]; +}; + +static size_t ctdb_public_ip_info_len_old(struct ctdb_public_ip_info *in) +{ + return offsetof(struct ctdb_public_ip_info_wire, num) + + ctdb_iface_list_len_old(in->ifaces); +} + +static void ctdb_public_ip_info_push_old(struct ctdb_public_ip_info *in, + uint8_t *buf) +{ + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + size_t offset; + + offset = offsetof(struct ctdb_public_ip_info_wire, num); + memcpy(wire, in, offset); + wire->num = in->ifaces->num; + memcpy(wire->ifaces, in->ifaces->iface, + in->ifaces->num * sizeof(struct ctdb_iface)); +} + +static int ctdb_public_ip_info_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_public_ip_info **out) +{ + struct ctdb_public_ip_info *val; + struct ctdb_public_ip_info_wire *wire = + (struct ctdb_public_ip_info_wire *)buf; + + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface) < + offsetof(struct ctdb_public_ip_info_wire, ifaces)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_public_ip_info_wire, ifaces) + + wire->num * sizeof(struct ctdb_iface)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_public_ip_info); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_public_ip_info_wire, num)); + + val->ifaces = talloc(val, struct ctdb_iface_list); + if (val->ifaces == NULL) { + talloc_free(val); + return ENOMEM; + } + + val->ifaces->num = wire->num; + val->ifaces->iface = talloc_array(val->ifaces, struct ctdb_iface, + wire->num); + if (val->ifaces->iface == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->ifaces->iface, wire->ifaces, + wire->num * sizeof(struct ctdb_iface)); + + *out = val; + return 0; +} + +struct ctdb_statistics_list_wire { + uint32_t num; + struct ctdb_statistics stats[1]; +}; + +static size_t ctdb_statistics_list_len_old(struct ctdb_statistics_list *in) +{ + return offsetof(struct ctdb_statistics_list_wire, stats) + + in->num * sizeof(struct ctdb_statistics); +} + +static void ctdb_statistics_list_push_old(struct ctdb_statistics_list *in, + uint8_t *buf) +{ + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + wire->num = in->num; + memcpy(wire->stats, in->stats, + in->num * sizeof(struct ctdb_statistics)); +} + +static int ctdb_statistics_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_statistics_list **out) +{ + struct ctdb_statistics_list *val; + struct ctdb_statistics_list_wire *wire = + (struct ctdb_statistics_list_wire *)buf; + + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (wire->num > buflen / sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics) < + offsetof(struct ctdb_statistics_list_wire, stats)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_statistics_list_wire, stats) + + wire->num * sizeof(struct ctdb_statistics)) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_statistics_list); + if (val == NULL) { + return ENOMEM; + } + + val->num = wire->num; + + val->stats = talloc_array(val, struct ctdb_statistics, wire->num); + if (val->stats == NULL) { + talloc_free(val); + return ENOMEM; + } + + memcpy(val->stats, wire->stats, + wire->num * sizeof(struct ctdb_statistics)); + + *out = val; + return 0; +} + +struct ctdb_key_data_wire { + uint32_t db_id; + struct ctdb_ltdb_header header; + uint32_t keylen; + uint8_t key[1]; +}; + +static size_t ctdb_key_data_len_old(struct ctdb_key_data *in) +{ + return offsetof(struct ctdb_key_data_wire, key) + in->key.dsize; +} + +static void ctdb_key_data_push_old(struct ctdb_key_data *in, uint8_t *buf) +{ + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + memcpy(wire, in, offsetof(struct ctdb_key_data, key)); + wire->keylen = in->key.dsize; + memcpy(wire->key, in->key.dptr, in->key.dsize); +} + +static int ctdb_key_data_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_key_data **out) +{ + struct ctdb_key_data *val; + struct ctdb_key_data_wire *wire = (struct ctdb_key_data_wire *)buf; + + if (buflen < offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (wire->keylen > buflen) { + return EMSGSIZE; + } + if (offsetof(struct ctdb_key_data_wire, key) + wire->keylen < + offsetof(struct ctdb_key_data_wire, key)) { + return EMSGSIZE; + } + if (buflen < offsetof(struct ctdb_key_data_wire, key) + wire->keylen) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_key_data); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, offsetof(struct ctdb_key_data, key)); + + val->key.dsize = wire->keylen; + val->key.dptr = talloc_memdup(val, wire->key, wire->keylen); + if (val->key.dptr == NULL) { + talloc_free(val); + return ENOMEM; + } + + *out = val; + return 0; +} + +struct ctdb_db_statistics_wire { + struct ctdb_db_statistics dbstats; + char hot_keys_wire[1]; +}; + +static size_t ctdb_db_statistics_len_old(struct ctdb_db_statistics *in) +{ + size_t len; + int i; + + len = sizeof(struct ctdb_db_statistics); + for (i=0; i<MAX_HOT_KEYS; i++) { + len += in->hot_keys[i].key.dsize; + } + return len; +} + +static void ctdb_db_statistics_push_old(struct ctdb_db_statistics *in, + void *buf) +{ + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + int i; + + in->num_hot_keys = MAX_HOT_KEYS; + memcpy(wire, in, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; i<MAX_HOT_KEYS; i++) { + memcpy(&wire->hot_keys_wire[offset], + in->hot_keys[i].key.dptr, + in->hot_keys[i].key.dsize); + offset += in->hot_keys[i].key.dsize; + } +} + +static int ctdb_db_statistics_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_db_statistics **out) +{ + struct ctdb_db_statistics *val; + struct ctdb_db_statistics_wire *wire = + (struct ctdb_db_statistics_wire *)buf; + size_t offset; + unsigned int i; + + if (buflen < sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + + offset = 0; + for (i=0; i<wire->dbstats.num_hot_keys; i++) { + if (wire->dbstats.hot_keys[i].key.dsize > buflen) { + return EMSGSIZE; + } + if (offset + wire->dbstats.hot_keys[i].key.dsize < offset) { + return EMSGSIZE; + } + offset += wire->dbstats.hot_keys[i].key.dsize; + if (offset > buflen) { + return EMSGSIZE; + } + } + if (sizeof(struct ctdb_db_statistics) + offset < + sizeof(struct ctdb_db_statistics)) { + return EMSGSIZE; + } + if (buflen < sizeof(struct ctdb_db_statistics) + offset) { + return EMSGSIZE; + } + + val = talloc(mem_ctx, struct ctdb_db_statistics); + if (val == NULL) { + return ENOMEM; + } + + memcpy(val, wire, sizeof(struct ctdb_db_statistics)); + + offset = 0; + for (i=0; i<wire->dbstats.num_hot_keys; i++) { + uint8_t *ptr; + size_t key_size; + + key_size = val->hot_keys[i].key.dsize; + ptr = talloc_memdup(mem_ctx, &wire->hot_keys_wire[offset], + key_size); + if (ptr == NULL) { + talloc_free(val); + return ENOMEM; + } + val->hot_keys[i].key.dptr = ptr; + offset += key_size; + } + + *out = val; + return 0; +} + +static size_t ctdb_election_message_len_old(struct ctdb_election_message *in) +{ + return sizeof(struct ctdb_election_message); +} + +static void ctdb_election_message_push_old(struct ctdb_election_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_election_message)); +} + +static int ctdb_election_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_election_message **out) +{ + struct ctdb_election_message *val; + + if (buflen < sizeof(struct ctdb_election_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, + sizeof(struct ctdb_election_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_srvid_message_len_old(struct ctdb_srvid_message *in) +{ + return sizeof(struct ctdb_srvid_message); +} + +static void ctdb_srvid_message_push_old(struct ctdb_srvid_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_srvid_message)); +} + +static int ctdb_srvid_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_srvid_message **out) +{ + struct ctdb_srvid_message *val; + + if (buflen < sizeof(struct ctdb_srvid_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_srvid_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_disable_message_len_old(struct ctdb_disable_message *in) +{ + return sizeof(struct ctdb_disable_message); +} + +static void ctdb_disable_message_push_old(struct ctdb_disable_message *in, + uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_disable_message)); +} + +static int ctdb_disable_message_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_disable_message **out) +{ + struct ctdb_disable_message *val; + + if (buflen < sizeof(struct ctdb_disable_message)) { + return EMSGSIZE; + } + + val = talloc_memdup(mem_ctx, buf, sizeof(struct ctdb_disable_message)); + if (val == NULL) { + return ENOMEM; + } + + *out = val; + return 0; +} + +static size_t ctdb_server_id_len_old(struct ctdb_server_id *in) +{ + return sizeof(struct ctdb_server_id); +} + +static void ctdb_server_id_push_old(struct ctdb_server_id *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_server_id)); +} + +static int ctdb_server_id_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_server_id *out) +{ + if (buflen < sizeof(struct ctdb_server_id)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_server_id)); + return 0; +} + +static size_t ctdb_g_lock_len_old(struct ctdb_g_lock *in) +{ + return sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_push_old(struct ctdb_g_lock *in, uint8_t *buf) +{ + memcpy(buf, in, sizeof(struct ctdb_g_lock)); +} + +static int ctdb_g_lock_pull_old(uint8_t *buf, size_t buflen, + struct ctdb_g_lock *out) +{ + if (buflen < sizeof(struct ctdb_g_lock)) { + return EMSGSIZE; + } + + memcpy(out, buf, sizeof(struct ctdb_g_lock)); + return 0; +} + +static size_t ctdb_g_lock_list_len_old(struct ctdb_g_lock_list *in) +{ + return in->num * sizeof(struct ctdb_g_lock); +} + +static void ctdb_g_lock_list_push_old(struct ctdb_g_lock_list *in, + uint8_t *buf) +{ + size_t offset = 0; + unsigned int i; + + for (i=0; i<in->num; i++) { + ctdb_g_lock_push_old(&in->lock[i], &buf[offset]); + offset += sizeof(struct ctdb_g_lock); + } +} + +static int ctdb_g_lock_list_pull_old(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, + struct ctdb_g_lock_list **out) +{ + struct ctdb_g_lock_list *val; + unsigned count; + size_t offset; + unsigned int i; + int ret; + + val = talloc_zero(mem_ctx, struct ctdb_g_lock_list); + if (val == NULL) { + return ENOMEM; + } + + count = buflen / sizeof(struct ctdb_g_lock); + val->lock = talloc_array(val, struct ctdb_g_lock, count); + if (val->lock == NULL) { + talloc_free(val); + return ENOMEM; + } + + offset = 0; + for (i=0; i<count; i++) { + ret = ctdb_g_lock_pull_old(&buf[offset], buflen-offset, + &val->lock[i]); + if (ret != 0) { + talloc_free(val); + return ret; + } + offset += sizeof(struct ctdb_g_lock); + } + + val->num = count; + + *out = val; + return 0; +} + +COMPAT_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +COMPAT_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +COMPAT_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +COMPAT_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +COMPAT_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); + +COMPAT_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); + +COMPAT_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +COMPAT_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +COMPAT_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +COMPAT_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +COMPAT_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +COMPAT_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +COMPAT_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +COMPAT_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +COMPAT_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +COMPAT_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +COMPAT_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +COMPAT_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +COMPAT_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +COMPAT_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +COMPAT_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +COMPAT_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +COMPAT_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +COMPAT_TYPE3_TEST(struct ctdb_script, ctdb_script); +COMPAT_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +COMPAT_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +COMPAT_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +COMPAT_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +COMPAT_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +COMPAT_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +COMPAT_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +COMPAT_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +COMPAT_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); + +COMPAT_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +COMPAT_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +COMPAT_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); + +COMPAT_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +COMPAT_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); + +COMPAT_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +static void protocol_types_compat_test(void) +{ + COMPAT_TEST_FUNC(ctdb_statistics)(); + COMPAT_TEST_FUNC(ctdb_vnn_map)(); + COMPAT_TEST_FUNC(ctdb_dbid_map)(); + COMPAT_TEST_FUNC(ctdb_pulldb)(); + COMPAT_TEST_FUNC(ctdb_pulldb_ext)(); + COMPAT_TEST_FUNC(ctdb_ltdb_header)(); + COMPAT_TEST_FUNC(ctdb_rec_data)(); + COMPAT_TEST_FUNC(ctdb_rec_buffer)(); + COMPAT_TEST_FUNC(ctdb_traverse_start)(); + COMPAT_TEST_FUNC(ctdb_traverse_all)(); + COMPAT_TEST_FUNC(ctdb_traverse_start_ext)(); + COMPAT_TEST_FUNC(ctdb_traverse_all_ext)(); + COMPAT_TEST_FUNC(ctdb_sock_addr)(); + COMPAT_TEST_FUNC(ctdb_connection)(); + COMPAT_TEST_FUNC(ctdb_tunable)(); + COMPAT_TEST_FUNC(ctdb_node_flag_change)(); + COMPAT_TEST_FUNC(ctdb_var_list)(); + COMPAT_TEST_FUNC(ctdb_tunable_list)(); + COMPAT_TEST_FUNC(ctdb_tickle_list)(); + COMPAT_TEST_FUNC(ctdb_addr_info)(); + COMPAT_TEST_FUNC(ctdb_transdb)(); + COMPAT_TEST_FUNC(ctdb_uptime)(); + COMPAT_TEST_FUNC(ctdb_public_ip)(); + COMPAT_TEST_FUNC(ctdb_public_ip_list)(); + COMPAT_TEST_FUNC(ctdb_node_and_flags)(); + COMPAT_TEST_FUNC(ctdb_node_map)(); + COMPAT_TEST_FUNC(ctdb_script)(); + COMPAT_TEST_FUNC(ctdb_script_list)(); + COMPAT_TEST_FUNC(ctdb_ban_state)(); + COMPAT_TEST_FUNC(ctdb_notify_data)(); + COMPAT_TEST_FUNC(ctdb_iface)(); + COMPAT_TEST_FUNC(ctdb_iface_list)(); + COMPAT_TEST_FUNC(ctdb_public_ip_info)(); + COMPAT_TEST_FUNC(ctdb_statistics_list)(); + COMPAT_TEST_FUNC(ctdb_key_data)(); + COMPAT_TEST_FUNC(ctdb_db_statistics)(); + + COMPAT_TEST_FUNC(ctdb_election_message)(); + COMPAT_TEST_FUNC(ctdb_srvid_message)(); + COMPAT_TEST_FUNC(ctdb_disable_message)(); + COMPAT_TEST_FUNC(ctdb_server_id)(); + COMPAT_TEST_FUNC(ctdb_g_lock)(); + COMPAT_TEST_FUNC(ctdb_g_lock_list)(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_types_compat_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_types_test.c b/ctdb/tests/src/protocol_types_test.c new file mode 100644 index 0000000..f4a3048 --- /dev/null +++ b/ctdb/tests/src/protocol_types_test.c @@ -0,0 +1,194 @@ +/* + protocol types tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_sock.c" + +#include "tests/src/protocol_common.h" + +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_data); +PROTOCOL_TYPE2_TEST(TDB_DATA, ctdb_tdb_datan); +PROTOCOL_TYPE1_TEST(struct ctdb_latency_counter, ctdb_latency_counter); + +PROTOCOL_TYPE3_TEST(struct ctdb_statistics, ctdb_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_vnn_map, ctdb_vnn_map); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid, ctdb_dbid); +PROTOCOL_TYPE3_TEST(struct ctdb_dbid_map, ctdb_dbid_map); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb, ctdb_pulldb); +PROTOCOL_TYPE3_TEST(struct ctdb_pulldb_ext, ctdb_pulldb_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_db_vacuum, ctdb_db_vacuum); +PROTOCOL_TYPE3_TEST(struct ctdb_echo_data, ctdb_echo_data); +PROTOCOL_TYPE1_TEST(struct ctdb_ltdb_header, ctdb_ltdb_header); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_data, ctdb_rec_data); +PROTOCOL_TYPE3_TEST(struct ctdb_rec_buffer, ctdb_rec_buffer); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start, ctdb_traverse_start); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all, ctdb_traverse_all); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_start_ext, ctdb_traverse_start_ext); +PROTOCOL_TYPE3_TEST(struct ctdb_traverse_all_ext, ctdb_traverse_all_ext); +PROTOCOL_TYPE3_TEST(ctdb_sock_addr, ctdb_sock_addr); +PROTOCOL_TYPE3_TEST(struct ctdb_connection, ctdb_connection); +PROTOCOL_TYPE3_TEST(struct ctdb_connection_list, ctdb_connection_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable, ctdb_tunable); +PROTOCOL_TYPE3_TEST(struct ctdb_node_flag_change, ctdb_node_flag_change); +PROTOCOL_TYPE3_TEST(struct ctdb_var_list, ctdb_var_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tunable_list, ctdb_tunable_list); +PROTOCOL_TYPE3_TEST(struct ctdb_tickle_list, ctdb_tickle_list); +PROTOCOL_TYPE3_TEST(struct ctdb_addr_info, ctdb_addr_info); +PROTOCOL_TYPE3_TEST(struct ctdb_transdb, ctdb_transdb); +PROTOCOL_TYPE3_TEST(struct ctdb_uptime, ctdb_uptime); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip, ctdb_public_ip); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_list, ctdb_public_ip_list); +PROTOCOL_TYPE3_TEST(struct ctdb_node_and_flags, ctdb_node_and_flags); +PROTOCOL_TYPE3_TEST(struct ctdb_node_map, ctdb_node_map); +PROTOCOL_TYPE3_TEST(struct ctdb_script, ctdb_script); +PROTOCOL_TYPE3_TEST(struct ctdb_script_list, ctdb_script_list); +PROTOCOL_TYPE3_TEST(struct ctdb_ban_state, ctdb_ban_state); +PROTOCOL_TYPE3_TEST(struct ctdb_notify_data, ctdb_notify_data); +PROTOCOL_TYPE3_TEST(struct ctdb_iface, ctdb_iface); +PROTOCOL_TYPE3_TEST(struct ctdb_iface_list, ctdb_iface_list); +PROTOCOL_TYPE3_TEST(struct ctdb_public_ip_info, ctdb_public_ip_info); +PROTOCOL_TYPE3_TEST(struct ctdb_statistics_list, ctdb_statistics_list); +PROTOCOL_TYPE3_TEST(struct ctdb_key_data, ctdb_key_data); +PROTOCOL_TYPE3_TEST(struct ctdb_db_statistics, ctdb_db_statistics); +PROTOCOL_TYPE3_TEST(struct ctdb_pid_srvid, ctdb_pid_srvid); +PROTOCOL_TYPE3_TEST(struct ctdb_election_message, ctdb_election_message); +PROTOCOL_TYPE3_TEST(struct ctdb_srvid_message, ctdb_srvid_message); +PROTOCOL_TYPE3_TEST(struct ctdb_disable_message, ctdb_disable_message); +PROTOCOL_TYPE1_TEST(struct ctdb_server_id, ctdb_server_id); +PROTOCOL_TYPE1_TEST(struct ctdb_g_lock, ctdb_g_lock); +PROTOCOL_TYPE3_TEST(struct ctdb_g_lock_list, ctdb_g_lock_list); + +PROTOCOL_TYPE1_TEST(struct sock_packet_header, sock_packet_header); + +static void test_ctdb_rec_buffer_read_write(void) +{ + TALLOC_CTX *mem_ctx = talloc_new(NULL); + struct ctdb_rec_buffer *p1, **p2; + const char *filename = "ctdb_rec_buffer_test.dat"; + int count = 100; + int fd, i, ret; + off_t offset; + + p1 = talloc_array(mem_ctx, struct ctdb_rec_buffer, count); + assert(p1 != NULL); + for (i=0; i<count; i++) { + fill_ctdb_rec_buffer(mem_ctx, &p1[i]); + } + + fd = open(filename, O_RDWR|O_CREAT, 0600); + assert(fd != -1); + unlink(filename); + + for (i=0; i<count; i++) { + ret = ctdb_rec_buffer_write(&p1[i], fd); + assert(ret == 0); + } + + offset = lseek(fd, 0, SEEK_CUR); + assert(offset != -1); + offset = lseek(fd, -offset, SEEK_CUR); + assert(offset == 0); + + p2 = talloc_array(mem_ctx, struct ctdb_rec_buffer *, count); + assert(p2 != NULL); + + for (i=0; i<count; i++) { + ret = ctdb_rec_buffer_read(fd, mem_ctx, &p2[i]); + assert(ret == 0); + } + + close(fd); + + for (i=0; i<count; i++) { + verify_ctdb_rec_buffer(&p1[i], p2[i]); + } + + talloc_free(mem_ctx); +} + +static void protocol_types_test(void) +{ + TEST_FUNC(ctdb_tdb_data)(); + TEST_FUNC(ctdb_tdb_datan)(); + TEST_FUNC(ctdb_latency_counter)(); + + TEST_FUNC(ctdb_statistics)(); + TEST_FUNC(ctdb_vnn_map)(); + TEST_FUNC(ctdb_dbid)(); + TEST_FUNC(ctdb_dbid_map)(); + TEST_FUNC(ctdb_pulldb)(); + TEST_FUNC(ctdb_pulldb_ext)(); + TEST_FUNC(ctdb_db_vacuum)(); + TEST_FUNC(ctdb_echo_data)(); + TEST_FUNC(ctdb_ltdb_header)(); + TEST_FUNC(ctdb_rec_data)(); + TEST_FUNC(ctdb_rec_buffer)(); + TEST_FUNC(ctdb_traverse_start)(); + TEST_FUNC(ctdb_traverse_all)(); + TEST_FUNC(ctdb_traverse_start_ext)(); + TEST_FUNC(ctdb_traverse_all_ext)(); + TEST_FUNC(ctdb_sock_addr)(); + TEST_FUNC(ctdb_connection)(); + TEST_FUNC(ctdb_connection_list)(); + TEST_FUNC(ctdb_tunable)(); + TEST_FUNC(ctdb_node_flag_change)(); + TEST_FUNC(ctdb_var_list)(); + TEST_FUNC(ctdb_tunable_list)(); + TEST_FUNC(ctdb_tickle_list)(); + TEST_FUNC(ctdb_addr_info)(); + TEST_FUNC(ctdb_transdb)(); + TEST_FUNC(ctdb_uptime)(); + TEST_FUNC(ctdb_public_ip)(); + TEST_FUNC(ctdb_public_ip_list)(); + TEST_FUNC(ctdb_node_and_flags)(); + TEST_FUNC(ctdb_node_map)(); + TEST_FUNC(ctdb_script)(); + TEST_FUNC(ctdb_script_list)(); + TEST_FUNC(ctdb_ban_state)(); + TEST_FUNC(ctdb_notify_data)(); + TEST_FUNC(ctdb_iface)(); + TEST_FUNC(ctdb_iface_list)(); + TEST_FUNC(ctdb_public_ip_info)(); + TEST_FUNC(ctdb_statistics_list)(); + TEST_FUNC(ctdb_key_data)(); + TEST_FUNC(ctdb_db_statistics)(); + TEST_FUNC(ctdb_pid_srvid)(); + TEST_FUNC(ctdb_election_message)(); + TEST_FUNC(ctdb_srvid_message)(); + TEST_FUNC(ctdb_disable_message)(); + TEST_FUNC(ctdb_server_id)(); + TEST_FUNC(ctdb_g_lock)(); + TEST_FUNC(ctdb_g_lock_list)(); + + TEST_FUNC(sock_packet_header)(); + + test_ctdb_rec_buffer_read_write(); +} + +int main(int argc, const char *argv[]) +{ + protocol_test_iterate(argc, argv, protocol_types_test); + return 0; +} diff --git a/ctdb/tests/src/protocol_util_test.c b/ctdb/tests/src/protocol_util_test.c new file mode 100644 index 0000000..4ffe58c --- /dev/null +++ b/ctdb/tests/src/protocol_util_test.c @@ -0,0 +1,417 @@ +/* + protocol utilities tests + + Copyright (C) Martin Schwenke 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <assert.h> + +#include "protocol/protocol_basic.c" +#include "protocol/protocol_types.c" +#include "protocol/protocol_util.c" + +/* + * Test parsing of IPs, conversion to string + */ + +static void test_sock_addr_to_string(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + const char *s; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, with_port); + assert(strcmp(ip, s) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_from_string_bad(const char *ip, bool with_port) +{ + ctdb_sock_addr sa; + int ret; + + ret = ctdb_sock_addr_from_string(ip, &sa, with_port); + assert(ret == EINVAL); +} + +static void test_sock_addr_from_string_memcmp(const char *ip1, + const char* ip2) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, false); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, false); + assert(ret == 0); + ret = memcmp(&sa1, &sa2, sizeof(ctdb_sock_addr)); + assert(ret == 0); +} + +static void test_sock_addr_cmp(const char *ip1, const char *ip2, + bool with_port, int res) +{ + ctdb_sock_addr sa1, sa2; + int ret; + + ret = ctdb_sock_addr_from_string(ip1, &sa1, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_from_string(ip2, &sa2, with_port); + assert(ret == 0); + ret = ctdb_sock_addr_cmp(&sa1, &sa2); + if (ret < 0) { + ret = -1; + } else if (ret > 0) { + ret = 1; + } + + assert(ret == res); +} + +/* + * Test parsing of IP/mask, conversion to string + */ + +static void test_sock_addr_mask_from_string(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + const char *s, *t; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == 0); + s = ctdb_sock_addr_to_string(NULL, &sa, false); + assert(s != NULL); + t = talloc_asprintf(s, "%s/%u", s, mask); + assert(strcmp(ip_mask, t) == 0); + talloc_free(discard_const(s)); +} + +static void test_sock_addr_mask_from_string_bad(const char *ip_mask) +{ + ctdb_sock_addr sa; + unsigned mask; + int ret; + + ret = ctdb_sock_addr_mask_from_string(ip_mask, &sa, &mask); + assert(ret == EINVAL); +} + +/* + * Test parsing of connection, conversion to string + */ + +static void test_connection_to_string(const char *conn_str) +{ + TALLOC_CTX *tmp_ctx; + struct ctdb_connection conn; + const char *s, *r; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + /* + * Test non-reversed parse and render + */ + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed render + */ + r = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(r != NULL); + ret = strcmp(conn_str, r); + assert(ret != 0); + + /* + * Reversed parse with forward render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, false); + assert(s != NULL); + ret = strcmp(r, s); + assert(ret == 0); + + talloc_free(discard_const(s)); + + /* + * Reversed parse and render + */ + ret = ctdb_connection_from_string(conn_str, true, &conn); + assert(ret == 0); + + s = ctdb_connection_to_string(tmp_ctx, &conn, true); + assert(s != NULL); + ret = strcmp(conn_str, s); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_from_string_bad(const char *conn_str) +{ + struct ctdb_connection conn; + int ret; + + ret = ctdb_connection_from_string(conn_str, false, &conn); + assert(ret == EINVAL); +} + +/* + * Test connection list utilities + */ + +static void test_connection_list_read(const char *s1, const char *s2) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + const char *t; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == 0); + + close(pipefd[0]); + + ret = ctdb_connection_list_sort(conn_list); + assert(ret == 0); + + t = ctdb_connection_list_to_string(tmp_ctx, conn_list, false); + assert(t != NULL); + ret = strcmp(t, s2); + assert(ret == 0); + + talloc_free(tmp_ctx); +} + +static void test_connection_list_read_bad(const char *s1) +{ + TALLOC_CTX *tmp_ctx; + int pipefd[2]; + pid_t pid; + struct ctdb_connection_list *conn_list = NULL; + int ret; + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = pipe(pipefd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(pipefd[0]); + + ret = dup2(pipefd[1], STDOUT_FILENO); + assert(ret != -1); + + close(pipefd[1]); + + printf("%s", s1); + fflush(stdout); + + exit(0); + } + + close(pipefd[1]); + + ret = ctdb_connection_list_read(tmp_ctx, pipefd[0], false, &conn_list); + assert(ret == EINVAL); + + close(pipefd[0]); + + talloc_free(tmp_ctx); +} + +/* + * Use macros for these to make them easy to concatenate + */ + +#define CONN4 \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +" + +#define CONN4_SORT \ +"\ +127.0.0.1:12345 127.0.0.2:54321\n\ +127.0.0.1:12345 127.0.0.2:54325\n\ +127.0.0.1:12346 127.0.0.2:54323\n\ +127.0.0.2:12345 127.0.0.1:54322\n\ +127.0.0.2:12345 127.0.0.1:54324\n\ +" + +#define CONN6 \ +"\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +" + +#define CONN6_SORT \ +"\ +[fe80::6af7:28ff:fefa:d132]:12345 [fe80::6af7:28ff:fefa:d137]:54324\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54321\n\ +[fe80::6af7:28ff:fefa:d136]:12345 [fe80::6af7:28ff:fefa:d137]:54325\n\ +[fe80::6af7:28ff:fefa:d136]:12346 [fe80::6af7:28ff:fefa:d137]:54323\n\ +[fe80::6af7:28ff:fefa:d138]:12345 [fe80::6af7:28ff:fefa:d137]:54322\n\ +" + +int main(int argc, char *argv[]) +{ + test_sock_addr_to_string("0.0.0.0", false); + test_sock_addr_to_string("127.0.0.1", false); + test_sock_addr_to_string("::1", false); + test_sock_addr_to_string("192.168.2.1", false); + test_sock_addr_to_string("fe80::6af7:28ff:fefa:d136", false); + + test_sock_addr_to_string("0.0.0.0:0", true); + test_sock_addr_to_string("127.0.0.1:123", true); + test_sock_addr_to_string("[::1]:234", true); + test_sock_addr_to_string("192.168.2.1:123", true); + test_sock_addr_to_string("[fe80::6af7:28ff:fefa:d136]:234", true); + + test_sock_addr_from_string_bad("0.0.0", false); + test_sock_addr_from_string_bad("0.0.0:0", true); + test_sock_addr_from_string_bad("fe80::6af7:28ff:fefa:d136", true); + test_sock_addr_from_string_bad("junk", false); + test_sock_addr_from_string_bad("0.0.0.0:0 trailing junk", true); + + test_sock_addr_from_string_memcmp("127.0.0.1", "127.0.0.1"); + test_sock_addr_from_string_memcmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136"); + test_sock_addr_from_string_memcmp("::ffff:192.0.2.128", "192.0.2.128"); + + test_sock_addr_cmp("127.0.0.1", "127.0.0.1" , false, 0); + test_sock_addr_cmp("127.0.0.1", "127.0.0.2" , false, -1); + test_sock_addr_cmp("127.0.0.2", "127.0.0.1" , false, 1); + test_sock_addr_cmp("127.0.1.2", "127.0.2.1" , false, -1); + test_sock_addr_cmp("127.0.2.1", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", "127.0.1.2" , false, 1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d136" , false, 0); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80::6af7:28ff:fefa:d137" , false, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136", + "fe80:0000:0000:0000:6af7:28ff:fefa:d136" , + false, 0); + test_sock_addr_cmp("::ffff:192.0.2.128", "192.0.2.128", false, 0); + + test_sock_addr_cmp("127.0.0.1:123", "127.0.0.1:124" , true, -1); + test_sock_addr_cmp("fe80::6af7:28ff:fefa:d136:123", + "fe80::6af7:28ff:fefa:d136:122" , true, 1); + + /* + * Confirm equivalence of IPv6 sockets with and without + * square-brackets + */ + test_sock_addr_cmp("[::1]:234", "::1:234", true, 0); + test_sock_addr_cmp("[fe80::6af7:28ff:fefa:d136]:234", + "fe80::6af7:28ff:fefa:d136:234", + true, + 0); + /* Check IPv4-mapped IPv6 addresses */ + test_sock_addr_cmp("::ffff:172.16.0.27:977", + "172.16.0.27:977", + true, + 0); + test_sock_addr_cmp("[::ffff:172.16.0.27]:977", + "172.16.0.27:977", + true, + 0); + + test_sock_addr_mask_from_string("127.0.0.1/8"); + test_sock_addr_mask_from_string("::1/128"); + test_sock_addr_mask_from_string("fe80::6af7:28ff:fefa:d136/64"); + test_sock_addr_mask_from_string_bad("127.0.0.1"); + + test_connection_to_string("127.0.0.1:12345 127.0.0.2:54321"); + test_connection_to_string("[fe80::6af7:28ff:fefa:d137]:12345 " + "[fe80::6af7:28ff:fefa:d138]:54321"); + + test_connection_from_string_bad("127.0.0.1:12345 127.0.0.2:"); + test_connection_from_string_bad("127.0.0.1:12345"); + test_connection_from_string_bad("127.0.0.1:12345 " + "[fe80::6af7:28ff:fefa:d136]:122"); + test_connection_from_string_bad("Junk!"); + test_connection_from_string_bad("More junk"); + + test_connection_list_read(CONN4, CONN4_SORT); + test_connection_list_read(CONN6, CONN6_SORT); + test_connection_list_read(CONN4 CONN6, CONN4_SORT CONN6_SORT); + test_connection_list_read(CONN4 "# Comment\n\n# Comment\n" CONN6, + CONN4_SORT CONN6_SORT); + + test_connection_list_read_bad(CONN4 "# Comment\n\nJunk!!!\n" CONN6); + test_connection_list_read_bad(CONN4 + "# Comment\n\n127.0.0.1: 127.0.0.1:124\n" + CONN6); + + return 0; +} diff --git a/ctdb/tests/src/rb_test.c b/ctdb/tests/src/rb_test.c new file mode 100644 index 0000000..7dc0629 --- /dev/null +++ b/ctdb/tests/src/rb_test.c @@ -0,0 +1,336 @@ +/* + simple rb test tool + + Copyright (C) Ronnie Sahlberg 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/time.h" + +#include <talloc.h> +#include <assert.h> + +#include "lib/util/dlinklist.h" +#include "lib/util/debug.h" + +#include "common/rb_tree.c" + +static struct timeval tp1,tp2; + +static void start_timer(void) +{ + gettimeofday(&tp1,NULL); +} + +static double end_timer(void) +{ + gettimeofday(&tp2,NULL); + return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) - + (tp1.tv_sec + (tp1.tv_usec*1.0e-6)); +} + +int num_records=5; + +static void *callback(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + if (d==NULL) { + data = (uint32_t *)p; + } + + (*data)++; + + return data; +} + +static void *random_add(void *p, void *d) +{ + return p; +} + +static int traverse(void *p, void *d) +{ + uint32_t *data = (uint32_t *)d; + + printf("traverse data:%d\n",*data); + return 0; +} + +static int random_traverse(void *p, void *d) +{ + printf("%s ",(char *)d); + return 0; +} + +static uint32_t calc_checksum = 0; +static int traverse_checksum(void *p, void *d) +{ + int i,j,k; + + sscanf(d, "%d.%d.%d", &i, &j, &k); + calc_checksum += i*100+j*10+k; + return 0; +} + +static int count_traverse(void *p, void *d) +{ + int *count = p; + (*count)++; + return 0; +} + +static int count_traverse_abort(void *p, void *d) +{ + int *count = p; + (*count)++; + return -1; +} + +/* + main program +*/ +int main(int argc, const char *argv[]) +{ + int traverse_count; + int i,j,k; + trbt_tree_t *tree; + uint32_t *data; + uint32_t key[3]; + uint32_t key1[3] = {0,10,20}; + uint32_t key2[3] = {0,10,21}; + uint32_t key3[3] = {0,11,20}; + uint32_t key4[3] = {2,10,20}; + TALLOC_CTX *memctx; + uint32_t **u32array; + uint32_t checksum; + + /* testing trbt_insert32_callback for num_records */ + memctx = talloc_new(NULL); + assert(memctx != NULL); + + u32array = talloc_array(memctx, uint32_t *, num_records); + assert(u32array != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + for (i=0; i<num_records; i++) { + u32array[i] = talloc(u32array, uint32_t); + assert(u32array[i] != NULL); + *u32array[i] = 0; + trbt_insert32_callback(tree, i, callback, u32array[i]); + } + for (i=3; i<num_records; i++) { + trbt_insert32_callback(tree, i, callback, NULL); + } + + /* first 3 keys should have data == 1 + * the rest of the keys should have data == 2 + */ + for (i=0; i<num_records; i++) { + data = trbt_lookup32(tree, i); + assert(data != NULL); + if (i < 3) { + assert(*data == 1); + } else { + assert(*data == 2); + } + } + + /* deleting key 2 */ + talloc_free(u32array[2]); + + /* deleting key 1 */ + talloc_free(u32array[1]); + + assert(talloc_total_size(memctx) == 212); + + /* freeing tree */ + talloc_free(memctx); + + + printf("testing trbt_insertarray32_callback\n"); + memctx = talloc_new(NULL); + assert(memctx != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + u32array = talloc_array(memctx, uint32_t *, 4); + assert(u32array != NULL); + + for (i=0;i<4;i++) { + u32array[i] = talloc(u32array, uint32_t); + assert(u32array[i] != NULL); + *u32array[i] = 0; + } + + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]); + trbt_insertarray32_callback(tree, 3, key3, callback, u32array[2]); + trbt_insertarray32_callback(tree, 3, key2, callback, u32array[1]); + trbt_insertarray32_callback(tree, 3, key1, callback, u32array[0]); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data != NULL && *data == 2); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key4\n"); + talloc_free(trbt_lookuparray32(tree, 3, key4)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data != NULL && *data == 2); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key2\n"); + talloc_free(trbt_lookuparray32(tree, 3, key2)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data != NULL && *data == 1); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key3\n"); + talloc_free(trbt_lookuparray32(tree, 3, key3)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data != NULL && *data == 3); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + printf("\ndeleting key1\n"); + talloc_free(trbt_lookuparray32(tree, 3, key1)); + + data = trbt_lookuparray32(tree, 3, key1); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key2); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key3); + assert(data == NULL); + data = trbt_lookuparray32(tree, 3, key4); + assert(data == NULL); + trbt_traversearray32(tree, 3, traverse, NULL); + + talloc_free(tree); + talloc_free(memctx); + + + printf("\nrun random insert and delete for 60 seconds\n"); + memctx = talloc_new(NULL); + assert(memctx != NULL); + + tree = trbt_create(memctx, 0); + assert(tree != NULL); + + i=0; + start_timer(); + checksum = 0; + /* add and delete nodes from a 3 level tree fro 60 seconds. + each time a node is added or deleted, traverse the tree and + compute a checksum over the data stored in the tree and compare this + with a checksum we keep which contains what the checksum should be + */ + while(end_timer() < 60.0){ + char *str; + + i++; + key[0]=random()%10; + key[1]=random()%10; + key[2]=random()%10; + + if (random()%2) { + if (trbt_lookuparray32(tree, 3, key) == NULL) { + /* this node does not yet exist, add it to the + tree and update the checksum + */ + str=talloc_asprintf(memctx, "%d.%d.%d", key[0],key[1],key[2]); + trbt_insertarray32_callback(tree, 3, key, random_add, str); + checksum += key[0]*100+key[1]*10+key[2]; + } + } else { + if ((str=trbt_lookuparray32(tree, 3, key)) != NULL) { + /* this node does exist in the tree, delete + it and update the checksum accordingly + */ + talloc_free(str); + checksum -= key[0]*100+key[1]*10+key[2]; + } + } + /* traverse all nodes in the tree and calculate the checksum + it better match the one we keep track of in + 'checksum' + */ + calc_checksum = 0; + trbt_traversearray32(tree, 3, traverse_checksum, NULL); + assert(checksum == calc_checksum); + } + + /* + printf("\niterations passed:%d\n", i); + trbt_traversearray32(tree, 3, random_traverse, NULL); + printf("\n"); + printf("first node: %s\n", (char *)trbt_findfirstarray32(tree, 3)); + */ + + traverse_count = 0; + trbt_traversearray32(tree, 3, count_traverse, &traverse_count); + assert(traverse_count > 0); + + traverse_count = 0; + trbt_traversearray32(tree, 3, count_traverse_abort, &traverse_count); + assert(traverse_count == 1); + + printf("\ndeleting all entries\n"); + for(i=0;i<10;i++){ + for(j=0;j<10;j++){ + for(k=0;k<10;k++){ + key[0]=i; + key[1]=j; + key[2]=k; + talloc_free(trbt_lookuparray32(tree, 3, key)); + } + } + } + trbt_traversearray32(tree, 3, random_traverse, NULL); + + assert(talloc_total_size(memctx) == 16); + + return 0; +} diff --git a/ctdb/tests/src/reqid_test.c b/ctdb/tests/src/reqid_test.c new file mode 100644 index 0000000..2a0828c --- /dev/null +++ b/ctdb/tests/src/reqid_test.c @@ -0,0 +1,89 @@ +/* + reqid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/reqid.c" + + +int main(void) +{ + struct reqid_context *reqid_ctx; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + int i, ret; + uint32_t reqid; + int *data, *tmp; + + ret = reqid_init(mem_ctx, INT_MAX-200, &reqid_ctx); + assert(ret == 0); + + data = talloc_zero(mem_ctx, int); + assert(data != 0); + + for (i=0; i<1024*1024; i++) { + reqid = reqid_new(reqid_ctx, data); + assert(reqid != REQID_INVALID); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == data); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == 0); + } + + for (i=0; i<1024; i++) { + tmp = reqid_find(reqid_ctx, i, int); + assert(tmp == NULL); + } + + for (i=0; i<1024; i++) { + ret = reqid_remove(reqid_ctx, i); + assert(ret == ENOENT); + } + + talloc_free(reqid_ctx); + assert(talloc_get_size(mem_ctx) == 0); + + ret = reqid_init(mem_ctx, INT_MAX-1, &reqid_ctx); + assert(ret == 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == INT_MAX); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 0); + + reqid_remove(reqid_ctx, 0); + + reqid = reqid_new(reqid_ctx, data); + assert(reqid == 1); + + talloc_free(reqid_ctx); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/run_event_test.c b/ctdb/tests/src/run_event_test.c new file mode 100644 index 0000000..9454864 --- /dev/null +++ b/ctdb/tests/src/run_event_test.c @@ -0,0 +1,251 @@ +/* + run_event test wrapper + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "common/db_hash.c" +#include "common/run_proc.c" +#include "common/event_script.c" +#include "common/run_event.c" + +static void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s <scriptdir> run|list|enable|disable <options>\n", prog); + fprintf(stderr, " %s <scriptdir> run <timeout> <event> [<args>]\n", prog); + fprintf(stderr, " %s <scriptdir> list\n", prog); + fprintf(stderr, " %s <scriptdir> enable <scriptname>\n", prog); + fprintf(stderr, " %s <scriptdir> disable <scriptname>\n", prog); +} + +static char *compact_args(const char **argv, int argc, int from) +{ + char *arg_str = NULL; + int i; + + for (i = from; i < argc; i++) { + arg_str = talloc_asprintf_append(arg_str, "%s ", argv[i]); + if (arg_str == NULL) { + fprintf(stderr, "talloc_asprintf_append() failed\n"); + exit(1); + } + } + + return arg_str; +} + +static void run_done(struct tevent_req *req) +{ + struct run_event_script_list **script_list = + tevent_req_callback_data_void(req); + bool status; + int ret; + + status = run_event_recv(req, &ret, NULL, script_list); + if (!status) { + fprintf(stderr, "run_event_recv() failed, ret=%d\n", ret); + } +} + +static void do_run(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct tevent_req *req; + struct timeval timeout; + struct run_event_script_list *script_list = NULL; + char *arg_str; + unsigned int i; + int t; + bool wait_for_signal = false; + + if (argc < 5) { + usage(argv[0]); + exit(1); + } + + t = atoi(argv[3]); + if (t > 0) { + timeout = tevent_timeval_current_ofs(t, 0); + } else { + timeout = tevent_timeval_zero(); + } + + arg_str = compact_args(argv, argc, 5); + + req = run_event_send(mem_ctx, + ev, + run_ctx, + argv[4], + arg_str, + timeout, + false); + if (req == NULL) { + fprintf(stderr, "run_event_send() failed\n"); + return; + } + + tevent_req_set_callback(req, run_done, &script_list); + + tevent_req_poll(req, ev); + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + printf("Event %s completed with result=%d\n", + argv[4], script_list->summary); + for (i=0; i<script_list->num_scripts; i++) { + struct run_event_script *s = &script_list->script[i]; + printf("%s result=%d\n", s->name, s->summary); + + if (s->summary == -ETIMEDOUT) { + wait_for_signal = true; + } + } + + TALLOC_FREE(script_list); + TALLOC_FREE(req); + + if (!wait_for_signal) { + return; + } + + req = tevent_wakeup_send( + ev, ev, tevent_timeval_current_ofs(10, 0)); + if (req == NULL) { + fprintf(stderr, "Could not wait for signal\n"); + return; + } + + tevent_req_poll(req, ev); + TALLOC_FREE(req); +} + +static void do_list(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + struct run_event_script_list *script_list = NULL; + unsigned int i; + int ret; + + ret = run_event_list(run_ctx, mem_ctx, &script_list); + if (ret != 0) { + printf("Script list failed with result=%d\n", ret); + return; + } + + if (script_list == NULL || script_list->num_scripts == 0) { + printf("No event scripts found\n"); + return; + } + + for (i=0; i<script_list->num_scripts; i++) { + printf("%s\n", script_list->script[i].name); + } +} + +static void do_enable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_enable(run_ctx, argv[3]); + printf("Script enable %s completed with result=%d\n", argv[3], ret); +} + +static void do_disable(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct run_event_context *run_ctx, + int argc, const char **argv) +{ + int ret; + + if (argc != 4) { + usage(argv[0]); + exit(1); + } + + ret = run_event_script_disable(run_ctx, argv[3]); + printf("Script disable %s completed with result=%d\n", argv[3], ret); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct run_proc_context *run_proc_ctx = NULL; + struct run_event_context *run_ctx = NULL; + int ret; + + if (argc < 3) { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + ret = run_proc_init(mem_ctx, ev, &run_proc_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + ret = run_event_init(mem_ctx, run_proc_ctx, argv[1], NULL, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_event_init() failed, ret=%d\n", ret); + exit(1); + } + + if (strcmp(argv[2], "run") == 0) { + do_run(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "list") == 0) { + do_list(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "enable") == 0) { + do_enable(mem_ctx, ev, run_ctx, argc, argv); + } else if (strcmp(argv[2], "disable") == 0) { + do_disable(mem_ctx, ev, run_ctx, argc, argv); + } else { + fprintf(stderr, "Invalid command %s\n", argv[2]); + usage(argv[0]); + } + + talloc_free(mem_ctx); + exit(0); +} + diff --git a/ctdb/tests/src/run_proc_test.c b/ctdb/tests/src/run_proc_test.c new file mode 100644 index 0000000..7cfb870 --- /dev/null +++ b/ctdb/tests/src/run_proc_test.c @@ -0,0 +1,111 @@ +/* + run_proc test wrapper + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <talloc.h> +#include <tevent.h> + +#include "common/db_hash.c" +#include "common/run_proc.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + struct run_proc_context *run_ctx; + struct timeval tv; + char *output; + struct run_proc_result result; + pid_t pid; + int timeout, ret, fd; + bool status; + + if (argc < 4) { + fprintf(stderr, + "Usage: %s <timeout> <stdin-fd> <program> <args>\n", + argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "talloc_new() failed\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "tevent_context_init() failed\n"); + exit(1); + } + + timeout = atoi(argv[1]); + if (timeout <= 0) { + tv = tevent_timeval_zero(); + } else { + tv = tevent_timeval_current_ofs(timeout, 0); + } + + fd = atoi(argv[2]); + if (fd < 0) { + fd = -1; + } + + ret = run_proc_init(mem_ctx, ev, &run_ctx); + if (ret != 0) { + fprintf(stderr, "run_proc_init() failed, ret=%d\n", ret); + exit(1); + } + + req = run_proc_send(mem_ctx, ev, run_ctx, argv[3], &argv[3], fd, tv); + if (req == NULL) { + fprintf(stderr, "run_proc_send() failed\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = run_proc_recv(req, &ret, &result, &pid, mem_ctx, &output); + if (! status) { + fprintf(stderr, "run_proc_recv() failed, ret=%d\n", ret); + exit(1); + } + + if (result.sig > 0) { + printf("Process exited with signal %d\n", result.sig); + } else if (result.err > 0) { + printf("Process exited with error %d\n", result.err); + } else { + printf("Process exited with status %d\n", result.status); + } + + if (pid != -1) { + printf("Child = %d\n", pid); + } + + if (output != NULL) { + printf("Output = (%s)\n", output); + } + + talloc_free(mem_ctx); + + exit(0); +} diff --git a/ctdb/tests/src/sigcode.c b/ctdb/tests/src/sigcode.c new file mode 100644 index 0000000..9e5ed81 --- /dev/null +++ b/ctdb/tests/src/sigcode.c @@ -0,0 +1,120 @@ +/* + Portability layer for signal codes + + Copyright (C) Amitay Isaacs 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * These signals are as listed in POSIX standard + * IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008) + */ + +#include "replace.h" +#include "system/wait.h" + +struct { + const char *label; + int code; +} sig_codes[] = { + { "SIGABRT", SIGABRT }, + { "SIGALRM", SIGALRM }, + { "SIBGUS", SIGBUS }, + { "SIGCHLD", SIGCHLD }, + { "SIGCONT", SIGCONT }, + { "SIGFPE", SIGFPE }, + { "SIGHUP", SIGHUP }, + { "SIGILL", SIGILL }, + { "SIGINT", SIGINT }, + { "SIGKILL", SIGKILL }, + { "SIGPIPE", SIGPIPE }, + { "SIGQUIT", SIGQUIT }, + { "SIGSEGV", SIGSEGV }, + { "SIGSTOP", SIGSTOP }, + { "SIGTERM", SIGTERM }, + { "SIGTSTP", SIGTSTP }, + { "SIGTTIN", SIGTTIN }, + { "SIGTTOU", SIGTTOU }, + { "SIGUSR1", SIGUSR1 }, + { "SIGUSR2", SIGUSR2 }, + { "SIGTRAP", SIGTRAP }, + { "SIGURG", SIGURG }, + { "SIGXCPU", SIGXCPU }, + { "SIGXFSZ", SIGXFSZ }, + +}; + +static void dump(void) +{ + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + printf("%s %d\n", sig_codes[i].label, sig_codes[i].code); + } +} + +static void match_label(const char *str) +{ + int code = -1; + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + if (strcasecmp(sig_codes[i].label, str) == 0) { + code = sig_codes[i].code; + break; + } + } + + printf("%d\n", code); +} + +static void match_code(int code) +{ + const char *label = "UNKNOWN"; + size_t i; + + for (i=0; i<ARRAY_SIZE(sig_codes); i++) { + if (sig_codes[i].code == code) { + label = sig_codes[i].label; + break; + } + } + + printf("%s\n", label); +} + +int main(int argc, const char **argv) +{ + long int code; + char *endptr; + + if (argc != 2) { + fprintf(stderr, "Usage: %s dump|<sigcode>\n", argv[0]); + exit(1); + } + + if (strcmp(argv[1], "dump") == 0) { + dump(); + } else { + code = strtol(argv[1], &endptr, 0); + if (*endptr == '\0') { + match_code(code); + } else { + match_label(argv[1]); + } + } + + exit(0); +} diff --git a/ctdb/tests/src/sock_daemon_test.c b/ctdb/tests/src/sock_daemon_test.c new file mode 100644 index 0000000..acafc9f --- /dev/null +++ b/ctdb/tests/src/sock_daemon_test.c @@ -0,0 +1,1980 @@ +/* + sock daemon tests + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/logging.c" +#include "common/pkt_read.c" +#include "common/pkt_write.c" +#include "common/comm.c" +#include "common/pidfile.c" +#include "common/sock_daemon.c" +#include "common/sock_io.c" + +struct dummy_wait_state { +}; + +static struct tevent_req *dummy_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct dummy_wait_state *state; + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == 0); + assert(S_ISSOCK(st.st_mode)); + + req = tevent_req_create(mem_ctx, &state, struct dummy_wait_state); + if (req == NULL) { + return NULL; + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static bool dummy_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static int test1_startup_fail(void *private_data) +{ + return 1; +} + +static int test1_startup(void *private_data) +{ + const char *sockpath = (const char *)private_data; + struct stat st; + int ret; + + ret = stat(sockpath, &st); + assert(ret == -1); + + return 0; +} + +struct test1_startup_state { +}; + +static struct tevent_req *test1_startup_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test1_startup_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test1_startup_state); + if (req == NULL) { + return NULL; + } + + tevent_req_error(req, 2); + return tevent_req_post(req, ev); +} + +static bool test1_startup_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static struct tevent_req *dummy_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + return NULL; +} + +static bool dummy_read_recv(struct tevent_req *req, int *perr) +{ + if (perr != NULL) { + *perr = EINVAL; + } + return false; +} + +static struct sock_socket_funcs dummy_socket_funcs = { + .read_send = dummy_read_send, + .read_recv = dummy_read_recv, +}; + +/* + * test1 + * + * Check setup without actually running daemon + */ + +static void test1(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test1_funcs; + struct stat st; + int ret; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup_fail, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup_send = test1_startup_send, + .startup_recv = test1_startup_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, NULL, &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == EIO); + talloc_free(sockd); + + test1_funcs = (struct sock_daemon_funcs){ + .startup = test1_startup, + .wait_send = dummy_wait_send, + .wait_recv = dummy_wait_recv, + }; + + ret = sock_daemon_setup(mem_ctx, "test1", "file:", "NOTICE", + &test1_funcs, discard_const(sockpath), &sockd); + assert(ret == 0); + assert(sockd != NULL); + + ret = sock_daemon_add_unix(sockd, sockpath, &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, -1); + assert(ret == 0); + + talloc_free(mem_ctx); +} + +/* + * test2 + * + * Start daemon, check PID file, sock daemon functions, termination, + * exit code + */ + +static int test2_startup(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + return 0; +} + +static int test2_reconfigure(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 2; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reconfigure_state { + int fd; +}; + +static struct tevent_req *test2_reconfigure_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reconfigure_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reconfigure_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reconfigure_recv(struct tevent_req *req, int *perr) +{ + struct test2_reconfigure_state *state = tevent_req_data( + req, struct test2_reconfigure_state); + int ret = 2; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test2_reopen_logs(void *private_data) +{ + static bool first_time = true; + int fd = *(int *)private_data; + int ret = 4; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (first_time) { + first_time = false; + return 1; + } + + return 0; +} + +struct test2_reopen_logs_state { + int fd; +}; + +static struct tevent_req *test2_reopen_logs_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_reopen_logs_state *state; + static bool first_time = true; + + req = tevent_req_create(mem_ctx, &state, + struct test2_reopen_logs_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + if (first_time) { + first_time = false; + tevent_req_error(req, 2); + } else { + tevent_req_done(req); + } + + return tevent_req_post(req, ev); +} + +static bool test2_reopen_logs_recv(struct tevent_req *req, int *perr) +{ + struct test2_reopen_logs_state *state = tevent_req_data( + req, struct test2_reopen_logs_state); + int ret = 4; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test2_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test2_shutdown_state { + int fd; +}; + +static struct tevent_req *test2_shutdown_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test2_shutdown_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct test2_shutdown_state); + if (req == NULL) { + return NULL; + } + + state->fd = *(int *)private_data; + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void test2_shutdown_recv(struct tevent_req *req) +{ + struct test2_shutdown_state *state = tevent_req_data( + req, struct test2_shutdown_state); + int ret = 3; + ssize_t nwritten; + + nwritten = write(state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +static void test2(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .reopen_logs = test2_reopen_logs, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + ret = fstat(pidfile_fd, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct sock_daemon_funcs test2_funcs = { + .startup = test2_startup, + .reconfigure_send = test2_reconfigure_send, + .reconfigure_recv = test2_reconfigure_recv, + .reopen_logs_send = test2_reopen_logs_send, + .reopen_logs_recv = test2_reopen_logs_recv, + .shutdown_send = test2_shutdown_send, + .shutdown_recv = test2_shutdown_recv, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test2", "file:", "NOTICE", + &test2_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGUSR1); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 2); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGHUP); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 4); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test3 + * + * Start daemon, test watching of (parent) PID + */ + +static void test3(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid_watch, pid, pid2; + int ret; + + pid_watch = fork(); + assert(pid_watch != -1); + + if (pid_watch == 0) { + sleep(10); + exit(0); + } + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test3", "file:", "NOTICE", + NULL, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, NULL, false, false, pid_watch); + assert(ret == ESRCH); + + exit(0); + } + + pid2 = waitpid(pid_watch, &ret, 0); + assert(pid2 == pid_watch); + assert(WEXITSTATUS(ret) == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test4 + * + * Start daemon, test termination via wait_send function + */ + +struct test4_wait_state { +}; + +static void test4_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test4_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req, *subreq; + struct test4_wait_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test4_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test4_wait_done, req); + + return req; +} + +static void test4_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + if (! status) { + tevent_req_error(req, EIO); + } else { + tevent_req_done(req); + } +} + +static bool test4_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test4_funcs = { + .wait_send = test4_wait_send, + .wait_recv = test4_wait_recv, +}; + +static void test4(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + pid_t pid, pid2; + int ret; + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test4", "file:", "NOTICE", + &test4_funcs, NULL, &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == 0); + + exit(0); + } + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +/* + * test5 + * + * Start daemon, multiple client connects, requests, disconnects + */ + +#define TEST5_VALID_CLIENTS 10 +#define TEST5_MAX_CLIENTS 100 + +struct test5_pkt { + uint32_t len; + int data; +}; + +struct test5_client_state { + int id; + int fd; + bool done; +}; + +static void test5_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_client_state *state = + (struct test5_client_state *)private_data; + struct test5_pkt *pkt; + ssize_t n; + int ret; + + if (buf == NULL) { + assert(buflen == 0); + + ret = 0; + } else { + assert(buflen == sizeof(struct test5_pkt)); + pkt = (struct test5_pkt *)buf; + assert(pkt->len == sizeof(struct test5_pkt)); + + ret = pkt->data; + } + + assert(state->fd != -1); + + n = write(state->fd, (void *)&ret, sizeof(int)); + assert(n == sizeof(int)); + + state->done = true; +} + +static int test5_client(const char *sockpath, int id, pid_t pid_server, + pid_t *client_pid) +{ + pid_t pid; + int fd[2]; + int ret; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct test5_client_state state; + struct sock_queue *queue; + struct test5_pkt pkt; + int conn; + + close(fd[0]); + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.id = id; + state.fd = fd[1]; + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test5_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xbaba; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test5_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + close(fd[1]); + state.fd = -1; + + while (kill(pid_server, 0) == 0 || errno != ESRCH) { + sleep(1); + } + exit(0); + } + + close(fd[1]); + + ret = 0; + n = read(fd[0], &ret, sizeof(ret)); + if (n == 0) { + fprintf(stderr, "client id %d read 0 bytes\n", id); + } + assert(n == 0 || n == sizeof(ret)); + + close(fd[0]); + + *client_pid = pid; + return ret; +} + +struct test5_server_state { + int num_clients; +}; + +static bool test5_connect(struct sock_client_context *client, + pid_t pid, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + if (state->num_clients == TEST5_VALID_CLIENTS) { + return false; + } + + state->num_clients += 1; + assert(state->num_clients <= TEST5_VALID_CLIENTS); + return true; +} + +static void test5_disconnect(struct sock_client_context *client, + void *private_data) +{ + struct test5_server_state *state = + (struct test5_server_state *)private_data; + + state->num_clients -= 1; + assert(state->num_clients >= 0); +} + +struct test5_read_state { + struct test5_pkt reply; +}; + +static void test5_read_done(struct tevent_req *subreq); + +static struct tevent_req *test5_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test5_server_state *server_state = + (struct test5_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test5_read_state *state; + struct test5_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test5_read_state); + assert(req != NULL); + + assert(buflen == sizeof(struct test5_pkt)); + + pkt = (struct test5_pkt *)buf; + assert(pkt->data == 0xbaba); + + state->reply.len = sizeof(struct test5_pkt); + state->reply.data = server_state->num_clients; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test5_read_done, req); + + return req; +} + +static void test5_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +static bool test5_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test5_client_funcs = { + .connect = test5_connect, + .disconnect = test5_disconnect, + .read_send = test5_read_send, + .read_recv = test5_read_recv, +}; + +struct test5_wait_state { +}; + +static struct tevent_req *test5_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct tevent_req *req; + struct test5_wait_state *state; + int fd = *(int *)private_data; + int ret = 1; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(fd); + + req = tevent_req_create(mem_ctx, &state, struct test5_wait_state); + if (req == NULL) { + return NULL; + } + + return req; +} + +static bool test5_wait_recv(struct tevent_req *req, int *perr) +{ + return true; +} + +static struct sock_daemon_funcs test5_funcs = { + .wait_send = test5_wait_send, + .wait_recv = test5_wait_recv, +}; + +static void test5(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret, i; + ssize_t n; + pid_t client_pid[TEST5_MAX_CLIENTS]; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test5_server_state state; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test5", "file:", "NOTICE", + &test5_funcs, &fd[1], &sockd); + assert(ret == 0); + + state.num_clients = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test5_client_funcs, &state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + for (i=0; i<TEST5_MAX_CLIENTS; i++) { + ret = test5_client(sockpath, i, pid_server, &client_pid[i]); + if (i < TEST5_VALID_CLIENTS) { + assert(ret == i+1); + } else { + assert(ret == 0); + } + } + + for (i=TEST5_MAX_CLIENTS-1; i>=0; i--) { + kill(client_pid[i], SIGKILL); + + pid = wait(&ret); + assert(pid != -1); + } + + ret = kill(pid_server, SIGTERM); + assert(ret == 0); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test6 + * + * Start daemon, test client connects, requests, replies, disconnects + */ + +struct test6_pkt { + uint32_t len; + uint32_t data; +}; + +struct test6_client_state { + bool done; +}; + +static void test6_client_callback(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_client_state *state = + (struct test6_client_state *)private_data; + struct test6_pkt *pkt; + + assert(buflen == sizeof(struct test6_pkt)); + pkt = (struct test6_pkt *)buf; + assert(pkt->len == sizeof(struct test6_pkt)); + assert(pkt->data == 0xffeeddcc); + + state->done = true; +} + +static void test6_client(const char *sockpath) +{ + struct tevent_context *ev; + struct test6_client_state state; + struct sock_queue *queue; + struct test6_pkt pkt; + int conn, ret; + + ev = tevent_context_init(NULL); + assert(ev != NULL); + + conn = sock_connect(sockpath); + assert(conn != -1); + + state.done = false; + + queue = sock_queue_setup(ev, ev, conn, + test6_client_callback, &state); + assert(queue != NULL); + + pkt.len = 8; + pkt.data = 0xaabbccdd; + + ret = sock_queue_write(queue, (uint8_t *)&pkt, + sizeof(struct test6_pkt)); + assert(ret == 0); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(ev); +} + +struct test6_server_state { + struct sock_daemon_context *sockd; + int fd, done; +}; + +struct test6_read_state { + struct test6_server_state *server_state; + struct test6_pkt reply; +}; + +static void test6_read_done(struct tevent_req *subreq); + +static struct tevent_req *test6_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *client, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_read_state *state; + struct test6_pkt *pkt; + + req = tevent_req_create(mem_ctx, &state, struct test6_read_state); + assert(req != NULL); + + state->server_state = server_state; + + assert(buflen == sizeof(struct test6_pkt)); + + pkt = (struct test6_pkt *)buf; + assert(pkt->data == 0xaabbccdd); + + state->reply.len = sizeof(struct test6_pkt); + state->reply.data = 0xffeeddcc; + + subreq = sock_socket_write_send(state, ev, client, + (uint8_t *)&state->reply, + state->reply.len); + assert(subreq != NULL); + + tevent_req_set_callback(subreq, test6_read_done, req); + + return req; +} + +static void test6_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_read_state *state = tevent_req_data( + req, struct test6_read_state); + int ret; + bool status; + + status = sock_socket_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + state->server_state->done = 1; + tevent_req_done(req); +} + +static bool test6_read_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_socket_funcs test6_client_funcs = { + .read_send = test6_read_send, + .read_recv = test6_read_recv, +}; + +struct test6_wait_state { + struct test6_server_state *server_state; +}; + +static void test6_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test6_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + struct test6_server_state *server_state = + (struct test6_server_state *)private_data; + struct tevent_req *req, *subreq; + struct test6_wait_state *state; + ssize_t nwritten; + int ret = 1; + + nwritten = write(server_state->fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + close(server_state->fd); + server_state->fd = -1; + + req = tevent_req_create(mem_ctx, &state, struct test6_wait_state); + if (req == NULL) { + return NULL; + } + + state->server_state = (struct test6_server_state *)private_data; + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10,0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test6_wait_done, req); + + return req; +} + +static void test6_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test6_wait_state *state = tevent_req_data( + req, struct test6_wait_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + if (state->server_state->done == 0) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test6_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test6_funcs = { + .wait_send = test6_wait_send, + .wait_recv = test6_wait_recv, +}; + +static void test6(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + pid_t pid_server, pid; + int fd[2], ret; + ssize_t n; + + pid = getpid(); + + ret = pipe(fd); + assert(ret == 0); + + pid_server = fork(); + assert(pid_server != -1); + + if (pid_server == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + struct test6_server_state server_state = { 0 }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + server_state.fd = fd[1]; + + ret = sock_daemon_setup(mem_ctx, "test6", "file:", "NOTICE", + &test6_funcs, &server_state, + &sockd); + assert(ret == 0); + + server_state.sockd = sockd; + server_state.done = 0; + + ret = sock_daemon_add_unix(sockd, sockpath, + &test6_client_funcs, &server_state); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, pid); + assert(ret == 0); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + close(fd[0]); + + test6_client(sockpath); + + pid = waitpid(pid_server, &ret, 0); + assert(pid == pid_server); + assert(WEXITSTATUS(ret) == 0); +} + +/* + * test7 + * + * Start daemon twice, confirm PID file contention + */ + +static void test7(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct sock_daemon_funcs test7_funcs; + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + /* Reuse test2 funcs for the startup synchronisation */ + test7_funcs = (struct sock_daemon_funcs) { + .startup = test2_startup, + .reconfigure = test2_reconfigure, + .shutdown = test2_shutdown, + }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + ret = stat(pidfile, &st); + assert(ret == 0); + assert(S_ISREG(st.st_mode)); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test7-parent", "file:", "NOTICE", + &test7_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EEXIST); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test8 + * + * Start daemon, confirm that create_session argument works as expected + */ + +static void test8(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2, sid; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session false above, so pid != sid */ + sid = getsid(pid); + assert(pid != sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test8_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test8", "file:", "NOTICE", + &test8_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, true, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* create_session true above, so pid == sid */ + sid = getsid(pid); + assert(pid == sid); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); +} + +/* + * test9 + * + * Confirm that do_fork causes the daemon to be forked as a separate child + */ + +static void test9(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + int fd[2]; + pid_t pid, pid2; + int ret; + struct tevent_context *ev; + struct sock_daemon_context *sockd; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + struct stat st; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork false above, so pid should be active */ + ret = kill(pid, 0); + assert(ret == 0); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* Reuse test2 funcs for the startup synchronisation */ + struct sock_daemon_funcs test9_funcs = { + .startup = test2_startup, + .shutdown = test2_shutdown, + }; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test9", "file:", "NOTICE", + &test9_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, true, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* do_fork true above, so pid should have exited */ + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid != pid2); + close(pidfile_fd); + + ret = kill(pid2, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + /* + * pid2 isn't our child, so can't call waitpid(). kill(pid2, 0) + * is unreliable - pid2 may have been recycled. Above indicates + * that the shutdown function was called, so just do 1 final + * check to see if pidfile has been removed. + */ + ret = stat(sockpath, &st); + assert(ret == -1); + + close(fd[0]); +} + +static void test10_shutdown(void *private_data) +{ + int fd = *(int *)private_data; + int ret = 3; + ssize_t nwritten; + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); +} + +struct test10_wait_state { +}; + +static void test10_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test10_wait_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + void *private_data) +{ + int fd = *(int *)private_data; + struct tevent_req *req, *subreq; + struct test10_wait_state *state; + size_t nwritten; + int ret = 1; + + req = tevent_req_create(mem_ctx, &state, struct test10_wait_state); + if (req == NULL) { + return NULL; + } + + subreq = tevent_wakeup_send(state, ev, + tevent_timeval_current_ofs(10, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test10_wait_done, req); + + nwritten = write(fd, &ret, sizeof(ret)); + assert(nwritten == sizeof(ret)); + + return req; +} + +static void test10_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + + status = tevent_wakeup_recv(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + tevent_req_done(req); +} + +static bool test10_wait_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +static struct sock_daemon_funcs test10_funcs = { + .shutdown = test10_shutdown, + .wait_send = test10_wait_send, + .wait_recv = test10_wait_recv, +}; + +/* + * test10 + * + * Confirm that the daemon starts successfully if there is a stale socket + */ + +static void test10(TALLOC_CTX *mem_ctx, const char *pidfile, + const char *sockpath) +{ + struct stat st; + int fd[2]; + pid_t pid, pid2; + int ret; + ssize_t n; + int pidfile_fd; + char pidstr[20] = { 0 }; + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + /* KILL will leave PID file and socket behind */ + ret = kill (pid, SIGKILL); + assert(ret == 0); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + ret = stat(sockpath, &st); + assert(ret == 0); + + close(fd[0]); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + struct tevent_context *ev; + struct sock_daemon_context *sockd; + + close(fd[0]); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = sock_daemon_setup(mem_ctx, "test10", "file:", "NOTICE", + &test10_funcs, &fd[1], &sockd); + assert(ret == 0); + + ret = sock_daemon_add_unix(sockd, sockpath, + &dummy_socket_funcs, NULL); + assert(ret == 0); + + ret = sock_daemon_run(ev, sockd, pidfile, false, false, -1); + assert(ret == EINTR); + + exit(0); + } + + close(fd[1]); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 1); + + pidfile_fd = open(pidfile, O_RDONLY, 0644); + assert(pidfile_fd != -1); + n = read(pidfile_fd, pidstr, sizeof(pidstr)-1); + assert(n != -1); + pid2 = (pid_t)atoi(pidstr); + assert(pid == pid2); + close(pidfile_fd); + + ret = kill(pid, SIGTERM); + assert(ret == 0); + + n = read(fd[0], &ret, sizeof(ret)); + assert(n == sizeof(ret)); + assert(ret == 3); + + pid2 = waitpid(pid, &ret, 0); + assert(pid2 == pid); + assert(WEXITSTATUS(ret) == 0); + + close(fd[0]); + + ret = stat(pidfile, &st); + assert(ret == -1); + + ret = stat(sockpath, &st); + assert(ret == -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *pidfile, *sockpath; + int num; + + if (argc != 4) { + fprintf(stderr, "%s <pidfile> <sockpath> <testnum>\n", argv[0]); + exit(1); + } + + pidfile = argv[1]; + sockpath = argv[2]; + num = atoi(argv[3]); + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + switch (num) { + case 1: + test1(mem_ctx, pidfile, sockpath); + break; + + case 2: + test2(mem_ctx, pidfile, sockpath); + break; + + case 3: + test3(mem_ctx, pidfile, sockpath); + break; + + case 4: + test4(mem_ctx, pidfile, sockpath); + break; + + case 5: + test5(mem_ctx, pidfile, sockpath); + break; + + case 6: + test6(mem_ctx, pidfile, sockpath); + break; + + case 7: + test7(mem_ctx, pidfile, sockpath); + break; + + case 8: + test8(mem_ctx, pidfile, sockpath); + break; + + case 9: + test9(mem_ctx, pidfile, sockpath); + break; + + case 10: + test10(mem_ctx, pidfile, sockpath); + break; + + default: + fprintf(stderr, "Unknown test number %d\n", num); + } + + return 0; +} diff --git a/ctdb/tests/src/sock_io_test.c b/ctdb/tests/src/sock_io_test.c new file mode 100644 index 0000000..ba4b637 --- /dev/null +++ b/ctdb/tests/src/sock_io_test.c @@ -0,0 +1,283 @@ +/* + sock I/O tests + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <assert.h> + +#include "common/sock_io.c" + +static int socket_init(const char *sockpath) +{ + struct sockaddr_un addr; + int fd, ret; + size_t len; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + assert(len < sizeof(addr.sun_path)); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(fd != -1); + + ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + assert(ret != -1); + + ret = listen(fd, 10); + assert(ret != -1); + + return fd; +} + +static void test1_writer(int fd) +{ + uint8_t buf[1024]; + ssize_t nwritten; + uint32_t len; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + nwritten = sys_write(fd, buf, buflen); + assert(nwritten == buflen); + } +} + +struct test1_reader_state { + size_t pkt_len; + bool done; +}; + +static void test1_reader(uint8_t *buf, size_t buflen, void *private_data) +{ + struct test1_reader_state *state = + (struct test1_reader_state *)private_data; + + if (buflen == 0) { + state->done = true; + return; + } + + assert(buflen == state->pkt_len); + + state->pkt_len += 10; +} + +static void test1(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct test1_reader_state state; + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test1_writer(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + state.pkt_len = 10 + sizeof(uint32_t); + state.done = false; + + queue = sock_queue_setup(mem_ctx, ev, fd, test1_reader, &state); + assert(queue != NULL); + + while (! state.done) { + tevent_loop_once(ev); + } + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +static void test2_reader(int fd) +{ + uint8_t buf[1024]; + size_t pkt_len = 10 + sizeof(uint32_t); + ssize_t n; + + while (1) { + n = sys_read(fd, buf, 1024); + assert(n != -1); + + if (n == 0) { + return; + } + + assert((size_t)n == pkt_len); + pkt_len += 10; + } +} + +static void test2_dummy_reader(uint8_t *buf, size_t buflen, + void *private_data) +{ + abort(); +} + +static void test2_writer(struct sock_queue *queue) +{ + uint8_t buf[1024]; + uint32_t len; + int ret; + + for (len = 10; len < 1000; len += 10) { + int value = len / 10; + uint32_t buflen = len + sizeof(uint32_t); + + memset(buf, value, buflen); + memcpy(buf, &buflen, sizeof(uint32_t)); + + ret = sock_queue_write(queue, buf, buflen); + assert(ret == 0); + } +} + +static void test2(TALLOC_CTX *mem_ctx, const char *sockpath) +{ + struct tevent_context *ev; + struct sock_queue *queue; + pid_t pid; + int pfd[2], fd, ret; + ssize_t n; + + ret = pipe(pfd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + int newfd; + + close(pfd[0]); + + fd = socket_init(sockpath); + assert(fd != -1); + + ret = 1; + n = sys_write(pfd[1], &ret, sizeof(int)); + assert(n == sizeof(int)); + + newfd = accept(fd, NULL, NULL); + assert(newfd != -1); + + test2_reader(newfd); + close(newfd); + unlink(sockpath); + + exit(0); + } + + close(pfd[1]); + + n = sys_read(pfd[0], &ret, sizeof(int)); + assert(n == sizeof(int)); + assert(ret == 1); + + close(pfd[0]); + + fd = sock_connect(sockpath); + assert(fd != -1); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + queue = sock_queue_setup(mem_ctx, ev, fd, test2_dummy_reader, NULL); + assert(queue != NULL); + + test2_writer(queue); + + talloc_free(queue); + talloc_free(ev); + + pid = wait(&ret); + assert(pid != -1); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + const char *sockpath; + + if (argc != 2) { + fprintf(stderr, "%s <sockpath>\n", argv[0]); + exit(1); + } + + sockpath = argv[1]; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + test1(mem_ctx, sockpath); + test2(mem_ctx, sockpath); + + return 0; +} diff --git a/ctdb/tests/src/srvid_test.c b/ctdb/tests/src/srvid_test.c new file mode 100644 index 0000000..2367c6c --- /dev/null +++ b/ctdb/tests/src/srvid_test.c @@ -0,0 +1,105 @@ +/* + srvid tests + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +#include "common/db_hash.c" +#include "common/srvid.c" + +#define TEST_SRVID 0xBE11223344556677 + +static void test_handler(uint64_t srvid, TDB_DATA data, void *private_data) +{ + int *count = (int *)private_data; + (*count)++; +} + +int main(void) +{ + struct srvid_context *srv = NULL; + TALLOC_CTX *mem_ctx = talloc_new(NULL); + TALLOC_CTX *tmp_ctx = talloc_new(NULL); + int ret; + int count = 0; + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == EINVAL); + + ret = srvid_init(mem_ctx, &srv); + assert(ret == 0); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == 0); + + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == 0); + assert(count == 1); + + ret = srvid_dispatch(srv, 0, TEST_SRVID, tdb_null); + assert(ret == 0); + assert(count == 2); + + ret = srvid_deregister(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_deregister(srv, TEST_SRVID, &count); + assert(ret == 0); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + + talloc_free(tmp_ctx); + ret = srvid_exists(srv, TEST_SRVID, NULL); + assert(ret == ENOENT); + + ret = srvid_dispatch(srv, TEST_SRVID, 0, tdb_null); + assert(ret == ENOENT); + + tmp_ctx = talloc_new(NULL); + assert(tmp_ctx != NULL); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, NULL); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == ENOENT); + + ret = srvid_register(srv, tmp_ctx, TEST_SRVID, test_handler, &count); + assert(ret == 0); + ret = srvid_exists(srv, TEST_SRVID, &count); + assert(ret == 0); + + talloc_free(srv); + assert(talloc_get_size(mem_ctx) == 0); + assert(talloc_get_size(tmp_ctx) == 0); + + talloc_free(mem_ctx); + + return 0; +} diff --git a/ctdb/tests/src/system_socket_test.c b/ctdb/tests/src/system_socket_test.c new file mode 100644 index 0000000..436f52a --- /dev/null +++ b/ctdb/tests/src/system_socket_test.c @@ -0,0 +1,266 @@ +/* + Raw socket (un) marshalling tests + + Copyright (C) Martin Schwenke 2018 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> + +/* For ether_aton() */ +#ifdef _AIX +#include <arpa/inet.h> +#endif +#ifdef __FreeBSD__ +#include <net/ethernet.h> +#endif +#ifdef linux +#include <netinet/ether.h> +#endif + +#include "common/system_socket.c" + +#include "protocol/protocol_util.h" + +#include "tests/src/test_backtrace.h" + +static void hexdump(uint8_t *buf, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (i % 16 == 0) { + if (i != 0) { + printf("\n"); + } + printf("%06zx", i); + } + printf(" %02x", buf[i]); + } + + printf("\n%06zx\n", i); +} + +static void test_types(void) +{ + /* + * We use this struct in the code but don't pack it due to + * portability concerns. It should have no padding. + */ + struct { + struct ip ip; + struct tcphdr tcp; + } ip4pkt; + + assert(sizeof(ip4pkt) == sizeof(struct ip) + sizeof(struct tcphdr)); +} + +#ifdef HAVE_PACKETSOCKET + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + ctdb_sock_addr addr; + struct ether_addr *hw, *dhw; + uint8_t buf[512]; + size_t buflen = sizeof(buf); + size_t len; + int ret; + + ret = ctdb_sock_addr_from_string(addr_str, &addr, false); + assert(ret == 0); + + hw = ether_aton(hwaddr_str); + assert(hw != NULL); + + switch (addr.ip.sin_family) { + case AF_INET: + ret = arp_build(buf, buflen, &addr.ip, hw, reply, &dhw, &len); + break; + case AF_INET6: + ret = ip6_na_build(buf, buflen, &addr.ip6, hw, &dhw, &len); + break; + default: + abort(); + } + + assert(ret == 0); + + hexdump(buf, len); +} + +#else /* HAVE_PACKETSOCKET */ + +static void test_arp(const char *addr_str, const char *hwaddr_str, bool reply) +{ + fprintf(stderr, "PACKETSOCKET not supported\n"); +} + +#endif /* HAVE_PACKETSOCKET */ + +static void test_tcp(const char *src_str, + const char *dst_str, + const char *seq_str, + const char *ack_str, + const char *rst_str) +{ + ctdb_sock_addr src, dst; + uint32_t seq, ack; + int rst; + uint8_t buf[512]; + struct ether_header *eth; + size_t expected_len, len; + char src_str_out[64], dst_str_out[64]; + uint32_t seq_out, ack_out; + int rst_out = 0; + uint16_t window; + int ret; + + ret = ctdb_sock_addr_from_string(src_str, &src, true); + assert(ret == 0); + + ret = ctdb_sock_addr_from_string(dst_str, &dst, true); + assert(ret == 0); + + seq = atoi(seq_str); + ack = atoi(ack_str); + rst = atoi(rst_str); + + /* Need to fake this up */ + eth = (struct ether_header *) buf; + memset(eth, 0, sizeof(*eth)); + + switch (src.ip.sin_family) { + case AF_INET: + eth->ether_type = htons(ETHERTYPE_IP); + expected_len = 40; + ret = tcp4_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip, + &dst.ip, + seq, + ack, + rst, + &len); + break; + case AF_INET6: + eth->ether_type = htons(ETHERTYPE_IP6); + expected_len = 60; + ret = tcp6_build(buf + sizeof(struct ether_header), + sizeof(buf) - sizeof(struct ether_header), + &src.ip6, + &dst.ip6, + seq, + ack, + rst, + &len); + break; + default: + abort(); + } + + assert(ret == 0); + assert(len == expected_len); + + hexdump(buf + sizeof(struct ether_header), len); + + switch (ntohs(eth->ether_type)) { + case ETHERTYPE_IP: + ret = tcp4_extract(buf + sizeof(struct ether_header), + len, + &src.ip, + &dst.ip, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + case ETHERTYPE_IP6: + ret = tcp6_extract(buf + sizeof(struct ether_header), + len, + &src.ip6, + &dst.ip6, + &ack_out, + &seq_out, + &rst_out, + &window); + break; + default: + abort(); + } + + assert(ret == 0); + + assert(seq == seq_out); + assert(ack == ack_out); + assert((rst != 0) == (rst_out != 0)); + assert(window == htons(1234)); + + ret = ctdb_sock_addr_to_buf(src_str_out, sizeof(src_str_out), + &src, true); + assert(ret == 0); + ret = strcmp(src_str, src_str_out); + assert(ret == 0); + + ret = ctdb_sock_addr_to_buf(dst_str_out, sizeof(dst_str_out), + &dst, true); + assert(ret == 0); + ret = strcmp(dst_str, dst_str_out); + assert(ret == 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, "usage: %s <cmd> [<arg> ...]\n", prog); + fprintf(stderr, " commands:\n"); + fprintf(stderr, " types\n"); + fprintf(stderr, " arp <ipaddr> <hwaddr> [reply]\n"); + fprintf(stderr, " tcp <src> <dst> <seq> <ack> <rst>\n"); + + exit(1); +} + +int main(int argc, char **argv) +{ + + if (argc < 2) { + usage(argv[0]); + } + + test_backtrace_setup(); + + if (strcmp(argv[1], "types") == 0) { + test_types(); + } else if (strcmp(argv[1], "arp") == 0) { + /* + * Extra arg indicates that a reply should be + * constructed for IPv4 - value is ignored + */ + if (argc != 4 && argc != 5) { + usage(argv[0]); + } + test_arp(argv[2], argv[3], (argc == 5)); + } else if (strcmp(argv[1], "tcp") == 0) { + if (argc != 7) { + usage(argv[0]); + } + test_tcp(argv[2], argv[3], argv[4], argv[5], argv[6]); + } else { + usage(argv[0]); + } + + return 0; +} diff --git a/ctdb/tests/src/test_backtrace.c b/ctdb/tests/src/test_backtrace.c new file mode 100644 index 0000000..aa3fc0c --- /dev/null +++ b/ctdb/tests/src/test_backtrace.c @@ -0,0 +1,37 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include "lib/util/fault.h" +#include "lib/util/signal.h" + +#include "tests/src/test_backtrace.h" + +static void test_abort_backtrace_handler(int sig) +{ + log_stack_trace(); + CatchSignal(SIGABRT, SIG_DFL); + abort(); +} + +void test_backtrace_setup(void) +{ + CatchSignal(SIGABRT, test_abort_backtrace_handler); +} diff --git a/ctdb/tests/src/test_backtrace.h b/ctdb/tests/src/test_backtrace.h new file mode 100644 index 0000000..a6089c9 --- /dev/null +++ b/ctdb/tests/src/test_backtrace.h @@ -0,0 +1,25 @@ +/* + Print a backtrace when a test aborts + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_TEST_BACKTRACE_H__ +#define __CTDB_TEST_BACKTRACE_H__ + +void test_backtrace_setup(void); + +#endif /* __CTDB_TEST_BACKTRACE_H__ */ diff --git a/ctdb/tests/src/test_mutex_raw.c b/ctdb/tests/src/test_mutex_raw.c new file mode 100644 index 0000000..926a525 --- /dev/null +++ b/ctdb/tests/src/test_mutex_raw.c @@ -0,0 +1,434 @@ +/* + * Test the system robust mutex implementation + * + * Copyright (C) 2016 Amitay Isaacs + * Copyright (C) 2018 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* + * To run the test do the following: + * + * (a) Compile the test. + * + * gcc -O2 -g3 -o test-robust-mutex test-robust-mutex.c -lpthread + * + * (b) Start the "init" process. + * + * ./test-robust-mutex /tmp/shared-mutex init + * + * (c) Start any number of "worker" instances. + * + * ./test-robust-mutex <Shared memory file> worker <#> <Priority> + * + * <Shared memory file> e.g. /tmp/shared-mutex. + * + * <#> : Number of children processes. + * + * <Priority> : 0 - Normal, 1 - Realtime, 2 - Nice 20. + * + * For example: + * + * As non-root: + * + * $ while true ; do ./test-robust-mutex /tmp/foo worker 10 0 ; done; + * + * As root: + * + * while true ; do ./test-robust-mutex /tmp/foo worker 10 1 ; done; + * + * This creates 20 processes, 10 at normal priority and 10 at realtime + * priority, all taking the lock, being killed and recovering the lock. + * + * If while runnig (c) the processes block, it might mean that a futex wakeup + * was lost, or that the handoff of EOWNERDEAD did not happen correctly. In + * either case you can debug the resulting mutex like this: + * + * $ ./test-robust-mutex /tmp/shared-mutex debug + * + * This prints the PID of the process holding the mutex or nothing if + * the value was cleared by the kernel and now no process holds the mutex. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <sys/wait.h> + +/* Define DEBUG to 1 to enable verbose debugging. */ +#define DEBUG 0 + +/* Implement the worker. The worker has to do the following things: + + * Succeed at locking the mutex, including possible recovery. + * Kill itself. + + Other workers are attempting exactly the same thing in order to + test the loss and recovery of the robust mutex. */ +static void worker (const char *filename) +{ + pthread_mutex_t *mutex; + void *addr; + int ret, fd; + + /* Open the file and map the shared robust mutex. */ + fd = open(filename, O_RDWR, 0600); + if (fd == -1) { + perror ("FAIL: open"); + exit(EXIT_FAILURE); + } + + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror ("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)addr; + + /* Every process will lock once, and die once. */ + printf("INFO: pid %u locking\n", getpid()); + do { + ret = pthread_mutex_lock(mutex); + +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u lock attempt, ret=%d\n", + getpid(), + ret); +#endif + + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit(EXIT_FAILURE); + } +#if DEBUG + fprintf(stderr, + "DEBUG: pid %u recovery lock attempt, ret=%d\n", + getpid(), + ret); +#endif + /* Will loop and try to lock again. */ + } + + } while (ret != 0); + + printf ("INFO: pid %u locked, now killing\n", getpid()); + kill(getpid(), SIGKILL); +} + +/* One of three priority modes. */ +#define PRIO_NORMAL 0 +#define PRIO_REALTIME 1 +#define PRIO_NICE_20 2 + +/* One of three operation modes. */ +#define MODE_INIT 0 +#define MODE_WORKER 1 +#define MODE_DEBUG 2 + +/* Print usage information and exit. */ +static void usage (const char *name) +{ + fprintf(stderr, + "Usage: %s <file> [init|worker|debug] [#] [0|1|2]\n", + name); + exit(EXIT_FAILURE); +} + +/* Set the process priority. */ +static void set_priority (int priority) +{ + struct sched_param p; + int ret; + + switch (priority) { + case PRIO_REALTIME: + p.sched_priority = 1; + ret = sched_setscheduler(0, SCHED_FIFO, &p); + if (ret == -1) + perror("FAIL: sched_setscheduler"); + break; + + case PRIO_NICE_20: + ret = nice(-20); + if (ret == -1) + perror("FAIL: nice"); + break; + + case PRIO_NORMAL: + default: + /* Normal priority is the default. */ + break; + } +} + +int main(int argc, const char **argv) +{ + int i, fd, ret, num_children, mode = -1, priority = PRIO_NORMAL; + const char *mode_str; + const char *file; + char *addr; + pthread_mutex_t *mutex; + pthread_mutexattr_t mattr; + pid_t pid; + + /* One of three modes, init, worker, or debug. */ + if (argc < 3 || argc > 5) + usage (argv[0]); + + /* + * The shared memory file. Care should be taken here because if glibc + * is upgraded between runs the internals of the robust mutex could + * change. See this blog post about the dangers: + * https://developers.redhat.com/blog/2017/03/13/cc-library-upgrades-and-opaque-data-types-in-process-shared-memory/ + * and how to avoid problems inherent in this. + */ + file = argv[1]; + + /* Set the mode. */ + mode_str = argv[2]; + if (strcmp ("init", mode_str) == 0) { + mode = MODE_INIT; + } else if (strcmp ("worker", mode_str) == 0) { + mode = MODE_WORKER; + } else if (strcmp ("debug", mode_str) == 0) { + mode = MODE_DEBUG; + } else { + usage (argv[0]); + } + + /* This is "worker" mode, so set the priority. */ + if (mode == MODE_WORKER) { + priority = atoi(argv[4]); + set_priority(priority); + } + + /* All modes open the file. */ + fd = open(argv[1], O_CREAT|O_RDWR, 0600); + if (fd == -1) { + perror("FAIL: open"); + exit(EXIT_FAILURE); + } + + ret = lseek(fd, 0, SEEK_SET); + if (ret != 0) { + perror("FAIL: lseek"); + exit(EXIT_FAILURE); + } + + /* Truncate the file backing the mutex only in the init phase. */ + if (mode == MODE_INIT) { + ret = ftruncate(fd, sizeof(pthread_mutex_t)); + if (ret != 0) { + perror("FAIL: ftruncate"); + exit(EXIT_FAILURE); + } + } + + /* Map the robust mutex. */ + addr = mmap(NULL, + sizeof(pthread_mutex_t), + PROT_READ|PROT_WRITE, + MAP_SHARED|MAP_FILE, + fd, + 0); + if (addr == NULL) { + perror("FAIL: mmap"); + exit(EXIT_FAILURE); + } + + mutex = (pthread_mutex_t *)(void *)addr; + + /* + * In the debug mode we try to recover the mutex and print it. + * WARNING: All other processes should be stuck, otherwise they may + * change the value of the lock between trylock and the printing after + * EBUSY. + */ + if (mode == MODE_DEBUG) { + ret = pthread_mutex_trylock(mutex); + if (ret == EOWNERDEAD) { + ret = pthread_mutex_consistent(mutex); + if (ret == 0) { + pthread_mutex_unlock(mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + } else if (ret == EBUSY) { + printf("INFO: pid=%u\n", mutex->__data.__owner); + } else if (ret == 0) { + pthread_mutex_unlock(mutex); + } + exit(EXIT_SUCCESS); + } + + /* + * Only the initializing process does initialization because it is + * undefined behaviour to re-initialize an already initialized mutex + * that was not destroyed. + */ + if (mode == MODE_INIT) { + + ret = pthread_mutexattr_init(&mattr); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_init failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_settype(&mattr, + PTHREAD_MUTEX_ERRORCHECK); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_settype failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setpshared(&mattr, + PTHREAD_PROCESS_SHARED); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setpshared failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); + if (ret != 0) { + fprintf(stderr, + "FAIL: pthread_mutexattr_setrobust failed\n"); + exit(EXIT_FAILURE); + } + + ret = pthread_mutex_init(mutex, &mattr); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_init failed\n"); + exit(EXIT_FAILURE); + } + + printf ("INFO: init: Mutex initialization complete.\n"); + /* Never exit. */ + for (;;) + sleep (1); + } + + /* Acquire the mutext for the first time. Might be dead. + Might also be concurrent with the high-priority threads. */ + fprintf(stderr, + "INFO: parent: Acquiring mutex (pid = %d).\n", + getpid()); + do { + ret = pthread_mutex_lock(mutex); + + /* Not consistent? Try to make it so. */ + if (ret == EOWNERDEAD) { + int rc; + + rc = pthread_mutex_consistent(mutex); + if (rc == 0) { + pthread_mutex_unlock (mutex); + } else { + fprintf(stderr, + "FAIL: pthread_mutex_consistent " + "failed\n"); + exit (EXIT_FAILURE); + } + + /* Will loop and try to lock again. */ + fprintf(stderr, + "INFO: parent: Unlock recovery ret = %d\n", + ret); + } + + } while (ret != 0); + + /* + * Set the parent process into it's own process group (hides the + * children). + */ + setpgid(0, 0); + + /* Create # of children. */ + fprintf(stderr, "INFO: parent: Creating children\n"); + num_children = atoi(argv[3]); + + for (i = 0; i < num_children; i++) { + pid = fork(); + if (pid < 0) { + fprintf(stderr, "FAIL: fork() failed\n"); + exit(EXIT_FAILURE); + } + if (pid == 0) { + close(fd); + worker(file); + exit(EXIT_FAILURE); + } + } + + fprintf(stderr, "INFO: parent: Waiting for children\n"); + + /* Unlock the recently acquired mutex or the old lost mutex. */ + ret = pthread_mutex_unlock(mutex); + if (ret != 0) { + fprintf(stderr, "FAIL: pthread_mutex_unlock failed\n"); + exit(EXIT_FAILURE); + } + + /* + * All threads are running now, and each will take the lock and + * die in turn. When they are all dead we will exit and be started + * again by the caller. + */ + for (i = 0; i < num_children; i++) { + int status; + pid = waitpid(-1, &status, 0); + if (pid <= 0) { + fprintf(stderr, "FAIL: waitpid() failed\n"); + exit(EXIT_FAILURE); + } + fprintf(stderr, + "INFO: parent: Reaped %u\n", + (unsigned int) pid); + } + + /* We never unlink fd. The file must be cleaned up by the caller. */ + close(fd); + + exit(EXIT_SUCCESS); +} diff --git a/ctdb/tests/src/test_options.c b/ctdb/tests/src/test_options.c new file mode 100644 index 0000000..2c64404 --- /dev/null +++ b/ctdb/tests/src/test_options.c @@ -0,0 +1,245 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" + +#include <assert.h> +#include <popt.h> +#include <talloc.h> + +#include "lib/util/debug.h" + +#include "common/logging.h" +#include "common/path.h" + +#include "tests/src/test_options.h" + +static struct test_options _values; + +static struct poptOption options_basic[] = { + { + .longName = "socket", + .shortName = 's', + .argInfo = POPT_ARG_STRING, + .arg = &_values.socket, + .descrip = "CTDB socket path", + .argDescrip = "filename", + }, + { + .longName = "timelimit", + .shortName = 't', + .argInfo = POPT_ARG_INT, + .arg = &_values.timelimit, + .descrip = "Time limit (in seconds)", + }, + { + .longName = "num-nodes", + .shortName = 'n', + .argInfo = POPT_ARG_INT, + .arg = &_values.num_nodes, + .descrip = "Number of cluster nodes", + }, + { + .longName = "debug", + .shortName = 'd', + .argInfo = POPT_ARG_STRING, + .arg = &_values.debugstr, + .descrip = "Debug level", + }, + { + .longName = "interactive", + .shortName = 'i', + .argInfo = POPT_ARG_NONE, + .arg = &_values.interactive, + .val = 0, + .descrip = "Interactive output", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_BASIC \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_basic, \ + .descrip = "General options:", \ + }, + +static struct poptOption options_database[] = { + { + .longName = "database", + .shortName = 'D', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbname, + .descrip = "CTDB database name", + }, + { + .longName = "key", + .shortName = 'k', + .argInfo = POPT_ARG_STRING, + .arg = &_values.keystr, + .descrip = "Name of database key", + }, + { + .longName = "value", + .shortName = 'v', + .argInfo = POPT_ARG_STRING, + .arg = &_values.valuestr, + .descrip = "Value of database key", + }, + { + .longName = "dbtype", + .shortName = 'T', + .argInfo = POPT_ARG_STRING, + .arg = &_values.dbtype, + .descrip = "CTDB database type", + }, + POPT_TABLEEND +}; + +#define TEST_OPTIONS_DATABASE \ + { \ + .argInfo = POPT_ARG_INCLUDE_TABLE, \ + .arg = options_database, \ + .descrip = "Database options:", \ + }, + +static void set_defaults_basic(struct test_options *opts) +{ + /* Set default options */ + opts->socket = path_socket(NULL, "ctdbd"); /* leaked */ + assert(opts->socket != NULL); + + opts->timelimit = 10; + opts->num_nodes = 1; + opts->debugstr = "ERR"; + opts->interactive = 0; +} + +static void set_defaults_database(struct test_options *opts) +{ + opts->dbname = NULL; + opts->keystr = NULL; + opts->valuestr = NULL; + opts->dbtype = "volatile"; +} + +static bool verify_options_basic(struct test_options *opts) +{ + int log_level; + bool status; + + status = debug_level_parse(opts->debugstr, &log_level); + if (! status) { + fprintf(stderr, "Error: Invalid debug string '%s'\n", + opts->debugstr); + return false; + } + + debuglevel_set(log_level); + + return true; +} + +static bool verify_options_database(struct test_options *opts) +{ + if (opts->dbname == NULL) { + fprintf(stderr, "Error: Please specify database\n"); + return false; + } + if (opts->keystr == NULL) { + fprintf(stderr, "Error: Please specify key name\n"); + return false; + } + + if ((strcmp(opts->dbtype, "volatile") != 0) && + (strcmp(opts->dbtype, "persistent") != 0) && + (strcmp(opts->dbtype, "replicated") != 0)) { + return false; + } + + return true; +} + +static bool process_options_common(int argc, const char **argv, + struct poptOption *options) +{ + poptContext pc; + int opt; + + pc = poptGetContext(argv[0], argc, argv, options, + POPT_CONTEXT_KEEP_FIRST); + while ((opt = poptGetNextOpt(pc)) != -1) { + fprintf(stderr, "Invalid option %s: %s\n", + poptBadOption(pc, 0), poptStrerror(opt)); + return false; + } + + return true; +} + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + + *opts = &_values; + return true; +} + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts) +{ + struct poptOption options[] = { + POPT_AUTOHELP + TEST_OPTIONS_BASIC + TEST_OPTIONS_DATABASE + POPT_TABLEEND + }; + + set_defaults_basic(&_values); + set_defaults_database(&_values); + + if (! process_options_common(argc, argv, options)) { + return false; + } + + if (! verify_options_basic(&_values)) { + return false; + } + if (! verify_options_database(&_values)) { + return false; + } + + *opts = &_values; + return true; +} diff --git a/ctdb/tests/src/test_options.h b/ctdb/tests/src/test_options.h new file mode 100644 index 0000000..1e194c9 --- /dev/null +++ b/ctdb/tests/src/test_options.h @@ -0,0 +1,44 @@ +/* + CTDB tests commandline options + + Copyright (C) Amitay Isaacs 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __TEST_OPTIONS_H__ +#define __TEST_OPTIONS_H__ + +struct test_options { + /* Basic options */ + const char *socket; + int timelimit; + int num_nodes; + const char *debugstr; + int interactive; + + /* Database options */ + const char *dbname; + const char *keystr; + const char *valuestr; + const char *dbtype; +}; + +bool process_options_basic(int argc, const char **argv, + const struct test_options **opts); + +bool process_options_database(int argc, const char **argv, + const struct test_options **opts); + +#endif /* __TEST_OPTIONS_H__ */ diff --git a/ctdb/tests/src/tmon_ping_test.c b/ctdb/tests/src/tmon_ping_test.c new file mode 100644 index 0000000..491792d --- /dev/null +++ b/ctdb/tests/src/tmon_ping_test.c @@ -0,0 +1,381 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" +#include "system/wait.h" + +#include <talloc.h> +#include <tevent.h> +#include <assert.h> + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_state { + const char *label; + unsigned long async_wait_time; + unsigned long blocking_sleep_time; +}; + +static void test_tmon_ping_done(struct tevent_req *subreq); +static void test_async_wait_done(struct tevent_req *subreq); + +static struct tevent_req *test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *label, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + struct tevent_req *req, *subreq; + struct test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct test_state); + if (req == NULL) { + return NULL; + } + + state->label = label; + state->async_wait_time = async_wait_time; + state->blocking_sleep_time = blocking_sleep_time; + + subreq = tmon_ping_send(state, + ev, + fd, + direction, + timeout, + interval); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_ping_done, req); + + if (state->async_wait_time != 0) { + fprintf(stderr, + "%s: async wait start %lu\n", + state->label, + state->async_wait_time); + } + subreq = tevent_wakeup_send(state, + ev, + tevent_timeval_current_ofs( + (uint32_t)async_wait_time, 0)); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_async_wait_done, req); + + return req; +} + +static void test_tmon_ping_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + bool status; + int err; + + status = tmon_ping_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + switch(err) { + case EPIPE: + fprintf(stderr, "%s: pipe closed\n", state->label); + break; + case ETIMEDOUT: + fprintf(stderr, "%s: ping timeout\n", state->label); + break; + default: + fprintf(stderr, "%s: error (%d)\n", state->label, err); + } + tevent_req_error(req, err); + return; + } + + fprintf(stderr, "%s: done\n", state->label); + tevent_req_done(req); +} + +static void test_async_wait_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct test_state *state = tevent_req_data(req, struct test_state); + unsigned int left; + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (!status) { + fprintf(stderr, + "%s: tevent_wakeup_recv() failed\n", + state->label); + /* Ignore error */ + } + if (state->async_wait_time != 0) { + fprintf(stderr, "%s: async wait end\n", state->label); + } + + if (state->blocking_sleep_time == 0) { + goto done; + } + + fprintf(stderr, + "%s: blocking sleep start %lu\n", + state->label, + state->blocking_sleep_time); + left = sleep((unsigned int)state->blocking_sleep_time); + fprintf(stderr, + "%s: blocking sleep end\n", + state->label); + if (left != 0) { + tevent_req_error(req, EINTR); + return; + } + +done: + tevent_req_done(req); +} + +static bool test_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_one(bool is_parent, + int sync_fd, + int fd, + int direction, + unsigned long timeout, + unsigned long interval, + unsigned long async_wait_time, + unsigned long blocking_sleep_time) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + bool status; + char buf[1] = ""; + ssize_t count; + int err; + int ret; + + if (!is_parent) { + count = read(sync_fd, buf, sizeof(buf)); + assert(count == 1); + assert(buf[0] == '\0'); + close(sync_fd); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + ret = ENOMEM; + goto done; + } + + req = test_send(mem_ctx, + ev, + is_parent ? "parent" : "child", + fd, + direction, + timeout, + interval, + async_wait_time, + blocking_sleep_time); + if (req == NULL) { + ret = ENOMEM; + goto done; + } + + if (is_parent) { + count = write(sync_fd, buf, sizeof(buf)); + assert(count == 1); + } + + status = tevent_req_poll(req, ev); + if (!status) { + ret = EIO; + goto done; + } + + status = test_recv(req, &err); + ret = status ? 0 : err; + +done: + return ret; +} + +static void test(unsigned long parent_timeout, + unsigned long parent_interval, + unsigned long parent_async_wait_time, + unsigned long parent_blocking_sleep_time, + int parent_result, + unsigned long child_timeout, + unsigned long child_interval, + unsigned long child_async_wait_time, + unsigned long child_blocking_sleep_time, + int child_result) +{ + int sync[2]; + int fd[2]; + pid_t pid; + int wstatus; + int ret; + + /* Pipe for synchronisation */ + ret = pipe(sync); + assert(ret == 0); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(sync[1]); + close(fd[0]); + + ret = test_one(false, + sync[0], + fd[1], + TMON_FD_BOTH, + child_timeout, + child_interval, + child_async_wait_time, + child_blocking_sleep_time); + _exit(ret); + } + + /* Parent */ + close(sync[0]); + close(fd[1]); + + ret = test_one(true, + sync[1], + fd[0], + TMON_FD_BOTH, + parent_timeout, + parent_interval, + parent_async_wait_time, + parent_blocking_sleep_time); + assert(ret == parent_result); + + /* Close to mimick exit, so child status can be checked below */ + close(fd[0]); + + /* Abort if child failed */ + waitpid(pid, &wstatus, 0); + if (WIFEXITED(wstatus)) { + assert(WEXITSTATUS(wstatus) == child_result); + } +} + +struct test_inputs { + unsigned int timeout; + unsigned int interval; + unsigned int async_wait_time; + unsigned int blocking_sleep_time; + int expected_result; +}; + +static void get_test_inputs(const char **args, struct test_inputs *inputs) +{ + if (strcmp(args[0], "false") == 0) { + inputs->interval = 0; + } else if (strcmp(args[0], "true") == 0) { + inputs->interval = 1; + } else { + inputs->interval = strtoul(args[0], NULL, 0); + } + + inputs->timeout = strtoul(args[1], NULL, 0); + inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0); + inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0); + inputs->expected_result = (int)strtoul(args[4], NULL, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s " + "\\\n\t" + "<parent_send_pings> " + "<parent_ping_timeout> " + "<parent_async_wait_time> " + "<parent_blocking_sleep_time> " + "<parent_expected_result> " + "\\\n\t" + "<child_send_pings> " + "<child_ping_timeout> " + "<child_async_wait_time> " + "<child_blocking_sleep_time> " + "<child_expected_result> " + "\n", + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + struct test_inputs parent; + struct test_inputs child; + + if (argc != 11) { + usage(argv[0]); + } + + test_backtrace_setup(); + + get_test_inputs(&argv[1], &parent); + get_test_inputs(&argv[6], &child); + + test(parent.timeout, + parent.interval, + parent.async_wait_time, + parent.blocking_sleep_time, + parent.expected_result, + child.timeout, + child.interval, + child.async_wait_time, + child.blocking_sleep_time, + child.expected_result); + + return 0; +} diff --git a/ctdb/tests/src/tmon_test.c b/ctdb/tests/src/tmon_test.c new file mode 100644 index 0000000..fb1f5eb --- /dev/null +++ b/ctdb/tests/src/tmon_test.c @@ -0,0 +1,406 @@ +/* + Test trivial FD monitoring + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/wait.h" + +#include <talloc.h> +#include <tevent.h> +#include <assert.h> +#include <ctype.h> + +#include "lib/util/tevent_unix.h" + +#include "common/tmon.h" + +#include "tests/src/test_backtrace.h" + +struct test_write_state { + const char *write_data; + size_t write_data_len; + unsigned int offset; + struct tevent_req *req; +}; + +static int test_write_callback(void *private_data, struct tmon_pkt *pkt) +{ + struct test_write_state *state = talloc_get_type_abort( + private_data, struct test_write_state); + bool status; + size_t len; + char *end; + int err; + char c; + const char *t; + + assert(state->write_data != NULL); + + len = strlen(state->write_data); + if (state->offset >= len) { + return TMON_STATUS_EXIT; + } + + c = state->write_data[state->offset]; + state->offset++; + + if (isdigit(c)) { + err = c - '0'; + + if (err == 0) { + status = tmon_set_exit(pkt); + } else { + status = tmon_set_errno(pkt, err); + } + } else if (ispunct(c)) { + switch (c) { + case '.': + return TMON_STATUS_SKIP; + break; + case '!': + status = tmon_set_ping(pkt); + break; + case '#': + /* Additional errno syntax: #nnn[;] */ + t = &state->write_data[state->offset]; + err = (int)strtol(t, &end, 10); + state->offset += (end - t); + if (state->write_data[state->offset] == ';') { + state->offset++; + } + status = tmon_set_errno(pkt, err); + break; + default: + status = false; + } + } else if (isascii(c) && !isspace(c)) { + status = tmon_set_ascii(pkt, c); + } else { + status = tmon_set_custom(pkt, (uint16_t)c); + } + + if (!status) { + return EDOM; + } + + t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE"); + if (t == NULL) { + return 0; + } + + /* + * This is write-skip mode: tmon_write() is called directly + * here in the callback and TMON_WRITE_SKIP is returned. This + * allows tmon_write() to be exercised by reusing test cases + * rather than writing extra test code and test cases. + */ + + status = tmon_write(state->req, pkt); + if (!status) { + return EIO; + } + + return TMON_STATUS_SKIP; +} + +static void test_tmon_done(struct tevent_req *subreq); + +static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + const char *write_data) +{ + struct tevent_req *req, *subreq; + struct test_write_state *state; + struct tmon_actions actions = { + .write_callback = test_write_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_write_state); + if (req == NULL) { + return NULL; + } + + state->write_data = write_data; + state->offset = 0; + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_WRITE, + 0, + 1, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + /* Nasty hack, but OK to cheapen testing - see test_write_callback() */ + state->req = subreq; + + return req; +} + +static void test_tmon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + bool status; + int err; + + status = tmon_recv(subreq, &err); + TALLOC_FREE(subreq); + if (!status) { + tevent_req_error(req, err); + return; + } + + tevent_req_done(req); +} + +static bool test_write_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static int test_timeout_ok_callback(void *private_data) +{ + return 0; +} + +static int test_read_callback(void *private_data, struct tmon_pkt *pkt) +{ + bool status; + char c; + uint16_t val; + + status = tmon_parse_ping(pkt); + if (status) { + printf("PING\n"); + fflush(stdout); + return 0; + } + + status = tmon_parse_ascii(pkt, &c); + if (status) { + printf("ASCII %c\n", c); + fflush(stdout); + return 0; + } + + status = tmon_parse_custom(pkt, &val); + if (status) { + printf("CUSTOM 0x%"PRIx16"\n", val); + fflush(stdout); + return 0; + } + + return 0; +} + +static int test_close_ok_callback(void *private_data) +{ + return 0; +} + +struct test_read_state { +}; + +static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + int fd, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + struct tevent_req *req, *subreq; + struct test_read_state *state; + struct tmon_actions actions = { + .read_callback = test_read_callback, + }; + + req = tevent_req_create(mem_ctx, &state, struct test_read_state); + if (req == NULL) { + return NULL; + } + + if (timeout_ok) { + actions.timeout_callback = test_timeout_ok_callback; + } + if (close_ok) { + actions.close_callback = test_close_ok_callback; + } + + subreq = tmon_send(state, + ev, + fd, + TMON_FD_READ, + timeout, + 0, + &actions, + state); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, test_tmon_done, req); + + return req; +} + +static bool test_read_recv(struct tevent_req *req, int *perr) +{ + if (tevent_req_is_unix_error(req, perr)) { + return false; + } + + return true; +} + +static void test(const char *write_data, + bool close_ok, + unsigned long timeout, + bool timeout_ok) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct tevent_req *req; + int fd[2]; + pid_t pid; + int wstatus; + bool status; + int err; + int ret; + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + ev = tevent_context_init(mem_ctx); + assert(ev != NULL); + + ret = pipe(fd); + assert(ret == 0); + + pid = fork(); + assert(pid != -1); + + if (pid == 0) { + /* child */ + close(fd[1]); + + req = test_read_send(mem_ctx, + ev, + fd[0], + close_ok, + timeout, + timeout_ok); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_read_recv(req, &err); + if (status) { + err = 0; + printf("READER OK\n"); + } else { + printf("READER ERR=%d\n", err); + } + fflush(stdout); + + _exit(ret); + } + + /* Parent */ + close(fd[0]); + + req = test_write_send(mem_ctx, + ev, + fd[1], + write_data); + assert(req != NULL); + + status = tevent_req_poll(req, ev); + assert(status); + + status = test_write_recv(req, &err); + if (status) { + err = 0; + printf("WRITER OK\n"); + } else { + printf("WRITER ERR=%d\n", err); + } + fflush(stdout); + + /* Close to mimick exit, so child status can be checked below */ + close(fd[1]); + + waitpid(pid, &wstatus, 0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "usage: %s <write_data> <close_ok> <timeout> <timeout_ok>\n\n" + " <write_data> is processed by test_write_callback(), " + "1 character per second:\n" + " 0: write EXIT\n" + " 1-9: write ERRNO 1-9\n" + " .: skip write\n" + " <space>: write CUSTOM containing <space>\n" + " other <ascii>: write ASCII containing <ascii>\n" + " other: write CUSTOM\n" + " See test_write_callback() for more details\n" + , + prog); + exit(1); +} + +int main(int argc, const char **argv) +{ + bool close_ok, timeout_ok; + unsigned long timeout; + + if (argc != 5) { + usage(argv[0]); + } + + test_backtrace_setup(); + + close_ok = (strcmp(argv[2], "true") == 0); + timeout = strtoul(argv[3], NULL, 0); + if (timeout == 0) { + /* + * Default timeout that should not come into play but + * will cause tests to fail after a reasonable amount + * of time, if something unexpected happens. + */ + timeout = 20; + } + timeout_ok = (strcmp(argv[4], "true") == 0); + + test(argv[1], close_ok, timeout, timeout_ok); + + return 0; +} diff --git a/ctdb/tests/src/transaction_loop.c b/ctdb/tests/src/transaction_loop.c new file mode 100644 index 0000000..c6bf35d --- /dev/null +++ b/ctdb/tests/src/transaction_loop.c @@ -0,0 +1,419 @@ +/* + simple ctdb benchmark for persistent databases + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct transaction_loop_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + int num_nodes; + int timelimit; + int interactive; + TDB_DATA key; + uint32_t pnn; + struct ctdb_transaction_handle *h; + uint32_t *old_counter, *counter; + struct tevent_req *subreq; + bool done; +}; + +static void transaction_loop_start(struct tevent_req *subreq); +static void transaction_loop_started(struct tevent_req *subreq); +static void transaction_loop_committed(struct tevent_req *subreq); +static void transaction_loop_each_second(struct tevent_req *subreq); +static bool transaction_loop_check_counters(struct tevent_req *req); +static void transaction_loop_finish(struct tevent_req *subreq); + +static struct tevent_req *transaction_loop_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *ctdb_db, + int num_nodes, int timelimit, int interactive, + const char *keystr) +{ + struct tevent_req *req, *subreq; + struct transaction_loop_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct transaction_loop_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->ctdb_db = ctdb_db; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->interactive = interactive; + state->key.dptr = discard_const(keystr); + state->key.dsize = strlen(keystr); + state->pnn = ctdb_client_pnn(client); + state->old_counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->old_counter, req)) { + return tevent_req_post(req, ev); + } + state->counter = talloc_zero_array(state, uint32_t, num_nodes); + if (tevent_req_nomem(state->counter, req)) { + return tevent_req_post(req, ev); + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, transaction_loop_start, req); + + return req; +} + +static void transaction_loop_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int ret; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); + state->subreq = subreq; + + if (ctdb_client_pnn(state->client) == 0) { + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, + req); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_finish, req); +} + +static void transaction_loop_started(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + TDB_DATA data; + int ret; + uint32_t *counter; + + state->h = ctdb_transaction_start_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (state->h == NULL) { + fprintf(stderr, "transaction start failed\n"); + tevent_req_error(req, ret); + return; + } + + ret = ctdb_transaction_fetch_record(state->h, state->key, + state, &data); + if (ret != 0) { + fprintf(stderr, "transaction fetch record failed\n"); + tevent_req_error(req, ret); + return; + } + + if (data.dsize < state->num_nodes * sizeof(uint32_t)) { + TALLOC_FREE(data.dptr); + + data.dsize = state->num_nodes * sizeof(uint32_t); + data.dptr = (uint8_t *)talloc_zero_array(state, uint32_t, + state->num_nodes); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + } + + counter = (uint32_t *)data.dptr; + counter[state->pnn] += 1; + memcpy(state->counter, counter, state->num_nodes * sizeof(uint32_t)); + + ret = ctdb_transaction_store_record(state->h, state->key, data); + if (ret != 0) { + fprintf(stderr, "transaction store failed\n"); + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_transaction_commit_send(state, state->ev, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->h); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_committed, req); + state->subreq = subreq; +} + +static void transaction_loop_committed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int ret; + bool status; + + status = ctdb_transaction_commit_recv(subreq, &ret); + TALLOC_FREE(subreq); + state->subreq = NULL; + if (! status) { + fprintf(stderr, "transaction commit failed - %s\n", + strerror(ret)); + tevent_req_error(req, ret); + return; + } + + if (state->pnn == 0) { + if (! transaction_loop_check_counters(req)) { + return; + } + } + + if (state->done) { + int i; + + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; i<state->num_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + + tevent_req_done(req); + + return; + } + + subreq = ctdb_transaction_start_send(state, state->ev, state->client, + tevent_timeval_current_ofs( + state->timelimit, 0), + state->ctdb_db, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_started, req); +} + +static void transaction_loop_each_second(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + int i; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + fprintf(stderr, "tevent wakeup failed\n"); + tevent_req_error(req, EIO); + return; + } + + if (state->interactive == 1) { + printf("Transaction[%u]: ", ctdb_client_pnn(state->client)); + for (i=0; i<state->num_nodes; i++) { + printf("%6u ", state->counter[i]); + } + printf("\n"); + fflush(stdout); + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(1, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, transaction_loop_each_second, req); +} + +static bool transaction_loop_check_counters(struct tevent_req *req) +{ + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + int i; + bool monotonous = true; + + for (i=0; i<state->num_nodes; i++) { + if (state->counter[i] < state->old_counter[i]) { + fprintf(stderr, + "Counter reduced for node %d: %u -> %u\n", + i, state->old_counter[i], state->counter[i]); + monotonous = false; + break; + } + } + + if (monotonous) { + memcpy(state->old_counter, state->counter, + state->num_nodes * sizeof(uint32_t)); + } + + return monotonous; +} + +static void transaction_loop_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct transaction_loop_state *state = tevent_req_data( + req, struct transaction_loop_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + + state->done = true; + + if (! status) { + tevent_req_error(req, EIO); + return; + } +} + +static bool transaction_loop_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + uint8_t db_flags; + int ret; + bool status; + + setup_logging("transaction_loop", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + if (strcmp(opts->dbtype, "persistent") == 0) { + db_flags = CTDB_DB_FLAGS_PERSISTENT; + } else if (strcmp(opts->dbtype, "replicated") == 0) { + db_flags = CTDB_DB_FLAGS_REPLICATED; + } else { + fprintf(stderr, "Database must be persistent or replicated\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + db_flags, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach to persistent DB %s\n", + opts->dbname); + exit(1); + } + + req = transaction_loop_send(mem_ctx, ev, client, ctdb_db, + opts->num_nodes, opts->timelimit, + opts->interactive, opts->keystr); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = transaction_loop_recv(req, &ret); + if (! status) { + fprintf(stderr, "transaction loop test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/tunable_test.c b/ctdb/tests/src/tunable_test.c new file mode 100644 index 0000000..ea94aec --- /dev/null +++ b/ctdb/tests/src/tunable_test.c @@ -0,0 +1,71 @@ +/* + Test tunable handling + + Copyright (C) Martin Schwenke, DataDirect Networks 2022 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" + +#include <talloc.h> +#include <assert.h> + +#include "common/tunable.c" + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct ctdb_tunable_list tun_list; + struct ctdb_var_list *list; + bool status; + int ret = 0; + int i; + + if (argc != 2) { + fprintf(stderr, "Usage: %s <filename>\n", argv[0]); + return 1; + } + + mem_ctx = talloc_new(NULL); + assert(mem_ctx != NULL); + + status = ctdb_tunable_load_file(mem_ctx, &tun_list, argv[1]); + if (!status) { + ret = EINVAL; + goto done; + } + + list = ctdb_tunable_names(mem_ctx); + assert(list != NULL); + + for (i = 0; i < list->count; i++) { + const char *var = list->var[i]; + uint32_t val; + + status = ctdb_tunable_get_value(&tun_list, var, &val); + if (!status) { + ret = EIO; + goto done; + } + + printf("%s=%"PRIu32"\n", var, val); + fflush(stdout); + } + +done: + talloc_free(mem_ctx); + return ret; +} diff --git a/ctdb/tests/src/tunnel_cmd.c b/ctdb/tests/src/tunnel_cmd.c new file mode 100644 index 0000000..73a2297 --- /dev/null +++ b/ctdb/tests/src/tunnel_cmd.c @@ -0,0 +1,199 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" + +#define TUNNEL_ID (CTDB_TUNNEL_TEST | 0xf0f0f0f0) + +struct listen_state { + TALLOC_CTX *mem_ctx; + bool done; +}; + +static void listen_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct listen_state *state = (struct listen_state *)private_data; + const char *msg; + size_t np; + int ret; + + ret = ctdb_stringn_pull(buf, buflen, state->mem_ctx, &msg, &np); + if (ret != 0) { + fprintf(stderr, "Invalid tunnel message, ret=%d\n", ret); + return; + } + + fprintf(stderr, "%u: %s\n", srcnode, msg); + + if (strcmp(msg, "quit") == 0) { + state->done = true; + } + + talloc_free(discard_const(msg)); +} + +static int cmd_listen(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client) +{ + struct ctdb_tunnel_context *tunnel; + struct listen_state state; + int ret; + + state.mem_ctx = mem_ctx; + state.done = false; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + listen_callback, &state, &tunnel); + if (ret != 0) { + return ret; + } + + ctdb_client_wait(ev, &state.done); + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void send_callback(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, void *private_data) +{ + fprintf(stderr, "send received a message - %u: %zu\n", srcnode, buflen); +} + +static int cmd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct ctdb_client_context *client, + uint32_t destnode, const char *msg) +{ + struct ctdb_tunnel_context *tunnel; + uint8_t *buf; + size_t buflen, np; + int ret; + + ret = ctdb_tunnel_setup(mem_ctx, ev, client, TUNNEL_ID, + send_callback, NULL, &tunnel); + if (ret != 0) { + return ret; + } + + buflen = ctdb_stringn_len(&msg); + buf = talloc_size(mem_ctx, buflen); + if (buf == NULL) { + return ENOMEM; + } + ctdb_stringn_push(&msg, buf, &np); + + ret = ctdb_tunnel_request(mem_ctx, ev, tunnel, destnode, + tevent_timeval_zero(), buf, buflen, false); + if (ret != 0) { + return ret; + } + + ret = ctdb_tunnel_destroy(ev, tunnel); + if (ret != 0) { + return ret; + } + + return 0; +} + +static void usage(const char *cmd) +{ + fprintf(stderr, "usage: %s <ctdb-socket> listen\n", cmd); + fprintf(stderr, "usage: %s <ctdb-socket> send <pnn> <msg>\n", cmd); +} + +int main(int argc, const char **argv) +{ + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + const char *socket = NULL, *msg = NULL; + uint32_t pnn = CTDB_UNKNOWN_PNN; + int ret; + bool do_listen = false; + bool do_send = false; + + if (argc != 3 && argc != 5) { + usage(argv[0]); + exit(1); + } + + socket = argv[1]; + + if (strcmp(argv[2], "listen") == 0) { + do_listen = true; + } else if (strcmp(argv[2], "send") == 0) { + if (argc != 5) { + usage(argv[0]); + exit(1); + } + + pnn = atol(argv[3]); + msg = argv[4]; + do_send = true; + } else { + usage(argv[0]); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + talloc_free(mem_ctx); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, socket, &client); + if (ret != 0) { + talloc_free(mem_ctx); + exit(1); + } + + if (do_listen) { + ret = cmd_listen(mem_ctx, ev, client); + } + if (do_send) { + ret = cmd_send(mem_ctx, ev, client, pnn, msg); + } + + talloc_free(mem_ctx); + + return ret; +} diff --git a/ctdb/tests/src/tunnel_test.c b/ctdb/tests/src/tunnel_test.c new file mode 100644 index 0000000..a6d44ba --- /dev/null +++ b/ctdb/tests/src/tunnel_test.c @@ -0,0 +1,480 @@ +/* + CTDB tunnel test + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_private.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct test_data { + uint32_t pnn; + uint32_t count; +}; + +static size_t test_data_len(struct test_data *in) +{ + return ctdb_uint32_len(&in->pnn) + ctdb_uint32_len(&in->count); +} + +static void test_data_push(struct test_data *in, uint8_t *buf, size_t *npush) +{ + size_t offset = 0, np; + + ctdb_uint32_push(&in->pnn, buf+offset, &np); + offset += np; + + ctdb_uint32_push(&in->count, buf+offset, &np); + offset += np; + + *npush = offset; +} + +static int test_data_pull(uint8_t *buf, size_t buflen, struct test_data *out, + size_t *npull) +{ + size_t offset = 0, np; + int ret; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->pnn, &np); + if (ret != 0) { + return ret; + } + offset += np; + + ret = ctdb_uint32_pull(buf+offset, buflen-offset, &out->count, &np); + if (ret != 0) { + return ret; + } + offset += np; + + *npull = offset; + return 0; +} + +/* + * Set up 2 tunnels from each node - one to the next node and one to the + * previous node. The tunnel to the next node is used for sending data and + * tunnel to the previous node is used for receiving data. + */ + +struct tunnel_test_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + int num_nodes; + int timelimit; + + uint32_t pnn; + uint32_t next_node; + uint32_t prev_node; + bool done; + struct ctdb_tunnel_context *send_tunnel; + struct ctdb_tunnel_context *recv_tunnel; + uint32_t count; + uint8_t *buf; +}; + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq); +static void tunnel_test_start(struct tevent_req *subreq); +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata); +static void tunnel_test_msg_send_done(struct tevent_req *subreq); +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data); +static void tunnel_test_done(struct tevent_req *subreq); +static void tunnel_test_finish(struct tevent_req *subreq); +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq); +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq); + +static struct tevent_req *tunnel_test_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + int num_nodes, int timelimit) +{ + struct tevent_req *req, *subreq; + struct tunnel_test_state *state; + + req = tevent_req_create(mem_ctx, &state, struct tunnel_test_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->num_nodes = num_nodes; + state->timelimit = timelimit; + state->pnn = ctdb_client_pnn(client); + state->prev_node = (state->pnn + num_nodes - 1) % num_nodes; + state->next_node = (state->pnn + 1) % num_nodes; + state->done = false; + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->pnn, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_done, req); + + return req; +} + +static void tunnel_test_send_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->send_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = ctdb_tunnel_setup_send(state, state->ev, state->client, + CTDB_TUNNEL_TEST | state->prev_node, + tunnel_test_handler, req); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_done, req); +} + +static void tunnel_test_recv_tunnel_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_setup_recv(subreq, &ret, &state->recv_tunnel); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = cluster_wait_send(state, state->ev, state->client, + state->num_nodes); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_start, req); +} + +static void tunnel_test_start(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + int ret; + bool status; + + status = cluster_wait_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(state->timelimit, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_done, req); + + tdata.pnn = state->pnn; + tdata.count = state->count; + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_msg_send(struct tevent_req *req, + struct test_data *tdata) +{ + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct tevent_req *subreq; + size_t buflen, np; + + buflen = test_data_len(tdata); + state->buf = talloc_size(state, buflen); + if (tevent_req_nomem(state->buf, req)) { + return; + } + test_data_push(tdata, state->buf, &np); + + subreq = ctdb_tunnel_request_send(state, state->ev, + state->send_tunnel, + state->next_node, + tevent_timeval_zero(), + state->buf, buflen, false); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_msg_send_done, req); +} + +static void tunnel_test_msg_send_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_request_recv(subreq, &ret, NULL, NULL, NULL); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + TALLOC_FREE(state->buf); +} + +static void tunnel_test_handler(struct ctdb_tunnel_context *tctx, + uint32_t srcnode, uint32_t reqid, + uint8_t *buf, size_t buflen, + void *private_data) +{ + struct tevent_req *req = talloc_get_type_abort( + private_data, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + struct test_data tdata; + size_t np; + int ret; + + if (state->done) { + return; + } + + if (tctx == state->send_tunnel) { + fprintf(stderr, "pnn:%u Received data on send tunnel\n", + state->pnn); + tevent_req_error(req, EPROTO); + return; + } + + ret = test_data_pull(buf, buflen, &tdata, &np); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + if (tdata.pnn == state->pnn) { + if (tdata.count != state->count) { + tevent_req_error(req, EPROTO); + return; + } + + state->count = tdata.count + 1; + tdata.count = state->count; + } + + tunnel_test_msg_send(req, &tdata); +} + +static void tunnel_test_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + printf("pnn[%u] %.1lf msgs/sec\n", + state->pnn, (double)state->count / state->timelimit); + + state->done = true; + + /* wait few more seconds */ + subreq = tevent_wakeup_send(state, state->ev, + tevent_timeval_current_ofs(3, 0)); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_finish, req); +} + +static void tunnel_test_finish(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + bool status; + + status = tevent_wakeup_recv(subreq); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, EIO); + return; + } + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->send_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_send_tunnel_closed, req); +} + +static void tunnel_test_send_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->send_tunnel = NULL; + + subreq = ctdb_tunnel_destroy_send(state, state->ev, + state->recv_tunnel); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, tunnel_test_recv_tunnel_closed, req); +} + +static void tunnel_test_recv_tunnel_closed(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct tunnel_test_state *state = tevent_req_data( + req, struct tunnel_test_state); + int ret; + bool status; + + status = ctdb_tunnel_destroy_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + state->recv_tunnel = NULL; + + tevent_req_done(req); +} + +static bool tunnel_test_recv(struct tevent_req *req, int *perr) +{ + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("tunnel_test", DEBUG_STDERR); + + status = process_options_basic(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client, ret=%d\n", ret); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + req = tunnel_test_send(mem_ctx, ev, client, opts->num_nodes, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = tunnel_test_recv(req, &ret); + if (! status) { + fprintf(stderr, "tunnel test failed, ret=%d\n", ret); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record.c b/ctdb/tests/src/update_record.c new file mode 100644 index 0000000..11b6050 --- /dev/null +++ b/ctdb/tests/src/update_record.c @@ -0,0 +1,236 @@ +/* + Update a record and increase it's RSN + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key; +}; + +static void update_record_fetch_done(struct tevent_req *subreq); +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + + subreq = ctdb_fetch_lock_send(state, state->ev, state->client, + state->db, state->key, false); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_fetch_done, req); + + return req; +} + +static void update_record_fetch_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_record_handle *h; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + TDB_DATA data; + int ret; + + h = ctdb_fetch_lock_recv(subreq, &header, NULL, NULL, &ret); + TALLOC_FREE(subreq); + if (h == NULL) { + tevent_req_error(req, ret); + return; + } + + talloc_free(h); + + header.rsn += 10; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return; + } + + data.dptr = (uint8_t *)talloc_asprintf(recbuf, "%"PRIu64, header.rsn); + if (tevent_req_nomem(data.dptr, req)) { + return; + } + data.dsize = strlen((char *)data.dptr); + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, state->key, data); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + 0, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} diff --git a/ctdb/tests/src/update_record_persistent.c b/ctdb/tests/src/update_record_persistent.c new file mode 100644 index 0000000..2d6d21e --- /dev/null +++ b/ctdb/tests/src/update_record_persistent.c @@ -0,0 +1,218 @@ +/* + Update a record in persistent database + + Copyright (C) Amitay Isaacs 2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/network.h" + +#include "lib/util/debug.h" +#include "lib/util/tevent_unix.h" + +#include "protocol/protocol_api.h" +#include "client/client.h" +#include "tests/src/test_options.h" +#include "tests/src/cluster_wait.h" + +struct update_record_state { + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *db; + int timelimit; + TDB_DATA key, data; +}; + +static void update_record_update_done(struct tevent_req *subreq); + +static struct tevent_req *update_record_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct ctdb_client_context *client, + struct ctdb_db_context *db, + const char *keystr, + const char *valuestr, + int timelimit) +{ + struct tevent_req *req, *subreq; + struct update_record_state *state; + struct ctdb_ltdb_header header; + struct ctdb_rec_buffer *recbuf; + struct ctdb_req_control request; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct update_record_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->client = client; + state->db = db; + state->timelimit = timelimit; + state->key.dptr = (uint8_t *)discard_const(keystr); + state->key.dsize = strlen(keystr); + state->data.dptr = (uint8_t *)discard_const(valuestr); + state->data.dsize = strlen(valuestr); + + ret = ctdb_ltdb_fetch(state->db, state->key, &header, NULL, NULL); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + header.rsn += 1; + + recbuf = ctdb_rec_buffer_init(state, ctdb_db_id(state->db)); + if (tevent_req_nomem(recbuf, req)) { + return tevent_req_post(req, ev); + } + + ret = ctdb_rec_buffer_add(state, recbuf, 0, &header, + state->key, state->data); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + ctdb_req_control_update_record(&request, recbuf); + subreq = ctdb_client_control_send(state, state->ev, state->client, + CTDB_CURRENT_NODE, + tevent_timeval_current_ofs( + state->timelimit, 0), + &request); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, update_record_update_done, req); + + talloc_free(recbuf); + return req; +} + +static void update_record_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct update_record_state *state = tevent_req_data( + req, struct update_record_state); + struct ctdb_reply_control *reply; + int ret; + bool status; + + status = ctdb_client_control_recv(subreq, &ret, state, &reply); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + ret = ctdb_reply_control_update_record(reply); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + talloc_free(reply); + + tevent_req_done(req); +} + +static bool update_record_recv(struct tevent_req *req, int *perr) +{ + int err; + + if (tevent_req_is_unix_error(req, &err)) { + if (perr != NULL) { + *perr = err; + } + return false; + } + return true; +} + +int main(int argc, const char *argv[]) +{ + const struct test_options *opts; + TALLOC_CTX *mem_ctx; + struct tevent_context *ev; + struct ctdb_client_context *client; + struct ctdb_db_context *ctdb_db; + struct tevent_req *req; + int ret; + bool status; + + setup_logging("update_record_persistene", DEBUG_STDERR); + + status = process_options_database(argc, argv, &opts); + if (! status) { + exit(1); + } + + if (opts->valuestr == NULL) { + fprintf(stderr, "Error: please specify key value (-v)\n"); + exit(1); + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ev = tevent_context_init(mem_ctx); + if (ev == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_client_init(mem_ctx, ev, opts->socket, &client); + if (ret != 0) { + fprintf(stderr, "Failed to initialize client (%s), %s\n", + opts->socket, strerror(ret)); + exit(1); + } + + if (! ctdb_recovery_wait(ev, client)) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + ret = ctdb_attach(ev, client, tevent_timeval_zero(), opts->dbname, + CTDB_DB_FLAGS_PERSISTENT, &ctdb_db); + if (ret != 0) { + fprintf(stderr, "Failed to attach DB %s\n", opts->dbname); + exit(1); + } + + req = update_record_send(mem_ctx, ev, client, ctdb_db, + opts->keystr, opts->valuestr, + opts->timelimit); + if (req == NULL) { + fprintf(stderr, "Memory allocation error\n"); + exit(1); + } + + tevent_req_poll(req, ev); + + status = update_record_recv(req, &ret); + if (! status) { + fprintf(stderr, "update record failed\n"); + exit(1); + } + + talloc_free(mem_ctx); + return 0; +} |