]> git.apps.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
btrfs/301: new test for simple quotas
authorBoris Burkov <boris@bur.io>
Thu, 28 Sep 2023 23:16:45 +0000 (16:16 -0700)
committerZorro Lang <zlang@kernel.org>
Thu, 5 Oct 2023 14:32:01 +0000 (22:32 +0800)
Test some interesting basic and edge cases of simple quotas.

To some extent, this is redundant with the alternate testing strategy of
using MKFS_OPTIONS to enable simple quotas, running the full suite and
relying on kernel warnings and fsck to surface issues.

Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: Zorro Lang <zlang@kernel.org>
tests/btrfs/301 [new file with mode: 0755]
tests/btrfs/301.out [new file with mode: 0644]

diff --git a/tests/btrfs/301 b/tests/btrfs/301
new file mode 100755 (executable)
index 0000000..7a0b4c0
--- /dev/null
@@ -0,0 +1,444 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2023 Meta Platforms, Inc.  All Rights Reserved.
+#
+# FS QA Test 301
+#
+# Test common btrfs simple quotas scenarios involving sharing extents and
+# removing them in various orders.
+#
+. ./common/preamble
+_begin_fstest auto quick qgroup clone subvol prealloc snapshot
+
+# Import common functions.
+. ./common/reflink
+
+# Real QA test starts here.
+
+# Modify as appropriate.
+_supported_fs btrfs
+_require_scratch_reflink
+_require_cp_reflink
+_require_btrfs_command inspect-internal dump-tree
+_require_xfs_io_command "falloc"
+_require_scratch_enable_simple_quota
+
+subv=$SCRATCH_MNT/subv
+nested=$SCRATCH_MNT/subv/nested
+snap=$SCRATCH_MNT/snap
+nr_fill=512
+fill_sz=$((8 * 1024))
+total_fill=$(($nr_fill * $fill_sz))
+nodesize=$($BTRFS_UTIL_PROG inspect-internal dump-super $SCRATCH_DEV | \
+                                       grep nodesize | $AWK_PROG '{print $2}')
+ext_sz=$((128 * 1024 * 1024))
+limit_nr=8
+limit=$(($ext_sz * $limit_nr))
+
+prep_fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+cat >$prep_fio_config <<EOF
+[randwrite]
+name=filler
+directory=${subv}
+rw=randwrite
+nrfiles=${nr_fill}
+filesize=${fill_sz}
+EOF
+_require_fio $fio_config
+
+get_qgroup_usage()
+{
+       local qgroupid=$1
+
+       $BTRFS_UTIL_PROG qgroup show --sync --raw $SCRATCH_MNT | \
+                               grep "$qgroupid" | $AWK_PROG '{print $3}'
+}
+
+get_subvol_usage()
+{
+       local subvolid=$1
+
+       get_qgroup_usage "0/$subvolid"
+}
+
+count_subvol_owned_metadata()
+{
+       local subvolid=$1
+
+       # We need to sync so that the metadata extents are on disk and visible
+       # to dump-tree.
+       sync
+       # Find nodes and leaves owned by the subvol, then get unique offsets
+       # to account for snapshots sharing metadata.
+       count=$($BTRFS_UTIL_PROG inspect-internal dump-tree $SCRATCH_DEV | \
+               grep "owner $subvolid" | $AWK_PROG '{print $2}' | sort | \
+                                                               uniq | wc -l)
+       # Output bytes rather than number of metadata blocks.
+       echo $(($count * $nodesize))
+}
+
+check_qgroup_usage()
+{
+       local qgroupid=$1
+       local expected=$2
+       local actual=$(get_qgroup_usage $qgroupid)
+
+       [ $expected -eq $actual ] || \
+               echo "qgroup $qgroupid mismatched usage $actual vs $expected"
+}
+
+check_subvol_usage()
+{
+       local subvolid=$1
+       local expected_data=$2
+       local expected_meta=$(count_subvol_owned_metadata $subvolid)
+       local actual=$(get_subvol_usage $subvolid)
+       local expected=$(($expected_data + $expected_meta))
+
+       [ $expected -eq $actual ] || \
+               echo "subvol $subvolid mismatched usage $actual vs $expected (expected data $expected_data expected meta $expected_meta diff $(($actual - $expected)))"
+}
+
+set_subvol_limit()
+{
+       local subvolid=$1
+       local limit=$2
+
+       $BTRFS_UTIL_PROG qgroup limit $2 0/$1 $SCRATCH_MNT
+}
+
+# We need the cleaner thread to run to actually delete extents for test
+# cases that care about that. The remount wakes up the cleaner thread and sets
+# the commit interval to 1s, so the 1.5s sleep is enough to wait for the cleaner
+# thread to run.
+trigger_cleaner()
+{
+       _scratch_remount commit=1
+       sleep 1.5
+}
+
+cycle_mount_check_subvol_usage()
+{
+       _scratch_cycle_mount
+       check_subvol_usage $@
+}
+
+do_write()
+{
+       local file=$1
+       local sz=$2
+
+       $XFS_IO_PROG -fc "pwrite -q 0 $sz" $file
+}
+
+do_enospc_write()
+{
+       local file=$1
+       local sz=$2
+
+       do_write $file $sz
+}
+
+do_falloc()
+{
+       local file=$1
+       local sz=$2
+
+       $XFS_IO_PROG -fc "falloc 0 $sz" $file
+}
+
+do_enospc_falloc()
+{
+       local file=$1
+       local sz=$2
+
+       do_falloc $file $sz
+}
+
+enable_quota()
+{
+       local mode=$1
+
+       [ $mode == "n" ] && return
+       arg=$([ $mode == "s" ] && echo "--simple")
+
+       $BTRFS_UTIL_PROG quota enable $arg $SCRATCH_MNT
+}
+
+prepare()
+{
+       _scratch_mkfs >> $seqres.full
+       _scratch_mount
+       enable_quota "s"
+       $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full
+       set_subvol_limit 256 $limit
+       check_subvol_usage 256 0
+
+       # Create a bunch of little filler files to generate several levels in
+       # the btree, to make snapshotting sharing scenarios complex enough.
+       $FIO_PROG $prep_fio_config --output=$fio_out
+       check_subvol_usage 256 $total_fill
+
+       # Create a single file whose extents we will explicitly share/unshare.
+       do_write $subv/f $ext_sz
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+}
+
+prepare_snapshotted()
+{
+       prepare
+       $BTRFS_UTIL_PROG subvolume snapshot $subv $snap >> $seqres.full
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+}
+
+prepare_nested()
+{
+       prepare
+       $BTRFS_UTIL_PROG qgroup create 1/100 $SCRATCH_MNT
+       $BTRFS_UTIL_PROG qgroup limit $limit 1/100 $SCRATCH_MNT
+       $BTRFS_UTIL_PROG qgroup assign 0/256 1/100 $SCRATCH_MNT >> $seqres.full
+       $BTRFS_UTIL_PROG subvolume create $nested >> $seqres.full
+       do_write $nested/f $ext_sz
+       check_subvol_usage 257 $ext_sz
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       local subv_usage=$(get_subvol_usage 256)
+       local nested_usage=$(get_subvol_usage 257)
+       check_qgroup_usage 1/100 $(($subv_usage + $nested_usage))
+}
+
+# Write in a single subvolume, including going over the limit.
+basic_accounting()
+{
+       echo "basic accounting"
+       prepare
+       rm $subv/f
+       check_subvol_usage 256 $total_fill
+       cycle_mount_check_subvol_usage 256 $total_fill
+       do_write $subv/tmp 512M
+       rm $subv/tmp
+       do_write $subv/tmp 512M
+       rm $subv/tmp
+       do_enospc_falloc $subv/large_falloc 2G
+       do_enospc_write $subv/large 2G
+       _scratch_unmount
+}
+
+# Write to the same range of a file a bunch of times in a row
+# to test extent aware reservations.
+reservation_accounting()
+{
+       echo "reservation accounting"
+       prepare
+       for i in $(seq 10); do
+               do_write $subv/tmp 512M
+               rm $subv/tmp
+       done
+       do_enospc_write $subv/large 2G
+       _scratch_unmount
+}
+
+# Write in a snapshot.
+snapshot_accounting()
+{
+       echo "snapshot accounting"
+       prepare_snapshotted
+       touch $snap/f
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       do_write $snap/f $ext_sz
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 $ext_sz
+       rm $snap/f
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       rm $subv/f
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       cycle_mount_check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       _scratch_unmount
+}
+
+# Delete the original ref first after a snapshot.
+delete_snapshot_src_ref()
+{
+       echo "delete src ref first"
+       prepare_snapshotted
+       rm $subv/f
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       rm $snap/f
+       trigger_cleaner
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       cycle_mount_check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       _scratch_unmount
+}
+
+# Delete the snapshot ref first after a snapshot.
+delete_snapshot_ref()
+{
+       echo "delete snapshot ref first"
+       prepare_snapshotted
+       rm $snap/f
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       rm $subv/f
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       cycle_mount_check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       _scratch_unmount
+}
+
+# Delete the snapshotted subvolume after a snapshot.
+delete_snapshot_src()
+{
+       echo "delete snapshot src first"
+       prepare_snapshotted
+       $BTRFS_UTIL_PROG subvolume delete $subv >> $seqres.full
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       rm $snap/f
+       trigger_cleaner
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       $BTRFS_UTIL_PROG subvolume delete $snap >> $seqres.full
+       trigger_cleaner
+       check_subvol_usage 256 0
+       check_subvol_usage 257 0
+       cycle_mount_check_subvol_usage 256 0
+       check_subvol_usage 257 0
+       _scratch_unmount
+}
+
+# Delete the snapshot subvolume after a snapshot.
+delete_snapshot()
+{
+       echo "delete snapshot first"
+       prepare_snapshotted
+       $BTRFS_UTIL_PROG subvolume delete $snap >> $seqres.full
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       check_subvol_usage 257 0
+       $BTRFS_UTIL_PROG subvolume delete $subv >> $seqres.full
+       trigger_cleaner
+       check_subvol_usage 256 0
+       check_subvol_usage 257 0
+       _scratch_unmount
+}
+
+# Write to a subvolume nested in another subvolume.
+# Exercises the auto-inheritance feature of simple quotas.
+nested_accounting()
+{
+       echo "nested accounting"
+       prepare_nested
+       rm $subv/f
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 $ext_sz
+       local subv_usage=$(get_subvol_usage 256)
+       local nested_usage=$(get_subvol_usage 257)
+       check_qgroup_usage 1/100 $(($subv_usage + $nested_usage))
+       rm $nested/f
+       check_subvol_usage 256 $total_fill
+       check_subvol_usage 257 0
+       subv_usage=$(get_subvol_usage 256)
+       nested_usage=$(get_subvol_usage 257)
+       check_qgroup_usage 1/100 $(($subv_usage + $nested_usage))
+       do_enospc_falloc $nested/large_falloc 2G
+       do_enospc_write $nested/large 2G
+       _scratch_unmount
+}
+
+# Enable simple quotas on a filesystem with existing extents.
+enable_mature()
+{
+       echo "enable mature"
+       _scratch_mkfs >> $seqres.full
+       _scratch_mount
+       $BTRFS_UTIL_PROG subvolume create $subv >> $seqres.full
+       do_write $subv/f $ext_sz
+       # Sync before enabling squotas to reliably *not* count the writes
+       # we did before enabling.
+       sync
+       enable_quota "s"
+       set_subvol_limit 256 $limit
+       _scratch_cycle_mount
+       usage=$(get_subvol_usage 256)
+       [ $usage -lt $ext_sz ] || \
+               echo "captured usage from before enable $usage >= $ext_sz"
+       do_write $subv/g $ext_sz
+       usage=$(get_subvol_usage 256)
+       [ $usage -lt $ext_sz ] && \
+               echo "failed to capture usage after enable $usage < $ext_sz"
+       check_subvol_usage 256 $ext_sz
+       rm $subv/f
+       check_subvol_usage 256 $ext_sz
+       _scratch_cycle_mount
+       rm $subv/g
+       check_subvol_usage 256 0
+       _scratch_unmount
+}
+
+# Reflink a file within the subvolume.
+reflink_accounting()
+{
+       echo "reflink"
+       prepare
+       # Do enough reflinks to prove that they're free. If they counted, then
+       # this wouldn't fit in the limit.
+       for i in $(seq $(($limit_nr * 2))); do
+               _cp_reflink $subv/f $subv/f.i
+       done
+       # Confirm that there is no additional data usage from the reflinks.
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       _scratch_unmount
+}
+
+# Delete the src ref of a reflink first.
+delete_reflink_src_ref()
+{
+       echo "delete reflink src ref"
+       prepare
+       _cp_reflink $subv/f $subv/f.link
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       rm $subv/f
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       rm $subv/f.link
+       check_subvol_usage 256 $(($total_fill))
+       _scratch_unmount
+}
+
+# Delete the link ref of a reflink first.
+delete_reflink_ref()
+{
+       echo "delete reflink ref"
+       prepare
+       _cp_reflink $subv/f $subv/f.link
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       rm $subv/f.link
+       check_subvol_usage 256 $(($total_fill + $ext_sz))
+       rm $subv/f
+       check_subvol_usage 256 $(($total_fill))
+       _scratch_unmount
+}
+
+basic_accounting
+reservation_accounting
+snapshot_accounting
+delete_snapshot_src_ref
+delete_snapshot_ref
+delete_snapshot_src
+delete_snapshot
+nested_accounting
+enable_mature
+reflink_accounting
+delete_reflink_src_ref
+delete_reflink_ref
+
+# success, all done
+status=0
+exit
diff --git a/tests/btrfs/301.out b/tests/btrfs/301.out
new file mode 100644 (file)
index 0000000..1c50268
--- /dev/null
@@ -0,0 +1,18 @@
+QA output created by 301
+basic accounting
+fallocate: Disk quota exceeded
+pwrite: Disk quota exceeded
+reservation accounting
+pwrite: Disk quota exceeded
+snapshot accounting
+delete src ref first
+delete snapshot ref first
+delete snapshot src first
+delete snapshot first
+nested accounting
+fallocate: Disk quota exceeded
+pwrite: Disk quota exceeded
+enable mature
+reflink
+delete reflink src ref
+delete reflink ref