s3: add dbwrap_torture - a tool to stress test tdb transactions through dbwrap
authorMichael Adam <obnox@samba.org>
Fri, 20 Nov 2009 23:48:40 +0000 (00:48 +0100)
committerMichael Adam <obnox@samba.org>
Mon, 23 Nov 2009 22:51:40 +0000 (23:51 +0100)
This can be used to also test tdb transactions on clustered installations
throught ctdb. The test is modeled after the ctdb_transaction.c test program
from the ctdb source code. It runs transactions in a tight loop on a test
database called "transactions.tdb" (by default), increasing a counter in each
iteration. In a clustered environment, a counter is maintained for each node.

Michael

source3/Makefile.in
source3/utils/dbwrap_torture.c [new file with mode: 0644]

index 724c221..28be9d1 100644 (file)
@@ -234,7 +234,8 @@ EVERYTHING_PROGS = bin/debug2html@EXEEXT@ bin/smbfilter@EXEEXT@ \
        $(TALLOCTORT) bin/replacetort@EXEEXT@ \
        bin/log2pcap@EXEEXT@ \
        bin/vlp@EXEEXT@ bin/smbiconv@EXEEXT@ \
-       bin/dbwrap_tool@EXEEXT@
+       bin/dbwrap_tool@EXEEXT@ \
+       bin/dbwrap_torture@EXEEXT@
 
 PAM_MODULES = @PAM_MODULES@
 
@@ -1338,6 +1339,12 @@ DBWRAP_TOOL_OBJ = utils/dbwrap_tool.o \
                  $(LIB_NONSMBD_OBJ) \
                  $(LIBSAMBA_OBJ)
 
+DBWRAP_TORTURE_OBJ = utils/dbwrap_torture.o \
+                    $(PARAM_OBJ) \
+                    $(LIB_NONSMBD_OBJ) \
+                    $(LIBSAMBA_OBJ) \
+                    $(POPT_LIB_OBJ)
+
 ######################################################################
 # now the rules...
 ######################################################################
@@ -2912,6 +2919,17 @@ bin/dbwrap_tool@EXEEXT@: $(DBWRAP_TOOL_OBJ) $(LIBTALLOC) $(LIBTDB)
 install-dbwrap_tool:: bin/dbwrap_tool@EXEEXT@
        @$(SHELL) script/installbin.sh $(INSTALLPERMS_BIN) $(DESTDIR) $(prefix) $(BINDIR) $<
 
+bin/dbwrap_torture@EXEEXT@: $(DBWRAP_TORTURE_OBJ) $(LIBTALLOC) $(LIBTDB)
+       @echo "Linking $@"
+       @$(CC) -o $@ $(DBWRAP_TORTURE_OBJ)\
+               $(LDFLAGS) $(DYNEEXP) $(LIBS) \
+               $(LDAP_LIBS) \
+               $(POPT_LIBS) \
+               $(LIBTALLOC_LIBS) $(LIBTDB_LIBS)
+
+install-dbwrap_torture:: bin/dbwrap_torture@EXEEXT@
+       @$(SHELL) script/installbin.sh $(INSTALLPERMS_BIN) $(DESTDIR) $(prefix) $(BINDIR) $<
+
 install:: installservers installbin @INSTALL_CIFSMOUNT@ @INSTALL_CIFSUMOUNT@ @INSTALL_CIFSUPCALL@ installman \
                installscripts installdat installmodules @SWAT_INSTALL_TARGETS@ \
                @INSTALL_PAM_MODULES@ installlibs installmo
