libctdb: test: add database save and restore
authorRusty Russell <rusty@rustcorp.com.au>
Mon, 21 Jun 2010 05:30:46 +0000 (15:00 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Mon, 21 Jun 2010 05:30:46 +0000 (15:00 +0930)
Once we do operations which alter the TDBs, we need to restore them to
pristine state after a failed child dies.

The method used here is a terrible hack: it should at least do a
tdb_lockall() on the database before blatting it.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
(This used to be ctdb commit d48ec16bd2b4932442d95fc43bea52baa0425501)

ctdb/libctdb/test/ctdb-test.c
ctdb/libctdb/test/ctdb-test.h
ctdb/libctdb/test/databases.c [new file with mode: 0644]
ctdb/libctdb/test/failtest.c
ctdb/libctdb/test/tui.c

index 1587a4f8c3082ad488cf058f85f248b3c6f81020..c1dc4b8bab555aa76e015371378d251e2beda13f 100644 (file)
@@ -447,6 +447,7 @@ int main(int argc, char *argv[])
                     "unfulfilled remaining. / "
                     "Testing blossoms fail.");
        check_allocations();
+       check_databases();
        dump_failinfo();
 
        return EXIT_SUCCESS;
index 68ab2ea8f39852fcde8ee4f0d7b7c2c4822703e4..ba8fbe147a02c10dded8e9d84cfc5f08f77e30a1 100644 (file)
@@ -16,4 +16,11 @@ struct ctdb_connection *get_ctdb(void);
 /* Talloc bytes from an fd until EOF.  Nul terminate. */
 void *grab_fd(int fd, size_t *size);
 
+/* Check the databases are still ok. */
+void check_databases(void);
+
+/* Save and restore databases, in case children do damage. */
+void *save_databases(void);
+void restore_databases(void *);
+
 #endif /* __HAVE_CTDB_TEST_H */
