From 7731c7fc7464dd6158ac914d2fbdf0197a947a9a Mon Sep 17 00:00:00 2001 From: Patrick Farrell Date: Tue, 12 Dec 2023 10:00:41 -0500 Subject: [PATCH] EX-7601 tests: unaligned read tests This adds testing for handling unaligned reads and partial chunk reads from compressed files. Testing for writes and multi-client and racing tests will be added later, but we put the checking function in test-framework now so it's easy to use later. Test-Parameters: trivial Test-Parameters: testlist=sanity-compr env=ONLY="1001 1002",ONLY_REPEAT=10 Signed-off-by: Patrick Farrell Signed-off-by: Artem Blagodarenko Change-Id: I06217c8aeba75016aa4168f329026842dff1d979 Reviewed-on: https://review.whamcloud.com/c/ex/lustre-release/+/51841 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger --- lustre/llite/vvp_io.c | 4 +- lustre/tests/sanity-compr.sh | 228 +++++++++++++++++++++++++++++++++++++++++ lustre/tests/test-framework.sh | 10 ++ 3 files changed, 240 insertions(+), 2 deletions(-) diff --git a/lustre/llite/vvp_io.c b/lustre/llite/vvp_io.c index 659b0c3..d76e45b 100644 --- a/lustre/llite/vvp_io.c +++ b/lustre/llite/vvp_io.c @@ -1360,8 +1360,8 @@ static int vvp_io_write_start(const struct lu_env *env, */ if (io->ci_compressed_file && !write_beyond_eof && (pos % chunk_size || cnt % chunk_size)) { - CERROR("Compression preview requires writes to be chunk size aligned or at EOF, write pos: %llu, bytes: %lu, chunk_size: %d\n", - pos, cnt, chunk_size); + CERROR("Compression preview requires writes to be chunk size aligned or at EOF, write pos: %llu, bytes: %lu, chunk_size: %d, inode_size %llu\n", + pos, cnt, chunk_size, inode_size); RETURN(-EINVAL); } diff --git a/lustre/tests/sanity-compr.sh b/lustre/tests/sanity-compr.sh index caa3b24..7a83577 100644 --- a/lustre/tests/sanity-compr.sh +++ b/lustre/tests/sanity-compr.sh @@ -166,6 +166,234 @@ test_fsx() { } run_test fsx "verify dense writes with fsx on ldiskfs" +test_1001() { + (( MDS1_VERSION >= $(version_code 2.14.0.91) )) || + skip "Need MDS version at least 2.14.0.91" + + local tf=$DIR/$tfile + # We read from $tfile to this + local tf_copy=$DIR/$tfile.copy + # Larger than arm page size + local chunksize=128 + local read_ahead_mb + local hdf=$LUSTRE/tests/AMSR_E_L3_DailyOcean_V05_20111003.hdf + local tmp_hdf=$TMP/$tfile.hdf + local source=$tmp_hdf + + if [[ -f $hdf.bz2 ]] && type -p bzcat >/dev/null; then + bzcat $hdf.bz2 > $tmp_hdf + elif [[ -f $hdf.bz2 ]] && type -p bunzip2 >/dev/null; then + cp $hdf.bz2 $tmp_hdf.bz2 || error "cp $tmp_hdf.bz2" + bunzip2 $tmp_hdf.bz2 || error "bunzip2 $tmp_hdf.bz2" + else + echo "bunzip2 is not installed, skip it" + return 0 + fi + + # Fail test if source size changes so we catch this + # Source should be a few MiB in size + $CHECKSTAT -s 14625450 $source || error "checkstat wrong size" + + stack_trap "rm -f $tf; disable_compression" + enable_compression + + # Simple compressed layout + + $LFS setstripe -E -1 -Z lz4:0 --compress-chunk=$chunksize $tf || + error "set a compress component in $tf failed" + + # Create file and verify - trivial + #FIXME: We use O_DIRECT here to avoid RPC tearing which can create + # read-modify-writes on the server, which aren't supported yet + # We will remove this once read-modify-write is supported + dd if=$source bs=${chunksize}K of=$tf oflag=direct || + error "(0) dd failed" + cmp -bl $source $tf || error "(1) cmp failed" + flush_and_compare $source $tf "(2)" + + # Copy from compressed to a non-compressed file + dd if=$tf bs=4K of=$tf_copy conv=notrunc || error "(3) dd failed" + flush_and_compare $tf $tf_copy "(4)" + flush_and_compare $source $tf_copy "(5)" + + # Disable readahead so reads are not expanded to full chinks + $LCTL set_param osc.*.rpc_stats=c + read_ahead_mb=$($LCTL get_param -n llite.*.max_read_ahead_mb) + $LCTL set_param llite.*.max_read_ahead_mb=0 + stack_trap "$LCTL set_param llite.*.max_read_ahead_mb=$read_ahead_mb" EXIT + rm -f $tf_copy + # Copy from compressed to a non-compressed file with readahead disabled + dd if=$tf bs=16K of=$tf_copy conv=notrunc || error "(6) dd failed" + flush_and_compare $tf $tf_copy "(7)" + flush_and_compare $source $tf_copy "(8)" + + rm -f $tf_copy + # Same copy with larger size + dd if=$tf bs=$((chunksize * 3/2))K of=$tf_copy conv=notrunc || + error "(9) dd failed" + flush_and_compare $tf $tf_copy "(10)" + flush_and_compare $source $tf_copy "(11)" + + # Same copy, but starting at non-zero offset + # NB: In order to start at offset, we write in to an already existing + # copy. This isn't perfect, but it still catches lots of cases + dd if=$tf bs=$((chunksize * 3/2))K seek=1 skip=1 of=$tf_copy conv=notrunc || + error "(12) dd failed" + flush_and_compare $tf $tf_copy "(13)" + flush_and_compare $source $tf_copy "(14)" + + rm -f $tf_copy + # Explicit tests of partial page reads + dd if=$tf bs=$((PAGE_SIZE * 2 - 1024)) of=$tf_copy conv=notrunc || + error "(15) dd failed" + flush_and_compare $tf $tf_copy "(16)" + flush_and_compare $source $tf_copy "(17)" + + # Partial page read starting at non-zero offset + # NB: In order to start at offset, we write in to an already existing + # copy. This isn't perfect, but it still catches lots of cases + dd if=$tf bs=$((PAGE_SIZE * 2 - 1024)) seek=10 skip=10 of=$tf_copy conv=notrunc || + error "(18) dd failed" + flush_and_compare $tf $tf_copy "(19)" + flush_and_compare $source $tf_copy "(20)" + + # There should be many smaller read RPCs + $LCTL get_param osc.*.rpc_stats +} +run_test 1001 "test partial chunk reads" + +test_1002() { + (( MDS1_VERSION >= $(version_code 2.14.0.91) )) || + skip "Need MDS version at least 2.14.0.91" + + local tf=$DIR/$tfile + # We read from $tfile to this + local tf_copy=$DIR/$tfile.copy + # Larger than arm page size + local chunksize=128 + local read_ahead_mb + local hdf=$LUSTRE/tests/AMSR_E_L3_DailyOcean_V05_20111003.hdf + local tmp_hdf=$TMP/$tfile.hdf + local source=$tmp_hdf + + if [[ -f $hdf.bz2 ]] && type -p bzcat >/dev/null; then + bzcat $hdf.bz2 > $tmp_hdf + elif [[ -f $hdf.bz2 ]] && type -p bunzip2 >/dev/null; then + cp $hdf.bz2 $tmp_hdf.bz2 || error "cp $tmp_hdf.bz2" + bunzip2 $tmp_hdf.bz2 || error "bunzip2 $tmp_hdf.bz2" + else + echo "bunzip2 is not installed, skip it" + return 0 + fi + + # Fail test if source size changes so we catch this + # Source should be a few MiB in size + $CHECKSTAT -s 14625450 $source || error "checkstat wrong size" + + stack_trap "rm -f $tf; disable_compression" + enable_compression + + # Disable readahead so reads are not expanded to full chinks + $LCTL set_param osc.*.rpc_stats=c + read_ahead_mb=$($LCTL get_param -n llite.*.max_read_ahead_mb) + $LCTL set_param llite.*.max_read_ahead_mb=0 + stack_trap "$LCTL set_param llite.*.max_read_ahead_mb=$read_ahead_mb" EXIT + + # Simple compressed layout + $LFS setstripe -E -1 -Z lz4:0 --compress-chunk=$chunksize $tf || + error "set a compress component in $tf failed" + + # These tests will deliberately create unusual files, using the sample + # hdf5 file as a reference, but cannot compare to it because they + # copy only part of it. So all of them will also create an identical + # uncompressed file to compare against. + # Create the simplest possible example of a file with an incomplete + # chunk, just 4K at the beginning (this won't be compressed, but it's + # still a valid test) + dd if=$source bs=4K of=$tf count=1 || error "(0) dd failed" + dd if=$source bs=4K of=$tf.2 count=1 || error "(1) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(2) cmp failed" + flush_and_compare $tf $tf.2 "(3)" + + # 16K - this will be compressed + dd if=$source bs=16K of=$tf count=1 || error "(4) dd failed" + dd if=$source bs=16K of=$tf.2 count=1 || error "(5) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(6) cmp failed" + flush_and_compare $tf $tf.2 "(7)" + + # OK, now we're going to create a complete compressed chunk further + # along in the file + dd if=$source bs=128K count=1 skip=1 seek=1 conv=notrunc of=$tf || + error "(8) dd failed" + dd if=$source bs=128K count=1 skip=1 seek=1 conv=notrunc of=$tf.2 || + error "(9) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(10) cmp failed" + flush_and_compare $tf $tf.2 "(11)" + + # OK, now we're going to add another incomplete chunk after those + # two - starting in the third chunk, ie, offset of 256K + dd if=$source bs=64K count=1 skip=4 seek=4 conv=notrunc of=$tf || + error "(12) dd failed" + dd if=$source bs=64K count=1 skip=4 seek=4 conv=notrunc of=$tf.2 || + error "(13) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(14) cmp failed" + flush_and_compare $tf $tf.2 "(15)" + + # And now we're going to add a chunk that doesn't start at offset 0, + # so it won't be compressed. This is the fourth chunk, so starts at + # 384K. So we'll start at 448K + dd if=$source bs=64K count=1 skip=7 seek=7 conv=notrunc of=$tf || + error "(16) dd failed" + dd if=$source bs=64K count=1 skip=7 seek=7 conv=notrunc of=$tf.2 || + error "(17) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(18) cmp failed" + flush_and_compare $tf $tf.2 "(19)" + + # Now let's skip an entire chunk and do a full chunk at 640K + dd if=$source bs=128K count=1 skip=5 seek=5 conv=notrunc of=$tf || + error "(20) dd failed" + dd if=$source bs=128K count=1 skip=5 seek=5 conv=notrunc of=$tf.2 || + error "(21) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(22) cmp failed" + flush_and_compare $tf $tf.2 "(23)" + + # Then one more partial, but compressible, chunk after that at + # 768K + dd if=$source bs=64K count=1 skip=12 seek=12 conv=notrunc of=$tf || + error "(24) dd failed" + dd if=$source bs=64K count=1 skip=12 seek=12 conv=notrunc of=$tf.2 || + error "(25) dd failed" + sync + + cmp -bl $tf $tf.2 || error "(26) cmp failed" + flush_and_compare $tf $tf.2 "(27)" + + # OK, now we have this complex file, and we've tested reading it, let's + # test reading it at a number of block sizes to give us unusual offsets + echo "copying from compressed file with $bs" + for bs in 3 4 7 32 97 128 130 192; do + dd if=$tf bs=${bs}K of=$tf.3 || + error "(28) dd with block size ${bs}K failed" + + cmp -bl $tf.3 $tf.2 || error "(29) cmp failed" + flush_and_compare $tf.3 $tf.2 "(30)" + rm -f $tf.3 + done +} +run_test 1002 "test reads with incomplete chunks" + complete_test $SECONDS check_and_cleanup_lustre declare -a logs=($ONLY) diff --git a/lustre/tests/test-framework.sh b/lustre/tests/test-framework.sh index f376ef9..1b40b64 100755 --- a/lustre/tests/test-framework.sh +++ b/lustre/tests/test-framework.sh @@ -10690,6 +10690,16 @@ disable_compression() { unset LFS_SETSTRIPE_COMPR_OK } +flush_and_compare() { + # Compare using data in cache + cmp -bl $1 $2 || error "$3 failed compare in cache" + # Sync to disk and drop cache + sync + echo 3 > /proc/sys/vm/drop_caches + # This reads the actual data from disk + cmp -bl $1 $2 || error "$3 failed compare from disk" +} + is_project_quota_supported() { $ENABLE_PROJECT_QUOTAS || return 1 -- 1.8.3.1