xfs: test inode allocation with fragmented free space
authorBrian Foster <bfoster@redhat.com>
Tue, 4 Aug 2015 04:10:48 +0000 (14:10 +1000)
committerDave Chinner <david@fromorbit.com>
Tue, 4 Aug 2015 04:10:48 +0000 (14:10 +1000)
XFS dynamic inode allocation has a fundamental limitation in that an
inode chunk requires a contiguous extent of a minimum size. Depending on
the level of free space fragmentation, inode allocation can fail with
ENOSPC where the filesystem might not be near 100% usage.

The sparse inodes feature was implemented to provide an inode allocation
strategy that maximizes the ability to allocate inodes under free space
fragmentation. This test fragments free space and verifies that
filesystems that support sparse inode allocation can allocate a minimum
percentage of inodes on the fs.

Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
common/rc
tests/xfs/076 [new file with mode: 0755]
tests/xfs/076.out [new file with mode: 0644]
tests/xfs/group

index 610045eab304b823d95564a279957045a7d621b3..0562360134393739026dd40cf212b5a9ac3bf127 100644 (file)
--- a/common/rc
+++ b/common/rc
@@ -1449,6 +1449,18 @@ _require_xfs_sysfs()
        fi
 }
 
+# this test requires the xfs sparse inode feature
+#
+_require_xfs_sparse_inodes()
+{
+       _scratch_mkfs_xfs_supported -m crc=1 -i sparse > /dev/null 2>&1 \
+               || _notrun "mkfs.xfs does not support sparse inodes"
+       _scratch_mkfs_xfs -m crc=1 -i sparse > /dev/null 2>&1
+       _scratch_mount >/dev/null 2>&1 \
+               || _notrun "kernel does not support sparse inodes"
+       umount $SCRATCH_MNT
+}
+
 # this test requires that external log/realtime devices are not in use
 #
 _require_nonexternal()
@@ -2724,6 +2736,18 @@ _get_used_inode()
        echo $nr_inode
 }
 
+_get_used_inode_percent()
+{
+       if [ -z "$1" ]; then
+               echo "Usage: _get_used_inode_percent <mnt>"
+               exit 1
+       fi
+       local pct_inode;
+       pct_inode=`$DF_PROG -i $1 | tail -1 | awk '{ print $6 }' | \
+                  sed -e 's/%//'`
+       echo $pct_inode
+}
+
 _get_free_inode()
 {
        if [ -z "$1" ]; then
@@ -2735,6 +2759,19 @@ _get_free_inode()
        echo $nr_inode
 }
 
+# get the available space in bytes
+#
+_get_available_space()
+{
+       if [ -z "$1" ]; then
+               echo "Usage: _get_available_space <mnt>"
+               exit 1
+       fi
+       local avail_kb;
+       avail_kb=`$DF_PROG $1 | tail -n1 | awk '{ print $5 }'`
+       echo $((avail_kb * 1024))
+}
+
 # get btrfs profile configs being tested
 #
 # A set of pre-set profile configs are exported via _btrfs_profile_configs
