mmp: skip non-writeable vdevs during activity check

The import-time MMP activity check added by c710f8792 writes an
uberblock to each top-level vdev and requires a matching number of
good writes before the pool is claimed.  Two vdev types that carry
no writeable device break this:

  - A hole vdev (left by removing a log) and an indirect vdev (left
    by removing a data device) are counted in the required-write
    total but can never be written, so good_writes never reaches
    req_writes.  The activity check then spuriously fails and the
    pool is reported as held by another host with hostid 0.

  - mmp_claim_uberblock() also issues a zio_flush() to the root vdev
    after the writes.  zio_flush() recurses to every leaf, and an
    indirect vdev is a childless top-level vdev, so it is issued a
    ZIO_TYPE_FLUSH.  That trips the ZIO_TYPE_WRITE assertion in
    vdev_indirect_io_start() and panics.

Skip hole and indirect vdevs when counting required writes, and skip
non-concrete vdevs in zio_flush() as they have no device to flush.

Add hole, indirect, log, cache, and spare vdevs to the pool used by
the mmp_inactive_import, mmp_exported_import, and mmp_concurrent_import
tests so the activity check exercises these vdev types.  The enriched
layout is opt-in, leaving multihost_history on the simple two-device
pool it relies on.

Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Michael Heller <[email protected]>
Closes #18823
Closes #18835
This commit is contained in:
mkhllr authored and GitHub committed 2026-07-22 14:45:26 -07:00
1 parent 87e2e40475
commit 1ac3f2786b
6 files changed
+85 -51

No files matched your search

