nopwrites on dmu_sync-ed blocks can result in a panic
After a device has been removed, any nopwrites for blocks on that indirect vdev should be ignored and a new block should be allocated. The original code attempted to handle this but used the wrong block pointer when checking for indirect vdevs and failed to check all DVAs. This change corrects both of these issues and modifies the test case to ensure that it properly tests nopwrites with device removal. Reviewed-by: Prakash Surya <prakash.surya@delphix.com> Reviewed-by: Matthew Ahrens <mahrens@delphix.com> Reviewed-by: Richard Yao <richard.yao@alumni.stonybrook.edu> Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: George Wilson <gwilson@delphix.com> Closes #14235
This commit is contained in:
parent
2c590bdede
commit
d7cf06a25d
|
@ -20,7 +20,7 @@
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||||
* Copyright (c) 2011, 2020 by Delphix. All rights reserved.
|
* Copyright (c) 2011, 2022 by Delphix. All rights reserved.
|
||||||
* Copyright (c) 2011 Nexenta Systems, Inc. All rights reserved.
|
* Copyright (c) 2011 Nexenta Systems, Inc. All rights reserved.
|
||||||
* Copyright (c) 2017, Intel Corporation.
|
* Copyright (c) 2017, Intel Corporation.
|
||||||
* Copyright (c) 2019, Klara Inc.
|
* Copyright (c) 2019, Klara Inc.
|
||||||
|
@ -2974,6 +2974,7 @@ zio_nop_write(zio_t *zio)
|
||||||
blkptr_t *bp_orig = &zio->io_bp_orig;
|
blkptr_t *bp_orig = &zio->io_bp_orig;
|
||||||
zio_prop_t *zp = &zio->io_prop;
|
zio_prop_t *zp = &zio->io_prop;
|
||||||
|
|
||||||
|
ASSERT(BP_IS_HOLE(bp));
|
||||||
ASSERT(BP_GET_LEVEL(bp) == 0);
|
ASSERT(BP_GET_LEVEL(bp) == 0);
|
||||||
ASSERT(!(zio->io_flags & ZIO_FLAG_IO_REWRITE));
|
ASSERT(!(zio->io_flags & ZIO_FLAG_IO_REWRITE));
|
||||||
ASSERT(zp->zp_nopwrite);
|
ASSERT(zp->zp_nopwrite);
|
||||||
|
@ -3007,8 +3008,7 @@ zio_nop_write(zio_t *zio)
|
||||||
ASSERT3U(BP_GET_PSIZE(bp), ==, BP_GET_PSIZE(bp_orig));
|
ASSERT3U(BP_GET_PSIZE(bp), ==, BP_GET_PSIZE(bp_orig));
|
||||||
ASSERT3U(BP_GET_LSIZE(bp), ==, BP_GET_LSIZE(bp_orig));
|
ASSERT3U(BP_GET_LSIZE(bp), ==, BP_GET_LSIZE(bp_orig));
|
||||||
ASSERT(zp->zp_compress != ZIO_COMPRESS_OFF);
|
ASSERT(zp->zp_compress != ZIO_COMPRESS_OFF);
|
||||||
ASSERT(memcmp(&bp->blk_prop, &bp_orig->blk_prop,
|
ASSERT3U(bp->blk_prop, ==, bp_orig->blk_prop);
|
||||||
sizeof (uint64_t)) == 0);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we're overwriting a block that is currently on an
|
* If we're overwriting a block that is currently on an
|
||||||
|
@ -3016,11 +3016,13 @@ zio_nop_write(zio_t *zio)
|
||||||
* allow a new block to be allocated on a concrete vdev.
|
* allow a new block to be allocated on a concrete vdev.
|
||||||
*/
|
*/
|
||||||
spa_config_enter(zio->io_spa, SCL_VDEV, FTAG, RW_READER);
|
spa_config_enter(zio->io_spa, SCL_VDEV, FTAG, RW_READER);
|
||||||
vdev_t *tvd = vdev_lookup_top(zio->io_spa,
|
for (int d = 0; d < BP_GET_NDVAS(bp_orig); d++) {
|
||||||
DVA_GET_VDEV(&bp->blk_dva[0]));
|
vdev_t *tvd = vdev_lookup_top(zio->io_spa,
|
||||||
if (tvd->vdev_ops == &vdev_indirect_ops) {
|
DVA_GET_VDEV(&bp_orig->blk_dva[d]));
|
||||||
spa_config_exit(zio->io_spa, SCL_VDEV, FTAG);
|
if (tvd->vdev_ops == &vdev_indirect_ops) {
|
||||||
return (zio);
|
spa_config_exit(zio->io_spa, SCL_VDEV, FTAG);
|
||||||
|
return (zio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
spa_config_exit(zio->io_spa, SCL_VDEV, FTAG);
|
spa_config_exit(zio->io_spa, SCL_VDEV, FTAG);
|
||||||
|
|
||||||
|
|
|
@ -57,12 +57,12 @@ function verify_nopwrite
|
||||||
within_percent $origin_used $snap_refer $high || return 1
|
within_percent $origin_used $snap_refer $high || return 1
|
||||||
|
|
||||||
#
|
#
|
||||||
# The comparisons below should pass regardless of nopwrite. They're
|
# The comparisons below should be within 90% regardless of nopwrite.
|
||||||
# here for sanity.
|
# They're here for sanity.
|
||||||
#
|
#
|
||||||
typeset deadlist=$(zdb -Pddd $clone | awk '/Deadlist:/ {print $2}')
|
typeset deadlist=$(zdb -Pddd $clone | awk '/Deadlist:/ {print $2}')
|
||||||
within_percent $deadlist $clone_written $high || return 1
|
within_percent $deadlist $clone_written 90 || return 1
|
||||||
within_percent $snap_refer $snap_written $high || return 1
|
within_percent $snap_refer $snap_written 90 || return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright (c) 2019 by Delphix. All rights reserved.
|
# Copyright (c) 2019, 2022 by Delphix. All rights reserved.
|
||||||
#
|
#
|
||||||
|
|
||||||
. $STF_SUITE/include/libtest.shlib
|
. $STF_SUITE/include/libtest.shlib
|
||||||
|
@ -24,19 +24,35 @@
|
||||||
|
|
||||||
default_setup_noexit "$DISKS"
|
default_setup_noexit "$DISKS"
|
||||||
log_onexit default_cleanup_noexit
|
log_onexit default_cleanup_noexit
|
||||||
BLOCKSIZE=8192
|
|
||||||
|
#
|
||||||
|
# Randomly pick a device to remove
|
||||||
|
#
|
||||||
|
DISK_TOKENS=( $DISKS )
|
||||||
|
DISK_INDEX_TO_REMOVE=$((RANDOM%${#DISK_TOKENS[@]}))
|
||||||
|
DISK_TO_REMOVE=${DISK_TOKENS[${DISK_INDEX_TO_REMOVE}]}
|
||||||
|
|
||||||
origin="$TESTPOOL/$TESTFS"
|
origin="$TESTPOOL/$TESTFS"
|
||||||
|
|
||||||
log_must zfs set compress=on $origin
|
log_must zfs set compress=on $origin
|
||||||
log_must zfs set checksum=skein $origin
|
log_must zfs set checksum=skein $origin
|
||||||
|
|
||||||
|
log_must zfs set copies=1 $origin
|
||||||
log_must zfs set recordsize=8k $origin
|
log_must zfs set recordsize=8k $origin
|
||||||
dd if=/dev/urandom of=$TESTDIR/file_8k bs=1024k count=$MEGS oflag=sync \
|
dd if=/dev/urandom of=$TESTDIR/file_8k bs=1024k count=$MEGS oflag=sync \
|
||||||
conv=notrunc >/dev/null 2>&1 || log_fail "dd into $TESTDIR/file failed."
|
conv=notrunc >/dev/null 2>&1 || log_fail "dd into $TESTDIR/file failed."
|
||||||
|
log_must zfs set copies=3 $origin
|
||||||
|
dd if=/dev/urandom of=$TESTDIR/file_8k_copies bs=1024k count=$MEGS oflag=sync \
|
||||||
|
conv=notrunc >/dev/null 2>&1 || log_fail "dd into $TESTDIR/file failed."
|
||||||
|
|
||||||
|
log_must zfs set copies=1 $origin
|
||||||
log_must zfs set recordsize=128k $origin
|
log_must zfs set recordsize=128k $origin
|
||||||
dd if=/dev/urandom of=$TESTDIR/file_128k bs=1024k count=$MEGS oflag=sync \
|
dd if=/dev/urandom of=$TESTDIR/file_128k bs=1024k count=$MEGS oflag=sync \
|
||||||
conv=notrunc >/dev/null 2>&1 || log_fail "dd into $TESTDIR/file failed."
|
conv=notrunc >/dev/null 2>&1 || log_fail "dd into $TESTDIR/file failed."
|
||||||
|
log_must zfs set copies=3 $origin
|
||||||
|
dd if=/dev/urandom of=$TESTDIR/file_128k_copies bs=1024k \
|
||||||
|
count=$MEGS oflag=sync conv=notrunc >/dev/null 2>&1 || \
|
||||||
|
log_fail "dd into $TESTDIR/file failed."
|
||||||
|
|
||||||
zfs snapshot $origin@a || log_fail "zfs snap failed"
|
zfs snapshot $origin@a || log_fail "zfs snap failed"
|
||||||
log_must zfs clone $origin@a $origin/clone
|
log_must zfs clone $origin@a $origin/clone
|
||||||
|
@ -44,22 +60,33 @@ log_must zfs clone $origin@a $origin/clone
|
||||||
#
|
#
|
||||||
# Verify that nopwrites work prior to removal
|
# Verify that nopwrites work prior to removal
|
||||||
#
|
#
|
||||||
|
log_must zfs set copies=1 $origin/clone
|
||||||
log_must zfs set recordsize=8k $origin/clone
|
log_must zfs set recordsize=8k $origin/clone
|
||||||
dd if=/$TESTDIR/file_8k of=/$TESTDIR/clone/file_8k bs=1024k \
|
dd if=/$TESTDIR/file_8k of=/$TESTDIR/clone/file_8k bs=1024k \
|
||||||
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
log_must verify_nopwrite $origin $origin@a $origin/clone
|
log_must verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
log_must zfs set copies=3 $origin/clone
|
||||||
|
dd if=/$TESTDIR/file_8k_copies of=/$TESTDIR/clone/file_8k_copies bs=1024k \
|
||||||
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
|
log_must verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
|
||||||
|
log_must zfs set copies=1 $origin/clone
|
||||||
log_must zfs set recordsize=128k $origin/clone
|
log_must zfs set recordsize=128k $origin/clone
|
||||||
dd if=/$TESTDIR/file_128k of=/$TESTDIR/clone/file_128k bs=1024k \
|
dd if=/$TESTDIR/file_128k of=/$TESTDIR/clone/file_128k bs=1024k \
|
||||||
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
log_must verify_nopwrite $origin $origin@a $origin/clone
|
log_must verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
log_must zfs set copies=3 $origin/clone
|
||||||
|
dd if=/$TESTDIR/file_128k_copies of=/$TESTDIR/clone/file_128k_copies bs=1024k \
|
||||||
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
|
log_must verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
|
||||||
#
|
#
|
||||||
# Remove a device before testing nopwrites again
|
# Remove a device before testing nopwrites again
|
||||||
#
|
#
|
||||||
log_must zpool remove $TESTPOOL $REMOVEDISK
|
log_note "Removing: $DISK_TO_REMOVE"
|
||||||
|
log_must zpool remove $TESTPOOL $DISK_TO_REMOVE
|
||||||
log_must wait_for_removal $TESTPOOL
|
log_must wait_for_removal $TESTPOOL
|
||||||
log_mustnot vdevs_in_pool $TESTPOOL $REMOVEDISK
|
log_mustnot vdevs_in_pool $TESTPOOL $DISK_TO_REMOVE
|
||||||
|
|
||||||
#
|
#
|
||||||
# Normally, we expect nopwrites to avoid allocating new blocks, but
|
# Normally, we expect nopwrites to avoid allocating new blocks, but
|
||||||
|
@ -71,17 +98,27 @@ log_mustnot vdevs_in_pool $TESTPOOL $REMOVEDISK
|
||||||
#
|
#
|
||||||
# Perform a direct zil nopwrite test
|
# Perform a direct zil nopwrite test
|
||||||
#
|
#
|
||||||
|
log_must zfs set copies=1 $origin/clone
|
||||||
log_must zfs set recordsize=8k $origin/clone
|
log_must zfs set recordsize=8k $origin/clone
|
||||||
dd if=/$TESTDIR/file_8k of=/$TESTDIR/clone/file_8k bs=1024k \
|
dd if=/$TESTDIR/file_8k of=/$TESTDIR/clone/file_8k bs=1024k \
|
||||||
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
log_must zfs set copies=3 $origin/clone
|
||||||
|
dd if=/$TESTDIR/file_8k_copies of=/$TESTDIR/clone/file_8k_copies bs=1024k \
|
||||||
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
|
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
|
||||||
#
|
#
|
||||||
# Perform an indirect zil nopwrite test
|
# Perform an indirect zil nopwrite test
|
||||||
#
|
#
|
||||||
|
log_must zfs set copies=1 $origin/clone
|
||||||
log_must zfs set recordsize=128k $origin/clone
|
log_must zfs set recordsize=128k $origin/clone
|
||||||
dd if=/$TESTDIR/file_128k of=/$TESTDIR/clone/file_128k bs=1024k \
|
dd if=/$TESTDIR/file_128k of=/$TESTDIR/clone/file_128k bs=1024k \
|
||||||
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
log_must zfs set copies=3 $origin/clone
|
||||||
|
dd if=/$TESTDIR/file_128k_copies of=/$TESTDIR/clone/file_128k_copies bs=1024k \
|
||||||
|
oflag=sync conv=notrunc >/dev/null 2>&1 || log_fail "dd failed."
|
||||||
|
log_mustnot verify_nopwrite $origin $origin@a $origin/clone
|
||||||
|
|
||||||
log_pass "Remove works with nopwrite."
|
log_pass "Remove works with nopwrite."
|
||||||
|
|
Loading…
Reference in New Issue