diff --git a/tests/xfs/076 b/tests/xfs/076
new file mode 100755 (executable)
index 0000000..1ecfca6
--- /dev/null
@@ -0,0 +1,130 @@
+#!/bin/bash
+# FS QA Test No. xfs/076
+#
+# Verify that a filesystem with sparse inode support can allocate inodes in the
+# event of free space fragmentation. This test is generic in nature but
+# primarily relevant to filesystems that implement dynamic inode allocation
+# (e.g., XFS).
+#
+# The test is inspired by inode allocation limitations on XFS when available
+# free space is fragmented. XFS allocates inodes 64 at a time and thus requires
+# an extent of length that depends on inode size (64 * isize / blksize).
+#
+# The test creates a small, sparse inode enabled filesystem. It fragments free
+# space, allocates inodes to ENOSPC and then verifies that most of the available
+# inodes (.i.e., free space) have been consumed.
+#
+#-----------------------------------------------------------------------
+# Copyright (c) 2015 Red Hat, Inc.  All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it would 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 this program; if not, write the Free Software Foundation,
+# Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+#-----------------------------------------------------------------------
+#
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1       # failure is the default!
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+
+_cleanup()
+{
+       cd /
+       umount $SCRATCH_MNT 2>/dev/null
+       rm -f $tmp.*
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_consume_freesp()
+{
+       file=$1
+
+       # consume nearly all available space (leave ~1MB)
+       avail=`_get_available_space $SCRATCH_MNT`
+       filesizemb=$((avail / 1024 / 1024 - 1))
+       $XFS_IO_PROG -fc "falloc 0 ${filesizemb}m" $file
+}
+
+# Allocate inodes in a directory until failure.
+_alloc_inodes()
+{
+       dir=$1
+
+       i=0
+       while [ true ]; do
+               touch $dir/$i 2>> $seqres.full || break
+               i=$((i + 1))
+       done
+}
+
+# real QA test starts here
+_supported_os Linux
+
+_require_scratch
+_require_xfs_io_command "falloc"
+_require_xfs_io_command "fpunch"
+_require_xfs_sparse_inodes
+
+rm -f $seqres.full
+
+_scratch_mkfs "-d size=50m -m crc=1 -i sparse" |
+       _filter_mkfs > /dev/null 2> $tmp.mkfs
+. $tmp.mkfs    # for isize
+
+_scratch_mount
+
+# Calculate the fs inode chunk size based on the inode size and fixed 64-inode
+# record. This value is used as the target level of free space fragmentation
+# induced by the test (i.e., max size of free extents). We don't need to go
+# smaller than a full chunk because the XFS block allocator tacks on alignment
+# requirements to the size of the requested allocation. In other words, a chunk
+# sized free chunk is not enough to guarantee a successful chunk sized
+# allocation.
+CHUNK_SIZE=$((isize * 64))
+
+_consume_freesp $SCRATCH_MNT/spc
+
+# Now that the fs is nearly full, punch holes in every other $CHUNK_SIZE range
+# of the space consumer file. This should ensure that most freed extents are not
+# contiguous with any others and thus sufficiently fragment free space. After
+# each hole punch, allocate as many inodes as possible into the newly freed
+# space. Note that we start at the end of the file and work backwards as a
+# reverse allocation pattern increases the chances of both left and right sparse
+# record merges.
+offset=`stat -c "%s" $SCRATCH_MNT/spc`
+offset=$((offset - $CHUNK_SIZE * 2))
+while [ $offset -ge 0 ]; do
+       $XFS_IO_PROG -c "fpunch $offset $CHUNK_SIZE" $SCRATCH_MNT/spc \
+               2>> $seqres.full || _fail "fpunch failed"
+
+       # allocate as many inodes as possible
+       mkdir -p $SCRATCH_MNT/offset.$offset > /dev/null 2>&1
+       _alloc_inodes $SCRATCH_MNT/offset.$offset
+
+       offset=$((offset - $CHUNK_SIZE * 2))
+done
+
+# check that we've hit at least 95% inode usage
+iusepct=`_get_used_inode_percent $SCRATCH_MNT`
+_within_tolerance "iusepct" $iusepct 100 5 0 -v
+
+status=0
+exit
diff --git a/tests/xfs/076.out b/tests/xfs/076.out
new file mode 100644 (file)
index 0000000..f9a0436
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 076
+iusepct is in range
index 612e4f4b0ef50c73604cc2f2ab2526fe1627930a..4c20141ff9d83082e4d37a93ab8816f6d99f9d00 100644 (file)
@@ -73,6 +73,7 @@
 073 copy auto
 074 quick auto prealloc rw
 075 auto quick mount
+076 auto enospc
 078 growfs auto quick
 080 rw ioctl
 081 deprecated # log logprint quota