+3 -1
View File
@@ -570,7 +570,9 @@ mmp_claim_uberblock_sync(zio_t *zio, uint64_t *good_writes,
for (uint64_t c = 0; c < vd->vdev_children; c++) {
vdev_t *cvd = vd->vdev_child[c];
if (cvd->vdev_islog || cvd->vdev_isspare || cvd->vdev_isl2cache)
if (cvd->vdev_islog || cvd->vdev_isspare ||
cvd->vdev_isl2cache || cvd->vdev_ishole ||
cvd->vdev_ops == &vdev_indirect_ops)
continue;
if (cvd->vdev_top == cvd) {
+9
View File
@@ -1732,6 +1732,15 @@ zio_flush(zio_t *pio, vdev_t *vd)
return;
if (vd->vdev_children == 0) {
/*
* A non-concrete vdev (a hole or indirect vdev left behind
* by removing a log or data device) has no leaf device to
* flush. Skip it; issuing a flush to an indirect vdev would
* trip the ZIO_TYPE_WRITE assertion in
* vdev_indirect_io_start().
*/
if (!vdev_is_concrete(vd))
return;
zio_nowait(zio_create(pio, vd->vdev_spa, 0, NULL, NULL, 0, 0,
NULL, NULL, ZIO_TYPE_FLUSH, ZIO_PRIORITY_NOW, flags, vd, 0,
NULL, ZIO_STAGE_OPEN, ZIO_FLUSH_PIPELINE));
@@ -83,10 +83,11 @@ function mmp_clear_hostid
rm -f $HOSTID_FILE
}
function mmp_pool_create_simple # pool dir
function mmp_pool_create_simple # pool dir enriched
{
typeset pool=${1:-$MMP_POOL}
typeset dir=${2:-$MMP_DIR}
typeset enriched=${3:-""}
log_must mkdir -p $dir
log_must rm -f $dir/*
@@ -96,6 +97,28 @@ function mmp_pool_create_simple # pool dir
log_must mmp_set_hostid $HOSTID1
log_must zpool create -f -o cachefile=$MMP_CACHE $pool \
mirror $dir/vdev1 $dir/vdev2
if [[ -n "$enriched" ]]; then
#
# Add then remove a data vdev to leave an indirect vdev, and
# add then remove a log to leave a hole vdev, so the pool
# exercises the non-writeable top-level vdev types skipped by
# the MMP claim check. Round it out with a log, a cache, and
# a spare vdev.
#
log_must truncate -s $MINVDEVSIZE $dir/vdev3 $dir/vdev4 \
$dir/vdev5 $dir/vdev6 $dir/vdev7
log_must zpool add -f $pool $dir/vdev3
log_must zpool remove $pool $dir/vdev3
log_must zpool wait -t remove $pool
log_must sync_pool $pool
log_must zpool add -f $pool log $dir/vdev4
log_must zpool remove $pool $dir/vdev4
log_must zpool add -f $pool log $dir/vdev5
log_must zpool add -f $pool cache $dir/vdev6
log_must zpool add -f $pool spare $dir/vdev7
fi
log_must zpool set multihost=on $pool
}
@@ -148,7 +171,7 @@ function mmp_pool_set_hostid # pool hostid
log_must mmp_clear_hostid
log_must mmp_set_hostid $hostid
log_must zpool export $pool
log_must zpool import $pool
log_must zpool import -d $MMP_DIR $pool
return 0
}
@@ -93,7 +93,7 @@ log_assert "multihost=on concurrent imports"
log_onexit cleanup
# 1. Create a multihost enabled pool with HOSTID1
mmp_pool_create_simple $MMP_POOL $MMP_DIR
mmp_pool_create_simple $MMP_POOL $MMP_DIR enriched
log_must zpool export -F $MMP_POOL
# 2. zhack imports: $HOSTID1 (matching) and $HOSTID1 (matching)
@@ -40,34 +40,34 @@ verify_runnable "both"
function cleanup
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
mmp_pool_destroy $MMP_POOL $MMP_DIR
log_must mmp_clear_hostid
}
log_assert "multihost=on|off activity checks exported pool"
log_onexit cleanup
# 1. Create a zpool
log_must mmp_set_hostid $HOSTID1
log_must zpool create -f $TESTPOOL $DISK
# 1. Create an enriched zpool (hole, indirect, log, cache, and spare
# vdevs) so the activity checks exercise the non-writeable types.
mmp_pool_create_simple $MMP_POOL $MMP_DIR enriched
# 2. Verify multihost=off and hostids match (no activity check)
log_note "Verify multihost=off and hostids match (no activity check)"
log_must zpool set multihost=off $TESTPOOL
log_must zpool set multihost=off $MMP_POOL
for opt in "" "-f"; do
log_must zpool export $TESTPOOL
log_must import_no_activity_check $TESTPOOL $opt
log_must zpool export $MMP_POOL
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 3. Verify multihost=off and hostids differ (no activity check)
log_note "Verify multihost=off and hostids differ (no activity check)"
for opt in "" "-f"; do
log_must mmp_pool_set_hostid $TESTPOOL $HOSTID1
log_must zpool export $TESTPOOL
log_must mmp_pool_set_hostid $MMP_POOL $HOSTID1
log_must zpool export $MMP_POOL
log_must mmp_clear_hostid
log_must mmp_set_hostid $HOSTID2
log_must import_no_activity_check $TESTPOOL $opt
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 4. Verify multihost=off and hostid zero allowed (no activity check)
@@ -75,39 +75,39 @@ log_note "Verify multihost=off and hostid zero allowed (no activity check)"
log_must mmp_clear_hostid
for opt in "" "-f"; do
log_must zpool export $TESTPOOL
log_must import_no_activity_check $TESTPOOL $opt
log_must zpool export $MMP_POOL
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 5. Verify multihost=on and hostids match (no activity check)
log_note "Verify multihost=on and hostids match (no activity check)"
log_must mmp_pool_set_hostid $TESTPOOL $HOSTID1
log_must zpool set multihost=on $TESTPOOL
log_must mmp_pool_set_hostid $MMP_POOL $HOSTID1
log_must zpool set multihost=on $MMP_POOL
for opt in "" "-f"; do
log_must zpool export $TESTPOOL
log_must import_no_activity_check $TESTPOOL $opt
log_must zpool export $MMP_POOL
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 6. Verify multihost=on and hostids differ (activity check)
log_note "Verify multihost=on and hostids differ (activity check)"
for opt in "" "-f"; do
log_must mmp_pool_set_hostid $TESTPOOL $HOSTID1
log_must zpool export $TESTPOOL
log_must mmp_pool_set_hostid $MMP_POOL $HOSTID1
log_must zpool export $MMP_POOL
log_must mmp_clear_hostid
log_must mmp_set_hostid $HOSTID2
log_must import_activity_check $TESTPOOL $opt
log_must import_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 7. Verify multihost=on and hostid zero fails (no activity check)
log_note "Verify multihost=on and hostid zero fails (no activity check)"
log_must zpool export $TESTPOOL
log_must zpool export $MMP_POOL
log_must mmp_clear_hostid
for opt in "" "-f"; do
MMP_IMPORTED_MSG="Set a unique system hostid"
log_must check_pool_import $TESTPOOL "" "action" "$MMP_IMPORTED_MSG"
log_mustnot import_no_activity_check $TESTPOOL $opt
log_must check_pool_import $MMP_POOL "-d $MMP_DIR" "action" "$MMP_IMPORTED_MSG"
log_mustnot import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
log_pass "multihost=on|off exported pool activity checks passed"
@@ -42,7 +42,7 @@ verify_runnable "both"
function cleanup
{
datasetexists $TESTPOOL && destroy_pool $TESTPOOL
mmp_pool_destroy $MMP_POOL $MMP_DIR
log_must mmp_clear_hostid
log_must set_tunable64 MULTIHOST_INTERVAL $MMP_INTERVAL_DEFAULT
}
@@ -50,63 +50,63 @@ function cleanup
log_assert "multihost=on|off inactive pool activity checks"
log_onexit cleanup
# 1. Create a zpool
log_must mmp_set_hostid $HOSTID1
log_must zpool create -f $TESTPOOL $DISK
# 1. Create an enriched zpool (hole, indirect, log, cache, and spare
# vdevs) so the activity checks exercise the non-writeable types.
mmp_pool_create_simple $MMP_POOL $MMP_DIR enriched
# 2. Verify multihost=off and hostids match (no activity check)
log_note "Verify multihost=off and hostids match (no activity check)"
log_must zpool set multihost=off $TESTPOOL
log_must zpool set multihost=off $MMP_POOL
for opt in "" "-f"; do
log_must zpool export -F $TESTPOOL
log_must import_no_activity_check $TESTPOOL $opt
log_must zpool export -F $MMP_POOL
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 3. Verify multihost=off and hostids differ (no activity check)
log_note "Verify multihost=off and hostids differ (no activity check)"
log_must zpool export -F $TESTPOOL
log_must zpool export -F $MMP_POOL
log_must mmp_clear_hostid
log_must mmp_set_hostid $HOSTID2
log_mustnot import_no_activity_check $TESTPOOL ""
log_must import_no_activity_check $TESTPOOL "-f"
log_mustnot import_no_activity_check $MMP_POOL "-d $MMP_DIR"
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR -f"
# 4. Verify multihost=off and hostid zero allowed (no activity check)
log_note "Verify multihost=off and hostid zero allowed (no activity check)"
log_must zpool export -F $TESTPOOL
log_must zpool export -F $MMP_POOL
log_must mmp_clear_hostid
log_mustnot import_no_activity_check $TESTPOOL ""
log_must import_no_activity_check $TESTPOOL "-f"
log_mustnot import_no_activity_check $MMP_POOL "-d $MMP_DIR"
log_must import_no_activity_check $MMP_POOL "-d $MMP_DIR -f"
# 5. Verify multihost=on and hostids match (activity check)
log_note "Verify multihost=on and hostids match (activity check)"
log_must mmp_pool_set_hostid $TESTPOOL $HOSTID1
log_must zpool set multihost=on $TESTPOOL
log_must mmp_pool_set_hostid $MMP_POOL $HOSTID1
log_must zpool set multihost=on $MMP_POOL
for opt in "" "-f"; do
log_must zpool export -F $TESTPOOL
log_must import_activity_check $TESTPOOL $opt
log_must zpool export -F $MMP_POOL
log_must import_activity_check $MMP_POOL "-d $MMP_DIR $opt"
done
# 6. Verify multihost=on and hostids differ (activity check)
log_note "Verify multihost=on and hostids differ (activity check)"
log_must zpool export -F $TESTPOOL
log_must zpool export -F $MMP_POOL
log_must mmp_clear_hostid
log_must mmp_set_hostid $HOSTID2
log_mustnot import_activity_check $TESTPOOL ""
log_must import_activity_check $TESTPOOL "-f"
log_mustnot import_activity_check $MMP_POOL "-d $MMP_DIR"
log_must import_activity_check $MMP_POOL "-d $MMP_DIR -f"
# 7. Verify mmp_write and mmp_fail are set correctly
log_note "Verify mmp_write and mmp_fail are set correctly"
log_must zpool export -F $TESTPOOL
log_must verify_mmp_write_fail_present ${DISK[0]}
log_must zpool export -F $MMP_POOL
log_must verify_mmp_write_fail_present $MMP_DIR/vdev1
# 8. Verify multihost=on and hostid zero fails (no activity check)
log_note "Verify multihost=on and hostid zero fails (no activity check)"
log_must mmp_clear_hostid
MMP_IMPORTED_MSG="Set a unique system hostid"
log_must check_pool_import $TESTPOOL "-f" "action" "$MMP_IMPORTED_MSG"
log_mustnot import_no_activity_check $TESTPOOL "-f"
log_must check_pool_import $MMP_POOL "-d $MMP_DIR -f" "action" "$MMP_IMPORTED_MSG"
log_mustnot import_no_activity_check $MMP_POOL "-d $MMP_DIR -f"
# 9. Verify activity check duration based on mmp_write and mmp_fail
# Specify a short test via tunables but import pool imported while
@@ -115,6 +115,6 @@ log_note "Verify activity check duration based on mmp_write and mmp_fail"
log_must set_tunable64 MULTIHOST_INTERVAL $MMP_INTERVAL_MIN
log_must mmp_clear_hostid
log_must mmp_set_hostid $HOSTID1
log_must import_activity_check $TESTPOOL "-f"
log_must import_activity_check $MMP_POOL "-d $MMP_DIR -f"
log_pass "multihost=on|off inactive pool activity checks passed"