From: John Garry <john.g.garry@oracle.com>
To: linux-block@vger.kernel.org, shinichiro.kawasaki@wdc.com
Cc: John Garry <john.g.garry@oracle.com>
Subject: [PATCH blktests v2 4/9] md/rc: add _md_atomics_test
Date: Mon, 22 Sep 2025 10:24:28 +0000 [thread overview]
Message-ID: <20250922102433.1586402-5-john.g.garry@oracle.com> (raw)
In-Reply-To: <20250922102433.1586402-1-john.g.garry@oracle.com>
The stacked device atomic writes testing is currently limited.
md/002 currently only tests scsi_debug. SCSI does not support atomic
boundaries, so it would be nice to test NVMe (which does support them).
Furthermore, the testing in md/002 for chunk boundaries is very limited,
in that we test once one boundary value. Indeed, for RAID0 and RAID10, a
boundary should always be set for testing.
Finally, md/002 only tests md RAID0/1/10. In future we will also want to
test the following stacked device personalities which support atomic
writes:
- md-linear (being upstreamed)
- dm-linear
- dm-stripe
- dm-mirror
To solve all those problems, add a generic test handler,
_md_atomics_test(). This can be extended for more extensive testing.
This test handler will accept a group of devices and test as follows:
a. calculate expected atomic write limits based on device limits
b. Take results from a., and refine expected limits based on any chunk
size
c. loop through creating a stacked device for different chunk size. We loop
once for any personality which does not have a chunk size, e.g. RAID1
d. test sysfs and statx limits vs what is calculated in a. and b.
e. test RWF_ATOMIC is accepted or rejected as expected
Steps c, d, and e are really same as md/002.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
tests/md/rc | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 377 insertions(+)
diff --git a/tests/md/rc b/tests/md/rc
index 96bcd97..ee5934c 100644
--- a/tests/md/rc
+++ b/tests/md/rc
@@ -5,9 +5,386 @@
# Tests for md raid
. common/rc
+. common/xfs
group_requires() {
_have_root
_have_program mdadm
_have_driver md-mod
}
+
+_stacked_atomic_test_requires() {
+ _have_kver 6 14 0
+ _have_xfs_io_atomic_write
+ _have_driver raid0
+ _have_driver raid1
+ _have_driver raid10
+}
+
+_max_pow_of_two_factor() {
+ local part1=$1
+ local part2=-$1
+ local retval=$((part1 & part2))
+ echo "$retval"
+}
+
+# Find max atomic size given a boundary and chunk size
+# @unit is set if we want atomic write "unit" size, i.e power-of-2
+# @chunk must be > 0
+_md_atomics_boundaries_max() {
+ local boundary=$1
+ local chunk=$2
+ local unit=$3
+ local retval
+
+ if [ "$boundary" -eq 0 ]
+ then
+ if [ "$unit" -eq 1 ]
+ then
+ retval=$(_max_pow_of_two_factor "$chunk")
+ echo "$retval"
+ return
+ fi
+
+ echo "$chunk"
+ return
+ fi
+
+ # boundary is always a power-of-2
+ if [ "$boundary" -eq "$chunk" ]
+ then
+ echo "$boundary"
+ return
+ fi
+
+ if [ "$boundary" -gt "$chunk" ]
+ then
+ if (( boundary % chunk == 0))
+ then
+ if [ "$unit" -eq 1 ]
+ then
+ retval=$(_max_pow_of_two_factor "$chunk")
+ echo "$retval"
+ return
+ fi
+ echo "$chunk"
+ return
+ fi
+ echo "0"
+ return
+ fi
+
+ if (( chunk % boundary == 0))
+ then
+ echo "$boundary"
+ return
+ fi
+
+ echo "0"
+}
+
+declare -A MD_DEVICES
+
+_md_atomics_test() {
+ local md_sysfs_max_hw_sectors_kb
+ local md_sysfs_max_hw
+ local md_chunk_size
+ local sysfs_logical_block_size
+ local sysfs_atomic_write_max
+ local sysfs_atomic_write_unit_min
+ local sysfs_atomic_write_unit_max
+ local bytes_to_write
+ local bytes_written
+ local test_desc
+ local md_dev
+ local md_dev_sysfs
+ local raw_atomic_write_unit_min
+ local raw_atomic_write_unit_max
+ local raw_atomic_write_max
+ local raw_atomic_write_boundary
+ local raw_atomic_write_supported=1
+ local dev0=$1
+ local dev1=$2
+ local dev2=$3
+ local dev3=$4
+
+ unset MD_DEVICES
+ MD_DEVICES=([0]=$dev0 [1]=$dev1 [2]=$dev2 [3]=$dev3);
+
+ # Calculate what we expect the atomic write limits to be
+ # Don't consider any chunk size at this stage
+ # Use the limits from the first device and then loop again to find
+ # lowest common supported
+ raw_atomic_write_unit_min=$(< /sys/block/"$dev0"/queue/atomic_write_unit_min_bytes);
+ raw_atomic_write_unit_max=$(< /sys/block/"$dev0"/queue/atomic_write_unit_max_bytes);
+ raw_atomic_write_max=$(< /sys/block/"$dev0"/queue/atomic_write_max_bytes);
+ raw_atomic_write_boundary=$(< /sys/block/"$dev0"/queue/atomic_write_boundary_bytes);
+
+ for i in "${MD_DEVICES[@]}"; do
+ if [[ $(< /sys/block/"$i"/queue/atomic_write_unit_min_bytes) -gt raw_atomic_write_unit_min ]]; then
+ raw_atomic_write_unit_min=$(< /sys/block/"$i"/queue/atomic_write_unit_min_bytes)
+ fi
+ if [[ $(< /sys/block/"$i"/queue/atomic_write_unit_max_bytes) -lt raw_atomic_write_unit_max ]]; then
+ raw_atomic_write_unit_max=$(< /sys/block/"$i"/queue/atomic_write_unit_max_bytes)
+ fi
+ if [[ $(< /sys/block/"$i"/queue/atomic_write_max_bytes) -lt raw_atomic_write_max ]]; then
+ raw_atomic_write_max=$(< /sys/block/"$i"/queue/atomic_write_max_bytes)
+ fi
+ # The kernel only supports same boundary size for all devices in the array
+ if [[ $(< /sys/block/"$i"/queue/atomic_write_boundary_bytes) -ne raw_atomic_write_boundary ]]; then
+ raw_atomic_write_supported=0;
+ fi
+ done
+
+ # Check if we can support atomic writes for the array of devices given.
+ # If we cannot, then it is still worth trying to test that atomic
+ # writes don't work (as we would expect).
+
+ if [[ raw_atomic_write_supported -eq 0 ]]; then
+ raw_atomic_write_unit_min=0;
+ raw_atomic_write_unit_max=0;
+ raw_atomic_write_max=0;
+ raw_atomic_write_boundary=0;
+ fi
+
+ for personality in raid0 raid1 raid10; do
+ local step_limit
+ if [ "$personality" = raid0 ] || [ "$personality" = raid10 ]
+ then
+ step_limit=4
+ else
+ step_limit=1
+ fi
+ chunk_gran=$(( "$raw_atomic_write_unit_max" / 2))
+ if [ "$chunk_gran" -lt 4096 ]
+ then
+ chunk_gran=4096
+ fi
+
+ local chunk_multiple=1
+ for step in $(seq 1 $step_limit)
+ do
+ local expected_atomic_write_unit_min
+ local expected_atomic_write_unit_max
+ local expected_atomic_write_max
+ local expected_atomic_write_boundary
+ local atomics_boundaries_unit_max
+ local atomics_boundaries_max
+
+ # only raid0 does not require a power-of-2 chunk size
+ if [ "$personality" = raid0 ]
+ then
+ chunk_multiple=$step
+ else
+ chunk_multiple=$(( 2 * "$chunk_multiple"))
+ fi
+ md_chunk_size=$(( "$chunk_gran" * "$chunk_multiple"))
+ md_chunk_size_kb=$(( "$md_chunk_size" / 1024))
+
+ # We may reassign these for RAID0/10
+ expected_atomic_write_unit_min=$raw_atomic_write_unit_min
+ expected_atomic_write_unit_max=$raw_atomic_write_unit_max
+ expected_atomic_write_max=$raw_atomic_write_max
+ expected_atomic_write_boundary=$raw_atomic_write_boundary
+
+ if [ "$personality" = raid0 ] || [ "$personality" = raid10 ]
+ then
+ echo y | mdadm --create /dev/md/blktests_md --level=$personality \
+ --chunk="${md_chunk_size_kb}"K \
+ --raid-devices=4 --force /dev/"${dev0}" /dev/"${dev1}" \
+ /dev/"${dev2}" /dev/"${dev3}" 2> /dev/null 1>&2
+
+ atomics_boundaries_unit_max=$(_md_atomics_boundaries_max $raw_atomic_write_boundary $md_chunk_size "1")
+ atomics_boundaries_max=$(_md_atomics_boundaries_max "$raw_atomic_write_boundary" "$md_chunk_size" "0")
+ expected_atomic_write_unit_min=$(_min "$expected_atomic_write_unit_min" "$atomics_boundaries_unit_max")
+ expected_atomic_write_unit_max=$(_min "$expected_atomic_write_unit_max" "$atomics_boundaries_unit_max")
+ expected_atomic_write_max=$(_min "$expected_atomic_write_max" "$atomics_boundaries_max")
+ if [ "$atomics_boundaries_max" -eq 0 ]
+ then
+ expected_atomic_write_boundary=0
+ fi
+ md_dev=$(readlink /dev/md/blktests_md | sed 's|\.\./||')
+ fi
+
+ if [ "$personality" = raid1 ]
+ then
+ echo y | mdadm --create /dev/md/blktests_md --level=$personality \
+ --raid-devices=4 --force /dev/"${dev0}" /dev/"${dev1}" \
+ /dev/"${dev2}" /dev/"${dev3}" 2> /dev/null 1>&2
+
+ md_dev=$(readlink /dev/md/blktests_md | sed 's|\.\./||')
+ fi
+
+ md_dev_sysfs="/sys/devices/virtual/block/${md_dev}"
+
+ sysfs_logical_block_size=$(< "${md_dev_sysfs}"/queue/logical_block_size)
+ md_sysfs_max_hw_sectors_kb=$(< "${md_dev_sysfs}"/queue/max_hw_sectors_kb)
+ md_sysfs_max_hw=$(( "$md_sysfs_max_hw_sectors_kb" * 1024 ))
+ sysfs_atomic_write_max=$(< "${md_dev_sysfs}"/queue/atomic_write_max_bytes)
+ sysfs_atomic_write_unit_max=$(< "${md_dev_sysfs}"/queue/atomic_write_unit_max_bytes)
+ sysfs_atomic_write_unit_min=$(< "${md_dev_sysfs}"/queue/atomic_write_unit_min_bytes)
+ sysfs_atomic_write_boundary=$(< "${md_dev_sysfs}"/queue/atomic_write_boundary_bytes)
+
+ test_desc="TEST 1 $personality step $step - Verify md sysfs atomic attributes matches"
+ if [ "$sysfs_atomic_write_unit_min" = "$expected_atomic_write_unit_min" ] &&
+ [ "$sysfs_atomic_write_unit_max" = "$expected_atomic_write_unit_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail sysfs_atomic_write_unit_min=$sysfs_atomic_write_unit_min" \
+ "expected_atomic_write_unit_min=$expected_atomic_write_unit_min" \
+ "sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "expected_atomic_write_unit_max=$expected_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 2 $personality step $step - Verify sysfs atomic attributes"
+ if [ "$md_sysfs_max_hw" -ge "$sysfs_atomic_write_max" ] &&
+ [ "$sysfs_atomic_write_unit_max" -ge "$sysfs_atomic_write_unit_min" ] &&
+ [ "$sysfs_atomic_write_max" -ge "$sysfs_atomic_write_unit_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail md_sysfs_max_hw=$md_sysfs_max_hw" \
+ "sysfs_atomic_write_max=$sysfs_atomic_write_max" \
+ "sysfs_atomic_write_unit_min=$sysfs_atomic_write_unit_min" \
+ "sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 3 $personality step $step - Verify md sysfs_atomic_write_max is equal to "
+ test_desc+="expected_atomic_write_max"
+ if [ "$sysfs_atomic_write_max" -eq "$expected_atomic_write_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail sysfs_atomic_write_max=$sysfs_atomic_write_max" \
+ "expected_atomic_write_max=$expected_atomic_write_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 4 $personality step $step - Verify sysfs atomic_write_unit_max_bytes = expected_atomic_write_unit_max"
+ if [ "$sysfs_atomic_write_unit_max" = "$expected_atomic_write_unit_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "expected_atomic_write_unit_max=$expected_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 5 $personality step $step - Verify sysfs atomic_write_unit_boundary_bytes = expected atomic_write_unit_boundary_bytes"
+ if [ "$sysfs_atomic_write_boundary" = "$expected_atomic_write_boundary" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail sysfs_atomic_write_boundary=$sysfs_atomic_write_boundary" \
+ "expected_atomic_write_boundary=$expected_atomic_write_boundary"
+ fi
+
+ test_desc="TEST 6 $personality step $step - Verify statx stx_atomic_write_unit_min"
+ statx_atomic_write_unit_min=$(run_xfs_io_xstat /dev/"$md_dev" "stat.atomic_write_unit_min")
+ if [ "$statx_atomic_write_unit_min" = "$sysfs_atomic_write_unit_min" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail statx_atomic_write_unit_min=$statx_atomic_write_unit_min" \
+ "sysfs_atomic_write_unit_min=$sysfs_atomic_write_unit_min" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 7 $personality step $step - Verify statx stx_atomic_write_unit_max"
+ statx_atomic_write_unit_max=$(run_xfs_io_xstat /dev/"$md_dev" "stat.atomic_write_unit_max")
+ if [ "$statx_atomic_write_unit_max" = "$sysfs_atomic_write_unit_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail statx_atomic_write_unit_max=$statx_atomic_write_unit_max" \
+ "sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+
+ test_desc="TEST 8 $personality step $step - perform a pwritev2 with size of sysfs_atomic_unit_max_bytes with "
+ test_desc+="RWF_ATOMIC flag - pwritev2 should fail"
+ if [ "$sysfs_atomic_write_unit_max" = 0 ]
+ then
+ echo "$test_desc - pass"
+ else
+ bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$sysfs_atomic_write_unit_max")
+ if [ "$bytes_written" = "$sysfs_atomic_write_unit_max" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail bytes_written=$bytes_written" \
+ "sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+ fi
+
+ test_desc="TEST 9 $personality step $step - perform a pwritev2 with size of sysfs_atomic_unit_max_bytes + LBS "
+ test_desc+="bytes with RWF_ATOMIC flag - pwritev2 should not be succesful"
+ if [ "$sysfs_atomic_write_unit_max" = 0 ]
+ then
+ echo "pwrite: Invalid argument"
+ echo "$test_desc - pass"
+ else
+ bytes_to_write=$(( "${sysfs_atomic_write_unit_max}" + "${sysfs_logical_block_size}" ))
+ bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$bytes_to_write")
+ if [ "$bytes_written" = "" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail bytes_written=$bytes_written" \
+ "bytes_to_write=$bytes_to_write" \
+ "sysfs_atomic_write_unit_max=$sysfs_atomic_write_unit_max" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+ fi
+
+ test_desc="TEST 10 $personality step $step - perform a pwritev2 with size of sysfs_atomic_unit_min_bytes "
+ test_desc+="with RWF_ATOMIC flag - pwritev2 should fail"
+ if [ "$sysfs_atomic_write_unit_min" = 0 ]
+ then
+ echo "$test_desc - pass"
+ else
+ bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$sysfs_atomic_write_unit_min")
+ if [ "$bytes_written" = "$sysfs_atomic_write_unit_min" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail bytes_written=$bytes_written" \
+ "sysfs_atomic_write_unit_min=$sysfs_atomic_write_unit_min" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+ fi
+
+ test_desc="TEST 11 $personality step $step - perform a pwritev2 with a size of sysfs_atomic_write_unit_max_bytes - LBS "
+ test_desc+="bytes with RWF_ATOMIC flag - pwritev2 should fail"
+ if [ "${sysfs_atomic_write_unit_max}" -le "${sysfs_logical_block_size}" ]
+ then
+ echo "pwrite: Invalid argument"
+ echo "$test_desc - pass"
+ else
+ bytes_to_write=$(( "${sysfs_atomic_write_unit_max}" - "${sysfs_logical_block_size}" ))
+ bytes_written=$(run_xfs_io_pwritev2_atomic /dev/"$md_dev" "$bytes_to_write")
+ if [ "$bytes_written" = "" ]
+ then
+ echo "$test_desc - pass"
+ else
+ echo "$test_desc - fail bytes_written=$bytes_written" \
+ "bytes_to_write=$bytes_to_write" \
+ "md_chunk_size=$md_chunk_size"
+ fi
+ fi
+
+ if [ "$personality" = raid0 ] || [ "$personality" = raid1 ] || [ "$personality" = raid10 ]
+ then
+ mdadm --stop /dev/md/blktests_md 2> /dev/null 1>&2
+
+ for i in "${MD_DEVICES[@]}"; do
+ mdadm --zero-superblock /dev/"$i" 2> /dev/null 1>&2
+ done
+ fi
+ done
+ done
+}
--
2.43.5
next prev parent reply other threads:[~2025-09-22 10:24 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-22 10:24 [PATCH blktests v2 0/9] Further stacked device atomic writes testing John Garry
2025-09-22 10:24 ` [PATCH blktests v2 1/9] common/rc: add _min() John Garry
2025-09-22 10:24 ` [PATCH blktests v2 2/9] nvme: relocate _nvme_requires and _require_nvme_test_img_size John Garry
2025-09-22 10:24 ` [PATCH blktests v2 3/9] nvme: relocate _require_test_dev_is_nvme John Garry
2025-09-22 10:24 ` John Garry [this message]
2025-09-22 10:24 ` [PATCH blktests v2 5/9] md/002: convert to use _md_atomics_test John Garry
2025-09-22 10:24 ` [PATCH blktests v2 6/9] md/003: add NVMe atomic write tests for stacked devices John Garry
2025-09-25 8:39 ` Shinichiro Kawasaki
2025-09-25 8:46 ` John Garry
2025-09-25 14:12 ` Shinichiro Kawasaki
2025-09-25 14:14 ` John Garry
2025-09-25 15:02 ` John Garry
2025-09-25 23:53 ` Shinichiro Kawasaki
2025-09-22 10:24 ` [PATCH blktests v2 7/9] md/rc: test atomic writes for dm-linear John Garry
2025-09-22 10:24 ` [PATCH blktests v2 8/9] md/rc: test atomic writes for dm-stripe John Garry
2025-09-22 10:24 ` [PATCH blktests v2 9/9] md/rc: test atomic writes for dm-mirror John Garry
2025-09-25 14:09 ` [PATCH blktests v2 0/9] Further stacked device atomic writes testing Shinichiro Kawasaki
2025-09-25 14:13 ` John Garry
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250922102433.1586402-5-john.g.garry@oracle.com \
--to=john.g.garry@oracle.com \
--cc=linux-block@vger.kernel.org \
--cc=shinichiro.kawasaki@wdc.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox