diff --git a/CMakeLists.txt b/CMakeLists.txt index a3b94062c06..005d89fe53d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ include(cmake/configs/base.cmake) include(cmake/third_party/aws_sdk.cmake) include(cmake/third_party/gcp_sdk.cmake) include(cmake/third_party/gperftools.cmake) +include(cmake/third_party/lazyfs.cmake) include(cmake/third_party/lz4.cmake) include(cmake/third_party/memkind.cmake) include(cmake/third_party/snappy.cmake) diff --git a/cmake/configs/base.cmake b/cmake/configs/base.cmake index 9ffca1af49d..4b367c1639c 100644 --- a/cmake/configs/base.cmake +++ b/cmake/configs/base.cmake @@ -281,6 +281,12 @@ config_bool( DEFAULT ON ) +config_bool( + ENABLE_LAZYFS + "Build LazyFS for testing" + DEFAULT OFF +) + config_bool( ENABLE_S3 "Build the S3 storage extension" diff --git a/cmake/third_party/lazyfs.cmake b/cmake/third_party/lazyfs.cmake new file mode 100644 index 00000000000..0cd9dc25b30 --- /dev/null +++ b/cmake/third_party/lazyfs.cmake @@ -0,0 +1,25 @@ +include(ExternalProject) +include(GNUInstallDirs) +include(${CMAKE_SOURCE_DIR}/cmake/helpers.cmake) + +# Skip the LazyFS build step if it is not enabled. +if(NOT ENABLE_LAZYFS) + return() +endif() + +if(TARGET lazyfs) + # Avoid redefining the imported library, given this file can be used as an include. + return() +endif() + +# Download and install the project into the build directory. +ExternalProject_Add(lazyfs + PREFIX lazyfs + GIT_CONFIG advice.detachedHead=false + GIT_REPOSITORY https://github.com/dsrhaslab/lazyfs.git + GIT_TAG b0383127 + CONFIGURE_COMMAND "" + BUILD_IN_SOURCE TRUE + BUILD_COMMAND cd libs/libpcache && ./build.sh COMMAND cd ../../lazyfs && ./build.sh + INSTALL_COMMAND "" +) diff --git a/dist/s_string.ok b/dist/s_string.ok index 913c9d9c928..4696ac4a591 100644 --- a/dist/s_string.ok +++ b/dist/s_string.ok @@ -37,7 +37,7 @@ BBBBBBBBBB BBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBB -BCmvxz +BClmvxz BDB BDB's BDmpvXx @@ -87,6 +87,8 @@ CURSORs CURSTD CallsCustDate Castagnoli +CcLlsvz +Cclmpv CentOS Checkpointing Checksum @@ -95,7 +97,6 @@ CityHash CloseHandle CmP CmPTh -Cmvz Collet Comparator Compressibility @@ -276,6 +277,8 @@ LTE LWN LZ LZO +LazyFS +LazyFS's LeafGreen LevelDB Levyx @@ -351,7 +354,9 @@ Opcode Optane Outfmt PARAM +PDEATHSIG PFX +PID POS POSIX PPC @@ -457,6 +462,7 @@ Stoica StoreLoad StoreStore Su +Subdirectory Syscall TAILQ TCMalloc @@ -501,6 +507,7 @@ UnlockFile Unmap UnmapViewOfFile Unmarshall +Unmount Unordered Uryyb VALGRIND @@ -919,6 +926,7 @@ fgetln fgets fh fhandle +fifo fileX filefrag filehandle @@ -963,6 +971,7 @@ fuzzer fuzzutil fvisibility fwrite +gb gc gcc gdb @@ -1104,6 +1113,7 @@ kvs lang las latencies +lazyfs lbrace lbracket ld @@ -1124,6 +1134,7 @@ libsodium libtool libwiredtiger linkers +linux liveness llll llu @@ -1322,6 +1333,7 @@ pclose pcpu perf pfx +pid pluggable pmem poc @@ -1353,6 +1365,7 @@ primary's printf printlog priv +proc prog progname programmatically @@ -1499,6 +1512,8 @@ strtouq struct structs su +subdir +subdirectories subdirectory subget subgetraw @@ -1507,6 +1522,7 @@ subinit sublicense subone suboptimal +subprocess subsystem's subtest subtree @@ -1552,6 +1568,7 @@ tokstart toktype tolower tombstoned +toml toolchain toplevel totalsec @@ -1585,6 +1602,7 @@ uS ui uint uintmax +umount unbare unbuffered uncacheable @@ -1612,6 +1630,7 @@ unmarshall unmarshalled unmerged unmodify +unmount unordered unpackv unpadded diff --git a/test/csuite/CMakeLists.txt b/test/csuite/CMakeLists.txt index 6dc7c3ce15f..643b3252010 100644 --- a/test/csuite/CMakeLists.txt +++ b/test/csuite/CMakeLists.txt @@ -22,6 +22,15 @@ define_c_test( DEPENDS "WT_POSIX" ) +define_c_test( + TARGET test_random_abort_lazyfs + SOURCES random_abort/main.c + DIR_NAME random_abort + EXEC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/random_abort/smoke_lazyfs.sh + ARGUMENTS $ + DEPENDS "WT_POSIX" "ENABLE_LAZYFS" +) + define_c_test( TARGET test_random_directio SOURCES @@ -52,6 +61,15 @@ define_c_test( DEPENDS "WT_POSIX" ) +define_c_test( + TARGET test_schema_abort_lazyfs + SOURCES schema_abort/main.c + DIR_NAME schema_abort + EXEC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/schema_abort/smoke_lazyfs.sh + ARGUMENTS $ + DEPENDS "WT_POSIX" "ENABLE_LAZYFS" +) + define_c_test( TARGET test_scope SOURCES scope/main.c @@ -91,6 +109,15 @@ define_c_test( DEPENDS "WT_POSIX" ) +define_c_test( + TARGET test_timestamp_abort_lazyfs + SOURCES timestamp_abort/main.c + DIR_NAME timestamp_abort + EXEC_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/timestamp_abort/smoke_lazyfs.sh + ARGUMENTS -b $ + DEPENDS "WT_POSIX" "ENABLE_LAZYFS" +) + define_c_test( TARGET test_truncated_log SOURCES truncated_log/main.c diff --git a/test/csuite/README b/test/csuite/README index c1c42372ec3..6675848763e 100644 --- a/test/csuite/README +++ b/test/csuite/README @@ -10,3 +10,11 @@ crafted and put into the Evergreen configuration file. We have a utility program 'test/evergreen/evg_cfg.py' to help with identifying and auto-generating the Evergreen configuration for new or missing csuite tests. The program checking has been bound into developer workflow through 'dist/s_evergreen'. + +Some csuite tests that check WiredTiger's failure recovery have LazyFS variants, +which use LazyFS (a FUSE file system) to emulate losing the contents of the buffer +cache, such as to test the system behavior in face of sudden reboots or power +failures. To use these tests, you need to have FUSE 3 installed together with the +development libraries, have 'user_allow_other' enabled in /etc/fuse.conf, and you +need to also enable LazyFS in cmake via the '-DENABLE_LAZYFS=1' command-line +option. The tests are not currently available in Evergreen and must be run manually. diff --git a/test/csuite/random_abort/main.c b/test/csuite/random_abort/main.c index 819523413ae..e5107448c99 100644 --- a/test/csuite/random_abort/main.c +++ b/test/csuite/random_abort/main.c @@ -31,7 +31,7 @@ #include #include -static char home[1024]; /* Program working dir */ +static char home[PATH_MAX]; /* Program working dir */ /* * These two names for the URI and file system must be maintained in tandem. @@ -41,6 +41,7 @@ static const char *const uri = "table:main"; static bool compaction; static bool compat; static bool inmem; +static bool use_lazyfs; #define MAX_TH 12 #define MIN_TH 5 @@ -52,9 +53,9 @@ static bool inmem; #define OP_TYPE_MODIFY 2 #define MAX_NUM_OPS 3 -#define DELETE_RECORDS_FILE "delete-records-%" PRIu32 -#define INSERT_RECORDS_FILE "insert-records-%" PRIu32 -#define MODIFY_RECORDS_FILE "modify-records-%" PRIu32 +#define DELETE_RECORDS_FILE RECORDS_DIR DIR_DELIM_STR "delete-records-%" PRIu32 +#define INSERT_RECORDS_FILE RECORDS_DIR DIR_DELIM_STR "insert-records-%" PRIu32 +#define MODIFY_RECORDS_FILE RECORDS_DIR DIR_DELIM_STR "modify-records-%" PRIu32 #define DELETE_RECORD_FILE_ID 0 #define INSERT_RECORD_FILE_ID 1 @@ -67,6 +68,10 @@ static bool inmem; "create,log=(file_max=10M,enabled)," \ "transaction_sync=(enabled,method=none),statistics=(all),statistics_log=(json,on_close,wait=" \ "1)" +#define ENV_CONFIG_TXNSYNC_FSYNC \ + "create,log=(file_max=10M,enabled)," \ + "transaction_sync=(enabled,method=fsync),statistics=(all),statistics_log=(json,on_close,wait=" \ + "1)" /* * A minimum width of 10, along with zero filling, means that all the keys sort according to their @@ -95,7 +100,7 @@ static void usage(void) WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn)); static void usage(void) { - fprintf(stderr, "usage: %s [-h dir] [-T threads]\n", progname); + fprintf(stderr, "usage: %s [-h dir] [-T threads] [-Cclmpv]\n", progname); exit(EXIT_FAILURE); } @@ -321,12 +326,14 @@ fill_db(uint32_t nth) testutil_die(errno, "Child chdir: %s", home); if (inmem) strcpy(envconf, ENV_CONFIG_DEF); + else if (use_lazyfs) + strcpy(envconf, ENV_CONFIG_TXNSYNC_FSYNC); else strcpy(envconf, ENV_CONFIG_TXNSYNC); if (compat) strcat(envconf, TESTUTIL_ENV_CONFIG_COMPAT); - testutil_check(wiredtiger_open(NULL, NULL, envconf, &conn)); + testutil_check(wiredtiger_open(WT_HOME_DIR, NULL, envconf, &conn)); testutil_check(conn->open_session(conn, NULL, NULL, &session)); testutil_check(session->create(session, col_uri, "key_format=r,value_format=u")); testutil_check(session->create(session, uri, "key_format=S,value_format=u")); @@ -394,7 +401,7 @@ recover_and_verify(uint32_t nthreads) bool columnar_table, fatal; printf("Open database, run recovery and verify content\n"); - testutil_check(wiredtiger_open(NULL, NULL, TESTUTIL_ENV_CONFIG_REC, &conn)); + testutil_check(wiredtiger_open(WT_HOME_DIR, NULL, TESTUTIL_ENV_CONFIG_REC, &conn)); testutil_check(conn->open_session(conn, NULL, NULL, &session)); testutil_check(session->open_cursor(session, col_uri, NULL, NULL, &col_cursor)); testutil_check(session->open_cursor(session, uri, NULL, NULL, &row_cursor)); @@ -629,25 +636,28 @@ main(int argc, char *argv[]) { struct sigaction sa; struct stat sb; + WT_LAZY_FS lazyfs; WT_RAND_STATE rnd; pid_t pid; uint32_t i, j, nth, timeout; - int ch, status, ret; - char buf[1024], fname[MAX_RECORD_FILES][64]; + int ch, ret, status; + char buf[PATH_MAX], fname[MAX_RECORD_FILES][64]; + char cwd_start[PATH_MAX]; /* The working directory when we started */ const char *working_dir; bool preserve, rand_th, rand_time, verify_only; (void)testutil_set_progname(argv); compaction = compat = inmem = false; + use_lazyfs = lazyfs_is_implicitly_enabled(); nth = MIN_TH; preserve = false; rand_th = rand_time = true; timeout = MIN_TIME; verify_only = false; - working_dir = "WT_TEST.random-abort"; + working_dir = use_lazyfs ? "WT_TEST.random-abort-lazyfs" : "WT_TEST.random-abort"; - while ((ch = __wt_getopt(progname, argc, argv, "Cch:mpT:t:v")) != EOF) + while ((ch = __wt_getopt(progname, argc, argv, "Cch:lmpT:t:v")) != EOF) switch (ch) { case 'C': compat = true; @@ -658,6 +668,9 @@ main(int argc, char *argv[]) case 'h': working_dir = __wt_optarg; break; + case 'l': + use_lazyfs = true; + break; case 'm': inmem = true; break; @@ -683,6 +696,7 @@ main(int argc, char *argv[]) usage(); testutil_work_dir_from_path(home, sizeof(home), working_dir); + /* * If the user wants to verify they need to tell us how many threads there were so we can find * the old record files. @@ -691,9 +705,26 @@ main(int argc, char *argv[]) fprintf(stderr, "Verify option requires specifying number of threads\n"); exit(EXIT_FAILURE); } + + /* Remember the current working directory. */ + testutil_assert_errno(getcwd(cwd_start, sizeof(cwd_start)) != NULL); + + /* Create the database, run the test, and fail. */ if (!verify_only) { + /* Create the test's home directory. */ testutil_make_work_dir(home); + /* Set up the test subdirectories. */ + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, RECORDS_DIR)); + testutil_make_work_dir(buf); + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, WT_HOME_DIR)); + testutil_make_work_dir(buf); + + /* Set up LazyFS. */ + if (use_lazyfs) + testutil_lazyfs_setup(&lazyfs, home); + + /* Set up the rest of the test. */ __wt_random_init_seed(NULL, &rnd); if (rand_time) { timeout = __wt_random(&rnd) % MAX_TIME; @@ -708,9 +739,10 @@ main(int argc, char *argv[]) printf("Parent: Compatibility %s in-mem log %s\n", compat ? "true" : "false", inmem ? "true" : "false"); printf("Parent: Create %" PRIu32 " threads; sleep %" PRIu32 " seconds\n", nth, timeout); - printf("CONFIG: %s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 "\n", progname, - compat ? " -C" : "", compaction ? " -c" : "", inmem ? " -m" : "", working_dir, nth, - timeout); + printf("CONFIG: %s%s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 "\n", progname, + compat ? " -C" : "", compaction ? " -c" : "", use_lazyfs ? " -l" : "", inmem ? " -m" : "", + working_dir, nth, timeout); + /* * Fork a child to insert as many items. We will then randomly kill the child, run recovery * and make sure all items we wrote exist after recovery runs. @@ -765,6 +797,7 @@ main(int argc, char *argv[]) testutil_assert_errno(kill(pid, SIGKILL) == 0); testutil_assert_errno(waitpid(pid, &status, 0) != -1); } + /* * !!! If we wanted to take a copy of the directory before recovery, * this is the place to do it. @@ -775,16 +808,38 @@ main(int argc, char *argv[]) /* Copy the data to a separate folder for debugging purpose. */ testutil_copy_data(home); + /* + * Clear the cache, if we are using LazyFS. Do this after we save the data for debugging + * purposes, so that we can see what we might have lost. If we are using LazyFS, the underlying + * directory shows the state that we'd get after we clear the cache. + */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_clear_cache(&lazyfs); + /* * Recover the database and verify whether all the records from all threads are present or not? */ ret = recover_and_verify(nth); - if (ret == EXIT_SUCCESS && !preserve) { + + /* + * Clean up. + */ + + /* Clean up the test directory. */ + if (ret == EXIT_SUCCESS && !preserve) testutil_clean_test_artifacts(home); - /* At this point $PATH is inside `home`, which we intend to delete. cd to the parent dir. */ - if (chdir("../") != 0) - testutil_die(errno, "root chdir: %s", home); + + /* At this point, we are inside `home`, which we intend to delete. cd to the parent dir. */ + if (chdir(cwd_start) != 0) + testutil_die(errno, "root chdir: %s", home); + + /* Clean up LazyFS. */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_cleanup(&lazyfs); + + /* Delete the work directory. */ + if (ret == EXIT_SUCCESS && !preserve) testutil_clean_work_dir(home); - } - return ret; + + return (ret); } diff --git a/test/csuite/random_abort/smoke_lazyfs.sh b/test/csuite/random_abort/smoke_lazyfs.sh new file mode 100755 index 00000000000..a6c004060b5 --- /dev/null +++ b/test/csuite/random_abort/smoke_lazyfs.sh @@ -0,0 +1,21 @@ +#! /bin/sh + +set -e + +# Smoke-test random-abort as part of running "make check". + +if [ -n "$1" ] +then + # If the test binary is passed in manually. + test_bin=$1 +else + # If $binary_dir isn't set, default to using the build directory + # this script resides under. Our CMake build will sync a copy of this + # script to the build directory. Note this assumes we are executing a + # copy of the script that lives under the build directory. Otherwise + # passing the binary path is required. + binary_dir=${binary_dir:-`dirname $0`} + test_bin=$binary_dir/test_random_abort_lazyfs +fi +$TEST_WRAPPER $test_bin -l -t 30 -T 5 +$TEST_WRAPPER $test_bin -l -C -t 30 -T 5 diff --git a/test/csuite/schema_abort/main.c b/test/csuite/schema_abort/main.c index 9634fe589e7..256e64ba569 100644 --- a/test/csuite/schema_abort/main.c +++ b/test/csuite/schema_abort/main.c @@ -65,7 +65,7 @@ static char home[1024]; /* Program working dir */ #define MIN_TIME 10 #define PREPARE_FREQ 5 #define PREPARE_YIELD (PREPARE_FREQ * 10) -#define RECORDS_FILE "records-%" PRIu32 +#define RECORDS_FILE RECORDS_DIR DIR_DELIM_STR "records-%" PRIu32 #define STABLE_PERIOD 100 static const char *const uri = "table:wt"; @@ -75,7 +75,7 @@ static const char *const uri_collection = "table:collection"; static const char *const ckpt_file = "checkpoint_done"; -static bool use_columns, use_ts, use_txn; +static bool use_columns, use_lazyfs, use_ts, use_txn; static volatile bool stable_set; static uint32_t nth; /* Number of threads. */ @@ -104,6 +104,10 @@ static TEST_OPTS *opts, _opts; ENV_CONFIG_DEF \ ",transaction_sync=(enabled,method=none)" +#define ENV_CONFIG_TXNSYNC_FSYNC \ + ENV_CONFIG_DEF \ + ",transaction_sync=(enabled,method=fsync)" + /* * A minimum width of 10, along with zero filling, means that all the keys sort according to their * integer value, making each thread's key space distinct. For column-store we just use the integer @@ -148,7 +152,7 @@ static void usage(void) WT_GCC_FUNC_DECL_ATTRIBUTE((noreturn)); static void usage(void) { - fprintf(stderr, "usage: %s [-h dir] [-T threads] [-t time] [-BCmvxz]\n", progname); + fprintf(stderr, "usage: %s [-h dir] [-T threads] [-t time] [-BClmvxz]\n", progname); exit(EXIT_FAILURE); } @@ -889,11 +893,13 @@ run_workload(void) testutil_die(errno, "Child chdir: %s", home); if (opts->inmem) strcpy(envconf, ENV_CONFIG_DEF); + else if (use_lazyfs) + strcpy(envconf, ENV_CONFIG_TXNSYNC_FSYNC); else strcpy(envconf, ENV_CONFIG_TXNSYNC); /* Open WiredTiger without recovery. */ - testutil_wiredtiger_open(opts, NULL, envconf, &event_handler, &conn, false, false); + testutil_wiredtiger_open(opts, WT_HOME_DIR, envconf, &event_handler, &conn, false, false); testutil_check(conn->open_session(conn, NULL, NULL, &session)); @@ -1014,20 +1020,23 @@ main(int argc, char *argv[]) WT_CONNECTION *conn; WT_CURSOR *cur_coll, *cur_local, *cur_oplog; WT_DECL_RET; + WT_LAZY_FS lazyfs; WT_SESSION *session; pid_t pid; uint64_t absent_coll, absent_local, absent_oplog, count, key, last_key; uint64_t stable_fp, stable_val; uint32_t i, rand_value, timeout; int ch, status; - char buf[1024], statname[1024]; - char fname[64], kname[64]; + char buf[PATH_MAX], fname[64], kname[64], statname[1024]; + char cwd_start[PATH_MAX]; /* The working directory when we started */ bool fatal, rand_th, rand_time, verify_only; (void)testutil_set_progname(argv); opts = &_opts; memset(opts, 0, sizeof(*opts)); + + use_lazyfs = lazyfs_is_implicitly_enabled(); use_ts = true; /* * Setting this to false forces us to use internal library code. Allow an override but default @@ -1041,12 +1050,15 @@ main(int argc, char *argv[]) testutil_parse_begin_opt(argc, argv, "b:CmPTh:pv", opts); - while ((ch = __wt_getopt(progname, argc, argv, "Cch:mpP:T:t:vxz")) != EOF) + while ((ch = __wt_getopt(progname, argc, argv, "Cch:lmpP:T:t:vxz")) != EOF) switch (ch) { case 'c': /* Variable-length columns only; fixed would require considerable changes */ use_columns = true; break; + case 'l': + use_lazyfs = true; + break; case 'T': rand_th = false; nth = (uint32_t)atoi(__wt_optarg); @@ -1089,8 +1101,25 @@ main(int argc, char *argv[]) fprintf(stderr, "Verify option requires specifying number of threads\n"); exit(EXIT_FAILURE); } + + /* Remember the current working directory. */ + testutil_assert_errno(getcwd(cwd_start, sizeof(cwd_start)) != NULL); + + /* Create the database, run the test, and fail. */ if (!verify_only) { + /* Create the test's home directory. */ testutil_make_work_dir(home); + + /* Set up the test subdirectories. */ + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, RECORDS_DIR)); + testutil_make_work_dir(buf); + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, WT_HOME_DIR)); + testutil_make_work_dir(buf); + + /* Set up LazyFS. */ + if (use_lazyfs) + testutil_lazyfs_setup(&lazyfs, home); + if (opts->tiered_storage) { testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/bucket", home)); testutil_make_work_dir(buf); @@ -1124,8 +1153,9 @@ main(int argc, char *argv[]) opts->compat ? "true" : "false", opts->inmem ? "true" : "false", use_ts ? "true" : "false", opts->tiered_storage ? "true" : "false"); printf("Parent: Create %" PRIu32 " threads; sleep %" PRIu32 " seconds\n", nth, timeout); - printf("CONFIG: %s%s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 " " TESTUTIL_SEED_FORMAT "\n", - progname, opts->compat ? " -C" : "", opts->inmem ? " -m" : "", + printf("CONFIG: %s%s%s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 " " TESTUTIL_SEED_FORMAT + "\n", + progname, opts->compat ? " -C" : "", use_lazyfs ? " -l" : "", opts->inmem ? " -m" : "", opts->tiered_storage ? " -PT" : "", !use_ts ? " -z" : "", opts->home, nth, timeout, opts->data_seed, opts->extra_seed); /* @@ -1176,12 +1206,21 @@ main(int argc, char *argv[]) /* Copy the data to a separate folder for debugging purpose. */ testutil_copy_data(home); + + /* + * Clear the cache, if we are using LazyFS. Do this after we save the data for debugging + * purposes, so that we can see what we might have lost. If we are using LazyFS, the underlying + * directory shows the state that we'd get after we clear the cache. + */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_clear_cache(&lazyfs); + printf("Open database, run recovery and verify content\n"); /* * Open the connection which forces recovery to be run. */ - testutil_wiredtiger_open(opts, NULL, NULL, &event_handler, &conn, true, false); + testutil_wiredtiger_open(opts, WT_HOME_DIR, NULL, &event_handler, &conn, true, false); testutil_check(conn->open_session(conn, NULL, NULL, &session)); /* @@ -1350,16 +1389,33 @@ main(int argc, char *argv[]) printf("OPLOG: %" PRIu64 " record(s) absent from %" PRIu64 "\n", absent_oplog, count); fatal = true; } - if (fatal) - return (EXIT_FAILURE); - printf("%" PRIu64 " records verified\n", count); - if (!opts->preserve) { + if (fatal) { + ret = EXIT_FAILURE; + } else { + ret = EXIT_SUCCESS; + printf("%" PRIu64 " records verified\n", count); + } + + /* + * Clean up. + */ + + /* Clean up the test directory. */ + if (ret == EXIT_SUCCESS && !opts->preserve) testutil_clean_test_artifacts(home); - /* At this point $PATH is inside `home`, which we intend to delete. cd to the parent dir. */ - if (chdir("../") != 0) - testutil_die(errno, "root chdir: %s", home); + + /* At this point, we are inside `home`, which we intend to delete. cd to the parent dir. */ + if (chdir(cwd_start) != 0) + testutil_die(errno, "root chdir: %s", home); + + /* Clean up LazyFS. */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_cleanup(&lazyfs); + + /* Delete the work directory. */ + if (ret == EXIT_SUCCESS && !opts->preserve) testutil_clean_work_dir(home); - } + testutil_cleanup(opts); - return (EXIT_SUCCESS); + return (ret); } diff --git a/test/csuite/schema_abort/smoke_lazyfs.sh b/test/csuite/schema_abort/smoke_lazyfs.sh new file mode 100755 index 00000000000..b9a5d895b73 --- /dev/null +++ b/test/csuite/schema_abort/smoke_lazyfs.sh @@ -0,0 +1,23 @@ +#! /bin/sh + +set -e + +# Smoke-test schema-abort as part of running "make check". + + +if [ -n "$1" ] +then + # If the test binary is passed in manually. + test_bin=$1 +else + # If $binary_dir isn't set, default to using the build directory + # this script resides under. Our CMake build will sync a copy of this + # script to the build directory. Note this assumes we are executing a + # copy of the script that lives under the build directory. Otherwise + # passing the binary path is required. + binary_dir=${binary_dir:-`dirname $0`} + test_bin=$binary_dir/test_schema_abort_lazyfs +fi + +$TEST_WRAPPER $test_bin -l -t 20 -T 5 +$TEST_WRAPPER $test_bin -l -C -t 20 -T 5 diff --git a/test/csuite/timestamp_abort/main.c b/test/csuite/timestamp_abort/main.c index eb1ff602057..35c838e61eb 100644 --- a/test/csuite/timestamp_abort/main.c +++ b/test/csuite/timestamp_abort/main.c @@ -67,7 +67,7 @@ static char home[1024]; /* Program working dir */ #define PREPARE_FREQ 5 #define PREPARE_PCT 10 #define PREPARE_YIELD (PREPARE_FREQ * 10) -#define RECORDS_FILE "records-%" PRIu32 +#define RECORDS_FILE RECORDS_DIR DIR_DELIM_STR "records-%" PRIu32 /* Include worker threads and prepare extra sessions */ #define SESSION_MAX (MAX_TH + 3 + MAX_TH * PREPARE_PCT) #define STAT_WAIT 1 @@ -81,7 +81,7 @@ static const char *const uri_shadow = "shadow"; static const char *const ckpt_file = "checkpoint_done"; -static bool columns, stress, use_ts; +static bool columns, stress, use_lazyfs, use_ts; static TEST_OPTS *opts, _opts; @@ -105,6 +105,9 @@ static TEST_OPTS *opts, _opts; #define ENV_CONFIG_TXNSYNC \ ENV_CONFIG_DEF \ ",transaction_sync=(enabled,method=none)" +#define ENV_CONFIG_TXNSYNC_FSYNC \ + ENV_CONFIG_DEF \ + ",transaction_sync=(enabled,method=fsync)" /* * A minimum width of 10, along with zero filling, means that all the keys sort according to their @@ -254,7 +257,7 @@ handle_general(WT_EVENT_HANDLER *handler, WT_CONNECTION *conn, WT_SESSION *sessi static void usage(void) { - fprintf(stderr, "usage: %s %s [-T threads] [-t time] [-Cmvz]\n", progname, opts->usage); + fprintf(stderr, "usage: %s %s [-T threads] [-t time] [-CcLlsvz]\n", progname, opts->usage); exit(EXIT_FAILURE); } @@ -672,6 +675,9 @@ run_workload(void) if (opts->inmem) testutil_check(__wt_snprintf( envconf, sizeof(envconf), ENV_CONFIG_DEF, cache_mb, SESSION_MAX, STAT_WAIT)); + else if (use_lazyfs) + testutil_check(__wt_snprintf( + envconf, sizeof(envconf), ENV_CONFIG_TXNSYNC_FSYNC, cache_mb, SESSION_MAX, STAT_WAIT)); else testutil_check(__wt_snprintf( envconf, sizeof(envconf), ENV_CONFIG_TXNSYNC, cache_mb, SESSION_MAX, STAT_WAIT)); @@ -687,7 +693,7 @@ run_workload(void) if (!opts->compat && !opts->inmem) strcat(envconf, ENV_CONFIG_ADD_EVICT_DIRTY); - testutil_wiredtiger_open(opts, NULL, envconf, NULL, &conn, false, false); + testutil_wiredtiger_open(opts, WT_HOME_DIR, envconf, NULL, &conn, false, false); testutil_check(conn->open_session(conn, NULL, NULL, &session)); /* @@ -812,13 +818,15 @@ main(int argc, char *argv[]) REPORT c_rep[MAX_TH], l_rep[MAX_TH], o_rep[MAX_TH]; WT_CONNECTION *conn; WT_CURSOR *cur_coll, *cur_local, *cur_oplog, *cur_shadow; + WT_LAZY_FS lazyfs; WT_SESSION *session; pid_t pid; uint64_t absent_coll, absent_local, absent_oplog, absent_shadow, count, key, last_key; uint64_t commit_fp, durable_fp, stable_val; uint32_t i, rand_value, timeout; int ch, status, ret; - char buf[512], fname[64], kname[64], statname[1024], bucket[512]; + char buf[PATH_MAX], fname[64], kname[64], statname[1024], bucket[512]; + char cwd_start[PATH_MAX]; /* The working directory when we started */ char ts_string[WT_TS_HEX_STRING_SIZE]; bool fatal, rand_th, rand_time, verify_only; @@ -828,6 +836,7 @@ main(int argc, char *argv[]) memset(opts, 0, sizeof(*opts)); columns = stress = false; + use_lazyfs = lazyfs_is_implicitly_enabled(); use_ts = true; nth = MIN_TH; rand_th = rand_time = true; @@ -836,7 +845,7 @@ main(int argc, char *argv[]) testutil_parse_begin_opt(argc, argv, SHARED_PARSE_OPTIONS, opts); - while ((ch = __wt_getopt(progname, argc, argv, "cLsT:t:vz" SHARED_PARSE_OPTIONS)) != EOF) + while ((ch = __wt_getopt(progname, argc, argv, "cLlsT:t:vz" SHARED_PARSE_OPTIONS)) != EOF) switch (ch) { case 'c': /* Variable-length columns only (for now) */ @@ -845,6 +854,9 @@ main(int argc, char *argv[]) case 'L': table_pfx = "lsm"; break; + case 'l': + use_lazyfs = true; + break; case 's': stress = true; break; @@ -891,9 +903,25 @@ main(int argc, char *argv[]) fprintf(stderr, "Verify option requires specifying number of threads\n"); exit(EXIT_FAILURE); } + + /* Remember the current working directory. */ + testutil_assert_errno(getcwd(cwd_start, sizeof(cwd_start)) != NULL); + + /* Create the database, run the test, and fail. */ if (!verify_only) { + /* Create the test's home directory. */ testutil_make_work_dir(home); + /* Set up the test subdirectories. */ + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, RECORDS_DIR)); + testutil_make_work_dir(buf); + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s/%s", home, WT_HOME_DIR)); + testutil_make_work_dir(buf); + + /* Set up LazyFS. */ + if (use_lazyfs) + testutil_lazyfs_setup(&lazyfs, home); + if (opts->tiered_storage) { testutil_check(__wt_snprintf(bucket, sizeof(bucket), "%s/bucket", home)); testutil_make_work_dir(bucket); @@ -927,11 +955,11 @@ main(int argc, char *argv[]) opts->compat ? "true" : "false", opts->inmem ? "true" : "false", stress ? "true" : "false", use_ts ? "true" : "false"); printf("Parent: Create %" PRIu32 " threads; sleep %" PRIu32 " seconds\n", nth, timeout); - printf("CONFIG: %s%s%s%s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 " " TESTUTIL_SEED_FORMAT + printf("CONFIG: %s%s%s%s%s%s%s%s -h %s -T %" PRIu32 " -t %" PRIu32 " " TESTUTIL_SEED_FORMAT "\n", - progname, opts->compat ? " -C" : "", columns ? " -c" : "", opts->inmem ? " -m" : "", - opts->tiered_storage ? " -PT" : "", stress ? " -s" : "", !use_ts ? " -z" : "", opts->home, - nth, timeout, opts->data_seed, opts->extra_seed); + progname, opts->compat ? " -C" : "", columns ? " -c" : "", use_lazyfs ? " -l" : "", + opts->inmem ? " -m" : "", opts->tiered_storage ? " -PT" : "", stress ? " -s" : "", + !use_ts ? " -z" : "", opts->home, nth, timeout, opts->data_seed, opts->extra_seed); /* * Fork a child to insert as many items. We will then randomly kill the child, run recovery * and make sure all items we wrote exist after recovery runs. @@ -981,12 +1009,20 @@ main(int argc, char *argv[]) /* Copy the data to a separate folder for debugging purpose. */ testutil_copy_data(home); + /* + * Clear the cache, if we are using LazyFS. Do this after we save the data for debugging + * purposes, so that we can see what we might have lost. If we are using LazyFS, the underlying + * directory shows the state that we'd get after we clear the cache. + */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_clear_cache(&lazyfs); + printf("Open database and run recovery\n"); /* * Open the connection which forces recovery to be run. */ - testutil_wiredtiger_open(opts, NULL, NULL, &my_event, &conn, true, false); + testutil_wiredtiger_open(opts, WT_HOME_DIR, NULL, &my_event, &conn, true, false); printf("Connection open and recovery complete. Verify content\n"); /* Sleep to guarantee the statistics thread has enough time to run. */ @@ -1191,15 +1227,33 @@ main(int argc, char *argv[]) printf("OPLOG: %" PRIu64 " record(s) absent from %" PRIu64 "\n", absent_oplog, count); fatal = true; } - if (fatal) - return (EXIT_FAILURE); - printf("%" PRIu64 " records verified\n", count); - if (!opts->preserve) { + if (fatal) { + ret = EXIT_FAILURE; + } else { + ret = EXIT_SUCCESS; + printf("%" PRIu64 " records verified\n", count); + } + + /* + * Clean up. + */ + + /* Clean up the test directory. */ + if (ret == EXIT_SUCCESS && !opts->preserve) testutil_clean_test_artifacts(home); - /* At this point $PATH is inside `home`, which we intend to delete. cd to the parent dir. */ - if (chdir("../") != 0) - testutil_die(errno, "root chdir: %s", home); + + /* At this point, we are inside `home`, which we intend to delete. cd to the parent dir. */ + if (chdir(cwd_start) != 0) + testutil_die(errno, "root chdir: %s", home); + + /* Clean up LazyFS. */ + if (!verify_only && use_lazyfs) + testutil_lazyfs_cleanup(&lazyfs); + + /* Delete the work directory. */ + if (ret == EXIT_SUCCESS && !opts->preserve) testutil_clean_work_dir(home); - } - return (EXIT_SUCCESS); + + testutil_cleanup(opts); + return (ret); } diff --git a/test/csuite/timestamp_abort/smoke_lazyfs.sh b/test/csuite/timestamp_abort/smoke_lazyfs.sh new file mode 100755 index 00000000000..52ca8dcfd86 --- /dev/null +++ b/test/csuite/timestamp_abort/smoke_lazyfs.sh @@ -0,0 +1,28 @@ +#! /bin/sh + +set -e + +# Smoke-test timestamp-abort as part of running "make check". Use the -s option +# to add a stress timing in checkpoint prepare. + +default_test_args="-t 20 -T 5" +while getopts ":sb:" opt; do + case $opt in + s) default_test_args="$default_test_args -s" ;; + b) test_bin=$OPTARG ;; + esac +done + +if [ -z "$test_bin" ] +then + # If $binary_dir isn't set, default to using the build directory + # this script resides under. Our CMake build will sync a copy of this + # script to the build directory. Note this assumes we are executing a + # copy of the script that lives under the build directory. Otherwise + # passing the binary path is required. + binary_dir=${binary_dir:-`dirname $0`} + test_bin=$binary_dir/test_timestamp_abort_lazyfs +fi + +$TEST_WRAPPER $test_bin -l $default_test_args +$TEST_WRAPPER $test_bin -l -C $default_test_args diff --git a/test/ctest_helpers.cmake b/test/ctest_helpers.cmake index 3095f0ad7ec..f3a76daab45 100644 --- a/test/ctest_helpers.cmake +++ b/test/ctest_helpers.cmake @@ -233,8 +233,8 @@ function(define_c_test) 0 "C_TEST" "" - "TARGET;DIR_NAME;DEPENDS;EXEC_SCRIPT" - "SOURCES;FLAGS;ARGUMENTS;VARIANTS;ADDITIONAL_FILES" + "TARGET;DIR_NAME;EXEC_SCRIPT" + "SOURCES;FLAGS;ARGUMENTS;VARIANTS;DEPENDS;ADDITIONAL_FILES" ) if (NOT "${C_TEST_UNPARSED_ARGUMENTS}" STREQUAL "") message(FATAL_ERROR "Unknown arguments to define_c_test: ${C_TEST_UNPARSED_ARGUMENTS}") diff --git a/test/utility/CMakeLists.txt b/test/utility/CMakeLists.txt index c3673800601..01259115453 100644 --- a/test/utility/CMakeLists.txt +++ b/test/utility/CMakeLists.txt @@ -2,11 +2,12 @@ project(libtest C) set(sources misc.c - util_modify.c + lazyfs.c parse_opts.c - util_random.c thread.c tiered.c + util_modify.c + util_random.c ) set(link_type) diff --git a/test/utility/lazyfs.c b/test/utility/lazyfs.c new file mode 100644 index 00000000000..2b9e188629c --- /dev/null +++ b/test/utility/lazyfs.c @@ -0,0 +1,335 @@ +/*- + * Public Domain 2014-present MongoDB, Inc. + * Public Domain 2008-2014 WiredTiger, Inc. + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "test_util.h" + +#ifdef __linux__ +#include +#include +#include +#include +#include +#endif + +#define LAZYFS_PATH "../../../lazyfs/src/lazyfs/lazyfs" +#define LAZYFS_SUFFIX "_lazyfs" + +static char lazyfs_home[PATH_MAX]; /* Path to LazyFS */ + +/* + * lazyfs_is_implicitly_enabled -- + * Check whether LazyFS is implicitly enabled through being the executable name. + */ +bool +lazyfs_is_implicitly_enabled(void) +{ + size_t len; + + if (progname == NULL) + return false; + + len = strlen(progname); + if (len < strlen(LAZYFS_SUFFIX)) + return false; + + return strcmp(progname + (len - strlen(LAZYFS_SUFFIX)), LAZYFS_SUFFIX) == 0; +} + +/* + * lazyfs_init -- + * Initialize the use of LazyFS for this program. + */ +void +lazyfs_init(void) +{ +#ifndef __linux__ + testutil_die(ENOENT, "LazyFS is not available on this platform."); +#else + struct stat sb; + char buf[PATH_MAX]; + char program_dir[PATH_MAX]; + + /* Find LazyFS relative to the path to the current executable. */ + testutil_assert_errno(readlink("/proc/self/exe", buf, sizeof(buf)) >= 0); + testutil_check(__wt_snprintf(program_dir, sizeof(program_dir), "%s", dirname(buf))); + + testutil_check(__wt_snprintf(lazyfs_home, sizeof(lazyfs_home), "%s/" LAZYFS_PATH, program_dir)); + if (stat(lazyfs_home, &sb) != 0) + testutil_die(errno, "Cannot find LazyFS."); +#endif +} + +/* + * lazyfs_create_config -- + * Create the config file for LazyFS. Note that the path to the FIFO file must be absolute. + */ +void +lazyfs_create_config( + const char *lazyfs_config, const char *lazyfs_control, const char *lazyfs_log_file) +{ + FILE *config_fh; + + if ((config_fh = fopen(lazyfs_config, "w")) == NULL) + testutil_die(errno, "Cannot create LazyFS's config file."); + + testutil_assert_errno(fprintf(config_fh, "[faults]\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "fifo_path=\"%s\"\n", lazyfs_control) >= 0); + + testutil_assert_errno(fprintf(config_fh, "\n[cache]\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "apply_eviction=false\n") >= 0); + + testutil_assert_errno(fprintf(config_fh, "\n[cache.simple]\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "custom_size=\"1gb\"\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "blocks_per_page=1\n") >= 0); + + testutil_assert_errno(fprintf(config_fh, "\n[filesystem]\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "log_all_operations=false\n") >= 0); + testutil_assert_errno(fprintf(config_fh, "logfile=\"%s\"\n", lazyfs_log_file) >= 0); + + testutil_check(fclose(config_fh)); +} + +/* + * lazyfs_mount -- + * Mount LazyFS. Note that the passed paths must be absolute. + */ +pid_t +lazyfs_mount(const char *mount_dir, const char *base_dir, const char *lazyfs_config) +{ +#ifndef __linux__ + WT_UNUSED(mount_dir); + WT_UNUSED(base_dir); + WT_UNUSED(lazyfs_config); + testutil_die(ENOENT, "LazyFS is not available on this platform."); +#else + char subdir_arg[PATH_MAX]; + int e, count; + pid_t p, parent_pid, pid; + + /* + * Mount in a separate process that will be automatically killed if the parent unexpectedly + * exits. In this way, we never forget to unmount. + */ + + testutil_check(__wt_snprintf(subdir_arg, sizeof(subdir_arg), "subdir=%s", base_dir)); + + parent_pid = getpid(); + testutil_assert_errno((pid = fork()) >= 0); + + if (pid == 0) { /* child */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) != 0) { + e = errno; + kill(parent_pid, SIGTERM); + testutil_die(e, "Failed to set up PR_SET_PDEATHSIG"); + } + + if (chdir(lazyfs_home) != 0) { + e = errno; + kill(parent_pid, SIGTERM); + testutil_die(e, "Failed to change directory to LazyFS's home"); + } + + /* + * Note that we need to call the executable directly, not via the mount script, because + * there is otherwise no easy way to kill it automatically if the parent process suddenly + * exits. + */ + if (execl("./build/lazyfs", "lazyfs", mount_dir, "--config-path", lazyfs_config, "-o", + "allow_other", "-o", "modules=subdir", "-o", subdir_arg, "-f", NULL) != 0) { + e = errno; + kill(parent_pid, SIGTERM); + testutil_die(e, "Failed to start LazyFS"); + } + + /* NOTREACHED */ + } + + /* Parent. */ + if (pid < 0) + testutil_die(errno, "Failed to start LazyFS on `%s`", mount_dir); + + /* Wait for the mount to finish. */ + count = 0; + for (;;) { + sleep(2); + if (is_mounted(mount_dir)) + break; + if (++count >= 10) + testutil_die(ETIMEDOUT, "Failed to mount LazyFS on `%s`", mount_dir); + } + + /* Check on the child process. */ + testutil_assert_errno((p = waitpid(pid, &e, WNOHANG)) >= 0); + if (p > 0) + testutil_die(ECHILD, "Failed to mount LazyFS on `%s`: Process exited with status %d", + mount_dir, WEXITSTATUS(e)); + + return pid; +#endif +} + +/* + * lazyfs_unmount -- + * Unmount LazyFS if it is mounted. If lazyfs_pid > 0, wait for the subprocess to exit. + */ +void +lazyfs_unmount(const char *mount_dir, pid_t lazyfs_pid) +{ +#ifndef __linux__ + WT_UNUSED(mount_dir); + WT_UNUSED(lazyfs_pid); + testutil_die(ENOENT, "LazyFS is not available on this platform."); +#else + struct stat sb; + int status; + char buf[PATH_MAX]; + + /* Check whether the file system is mounted. */ + if (stat(mount_dir, &sb) != 0) { + if (errno == ENOENT) + return; /* It's ok if the mount point doesn't exist. */ + testutil_die(errno, "Error while accessing the LazyFS mount point."); + } + if (!is_mounted(mount_dir)) + return; + + /* Unmount. */ + testutil_check(__wt_snprintf( + buf, sizeof(buf), "cd '%s' && ./scripts/umount-lazyfs.sh -m \"%s\"", lazyfs_home, mount_dir)); + testutil_check(system(buf)); + if (lazyfs_pid > 0) + testutil_assert_errno(waitpid(lazyfs_pid, &status, 0) >= 0); +#endif +} + +/* + * lazyfs_command -- + * Run a LazyFS command. + */ +void +lazyfs_command(const char *lazyfs_control, const char *command) +{ + FILE *f; + + if ((f = fopen(lazyfs_control, "w")) == NULL) + testutil_die(errno, "Cannot open LazyFS's control file."); + + testutil_assert_errno(fprintf(f, "lazyfs::%s\n", command) >= 0); + testutil_check(fclose(f)); + usleep(500 * WT_THOUSAND); +} + +/* + * lazyfs_clear_cache -- + * Clear cache. + */ +void +lazyfs_clear_cache(const char *lazyfs_control) +{ + lazyfs_command(lazyfs_control, "clear-cache"); +} + +/* + * lazyfs_display_cache_usage -- + * Display cache usage. + */ +void +lazyfs_display_cache_usage(const char *lazyfs_control) +{ + lazyfs_command(lazyfs_control, "display-cache-usage"); +} + +/* + * testutil_lazyfs_setup -- + * Set up LazyFS for the test. Note that the home directory must already exist. + */ +void +testutil_lazyfs_setup(WT_LAZY_FS *lazyfs, const char *home) +{ +#ifndef __linux__ + WT_UNUSED(lazyfs); + WT_UNUSED(home); + testutil_die(ENOENT, "LazyFS is not available on this platform."); +#else + char home_canonical[PATH_MAX]; + char *str; + + memset(lazyfs, 0, sizeof(*lazyfs)); + + /* Initialize LazyFS for the application. */ + lazyfs_init(); + + /* Get the canonical path to the home directory. */ + testutil_assert_errno((str = canonicalize_file_name(home)) != NULL); + testutil_check(__wt_snprintf(home_canonical, sizeof(home_canonical), "%s", str)); + free(str); + + /* Create the base directory on the underlying file system. */ + testutil_check( + __wt_snprintf(lazyfs->base, sizeof(lazyfs->base), "%s/%s", home_canonical, LAZYFS_BASE_DIR)); + testutil_make_work_dir(lazyfs->base); + + /* Set up the relevant LazyFS files. */ + testutil_check(__wt_snprintf( + lazyfs->control, sizeof(lazyfs->control), "%s/%s", home_canonical, LAZYFS_CONTROL_FILE)); + testutil_check(__wt_snprintf( + lazyfs->config, sizeof(lazyfs->config), "%s/%s", home_canonical, LAZYFS_CONFIG_FILE)); + testutil_check(__wt_snprintf( + lazyfs->logfile, sizeof(lazyfs->logfile), "%s/%s", home_canonical, LAZYFS_LOG_FILE)); + lazyfs_create_config(lazyfs->config, lazyfs->control, lazyfs->logfile); + + /* Mount LazyFS. */ + testutil_check(__wt_snprintf( + lazyfs->mountpoint, sizeof(lazyfs->mountpoint), "%s/%s", home_canonical, WT_HOME_DIR)); + lazyfs->pid = lazyfs_mount(lazyfs->mountpoint, lazyfs->base, lazyfs->config); +#endif +} + +/* + * testutil_lazyfs_clear_cache -- + * Clear the cache. Also print the cache usage to the log for debugging purposes. + */ +void +testutil_lazyfs_clear_cache(WT_LAZY_FS *lazyfs) +{ + lazyfs_display_cache_usage(lazyfs->control); + lazyfs_clear_cache(lazyfs->control); +} + +/* + * testutil_lazyfs_cleanup -- + * Clean up LazyFS: Unmount the file system and delete the (now empty) working directory. + */ +void +testutil_lazyfs_cleanup(WT_LAZY_FS *lazyfs) +{ + lazyfs_unmount(lazyfs->mountpoint, lazyfs->pid); + lazyfs->pid = 0; + + testutil_clean_work_dir(lazyfs->mountpoint); +} diff --git a/test/utility/misc.c b/test/utility/misc.c index 74091e6cf69..230d958dae5 100644 --- a/test/utility/misc.c +++ b/test/utility/misc.c @@ -31,6 +31,10 @@ #include #endif +#ifdef __linux__ +#include +#endif + void (*custom_die)(void) = NULL; const char *progname = "program name not set"; @@ -564,3 +568,25 @@ example_setup(int argc, char *const *argv) testutil_make_work_dir(home); return (home); } + +/* + * is_mounted -- + * Check whether the given directory (other than /) is mounted. Works only on Linux. + */ +bool +is_mounted(const char *mount_dir) +{ +#ifndef __linux__ + WT_UNUSED(mount_dir); + return false; +#else + struct stat sb, parent_sb; + char buf[PATH_MAX]; + + testutil_check(__wt_snprintf(buf, sizeof(buf), "%s", mount_dir)); + testutil_assert_errno(stat(mount_dir, &sb) == 0); + testutil_assert_errno(stat(dirname(buf), &parent_sb) == 0); + + return sb.st_dev != parent_sb.st_dev; +#endif +} diff --git a/test/utility/test_util.h b/test/utility/test_util.h index f1cbc12b4c3..7319ae06cdf 100644 --- a/test/utility/test_util.h +++ b/test/utility/test_util.h @@ -45,6 +45,16 @@ #define DEFAULT_TABLE_SCHEMA "key_format=i,value_format=S" #define MKDIR_COMMAND "mkdir " +/* Subdirectory names, if we need to split the test directory into multiple subdirectories. */ +#define RECORDS_DIR "records" +#define WT_HOME_DIR "WT_HOME" + +/* Default file and subdirectory names to use for LazyFS in the tests. */ +#define LAZYFS_BASE_DIR "base" +#define LAZYFS_CONFIG_FILE "lazyfs-config.toml" +#define LAZYFS_CONTROL_FILE "lazyfs-control.fifo" +#define LAZYFS_LOG_FILE "lazyfs.log" + #ifdef _WIN32 #include "windows_shim.h" #endif @@ -139,6 +149,18 @@ typedef struct { int thread_counter; } TEST_PER_THREAD_OPTS; +/* + * A data structure for everything that we need to keep track of when using LazyFS. + */ +typedef struct { + char base[PATH_MAX]; /* The base home directory under LazyFS, if using it */ + char config[PATH_MAX]; /* The LazyFS config file */ + char control[PATH_MAX]; /* The LazyFS FIFO file for controlling it */ + char mountpoint[PATH_MAX]; /* The mount home directory under LazyFS, if using it */ + char logfile[PATH_MAX]; /* The LazyFS log file */ + pid_t pid; /* The PID of the LazyFS process */ +} WT_LAZY_FS; + /* * testutil_assert -- * Complain and quit if something isn't true, with no error value. @@ -389,6 +411,15 @@ const char *example_setup(int, char *const *); */ int handle_op_error(WT_EVENT_HANDLER *, WT_SESSION *, int, const char *); int handle_op_message(WT_EVENT_HANDLER *, WT_SESSION *, const char *); +bool is_mounted(const char *); +void lazyfs_command(const char *, const char *); +void lazyfs_clear_cache(const char *); +void lazyfs_create_config(const char *, const char *, const char *); +void lazyfs_display_cache_usage(const char *); +void lazyfs_init(void); +bool lazyfs_is_implicitly_enabled(void); +pid_t lazyfs_mount(const char *, const char *, const char *); +void lazyfs_unmount(const char *, pid_t); void op_bulk(void *); void op_bulk_unique(void *); void op_create(void *); @@ -407,6 +438,9 @@ void testutil_create_backup_directory(const char *); void testutil_deduce_build_dir(TEST_OPTS *opts); int testutil_general_event_handler( WT_EVENT_HANDLER *, WT_CONNECTION *, WT_SESSION *, WT_EVENT_TYPE, void *); +void testutil_lazyfs_cleanup(WT_LAZY_FS *); +void testutil_lazyfs_clear_cache(WT_LAZY_FS *); +void testutil_lazyfs_setup(WT_LAZY_FS *, const char *); void testutil_make_work_dir(const char *); void testutil_modify_apply(WT_ITEM *, WT_ITEM *, WT_MODIFY *, int, uint8_t); void testutil_parse_begin_opt(int, char *const *, const char *, TEST_OPTS *); diff --git a/test/windows/windows_shim.h b/test/windows/windows_shim.h index 06b60360c69..cc19f5ff371 100644 --- a/test/windows/windows_shim.h +++ b/test/windows/windows_shim.h @@ -40,6 +40,13 @@ #define strcasecmp stricmp +/* + * Emulate + */ +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + /* * Emulate */ @@ -56,6 +63,12 @@ struct timeval { int gettimeofday(struct timeval *tp, void *tzp); +/* + * Emulate + */ + +typedef int pid_t; + /* * Emulate */