summaryrefslogtreecommitdiffstats
path: root/storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc')
-rw-r--r--storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc518
1 files changed, 518 insertions, 0 deletions
diff --git a/storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc b/storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc
new file mode 100644
index 00000000..09ce645a
--- /dev/null
+++ b/storage/tokudb/PerconaFT/src/tests/recover-loader-test.cc
@@ -0,0 +1,518 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
+#ident "$Id$"
+/*======
+This file is part of PerconaFT.
+
+
+Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
+
+ PerconaFT is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License, version 2,
+ as published by the Free Software Foundation.
+
+ PerconaFT 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 PerconaFT. If not, see <http://www.gnu.org/licenses/>.
+
+----------------------------------------
+
+ PerconaFT is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ PerconaFT 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
+======= */
+
+#ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
+
+/* NOTE:
+ *
+ * Someday figure out a better way to verify inames that should not be
+ * in data dir after recovery. Currently, they are just hard-coded in
+ * the new_iname_str[] array. This will break when something changes,
+ * such as the xid of the transaction that creates the loader.
+ */
+
+
+/* Purpose is to verify that when a loader crashes:
+ * - there are no temp files remaining
+ * - the loader-generated iname file is not present
+ *
+ * In the event of a crash, the verification of no temp files and
+ * no loader-generated iname file is done after recovery.
+ *
+ * Mechanism:
+ * This test is derived from loader-cleanup-test, which was derived from loader-stress-test.
+ *
+ * The outline of the test is as follows:
+ * - use loader to create table
+ * - verify presence of temp files
+ * - crash
+ * - recover
+ * - verify absence of temp files
+ * - verify absence of unwanted iname files (new inames) - how?
+ *
+ *
+ */
+
+
+#include "test.h"
+#include "toku_pthread.h"
+#include <db.h>
+#include <sys/stat.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "ydb-internal.h"
+
+static const int envflags = DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE | DB_PRIVATE;
+
+#define NUM_DBS 5
+
+static bool do_test=false, do_recover=false;
+
+static DB_ENV *env;
+static int NUM_ROWS=50000000;
+static int COMPRESS=0;
+
+enum {MAX_NAME=128};
+enum {MAGIC=311};
+
+static DBT old_inames[NUM_DBS];
+static DBT new_inames[NUM_DBS];
+
+
+static char const * const new_iname_str[NUM_DBS] = {"qo_0000_35_c_L_0.tokudb",
+ "qo_0001_35_c_L_1.tokudb",
+ "qo_0002_35_c_L_2.tokudb",
+ "qo_0003_35_c_L_3.tokudb",
+ "qo_0004_35_c_L_4.tokudb"};
+
+static const char *loader_temp_prefix = "tokuld"; // 2536
+static int count_temp(char * dirname);
+static void get_inames(DBT* inames, DB** dbs);
+static int verify_file(char const * const dirname, char const * const filename);
+static int print_dir(char * dirname);
+
+// return number of temp files
+int
+count_temp(char * dirname) {
+ int n = 0;
+
+ DIR * dir = opendir(dirname);
+
+ struct dirent *ent;
+ while ((ent = readdir(dir)))
+ if ((ent->d_type == DT_REG || ent->d_type == DT_UNKNOWN) && strncmp(ent->d_name, loader_temp_prefix, 6) == 0)
+ n++;
+ closedir(dir);
+ return n;
+}
+
+// print contents of directory
+int
+print_dir(char * dirname) {
+ int n = 0;
+
+ DIR * dir = opendir(dirname);
+
+ struct dirent *ent;
+ while ((ent = readdir(dir))) {
+ if (ent->d_type == DT_REG || ent->d_type == DT_UNKNOWN) {
+ n++;
+ printf("File: %s\n", ent->d_name);
+ }
+ }
+ closedir(dir);
+ return n;
+}
+
+
+
+// return non-zero if file exists
+int
+verify_file(char const * const dirname, char const * const filename) {
+ int n = 0;
+ DIR * dir = opendir(dirname);
+
+ struct dirent *ent;
+ while ((ent=readdir(dir))) {
+ if ((ent->d_type==DT_REG || ent->d_type==DT_UNKNOWN) && strcmp(ent->d_name, filename)==0) {
+ n++;
+ }
+ }
+ closedir(dir);
+ return n;
+}
+
+void
+get_inames(DBT* inames, DB** dbs) {
+ int i;
+ for (i = 0; i < NUM_DBS; i++) {
+ DBT dname;
+ char * dname_str = dbs[i]->i->dname;
+ dbt_init(&dname, dname_str, strlen(dname_str)+1);
+ dbt_init(&(inames[i]), NULL, 0);
+ inames[i].flags |= DB_DBT_MALLOC;
+ int r = env->get_iname(env, &dname, &inames[i]);
+ CKERR(r);
+ char * iname_str = (char*) (inames[i].data);
+ // if (verbose)
+ printf("dname = %s, iname = %s\n", dname_str, iname_str);
+ }
+}
+
+
+#if 0
+void print_inames(DB** dbs);
+void
+print_inames(DB** dbs) {
+ int i;
+ for (i = 0; i < NUM_DBS; i++) {
+ DBT dname;
+ DBT iname;
+ char * dname_str = dbs[i]->i->dname;
+ dbt_init(&dname, dname_str, sizeof(dname_str));
+ dbt_init(&iname, NULL, 0);
+ iname.flags |= DB_DBT_MALLOC;
+ int r = env->get_iname(env, &dname, &iname);
+ CKERR(r);
+ char * iname_str = (char*)iname.data;
+ if (verbose) printf("dname = %s, iname = %s\n", dname_str, iname_str);
+ int n = verify_file(env->i->real_data_dir, iname_str);
+ assert(n == 1);
+ toku_free(iname.data);
+ }
+}
+#endif
+
+
+//
+// Functions to create unique key/value pairs, row generators, checkers, ... for each of NUM_DBS
+//
+
+// a is the bit-wise permute table. For DB[i], permute bits as described in a[i] using 'twiddle32'
+// inv is the inverse bit-wise permute of a[]. To get the original value from a twiddled value, twiddle32 (again) with inv[]
+int a[NUM_DBS][32];
+int inv[NUM_DBS][32];
+
+
+// rotate right and left functions
+#if 0
+static inline unsigned int rotr32(const unsigned int x, const unsigned int num) {
+ const unsigned int n = num % 32;
+ return (x >> n) | ( x << (32 - n));
+}
+#endif
+static inline unsigned int rotl32(const unsigned int x, const unsigned int num) {
+ const unsigned int n = num % 32;
+ return (x << n) | ( x >> (32 - n));
+}
+
+static void generate_permute_tables(void) {
+ int i, j, tmp;
+ for(int db=0;db<NUM_DBS;db++) {
+ for(i=0;i<32;i++) {
+ a[db][i] = i;
+ }
+ for(i=0;i<32;i++) {
+ j = random() % (i + 1);
+ tmp = a[db][j];
+ a[db][j] = a[db][i];
+ a[db][i] = tmp;
+ }
+// if(db < NUM_DBS){ printf("a[%d] = ", db); for(i=0;i<32;i++) { printf("%2d ", a[db][i]); } printf("\n");}
+ for(i=0;i<32;i++) {
+ inv[db][a[db][i]] = i;
+ }
+ }
+}
+
+// permute bits of x based on permute table bitmap
+static unsigned int twiddle32(unsigned int x, int db)
+{
+ unsigned int b = 0;
+ for(int i=0;i<32;i++) {
+ b |= (( x >> i ) & 1) << a[db][i];
+ }
+ return b;
+}
+
+// generate val from key, index
+static unsigned int generate_val(int key, int i) {
+ return rotl32((key + MAGIC), i);
+}
+
+// There is no handlerton in this test, so this function is a local replacement
+// for the handlerton's generate_row_for_put().
+static int put_multiple_generate(DB *dest_db, DB *src_db, DBT_ARRAY *dest_keys, DBT_ARRAY *dest_vals, const DBT *src_key, const DBT *src_val) {
+ toku_dbt_array_resize(dest_keys, 1);
+ toku_dbt_array_resize(dest_vals, 1);
+ DBT *dest_key = &dest_keys->dbts[0];
+ DBT *dest_val = &dest_vals->dbts[0];
+
+ (void) src_db;
+
+ uint32_t which = *(uint32_t*)dest_db->app_private;
+
+ if ( which == 0 ) {
+ if (dest_key->flags==DB_DBT_REALLOC) {
+ if (dest_key->data) toku_free(dest_key->data);
+ dest_key->flags = 0;
+ dest_key->ulen = 0;
+ }
+ if (dest_val->flags==DB_DBT_REALLOC) {
+ if (dest_val->data) toku_free(dest_val->data);
+ dest_val->flags = 0;
+ dest_val->ulen = 0;
+ }
+ dbt_init(dest_key, src_key->data, src_key->size);
+ dbt_init(dest_val, src_val->data, src_val->size);
+ }
+ else {
+ assert(dest_key->flags==DB_DBT_REALLOC);
+ if (dest_key->ulen < sizeof(unsigned int)) {
+ dest_key->data = toku_xrealloc(dest_key->data, sizeof(unsigned int));
+ dest_key->ulen = sizeof(unsigned int);
+ }
+ assert(dest_val->flags==DB_DBT_REALLOC);
+ if (dest_val->ulen < sizeof(unsigned int)) {
+ dest_val->data = toku_xrealloc(dest_val->data, sizeof(unsigned int));
+ dest_val->ulen = sizeof(unsigned int);
+ }
+ unsigned int *new_key = (unsigned int *)dest_key->data;
+ unsigned int *new_val = (unsigned int *)dest_val->data;
+
+ *new_key = twiddle32(*(unsigned int*)src_key->data, which);
+ *new_val = generate_val(*(unsigned int*)src_key->data, which);
+
+ dest_key->size = sizeof(unsigned int);
+ dest_val->size = sizeof(unsigned int);
+ //data is already set above
+ }
+
+// printf("dest_key.data = %d\n", *(int*)dest_key->data);
+// printf("dest_val.data = %d\n", *(int*)dest_val->data);
+
+ return 0;
+}
+
+
+static void *expect_poll_void = &expect_poll_void;
+
+static int poll_function (void *UU(extra), float UU(progress)) {
+ toku_hard_crash_on_purpose();
+ return -1;
+}
+
+static void test_loader(DB **dbs)
+{
+ int r;
+ DB_TXN *txn;
+ DB_LOADER *loader;
+ uint32_t db_flags[NUM_DBS];
+ uint32_t dbt_flags[NUM_DBS];
+ for(int i=0;i<NUM_DBS;i++) {
+ db_flags[i] = DB_NOOVERWRITE;
+ dbt_flags[i] = 0;
+ }
+ uint32_t loader_flags = COMPRESS; // set with -p option
+
+ int n = count_temp(env->i->real_data_dir);
+ assert(n == 0); // Must be no temp files before loader is run
+
+ if (verbose) printf("old inames:\n");
+ get_inames(old_inames, dbs);
+
+ // create and initialize loader
+ r = env->txn_begin(env, NULL, &txn, 0);
+ CKERR(r);
+ r = env->create_loader(env, txn, &loader, dbs[0], NUM_DBS, dbs, db_flags, dbt_flags, loader_flags);
+ CKERR(r);
+ r = loader->set_error_callback(loader, NULL, NULL);
+ CKERR(r);
+ r = loader->set_poll_function(loader, poll_function, expect_poll_void);
+ CKERR(r);
+
+ printf("COMPRESS = %d\n", COMPRESS);
+ if (verbose) printf("new inames:\n");
+ get_inames(new_inames, dbs);
+
+ // using loader->put, put values into DB
+ DBT key, val;
+ unsigned int k, v;
+ for(int i=1;i<=NUM_ROWS;i++) {
+ k = i;
+ v = generate_val(i, 0);
+ dbt_init(&key, &k, sizeof(unsigned int));
+ dbt_init(&val, &v, sizeof(unsigned int));
+ r = loader->put(loader, &key, &val);
+ CKERR(r);
+ if (verbose) { if((i%10000) == 0){printf("."); fflush(stdout);} }
+ }
+ if( verbose) {printf("\n"); fflush(stdout);}
+
+ printf("Data dir is %s\n", env->i->real_data_dir);
+ n = count_temp(env->i->real_data_dir);
+ printf("Num temp files = %d\n", n);
+ assert(n); // test is useless unless at least one temp file is created
+ if (verbose) {
+ printf("Contents of data dir:\n");
+ print_dir(env->i->real_data_dir);
+ }
+ printf("closing, will crash\n"); fflush(stdout);
+ r = loader->close(loader);
+ printf("Should never return from loader->close()\n"); fflush(stdout);
+ assert(0);
+
+}
+
+
+static void run_test(void)
+{
+ int r;
+
+ toku_os_recursive_delete(TOKU_TEST_FILENAME);
+ r = toku_os_mkdir(TOKU_TEST_FILENAME, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
+
+ r = db_env_create(&env, 0); CKERR(r);
+ r = env->set_default_bt_compare(env, uint_dbt_cmp); CKERR(r);
+ r = env->set_generate_row_callback_for_put(env, put_multiple_generate);
+ CKERR(r);
+// int envflags = DB_INIT_LOCK | DB_INIT_MPOOL | DB_INIT_TXN | DB_CREATE | DB_PRIVATE;
+ r = env->open(env, TOKU_TEST_FILENAME, envflags, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
+ env->set_errfile(env, stderr);
+ //Disable auto-checkpointing
+ r = env->checkpointing_set_period(env, 0); CKERR(r);
+
+ DBT desc;
+ dbt_init(&desc, "foo", sizeof("foo"));
+ char name[MAX_NAME*2];
+
+ DB **dbs = (DB**)toku_malloc(sizeof(DB*) * NUM_DBS);
+ assert(dbs != NULL);
+ int idx[NUM_DBS];
+ for(int i=0;i<NUM_DBS;i++) {
+ idx[i] = i;
+ r = db_create(&dbs[i], env, 0); CKERR(r);
+ dbs[i]->app_private = &idx[i];
+ snprintf(name, sizeof(name), "db_%04x", i);
+ r = dbs[i]->open(dbs[i], NULL, name, NULL, DB_BTREE, DB_CREATE, 0666); CKERR(r);
+ IN_TXN_COMMIT(env, NULL, txn_desc, 0, {
+ { int chk_r = dbs[i]->change_descriptor(dbs[i], txn_desc, &desc, 0); CKERR(chk_r); }
+ });
+ }
+
+ generate_permute_tables();
+
+ test_loader(dbs);
+ printf("Should never return from test_loader\n"); fflush(stdout);
+ assert(0);
+}
+
+// ------------ infrastructure ----------
+static void do_args(int argc, char * const argv[]);
+
+
+
+static void run_recover (void) {
+ int i;
+
+ // Recovery starts from oldest_living_txn, which is older than any inserts done in run_test,
+ // so recovery always runs over the entire log.
+
+ // run recovery
+ int r = db_env_create(&env, 0); CKERR(r);
+ r = env->open(env, TOKU_TEST_FILENAME, envflags + DB_RECOVER, S_IRWXU+S_IRWXG+S_IRWXO); CKERR(r);
+
+ // now verify contents of data_dir, should be no temp files, no loader-created iname files
+ if (verbose)
+ print_dir(env->i->real_data_dir);
+
+ int n = count_temp(env->i->real_data_dir);
+ printf("Num temp files = %d\n", n);
+ assert(n==0); // There should be no temp files remaining after recovery
+
+ for (i = 0; i < NUM_DBS; i++) {
+ char const * const iname = new_iname_str[i];
+ r = verify_file(env->i->real_data_dir, iname);
+ if (r) {
+ printf("File %s exists, but it should not\n", iname);
+ }
+ assert(r == 0);
+ if (verbose)
+ printf("File has been properly deleted: %s\n", iname);
+ }
+
+ r = env->close(env, 0); CKERR(r);
+ exit(0);
+
+}
+
+int test_main(int argc, char * const *argv) {
+ do_args(argc, argv);
+
+ if (do_test) {
+ printf("\n\n perform test, crash\n");
+ fflush(stdout);
+ run_test();
+ }
+ else if (do_recover) {
+ printf("\n\n perform recovery\n");
+ run_recover();
+ }
+ else {
+ printf("\n\n BOGUS!\n");
+ assert(0);
+ }
+
+ return 0;
+}
+
+static void do_args(int argc, char * const argv[]) {
+ int resultcode;
+ char *cmd = argv[0];
+ argc--; argv++;
+ while (argc>0) {
+ if (strcmp(argv[0], "-v")==0) {
+ verbose++;
+ } else if (strcmp(argv[0],"-q")==0) {
+ verbose--;
+ if (verbose<0) verbose=0;
+ } else if (strcmp(argv[0], "-h")==0) {
+ resultcode=0;
+ do_usage:
+ fprintf(stderr, "Usage: -h -c -d <num_dbs> -r <num_rows>\n%s\n", cmd);
+ exit(resultcode);
+ } else if (strcmp(argv[0], "-r")==0) {
+ argc--; argv++;
+ NUM_ROWS = atoi(argv[0]);
+ } else if (strcmp(argv[0], "-z")==0) {
+ COMPRESS = LOADER_COMPRESS_INTERMEDIATES;
+ printf("Compressing\n");
+ } else if (strcmp(argv[0], "--test")==0) {
+ do_test=true;
+ } else if (strcmp(argv[0], "--recover") == 0) {
+ do_recover=true;
+
+ } else {
+ fprintf(stderr, "Unknown arg: %s\n", argv[0]);
+ resultcode=1;
+ goto do_usage;
+ }
+ argc--;
+ argv++;
+ }
+}