mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-29 03:45:02 +01:00
Keep a grown vdev from adopting an older pool's labels
Labels 2 and 3 sit at offsets relative to the end of a device, so growing a vdev moves them onto space the pool has never written. vdev_copy_uberblocks() already says as much of the uberblock rings found there, "either empty or contain garbage", yet the config and the uberblocks are still read from those labels as though they were ours. Whatever pool held the device before is usually still on it, its labels are perfectly well formed, and vdev_label_read_config() ranks labels by nothing but txg, so a leftover label with a higher txg wins and vdev_validate() fails the device for belonging to a foreign pool. The reporter grew LVM volumes laid over used disks and got a raidz child back FAULTED with "corrupted data"; where no redundancy is left to cover the member, the pool suspends. Issue #16144. Read the config for validation from the two labels at the head of the device, whose offsets do not depend on its size, and fall back to the trailing pair only when the head cannot be read, so a damaged head still has the second copy behind it. vdev_label_sync() writes the even and odd labels in separate passes, so the head pair spans both and is never systematically staler than the tail. A device with both a damaged head and a foreign tail has nothing left to tell the two apart, and comes out where it does today. vdev_validate_aux() goes on reading all four: an aux label carries no pool txg, so that path takes the first valid label rather than ranking them. That alone is not enough. With the device validating again its stale uberblocks are back in play, and vdev_uberblock_load() takes the highest txg it finds anywhere, which is the old pool's; the load then dies on a rootbp describing somebody else's MOS. So compare the two pairs while validating, and when the trailing labels name a different pool, mark the vdev and keep uberblock selection away from them as well. vdev_validate() runs ahead of uberblock selection in spa_load(), so the mark is set in time. Getting rid of the mark takes more than waiting for the pool to write to those labels. A sync puts the config in all four but touches only one slot of each uberblock ring, so the rest of a foreign ring lives on, outranking our own until the txg comes round to overwrite it, while the config write has already destroyed the evidence that anything was wrong: both pairs now name this pool. The pool would import once and refuse to the next time, which is worse than it behaves today. So the mark drives the same full rebuild of the trailing rings that an expanded vdev gets from vdev_copy_uberblocks(), and is cleared only once that copy has been made. Tests: on a mirror with one member grown from 256M to 512M over an exported pool's labels (donor txg 93, pool txg 4). Before, "zpool online -e" left the member FAULTED "corrupted data" with "vdev label pool_guid doesn't match config" and exited 1, and a cache file import brought the pool up DEGRADED with the member faulted out. After, both leave member and pool ONLINE, and a sync puts four of the pool's own labels back on the device. With only the head-label change the import instead failed with "unable to open rootbp in dsl_pool_init [error=5]" on the donor's uberblock at txg 93. New zpool_expand_007_pos covers the expansion, the import, and an export and import after that with nothing planted, which is what catches a foreign ring left behind the pool's own config; it fails without this change. The zpool_expand, mmp, zpool_split and zpool_reguid groups pass, and zpool_import has no unexpected results. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: MorganaFuture <[email protected]> Closes #16144 Closes #18909
This commit is contained in:
1 parent
2aadd73072
commit
1b71435579
7 files changed
+290
-13
No files matched your search
+1
-1
@@ -212,7 +212,7 @@ extern nvlist_t *vdev_config_generate(spa_t *spa, vdev_t *vd,
|
||||
struct uberblock;
|
||||
extern uint64_t vdev_label_offset(uint64_t psize, int l, uint64_t offset);
|
||||
extern int vdev_label_number(uint64_t psise, uint64_t offset);
|
||||
extern nvlist_t *vdev_label_read_config(vdev_t *vd, uint64_t txg);
|
||||
extern nvlist_t *vdev_label_read_config(vdev_t *vd, uint64_t txg, int labels);
|
||||
extern void vdev_uberblock_load(vdev_t *, struct uberblock *, nvlist_t **);
|
||||
extern void vdev_config_generate_stats(vdev_t *vd, nvlist_t *nv);
|
||||
extern void vdev_label_write(zio_t *zio, vdev_t *vd, int l, abd_t *buf, uint64_t
|
||||
|
||||
@@ -406,6 +406,7 @@ struct vdev {
|
||||
boolean_t vdev_isspare; /* was a hot spare */
|
||||
boolean_t vdev_isl2cache; /* was a l2cache device */
|
||||
boolean_t vdev_copy_uberblocks; /* post expand copy uberblocks */
|
||||
boolean_t vdev_tail_labels_foreign; /* labels 2-3 are not ours */
|
||||
boolean_t vdev_resilver_deferred; /* resilver deferred */
|
||||
boolean_t vdev_kobj_flag; /* kobj event record */
|
||||
boolean_t vdev_attaching; /* vdev attach ashift handling */
|
||||
@@ -547,6 +548,25 @@ typedef struct vdev_label {
|
||||
#define VDEV_LABEL_END_SIZE (2 * sizeof (vdev_label_t))
|
||||
#define VDEV_LABELS 4
|
||||
#define VDEV_BEST_LABEL VDEV_LABELS
|
||||
|
||||
/*
|
||||
* Subsets of the labels, for the routines which read them. Labels 0 and 1
|
||||
* sit at fixed offsets from the start of the device; labels 2 and 3 sit at
|
||||
* offsets relative to its end, and so move whenever the device is resized.
|
||||
*/
|
||||
#define VDEV_LABELS_HEAD 0x3
|
||||
#define VDEV_LABELS_TAIL 0xc
|
||||
#define VDEV_LABELS_ALL (VDEV_LABELS_HEAD | VDEV_LABELS_TAIL)
|
||||
|
||||
/*
|
||||
* The labels of a vdev which are known to describe the pool it belongs to.
|
||||
* vdev_validate() drops the trailing pair when it finds another pool's
|
||||
* labels there, which is what a vdev grown over an older pool is left with
|
||||
* until the next sync rewrites them.
|
||||
*/
|
||||
#define VDEV_TRUSTED_LABELS(vd) \
|
||||
((vd)->vdev_tail_labels_foreign ? VDEV_LABELS_HEAD : VDEV_LABELS_ALL)
|
||||
|
||||
#define VDEV_OFFSET_IS_LABEL(vd, off) \
|
||||
(((off) < VDEV_LABEL_START_SIZE) || \
|
||||
((off) >= ((vd)->vdev_psize - VDEV_LABEL_END_SIZE)))
|
||||
|
||||
+64
-2
@@ -2448,6 +2448,47 @@ vdev_open(vdev_t *vd, cred_t *cred)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note whether the labels at the end of the device describe a different pool
|
||||
* than the ones at its head, which is what a vdev grown over the remains of
|
||||
* an older pool is left with until the next sync rewrites them. The head
|
||||
* labels are the ones to believe: their offsets are fixed, while the trailing
|
||||
* pair moves with the size of the device.
|
||||
*
|
||||
* The trailing labels are read without a txg bound: which pool a label names
|
||||
* does not depend on how recent it is, and a leftover one is quite likely to
|
||||
* be from beyond our own txg.
|
||||
*/
|
||||
static void
|
||||
vdev_check_tail_labels(vdev_t *vd, nvlist_t *head)
|
||||
{
|
||||
nvlist_t *tail;
|
||||
uint64_t head_guid, tail_guid;
|
||||
|
||||
vd->vdev_tail_labels_foreign = B_FALSE;
|
||||
|
||||
/* A distributed spare's label is generated, not read off a disk. */
|
||||
if (vd->vdev_ops == &vdev_draid_spare_ops)
|
||||
return;
|
||||
|
||||
if (nvlist_lookup_uint64(head, ZPOOL_CONFIG_POOL_GUID, &head_guid) != 0)
|
||||
return;
|
||||
|
||||
tail = vdev_label_read_config(vd, UINT64_MAX, VDEV_LABELS_TAIL);
|
||||
if (tail == NULL)
|
||||
return;
|
||||
|
||||
if (nvlist_lookup_uint64(tail, ZPOOL_CONFIG_POOL_GUID,
|
||||
&tail_guid) == 0 && tail_guid != head_guid) {
|
||||
vd->vdev_tail_labels_foreign = B_TRUE;
|
||||
vdev_dbgmsg(vd, "labels 2 and 3 belong to pool_guid %llu, not "
|
||||
"%llu; ignoring them until they are rewritten",
|
||||
(u_longlong_t)tail_guid, (u_longlong_t)head_guid);
|
||||
}
|
||||
|
||||
nvlist_free(tail);
|
||||
}
|
||||
|
||||
static void
|
||||
vdev_validate_child(void *arg)
|
||||
{
|
||||
@@ -2530,7 +2571,27 @@ vdev_validate(vdev_t *vd)
|
||||
else
|
||||
txg = spa_last_synced_txg(spa);
|
||||
|
||||
if ((label = vdev_label_read_config(vd, txg)) == NULL) {
|
||||
/*
|
||||
* Labels 2 and 3 live at offsets relative to the end of the device, so
|
||||
* growing one moves them onto space this pool has never written: what
|
||||
* is found there belongs to whatever used the device before us, as
|
||||
* vdev_copy_uberblocks() already notes for the uberblock rings. Such
|
||||
* a leftover label is perfectly well formed and routinely carries a
|
||||
* higher txg than our own, which is all vdev_label_read_config() ranks
|
||||
* labels on, so it wins and the vdev is failed for belonging to a
|
||||
* foreign pool. Every label states the same identity, so read it from
|
||||
* the two whose position does not depend on the size of the device,
|
||||
* and fall back to the trailing pair only if those cannot be read.
|
||||
*/
|
||||
label = vdev_label_read_config(vd, txg, VDEV_LABELS_HEAD);
|
||||
if (label != NULL) {
|
||||
vdev_check_tail_labels(vd, label);
|
||||
} else {
|
||||
vd->vdev_tail_labels_foreign = B_FALSE;
|
||||
label = vdev_label_read_config(vd, txg, VDEV_LABELS_TAIL);
|
||||
}
|
||||
|
||||
if (label == NULL) {
|
||||
vdev_set_state(vd, B_FALSE, VDEV_STATE_CANT_OPEN,
|
||||
VDEV_AUX_BAD_LABEL);
|
||||
vdev_dbgmsg(vd, "vdev_validate: failed reading config for "
|
||||
@@ -4158,7 +4219,8 @@ vdev_validate_aux(vdev_t *vd)
|
||||
if (!vdev_readable(vd))
|
||||
return (0);
|
||||
|
||||
if ((label = vdev_label_read_config(vd, -1ULL)) == NULL) {
|
||||
if ((label = vdev_label_read_config(vd, -1ULL,
|
||||
VDEV_LABELS_ALL)) == NULL) {
|
||||
vdev_set_state(vd, B_TRUE, VDEV_STATE_CANT_OPEN,
|
||||
VDEV_AUX_CORRUPT_DATA);
|
||||
return (-1);
|
||||
|
||||
+45
-9
@@ -830,9 +830,13 @@ vdev_top_config_generate(spa_t *spa, nvlist_t *config)
|
||||
* the configuration from the first valid label we find. Otherwise,
|
||||
* find the most up-to-date label that does not exceed the specified
|
||||
* 'txg' value.
|
||||
*
|
||||
* Only the labels named by the 'labels' mask are consulted, so that a caller
|
||||
* which cannot vouch for the space some of them occupy is able to leave them
|
||||
* out. See VDEV_LABELS_HEAD and friends.
|
||||
*/
|
||||
nvlist_t *
|
||||
vdev_label_read_config(vdev_t *vd, uint64_t txg)
|
||||
vdev_label_read_config(vdev_t *vd, uint64_t txg, int labels)
|
||||
{
|
||||
spa_t *spa = vd->vdev_spa;
|
||||
nvlist_t *config = NULL;
|
||||
@@ -860,12 +864,21 @@ vdev_label_read_config(vdev_t *vd, uint64_t txg)
|
||||
return (vdev_draid_read_config_spare(vd));
|
||||
|
||||
for (int l = 0; l < VDEV_LABELS; l++) {
|
||||
if (!(labels & (1 << l))) {
|
||||
vp_abd[l] = NULL;
|
||||
vp[l] = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
vp_abd[l] = abd_alloc_linear(sizeof (vdev_phys_t), B_TRUE);
|
||||
vp[l] = abd_to_buf(vp_abd[l]);
|
||||
}
|
||||
|
||||
retry:
|
||||
for (int l = 0; l < VDEV_LABELS; l++) {
|
||||
if (!(labels & (1 << l)))
|
||||
continue;
|
||||
|
||||
zio[l] = zio_root(spa, NULL, NULL, flags);
|
||||
|
||||
vdev_label_read(zio[l], vd, l, vp_abd[l],
|
||||
@@ -875,6 +888,9 @@ retry:
|
||||
for (int l = 0; l < VDEV_LABELS; l++) {
|
||||
nvlist_t *label = NULL;
|
||||
|
||||
if (!(labels & (1 << l)))
|
||||
continue;
|
||||
|
||||
if (zio_wait(zio[l]) == 0 &&
|
||||
nvlist_unpack(vp[l]->vp_nvlist, sizeof (vp[l]->vp_nvlist),
|
||||
&label, 0) == 0) {
|
||||
@@ -890,7 +906,8 @@ retry:
|
||||
if ((error || label_txg == 0) && !config) {
|
||||
config = label;
|
||||
for (l++; l < VDEV_LABELS; l++)
|
||||
zio_wait(zio[l]);
|
||||
if (labels & (1 << l))
|
||||
zio_wait(zio[l]);
|
||||
break;
|
||||
} else if (label_txg <= txg && label_txg > best_txg) {
|
||||
best_txg = label_txg;
|
||||
@@ -920,7 +937,8 @@ retry:
|
||||
}
|
||||
|
||||
for (int l = 0; l < VDEV_LABELS; l++) {
|
||||
abd_free(vp_abd[l]);
|
||||
if (vp_abd[l] != NULL)
|
||||
abd_free(vp_abd[l]);
|
||||
}
|
||||
|
||||
return (config);
|
||||
@@ -947,7 +965,8 @@ vdev_inuse(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason,
|
||||
/*
|
||||
* Read the label, if any, and perform some basic sanity checks.
|
||||
*/
|
||||
if ((label = vdev_label_read_config(vd, -1ULL)) == NULL)
|
||||
if ((label = vdev_label_read_config(vd, -1ULL,
|
||||
VDEV_LABELS_ALL)) == NULL)
|
||||
return (B_FALSE);
|
||||
|
||||
(void) nvlist_lookup_uint64(label, ZPOOL_CONFIG_CREATE_TXG,
|
||||
@@ -1161,7 +1180,8 @@ vdev_label_init(vdev_t *vd, uint64_t crtxg, vdev_labeltype_t reason)
|
||||
fnvlist_add_string(nv,
|
||||
ZPOOL_CREATE_INFO_VDEV, vd->vdev_path);
|
||||
|
||||
cfg = vdev_label_read_config(vd, -1ULL);
|
||||
cfg = vdev_label_read_config(vd, -1ULL,
|
||||
VDEV_LABELS_ALL);
|
||||
if (cfg != NULL) {
|
||||
const char *pname;
|
||||
if (nvlist_lookup_string(cfg,
|
||||
@@ -1639,7 +1659,12 @@ vdev_uberblock_load_impl(zio_t *zio, vdev_t *vd, int flags,
|
||||
|
||||
if (vd->vdev_ops->vdev_op_leaf && vdev_readable(vd) &&
|
||||
vd->vdev_ops != &vdev_draid_spare_ops) {
|
||||
int labels = VDEV_TRUSTED_LABELS(vd);
|
||||
|
||||
for (int l = 0; l < VDEV_LABELS; l++) {
|
||||
if (!(labels & (1 << l)))
|
||||
continue;
|
||||
|
||||
for (int n = 0; n < VDEV_UBERBLOCK_COUNT(vd); n++) {
|
||||
vdev_label_read(zio, vd, l,
|
||||
abd_alloc_linear(VDEV_UBERBLOCK_SIZE(vd),
|
||||
@@ -1708,11 +1733,13 @@ vdev_uberblock_load(vdev_t *rvd, uberblock_t *ub, nvlist_t **config)
|
||||
return;
|
||||
}
|
||||
|
||||
*config = vdev_label_read_config(cb.ubl_vd, ub->ub_txg);
|
||||
*config = vdev_label_read_config(cb.ubl_vd, ub->ub_txg,
|
||||
VDEV_TRUSTED_LABELS(cb.ubl_vd));
|
||||
if (*config == NULL && spa->spa_extreme_rewind) {
|
||||
vdev_dbgmsg(cb.ubl_vd, "failed to read label config. "
|
||||
"Trying again without txg restrictions.");
|
||||
*config = vdev_label_read_config(cb.ubl_vd, UINT64_MAX);
|
||||
*config = vdev_label_read_config(cb.ubl_vd,
|
||||
UINT64_MAX, VDEV_TRUSTED_LABELS(cb.ubl_vd));
|
||||
}
|
||||
if (*config == NULL) {
|
||||
vdev_dbgmsg(cb.ubl_vd, "failed to read label config");
|
||||
@@ -1822,11 +1849,20 @@ vdev_uberblock_sync(zio_t *zio, uint64_t *good_writes,
|
||||
if (vd->vdev_ops == &vdev_draid_spare_ops)
|
||||
return;
|
||||
|
||||
/* If the vdev was expanded, need to copy uberblock rings. */
|
||||
/*
|
||||
* If the vdev was expanded, or its trailing labels were found to hold
|
||||
* an older pool's, the rings there have to be rebuilt in full: the
|
||||
* write below only lands in one slot of each, and every slot it does
|
||||
* not touch keeps whatever was there, which for a foreign ring is a
|
||||
* set of uberblocks that can outrank our own for as long as it takes
|
||||
* the txg to come round again. Only once the copy has been made are
|
||||
* the trailing labels ours to read from.
|
||||
*/
|
||||
if (vd->vdev_state == VDEV_STATE_HEALTHY &&
|
||||
vd->vdev_copy_uberblocks == B_TRUE) {
|
||||
(vd->vdev_copy_uberblocks || vd->vdev_tail_labels_foreign)) {
|
||||
vdev_copy_uberblocks(vd);
|
||||
vd->vdev_copy_uberblocks = B_FALSE;
|
||||
vd->vdev_tail_labels_foreign = B_FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -83,7 +83,7 @@ tags = ['functional', 'cli_root', 'zpool_add']
|
||||
[tests/functional/cli_root/zpool_expand:Linux]
|
||||
tests = ['zpool_expand_001_pos', 'zpool_expand_002_pos',
|
||||
'zpool_expand_003_neg', 'zpool_expand_004_pos', 'zpool_expand_005_pos',
|
||||
'zpool_expand_006_pos']
|
||||
'zpool_expand_006_pos', 'zpool_expand_007_pos']
|
||||
tags = ['functional', 'cli_root', 'zpool_expand']
|
||||
|
||||
[tests/functional/cli_root/zpool_import:Linux]
|
||||
|
||||
@@ -1200,6 +1200,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/cli_root/zpool_expand/zpool_expand_004_pos.ksh \
|
||||
functional/cli_root/zpool_expand/zpool_expand_005_pos.ksh \
|
||||
functional/cli_root/zpool_expand/zpool_expand_006_pos.ksh \
|
||||
functional/cli_root/zpool_expand/zpool_expand_007_pos.ksh \
|
||||
functional/cli_root/zpool_export/cleanup.ksh \
|
||||
functional/cli_root/zpool_export/setup.ksh \
|
||||
functional/cli_root/zpool_export/zpool_export_001_pos.ksh \
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# CDDL HEADER START
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# CDDL HEADER END
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2026 by Andrew Mochalskyi. All rights reserved.
|
||||
#
|
||||
|
||||
. $STF_SUITE/include/libtest.shlib
|
||||
|
||||
#
|
||||
# DESCRIPTION:
|
||||
# A vdev grown over the remains of an older pool keeps its own identity
|
||||
# (issue #16144).
|
||||
#
|
||||
# STRATEGY:
|
||||
# 1) Build a donor pool on a file of the grown size, drive its txg well past
|
||||
# anything the pool under test will reach, and keep the two labels that
|
||||
# live at the end of the file
|
||||
# 2) Create a mirror on two smaller files
|
||||
# 3) Grow one member and write the donor labels where labels 2 and 3 now fall
|
||||
# 4) "zpool online -e" that member, which must stay healthy, and must leave
|
||||
# only its own labels behind
|
||||
# 5) Plant the donor labels again and import from a cache file, the way the
|
||||
# pool comes back after a reboot
|
||||
# 6) Export and import once more with nothing planted, which is what catches
|
||||
# a donor uberblock ring left behind the pool's own config
|
||||
#
|
||||
# Labels 2 and 3 sit at offsets relative to the end of a device, so growing
|
||||
# one puts them on space the pool has never written. A label left there by
|
||||
# whoever had the device before is well formed and carries whatever txg it
|
||||
# was written with, which is what a config is chosen by, so a stale one used
|
||||
# to be picked and the vdev failed as belonging to a foreign pool. A mirror
|
||||
# is used so that a failed member leaves the pool readable and the test can
|
||||
# report rather than hang.
|
||||
#
|
||||
|
||||
verify_runnable "global"
|
||||
|
||||
typeset -r SMALLSZ=$((256 * 1024 * 1024))
|
||||
typeset -r BIGSZ=$((512 * 1024 * 1024))
|
||||
typeset -r TAILSZ=$((512 * 1024))
|
||||
typeset -r TAILAT=$(((BIGSZ - TAILSZ) / TAILSZ))
|
||||
typeset -r VDEV_A=$TEST_BASE_DIR/expand-stale-a
|
||||
typeset -r VDEV_B=$TEST_BASE_DIR/expand-stale-b
|
||||
typeset -r DONOR=$TEST_BASE_DIR/expand-stale-tail
|
||||
typeset -r CACHE=$TEST_BASE_DIR/expand-stale.cache
|
||||
|
||||
function cleanup
|
||||
{
|
||||
poolexists $TESTPOOL1 && destroy_pool $TESTPOOL1
|
||||
poolexists $TESTPOOL2 && destroy_pool $TESTPOOL2
|
||||
log_must restore_tunable TXG_TIMEOUT
|
||||
rm -f $VDEV_A $VDEV_B $DONOR $CACHE $CACHE.boot $CACHE.boot2
|
||||
}
|
||||
|
||||
# The txg a config is chosen by.
|
||||
function label_txg # file
|
||||
{
|
||||
zdb -l $1 | awk '$1 == "txg:" { print $2; exit }'
|
||||
}
|
||||
|
||||
function plant_donor_labels
|
||||
{
|
||||
log_must dd if=$DONOR of=$VDEV_A bs=$TAILSZ seek=$TAILAT count=1 \
|
||||
conv=notrunc
|
||||
}
|
||||
|
||||
log_must save_tunable TXG_TIMEOUT
|
||||
log_onexit cleanup
|
||||
|
||||
log_assert "a vdev grown over an older pool's labels keeps its own identity"
|
||||
|
||||
# The donor's labels only matter while they look newer than the pool's own, and
|
||||
# an idle pool moves a txg on every zfs_txg_timeout. Stop that clock, so the
|
||||
# lead built below is not quietly eaten by however long the run takes and the
|
||||
# test cannot pass for want of a repro.
|
||||
log_must set_tunable32 TXG_TIMEOUT 5000
|
||||
|
||||
# A pool that used to own the space the member is about to grow into.
|
||||
log_must truncate -s $BIGSZ $VDEV_A
|
||||
log_must zpool create $TESTPOOL2 $VDEV_A
|
||||
typeset -i i=0
|
||||
while ((i < 25)); do
|
||||
log_must zpool sync $TESTPOOL2
|
||||
((i = i + 1))
|
||||
done
|
||||
# The labels only carry the pool's final txg once it is exported.
|
||||
log_must zpool export $TESTPOOL2
|
||||
typeset -i donortxg=$(label_txg $VDEV_A)
|
||||
log_must dd if=$VDEV_A of=$DONOR bs=$TAILSZ skip=$TAILAT count=1
|
||||
log_must rm -f $VDEV_A
|
||||
|
||||
# The pool under test, on smaller members.
|
||||
log_must truncate -s $SMALLSZ $VDEV_A
|
||||
log_must truncate -s $SMALLSZ $VDEV_B
|
||||
log_must zpool create -o cachefile=$CACHE $TESTPOOL1 mirror $VDEV_A $VDEV_B
|
||||
typeset -i pooltxg=$(label_txg $VDEV_A)
|
||||
|
||||
# The donor labels are only interesting while they look newer than our own.
|
||||
if ((donortxg <= pooltxg)); then
|
||||
log_fail "donor txg $donortxg does not exceed pool txg $pooltxg"
|
||||
fi
|
||||
log_note "donor txg $donortxg, pool txg $pooltxg"
|
||||
|
||||
# Grow one member onto the donor labels and take the new space.
|
||||
log_must truncate -s $BIGSZ $VDEV_A
|
||||
plant_donor_labels
|
||||
log_must zpool online -e $TESTPOOL1 $VDEV_A
|
||||
|
||||
log_must check_state $TESTPOOL1 $VDEV_A "ONLINE"
|
||||
log_must check_state $TESTPOOL1 "" "ONLINE"
|
||||
|
||||
# All four labels are the pool's own again. zdb prints one block per distinct
|
||||
# label, tagged with the labels sharing it, so "labels = 0 1 2 3" says both
|
||||
# that the donor's are gone and that zdb found labels 2 and 3 at the offsets of
|
||||
# the grown device -- which is the only proof in this test that the member did
|
||||
# expand. The pool's size cannot show it: the mirror is still held to the
|
||||
# smaller member.
|
||||
log_must zpool sync $TESTPOOL1
|
||||
log_must eval "zdb -l $VDEV_A | grep -q 'labels = 0 1 2 3'"
|
||||
|
||||
# And the same for the import a reboot would do.
|
||||
log_must cp $CACHE $CACHE.boot
|
||||
log_must zpool export $TESTPOOL1
|
||||
plant_donor_labels
|
||||
log_must zpool import -c $CACHE.boot -o cachefile=$CACHE.boot $TESTPOOL1
|
||||
|
||||
log_must check_state $TESTPOOL1 $VDEV_A "ONLINE"
|
||||
log_must check_state $TESTPOOL1 "" "ONLINE"
|
||||
|
||||
# Once more round, with nothing planted this time. The pool has written its
|
||||
# own config over the trailing labels by now, so the mismatch that guarded the
|
||||
# first two imports is gone; if the donor's uberblock rings were left behind
|
||||
# them the pool would pick one of those and fail to open its rootbp.
|
||||
log_must zpool sync $TESTPOOL1
|
||||
log_must cp $CACHE.boot $CACHE.boot2
|
||||
log_must zpool export $TESTPOOL1
|
||||
log_must zpool import -c $CACHE.boot2 -o cachefile=$CACHE.boot2 $TESTPOOL1
|
||||
|
||||
log_must check_state $TESTPOOL1 $VDEV_A "ONLINE"
|
||||
log_must check_state $TESTPOOL1 "" "ONLINE"
|
||||
log_must eval "zdb -l $VDEV_A | grep -q 'labels = 0 1 2 3'"
|
||||
|
||||
log_pass "a vdev grown over an older pool's labels keeps its own identity"
|
||||
Reference in new issue
Block a user