mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 19:34:43 +01:00
zpool: accept more redundant special and dedup vdevs
The replication check required a special or dedup vdev to tolerate exactly as many device failures as the normal vdevs in the pool. A 3-way mirror special vdev on a raidz1 pool, or on a 2-way mirror pool, was rejected as a mismatch, and the only way past it was -f, which also overrides unrelated checks. Accept a special or dedup vdev which tolerates at least as many failures as the normal vdevs of a redundant pool. Special and dedup vdevs are now compared with the normal vdevs rather than with whichever vdev happens to precede them, so normal vdevs are still required to match each other. Less redundant special or dedup vdevs, and redundant ones added to a non-redundant pool, are still rejected. As a side effect, a pool that already has a more redundant special vdev is now considered consistent, so later additions to it are checked instead of being skipped. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Andrey Mnatsakanov <[email protected]> Assisted-by: Claude Code Closes #12596 Closes #19165
This commit is contained in:
1 parent
a58b0d52a4
commit
1d9c32c160
6 files changed
+227
-115
No files matched your search
+144
-107
@@ -407,6 +407,7 @@ typedef struct replication_level {
|
||||
const char *zprl_type;
|
||||
uint64_t zprl_children;
|
||||
uint64_t zprl_parity;
|
||||
boolean_t zprl_bias;
|
||||
} replication_level_t;
|
||||
|
||||
#define ZPOOL_FUZZ (16 * 1024 * 1024)
|
||||
@@ -445,6 +446,123 @@ is_raidz_draid(replication_level_t *a, replication_level_t *b)
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Number of device failures a toplevel vdev can tolerate.
|
||||
*/
|
||||
static uint64_t
|
||||
rep_failures(const replication_level_t *rep)
|
||||
{
|
||||
if (strcmp(rep->zprl_type, VDEV_TYPE_MIRROR) == 0)
|
||||
return (rep->zprl_children - 1);
|
||||
|
||||
return (rep->zprl_parity);
|
||||
}
|
||||
|
||||
/*
|
||||
* A special or dedup vdev need not match the layout of the normal vdevs in
|
||||
* a redundant pool, provided it can tolerate at least as many device
|
||||
* failures. Returns B_TRUE if one of 'a' and 'b' is such a vdev and the
|
||||
* other is a normal vdev which it satisfies.
|
||||
*/
|
||||
static boolean_t
|
||||
is_bias_more_redundant(const replication_level_t *a,
|
||||
const replication_level_t *b)
|
||||
{
|
||||
const replication_level_t *bias, *normal;
|
||||
|
||||
if (a->zprl_bias == b->zprl_bias)
|
||||
return (B_FALSE);
|
||||
|
||||
bias = a->zprl_bias ? a : b;
|
||||
normal = a->zprl_bias ? b : a;
|
||||
|
||||
return (rep_failures(normal) > 0 &&
|
||||
rep_failures(bias) >= rep_failures(normal));
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare the replication levels of two toplevel vdevs. Returns B_TRUE if
|
||||
* they are consistent. Otherwise, if 'fatal' is set, an error message is
|
||||
* displayed describing the mismatch.
|
||||
*/
|
||||
static boolean_t
|
||||
rep_consistent(replication_level_t *lastrep, replication_level_t *rep,
|
||||
boolean_t fatal)
|
||||
{
|
||||
replication_level_t *raidz, *mirror;
|
||||
|
||||
if (is_bias_more_redundant(lastrep, rep))
|
||||
return (B_TRUE);
|
||||
|
||||
if (is_raidz_mirror(lastrep, rep, &raidz, &mirror) ||
|
||||
is_raidz_mirror(rep, lastrep, &raidz, &mirror)) {
|
||||
/*
|
||||
* Accepted raidz and mirror when they can
|
||||
* handle the same number of disk failures.
|
||||
*/
|
||||
if (raidz->zprl_parity != mirror->zprl_children - 1) {
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"%s and %s vdevs with different "
|
||||
"redundancy, %llu vs. %llu (%llu-way) "
|
||||
"are present\n"),
|
||||
raidz->zprl_type,
|
||||
mirror->zprl_type,
|
||||
(u_longlong_t)raidz->zprl_parity,
|
||||
(u_longlong_t)mirror->zprl_children - 1,
|
||||
(u_longlong_t)mirror->zprl_children);
|
||||
return (B_FALSE);
|
||||
}
|
||||
} else if (is_raidz_draid(lastrep, rep)) {
|
||||
/*
|
||||
* Accepted raidz and draid when they can
|
||||
* handle the same number of disk failures.
|
||||
*/
|
||||
if (lastrep->zprl_parity != rep->zprl_parity) {
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"%s and %s vdevs with different "
|
||||
"redundancy, %llu vs. %llu are present\n"),
|
||||
lastrep->zprl_type,
|
||||
rep->zprl_type,
|
||||
(u_longlong_t)lastrep->zprl_parity,
|
||||
(u_longlong_t)rep->zprl_parity);
|
||||
return (B_FALSE);
|
||||
}
|
||||
} else if (strcmp(lastrep->zprl_type, rep->zprl_type) != 0) {
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %s and %s vdevs are present\n"),
|
||||
lastrep->zprl_type, rep->zprl_type);
|
||||
return (B_FALSE);
|
||||
} else if (lastrep->zprl_parity != rep->zprl_parity) {
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %llu and %llu device parity "
|
||||
"%s vdevs are present\n"),
|
||||
(u_longlong_t)lastrep->zprl_parity,
|
||||
(u_longlong_t)rep->zprl_parity,
|
||||
rep->zprl_type);
|
||||
return (B_FALSE);
|
||||
} else if (lastrep->zprl_children != rep->zprl_children) {
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %llu-way and %llu-way %s "
|
||||
"vdevs are present\n"),
|
||||
(u_longlong_t)lastrep->zprl_children,
|
||||
(u_longlong_t)rep->zprl_children,
|
||||
rep->zprl_type);
|
||||
return (B_FALSE);
|
||||
}
|
||||
|
||||
return (B_TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a list of toplevel vdevs, return the current replication level. If
|
||||
* the config is inconsistent, then NULL is returned. If 'fatal' is set, then
|
||||
@@ -460,9 +578,10 @@ get_replication(nvlist_t *nvroot, boolean_t fatal)
|
||||
nvlist_t *nv;
|
||||
const char *type;
|
||||
replication_level_t lastrep = {0};
|
||||
replication_level_t lastbias = {0};
|
||||
replication_level_t rep;
|
||||
replication_level_t *ret;
|
||||
replication_level_t *raidz, *mirror;
|
||||
replication_level_t *same, *other;
|
||||
boolean_t dontreport;
|
||||
|
||||
ret = safe_malloc(sizeof (replication_level_t));
|
||||
@@ -657,116 +776,34 @@ get_replication(nvlist_t *nvroot, boolean_t fatal)
|
||||
|
||||
/*
|
||||
* At this point, we have the replication of the last toplevel
|
||||
* vdev in 'rep'. Compare it to 'lastrep' to see if it is
|
||||
* different.
|
||||
* vdev in 'rep'. Compare it to the last vdev of the same class
|
||||
* (normal, or special and dedup), and to the last vdev of the
|
||||
* other class.
|
||||
*/
|
||||
if (lastrep.zprl_type != NULL) {
|
||||
if (is_raidz_mirror(&lastrep, &rep, &raidz, &mirror) ||
|
||||
is_raidz_mirror(&rep, &lastrep, &raidz, &mirror)) {
|
||||
/*
|
||||
* Accepted raidz and mirror when they can
|
||||
* handle the same number of disk failures.
|
||||
*/
|
||||
if (raidz->zprl_parity !=
|
||||
mirror->zprl_children - 1) {
|
||||
if (ret != NULL)
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication "
|
||||
"level: "
|
||||
"%s and %s vdevs with "
|
||||
"different redundancy, "
|
||||
"%llu vs. %llu (%llu-way) "
|
||||
"are present\n"),
|
||||
raidz->zprl_type,
|
||||
mirror->zprl_type,
|
||||
(u_longlong_t)
|
||||
raidz->zprl_parity,
|
||||
(u_longlong_t)
|
||||
mirror->zprl_children - 1,
|
||||
(u_longlong_t)
|
||||
mirror->zprl_children);
|
||||
else
|
||||
return (NULL);
|
||||
}
|
||||
} else if (is_raidz_draid(&lastrep, &rep)) {
|
||||
/*
|
||||
* Accepted raidz and draid when they can
|
||||
* handle the same number of disk failures.
|
||||
*/
|
||||
if (lastrep.zprl_parity != rep.zprl_parity) {
|
||||
if (ret != NULL)
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication "
|
||||
"level: %s and %s vdevs "
|
||||
"with different "
|
||||
"redundancy, %llu vs. "
|
||||
"%llu are present\n"),
|
||||
lastrep.zprl_type,
|
||||
rep.zprl_type,
|
||||
(u_longlong_t)
|
||||
lastrep.zprl_parity,
|
||||
(u_longlong_t)
|
||||
rep.zprl_parity);
|
||||
else
|
||||
return (NULL);
|
||||
}
|
||||
} else if (strcmp(lastrep.zprl_type, rep.zprl_type) !=
|
||||
0) {
|
||||
if (ret != NULL)
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %s and %s vdevs are "
|
||||
"present\n"),
|
||||
lastrep.zprl_type, rep.zprl_type);
|
||||
else
|
||||
return (NULL);
|
||||
} else if (lastrep.zprl_parity != rep.zprl_parity) {
|
||||
if (ret)
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %llu and %llu device parity "
|
||||
"%s vdevs are present\n"),
|
||||
(u_longlong_t)
|
||||
lastrep.zprl_parity,
|
||||
(u_longlong_t)rep.zprl_parity,
|
||||
rep.zprl_type);
|
||||
else
|
||||
return (NULL);
|
||||
} else if (lastrep.zprl_children != rep.zprl_children) {
|
||||
if (ret)
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (fatal)
|
||||
vdev_error(gettext(
|
||||
"mismatched replication level: "
|
||||
"both %llu-way and %llu-way %s "
|
||||
"vdevs are present\n"),
|
||||
(u_longlong_t)
|
||||
lastrep.zprl_children,
|
||||
(u_longlong_t)
|
||||
rep.zprl_children,
|
||||
rep.zprl_type);
|
||||
else
|
||||
return (NULL);
|
||||
}
|
||||
rep.zprl_bias = nvlist_exists(nv,
|
||||
ZPOOL_CONFIG_ALLOCATION_BIAS);
|
||||
same = rep.zprl_bias ? &lastbias : &lastrep;
|
||||
other = rep.zprl_bias ? &lastrep : &lastbias;
|
||||
|
||||
if ((same->zprl_type != NULL &&
|
||||
!rep_consistent(same, &rep, fatal)) ||
|
||||
(other->zprl_type != NULL &&
|
||||
!rep_consistent(other, &rep, fatal))) {
|
||||
free(ret);
|
||||
ret = NULL;
|
||||
if (!fatal)
|
||||
return (NULL);
|
||||
}
|
||||
lastrep = rep;
|
||||
*same = rep;
|
||||
}
|
||||
|
||||
/*
|
||||
* Report the replication level of the normal vdevs, since that is
|
||||
* what any special or dedup vdevs were required to satisfy.
|
||||
*/
|
||||
if (ret != NULL)
|
||||
*ret = rep;
|
||||
*ret = (lastrep.zprl_type != NULL) ? lastrep :
|
||||
(lastbias.zprl_type != NULL) ? lastbias : rep;
|
||||
|
||||
return (ret);
|
||||
}
|
||||
@@ -830,7 +867,7 @@ check_replication(nvlist_t *config, nvlist_t *newroot)
|
||||
* the current pool.
|
||||
*/
|
||||
ret = 0;
|
||||
if (current != NULL) {
|
||||
if (current != NULL && !is_bias_more_redundant(current, new)) {
|
||||
if (is_raidz_mirror(current, new, &raidz, &mirror) ||
|
||||
is_raidz_mirror(new, current, &raidz, &mirror)) {
|
||||
if (raidz->zprl_parity != mirror->zprl_children - 1) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
.\" Copyright (c) 2017 Open-E, Inc. All Rights Reserved.
|
||||
.\" Copyright (c) 2026 Seagate Technology, LLC.
|
||||
.\"
|
||||
.Dd August 6, 2025
|
||||
.Dd September 21, 2026
|
||||
.Dt ZPOOLCONCEPTS 7
|
||||
.Os
|
||||
.
|
||||
@@ -193,15 +193,15 @@ For more information, see the
|
||||
section.
|
||||
.It Sy dedup
|
||||
A device solely dedicated for deduplication tables.
|
||||
The redundancy of this device should match the redundancy of the other normal
|
||||
devices in the pool.
|
||||
The redundancy of this device should match or exceed the redundancy of the
|
||||
other normal devices in the pool.
|
||||
If more than one dedup device is specified, then
|
||||
allocations are load-balanced between those devices.
|
||||
.It Sy special
|
||||
A device dedicated solely for allocating various kinds of internal metadata,
|
||||
and optionally small file blocks.
|
||||
The redundancy of this device should match the redundancy of the other normal
|
||||
devices in the pool.
|
||||
The redundancy of this device should match or exceed the redundancy of the
|
||||
other normal devices in the pool.
|
||||
If more than one special device is specified, then
|
||||
allocations are load-balanced between those devices.
|
||||
.Pp
|
||||
|
||||
@@ -38,7 +38,7 @@ tests = ['alloc_class_001_pos', 'alloc_class_002_neg', 'alloc_class_003_pos',
|
||||
'alloc_class_007_pos', 'alloc_class_008_pos', 'alloc_class_009_pos',
|
||||
'alloc_class_010_pos', 'alloc_class_011_neg', 'alloc_class_012_pos',
|
||||
'alloc_class_013_pos', 'alloc_class_014_pos', 'alloc_class_015_neg',
|
||||
'alloc_class_016_pos']
|
||||
'alloc_class_016_pos', 'alloc_class_017_pos']
|
||||
tags = ['functional', 'alloc_class']
|
||||
|
||||
[tests/functional/append]
|
||||
|
||||
@@ -483,6 +483,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/alloc_class/alloc_class_014_pos.ksh \
|
||||
functional/alloc_class/alloc_class_015_neg.ksh \
|
||||
functional/alloc_class/alloc_class_016_pos.ksh \
|
||||
functional/alloc_class/alloc_class_017_pos.ksh \
|
||||
functional/alloc_class/cleanup.ksh \
|
||||
functional/alloc_class/setup.ksh \
|
||||
functional/append/file_append.ksh \
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/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.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/alloc_class/alloc_class.kshlib
|
||||
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# A special or dedup device which can tolerate more device failures
|
||||
# than the normal vdevs of a redundant pool is accepted without -f.
|
||||
# One which tolerates fewer failures, or which is added to a
|
||||
# non-redundant pool, is still rejected.
|
||||
#
|
||||
# STRATEGY:
|
||||
# 1. Create redundant pools with more redundant special/dedup mirrors.
|
||||
# 2. Add a more redundant special mirror to an existing raidz pool.
|
||||
# 3. Verify less redundant special mirrors are still rejected.
|
||||
#
|
||||
|
||||
verify_runnable "global"
|
||||
|
||||
claim="A more redundant special or dedup device is accepted."
|
||||
|
||||
log_assert $claim
|
||||
log_onexit cleanup
|
||||
|
||||
log_must disk_setup
|
||||
|
||||
# raidz1 tolerates one failure, a 3-way mirror tolerates two.
|
||||
for class in special dedup; do
|
||||
log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS \
|
||||
$class mirror $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
|
||||
log_must zpool destroy -f $TESTPOOL
|
||||
done
|
||||
|
||||
log_must zpool create $TESTPOOL mirror $ZPOOL_DISK0 $ZPOOL_DISK1 \
|
||||
special mirror $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
|
||||
log_must zpool destroy -f $TESTPOOL
|
||||
|
||||
log_must zpool create $TESTPOOL draid $ZPOOL_DISKS \
|
||||
special mirror $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
|
||||
log_must zpool destroy -f $TESTPOOL
|
||||
|
||||
log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS
|
||||
log_must zpool add $TESTPOOL special mirror \
|
||||
$CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
|
||||
log_must zpool iostat -H $TESTPOOL $CLASS_DISK2
|
||||
log_must zpool destroy -f $TESTPOOL
|
||||
|
||||
# raidz2 tolerates two failures, a 2-way mirror only one.
|
||||
log_mustnot zpool create $TESTPOOL raidz2 $ZPOOL_DISKS \
|
||||
special mirror $CLASS_DISK0 $CLASS_DISK1
|
||||
log_mustnot poolexists $TESTPOOL
|
||||
|
||||
log_must zpool create $TESTPOOL raidz2 $ZPOOL_DISKS
|
||||
log_mustnot zpool add $TESTPOOL special mirror $CLASS_DISK0 $CLASS_DISK1
|
||||
log_must zpool destroy -f $TESTPOOL
|
||||
|
||||
# A non-redundant pool with a redundant special device is still rejected.
|
||||
log_mustnot zpool create $TESTPOOL $ZPOOL_DISKS \
|
||||
special mirror $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
|
||||
log_mustnot poolexists $TESTPOOL
|
||||
|
||||
log_pass $claim
|
||||
@@ -102,6 +102,8 @@ set -A valid_args \
|
||||
cache $vdev7 log mirror $vdev8 $vdev9" \
|
||||
"draid $vdev0 $vdev1 $vdev2 draid $vdev4 $vdev5 $vdev6 $vdev7 \
|
||||
special mirror $vdev8 $vdev9" \
|
||||
"draid $vdev0 $vdev1 $vdev2 draid $vdev4 $vdev5 $vdev6 \
|
||||
special mirror $vdev7 $vdev8 $vdev9" \
|
||||
"spare $vdev0 $vdev1 $vdev2 mirror $vdev3 $vdev4 raidz $vdev5 $vdev6"
|
||||
|
||||
set -A forced_args \
|
||||
@@ -125,8 +127,6 @@ set -A forced_args \
|
||||
draid2 $vdev5 $vdev6 $vdev7 $vdev8 spare $vdev9" \
|
||||
"draid $vdev0 $vdev1 $vdev2 $vdev3 \
|
||||
draid2 $vdev4 $vdev5 $vdev6 $vdev7 $vdev8" \
|
||||
"draid $vdev0 $vdev1 $vdev2 draid $vdev4 $vdev5 $vdev6 \
|
||||
special mirror $vdev7 $vdev8 $vdev9" \
|
||||
"spare $vdev0 $vdev1 $vdev2 mirror $vdev3 $vdev4 \
|
||||
raidz2 $vdev5 $vdev6 $vdev7"
|
||||
|
||||
|
||||
Reference in new issue
Block a user