Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cmd/ztest.c
Original file line number Diff line number Diff line change
Expand Up @@ -4188,7 +4188,8 @@ ztest_device_removal(ztest_ds_t *zd, uint64_t id)
*/
error = spa_scan(spa, POOL_SCAN_SCRUB, 0);
if (error == 0) {
while (dsl_scan_scrubbing(spa_get_dsl(spa)))
while (dsl_scan_scrubbing(spa_get_dsl(spa)) ||
dsl_scan_resilvering(spa_get_dsl(spa)))
txg_wait_synced(spa_get_dsl(spa), 0);
}

Expand Down Expand Up @@ -6684,7 +6685,8 @@ ztest_fault_inject(ztest_ds_t *zd, uint64_t id)
if (injected && ztest_opts.zo_raid_do_expand) {
int error = spa_scan(spa, POOL_SCAN_SCRUB, 0);
if (error == 0) {
while (dsl_scan_scrubbing(spa_get_dsl(spa)))
while (dsl_scan_scrubbing(spa_get_dsl(spa)) ||
dsl_scan_resilvering(spa_get_dsl(spa)))
txg_wait_synced(spa_get_dsl(spa), 0);
}
}
Expand Down Expand Up @@ -6719,7 +6721,8 @@ ztest_scrub_impl(spa_t *spa)
if (error)
return (error);

while (dsl_scan_scrubbing(spa_get_dsl(spa)))
while (dsl_scan_scrubbing(spa_get_dsl(spa)) ||
dsl_scan_resilvering(spa_get_dsl(spa)))
txg_wait_synced(spa_get_dsl(spa), 0);

if (spa_approx_errlog_size(spa) > 0)
Expand Down
2 changes: 2 additions & 0 deletions include/sys/dsl_scan.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ typedef enum dsl_scan_flags {
DSF_VISIT_DS_AGAIN = 1<<0,
DSF_SCRUB_PAUSED = 1<<1,
DSF_SCRUB_THOROUGH = 1<<2,
DSF_SCRUB_REQUESTED = 1<<3, /* original request was a scrub */
} dsl_scan_flags_t;

typedef struct dsl_errorscrub_phys {
Expand Down Expand Up @@ -189,6 +190,7 @@ int dsl_scan_cancel(struct dsl_pool *);
int dsl_scan(struct dsl_pool *, pool_scan_func_t, uint64_t starttxg,
uint64_t txgend, dsl_scan_flags_t flags);
void dsl_scan_assess_vdev(struct dsl_pool *dp, vdev_t *vd);
boolean_t dsl_scan_is_scrub_requested(const dsl_scan_t *scn);
boolean_t dsl_scan_scrubbing(const struct dsl_pool *dp);
boolean_t dsl_errorscrubbing(const struct dsl_pool *dp);
boolean_t dsl_errorscrub_active(dsl_scan_t *scn);
Expand Down
29 changes: 25 additions & 4 deletions module/zfs/dsl_scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,13 @@ dsl_scan_resilver_scheduled(dsl_pool_t *dp)
(spa_async_tasks(dp->dp_spa) & SPA_ASYNC_RESILVER));
}

boolean_t
dsl_scan_is_scrub_requested(const dsl_scan_t *scn)
{
return (scn->scn_phys.scn_func == POOL_SCAN_SCRUB ||
scn->scn_phys.scn_flags & DSF_SCRUB_REQUESTED);
}

