diff --git a/cmd/zpool/zpool_vdev.c b/cmd/zpool/zpool_vdev.c
index fbd4b81dfa..74e24f9ee8 100644
--- a/cmd/zpool/zpool_vdev.c
+++ b/cmd/zpool/zpool_vdev.c
@@ -480,13 +480,43 @@ is_raidz_draid(replication_level_t *a, replication_level_t *b)
return (B_FALSE);
}
+/*
+ * Return true if 'props' contains either:
+ *
+ * feature@allow_backup_to_pool=disabled
+ *
+ * or
+ *
+ * backup_alloc_class_to_pool=off
+ */
+static boolean_t
+is_backup_to_pool_disabled_in_props(nvlist_t *props)
+{
+ const char *str = NULL;
+ if (nvlist_lookup_string(props, "feature@allow_backup_to_pool",
+ &str) == 0) {
+ if ((str != NULL) && strcmp(str, "disabled") == 0) {
+ return (B_TRUE); /* It is disabled */
+ }
+ }
+
+ if (nvlist_lookup_string(props, "backup_alloc_class_to_pool",
+ &str) == 0) {
+ if ((str != NULL) && strcmp(str, "off") == 0) {
+ return (B_TRUE); /* It is disabled */
+ }
+ }
+
+ return (B_FALSE);
+}
+
/*
* 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
* an error message will be displayed for each self-inconsistent vdev.
*/
static replication_level_t *
-get_replication(nvlist_t *nvroot, boolean_t fatal)
+get_replication(nvlist_t *props, nvlist_t *nvroot, boolean_t fatal)
{
nvlist_t **top;
uint_t t, toplevels;
@@ -507,6 +537,7 @@ get_replication(nvlist_t *nvroot, boolean_t fatal)
for (t = 0; t < toplevels; t++) {
uint64_t is_log = B_FALSE;
+ const char *str = NULL;
nv = top[t];
@@ -518,6 +549,21 @@ get_replication(nvlist_t *nvroot, boolean_t fatal)
if (is_log)
continue;
+ /*
+ * By default, all alloc class devices have their backup to pool
+ * props enabled, so their replication level doesn't matter.
+ * However, if they're disabled for any reason, then we do need
+ * to force redundancy.
+ */
+ (void) nvlist_lookup_string(nv, ZPOOL_CONFIG_ALLOCATION_BIAS,
+ &str);
+ if (str &&
+ ((strcmp(str, VDEV_ALLOC_BIAS_SPECIAL) == 0) ||
+ (strcmp(str, VDEV_ALLOC_BIAS_DEDUP) == 0))) {
+ if (!is_backup_to_pool_disabled_in_props(props))
+ continue; /* We're backed up, skip redundancy */
+ }
+
/*
* Ignore holes introduced by removing aux devices, along
* with indirect vdevs introduced by previously removed
@@ -808,7 +854,7 @@ get_replication(nvlist_t *nvroot, boolean_t fatal)
* report any difference between the two.
*/
static int
-check_replication(nvlist_t *config, nvlist_t *newroot)
+check_replication(nvlist_t *props, nvlist_t *config, nvlist_t *newroot)
{
nvlist_t **child;
uint_t children;
@@ -825,7 +871,7 @@ check_replication(nvlist_t *config, nvlist_t *newroot)
verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
- if ((current = get_replication(nvroot, B_FALSE)) == NULL)
+ if ((current = get_replication(props, nvroot, B_FALSE)) == NULL)
return (0);
}
/*
@@ -850,7 +896,7 @@ check_replication(nvlist_t *config, nvlist_t *newroot)
* Get the replication level of the new vdev spec, reporting any
* inconsistencies found.
*/
- if ((new = get_replication(newroot, B_TRUE)) == NULL) {
+ if ((new = get_replication(props, newroot, B_TRUE)) == NULL) {
free(current);
return (-1);
}
@@ -1888,7 +1934,7 @@ make_root_vdev(zpool_handle_t *zhp, nvlist_t *props, int force, int check_rep,
* found. We include the existing pool spec, if any, as we need to
* catch changes against the existing replication level.
*/
- if (check_rep && check_replication(poolconfig, newroot) != 0) {
+ if (check_rep && check_replication(props, poolconfig, newroot) != 0) {
nvlist_free(newroot);
return (NULL);
}
diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h
index 21f99baccc..dc2087f188 100644
--- a/include/sys/fs/zfs.h
+++ b/include/sys/fs/zfs.h
@@ -258,6 +258,7 @@ typedef enum {
ZPOOL_PROP_BCLONEUSED,
ZPOOL_PROP_BCLONESAVED,
ZPOOL_PROP_BCLONERATIO,
+ ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL,
ZPOOL_NUM_PROPS
} zpool_prop_t;
@@ -368,6 +369,7 @@ typedef enum {
VDEV_PROP_RAIDZ_EXPANDING,
VDEV_PROP_SLOW_IO_N,
VDEV_PROP_SLOW_IO_T,
+ VDEV_PROP_BACKUP_TO_POOL,
VDEV_NUM_PROPS
} vdev_prop_t;
@@ -845,6 +847,7 @@ typedef struct zpool_load_policy {
#define ZPOOL_CONFIG_EXPANSION_TIME "expansion_time" /* not stored */
#define ZPOOL_CONFIG_REBUILD_STATS "org.openzfs:rebuild_stats"
#define ZPOOL_CONFIG_COMPATIBILITY "compatibility"
+#define ZPOOL_CONFIG_BACKUP_TO_POOL "backup_to_pool"
/*
* The persistent vdev state is stored as separate values rather than a single
@@ -1604,6 +1607,7 @@ typedef enum {
ZFS_ERR_CRYPTO_NOTSUP,
ZFS_ERR_RAIDZ_EXPAND_IN_PROGRESS,
ZFS_ERR_ASHIFT_MISMATCH,
+ ZFS_ERR_BACKUP_DISABLED_BUT_REQUESTED,
} zfs_errno_t;
/*
diff --git a/include/sys/spa.h b/include/sys/spa.h
index b969f05afe..6405b04965 100644
--- a/include/sys/spa.h
+++ b/include/sys/spa.h
@@ -1113,7 +1113,8 @@ extern boolean_t spa_remap_blkptr(spa_t *spa, blkptr_t *bp,
extern uint64_t spa_get_last_removal_txg(spa_t *spa);
extern boolean_t spa_trust_config(spa_t *spa);
extern uint64_t spa_missing_tvds_allowed(spa_t *spa);
-extern void spa_set_missing_tvds(spa_t *spa, uint64_t missing);
+extern void spa_set_missing_tvds(spa_t *spa, uint64_t missing,
+ uint64_t missing_special);
extern boolean_t spa_top_vdevs_spacemap_addressable(spa_t *spa);
extern uint64_t spa_total_metaslabs(spa_t *spa);
extern boolean_t spa_multihost(spa_t *spa);
diff --git a/include/sys/spa_impl.h b/include/sys/spa_impl.h
index 0cd0c4720f..39643256f9 100644
--- a/include/sys/spa_impl.h
+++ b/include/sys/spa_impl.h
@@ -327,6 +327,12 @@ struct spa {
uint64_t spa_missing_tvds; /* unopenable tvds on load */
uint64_t spa_missing_tvds_allowed; /* allow loading spa? */
+ /*
+ * number of 'spa_missing_tvds' that are alloc class devices
+ * backed up to the pool, and thus recoverable from errors.
+ */
+ uint64_t spa_missing_recovered_tvds;
+
uint64_t spa_nonallocating_dspace;
spa_removing_phys_t spa_removing_phys;
spa_vdev_removal_t *spa_vdev_removal;
@@ -465,6 +471,9 @@ struct spa {
*/
spa_config_lock_t spa_config_lock[SCL_LOCKS]; /* config changes */
zfs_refcount_t spa_refcount; /* number of opens */
+
+ /* Backup special/dedup devices data to the pool */
+ boolean_t spa_backup_alloc_class;
};
extern char *spa_config_path;
diff --git a/include/sys/vdev.h b/include/sys/vdev.h
index 38f62b07dc..b6401a5f7b 100644
--- a/include/sys/vdev.h
+++ b/include/sys/vdev.h
@@ -172,6 +172,24 @@ extern uint32_t vdev_queue_length(vdev_t *vd);
extern uint64_t vdev_queue_last_offset(vdev_t *vd);
extern uint64_t vdev_queue_class_length(vdev_t *vq, zio_priority_t p);
+typedef enum {
+ /* (special flag) dry-run, get count only */
+ VDEV_ARRAY_COUNT = 1ULL << 0,
+
+ VDEV_ARRAY_ANY_LEAF = 1ULL << 1, /* match any leaf */
+ VDEV_ARRAY_SPECIAL_LEAF = 1ULL << 2, /* match special vdev leaves */
+ VDEV_ARRAY_DEDUP_LEAF = 1ULL << 3, /* match dedup vdev leaves */
+} vdev_array_flag_t;
+
+struct vdev_array
+{
+ vdev_t **vds; /* Array of vdev_t's */
+ int count;
+};
+
+extern struct vdev_array *vdev_array_alloc(vdev_t *rvd, uint64_t flags);
+extern void vdev_array_free(struct vdev_array *vda);
+
extern void vdev_config_dirty(vdev_t *vd);
extern void vdev_config_clean(vdev_t *vd);
extern int vdev_config_sync(vdev_t **svd, int svdcount, uint64_t txg);
diff --git a/include/sys/vdev_impl.h b/include/sys/vdev_impl.h
index 2a93f7c680..68aa3ae7da 100644
--- a/include/sys/vdev_impl.h
+++ b/include/sys/vdev_impl.h
@@ -284,6 +284,13 @@ struct vdev {
uint64_t vdev_failfast; /* device failfast setting */
boolean_t vdev_rz_expanding; /* raidz is being expanded? */
boolean_t vdev_ishole; /* is a hole in the namespace */
+
+ /*
+ * If this is set to true, then all the data on this vdev is backed up
+ * to the pool. This is only used by allocation class devices.
+ */
+ boolean_t vdev_backup_to_pool;
+
uint64_t vdev_top_zap;
vdev_alloc_bias_t vdev_alloc_bias; /* metaslab allocation bias */
@@ -641,6 +648,11 @@ extern int vdev_obsolete_counts_are_precise(vdev_t *vd, boolean_t *are_precise);
int vdev_checkpoint_sm_object(vdev_t *vd, uint64_t *sm_obj);
void vdev_metaslab_group_create(vdev_t *vd);
uint64_t vdev_best_ashift(uint64_t logical, uint64_t a, uint64_t b);
+extern boolean_t vdev_is_fully_backed_up(vdev_t *vd);
+extern boolean_t vdev_is_leaf(vdev_t *vd);
+extern boolean_t vdev_is_special(vdev_t *vd);
+extern boolean_t vdev_is_dedup(vdev_t *vd);
+extern boolean_t vdev_is_alloc_class(vdev_t *vd);
/*
* Vdev ashift optimization tunables
diff --git a/include/zfeature_common.h b/include/zfeature_common.h
index 2515ba3217..5d878f8783 100644
--- a/include/zfeature_common.h
+++ b/include/zfeature_common.h
@@ -82,6 +82,7 @@ typedef enum spa_feature {
SPA_FEATURE_AVZ_V2,
SPA_FEATURE_REDACTION_LIST_SPILL,
SPA_FEATURE_RAIDZ_EXPANSION,
+ SPA_FEATURE_ALLOW_BACKUP_TO_POOL,
SPA_FEATURES
} spa_feature_t;
diff --git a/lib/libnvpair/libnvpair.abi b/lib/libnvpair/libnvpair.abi
index ef92f3e9bd..b99a0d6a33 100644
--- a/lib/libnvpair/libnvpair.abi
+++ b/lib/libnvpair/libnvpair.abi
@@ -1156,6 +1156,11 @@
+
+
+
+
+
@@ -2536,11 +2541,6 @@
-
-
-
-
-
diff --git a/lib/libuutil/libuutil.abi b/lib/libuutil/libuutil.abi
index e942d24c65..620f384d8f 100644
--- a/lib/libuutil/libuutil.abi
+++ b/lib/libuutil/libuutil.abi
@@ -596,14 +596,11 @@
-
+
-
-
-
-
+
@@ -800,9 +797,16 @@
+
+
+
+
+
+
+
@@ -912,6 +916,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -920,12 +943,23 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -937,8 +971,9 @@
-
+
+
diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi
index 2bbaae6345..12f0b01a70 100644
--- a/lib/libzfs/libzfs.abi
+++ b/lib/libzfs/libzfs.abi
@@ -606,7 +606,7 @@
-
+
@@ -2895,7 +2895,8 @@
-
+
+
@@ -5675,7 +5676,8 @@
-
+
+
@@ -5936,7 +5938,8 @@
-
+
+
@@ -6251,6 +6254,11 @@
+
+
+
+
+
@@ -6362,7 +6370,7 @@
-
+
@@ -8987,8 +8995,8 @@
-
-
+
+
@@ -9065,7 +9073,7 @@
-
+
diff --git a/lib/libzfs/libzfs_util.c b/lib/libzfs/libzfs_util.c
index 73ae0950cc..02b7add294 100644
--- a/lib/libzfs/libzfs_util.c
+++ b/lib/libzfs/libzfs_util.c
@@ -774,6 +774,12 @@ zpool_standard_error_fmt(libzfs_handle_t *hdl, int error, const char *fmt, ...)
case ZFS_ERR_ASHIFT_MISMATCH:
zfs_verror(hdl, EZFS_ASHIFT_MISMATCH, fmt, ap);
break;
+ case ZFS_ERR_BACKUP_DISABLED_BUT_REQUESTED:
+ zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
+ "Cannot enable backup to pool since "
+ "feature@allow_backup_to_pool is not active."));
+ zfs_verror(hdl, EZFS_IOC_NOTSUPPORTED, fmt, ap);
+ break;
default:
zfs_error_aux(hdl, "%s", zfs_strerror(error));
zfs_verror(hdl, EZFS_UNKNOWN, fmt, ap);
diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi
index 5b95c8f779..ab7231971c 100644
--- a/lib/libzfs_core/libzfs_core.abi
+++ b/lib/libzfs_core/libzfs_core.abi
@@ -594,14 +594,11 @@
-
+
-
-
-
-
+
@@ -770,6 +767,13 @@
+
+
+
+
+
+
+
@@ -873,12 +877,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -895,8 +929,9 @@
-
+
+
@@ -1119,7 +1154,7 @@
-
+
@@ -1127,7 +1162,7 @@
-
+
diff --git a/lib/libzutil/zutil_import.c b/lib/libzutil/zutil_import.c
index 06705ff4d9..6e349920d2 100644
--- a/lib/libzutil/zutil_import.c
+++ b/lib/libzutil/zutil_import.c
@@ -1924,7 +1924,7 @@ zpool_find_config(libpc_handle_t *hdl, const char *target, nvlist_t **configp,
/* Return if a vdev is a leaf vdev. Note: draid spares are leaf vdevs. */
static boolean_t
-vdev_is_leaf(nvlist_t *nv)
+vdev_is_leaf_nv(nvlist_t *nv)
{
uint_t children = 0;
nvlist_t **child;
@@ -1937,10 +1937,10 @@ vdev_is_leaf(nvlist_t *nv)
/* Return if a vdev is a leaf vdev and a real device (disk or file) */
static boolean_t
-vdev_is_real_leaf(nvlist_t *nv)
+vdev_is_real_leaf_nv(nvlist_t *nv)
{
const char *type = NULL;
- if (!vdev_is_leaf(nv))
+ if (!vdev_is_leaf_nv(nv))
return (B_FALSE);
(void) nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type);
@@ -1973,7 +1973,7 @@ __for_each_vdev_macro_helper_func(void *state, nvlist_t *nv, void *last_nv,
/* The very first entry in the NV list is a special case */
if (*((nvlist_t **)state) == (nvlist_t *)FIRST_NV) {
- if (real_leaves_only && !vdev_is_real_leaf(nv))
+ if (real_leaves_only && !vdev_is_real_leaf_nv(nv))
return (0);
*((nvlist_t **)last_nv) = nv;
@@ -1996,7 +1996,7 @@ __for_each_vdev_macro_helper_func(void *state, nvlist_t *nv, void *last_nv,
* we want.
*/
if (*(nvlist_t **)state == (nvlist_t *)NEXT_IS_MATCH) {
- if (real_leaves_only && !vdev_is_real_leaf(nv))
+ if (real_leaves_only && !vdev_is_real_leaf_nv(nv))
return (0);
*((nvlist_t **)last_nv) = nv;
diff --git a/man/man7/vdevprops.7 b/man/man7/vdevprops.7
index 5ec37df179..9cfac09f00 100644
--- a/man/man7/vdevprops.7
+++ b/man/man7/vdevprops.7
@@ -148,6 +148,25 @@ If this device should perform new allocations, used to disable a device
when it is scheduled for later removal.
See
.Xr zpool-remove 8 .
+.It Sy backup_to_pool
+When
+.Sy backup_to_pool
+is "on" it means the vdev is fully backed up to the pool.
+That is, there is an extra copy of all the vdev's data on the pool itself.
+This allows vdevs with
+.Sy backup_to_pool=on
+to fail without losing data, regardless
+of their redundancy level.
+.Sy backup_to_pool
+is only used for alloc class devices
+(special and dedup) and is controlled by the
+.Sy feature@allow_backup_to_pool
+feature flag and
+.Sy backup_alloc_class_to_pool
+pool property.
+The
+.Sy backup_to_pool
+vdev property is read-only.
.El
.Ss User Properties
In addition to the standard native properties, ZFS supports arbitrary user
diff --git a/man/man7/zpool-features.7 b/man/man7/zpool-features.7
index ea3c68dc60..a1f6921239 100644
--- a/man/man7/zpool-features.7
+++ b/man/man7/zpool-features.7
@@ -322,6 +322,46 @@ With device removal, it can be returned to the
.Sy enabled
state if all the dedicated allocation class vdevs are removed.
.
+.feature org.zfsonlinux allow_backup_to_pool yes allocation_classes
+This feature allows the
+.Sy backup_alloc_class_to_pool
+pool property to be used.
+When the
+.Sy backup_alloc_class_to_pool
+pool property is set to "on" all proceeding writes to allocation class vdevs
+(like special and dedup vdevs) will also generate an additional copy of the data
+to be written to the pool.
+This allows alloc class vdev data to be "backed up" to the pool.
+A fully backed up allocation device vdev can fail without causing the pool to be
+suspended, even if the alloc class device is not redundant.
+.Pp
+It is important to note the difference between the
+.Sy allow_backup_to_pool
+feature flag and a
+.Sy backup_alloc_class_to_pool
+pool property since they appear similar.
+The
+.Sy allow_backup_to_pool
+feature flag is a safeguard to prevent a pool that is backed up from being
+imported read/write on an older version of ZFS that does not support backup to
+pool (and possibly compromising the integrity of the backup guarantees).
+The pool property is what actually allows you to turn on/off the backup copy
+writes.
+You can think of it as if the
+.Sy allow_backup_to_pool
+feature "unlocks" the
+.Sy backup_alloc_class_to_pool
+pool property.
+See the
+.Sy backup_alloc_class_to_pool
+pool property and
+.Sy backup_to_pool
+vdev property for more details.
+.Pp
+This feature becomes
+.Sy active
+by default on new pools (unless explicitly disabled at zpool creation time).
+.
.feature com.delphix async_destroy yes
Destroying a file system requires traversing all of its data in order to
return its used space to the pool.
diff --git a/man/man7/zpoolconcepts.7 b/man/man7/zpoolconcepts.7
index 18dfca6dc8..804ba6a086 100644
--- a/man/man7/zpoolconcepts.7
+++ b/man/man7/zpoolconcepts.7
@@ -180,17 +180,31 @@ 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.
If more than one dedup device is specified, then
allocations are load-balanced between those devices.
+The dedup vdevs only need to match the redundancy level of the normal devices
+if they are not being backed-up to the pool (backed-up is the default).
+See the
+.Sy feature@allow_backup_to_pool
+feature flag,
+.Sy backup_alloc_class_to_pool
+pool property and
+.Sy backup_to_pool
+vdev property for more details.
.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.
If more than one special device is specified, then
allocations are load-balanced between those devices.
+The special vdevs only need to match the redundancy level of the normal devices
+if they are not being backed-up to the pool (backed-up is the default).
+See the
+.Sy feature@allow_backup_to_pool
+feature flag,
+.Sy backup_alloc_class_to_pool
+pool property and
+.Sy backup_to_pool
+vdev property for more details.
.Pp
For more information on special allocations, see the
.Sx Special Allocation Class
diff --git a/man/man7/zpoolprops.7 b/man/man7/zpoolprops.7
index 5428ab8d30..0792cc5307 100644
--- a/man/man7/zpoolprops.7
+++ b/man/man7/zpoolprops.7
@@ -437,6 +437,37 @@ command, though this property can be used when a specific version is needed for
backwards compatibility.
Once feature flags are enabled on a pool this property will no longer have a
value.
+.It Sy backup_alloc_class_to_pool Ns = Ns Sy on Ns | Ns Sy off
+When set to "on" all proceeding writes to allocation class vdevs (like special
+and dedup vdevs) will also write an additional copy of the data to the pool.
+This allows alloc class vdev data to be "backed up" to the pool.
+If an alloc class vdev has all of its data backed up to the pool, then the vdev
+will be considered "fully backed up" and will have the
+.Sy backup_to_pool
+vdev property set to "on".
+Fully backed up alloc class vdevs can fail regardless of their redundancy level
+without the pool losing data.
+If
+.Sy backup_alloc_class_to_pool
+is set to "off" then all alloc class vdevs will no longer be considered fully
+backed up, and will have their
+.Sy backup_to_pool
+vdev property automatically
+set to "off".
+If
+.Sy backup_alloc_class_to_pool
+is set to "on" after being set to "off", the alloc class data writes will still
+write an extra copy of the data to the pool, and new top-level alloc class
+vdevs added after that point will be fully backed up, but the older alloc class
+vdevs will remain not fully backed up.
+Essentially, an alloc class vdev must have had
+.Sy backup_alloc_class_to_pool
+set to "on" for the entirety of its lifetime to be considered fully backed up.
+Note that the
+.Sy feature@allow_backup_to_pool
+feature flag must be active in order to use the
+.Sy backup_alloc_class_to_pool
+pool property.
.El
.
.Ss User Properties
diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c
index 309d9bf14c..1ccefc4b40 100644
--- a/module/zcommon/zfeature_common.c
+++ b/module/zcommon/zfeature_common.c
@@ -753,6 +753,18 @@ zpool_feature_init(void)
"org.openzfs:raidz_expansion", "raidz_expansion",
"Support for raidz expansion",
ZFEATURE_FLAG_MOS, ZFEATURE_TYPE_BOOLEAN, NULL, sfeatures);
+ {
+ static const spa_feature_t allow_backup_to_pool_deps[] = {
+ SPA_FEATURE_ALLOCATION_CLASSES,
+ SPA_FEATURE_NONE
+ };
+ zfeature_register(SPA_FEATURE_ALLOW_BACKUP_TO_POOL,
+ "org.openzfs:allow_backup_to_pool", "allow_backup_to_pool",
+ "Allow backing up allocation class device data to pool",
+ ZFEATURE_FLAG_MOS | ZFEATURE_FLAG_ACTIVATE_ON_ENABLE,
+ ZFEATURE_TYPE_BOOLEAN, allow_backup_to_pool_deps,
+ sfeatures);
+ }
zfs_mod_list_supported_free(sfeatures);
}
diff --git a/module/zcommon/zpool_prop.c b/module/zcommon/zpool_prop.c
index e2e3bf5be6..b2dd02bf26 100644
--- a/module/zcommon/zpool_prop.c
+++ b/module/zcommon/zpool_prop.c
@@ -153,6 +153,10 @@ zpool_prop_init(void)
zprop_register_index(ZPOOL_PROP_MULTIHOST, "multihost", 0,
PROP_DEFAULT, ZFS_TYPE_POOL, "on | off", "MULTIHOST",
boolean_table, sfeatures);
+ zprop_register_index(ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL,
+ "backup_alloc_class_to_pool", 1, PROP_DEFAULT, ZFS_TYPE_POOL,
+ "on | off", "BACKUP_ALLOC_CLASS_TO_POOL", boolean_table,
+ sfeatures);
/* default index properties */
zprop_register_index(ZPOOL_PROP_FAILUREMODE, "failmode",
@@ -448,6 +452,9 @@ vdev_prop_init(void)
zprop_register_index(VDEV_PROP_RAIDZ_EXPANDING, "raidz_expanding", 0,
PROP_READONLY, ZFS_TYPE_VDEV, "on | off", "RAIDZ_EXPANDING",
boolean_table, sfeatures);
+ zprop_register_index(VDEV_PROP_BACKUP_TO_POOL, "backup_to_pool", B_TRUE,
+ PROP_READONLY, ZFS_TYPE_VDEV, "on | off", "BACKUP_TO_POOL",
+ boolean_table, sfeatures);
/* default index properties */
zprop_register_index(VDEV_PROP_FAILFAST, "failfast", B_TRUE,
diff --git a/module/zfs/metaslab.c b/module/zfs/metaslab.c
index c4aa98ced4..4ed28266aa 100644
--- a/module/zfs/metaslab.c
+++ b/module/zfs/metaslab.c
@@ -5845,10 +5845,22 @@ metaslab_alloc(spa_t *spa, metaslab_class_t *mc, uint64_t psize, blkptr_t *bp,
dva_t *dva = bp->blk_dva;
dva_t *hintdva = (hintbp != NULL) ? hintbp->blk_dva : NULL;
int error = 0;
+ boolean_t is_backup_alloc_class = B_FALSE;
+
+ if ((spa->spa_backup_alloc_class && ((mc == spa_special_class(spa)) ||
+ (mc == spa_dedup_class(spa))))) {
+ is_backup_alloc_class = B_TRUE;
+ }
ASSERT0(BP_GET_LOGICAL_BIRTH(bp));
ASSERT0(BP_GET_PHYSICAL_BIRTH(bp));
+ /*
+ * Earlier layers of the code should set nvdas > 1 if the
+ * alloc class vdev is being backed up.
+ */
+ ASSERT(!(is_backup_alloc_class && ndvas == 1));
+
spa_config_enter(spa, SCL_ALLOC, FTAG, RW_READER);
if (mc->mc_allocator[allocator].mca_rotor == NULL) {
@@ -5863,7 +5875,21 @@ metaslab_alloc(spa_t *spa, metaslab_class_t *mc, uint64_t psize, blkptr_t *bp,
ASSERT3P(zal, !=, NULL);
for (int d = 0; d < ndvas; d++) {
- error = metaslab_alloc_dva(spa, mc, psize, dva, d, hintdva,
+ metaslab_class_t *_mc;
+ if (is_backup_alloc_class && (d == 1)) {
+ /*
+ * If we have the backup to pool props set, then make
+ * the 2nd copy of the data we are going to write go to
+ * the regular pool rather than yet another copy to the
+ * alloc class device. That way, if the special device
+ * is lost, there's still a backup in the pool.
+ */
+ _mc = spa_normal_class(spa);
+ } else {
+ _mc = mc;
+ }
+
+ error = metaslab_alloc_dva(spa, _mc, psize, dva, d, hintdva,
txg, flags, zal, allocator);
if (error != 0) {
for (d--; d >= 0; d--) {
diff --git a/module/zfs/spa.c b/module/zfs/spa.c
index 3704ffd088..daa02266c9 100644
--- a/module/zfs/spa.c
+++ b/module/zfs/spa.c
@@ -471,6 +471,15 @@ spa_prop_get_config(spa_t *spa, nvlist_t **nvp)
DNODE_MIN_SIZE, ZPROP_SRC_NONE);
}
+ if (spa_feature_is_active(spa, SPA_FEATURE_ALLOW_BACKUP_TO_POOL)) {
+ spa_prop_add_list(*nvp, ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL,
+ NULL, spa->spa_backup_alloc_class, ZPROP_SRC_NONE);
+ } else {
+ /* Feature not active, turn off backup to pool */
+ spa_prop_add_list(*nvp, ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL,
+ NULL, B_FALSE, ZPROP_SRC_NONE);
+ }
+
if ((dp = list_head(&spa->spa_config_list)) != NULL) {
if (dp->scd_path == NULL) {
spa_prop_add_list(*nvp, ZPOOL_PROP_CACHEFILE,
@@ -604,6 +613,8 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
int error = 0, reset_bootfs = 0;
uint64_t objnum = 0;
boolean_t has_feature = B_FALSE;
+ boolean_t allow_backup_to_pool = B_FALSE;
+ boolean_t backup_alloc_class_to_pool = B_FALSE;
elem = NULL;
while ((elem = nvlist_next_nvpair(props, elem)) != NULL) {
@@ -611,6 +622,7 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
const char *strval, *slash, *check, *fname;
const char *propname = nvpair_name(elem);
zpool_prop_t prop = zpool_name_to_prop(propname);
+ spa_feature_t fid = 0;
switch (prop) {
case ZPOOL_PROP_INVAL:
@@ -645,11 +657,30 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
}
fname = strchr(propname, '@') + 1;
- if (zfeature_lookup_name(fname, NULL) != 0) {
+ if (zfeature_lookup_name(fname, &fid) != 0) {
error = SET_ERROR(EINVAL);
break;
}
+ /*
+ * Special case - If both:
+ *
+ * SPA_FEATURE_ALLOW_BACKUP_TO_POOL = disabled
+ *
+ * ... and ...
+ *
+ * ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL = on
+ *
+ * then we need to fail. Note that the presence
+ * of SPA_FEATURE_ALLOW_BACKUP_TO_POOL in the
+ * nvlist means it is enabled (although its
+ * intval will be 0). If it's disabled, then
+ * SPA_FEATURE_ALLOW_BACKUP_TO_POOL will not
+ * be in the nvlist at all.
+ */
+ if (fid == SPA_FEATURE_ALLOW_BACKUP_TO_POOL) {
+ allow_backup_to_pool = B_TRUE;
+ }
has_feature = B_TRUE;
} else {
error = SET_ERROR(EINVAL);
@@ -793,6 +824,15 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
if (strlen(strval) > ZPROP_MAX_COMMENT)
error = SET_ERROR(E2BIG);
break;
+ case ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL:
+ error = nvpair_value_uint64(elem, &intval);
+ if (!error && intval > 1)
+ error = SET_ERROR(EINVAL);
+
+ if (intval == 1) {
+ backup_alloc_class_to_pool = B_TRUE;
+ }
+ break;
default:
break;
@@ -805,6 +845,18 @@ spa_prop_validate(spa_t *spa, nvlist_t *props)
(void) nvlist_remove_all(props,
zpool_prop_to_name(ZPOOL_PROP_DEDUPDITTO));
+ if (spa_feature_is_active(spa, SPA_FEATURE_ALLOW_BACKUP_TO_POOL)) {
+ allow_backup_to_pool = B_TRUE;
+ }
+
+ if (!allow_backup_to_pool && backup_alloc_class_to_pool) {
+ /*
+ * We can't enable pool props BACKUP_ALLOC_CLASS_TO_POOL if the
+ * feature flag SPA_FEATURE_ALLOW_BACKUP_TO_POOL is disabled.
+ */
+ error = SET_ERROR(ZFS_ERR_BACKUP_DISABLED_BUT_REQUESTED);
+ }
+
if (!error && reset_bootfs) {
error = nvlist_remove(props,
zpool_prop_to_name(ZPOOL_PROP_BOOTFS), DATA_TYPE_STRING);
@@ -2485,6 +2537,52 @@ spa_check_removed(vdev_t *vd)
}
}
+/*
+ * Decide what to do if we have missing/corrupted alloc class devices.
+ *
+ * If we have missing top-level vdevs and they are all alloc class devices with
+ * backup_to_pool set, then we may still be able to import the pool.
+ */
+static int
+spa_check_for_bad_alloc_class_devices(spa_t *spa)
+{
+ if (spa->spa_missing_recovered_tvds == 0)
+ return (0);
+
+ /*
+ * Are there missing alloc class devices but
+ * SPA_FEATURE_ALLOW_BACKUP_TO_POOL is not enabled? If so,
+ * then we can't import.
+ */
+ if (!spa_feature_is_active(spa, SPA_FEATURE_ALLOW_BACKUP_TO_POOL)) {
+ spa_load_note(spa, "some alloc class devices are missing, "
+ "cannot import.");
+ return (SET_ERROR(ENXIO));
+ }
+
+ /*
+ * If all the missing top-level devices are alloc class devices, and
+ * if they have all their data backed up to the pool, then we can still
+ * import the pool.
+ */
+ if (spa->spa_missing_tvds > 0 &&
+ spa->spa_missing_tvds == spa->spa_missing_recovered_tvds) {
+ spa_load_note(spa, "only alloc class devices are missing, and "
+ "the normal pool has copies of the alloc class data, so "
+ "it's still possible to import.");
+ return (0);
+ }
+
+ /*
+ * If we're here, then it means that not all the missing top-level vdevs
+ * were alloc class devices. This should have been caught earlier.
+ */
+ spa_load_note(spa, "some alloc class devices that are not backed up to "
+ "the pool are amongst those that are missing, cannot import");
+
+ return (SET_ERROR(ENXIO));
+}
+
static int
spa_check_for_missing_logs(spa_t *spa)
{
@@ -3914,7 +4012,24 @@ spa_ld_open_vdevs(spa_t *spa)
error = vdev_open(spa->spa_root_vdev);
spa_config_exit(spa, SCL_ALL, FTAG);
- if (spa->spa_missing_tvds != 0) {
+ if (spa->spa_missing_tvds != 0 &&
+ spa->spa_missing_tvds == spa->spa_missing_recovered_tvds &&
+ (error == 0 || error == ENOENT)) {
+ /*
+ * Special case: If all the missing top-level vdevs are special
+ * devices, we may or may not be able to import the pool,
+ * depending on if the relevant "backup to pool" feature and
+ * properties are set. At this early stage of import we do not
+ * have the feature flags loaded yet, so for now proceed
+ * with the import. We will do the backup checks later after
+ * the feature flags are loaded.
+ */
+ spa_load_note(spa, "vdev tree has %lld missing special "
+ "top-level vdevs. Keep importing for now until we "
+ "can check the feature flags.",
+ (u_longlong_t)spa->spa_missing_tvds);
+ error = 0;
+ } else if (spa->spa_missing_tvds != 0) {
spa_load_note(spa, "vdev tree has %lld missing top-level "
"vdevs.", (u_longlong_t)spa->spa_missing_tvds);
if (spa->spa_trust_config && (spa->spa_mode & SPA_MODE_WRITE)) {
@@ -5337,6 +5452,13 @@ spa_load_impl(spa_t *spa, spa_import_type_t type, const char **ereport)
if (error != 0)
return (error);
+ spa_import_progress_set_notes(spa, "Checking for bad alloc class "
+ "devices");
+ spa_check_for_bad_alloc_class_devices(spa);
+ if (error != 0)
+ return (error);
+
+
spa_import_progress_set_notes(spa, "Loading dedup tables");
error = spa_ld_load_dedup_tables(spa);
if (error != 0)
@@ -6240,6 +6362,47 @@ spa_create_check_encryption_params(dsl_crypto_params_t *dcp,
return (dmu_objset_create_crypt_check(NULL, dcp, NULL));
}
+/*
+ * For each special or dedup vdev, disable backing up its data to the pool.
+ *
+ * Return 0 on success, non-zero otherwise.
+ */
+static int
+spa_disable_alloc_class_backup(spa_t *spa)
+{
+ struct vdev_array *vda;
+ int rc;
+
+ /*
+ * TODO: I don't know what locks are required here
+ *
+ * I need to iterate over the vdev tree and write
+ * vd->vdev_backup_to_pool.
+ *
+ * Take more locks than I need to just to be sure.
+ */
+ int locks = SCL_CONFIG | SCL_STATE | SCL_VDEV;
+
+ spa_config_enter(spa, locks, FTAG, RW_READER);
+
+ /* Get an array of alloc class vdev_t's */
+ vda = vdev_array_alloc(spa->spa_root_vdev, VDEV_ARRAY_SPECIAL_LEAF |
+ VDEV_ARRAY_DEDUP_LEAF);
+ if (vda == NULL) {
+ spa_config_exit(spa, locks, FTAG);
+ return (-1);
+ }
+
+ for (int i = 0; i < vda->count; i++) {
+ vda->vds[i]->vdev_backup_to_pool = B_FALSE;
+ }
+
+ spa_config_exit(spa, locks, FTAG);
+
+ vdev_array_free(vda);
+ return (rc);
+}
+
/*
* Pool Creation
*/
@@ -6521,11 +6684,40 @@ spa_create(const char *pool, nvlist_t *nvroot, nvlist_t *props,
spa->spa_multihost = zpool_prop_default_numeric(ZPOOL_PROP_MULTIHOST);
spa->spa_autotrim = zpool_prop_default_numeric(ZPOOL_PROP_AUTOTRIM);
+ /*
+ * Set initial backup settings. These may change after the nvlist
+ * properties are processed a little later in spa_sync_props().
+ */
+ spa->spa_backup_alloc_class = (boolean_t)
+ zpool_prop_default_numeric(ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL);
+
if (props != NULL) {
spa_configfile_set(spa, props, B_FALSE);
spa_sync_props(props, tx);
}
+ /*
+ * At this point in the code, the pool features are loaded and
+ * we can query them. If SPA_FEATURE_ALLOW_BACKUP_TO_POOL is disabled,
+ * then disable the pool prop.
+ */
+ if (!spa_feature_is_active(spa, SPA_FEATURE_ALLOW_BACKUP_TO_POOL)) {
+ spa->spa_backup_alloc_class = B_FALSE;
+ }
+
+ /*
+ * We now have the spa->spa_backup_alloc_class correctly set.
+ *
+ * Unfortunately, our vdev's vd->vdev_backup_to_pool values were
+ * already set earlier in spa_config_parse(). We need to update
+ * these vdev values to reflect our pool backup settings.
+ *
+ * Make things right by setting the vd->backup_to_pool to the correct
+ * value on all the alloc class vdevs.
+ */
+ if (!spa->spa_backup_alloc_class)
+ spa_disable_alloc_class_backup(spa);
+
for (int i = 0; i < ndraid; i++)
spa_feature_incr(spa, SPA_FEATURE_DRAID, tx);
@@ -7353,6 +7545,14 @@ spa_vdev_attach(spa_t *spa, uint64_t guid, nvlist_t *nvroot, int replacing,
return (spa_vdev_exit(spa, newrootvd, txg, ENOTSUP));
}
+ /*
+ * Our new/replaced alloc class vdev's backup setting should inherit
+ * the current pool property.
+ */
+ if (vdev_is_leaf(oldvd) && vdev_is_alloc_class(oldvd)) {
+ newvd->vdev_backup_to_pool = spa->spa_backup_alloc_class;
+ }
+
/*
* A dRAID spare can only replace a child of its parent dRAID vdev.
*/
@@ -9381,6 +9581,7 @@ spa_sync_props(void *arg, dmu_tx_t *tx)
const char *elemname = nvpair_name(elem);
zprop_type_t proptype;
spa_feature_t fid;
+ boolean_t boolval;
switch (prop = zpool_name_to_prop(elemname)) {
case ZPOOL_PROP_VERSION:
@@ -9444,6 +9645,29 @@ spa_sync_props(void *arg, dmu_tx_t *tx)
"%s=%s", nvpair_name(elem), strval);
break;
+ case ZPOOL_PROP_BACKUP_ALLOC_CLASS_TO_POOL:
+ boolval = (boolean_t)fnvpair_value_uint64(elem);
+
+ /*
+ * If we're disabling backup, then mark all the alloc
+ * class vdevs as not fully backed-up anymore.
+ */
+ spa->spa_backup_alloc_class = boolval;
+ if (boolval == B_FALSE)
+ spa_disable_alloc_class_backup(spa);
+
+ /*
+ * Dirty the configuration on vdevs as above.
+ */
+ if (tx->tx_txg != TXG_INITIAL) {
+ vdev_config_dirty(spa->spa_root_vdev);
+ spa_async_request(spa, SPA_ASYNC_CONFIG_UPDATE);
+ }
+
+ spa_history_log_internal(spa, "set", tx,
+ "%s=%s", nvpair_name(elem), boolval ? "on" : "off");
+ break;
+
case ZPOOL_PROP_INVAL:
if (zpool_prop_feature(elemname)) {
fname = strchr(elemname, '@') + 1;
diff --git a/module/zfs/spa_misc.c b/module/zfs/spa_misc.c
index 68b9076141..36bdaaada7 100644
--- a/module/zfs/spa_misc.c
+++ b/module/zfs/spa_misc.c
@@ -721,6 +721,9 @@ spa_add(const char *name, nvlist_t *config, const char *altroot)
spa->spa_deadman_synctime = MSEC2NSEC(zfs_deadman_synctime_ms);
spa->spa_deadman_ziotime = MSEC2NSEC(zfs_deadman_ziotime_ms);
+
+ spa->spa_backup_alloc_class = B_TRUE;
+
spa_set_deadman_failmode(spa, zfs_deadman_failmode);
spa_set_allocator(spa, zfs_active_allocator);
@@ -2812,10 +2815,20 @@ spa_syncing_log_sm(spa_t *spa)
return (spa->spa_syncing_log_sm);
}
+/*
+ * Record the total number of missing top-level vdevs ('missing'), and the
+ * number of missing top-level vdevs that are recoverable ('missing_recovered').
+ * In this case, missing_recovered is the number of top-level alloc class vdevs
+ * that are fully backed up to the pool, and thus their data is recoverable.
+ *
+ * The separete 'missing_recovered' count is used during pool import to
+ * determine if we can import a pool with missing alloc class vdevs.
+ */
void
-spa_set_missing_tvds(spa_t *spa, uint64_t missing)
+spa_set_missing_tvds(spa_t *spa, uint64_t missing, uint64_t missing_recovered)
{
spa->spa_missing_tvds = missing;
+ spa->spa_missing_recovered_tvds = missing_recovered;
}
/*
diff --git a/module/zfs/vdev.c b/module/zfs/vdev.c
index ebba453e2b..86e6ca484d 100644
--- a/module/zfs/vdev.c
+++ b/module/zfs/vdev.c
@@ -722,6 +722,60 @@ vdev_alloc_common(spa_t *spa, uint_t id, uint64_t guid, vdev_ops_t *ops)
return (vd);
}
+boolean_t
+vdev_is_leaf(vdev_t *vd)
+{
+ return (vd->vdev_children == 0);
+}
+
+/* Return true if vdev or TLD vdev is special alloc class */
+boolean_t
+vdev_is_special(vdev_t *vd)
+{
+ if (vd->vdev_alloc_bias == VDEV_BIAS_SPECIAL)
+ return (B_TRUE);
+
+ /*
+ * If the vdev is a leaf vdev, and is part of a mirror, its parent
+ * 'mirror' TLD will have vdev_alloc_bias == VDEV_BIAS_SPECIAL, but the
+ * leaf vdev itself will not. So we also need to check the parent
+ * in those cases.
+ */
+ if (vdev_is_leaf(vd) &&
+ (vd->vdev_parent != NULL && vdev_is_special(vd->vdev_parent))) {
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+/* Return true if vdev or TLD vdev is dedup alloc class */
+boolean_t
+vdev_is_dedup(vdev_t *vd)
+{
+ if (vd->vdev_alloc_bias == VDEV_BIAS_DEDUP)
+ return (B_TRUE);
+
+ /*
+ * If the vdev is a leaf vdev, and is part of a mirror, it's parent
+ * 'mirror' TLD will have vdev_alloc_bias == VDEV_BIAS_DEDUP, but the
+ * leaf vdev itself will not. So we also need to check the parent
+ * in those cases.
+ */
+ if (vdev_is_leaf(vd) &&
+ (vd->vdev_parent != NULL && vdev_is_dedup(vd->vdev_parent))) {
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+boolean_t
+vdev_is_alloc_class(vdev_t *vd)
+{
+ return (vdev_is_special(vd) || vdev_is_dedup(vd));
+}
+
/*
* Allocate a new vdev. The 'alloctype' is used to control whether we are
* creating a new vdev or loading an existing one - the behavior is slightly
@@ -740,6 +794,7 @@ vdev_alloc(spa_t *spa, vdev_t **vdp, nvlist_t *nv, vdev_t *parent, uint_t id,
int rc;
vdev_alloc_bias_t alloc_bias = VDEV_BIAS_NONE;
boolean_t top_level = (parent && !parent->vdev_parent);
+ const char *bias = NULL;
ASSERT(spa_config_held(spa, SCL_ALL, RW_WRITER) == SCL_ALL);
@@ -791,8 +846,6 @@ vdev_alloc(spa_t *spa, vdev_t **vdp, nvlist_t *nv, vdev_t *parent, uint_t id,
return (SET_ERROR(ENOTSUP));
if (top_level && alloctype == VDEV_ALLOC_ADD) {
- const char *bias;
-
/*
* If creating a top-level vdev, check for allocation
* classes input.
@@ -834,6 +887,11 @@ vdev_alloc(spa_t *spa, vdev_t **vdp, nvlist_t *nv, vdev_t *parent, uint_t id,
vd->vdev_tsd = tsd;
vd->vdev_islog = islog;
+ if (nvlist_lookup_string(nv, ZPOOL_CONFIG_ALLOCATION_BIAS,
+ &bias) == 0) {
+ alloc_bias = vdev_derive_alloc_bias(bias);
+ }
+
if (top_level && alloc_bias != VDEV_BIAS_NONE)
vd->vdev_alloc_bias = alloc_bias;
@@ -1028,6 +1086,40 @@ vdev_alloc(spa_t *spa, vdev_t **vdp, nvlist_t *nv, vdev_t *parent, uint_t id,
*/
vdev_add_child(parent, vd);
+ /*
+ * Now that we're added to our parent, we can lookup if we're an alloc
+ * class device. Functions like vdev_is_special() and vdev_is_dedup()
+ * will look at the parent.
+ */
+ vd->vdev_backup_to_pool = B_FALSE;
+ if (vdev_is_alloc_class(vd)) {
+ if (alloctype == VDEV_ALLOC_LOAD ||
+ alloctype == VDEV_ALLOC_SPLIT) {
+ /*
+ * If ZPOOL_CONFIG_BACKUP_TO_POOL exists then
+ * vdev_backup_to_pool is true
+ */
+ if (nvlist_lookup_boolean(nv,
+ ZPOOL_CONFIG_BACKUP_TO_POOL) == 0) {
+ vd->vdev_backup_to_pool = B_TRUE;
+ }
+ } else if (alloctype == VDEV_ALLOC_ADD) {
+ vd->vdev_backup_to_pool = spa->spa_backup_alloc_class;
+ }
+ } else if ((nvlist_lookup_boolean(nv,
+ ZPOOL_CONFIG_BACKUP_TO_POOL) == 0) &&
+ alloctype == VDEV_ALLOC_SPLIT) {
+ /*
+ * Special case: our vd may not be marked as alloc class if
+ * it's in a mirror and its parent 'mirror-1' device is not
+ * initialized fully (as in the case of a split). If the user
+ * is doing a split, and the old vdev had
+ * ZPOOL_CONFIG_BACKUP_TO_POOL set, then also set it for the
+ * new vdev.
+ */
+ vd->vdev_backup_to_pool = B_TRUE;
+ }
+
*vdp = vd;
return (0);
@@ -3680,8 +3772,9 @@ vdev_load(vdev_t *vd)
VDEV_TOP_ZAP_ALLOCATION_BIAS, 1, sizeof (bias_str),
bias_str);
if (error == 0) {
- ASSERT(vd->vdev_alloc_bias == VDEV_BIAS_NONE);
- vd->vdev_alloc_bias = vdev_derive_alloc_bias(bias_str);
+ if (vd->vdev_alloc_bias == VDEV_BIAS_NONE)
+ vd->vdev_alloc_bias =
+ vdev_derive_alloc_bias(bias_str);
} else if (error != ENOENT) {
vdev_set_state(vd, B_FALSE, VDEV_STATE_CANT_OPEN,
VDEV_AUX_CORRUPT_DATA);
@@ -4140,7 +4233,8 @@ vdev_fault(spa_t *spa, uint64_t guid, vdev_aux_t aux)
* If this device has the only valid copy of the data, then
* back off and simply mark the vdev as degraded instead.
*/
- if (!tvd->vdev_islog && vd->vdev_aux == NULL && vdev_dtl_required(vd)) {
+ if (!tvd->vdev_islog && !vdev_is_fully_backed_up(vd) &&
+ vd->vdev_aux == NULL && vdev_dtl_required(vd)) {
vd->vdev_degraded = 1ULL;
vd->vdev_faulted = 0ULL;
@@ -4356,8 +4450,8 @@ top:
* don't allow it to be offlined. Log devices are always
* expendable.
*/
- if (!tvd->vdev_islog && vd->vdev_aux == NULL &&
- vdev_dtl_required(vd))
+ if (!tvd->vdev_islog && !vdev_is_fully_backed_up(vd) &&
+ vd->vdev_aux == NULL && vdev_dtl_required(vd))
return (spa_vdev_state_exit(spa, NULL,
SET_ERROR(EBUSY)));
@@ -4413,7 +4507,8 @@ top:
vd->vdev_offline = B_TRUE;
vdev_reopen(tvd);
- if (!tvd->vdev_islog && vd->vdev_aux == NULL &&
+ if (!tvd->vdev_islog && !vdev_is_fully_backed_up(vd) &&
+ vd->vdev_aux == NULL &&
vdev_is_dead(tvd)) {
vd->vdev_offline = B_FALSE;
vdev_reopen(tvd);
@@ -5095,6 +5190,104 @@ vdev_space_update(vdev_t *vd, int64_t alloc_delta, int64_t defer_delta,
/* Note: metaslab_class_space_update moved to metaslab_space_update */
}
+
+/* If the vdev matches any of the flags, then return true. */
+static boolean_t vdev_array_vdev_is_in_flags(vdev_t *vd, uint64_t flags)
+{
+ uint64_t vdflags = 0;
+
+ if (vdev_is_leaf(vd)) {
+ vdflags |= VDEV_ARRAY_ANY_LEAF;
+ if (vdev_is_special(vd))
+ vdflags |= VDEV_ARRAY_SPECIAL_LEAF;
+
+ if (vdev_is_dedup(vd))
+ vdflags |= VDEV_ARRAY_DEDUP_LEAF;
+ }
+
+ /* If any flags match then success */
+ if (flags & vdflags)
+ return (B_TRUE);
+
+ return (B_FALSE);
+}
+
+/*
+ * We assume vda->vds[] is already allocated with the correct number of entries.
+ */
+static void
+vdev_array_visit(vdev_t *vd, uint64_t flags, struct vdev_array *vda)
+{
+ if (vdev_array_vdev_is_in_flags(vd, flags)) {
+ if (!(flags & VDEV_ARRAY_COUNT)) {
+ /* Add it to our array */
+ vda->vds[vda->count] = vd;
+ }
+ vda->count++;
+ }
+
+ for (uint64_t i = 0; i < vd->vdev_children; i++) {
+ vdev_array_visit(vd->vdev_child[i], flags, vda);
+ }
+}
+
+void
+vdev_array_free(struct vdev_array *vda)
+{
+ if (vda->vds != NULL)
+ kmem_free(vda->vds, sizeof (*vda->vds) * vda->count);
+
+ kmem_free(vda, sizeof (*vda));
+}
+
+/*
+ * Convenience function to iterate over the vdev tree, selecting only the vdevs
+ * you want, and return an array of the vdevs.
+ *
+ * Flags are OR'd to include vdevs. When flags == 0x0, no vdevs are matched.
+ *
+ * Array entries are returned in breadth first search order.
+ *
+ * The vdev_array returned needs to be freed with vdev_array_free() when you
+ * are done with it.
+ *
+ * You must have SCL_VDEV held so that the vdev tree doesn't change out from
+ * under you while calling this function or using the vdev_array returned.
+ */
+struct vdev_array *
+vdev_array_alloc(vdev_t *rvd, uint64_t flags)
+{
+ struct vdev_array *vda;
+
+ ASSERT(spa_config_held(rvd->vdev_spa, SCL_VDEV, RW_READER));
+
+ vda = kmem_zalloc(sizeof (*vda), KM_SLEEP);
+ if (!vda)
+ return (NULL);
+
+ /*
+ * We're going to do a first pass where we visit all the vdevs
+ * to get the count. After we get the count, we can then do the
+ * real visit to all the vdevs and add them to the array.
+ */
+ vda->count = 0;
+ vda->vds = NULL;
+
+ /* Do a dry run to get the count only */
+ vdev_array_visit(rvd, VDEV_ARRAY_COUNT | flags, vda);
+
+ /* We have the count, allocate the array */
+ vda->vds = kmem_zalloc(sizeof (vda->vds[0]) * vda->count, KM_SLEEP);
+ if (vda->vds == NULL) {
+ vdev_array_free(vda);
+ return (NULL);
+ }
+
+ vda->count = 0; /* init count to 0 again for vdev_array_visit() */
+ vdev_array_visit(rvd, flags, vda);
+ return (vda);
+}
+
/*
* Mark a top-level vdev's config as dirty, placing it on the dirty list
* so that it will be written out next time the vdev configuration is synced.
@@ -5259,10 +5452,14 @@ vdev_propagate_state(vdev_t *vd)
* device, treat the root vdev as if it were
* degraded.
*/
- if (child->vdev_islog && vd == rvd)
+ if ((child->vdev_islog ||
+ vdev_is_fully_backed_up(child)) &&
+ (vd == rvd)) {
degraded++;
- else
+ } else {
faulted++;
+ }
+
} else if (child->vdev_state <= VDEV_STATE_DEGRADED) {
degraded++;
}
@@ -5438,8 +5635,9 @@ vdev_set_state(vdev_t *vd, boolean_t isopen, vdev_state_t state, vdev_aux_t aux)
zfs_post_state_change(spa, vd, save_state);
}
- if (!isopen && vd->vdev_parent)
+ if (!isopen && vd->vdev_parent) {
vdev_propagate_state(vd->vdev_parent);
+ }
}
boolean_t
@@ -5488,6 +5686,54 @@ vdev_is_concrete(vdev_t *vd)
}
}
+/*
+ * Given a TLD vdev one level under the root vdev, return true if the TLD
+ * is fully backed-up to the pool. Backed-up means:
+ *
+ * 1. TLD is an alloc class device
+ * 2. The TLD has vdev_backup_to_pool set. If the TLD is a group (like a
+ * mirror) then all the child devices in the group must have
+ * vdev_backup_to_pool set.
+ * 3. vdev_backup_to_pool has always been set since the creation of this vdev.
+ * That means that all it's data has a copy in the pool.
+ */
+static boolean_t
+tld_is_fully_backed_up(vdev_t *tvd)
+{
+ if (!vdev_is_alloc_class(tvd))
+ return (B_FALSE);
+
+ /* Just a single device under the root */
+ if (vdev_is_leaf(tvd))
+ return (tvd->vdev_backup_to_pool);
+
+ for (int c = 0; c < tvd->vdev_children; c++) {
+ vdev_t *cvd = tvd->vdev_child[c];
+
+ if (!cvd->vdev_backup_to_pool)
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Is the vdev an alloc class vdev that is fully backed up to the pool?
+ *
+ * This function works for both top-level vdevs and leaf vdevs.
+ */
+boolean_t
+vdev_is_fully_backed_up(vdev_t *vd)
+{
+ if (!vdev_is_alloc_class(vd))
+ return (B_FALSE);
+
+ if (!vdev_is_leaf(vd))
+ return (tld_is_fully_backed_up(vd));
+
+ return (vd->vdev_backup_to_pool);
+}
+
/*
* Determine if a log device has valid content. If the vdev was
* removed or faulted in the MOS config then we know that
@@ -6296,8 +6542,22 @@ vdev_prop_get(vdev_t *vd, nvlist_t *innvl, nvlist_t *outnvl)
}
continue;
/* Numeric Properites */
+ case VDEV_PROP_BACKUP_TO_POOL:
+ /*
+ * Property is only used on leaf alloc class
+ * vdevs.
+ */
+ if (vdev_is_leaf(vd) && vdev_is_alloc_class(vd))
+ vdev_prop_add_list(outnvl, propname,
+ NULL, vd->vdev_backup_to_pool,
+ ZPROP_SRC_NONE);
+ else
+ vdev_prop_add_list(outnvl, propname,
+ NULL, ZPROP_BOOLEAN_NA,
+ ZPROP_SRC_NONE);
+ continue;
+
case VDEV_PROP_ALLOCATING:
- /* Leaf vdevs cannot have this property */
if (vd->vdev_mg == NULL &&
vd->vdev_top != NULL) {
src = ZPROP_SRC_NONE;
diff --git a/module/zfs/vdev_label.c b/module/zfs/vdev_label.c
index c31f48028b..745dc1062e 100644
--- a/module/zfs/vdev_label.c
+++ b/module/zfs/vdev_label.c
@@ -521,8 +521,7 @@ vdev_config_generate(spa_t *spa, vdev_t *vd, boolean_t getstats,
vd->vdev_removing);
}
- /* zpool command expects alloc class data */
- if (getstats && vd->vdev_alloc_bias != VDEV_BIAS_NONE) {
+ if (vd->vdev_alloc_bias != VDEV_BIAS_NONE) {
const char *bias = NULL;
switch (vd->vdev_alloc_bias) {
@@ -539,11 +538,17 @@ vdev_config_generate(spa_t *spa, vdev_t *vd, boolean_t getstats,
ASSERT3U(vd->vdev_alloc_bias, ==,
VDEV_BIAS_NONE);
}
+
fnvlist_add_string(nv, ZPOOL_CONFIG_ALLOCATION_BIAS,
bias);
}
}
+ if (vdev_is_alloc_class(vd) && vdev_is_leaf(vd) &&
+ vd->vdev_backup_to_pool) {
+ fnvlist_add_boolean(nv, ZPOOL_CONFIG_BACKUP_TO_POOL);
+ }
+
if (vd->vdev_dtl_sm != NULL) {
fnvlist_add_uint64(nv, ZPOOL_CONFIG_DTL,
space_map_object(vd->vdev_dtl_sm));
@@ -1804,9 +1809,10 @@ vdev_uberblock_sync_list(vdev_t **svd, int svdcount, uberblock_t *ub, int flags)
spa_t *spa = svd[0]->vdev_spa;
zio_t *zio;
uint64_t good_writes = 0;
+ boolean_t all_failures_are_backed_up = B_FALSE;
+ int rc;
zio = zio_root(spa, NULL, NULL, flags);
-
for (int v = 0; v < svdcount; v++)
vdev_uberblock_sync(zio, &good_writes, ub, svd[v], flags);
@@ -1850,7 +1856,38 @@ vdev_uberblock_sync_list(vdev_t **svd, int svdcount, uberblock_t *ub, int flags)
(void) zio_wait(zio);
- return (good_writes >= 1 ? 0 : EIO);
+ /*
+ * Special case:
+ *
+ * If we had zero good writes, but all the writes were to alloc class
+ * disks that were fully backed up to the pool, then it's not fatal.
+ */
+ if (good_writes == 0) {
+ all_failures_are_backed_up = B_TRUE;
+
+ for (int v = 0; v < svdcount; v++) {
+ if (!vdev_is_fully_backed_up(svd[v])) {
+ all_failures_are_backed_up = B_FALSE;
+ break;
+ }
+ }
+ }
+
+ if (good_writes >= 1) {
+ /* success */
+ rc = 0;
+ } else if (all_failures_are_backed_up) {
+ /*
+ * All the failures are on allocation class disks that were
+ * fully backed up to the pool, so this isn't fatal.
+ */
+ rc = 0;
+ } else {
+ /* failure */
+ rc = EIO;
+ }
+
+ return (rc);
}
/*
@@ -1966,7 +2003,8 @@ vdev_label_sync_list(spa_t *spa, int l, uint64_t txg, int flags)
good_writes = kmem_zalloc(sizeof (uint64_t), KM_SLEEP);
zio_t *vio = zio_null(zio, spa, NULL,
- (vd->vdev_islog || vd->vdev_aux != NULL) ?
+ (vd->vdev_islog || vd->vdev_aux != NULL ||
+ vdev_is_fully_backed_up(vd)) ?
vdev_label_sync_ignore_done : vdev_label_sync_top_done,
good_writes, flags);
vdev_label_sync(vio, good_writes, vd, l, txg, flags);
@@ -2019,6 +2057,7 @@ retry:
if (error != 0) {
if ((flags & ZIO_FLAG_TRYHARD) != 0)
return (error);
+
flags |= ZIO_FLAG_TRYHARD;
}
diff --git a/module/zfs/vdev_root.c b/module/zfs/vdev_root.c
index e132643dc3..5ce21a6338 100644
--- a/module/zfs/vdev_root.c
+++ b/module/zfs/vdev_root.c
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
/*
* Virtual device vector for the pool's root vdev.
@@ -46,6 +47,7 @@ vdev_root_core_tvds(vdev_t *vd)
vdev_t *cvd = vd->vdev_child[c];
if (!cvd->vdev_ishole && !cvd->vdev_islog &&
+ !vdev_is_fully_backed_up(vd) &&
cvd->vdev_ops != &vdev_indirect_ops) {
tvds++;
}
@@ -87,6 +89,7 @@ vdev_root_open(vdev_t *vd, uint64_t *asize, uint64_t *max_asize,
spa_t *spa = vd->vdev_spa;
int lasterror = 0;
int numerrors = 0;
+ int numerrors_recovered = 0;
if (vd->vdev_children == 0) {
vd->vdev_stat.vs_aux = VDEV_AUX_BAD_LABEL;
@@ -97,18 +100,25 @@ vdev_root_open(vdev_t *vd, uint64_t *asize, uint64_t *max_asize,
for (int c = 0; c < vd->vdev_children; c++) {
vdev_t *cvd = vd->vdev_child[c];
-
if (cvd->vdev_open_error && !cvd->vdev_islog &&
cvd->vdev_ops != &vdev_indirect_ops) {
lasterror = cvd->vdev_open_error;
numerrors++;
+ if (vdev_is_fully_backed_up(cvd))
+ numerrors_recovered++;
}
}
- if (spa_load_state(spa) != SPA_LOAD_NONE)
- spa_set_missing_tvds(spa, numerrors);
+ if (spa_load_state(spa) != SPA_LOAD_NONE) {
+ spa_set_missing_tvds(spa, numerrors, numerrors_recovered);
+ }
- if (too_many_errors(vd, numerrors)) {
+ if (numerrors != 0 && (numerrors == numerrors_recovered)) {
+ vdev_dbgmsg(vd, "there were %lu top-level errors, but they were"
+ " all on backed up alloc class devices. Keep trying to "
+ "import.",
+ (long unsigned) numerrors);
+ } else if (too_many_errors(vd, numerrors)) {
vd->vdev_stat.vs_aux = VDEV_AUX_NO_REPLICAS;
return (lasterror);
}
diff --git a/module/zfs/zio.c b/module/zfs/zio.c
index 08d56eef83..7fc6794b32 100644
--- a/module/zfs/zio.c
+++ b/module/zfs/zio.c
@@ -3505,6 +3505,19 @@ zio_ddt_write(zio_t *zio)
ASSERT(BP_IS_HOLE(bp) || zio->io_bp_override);
ASSERT(!(zio->io_bp_override && (zio->io_flags & ZIO_FLAG_RAW)));
+ /*
+ * Dedup writes can either to do a dedicated dedup device or to a
+ * dedicated special device. If we have alloc class backups on, we need
+ * to make an extra copy of the data to go on the pool. To do this
+ * we need to adjust the ZIO's copies here so the later stages in the
+ * ZIO pipeline work correctly.
+ */
+ if (spa->spa_backup_alloc_class && zp->zp_copies == 1) {
+ zp->zp_copies = 2;
+ }
+
+ p = zp->zp_copies;
+
ddt_enter(ddt);
dde = ddt_lookup(ddt, bp, B_TRUE);
ddp = &dde->dde_phys[p];
@@ -3635,6 +3648,22 @@ zio_dva_throttle(zio_t *zio)
mc = spa_preferred_class(spa, zio->io_size, zio->io_prop.zp_type,
zio->io_prop.zp_level, zio->io_prop.zp_zpl_smallblk);
+ /*
+ * If backup alloc classes is enabled, we will do the regular
+ * write to the special/dedup device and an additional "backup"
+ * write to the normal pool. That way if the special/dedup devices
+ * all fail, we don't lose all data in our pool.
+ *
+ * Reserve that 2nd write to the regular pool here. The DVAs
+ * for both writes will later be allocated in the
+ * next step in the ZIO pipeline in
+ * zio_dva_allocate()->metaslab_alloc().
+ */
+ if ((spa->spa_backup_alloc_class && (mc == spa_special_class(spa) ||
+ mc == spa_dedup_class(spa))) && zio->io_prop.zp_copies == 1) {
+ zio->io_prop.zp_copies = 2;
+ }
+
if (zio->io_priority == ZIO_PRIORITY_SYNC_WRITE ||
!mc->mc_alloc_throttle_enabled ||
zio->io_child_type == ZIO_CHILD_GANG ||
diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run
index 912344b4ed..a648fabe39 100644
--- a/tests/runfiles/common.run
+++ b/tests/runfiles/common.run
@@ -53,6 +53,14 @@ tags = ['functional', 'arc']
tests = ['atime_001_pos', 'atime_002_neg', 'root_atime_off', 'root_atime_on']
tags = ['functional', 'atime']
+
+[tests/functional/backup_alloc_class]
+tests = ['backup_alloc_class_add', 'backup_alloc_class_create',
+ 'backup_alloc_class_files', 'backup_alloc_class_import',
+ 'backup_alloc_class_offline', 'backup_alloc_class_prop',
+ 'backup_alloc_class_scrub', 'backup_alloc_class_split']
+tags = ['functional', 'backup_alloc_class']
+
[tests/functional/bclone]
tests = ['bclone_crossfs_corner_cases_limited',
'bclone_crossfs_data',
diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib
index dfab48d2cd..49a508cc59 100644
--- a/tests/zfs-tests/include/libtest.shlib
+++ b/tests/zfs-tests/include/libtest.shlib
@@ -1081,6 +1081,16 @@ function get_pool_prop # property pool
zpool get -Hpo value "$prop" "$pool" || log_fail "zpool get $prop $pool"
}
+# Get the specified vdev property in parsable format or fail
+function get_vdev_prop
+{
+ typeset prop=$1
+ typeset pool=$2
+ typeset vdev=$3
+
+ zpool get -Hpo value "$prop" "$pool" "$vdev" || log_fail "zpool get $prop $pool $vdev"
+}
+
# Return 0 if a pool exists; $? otherwise
#
# $1 - pool name
@@ -1815,7 +1825,7 @@ function verify_pool
function get_disklist # pool
{
echo $(zpool iostat -v $1 | awk '(NR > 4) {print $1}' | \
- grep -vEe '^-----' -e "^(mirror|raidz[1-3]|draid[1-3]|spare|log|cache|special|dedup)|\-[0-9]$")
+ grep -vEe '^-----' -e "^(mirror|raidz[1-3]|draid[1-3]|spare|log|cache|special|dedup)(\-[0-9])+$")
}
#
@@ -3907,3 +3917,28 @@ function pop_coredump_pattern
;;
esac
}
+
+# Get a list of all vdevs in the pool that are a certain type.
+#
+# The returned list is in a space-separated string, with the full path of each
+# vdev included:
+#
+# "/dev/sda /dev/sdb /dev/sdc"
+#
+# $1: Type of disk to get ('special', 'dedup', 'log', 'cache', 'spare')
+# $2: (optional) pool name
+function get_list_of_vdevs_that_are {
+ poolname=${2:-$TESTPOOL}
+
+ zpool status -P $poolname | sed -r '/\s+(mirror|draid|raidz)/d' | \
+ awk -v token="$1" '{
+ if (tmp == 1 && substr($1,1,1) == "/") {
+ if (first != 1) {
+ printf "%s", $1;
+ first=1;
+ } else {
+ printf " %s", $1;
+ }
+ } else {tmp=0}; if ($1 == token) {tmp=1}}
+ END {print ""}'
+}
diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am
index db6b4c0146..51d75cf516 100644
--- a/tests/zfs-tests/tests/Makefile.am
+++ b/tests/zfs-tests/tests/Makefile.am
@@ -90,6 +90,8 @@ nobase_dist_datadir_zfs_tests_tests_DATA += \
functional/alloc_class/alloc_class.kshlib \
functional/atime/atime.cfg \
functional/atime/atime_common.kshlib \
+ functional/backup_alloc_class/backup_alloc_class.cfg \
+ functional/backup_alloc_class/backup_alloc_class.kshlib \
functional/bclone/bclone.cfg \
functional/bclone/bclone_common.kshlib \
functional/bclone/bclone_corner_cases.kshlib \
@@ -441,6 +443,16 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/atime/root_atime_on.ksh \
functional/atime/root_relatime_on.ksh \
functional/atime/setup.ksh \
+ functional/backup_alloc_class/backup_alloc_class_add.ksh \
+ functional/backup_alloc_class/backup_alloc_class_create.ksh \
+ functional/backup_alloc_class/backup_alloc_class_files.ksh \
+ functional/backup_alloc_class/backup_alloc_class_import.ksh \
+ functional/backup_alloc_class/backup_alloc_class_prop.ksh \
+ functional/backup_alloc_class/backup_alloc_class_offline.ksh \
+ functional/backup_alloc_class/backup_alloc_class_scrub.ksh \
+ functional/backup_alloc_class/backup_alloc_class_split.ksh \
+ functional/backup_alloc_class/cleanup.ksh \
+ functional/backup_alloc_class/setup.ksh \
functional/bclone/bclone_crossfs_corner_cases.ksh \
functional/bclone/bclone_crossfs_corner_cases_limited.ksh \
functional/bclone/bclone_crossfs_data.ksh \
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_001_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_001_pos.ksh
index 3237d7cb78..7fbae70681 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_001_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_001_pos.ksh
@@ -32,12 +32,16 @@ log_assert $claim
log_onexit cleanup
log_must disk_setup
-for type in special dedup; do
- log_mustnot zpool create -d $TESTPOOL $CLASS_DISK0 $type $CLASS_DISK1
+
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in special dedup; do
+ log_mustnot zpool create $args -d $TESTPOOL $CLASS_DISK0 $type \
+ $CLASS_DISK1
+ done
+ log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ log_must display_status "$TESTPOOL"
+ log_must zpool destroy -f "$TESTPOOL"
done
-log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
-log_must display_status "$TESTPOOL"
-log_must zpool destroy -f "$TESTPOOL"
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_002_neg.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_002_neg.ksh
index b2cac59fd4..7f11ccfe34 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_002_neg.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_002_neg.ksh
@@ -31,18 +31,31 @@ log_onexit cleanup
log_must disk_setup
-log_mustnot zpool create $TESTPOOL raidz $ZPOOL_DISKS special $CLASS_DISK0
-log_mustnot display_status $TESTPOOL
-log_mustnot zpool destroy -f $TESTPOOL
+# Test with the older mode where there was no allow_backup_to_pool
+# feature. With this configuration, the special device redundancy needs
+# to match the pool.
+arg='-o feature@allow_backup_to_pool=disabled'
+for atype in "special" "dedup" ; do
+ log_mustnot zpool create $arg $TESTPOOL raidz $ZPOOL_DISKS $atype $CLASS_DISK0
-log_mustnot zpool create $TESTPOOL $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
-log_mustnot display_status $TESTPOOL
-log_mustnot zpool destroy -f $TESTPOOL
+ log_mustnot zpool create $arg $TESTPOOL $ZPOOL_DISKS $atype mirror \
+ $CLASS_DISK0 $CLASS_DISK1
-log_mustnot zpool create $TESTPOOL raidz $ZPOOL_DISKS special raidz \
- $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
-log_mustnot display_status $TESTPOOL
-log_mustnot zpool destroy -f $TESTPOOL
+ log_mustnot zpool create $arg $TESTPOOL raidz $ZPOOL_DISKS $atype raidz \
+ $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
+
+ # Now test with backup_allocation_classes=enabled (default setting)
+ log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS $atype $CLASS_DISK0
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create $TESTPOOL $ZPOOL_DISKS $atype mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+
+ log_must zpool destroy $TESTPOOL
+
+ log_mustnot zpool create $TESTPOOL raidz $ZPOOL_DISKS $atype raidz \
+ $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
+
+done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_003_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_003_pos.ksh
index 78d40ce56d..1aa8f59eeb 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_003_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_003_pos.ksh
@@ -31,27 +31,29 @@ log_onexit cleanup
log_must disk_setup
-for type in "" "mirror" "raidz"
-do
- log_must zpool create $TESTPOOL $type $ZPOOL_DISKS
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in "" "mirror" "raidz"
+ do
+ log_must zpool create $arg $TESTPOOL $type $ZPOOL_DISKS
- if [ "$type" = "mirror" ]; then
- log_must zpool add $TESTPOOL special mirror \
- $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK1
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK2
- elif [ "$type" = "raidz" ]; then
- log_must zpool add $TESTPOOL special mirror \
- $CLASS_DISK0 $CLASS_DISK1
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK1
- else
- log_must zpool add $TESTPOOL special $CLASS_DISK0
- log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
- fi
+ if [ "$type" = "mirror" ]; then
+ log_must zpool add $TESTPOOL special mirror \
+ $CLASS_DISK0 $CLASS_DISK1 $CLASS_DISK2
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK1
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK2
+ elif [ "$type" = "raidz" ]; then
+ log_must zpool add $TESTPOOL special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK1
+ else
+ log_must zpool add $TESTPOOL special $CLASS_DISK0
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK0
+ fi
- log_must zpool destroy -f $TESTPOOL
+ log_must zpool destroy -f $TESTPOOL
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_004_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_004_pos.ksh
index 04ce486adb..7347c89bb7 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_004_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_004_pos.ksh
@@ -36,31 +36,35 @@ typeset ac_value
typeset stype=""
typeset sdisks=""
-for type in "" "mirror" "raidz"
-do
- if [ "$type" = "mirror" ]; then
- stype="mirror"
- sdisks="${CLASS_DISK0} ${CLASS_DISK1} ${CLASS_DISK2}"
- elif [ "$type" = "raidz" ]; then
- stype="mirror"
- sdisks="${CLASS_DISK0} ${CLASS_DISK1}"
- else
- stype=""
- sdisks="${CLASS_DISK0}"
- fi
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in "" "mirror" "raidz"
+ do
+ if [ "$type" = "mirror" ]; then
+ stype="mirror"
+ sdisks="${CLASS_DISK0} ${CLASS_DISK1} ${CLASS_DISK2}"
+ elif [ "$type" = "raidz" ]; then
+ stype="mirror"
+ sdisks="${CLASS_DISK0} ${CLASS_DISK1}"
+ else
+ stype=""
+ sdisks="${CLASS_DISK0}"
+ fi
- log_must zpool create $TESTPOOL $type $ZPOOL_DISKS \
- special $stype $sdisks
+ log_must zpool create $arg $TESTPOOL $type $ZPOOL_DISKS \
+ special $stype $sdisks
- ac_value="$(zpool get -H -o property,value all | awk '/allocation_classes/ {print $2}')"
- if [ "$ac_value" = "active" ]; then
- log_note "feature@allocation_classes is active"
- else
- log_fail "feature@allocation_classes not active, \
- status = $ac_value"
- fi
+ ac_value="$(zpool get -H -o property,value \
+ feature@allocation_classes | \
+ awk '/allocation_classes/ {print $2}')"
+ if [ "$ac_value" = "active" ]; then
+ log_note "feature@allocation_classes is active"
+ else
+ log_fail "feature@allocation_classes not active, \
+ status = $ac_value"
+ fi
- log_must zpool destroy -f $TESTPOOL
+ log_must zpool destroy -f $TESTPOOL
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_005_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_005_pos.ksh
index 08c703e21a..734488290c 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_005_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_005_pos.ksh
@@ -34,38 +34,44 @@ log_must disk_setup
typeset ac_value
-for type in "" "mirror" "raidz"
-do
- if [ "$type" = "mirror" ]; then
- log_must zpool create $TESTPOOL $type $ZPOOL_DISK0 $ZPOOL_DISK1
- else
- log_must zpool create $TESTPOOL $type $ZPOOL_DISKS
- fi
- ac_value="$(zpool get -H -o property,value all | \
- awk '/allocation_classes/ {print $2}')"
- if [ "$ac_value" = "enabled" ]; then
- log_note "feature@allocation_classes is enabled"
- else
- log_fail "feature@allocation_classes not enabled, \
- status = $ac_value"
- fi
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in "" "mirror" "raidz"
+ do
+ if [ "$type" = "mirror" ]; then
+ log_must zpool create $arg $TESTPOOL $type $ZPOOL_DISK0 \
+ $ZPOOL_DISK1
+ else
+ log_must zpool create $arg $TESTPOOL $type $ZPOOL_DISKS
+ fi
+ ac_value="$(zpool get -H -o property,value \
+ feature@allocation_classes | \
+ awk '/allocation_classes/ {print $2}')"
+ if [ "$ac_value" = "enabled" ]; then
+ log_note "feature@allocation_classes is enabled"
+ else
+ log_fail "feature@allocation_classes not enabled, \
+ status = $ac_value"
+ fi
- if [ "$type" = "" ]; then
- log_must zpool add $TESTPOOL special $CLASS_DISK0
- else
- log_must zpool add $TESTPOOL special mirror \
- $CLASS_DISK0 $CLASS_DISK1
- fi
- ac_value="$(zpool get -H -o property,value all | \
- awk '/allocation_classes/ {print $2}')"
- if [ "$ac_value" = "active" ]; then
- log_note "feature@allocation_classes is active"
- else
- log_fail "feature@allocation_classes not active, \
- status = $ac_value"
- fi
+ if [ "$type" = "" ]; then
+ log_must zpool add $TESTPOOL special $CLASS_DISK0
+ else
+ log_must zpool add $TESTPOOL special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ fi
+ ac_value="$(zpool get -H -o property,value \
+ feature@allocation_classes | \
+ awk '/allocation_classes/ {print $2}')"
- log_must zpool destroy -f $TESTPOOL
+ if [ "$ac_value" = "active" ]; then
+ log_note "feature@allocation_classes is active"
+ else
+ log_fail "feature@allocation_classes not active, \
+ status = $ac_value"
+ fi
+
+ log_must zpool destroy -f $TESTPOOL
+ done
done
log_pass "Values of allocation_classes feature flag correct."
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_006_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_006_pos.ksh
index 5852b2876e..60b485d7dc 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_006_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_006_pos.ksh
@@ -32,10 +32,14 @@ log_onexit cleanup
log_must disk_setup
-log_must zpool create $TESTPOOL \
- mirror $ZPOOL_DISK0 $ZPOOL_DISK1 \
- special mirror $CLASS_DISK0 $CLASS_DISK1
-log_must zpool split $TESTPOOL split_pool
-log_must zpool destroy -f $TESTPOOL
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ log_must zpool create $arg $TESTPOOL \
+ mirror $ZPOOL_DISK0 $ZPOOL_DISK1 \
+ special mirror $CLASS_DISK0 $CLASS_DISK1
+ log_must zpool split $TESTPOOL split_pool
+ log_must zpool import -d $(dirname $CLASS_DISK1) split_pool
+ log_must zpool destroy -f $TESTPOOL
+ log_must zpool destroy -f split_pool
+done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_007_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_007_pos.ksh
index 106a6d933a..cc7df310d1 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_007_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_007_pos.ksh
@@ -31,11 +31,13 @@ log_onexit cleanup
log_must disk_setup
-log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS \
- special mirror $CLASS_DISK0 $CLASS_DISK1
-log_must zpool replace $TESTPOOL $CLASS_DISK1 $CLASS_DISK2
-log_must sleep 10
-log_must zpool iostat -H $TESTPOOL $CLASS_DISK2
-log_must zpool destroy -f $TESTPOOL
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ log_must zpool create $arg $TESTPOOL raidz $ZPOOL_DISKS \
+ special mirror $CLASS_DISK0 $CLASS_DISK1
+ log_must zpool replace $TESTPOOL $CLASS_DISK1 $CLASS_DISK2
+ log_must sleep 10
+ log_must zpool iostat -H $TESTPOOL $CLASS_DISK2
+ log_must zpool destroy -f $TESTPOOL
+done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_008_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_008_pos.ksh
index f73fbbe38c..772b9e77ee 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_008_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_008_pos.ksh
@@ -35,22 +35,24 @@ typeset special_type=""
typeset create_disks=""
typeset added_disks=""
-for type in "" "raidz"
-do
- if [ "$type" = "raidz" ]; then
- special_type="mirror"
- create_disks="${CLASS_DISK0} ${CLASS_DISK1}"
- added_disks="${CLASS_DISK2} ${CLASS_DISK3}"
- else
- special_type=""
- create_disks="${CLASS_DISK0}"
- added_disks="${CLASS_DISK1}"
- fi
- log_must zpool create $TESTPOOL $type $ZPOOL_DISKS \
- special $special_type $create_disks
- log_must zpool add $TESTPOOL special $special_type $added_disks
- log_must zpool iostat $TESTPOOL $added_disks
- log_must zpool destroy -f $TESTPOOL
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in "" "raidz"
+ do
+ if [ "$type" = "raidz" ]; then
+ special_type="mirror"
+ create_disks="${CLASS_DISK0} ${CLASS_DISK1}"
+ added_disks="${CLASS_DISK2} ${CLASS_DISK3}"
+ else
+ special_type=""
+ create_disks="${CLASS_DISK0}"
+ added_disks="${CLASS_DISK1}"
+ fi
+ log_must zpool create $args$TESTPOOL $type $ZPOOL_DISKS \
+ special $special_type $create_disks
+ log_must zpool add $TESTPOOL special $special_type $added_disks
+ log_must zpool iostat $TESTPOOL $added_disks
+ log_must zpool destroy -f $TESTPOOL
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_009_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_009_pos.ksh
index e8061fdabc..e6f807d5d5 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_009_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_009_pos.ksh
@@ -35,35 +35,39 @@ typeset stype=""
typeset sdisks=""
typeset props=""
-for type in "" "mirror" "raidz"
-do
- if [ "$type" = "mirror" ]; then
- stype="mirror"
- sdisks="${CLASS_DISK0} ${CLASS_DISK1} ${CLASS_DISK2}"
- props="-o ashift=12"
- elif [ "$type" = "raidz" ]; then
- stype="mirror"
- sdisks="${CLASS_DISK0} ${CLASS_DISK1}"
- else
- stype=""
- sdisks="${CLASS_DISK0}"
- fi
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for type in "" "mirror" "raidz"
+ do
+ if [ "$type" = "mirror" ]; then
+ stype="mirror"
+ sdisks="${CLASS_DISK0} ${CLASS_DISK1} ${CLASS_DISK2}"
+ props="-o ashift=12"
+ elif [ "$type" = "raidz" ]; then
+ stype="mirror"
+ sdisks="${CLASS_DISK0} ${CLASS_DISK1}"
+ else
+ stype=""
+ sdisks="${CLASS_DISK0}"
+ fi
- #
- # 1/3 of the time add the special vdev after creating the pool
- #
- if [ $((RANDOM % 3)) -eq 0 ]; then
- log_must zpool create ${props} $TESTPOOL $type $ZPOOL_DISKS
- log_must zpool add ${props} $TESTPOOL special $stype $sdisks
- else
- log_must zpool create ${props} $TESTPOOL $type $ZPOOL_DISKS \
- special $stype $sdisks
- fi
+ #
+ # 1/3 of the time add the special vdev after creating the pool
+ #
+ if [ $((RANDOM % 3)) -eq 0 ]; then
+ log_must zpool create $arg ${props} $TESTPOOL $type \
+ $ZPOOL_DISKS
+ log_must zpool add ${props} $TESTPOOL special $stype \
+ $sdisks
+ else
+ log_must zpool create $arg ${props} $TESTPOOL $type \
+ $ZPOOL_DISKS special $stype $sdisks
+ fi
- log_must zpool export $TESTPOOL
- log_must zpool import -d $TEST_BASE_DIR -s $TESTPOOL
- log_must display_status $TESTPOOL
- log_must zpool destroy -f $TESTPOOL
+ log_must zpool export $TESTPOOL
+ log_must zpool import -d $TEST_BASE_DIR -s $TESTPOOL
+ log_must display_status $TESTPOOL
+ log_must zpool destroy -f $TESTPOOL
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_010_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_010_pos.ksh
index cbf5cbf89b..344725d9e5 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_010_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_010_pos.ksh
@@ -32,19 +32,22 @@ log_onexit cleanup
log_must disk_setup
-log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ log_must zpool create $arg $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
-for value in 0 512 1024 2048 4096 8192 16384 32768 65536 131072
-do
- log_must zfs set special_small_blocks=$value $TESTPOOL
- ACTUAL=$(zfs get -p special_small_blocks $TESTPOOL | \
- awk '/special_small_blocks/ {print $3}')
- if [ "$ACTUAL" != "$value" ]
- then
- log_fail "v. $ACTUAL set for $TESTPOOL, expected v. $value!"
- fi
+ for value in 0 512 1024 2048 4096 8192 16384 32768 65536 131072
+ do
+ log_must zfs set special_small_blocks=$value $TESTPOOL
+ ACTUAL=$(zfs get -p special_small_blocks $TESTPOOL | \
+ awk '/special_small_blocks/ {print $3}')
+ if [ "$ACTUAL" != "$value" ]
+ then
+ log_fail "v. $ACTUAL set for $TESTPOOL, expected v. $value"
+ fi
+ done
+
+ log_must zpool destroy -f "$TESTPOOL"
done
-log_must zpool destroy -f "$TESTPOOL"
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_011_neg.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_011_neg.ksh
index 0be49b8587..4b07900752 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_011_neg.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_011_neg.ksh
@@ -32,13 +32,17 @@ log_assert $claim
log_onexit cleanup
log_must disk_setup
-log_must zpool create $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
-for value in 256 1025 33554432
-do
- log_mustnot zfs set special_small_blocks=$value $TESTPOOL
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ log_must zpool create $arg $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+
+ for value in 256 1025 33554432
+ do
+ log_mustnot zfs set special_small_blocks=$value $TESTPOOL
+ done
+
+ log_must zpool destroy -f "$TESTPOOL"
done
-log_must zpool destroy -f "$TESTPOOL"
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_012_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_012_pos.ksh
index 0b1c18bafd..9902e6922d 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_012_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_012_pos.ksh
@@ -25,20 +25,20 @@
verify_runnable "global"
#
-# Verify the file identified by the input is written on a special vdev
-# According to the pool layout used in this test vdev_id 3 and 4 are special
-# XXX: move this function to libtest.shlib once we get "Vdev Properties"
+# Given a dataset and an inode number, return a list of all the vdev numbers
+# that the inode has blocks on.
#
-function file_in_special_vdev #
+# For example, if the inode has blocks on vdevs 0, 1 and 2, this would return
+# the string "0 1 2"
+#
+function vdevs_file_is_on #
{
typeset dataset="$1"
typeset inum="$2"
- typeset num_normal=$(echo $ZPOOL_DISKS | wc -w)
- num_normal=${num_normal##* }
-
- zdb -dddddd $dataset $inum | awk -v d=$num_normal '{
+ zdb -dddddd $dataset $inum | awk '
+/L0 [0-9]+/{
# find DVAs from string "offset level dva" only for L0 (data) blocks
-if (match($0,"L0 [0-9]+")) {
+# if (match($0,"L0 [0-9]+")) {
dvas[0]=$3
dvas[1]=$4
dvas[2]=$5
@@ -50,25 +50,46 @@ if (match($0,"L0 [0-9]+")) {
print "Error parsing DVA: <" dva ">";
exit 1;
}
- # verify vdev is "special"
- if (arr[1] < d) {
- exit 1;
- }
+ count[arr[1]]++;
}
}
-}}'
+#}
+}
+END {
+ # Print out the unique vdev numbers that had data
+ firstprint=1;
+ for (i in count) {
+ if (firstprint==1) {
+ printf("%d", i);
+ firstprint=0;
+ } else {
+ printf(" %d", i);
+ }
+ }
+}
+'
}
#
# Check that device removal works for special class vdevs
#
+# $1: Set to 1 to backup alloc class data to the pool. Leave blank to disable
+# backup.
function check_removal
{
+ typeset backup
+ if [ "$1" == "1" ] ; then
+ backup=1
+ args=""
+ else
+ backup=0
+ args="-o feature@allow_backup_to_pool=disabled"
+ fi
+
#
# Create a non-raidz pool so we can remove top-level vdevs
#
- log_must disk_setup
- log_must zpool create $TESTPOOL $ZPOOL_DISKS \
+ log_must zpool create $args $TESTPOOL $ZPOOL_DISKS \
special $CLASS_DISK0 special $CLASS_DISK1
log_must display_status "$TESTPOOL"
@@ -93,19 +114,49 @@ function check_removal
for i in 1 2 3 4; do
dataset="$TESTPOOL/$TESTFS"
inum="$(get_objnum /$TESTPOOL/$TESTFS/testfile.$i)"
- log_must file_in_special_vdev $dataset $inum
+
+ # Get a list of all the vdevs 'testfile.$i' has blocks on.
+ # The list will be string like "0 1 2 3" if the blocks are on
+ # vdevs 0-3.
+ on_vdevs="$(vdevs_file_is_on $dataset $inum)"
+
+ # Get the number of normal (non-special) pool disks
+ num_pool_disks=$(echo $ZPOOL_DISKS | wc -w)
+ num_pool_disks=${num_pool_disks##* }
+
+ if [ "$backup" == "1" ] ; then
+ # Data should be on all vdevs (both pool and special
+ # devices).
+ lowest_data_disk=0
+ highest_data_disk=$(($num_pool_disks + 1))
+ else
+
+ # Data should only be on special devices
+ lowest_data_disk=$num_pool_disks
+ highest_data_disk=$(($lowest_data_disk + 1))
+ fi
+
+ # Get the starting disks that we expect the data to be on.
+ # We assume two special devices are attached to the pool.
+ # Disk numbers start at zero.
+ expected_on_vdevs="$(seq -s ' ' $lowest_data_disk $highest_data_disk)"
+
+ # Compare the disks we expect to see the blocks on with
+ # the actual disks they're on.
+ if [ "$on_vdevs" != "$expected_on_vdevs" ] ; then
+ # Data distribution is not what we expected, break out of
+ # the loop so we can properly tear down the pool. We will
+ # error out after the loop.
+ break;
+ fi
done
log_must zpool remove $TESTPOOL $CLASS_DISK0
-
- sleep 5
- sync_pool $TESTPOOL
- sleep 1
-
- log_must zdb -bbcc $TESTPOOL
- log_must zpool list -v $TESTPOOL
log_must zpool destroy -f "$TESTPOOL"
- log_must disk_cleanup
+
+ if [ "$on_vdevs" != "$expected_on_vdevs" ] ; then
+ log_fail "Expected data on disks $expected_on_vdevs, got $on_vdevs"
+ fi
}
claim="Removing a special device from a pool succeeds."
@@ -113,12 +164,15 @@ claim="Removing a special device from a pool succeeds."
log_assert $claim
log_onexit cleanup
-typeset CLASS_DEVSIZE=$CLASS_DEVSIZE
-for CLASS_DEVSIZE in $CLASS_DEVSIZE $ZPOOL_DEVSIZE; do
- typeset ZPOOL_DISKS=$ZPOOL_DISKS
- for ZPOOL_DISKS in "$ZPOOL_DISKS" $ZPOOL_DISK0; do
- check_removal
+log_must disk_setup
+for backup in "1" "" ; do
+ typeset CLASS_DEVSIZE=$CLASS_DEVSIZE
+ for CLASS_DEVSIZE in $CLASS_DEVSIZE $ZPOOL_DEVSIZE; do
+ typeset ZPOOL_DISKS=$ZPOOL_DISKS
+ for ZPOOL_DISKS in "$ZPOOL_DISKS" $ZPOOL_DISK0; do
+ check_removal $backup
+ done
done
done
-
+log_must disk_cleanup
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_013_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_013_pos.ksh
index 624cab88af..97e177e10a 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_013_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_013_pos.ksh
@@ -33,31 +33,34 @@ log_onexit cleanup
# Create a non-raidz pool so we can remove top-level vdevs
#
log_must disk_setup
-log_must zpool create $TESTPOOL $ZPOOL_DISKS dedup $CLASS_DISK0
-log_must display_status "$TESTPOOL"
-#
-# Generate some dedup data in the dedup class before removal
-#
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ log_must zpool create $arg $TESTPOOL $ZPOOL_DISKS dedup $CLASS_DISK0
+ log_must display_status "$TESTPOOL"
-log_must zfs create -o dedup=on -V 2G $TESTPOOL/$TESTVOL
-block_device_wait "$ZVOL_DEVDIR/$TESTPOOL/$TESTVOL"
-log_must eval "new_fs $ZVOL_DEVDIR/$TESTPOOL/$TESTVOL >/dev/null"
+ #
+ # Generate some dedup data in the dedup class before removal
+ #
-sync_pool
-log_must zpool list -v $TESTPOOL
+ log_must zfs create -o dedup=on -V 2G $TESTPOOL/$TESTVOL
+ block_device_wait "$ZVOL_DEVDIR/$TESTPOOL/$TESTVOL"
+ log_must eval "new_fs $ZVOL_DEVDIR/$TESTPOOL/$TESTVOL >/dev/null"
-#
-# remove a dedup allocation vdev
-#
-log_must zpool remove $TESTPOOL $CLASS_DISK0
+ sync_pool
+ log_must zpool list -v $TESTPOOL
-sleep 5
-sync_pool $TESTPOOL
-sleep 1
+ #
+ # remove a dedup allocation vdev
+ #
+ log_must zpool remove $TESTPOOL $CLASS_DISK0
-log_must zdb -bbcc $TESTPOOL
+ sleep 5
+ sync_pool $TESTPOOL
+ sleep 1
-log_must zpool destroy -f "$TESTPOOL"
+ log_must zdb -bbcc $TESTPOOL
+
+ log_must zpool destroy -f "$TESTPOOL"
+done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_014_neg.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_014_neg.ksh
index 1b52014fd2..1b83b8cc09 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_014_neg.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_014_neg.ksh
@@ -26,13 +26,15 @@ log_assert $claim
log_onexit cleanup
log_must disk_setup
-for size in 512 4096 32768 131072 524288 1048576
-do
- let bigger=$size*2
- log_mustnot zpool create -O recordsize=$size \
- -O special_small_blocks=$bigger \
- $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for size in 512 4096 32768 131072 524288 1048576
+ do
+ let bigger=$size*2
+ log_mustnot zpool create $arg -O recordsize=$size \
+ -O special_small_blocks=$bigger \
+ $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_015_pos.ksh b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_015_pos.ksh
index 49c468af67..693f68ac36 100755
--- a/tests/zfs-tests/tests/functional/alloc_class/alloc_class_015_pos.ksh
+++ b/tests/zfs-tests/tests/functional/alloc_class/alloc_class_015_pos.ksh
@@ -26,20 +26,22 @@ log_assert $claim
log_onexit cleanup
log_must disk_setup
-for size in 8192 32768 131072 524288 1048576
-do
- let smaller=$size/2
- log_must zpool create -O recordsize=$size \
- -O special_small_blocks=$smaller \
- $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
- log_must zpool destroy -f "$TESTPOOL"
+for arg in '-o feature@allow_backup_to_pool=disabled' '' ; do
+ for size in 8192 32768 131072 524288 1048576
+ do
+ let smaller=$size/2
+ log_must zpool create $arg -O recordsize=$size \
+ -O special_small_blocks=$smaller \
+ $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ log_must zpool destroy -f "$TESTPOOL"
- log_must zpool create -O recordsize=$size \
- -O special_small_blocks=$size \
- $TESTPOOL raidz $ZPOOL_DISKS special mirror \
- $CLASS_DISK0 $CLASS_DISK1
- log_must zpool destroy -f "$TESTPOOL"
+ log_must zpool create $arg -O recordsize=$size \
+ -O special_small_blocks=$size \
+ $TESTPOOL raidz $ZPOOL_DISKS special mirror \
+ $CLASS_DISK0 $CLASS_DISK1
+ log_must zpool destroy -f "$TESTPOOL"
+ done
done
log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.cfg b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.cfg
new file mode 100644
index 0000000000..84200593eb
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.cfg
@@ -0,0 +1,36 @@
+#
+# 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
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2017, Intel Corporation.
+# Copyright (c) 2018 by Delphix. All rights reserved.
+#
+
+. $STF_SUITE/include/libtest.shlib
+
+export ZPOOL_DISK0="$TEST_BASE_DIR/device-0"
+export ZPOOL_DISK1="$TEST_BASE_DIR/device-1"
+export ZPOOL_DISK2="$TEST_BASE_DIR/device-2"
+export ZPOOL_DISKS="${ZPOOL_DISK0} ${ZPOOL_DISK1} ${ZPOOL_DISK2}"
+
+export CLASS_DISK0="$TEST_BASE_DIR/device-3"
+export CLASS_DISK1="$TEST_BASE_DIR/device-4"
+export CLASS_DISK2="$TEST_BASE_DIR/device-5"
+export CLASS_DISK3="$TEST_BASE_DIR/device-6"
+export CLASS_DISK4="$TEST_BASE_DIR/device-7"
+export CLASS_DISK5="$TEST_BASE_DIR/device-8"
+
+export CLASS_DISKS="${CLASS_DISK0} ${CLASS_DISK1} ${CLASS_DISK2} ${CLASS_DISK3} ${CLASS_DISK4} ${CLASS_DISK5}"
+
+export ZPOOL_DEVSIZE=200M
+export CLASS_DEVSIZE=200M
+
+export IMPORTDIR="$TEST_BASE_DIR"
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.kshlib b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
new file mode 100644
index 0000000000..06ed4f8fd0
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
@@ -0,0 +1,283 @@
+#
+# 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
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2017, Intel Corporation.
+# Copyright (c) 2018 by Delphix. All rights reserved.
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.cfg
+
+BACKUP_DIR=$TEST_BASE_DIR/backups
+
+function disk_setup
+{
+ truncate -s $ZPOOL_DEVSIZE $ZPOOL_DISKS
+ truncate -s $CLASS_DEVSIZE $CLASS_DISKS
+
+ if [ -d $BACKUP_DIR ] ; then
+ log_fail "Existing $TEST_BASE_DIR/backups directory (maybe leftover from failed test run?)"
+ fi
+
+ mkdir -p $BACKUP_DIR
+}
+
+function disk_cleanup
+{
+ rm -f $ZPOOL_DEVSIZE $ZPOOL_DISKS 2> /dev/null
+ rm -f $CLASS_DEVSIZE $CLASS_DISKS 2> /dev/null
+
+ rm -f backup_alloc_class.key
+ rm -fr $BACKUP_DIR
+}
+
+function cleanup
+{
+ if datasetexists $TESTPOOL ; then
+ zpool destroy -f $TESTPOOL 2> /dev/null
+ fi
+
+ disk_cleanup
+}
+
+# Write zeros to an existing file, keeping the same size.
+function zero_file {
+ dd status=none if=/dev/zero of="$1" bs=$(stat_size "$1") count=1
+}
+
+# Write a verifiable file that will end up on a 'dedup' or 'special' vdev.
+# The filename will include the sha256 of the file for easy verification later.
+#
+# $1: Write type - "dedup" or "special"
+# $2: Path to directory to write the file to
+#
+# Note: we don't use log_must here since this can get really chatty and
+# we don't want to spam the logs. It will log_fail if there is an error.
+function write_verifiable_file {
+ class="$1"
+ writedir="$2"
+
+ if [[ "$class" == "dedup" ]] ; then
+ # Our dedup file size can be up to a megabyte-ish
+ filesize=$((32768 + ($RANDOM * $RANDOM % 1000000)))
+
+ # Make write a multiple of the recordsize for dedup
+ bs=32768
+ count=$(($filesize / $bs))
+
+ # Fill data with the letter 'a' for dedup
+ file_write -b $bs -c $count -d 'a' -o create -f $writedir/tmp || return
+ else
+ # Make all files less than the 32k special_small_blocks size we
+ # setup at dataset creation time
+ filesize=$((($RANDOM % 32767) + 1))
+ bs=$filesize
+ count=1
+ dd status=none if=/dev/urandom bs=$bs count=$count of="$writedir/tmp" || return
+ fi
+
+
+ csum=$(sha256digest "$writedir/tmp")
+ newfile=$csum.$class$totalwritten
+ mv "$writedir/tmp" "$writedir/$newfile"
+
+ # Basic sanity that we created our final file, and it has a non-zero size
+ expectedsize=$(($bs * $count))
+ actualsize=$(stat_size "$writedir/$newfile")
+ if [[ "$actualsize" != "$expectedsize" ]] || [[ "$actualsize" == "0" ]] ; then
+ log_fail "File $writedir/$newfile bad size $actualsize (expected $expectedsize)"
+ return
+ fi
+
+ totalwritten=$(($totalwritten + 1))
+}
+
+# Write some files to all our datasets.
+#
+# For each dataset:
+#
+# - 10 files should hit special vdevs
+# - 10 files should hit dedup vdevs
+function write_some_files {
+ typeset i
+ for i in $TESTFS 2copies 3copies encrypted encrypted2copies encrypted3copies ; do
+ for j in $(seq 1 10) ; do
+ write_verifiable_file special /$TESTPOOL/$i
+ write_verifiable_file dedup /$TESTPOOL/$i
+ done
+ done
+}
+
+# Given a directory containing only files created by write_verifiable_file(),
+# verify that the contents of the file match the sha256sum in the file's name.
+#
+# $1: Dir path with files to verify
+function verify_directory {
+ typeset verifydir="$1"
+ typeset i
+ for i in $(ls $verifydir) ; do
+
+ # Files will look like:
+ #
+ # ed324386045fa39d3f41d4f13c8c3e6a4698466e2b694c327f7e490be9e4e33f.dedup13
+ #
+ # Just grab the sha256 part
+
+ shaname="$(echo $i | cut -f1 -d'.')"
+ if [[ $(sha256digest "$verifydir/$i") != "$shaname" ]] ; then
+ log_fail "$verifydir/$i sha256 not $shaname"
+ false
+ return
+ fi
+ done
+ true
+}
+
+function backup_alloc_class_disks {
+ typeset i
+ for i in $@ ; do
+ cp ${i} $BACKUP_DIR/$(basename $i)
+ done
+}
+
+function restore_alloc_class_disks {
+ typeset i
+ for i in $@ ; do
+ mv $BACKUP_DIR/$(basename $i) ${i}
+ done
+}
+
+function zero_alloc_class_disks {
+ typeset i
+ for i in $@ ; do
+ zero_file "${i}"
+ done
+}
+
+# Create multiple datasets with different permutations of copies and encryption
+function backup_alloc_class_make_datasets {
+
+ log_must zfs create -o compression=off -o special_small_blocks=32K -o recordsize=32K \
+ -o dedup=on $TESTPOOL/$TESTFS
+
+ keyfile=$(pwd)/backup_alloc_class.key
+ dd if=/dev/random of=$keyfile bs=32 count=1
+
+ log_must zfs create -o copies=2 -o special_small_blocks=32K -o recordsize=32K -o dedup=on \
+ $TESTPOOL/2copies
+
+ log_must zfs create -o copies=3 -o special_small_blocks=32K -o recordsize=32K -o dedup=on \
+ $TESTPOOL/3copies
+
+ log_must zfs create -o encryption=on -o keylocation=file:///$keyfile -o keyformat=raw -o special_small_blocks=32K -o recordsize=32K -o dedup=on \
+ $TESTPOOL/encrypted
+
+ log_must zfs create -o copies=2 -o encryption=on -o keylocation=file:///$keyfile -o keyformat=raw -o special_small_blocks=32K -o recordsize=32K -o dedup=on \
+ $TESTPOOL/encrypted2copies
+
+ log_must zfs create -o copies=3 -o encryption=on -o keylocation=file:///$keyfile -o keyformat=raw -o special_small_blocks=32K -o recordsize=32K -o dedup=on \
+ $TESTPOOL/encrypted3copies
+}
+
+# For each dataset we created in backup_alloc_class_make_datasets, go though
+# and check that all the files in the datasets have the correct data.
+function verify_all_directories {
+ typeset i
+ for i in $TESTFS 2copies 3copies encrypted encrypted2copies encrypted3copies ; do
+ verify_directory /$TESTPOOL/$i
+ done
+
+ # ...we should also have the correct number of files
+ totalfiles=0
+ for i in $TESTFS 2copies 3copies encrypted encrypted2copies encrypted3copies ; do
+ totalfiles=$(($totalfiles + $(ls /$TESTPOOL/$i | wc -w)))
+ done
+
+ if [[ "$totalfiles" != "$totalwritten" ]] ; then
+ log_fail "Wrong file count: expected $totalwritten, got $totalfiles"
+ else
+ log_note "Verified $totalfiles files"
+ fi
+}
+
+# Return a space separated string of disks that are alloc class vdevs. Disk
+# names will include the full path.
+function get_list_of_alloc_class_disks {
+ typeset special_disks=$(get_list_of_vdevs_that_are "special")
+ typeset dedup_disks=$(get_list_of_vdevs_that_are "dedup")
+ typeset disks="$dedup_disks"
+
+ if [ -n "$special_disks" ] ; then
+ disks="$special_disks $disks"
+ fi
+
+ echo "$disks"
+}
+
+# Check that backup_to_pool is set to $1 on all disks in $2.
+function check_backup_to_pool_is {
+ typeset val=$1
+ typeset disks="$2"
+ typeset i
+ for i in $disks ; do
+ # Backup to pool should be enabled on all leaf vdevs
+ str="$(zpool get -H -o value backup_to_pool $TESTPOOL $i)"
+ if [ "$str" != "$val" ] ; then
+ log_fail "$i reported $str, expected $val"
+ fi
+ done
+}
+
+# Check that the pool/vdev proprieties and features for alloc class backups
+# are sane. For example, if the feature is disabled, then backup_to_pool
+# should not be enabled on any of the disks.
+function check_pool_alloc_class_props {
+ typeset allow_backup_to_pool=$(get_pool_prop feature@allow_backup_to_pool $TESTPOOL)
+ typeset backup_alloc_class_to_pool=$(get_pool_prop backup_alloc_class_to_pool $TESTPOOL)
+ typeset alloc_class_disks="$(get_list_of_alloc_class_disks)"
+
+ if [ "$allow_backup_to_pool" == "disabled" ] ; then
+ log_must [ "$backup_alloc_class_to_pool" == "off" ]
+ fi
+
+ if [ "$backup_alloc_class_to_pool" == "off" ] ; then
+ check_backup_to_pool_is "off" "$alloc_class_disks"
+ fi
+}
+
+
+# Simple function to check pool and vdev proprieties are what we expect. The
+# values we expect are passed to this function:
+#
+# $1: 'feature@allow_backup_to_pool' pool feature
+# $2: 'backup_alloc_class_to_pool' pool prop
+# $3: All alloc class vdev's 'backup_to_pool' vdev prop
+#
+# This function will log_fail on error.
+function boilerplate_check {
+ typeset allow_backup_to_pool=$1
+ typeset backup_alloc_class_to_pool=$2
+ typeset special_val=$3
+
+ typeset alloc_class_disks="$(get_list_of_alloc_class_disks)"
+
+ if [ "$(get_pool_prop feature@allow_backup_to_pool $TESTPOOL)" != "$allow_backup_to_pool" ] ; then
+ log_fail "feature@allow_backup_to_pool = $(get_pool_prop feature@allow_backup_to_pool $TESTPOOL), expected $allow_backup_to_pool"
+ fi
+
+ if [ "$(get_pool_prop backup_alloc_class_to_pool $TESTPOOL)" != "$backup_alloc_class_to_pool" ] ; then
+ log_fail "backup_alloc_class_to_pool = $(get_pool_prop backup_alloc_class_to_pool $TESTPOOL), expected $backup_alloc_class_to_pool"
+ fi
+
+ check_backup_to_pool_is "$special_val" "$alloc_class_disks"
+}
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_add.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_add.ksh
new file mode 100755
index 0000000000..82697d269d
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_add.ksh
@@ -0,0 +1,94 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Verify that 'zpool add' and 'zpool attach' disks have the correct backup
+# to pool settings.
+
+verify_runnable "global"
+
+claim="zpool add|attach disks have correct backup_to_pool settings"
+
+log_assert $claim
+log_onexit cleanup
+
+# Try different pool configurations
+configs="mirror $ZPOOL_DISK0 $ZPOOL_DISK1 special mirror $CLASS_DISK0 $CLASS_DISK1
+mirror $ZPOOL_DISK0 $ZPOOL_DISK1 dedup mirror $CLASS_DISK0 $CLASS_DISK1"
+
+log_must disk_setup
+
+function do_test {
+ typeset config="$1"
+ typeset initial=$2
+ typeset new=$3
+
+ log_must zpool create -o backup_alloc_class_to_pool=$initial $TESTPOOL $config
+ totalwritten=0
+
+ boilerplate_check "active" "$initial" "$initial"
+ backup_alloc_class_make_datasets
+ write_some_files
+
+ log_must zpool set backup_alloc_class_to_pool=$new $TESTPOOL
+
+ # We've just set backup_alloc_class_to_pool (possibly) a new value. Check
+ # that our new value still gives us the right props.
+ if [ $new == "off" ] || [ $initial == "off" ] ; then
+ initial_expected="off"
+ else
+ initial_expected="on"
+ fi
+
+ # Attach to our special/dedup mirror. New device should be fully
+ # backed up, but the old devices should remain not baked up.
+ alloc_class_disks="$(get_list_of_alloc_class_disks)"
+ log_must zpool attach $TESTPOOL $CLASS_DISK0 $CLASS_DISK2
+ check_backup_to_pool_is "$initial_expected" "$alloc_class_disks"
+ check_backup_to_pool_is "$new" "$CLASS_DISK2"
+ write_some_files
+
+ # Now add a new special/dedup disk. It should be backed up.
+ log_must zpool add $TESTPOOL special $CLASS_DISK4
+
+ check_backup_to_pool_is "$initial_expected" "$alloc_class_disks"
+ check_backup_to_pool_is "$new" "$CLASS_DISK2 $CLASS_DISK4"
+
+ write_some_files
+ verify_all_directories
+
+ log_must zpool export $TESTPOOL
+ log_must zpool import -l -d $IMPORTDIR $TESTPOOL
+
+ verify_all_directories
+
+ log_must zpool destroy $TESTPOOL
+}
+
+# Create a pool that is initially not backed up. Then, enable backups
+# and add/attach a disk. The new disks should be backed up, but the
+# old disks should not be backed up.
+echo "$configs" | while read config ; do
+ for initial in "on" "off" ; do
+ for new in "on" "off" ; do
+ do_test "$config" $initial $new
+ done
+ done
+done
+
+cleanup
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_create.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_create.ksh
new file mode 100755
index 0000000000..4f5ac99ada
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_create.ksh
@@ -0,0 +1,86 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+# DESCRIPTION:
+# Verify zpool create with different alloc class redundancy
+# levels correctly succeed or fail.
+
+verify_runnable "global"
+
+claim="zpool create with different backup and disk permutations work"
+
+log_assert $claim
+log_onexit cleanup
+
+# These should always pass since they have same redundancy level
+configs_pass="mirror $ZPOOL_DISK1 $ZPOOL_DISK2 special mirror $CLASS_DISK0 $CLASS_DISK1
+mirror $ZPOOL_DISK1 $ZPOOL_DISK2 dedup mirror $CLASS_DISK0 $CLASS_DISK1
+mirror $ZPOOL_DISK1 $ZPOOL_DISK2 special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3"
+
+# These should always pass with backup_to_pool enabled or when '-f' is passed.
+# They should fail otherwise.
+configs_pass_backup="mirror $ZPOOL_DISK1 $ZPOOL_DISK2 special $CLASS_DISK0
+mirror $ZPOOL_DISK1 $ZPOOL_DISK2 dedup $CLASS_DISK0
+mirror $ZPOOL_DISK1 $ZPOOL_DISK2 special $CLASS_DISK0 dedup $CLASS_DISK2
+mirror $ZPOOL_DISK1 $ZPOOL_DISK2 special mirror $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2"
+
+log_must disk_setup
+
+# Try configs with matching redundancy levels. They should all pass.
+echo "$configs_pass" | while read config ; do
+ log_must zpool create -o feature@allow_backup_to_pool=disabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -o feature@allow_backup_to_pool=enabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -f -o feature@allow_backup_to_pool=disabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -f -o feature@allow_backup_to_pool=enabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -o feature@allow_backup_to_pool=disabled -o backup_alloc_class_to_pool=off $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -o feature@allow_backup_to_pool=enabled -o backup_alloc_class_to_pool=on $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+done
+
+# Try configs with lower redundancy level. They should fail if backup to
+# pool is turned off and -f is not used.
+echo "$configs_pass_backup" | while read config ; do
+ log_mustnot zpool create -o feature@allow_backup_to_pool=disabled $TESTPOOL $config
+
+ log_must zpool create -o feature@allow_backup_to_pool=enabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -f -o feature@allow_backup_to_pool=disabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -f -o feature@allow_backup_to_pool=enabled $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_mustnot zpool create -o feature@allow_backup_to_pool=disabled -o backup_alloc_class_to_pool=off $TESTPOOL $config
+
+ log_must zpool create -f -o feature@allow_backup_to_pool=disabled -o backup_alloc_class_to_pool=off $TESTPOOL $config
+ log_must zpool destroy $TESTPOOL
+
+ log_mustnot zpool create -o feature@allow_backup_to_pool=enabled -o backup_alloc_class_to_pool=off $TESTPOOL $config
+done
+
+cleanup
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_files.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_files.ksh
new file mode 100755
index 0000000000..76c3a6a679
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_files.ksh
@@ -0,0 +1,124 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Test multiple different backup to pool permutations. After each step
+# write a bunch of known files. Verify all files are present and correct
+# after all the steps are complete.
+
+verify_runnable "global"
+
+claim="Files on backed-up disks do not get corrupted"
+
+log_assert $claim
+log_onexit cleanup
+
+# Try different pool configurations
+configs="mirror $ZPOOL_DISKS special $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2 $CLASS_DISK3
+raidz $ZPOOL_DISKS special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3
+$ZPOOL_DISKS special $CLASS_DISK0 dedup $CLASS_DISK1
+$ZPOOL_DISKS special $CLASS_DISK0
+$ZPOOL_DISKS dedup $CLASS_DISK0"
+
+echo "$configs" | while read config ; do
+ log_must disk_setup
+ log_must zpool create -o backup_alloc_class_to_pool=on $TESTPOOL $config
+ totalwritten=0
+ backup_alloc_class_make_datasets
+
+ write_some_files
+ verify_all_directories
+
+ alloc_class_disks="$(get_list_of_alloc_class_disks)"
+ log_must zpool export $TESTPOOL
+
+ backup_alloc_class_disks $alloc_class_disks
+ zero_alloc_class_disks $alloc_class_disks
+
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+
+ # Our pool is imported but has all its special devices zeroed out. Try
+ # writing some files to it and export the pool
+ write_some_files
+
+ log_must zpool export $TESTPOOL
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+
+ write_some_files
+
+ log_must zpool export $TESTPOOL
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+
+ write_some_files
+
+ # Make our old disks appear again (which have older data). Do a zpool
+ # clear to make them come back online and resilver.
+ restore_alloc_class_disks $alloc_class_disks
+ log_must zpool clear $TESTPOOL
+
+ write_some_files
+
+ # At this point the pool should be normal. The next test is to
+ # corrupt the alloc class devices while the pool is running.
+ zero_alloc_class_disks $alloc_class_disks
+
+ # Trigger a scrub with our newly-zeroed alloc class disks
+ log_must zpool scrub $TESTPOOL
+
+ # The pool should be degraded, but still alive.
+ check_state $TESTPOOL "" "DEGRADED"
+
+ write_some_files
+
+ # Replace all the alloc class disks. This should get the pool
+ # back to normal.
+ for disk in $alloc_class_disks ; do
+ log_must zpool replace $TESTPOOL $disk
+ done
+
+ write_some_files
+
+ log_must zpool export $TESTPOOL
+
+ # Backup special disks, then totally remove them.
+ backup_alloc_class_disks $alloc_class_disks
+
+ rm -f $alloc_class_disks
+
+ # Try to import with the alloc class disks missing - it should work.
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+
+ # After all the pain we've put our pool though, it should still have all the
+ # correct file data.
+ log_must verify_all_directories
+
+ if [[ "$totalwritten" != "840" ]] ; then
+ log_fail "Didn't see 840 files, saw $totalwritten"
+ fi
+
+ # We've checked all the files. Do some more verifications.
+ verify_pool $TESTPOOL
+ verify_filesys $TESTPOOL $TESTPOOL $IMPORTDIR
+
+ # Record a few stats that show metadata re in use
+ zpool get dedup $TESTPOOL
+ zdb -bb $TESTPOOL 2>&1 | grep -Ei 'normal|special|dedup|ddt'
+
+ log_must zpool destroy $TESTPOOL
+ cleanup
+done
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_import.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_import.ksh
new file mode 100755
index 0000000000..7f026f748a
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_import.ksh
@@ -0,0 +1,95 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Verify we can import a backed-up pool even if all its alloc class
+# devices are missing.
+#
+verify_runnable "global"
+
+claim="Verify imports work when backed-up devices are missing"
+
+log_assert $claim
+log_onexit cleanup
+
+TWO_ZPOOL_DISKS="$ZPOOL_DISK0 $ZPOOL_DISK1"
+REPLACE_DISK="$ZPOOL_DISK2"
+
+# Try a bunch of different pool configurations
+configs="$TWO_ZPOOL_DISKS special $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2 $CLASS_DISK3
+raidz $TWO_ZPOOL_DISKS special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3
+$TWO_ZPOOL_DISKS special $CLASS_DISK0 dedup $CLASS_DISK1
+$TWO_ZPOOL_DISKS special $CLASS_DISK0
+$TWO_ZPOOL_DISKS dedup $CLASS_DISK0"
+
+function do_test {
+ typeset config="$1"
+ typeset action="$2"
+ typeset onoff="$3"
+
+ totalwritten=0
+ log_must disk_setup
+ log_must zpool create -o backup_alloc_class_to_pool=$onoff $TESTPOOL $config
+
+ alloc_class_disks="$(get_list_of_alloc_class_disks)"
+
+ check_backup_to_pool_is "$onoff" "$alloc_class_disks"
+
+ backup_alloc_class_make_datasets
+ write_some_files
+ verify_all_directories
+
+ log_must zpool export $TESTPOOL
+
+ # Backup alloc class disk before removing them
+ backup_alloc_class_disks $alloc_class_disks
+ if [ "$action" == "remove" ] ; then
+ rm -f $alloc_class_disks
+ else
+ zero_alloc_class_disks $alloc_class_disks
+ fi
+
+ # import should succeed or fail depending on how we're backed up
+ if [ "$onoff" == "on" ] ; then
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+ else
+ log_mustnot zpool import -l -d "$IMPORTDIR" $TESTPOOL
+
+ # With the disks restored, we should be able to import
+ restore_alloc_class_disks $alloc_class_disks
+ log_must zpool import -l -d "$IMPORTDIR" $TESTPOOL
+ fi
+ write_some_files
+
+ # Do a scrub and verify everything is correct
+ verify_pool $TESTPOOL
+
+ verify_all_directories
+
+ zpool destroy $TESTPOOL
+
+ cleanup
+}
+
+echo "$configs" | while read config ; do
+ for action in "remove" "zero" ; do
+ for onoff in "off" "on" ; do
+ do_test "$config" "$action" "$onoff"
+ done
+ done
+done
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_offline.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_offline.ksh
new file mode 100755
index 0000000000..7bbdd7e1ff
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_offline.ksh
@@ -0,0 +1,126 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Verify we can offline backed-up alloc class disks.
+# Verify we cannot offline non-backed-up alloc class disks.
+#
+verify_runnable "global"
+
+claim="Verify correct behavior when we force fault an alloc class disk"
+
+log_assert $claim
+log_onexit cleanup
+
+# Try a bunch of different pool configurations
+configs="mirror $ZPOOL_DISKS special $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2 $CLASS_DISK3
+raidz $ZPOOL_DISKS special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3
+$ZPOOL_DISKS special $CLASS_DISK0 dedup $CLASS_DISK1
+$ZPOOL_DISKS special $CLASS_DISK0
+$ZPOOL_DISKS dedup $CLASS_DISK0"
+
+function do_test {
+ prop="$1"
+ config="$2"
+ log_must disk_setup
+ log_must zpool create -f $prop $TESTPOOL $config
+ check_pool_alloc_class_props
+
+ backup_alloc_class_make_datasets
+ totalwritten=0
+ write_some_files
+
+ alloc_class_disks=$(get_list_of_alloc_class_disks)
+ alloc_class_disks_arr=($alloc_class_disks)
+
+ if [ "$prop" == "" ] ; then
+ log_must [ "$(get_pool_prop feature@allow_backup_to_pool $TESTPOOL)" == "active" ]
+ else
+ log_must [ "$(get_pool_prop feature@allow_backup_to_pool $TESTPOOL)" == "disabled" ]
+ fi
+
+ for ((i = 0; i < ${#alloc_class_disks_arr[@]}; i++)); do
+ disk="${alloc_class_disks_arr[$i]}"
+ if [ "$prop" == "" ] ; then
+ # Everything is backed-up. We should be able to
+ # offline all the disks.
+ log_must zpool offline $TESTPOOL $disk
+ log_note "$(zpool status)"
+ log_must check_state $TESTPOOL "$disk" "OFFLINE"
+ log_must check_state $TESTPOOL "" "DEGRADED"
+ else
+ PARENT=$(get_vdev_prop parent $TESTPOOL $disk)
+ if [ "$PARENT" == "$TESTPOOL" ] ; then
+ # Leaf is TLD, offline should fail
+ log_mustnot zpool offline $TESTPOOL $disk
+ log_must check_state $TESTPOOL "$disk" "ONLINE"
+ log_must check_state $TESTPOOL "" "ONLINE"
+ else
+ # We're part of a mirror. We know all
+ # mirrors in our test pool are two disk
+ # so we should be able to offline the
+ # first disk, but not the second.
+ if [ "$i" == "0" ] ; then
+ # First alloc class disk - pretend
+ # "previous" disk was online to
+ # make things easy.
+ prev_online=1
+ else
+ if check_state $TESTPOOL "${alloc_class_disks_arr[$i - 1]}" "ONLINE" ; then
+ prev_online=1
+ else
+ prev_online=0
+ fi
+ fi
+
+ if [ "$prev_online" == "1" ] ; then
+ # First disk in mirror, can offline
+ log_must zpool offline $TESTPOOL $disk
+ log_must check_state $TESTPOOL "$disk" "OFFLINE"
+ log_must check_state $TESTPOOL "" "DEGRADED"
+ else
+ # Second disk in mirror, can't offline
+ # but we should still be in a pool
+ # degraded state from the first disk
+ # going offline.
+ log_note "$(zpool status)"
+ log_mustnot zpool offline $TESTPOOL $disk
+ log_must check_state $TESTPOOL "$disk" "ONLINE"
+ log_must check_state $TESTPOOL "" "DEGRADED"
+ fi
+ fi
+ fi
+ done
+
+ write_some_files
+ verify_all_directories
+
+ # We've checked all the files. Do some more verifications.
+ verify_pool $TESTPOOL
+ verify_filesys $TESTPOOL $TESTPOOL $IMPORTDIR
+
+ zpool clear $TESTPOOL
+ zpool destroy $TESTPOOL
+ cleanup
+}
+
+for prop in "-o feature@allow_backup_to_pool=disabled" "" ; do
+ echo "$configs" | while read config ; do
+ do_test "$prop" "$config"
+ done
+done
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_prop.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_prop.ksh
new file mode 100755
index 0000000000..5096d22499
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_prop.ksh
@@ -0,0 +1,98 @@
+#!/bin/ksh -p
+
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049).
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Verify that alloc class backups to pool do not work if
+# SPA_FEATURE_ALLOW_BACKUP_TO_POOL is disabled. Also, test upgrades.
+
+verify_runnable "global"
+
+claim="alloc class backups shouldn't work without SPA_FEATURE_ALLOW_BACKUP_TO_POOL"
+
+log_assert $claim
+log_onexit cleanup
+
+IMPORTDIR="$(dirname ${CLASS_DISK0})"
+
+# Try a bunch of different pool configurations
+configs="$ZPOOL_DISKS special $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2 $CLASS_DISK3
+raidz $ZPOOL_DISKS special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3
+$ZPOOL_DISKS special $CLASS_DISK0 dedup $CLASS_DISK1
+$ZPOOL_DISKS special $CLASS_DISK0
+$ZPOOL_DISKS dedup $CLASS_DISK0"
+
+# Make the pool disks smaller to make them quicker to back up. We don't use
+# much data on them.
+export ZPOOL_DEVSIZE=200M
+export CLASS_DEVSIZE=200M
+
+log_must disk_setup
+
+echo "$configs" | while read config ; do
+ # We should not be able to set backup_alloc_class_to_pool=on if feature
+ # flag is disabled.
+ log_mustnot zpool create -o feature@allow_backup_to_pool=disabled -o backup_alloc_class_to_pool=on $TESTPOOL $config
+
+ # Try a few permutations that should succeed
+ log_must zpool create -o backup_alloc_class_to_pool=off $TESTPOOL $config
+ boilerplate_check "active" "off" "off"
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -o backup_alloc_class_to_pool=on $TESTPOOL $config
+ boilerplate_check "active" "on" "on"
+ log_must zpool destroy $TESTPOOL
+
+ log_must zpool create -o feature@allow_backup_to_pool=enabled -o backup_alloc_class_to_pool=on $TESTPOOL $config
+ boilerplate_check "active" "on" "on"
+ log_must zpool destroy $TESTPOOL
+
+ # Now let's do a multi-step test
+ for cmd in "zpool set feature@allow_backup_to_pool=enabled $TESTPOOL" "zpool upgrade $TESTPOOL" ; do
+ log_note "config='$config'"
+ log_must zpool create -o feature@allow_backup_to_pool=disabled -o backup_alloc_class_to_pool=off $TESTPOOL $config
+ totalwritten=0
+
+ boilerplate_check "disabled" "off" "off"
+ backup_alloc_class_make_datasets
+ write_some_files
+
+ # Test enabling the feature in two different ways:
+ #
+ # zpool set allow_backup_to_pool=enabled ...
+ # zpool upgrade ...
+ #
+ log_must eval "$cmd"
+ boilerplate_check "active" "off" "off"
+ write_some_files
+
+ log_must zpool set backup_alloc_class_to_pool=on $TESTPOOL
+ boilerplate_check "active" "on" "off"
+ write_some_files
+
+ log_must zpool export $TESTPOOL
+ log_must zpool import -l -d $IMPORTDIR $TESTPOOL
+
+ verify_all_directories
+
+ log_must zpool destroy $TESTPOOL
+ done
+
+done
+
+cleanup
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_scrub.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_scrub.ksh
new file mode 100755
index 0000000000..210fa53bd9
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_scrub.ksh
@@ -0,0 +1,112 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Destroy alloc class disks and then do a scrub on both a
+# backed-up and non-backed-up pool. The backed-up pool
+# should only be DEGRADED, while the no-backed-up pool should be
+# SUSPENDED.
+
+verify_runnable "global"
+
+claim="Backed-up pools survive a normally fatal scrub with bad disks"
+
+log_assert $claim
+log_onexit cleanup
+
+# Try different pool configurations
+configs="$ZPOOL_DISKS special $CLASS_DISK0 $CLASS_DISK1 dedup $CLASS_DISK2 $CLASS_DISK3
+raidz $ZPOOL_DISKS special mirror $CLASS_DISK0 $CLASS_DISK1 dedup mirror $CLASS_DISK2 $CLASS_DISK3
+$ZPOOL_DISKS special $CLASS_DISK0 dedup $CLASS_DISK1
+$ZPOOL_DISKS special $CLASS_DISK0
+$ZPOOL_DISKS dedup $CLASS_DISK0"
+
+function do_test {
+ typeset config="$1"
+ typeset action="$2"
+ typeset onoff="$3"
+ totalwritten=0
+
+ log_must disk_setup
+ log_must zpool create -o feature@allow_backup_to_pool=enabled -o backup_alloc_class_to_pool=$onoff $TESTPOOL $config
+
+ backup_alloc_class_make_datasets
+
+ totalwritten=0
+ write_some_files
+
+ alloc_class_disks="$(get_list_of_alloc_class_disks)"
+ log_note "$(zpool status)"
+ check_backup_to_pool_is "$onoff" "$alloc_class_disks"
+
+ # When we do a scrub later, we will either want it to suspend or not
+ # suspended the pool, depending on our backup settings.
+ # Make sure we are able to ride though the suspended pool so we
+ # can continue with our tests.
+ log_must zpool set failmode=continue $TESTPOOL
+
+ backup_alloc_class_disks $alloc_class_disks
+
+ check_backup_to_pool_is "$onoff" "$alloc_class_disks"
+
+ zero_alloc_class_disks $alloc_class_disks
+
+ # Spawn scrub into the background since the pool may be suspended and
+ # it will hang. We need to continue pass the hung scrub so we
+ # can restore the bad disks and do a 'zpool clear' to remove the
+ # suspended pool.
+ zpool scrub $TESTPOOL &
+
+ wait_scrubbed $TESTPOOL 3
+ if [ "$onoff" == "on" ] ; then
+ log_must check_state $TESTPOOL "" "DEGRADED"
+
+ verify_pool $TESTPOOL
+
+ write_some_files
+ verify_all_directories
+ else
+ log_must check_state $TESTPOOL "" "SUSPENDED"
+
+ # Pool should be suspended. Restore the old disks so we can
+ # clear the suspension. 'zpool clear' here will delete the
+ # pool.
+ restore_alloc_class_disks $alloc_class_disks
+ log_must zpool clear $TESTPOOL
+ fi
+
+ cleanup
+}
+
+# Stop zed in case we left it running from an old, aborted, test run.
+zed_stop
+zed_cleanup
+
+log_must zed_setup
+log_must zed_start
+log_must zed_events_drain
+
+# Verify scrubs work as expected with different permutations of backup_to_pool.
+echo "$configs" | while read config ; do
+ for i in "on" "off" ; do
+ do_test "$config" "zero" "$i"
+ done
+done
+
+log_must zed_stop
+log_must zed_cleanup
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_split.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_split.ksh
new file mode 100755
index 0000000000..1de18a8f61
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/backup_alloc_class_split.ksh
@@ -0,0 +1,101 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+#
+# DESCRIPTION:
+# Verify we can split a pool with backup to pool, and the new pool
+# keeps the backup to pool settings. Also verify the new pool has
+# all the data if the pool is backed up.
+#
+verify_runnable "global"
+
+claim="zpool split works with backup to pool"
+
+log_assert $claim
+log_onexit cleanup
+
+IMPORTDIR="$(dirname ${CLASS_DISK0})"
+
+
+# Create a normal, backed-up pool
+log_must disk_setup
+log_must zpool create -o backup_alloc_class_to_pool=on $TESTPOOL mirror \
+ $ZPOOL_DISK0 $ZPOOL_DISK1 special mirror $CLASS_DISK0 $CLASS_DISK1 dedup \
+ mirror $CLASS_DISK2 $CLASS_DISK3
+
+totalwritten=0
+backup_alloc_class_make_datasets
+write_some_files
+verify_all_directories
+
+# Split the pool and verify the old pool has all the data
+newpool="${TESTPOOL}-2"
+
+log_must zpool split $TESTPOOL $newpool
+check_backup_to_pool_is "on"
+check_pool_alloc_class_props
+verify_all_directories
+
+# Forcefault alloc class devices on the old pool and verify we have all the
+# data.
+log_must zpool offline -f $TESTPOOL $CLASS_DISK0
+log_must zpool offline -f $TESTPOOL $CLASS_DISK2
+log_must check_state $TESTPOOL $CLASS_DISK0 "FAULTED"
+log_must check_state $TESTPOOL $CLASS_DISK2 "FAULTED"
+
+log_must check_state $TESTPOOL "" "DEGRADED"
+verify_all_directories
+
+log_must zpool clear $TESTPOOL
+
+# All done with the old pool
+log_must zpool destroy $TESTPOOL
+
+# Import the new split pool and rename it $TESTPOOL since all our verification
+# functions expect the pool to be called $TESTPOOL.
+log_must zpool import -l -f -d $IMPORTDIR $newpool $TESTPOOL
+
+check_backup_to_pool_is "on"
+check_pool_alloc_class_props
+verify_all_directories
+
+# zero alloc class devices on the old pool and verify we have all the
+# data.
+log_must zpool export $TESTPOOL
+
+zero_file $CLASS_DISK1
+zero_file $CLASS_DISK3
+
+log_must zpool import -l -f -d $IMPORTDIR $TESTPOOL
+
+verify_all_directories
+log_must zpool destroy $TESTPOOL
+
+# Create a non-backed-up pool, split it, and verify the split pool is also
+# not backed-up.
+log_must zpool create -o backup_alloc_class_to_pool=off $TESTPOOL mirror \
+ $ZPOOL_DISK0 $ZPOOL_DISK1 special mirror $CLASS_DISK0 $CLASS_DISK1 dedup \
+ mirror $CLASS_DISK2 $CLASS_DISK3
+
+log_must zpool split $TESTPOOL $newpool
+check_backup_to_pool_is "off"
+check_pool_alloc_class_props
+log_must zpool destroy $TESTPOOL
+log_must zpool import -l -f -d $IMPORTDIR $newpool $TESTPOOL
+check_backup_to_pool_is "off"
+check_pool_alloc_class_props
+log_must zpool destroy $TESTPOOL
+
+log_pass $claim
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/cleanup.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/cleanup.ksh
new file mode 100755
index 0000000000..a3c60a3925
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/cleanup.ksh
@@ -0,0 +1,27 @@
+#!/bin/ksh -p
+
+#
+# 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
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2017, Intel Corporation.
+# Copyright (c) 2018, Delphix
+#
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+verify_runnable "global"
+
+default_cleanup_noexit
+disk_cleanup
+
+log_pass
diff --git a/tests/zfs-tests/tests/functional/backup_alloc_class/setup.ksh b/tests/zfs-tests/tests/functional/backup_alloc_class/setup.ksh
new file mode 100755
index 0000000000..bf044daa92
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/backup_alloc_class/setup.ksh
@@ -0,0 +1,24 @@
+#!/bin/ksh -p
+
+# Copyright (C) 2024 Lawrence Livermore National Security, LLC.
+# Refer to the OpenZFS git commit log for authoritative copyright attribution.
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License Version 1.0 (CDDL-1.0).
+# You can obtain a copy of the license from the top-level file
+# "OPENSOLARIS.LICENSE" or at .
+# You may not use this file except in compliance with the license.
+#
+# Developed at Lawrence Livermore National Laboratory (LLNL-CODE-403049)
+#
+# Copyright (c) 2017, Intel Corporation.
+# Copyright (c) 2018 by Delphix. All rights reserved.
+
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/backup_alloc_class/backup_alloc_class.kshlib
+
+verify_runnable "global"
+
+disk_cleanup
+
+log_pass