diff --git a/ctdb/libctdb/test/databases.c b/ctdb/libctdb/test/databases.c
new file mode 100644 (file)
index 0000000..b0b9bbc
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+   database code for libctdb
+
+   Copyright (C) Rusty Russell 2010
+
+   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 <unistd.h>
+#include <err.h>
+#include <talloc.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include "utils.h"
+#include "log.h"
+#include "ctdb-test.h"
+#include <tdb.h>
+#include <ctdb_protocol.h>
+
+/* FIXME */
+#define DB_PATH "/tmp/ctdbd-test/dbs/"
+
+static int check_header(TDB_DATA key, TDB_DATA data, void *unused)
+{
+       struct ctdb_ltdb_header *hdr = (void *)data.dptr;
+       if (data.dsize < sizeof(*hdr)) {
+               log_line(LOG_ALWAYS, "tdb entry '%.*s' is truncated",
+                        key.dsize, key.dptr);
+               return -1;
+       }
+       /* Currently a single-node cluster. */
+       if (hdr->dmaster != 0) {
+               log_line(LOG_ALWAYS, "tdb entry '%.*s' dmaster %u",
+                        key.dsize, key.dptr, hdr->dmaster);
+               return -1;
+       }
+       /* Currently a single-node cluster. */
+       if (hdr->laccessor != 0) {
+               log_line(LOG_ALWAYS, "tdb entry '%.*s' laccessor %u",
+                        key.dsize, key.dptr, hdr->laccessor);
+               return -1;
+       }
+       return 0;
+}
+
+static void check_database(const char *name)
+{
+       struct tdb_context *tdb = tdb_open(name, 0, TDB_DEFAULT, O_RDWR, 0);
+       if (!tdb)
+               err(1, "Opening tdb %s", name);
+
+       if (tdb_check(tdb, check_header, NULL) != 0) {
+               log_line(LOG_ALWAYS, "tdb %s is corrupt", name);
+               exit(EXIT_FAILURE);
+       }
+       tdb_close(tdb);
+}
+
+void check_databases(void)
+{
+       struct dirent *ent;
+       DIR *d = opendir(DB_PATH);
+       if (!d)
+               err(1, "Reading directory %s", DB_PATH);
+
+       while ((ent = readdir(d)) != NULL) {
+               if (strends(ent->d_name, ".tdb.0")) {
+                       char *fullpath = talloc_asprintf(NULL, "%s/%s",
+                                                        DB_PATH, ent->d_name);
+                       check_database(fullpath);
+                       talloc_free(fullpath);
+               }
+       }
+       closedir(d);
+}
+
+/* FIXME: We assume we don't need locks here.  Not a solid assumption! */
+void *save_databases(void)
+{
+       struct tdb_context *tdb = tdb_open(NULL, 0, TDB_INTERNAL, 0, 0);
+       struct dirent *ent;
+       DIR *d = opendir(DB_PATH);
+       if (!d)
+               err(1, "Reading directory %s", DB_PATH);
+
+       while ((ent = readdir(d)) != NULL) {
+               if (strends(ent->d_name, ".tdb.0")) {
+                       TDB_DATA data, key;
+                       int fd;
+                       char *fullpath = talloc_asprintf(NULL, "%s/%s",
+                                                        DB_PATH, ent->d_name);
+                       fd = open(fullpath, O_RDONLY);
+                       if (fd < 0)
+                               err(1, "Saving tdb %s", fullpath);
+                       data.dptr = grab_fd(fd, &data.dsize);
+                       key.dptr = (void *)fullpath;
+                       key.dsize = strlen(fullpath) + 1;
+                       tdb_store(tdb, key, data, TDB_INSERT);
+                       talloc_free(fullpath);
+                       close(fd);
+               }
+       }
+       closedir(d);
+       return tdb;
+}
+
+void restore_databases(void *_tdb)
+{
+       struct tdb_context *tdb = _tdb;
+       TDB_DATA key, data;
+
+       for (key = tdb_firstkey(tdb); key.dptr; key = tdb_nextkey(tdb, key)) {
+               int fd = open((char *)key.dptr, O_WRONLY);
+               if (fd < 0)
+                       err(1, "Restoring tdb %s", (char *)key.dptr);
+               data = tdb_fetch(tdb, key);
+               write(fd, data.dptr, data.dsize);
+               free(data.dptr);
+               close(fd);
+       }
+       tdb_close(tdb);
+}
index 3e80cb1f93964464374d4a08e7549a1a8e890676..c43047d31f0b128594b4938cfb987a3bc8589073 100644 (file)
@@ -261,6 +261,7 @@ bool should_i_fail(const char *func, const char *caller)
        size_t log_size;
        char *log;
        char *location = make_location(func, caller);
+       void *databases;
 
        if (failpath)
                return do_failpath(location);
@@ -286,6 +287,8 @@ bool should_i_fail(const char *func, const char *caller)
        if (pipe(pfd) != 0)
                err(1, "pipe failed for failtest!");
 
+       databases = save_databases();
+
        fflush(stdout);
        child = fork();
        if (child == -1)
@@ -319,10 +322,11 @@ bool should_i_fail(const char *func, const char *caller)
        if (WIFEXITED(status) && (WEXITSTATUS(status) == EXIT_SUCCESS
                                  || WEXITSTATUS(status) == EXIT_SCRIPTFAIL)) {
                talloc_free(log);
+               restore_databases(databases);
                return false;
        }
 
-       /* Reproduce child's path */
+       /* Reproduce child's path: leave databases for post-mortem. */
        dec->failed = true;
 
        log_line(LOG_ALWAYS, "Child %s %i on failure path: %s",
index d411e6f0b41f9cd52df95b64c61216c3282f001c..1667de7846cde4085a8f13be3b67c9041e9a7401 100644 (file)
@@ -196,6 +196,7 @@ void script_fail(const char *fmt, ...)
        talloc_free(str);
 
        check_allocations();
+       check_databases();
        exit(EXIT_SCRIPTFAIL);
 }