boolean_t
dsl_scan_scrubbing(const dsl_pool_t *dp)
{
Expand Down Expand Up @@ -805,14 +812,16 @@ dsl_errorscrub_is_paused(const dsl_scan_t *scn)
boolean_t
dsl_scan_is_paused_scrub(const dsl_scan_t *scn)
{
return (dsl_scan_scrubbing(scn->scn_dp) &&
return (dsl_scan_is_running(scn) &&
dsl_scan_is_scrub_requested(scn) &&
scn->scn_phys.scn_flags & DSF_SCRUB_PAUSED);
}

static boolean_t
dsl_scan_is_thorough_scrub(const dsl_scan_t *scn)
{
return (dsl_scan_scrubbing(scn->scn_dp) &&
return (dsl_scan_is_running(scn) &&
dsl_scan_is_scrub_requested(scn) &&
scn->scn_phys.scn_flags & DSF_SCRUB_THOROUGH);
}

Expand Down Expand Up @@ -1017,6 +1026,14 @@ dsl_scan_setup_sync(void *arg, dmu_tx_t *tx)
spa_event_notify(spa, NULL, aux,
ESC_ZFS_RESILVER_START);
nvlist_free(aux);
/*
* DTLs limit this scan to replacement ranges.
* Preserve the scrub request while using resilver
* repair semantics.
*/
if (setup_sync_arg->func == POOL_SCAN_SCRUB)
scn->scn_phys.scn_flags |= DSF_SCRUB_REQUESTED;
scn->scn_phys.scn_func = POOL_SCAN_RESILVER;
} else {
spa_event_notify(spa, NULL, NULL, ESC_ZFS_SCRUB_START);
}
Expand Down Expand Up @@ -1485,7 +1502,8 @@ dsl_scrub_pause_resume_check(void *arg, dmu_tx_t *tx)

if (*cmd == POOL_SCRUB_PAUSE) {
/* can't pause a scrub when there is no in-progress scrub */
if (!dsl_scan_scrubbing(dp))
if (!dsl_scan_is_running(scn) ||
!dsl_scan_is_scrub_requested(scn))
return (SET_ERROR(ENOENT));

/* can't pause a paused scrub */
Expand Down Expand Up @@ -5050,6 +5068,9 @@ dsl_scan_scrub_done(zio_t *zio)
{
spa_t *spa = zio->io_spa;
dsl_scan_io_queue_t *queue = zio->io_private;
boolean_t scrub_io = (zio->io_flags & ZIO_FLAG_SCRUB) ||
(spa->spa_dsl_pool->dp_scan->scn_phys.scn_flags &
DSF_SCRUB_REQUESTED);

abd_free(zio->io_abd);

Expand Down Expand Up @@ -5082,7 +5103,7 @@ dsl_scan_scrub_done(zio_t *zio)
*/
if (zio->io_error && (zio->io_error != ECKSUM ||
!(zio->io_flags & ZIO_FLAG_SPECULATIVE)) &&
!(zio->io_error == EACCES && (zio->io_flags & ZIO_FLAG_SCRUB) &&
!(zio->io_error == EACCES && scrub_io &&
!(zio->io_flags & ZIO_FLAG_RAW))) {
if (dsl_errorscrubbing(spa->spa_dsl_pool) &&
!dsl_errorscrub_is_paused(spa->spa_dsl_pool->dp_scan)) {
Expand Down
8 changes: 5 additions & 3 deletions module/zfs/spa.c
Original file line number Diff line number Diff line change
Expand Up @@ -9831,7 +9831,8 @@ spa_scrub_pause_resume(spa_t *spa, pool_scrub_cmd_t cmd)
{
ASSERT0(spa_config_held(spa, SCL_ALL, RW_WRITER));

if (dsl_scan_resilvering(spa->spa_dsl_pool))
if (dsl_scan_resilvering(spa->spa_dsl_pool) &&
!dsl_scan_is_scrub_requested(spa->spa_dsl_pool->dp_scan))
return (SET_ERROR(EBUSY));

return (dsl_scrub_set_pause_resume(spa->spa_dsl_pool, cmd));
Expand All @@ -9841,7 +9842,8 @@ int
spa_scan_stop(spa_t *spa)
{
ASSERT0(spa_config_held(spa, SCL_ALL, RW_WRITER));
if (dsl_scan_resilvering(spa->spa_dsl_pool))
if (dsl_scan_resilvering(spa->spa_dsl_pool) &&
!dsl_scan_is_scrub_requested(spa->spa_dsl_pool->dp_scan))
return (SET_ERROR(EBUSY));

return (dsl_scan_cancel(spa->spa_dsl_pool));
Expand Down Expand Up @@ -11755,7 +11757,7 @@ spa_activity_in_progress(spa_t *spa, zpool_wait_activity_t activity,
boolean_t scanning, paused, is_scrub;
dsl_scan_t *scn = spa->spa_dsl_pool->dp_scan;

is_scrub = (scn->scn_phys.scn_func == POOL_SCAN_SCRUB);
is_scrub = dsl_scan_is_scrub_requested(scn);
scanning = (scn->scn_phys.scn_state == DSS_SCANNING);
paused = dsl_scan_is_paused_scrub(scn);
*in_progress = (scanning && !paused &&
Expand Down
3 changes: 2 additions & 1 deletion module/zfs/spa_misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2847,7 +2847,8 @@ spa_scan_get_stats(spa_t *spa, pool_scan_stat_t *ps)
memset(ps, 0, sizeof (pool_scan_stat_t));

/* data stored on disk */
ps->pss_func = scn->scn_phys.scn_func;
ps->pss_func = dsl_scan_is_scrub_requested(scn) ? POOL_SCAN_SCRUB :
scn->scn_phys.scn_func;
ps->pss_state = scn->scn_phys.scn_state;
ps->pss_start_time = scn->scn_phys.scn_start_time;
ps->pss_end_time = scn->scn_phys.scn_end_time;
Expand Down
4 changes: 3 additions & 1 deletion module/zfs/zfs_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -6702,8 +6702,10 @@ zfs_ioc_pool_reopen(const char *pool, nvlist_t *innvl, nvlist_t *outnvl)
* Otherwise, let vdev_open() decided if a resilver is required.
*/

dsl_scan_t *scn = spa->spa_dsl_pool->dp_scan;
spa->spa_scrub_reopen = (!scrub_restart &&
dsl_scan_scrubbing(spa->spa_dsl_pool));
scn->scn_phys.scn_state == DSS_SCANNING &&
dsl_scan_is_scrub_requested(scn));
vdev_reopen(spa->spa_root_vdev);
spa->spa_scrub_reopen = B_FALSE;

Expand Down
3 changes: 2 additions & 1 deletion tests/runfiles/common.run
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,8 @@ tests = ['attach_import', 'attach_multiple', 'attach_rebuild',
'rebuild_disabled_feature', 'rebuild_multiple', 'rebuild_raidz',
'replace_import', 'replace_rebuild', 'replace_resilver',
'replace_resilver_sit_out', 'resilver_restart_001',
'resilver_restart_002', 'scrub_cancel']
'resilver_restart_002', 'scrub_cancel', 'scrub_resilver_classification',
'scrub_resilver_split']
tags = ['functional', 'replacement']

[tests/functional/reservation]
Expand Down
2 changes: 2 additions & 0 deletions tests/zfs-tests/tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/replacement/resilver_restart_001.ksh \
functional/replacement/resilver_restart_002.ksh \
functional/replacement/scrub_cancel.ksh \
functional/replacement/scrub_resilver_classification.ksh \
functional/replacement/scrub_resilver_split.ksh \
functional/replacement/setup.ksh \
functional/reservation/cleanup.ksh \
functional/reservation/reservation_001_pos.ksh \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/bin/ksh -p
# SPDX-License-Identifier: CDDL-1.0

#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source. A copy of the CDDL is also available via the Internet at
# https://opensource.org/license/CDDL-1.0.
#

# shellcheck disable=SC1091

. "$STF_SUITE"/include/libtest.shlib
. "$STF_SUITE"/tests/functional/replacement/replacement.cfg

#
# DESCRIPTION:
# Verify a DTL-limited scrub preserves scrub controls while using healing
# resilver I/O.
#
# STRATEGY:
# 1. Start a replacement with scan progress paused.
# 2. Complete its initial resilver under a checkpoint, preserving the
# replacement DTL, then discard the checkpoint.
# 3. Pause the DTL-limited scrub, export/import the pool, then resume
# and cancel it.
# 4. Run it again with read errors injected on the DTL-missing child.
# 5. Verify scrub status, resilver I/O, repair, detach, and data integrity.
#

verify_runnable "global"

function inject_count
{
zinject | awk '
/^ *[0-9]/ {
count++
inject = $NF
}
END {
if (count != 1)
exit 1
print inject
}'
}

function cleanup
{
zinject -c all >/dev/null 2>&1
set_tunable32 SCAN_SUSPEND_PROGRESS \
"$ORIG_SCAN_SUSPEND_PROGRESS" >/dev/null 2>&1
zpool checkpoint -d -w "$TESTPOOL1" >/dev/null 2>&1
destroy_pool "$TESTPOOL1"
rm -f "${VDEV_FILES[@]}" "$SPARE_VDEV_FILE"
}

log_assert "DTL-limited scrubs retain scrub controls and use resilver I/O"

ORIG_SCAN_SUSPEND_PROGRESS=$(get_tunable SCAN_SUSPEND_PROGRESS)

log_onexit cleanup

log_must zinject -c all
log_must truncate -s "$VDEV_FILE_SIZE" \
"${VDEV_FILES[0]}" "$SPARE_VDEV_FILE"
# Keep the replacement DTL available for the scrub instead of restarting a
# deferred resilver when the checkpointed initial scan completes.
log_must zpool create -f -o feature@resilver_defer=disabled \
"$TESTPOOL1" "${VDEV_FILES[0]}"
log_must zfs create "$TESTPOOL1/$TESTFS"

mntpnt=$(get_prop mountpoint "$TESTPOOL1/$TESTFS")
log_must dd if=/dev/urandom of="$mntpnt/file" bs=1M count=1
sync_pool "$TESTPOOL1"

# A checkpoint prevents the initial resilver from excising the target DTL.
log_must set_tunable32 SCAN_SUSPEND_PROGRESS 1
log_must zpool replace "$TESTPOOL1" "${VDEV_FILES[0]}" \
"$SPARE_VDEV_FILE"
log_must is_pool_resilvering "$TESTPOOL1"
log_must zpool checkpoint "$TESTPOOL1"
log_must set_tunable32 SCAN_SUSPEND_PROGRESS 0
log_must zpool wait -t resilver "$TESTPOOL1"
log_must is_pool_resilvered "$TESTPOOL1"
log_must zpool checkpoint -d -w "$TESTPOOL1"

pool_status=$(zpool status -P "$TESTPOOL1") ||
log_fail "unable to read pool status after checkpoint discard"
[[ "$pool_status" == *replacing-* &&
"$pool_status" == *"${VDEV_FILES[0]}"* &&
"$pool_status" == *"$SPARE_VDEV_FILE"* ]] ||
log_fail "replacement topology was not preserved: $pool_status"

# The effective resilver must retain the controls of the requested scrub.
log_must set_tunable32 SCAN_SUSPEND_PROGRESS 1
log_must zpool scrub -t "$TESTPOOL1"
log_must is_pool_scrubbing "$TESTPOOL1"
log_must zpool scrub -p "$TESTPOOL1"
log_must is_pool_scrub_paused "$TESTPOOL1"
log_must zpool export "$TESTPOOL1"
log_must zpool import -d "$TEST_BASE_DIR" "$TESTPOOL1"
log_must is_pool_scrub_paused "$TESTPOOL1"
log_must zpool scrub -t "$TESTPOOL1"
log_must is_pool_scrubbing "$TESTPOOL1"
log_must zpool scrub -s "$TESTPOOL1"
log_must is_pool_scrub_stopped "$TESTPOOL1"

# Resilver I/O must read the valid original child and repair the missing target.
log_must zinject -d "$SPARE_VDEV_FILE" -e io -T read -f 100 "$TESTPOOL1"
log_must zpool events -c
log_must zpool scrub -t "$TESTPOOL1"
log_must is_pool_scrubbing "$TESTPOOL1"

log_must set_tunable32 SCAN_SUSPEND_PROGRESS 0
log_must zpool wait -t scrub "$TESTPOOL1"
target_inject_count=$(inject_count) ||
log_fail "zinject did not report exactly one active rule"
log_must zinject -c all
[[ "$target_inject_count" == 0 ]] ||
log_fail "scan read the replacement target (inject=$target_inject_count)"
log_must is_pool_scrubbed "$TESTPOOL1"

log_must zpool wait -t replace "$TESTPOOL1"
pool_status=$(zpool status -P "$TESTPOOL1") ||
log_fail "unable to read pool status after scrub"
[[ "$pool_status" != *replacing-* &&
"$pool_status" != *"${VDEV_FILES[0]}"* &&
"$pool_status" == *"$SPARE_VDEV_FILE"* ]] ||
log_fail "replacement did not complete: $pool_status"

resilver_finish=$(zpool events | \
awk '/sysevent.fs.zfs.resilver_finish/ { count++ } END { print count + 0 }')
[[ "$resilver_finish" == 1 ]] ||
log_fail "expected one resilver finish event, found $resilver_finish"

last_scrubbed=$(zpool get -H -o value last_scrubbed_txg "$TESTPOOL1")
[[ "$last_scrubbed" == 0 ]] ||
log_fail "DTL-limited scrub advanced last_scrubbed_txg to $last_scrubbed"

log_must check_pool_status "$TESTPOOL1" "scan" \
"scrub repaired [1-9].*with 0 errors"
log_must check_pool_status "$TESTPOOL1" "errors" "No known data errors"
log_must check_pool_device "$TESTPOOL1" "$SPARE_VDEV_FILE" "0 *0 *0"
log_must zdb -cdui "$TESTPOOL1/$TESTFS"

log_pass "DTL-limited scrubs retain scrub controls and use resilver I/O"
Loading
Loading