diff --git a/source3/utils/dbwrap_torture.c b/source3/utils/dbwrap_torture.c
new file mode 100644 (file)
index 0000000..2077eea
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+   Samba Linux/Unix CIFS implementation
+
+   simple tool to test persistent databases
+
+   Copyright (C) Michael Adam     2009
+
+   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 "includes.h"
+
+#if 0
+#include "lib/events/events.h"
+#include "system/filesys.h"
+#include "popt.h"
+#include "cmdline.h"
+
+#include <sys/time.h>
+#include <time.h>
+#endif
+
+extern bool AllowDebugChange;
+
+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));
+}
+
+#define DEFAULT_DB_NAME "transaction.tdb"
+
+static int timelimit = 10;
+static int delay = 0;
+static int verbose = 0;
+static int no_trans = 0;
+static char *db_name = (char *)discard_const(DEFAULT_DB_NAME);
+
+
+static unsigned int pnn;
+
+static TDB_DATA old_data;
+
+static int success = true;
+
+static void print_counters(void)
+{
+       int i;
+       uint32_t *old_counters;
+
+       printf("[%4u] Counters: ", getpid());
+       old_counters = (uint32_t *)old_data.dptr;
+       for (i=0; i < old_data.dsize/sizeof(uint32_t); i++) {
+               printf("%6u ", old_counters[i]);
+       }
+       printf("\n");
+}
+
+static void each_second(struct tevent_context *ev,
+                       struct tevent_timer *te,
+                       struct timeval t,
+                       void *private_data)
+{
+       struct db_context *db = talloc_get_type(private_data, struct db_context);
+
+       print_counters();
+
+       tevent_add_timer(ev, db, timeval_current_ofs(1, 0), each_second, db);
+}
+
+static bool check_counters(struct db_context *db, TDB_DATA data)
+{
+       int i;
+       uint32_t *counters, *old_counters;
+
+       counters     = (uint32_t *)data.dptr;
+       old_counters = (uint32_t *)old_data.dptr;
+
+       /* check that all the counters are monotonic increasing */
+       for (i=0; i < old_data.dsize/sizeof(uint32_t); i++) {
+               if (counters[i] < old_counters[i]) {
+                       printf("[%4u] ERROR: counters has decreased for node %u  From %u to %u\n",
+                              getpid(), i, old_counters[i], counters[i]);
+                       success = false;
+                       return false;
+               }
+       }
+
+       if (old_data.dsize != data.dsize) {
+               old_data.dsize = data.dsize;
+               old_data.dptr = (unsigned char*)talloc_realloc_size(db, old_data.dptr, old_data.dsize);
+       }
+
+       memcpy(old_data.dptr, data.dptr, data.dsize);
+       if (verbose) print_counters();
+
+       return true;
+}
+
+
+static void do_sleep(unsigned int sec)
+{
+       unsigned int i;
+
+       if (sec == 0) {
+               return;
+       }
+
+       for (i=0; i<sec; i++) {
+               if (verbose) printf(".");
+               sleep(1);
+       }
+
+       if (verbose) printf("\n");
+}
+
+static void test_store_records(struct db_context *db, struct tevent_context *ev)
+{
+       TDB_DATA key;
+       uint32_t *counters;
+       TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+       key.dptr = (unsigned char *)discard_const("testkey");
+       key.dsize = strlen((const char *)key.dptr)+1;
+
+       start_timer();
+       while ((timelimit == 0) || (end_timer() < timelimit)) {
+               struct db_record *rec;
+               TDB_DATA data;
+               int ret;
+               NTSTATUS status;
+
+               if (!no_trans) {
+                       if (verbose) DEBUG(1, ("starting transaction\n"));
+                       ret = db->transaction_start(db);
+                       if (ret != 0) {
+                               DEBUG(0, ("Failed to start transaction on node "
+                                         "%d\n", pnn));
+                               goto fail;
+                       }
+                       if (verbose) DEBUG(1, ("transaction started\n"));
+                       do_sleep(delay);
+               }
+
+               if (verbose) DEBUG(1, ("calling fetch_lock\n"));
+               rec = db->fetch_locked(db, tmp_ctx, key);
+               if (rec == NULL) {
+                       DEBUG(0, ("Failed to fetch record\n"));
+                       goto fail;
+               }
+               if (verbose) DEBUG(1, ("fetched record ok\n"));
+               do_sleep(delay);
+
+               data.dsize = MAX(rec->value.dsize, sizeof(uint32_t) * (pnn+1));
+               data.dptr = (unsigned char *)talloc_zero_size(tmp_ctx,
+                                                             data.dsize);
+               if (data.dptr == NULL) {
+                       DEBUG(0, ("Failed to allocate data\n"));
+                       goto fail;
+               }
+               memcpy(data.dptr, rec->value.dptr,rec->value.dsize);
+
+               counters = (uint32_t *)data.dptr;
+
+               /* bump our counter */
+               counters[pnn]++;
+
+               if (verbose) DEBUG(1, ("storing data\n"));
+               status = rec->store(rec, data, TDB_REPLACE);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(0, ("Failed to store record\n"));
+                       if (!no_trans) {
+                               ret = db->transaction_cancel(db);
+                               if (ret != 0) {
+                                       DEBUG(0, ("Error cancelling transaction.\n"));
+                               }
+                       }
+                       goto fail;
+               }
+               talloc_free(rec);
+               if (verbose) DEBUG(1, ("stored data ok\n"));
+               do_sleep(delay);
+
+               if (!no_trans) {
+                       if (verbose) DEBUG(1, ("calling transaction_commit\n"));
+                       ret = db->transaction_commit(db);
+                       if (ret != 0) {
+                               DEBUG(0, ("Failed to commit transaction\n"));
+                               goto fail;
+                       }
+                       if (verbose) DEBUG(1, ("transaction committed\n"));
+               }
+
+               /* store the counters and verify that they are sane */
+               if (verbose || (pnn == 0)) {
+                       if (!check_counters(db, data)) {
+                               goto fail;
+                       }
+               }
+               talloc_free(data.dptr);
+
+               do_sleep(delay);
+       }
+
+       goto done;
+
+fail:
+       success = false;
+
+done:
+       talloc_free(tmp_ctx);
+       return;
+}
+
+/*
+  main program
+*/
+int main(int argc, const char *argv[])
+{
+       TALLOC_CTX *mem_ctx;
+       struct tevent_context *ev_ctx;
+       struct messaging_context *msg_ctx;
+       struct db_context *db;
+
+       int unsafe_writes = 0;
+       struct poptOption popt_options[] = {
+               POPT_AUTOHELP
+               POPT_COMMON_SAMBA
+               { "timelimit", 't', POPT_ARG_INT, &timelimit, 0, "timelimit", "INTEGER" },
+               { "delay", 'D', POPT_ARG_INT, &delay, 0, "delay (in seconds) between operations", "INTEGER" },
+               { "verbose", 'v', POPT_ARG_NONE,  &verbose, 0, "switch on verbose mode", NULL },
+               { "db-name", 'N', POPT_ARG_STRING, &db_name, 0, "name of the test db", "NAME" },
+               { "no-trans", 'n', POPT_ARG_NONE, &no_trans, 0, "use fetch_lock/record store instead of transactions", NULL },
+               { "unsafe-writes", 'u', POPT_ARG_NONE, &unsafe_writes, 0, "do not use tdb transactions when writing", NULL },
+               POPT_TABLEEND
+       };
+       int opt;
+       const char **extra_argv;
+       int extra_argc = 0;
+       poptContext pc;
+       int tdb_flags;
+
+       int ret = 1;
+
+       mem_ctx = talloc_stackframe();
+
+       if (verbose) {
+               setbuf(stdout, (char *)NULL); /* don't buffer */
+       } else {
+               setlinebuf(stdout);
+       }
+
+       DEBUGLEVEL_CLASS[DBGC_ALL] = 0;
+
+       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));
+                       goto done;
+               }
+       }
+
+       /* 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++;
+       }
+
+       load_case_tables();
+       dbf = x_stderr;
+       AllowDebugChange = false;
+       lp_load(get_dyn_CONFIGFILE(), true, false, false, true);
+
+       ev_ctx = tevent_context_init(mem_ctx);
+       if (ev_ctx == NULL) {
+               d_fprintf(stderr, "ERROR: could not init event context\n");
+               goto done;
+       }
+
+       msg_ctx = messaging_init(mem_ctx, server_id_self(), ev_ctx);
+       if (msg_ctx == NULL) {
+               d_fprintf(stderr, "ERROR: could not init messaging context\n");
+               goto done;
+       }
+
+       if (unsafe_writes == 1) {
+               tdb_flags = TDB_NOSYNC;
+       } else {
+               tdb_flags = TDB_DEFAULT;
+       }
+
+       db = db_open(mem_ctx, db_name, 0, tdb_flags,  O_RDWR | O_CREAT, 0644);
+
+       if (db == NULL) {
+               d_fprintf(stderr, "failed to open db '%s': %s\n", db_name,
+                         strerror(errno));
+               goto done;
+       }
+
+       if (get_my_vnn() == NONCLUSTER_VNN) {
+               set_my_vnn(0);
+       }
+       pnn = get_my_vnn();
+
+       printf("Starting test on node %u. running for %u seconds. "
+              "sleep delay: %u seconds.\n", pnn, timelimit, delay);
+
+       if (!verbose && (pnn == 0)) {
+               tevent_add_timer(ev_ctx, db, timeval_current_ofs(1, 0), each_second, db);
+       }
+
+       test_store_records(db, ev_ctx);
+
+       if (verbose || (pnn == 0)) {
+               if (success != true) {
+                       printf("The test FAILED\n");
+                       ret = 2;
+               } else {
+                       printf("SUCCESS!\n");
+                       ret = 0;
+               }
+       }
+
+done:
+       talloc_free(mem_ctx);
+       return ret;
+}