Skip to content

Commit

Permalink
OpenZFS 7614 - zfs device evacuation/removal
Browse files Browse the repository at this point in the history
Authored by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Alex Reece <alex@delphix.com>
Reviewed-by: George Wilson <george.wilson@delphix.com>
Reviewed-by: John Kennedy <john.kennedy@delphix.com>
Reviewed-by: Prakash Surya <prakash.surya@delphix.com>
Ported-by: Tim Chase <tim@chase2k.com>
Signed-off-by: Tim Chase <tim@chase2k.com>

OpenZFS-issue: https://www.illumos.org/issues/7614
OpenZFS-commit: openzfs/openzfs@XXXXXXXXXX

This project allows top-level vdevs to be removed from the storage pool
with "zpool remove", reducing the total amount of storage in the pool.
This operation copies all allocated regions of the device to be removed
onto other devices, recording the mapping from old to new location.
After the removal is complete, read and free operations to the removed
(now "indirect") vdev must be remapped and performed at the new location
on disk.  The indirect mapping table is kept in memory whenever the pool
is loaded, so there is minimal performance overhead when doing
operations on the indirect vdev.

The size of the in-memory mapping table will be reduced when its entries
become "obsolete" because they are no longer used by any block pointers
in the pool.  An entry becomes obsolete when all the blocks that use it
are freed.  An entry can also become obsolete when all the snapshots
that reference it are deleted, and the block pointers that reference it
have been "remapped" in all filesystems/zvols (and clones).  Whenever an
indirect block is written, all the block pointers in it will be
"remapped" to their new (concrete) locations if possible.  This process
can be accelerated by using the "zfs remap" command to proactively
rewrite all indirect blocks that reference indirect (removed) vdevs.

Note that when a device is removed, we do not verify the checksum of the
data that is copied.  This makes the process much faster, but if it were
used on redundant vdevs (i.e. mirror or raidz vdevs), it would be
possible to copy the wrong data, when we have the correct data on e.g.
the other side of the mirror.  Therefore, mirror and raidz devices can
not be removed.
  • Loading branch information
ahrens authored and dweeezil committed Nov 27, 2017
1 parent da5d469 commit 3d88404
Show file tree
Hide file tree
Showing 120 changed files with 9,336 additions and 899 deletions.
694 changes: 632 additions & 62 deletions cmd/zdb/zdb.c

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ static int zfs_do_holds(int argc, char **argv);
static int zfs_do_release(int argc, char **argv);
static int zfs_do_diff(int argc, char **argv);
static int zfs_do_bookmark(int argc, char **argv);
static int zfs_do_remap(int argc, char **argv);
static int zfs_do_load_key(int argc, char **argv);
static int zfs_do_unload_key(int argc, char **argv);
static int zfs_do_change_key(int argc, char **argv);
Expand Down Expand Up @@ -155,6 +156,7 @@ typedef enum {
HELP_HOLDS,
HELP_RELEASE,
HELP_DIFF,
HELP_REMAP,
HELP_BOOKMARK,
HELP_LOAD_KEY,
HELP_UNLOAD_KEY,
Expand Down Expand Up @@ -212,6 +214,7 @@ static zfs_command_t command_table[] = {
{ "holds", zfs_do_holds, HELP_HOLDS },
{ "release", zfs_do_release, HELP_RELEASE },
{ "diff", zfs_do_diff, HELP_DIFF },
{ "remap", zfs_do_remap, HELP_REMAP },
{ "load-key", zfs_do_load_key, HELP_LOAD_KEY },
{ "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY },
{ "change-key", zfs_do_change_key, HELP_CHANGE_KEY },
Expand Down Expand Up @@ -333,6 +336,8 @@ get_usage(zfs_help_t idx)
case HELP_DIFF:
return (gettext("\tdiff [-FHt] <snapshot> "
"[snapshot|filesystem]\n"));
case HELP_REMAP:
return (gettext("\tremap <filesystem | volume>\n"));
case HELP_BOOKMARK:
return (gettext("\tbookmark <snapshot> <bookmark>\n"));
case HELP_LOAD_KEY:
Expand Down Expand Up @@ -4253,6 +4258,7 @@ zfs_do_receive(int argc, char **argv)
#define ZFS_DELEG_PERM_RELEASE "release"
#define ZFS_DELEG_PERM_DIFF "diff"
#define ZFS_DELEG_PERM_BOOKMARK "bookmark"
#define ZFS_DELEG_PERM_REMAP "remap"
#define ZFS_DELEG_PERM_LOAD_KEY "load-key"
#define ZFS_DELEG_PERM_CHANGE_KEY "change-key"

Expand All @@ -4275,6 +4281,7 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
{ ZFS_DELEG_PERM_SHARE, ZFS_DELEG_NOTE_SHARE },
{ ZFS_DELEG_PERM_SNAPSHOT, ZFS_DELEG_NOTE_SNAPSHOT },
{ ZFS_DELEG_PERM_BOOKMARK, ZFS_DELEG_NOTE_BOOKMARK },
{ ZFS_DELEG_PERM_REMAP, ZFS_DELEG_NOTE_REMAP },
{ ZFS_DELEG_PERM_LOAD_KEY, ZFS_DELEG_NOTE_LOAD_KEY },
{ ZFS_DELEG_PERM_CHANGE_KEY, ZFS_DELEG_NOTE_CHANGE_KEY },

Expand Down Expand Up @@ -6903,7 +6910,7 @@ zfs_do_diff(int argc, char **argv)

if (argc < 1) {
(void) fprintf(stderr,
gettext("must provide at least one snapshot name\n"));
gettext("must provide at least one snapshot name\n"));
usage(B_FALSE);
}

Expand Down Expand Up @@ -6945,6 +6952,22 @@ zfs_do_diff(int argc, char **argv)
return (err != 0);
}

static int
zfs_do_remap(int argc, char **argv)
{
const char *fsname;
int err = 0;
if (argc != 2) {
(void) fprintf(stderr, gettext("wrong number of arguments\n"));
usage(B_FALSE);
}

fsname = argv[1];
err = zfs_remap_indirects(g_zfs, fsname);

return (err);
}

/*
* zfs bookmark <fs@snap> <fs#bmark>
*
Expand Down
171 changes: 158 additions & 13 deletions cmd/zpool/zpool_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -780,37 +780,61 @@ zpool_do_add(int argc, char **argv)
/*
* zpool remove <pool> <vdev> ...
*
* Removes the given vdev from the pool. Currently, this supports removing
* spares, cache, and log devices from the pool.
* Removes the given vdev from the pool.
*/
int
zpool_do_remove(int argc, char **argv)
{
char *poolname;
int i, ret = 0;
zpool_handle_t *zhp = NULL;
boolean_t stop = B_FALSE;
char c;

argc--;
argv++;
/* check options */
while ((c = getopt(argc, argv, "s")) != -1) {
switch (c) {
case 's':
stop = B_TRUE;
break;
case '?':
(void) fprintf(stderr, gettext("invalid option '%c'\n"),
optopt);
usage(B_FALSE);
}
}

argc -= optind;
argv += optind;

/* get pool name and check number of arguments */
if (argc < 1) {
(void) fprintf(stderr, gettext("missing pool name argument\n"));
usage(B_FALSE);
}
if (argc < 2) {
(void) fprintf(stderr, gettext("missing device\n"));
usage(B_FALSE);
}

poolname = argv[0];

if ((zhp = zpool_open(g_zfs, poolname)) == NULL)
return (1);

for (i = 1; i < argc; i++) {
if (zpool_vdev_remove(zhp, argv[i]) != 0)
if (stop) {
if (argc > 1) {
(void) fprintf(stderr, gettext("too many arguments\n"));
usage(B_FALSE);
}
if (zpool_vdev_remove_cancel(zhp) != 0)
ret = 1;
} else {
if (argc < 2) {
(void) fprintf(stderr, gettext("missing device\n"));
usage(B_FALSE);
}

for (i = 1; i < argc; i++) {
if (zpool_vdev_remove(zhp, argv[i]) != 0)
ret = 1;
}
}
zpool_close(zhp);

Expand Down Expand Up @@ -1653,6 +1677,7 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
uint64_t notpresent;
spare_cbdata_t spare_cb;
char *state;
char *type;
char *path = NULL;

if (nvlist_lookup_nvlist_array(nv, ZPOOL_CONFIG_CHILDREN,
Expand All @@ -1662,6 +1687,11 @@ print_status_config(zpool_handle_t *zhp, status_cbdata_t *cb, const char *name,
verify(nvlist_lookup_uint64_array(nv, ZPOOL_CONFIG_VDEV_STATS,
(uint64_t **)&vs, &c) == 0);

verify(nvlist_lookup_string(nv, ZPOOL_CONFIG_TYPE, &type) == 0);

if (strcmp(type, VDEV_TYPE_INDIRECT) == 0)
return;

state = zpool_state_to_name(vs->vs_state, vs->vs_aux);
if (isspare) {
/*
Expand Down Expand Up @@ -3659,6 +3689,9 @@ print_vdev_stats(zpool_handle_t *zhp, const char *name, nvlist_t *oldnv,

calcvs = safe_malloc(sizeof (*calcvs));

if (strcmp(name, VDEV_TYPE_INDIRECT) == 0)
return (ret);

if (oldnv != NULL) {
verify(nvlist_lookup_uint64_array(oldnv,
ZPOOL_CONFIG_VDEV_STATS, (uint64_t **)&oldvs, &c) == 0);
Expand Down Expand Up @@ -4947,6 +4980,9 @@ print_list_stats(zpool_handle_t *zhp, const char *name, nvlist_t *nv,
else
format = ZFS_NICENUM_1024;

if (strcmp(name, VDEV_TYPE_INDIRECT) == 0)
return;

if (scripted)
(void) printf("\t%s", name);
else if (strlen(name) + depth > cb->cb_namewidth)
Expand Down Expand Up @@ -5965,7 +6001,7 @@ zpool_do_scrub(int argc, char **argv)
/*
* Print out detailed scrub status.
*/
void
static void
print_scan_status(pool_scan_stat_t *ps)
{
time_t start, end, pause;
Expand Down Expand Up @@ -6112,6 +6148,111 @@ print_scan_status(pool_scan_stat_t *ps)
}
}

/*
* Print out detailed removal status.
*/
static void
print_removal_status(zpool_handle_t *zhp, pool_removal_stat_t *prs)
{
char copied_buf[7], examined_buf[7], total_buf[7], rate_buf[7];
time_t start, end;
nvlist_t *config, *nvroot;
nvlist_t **child;
uint_t children;
char *vdev_name;

if (prs == NULL || prs->prs_state == DSS_NONE)
return;

/*
* Determine name of vdev.
*/
config = zpool_get_config(zhp, NULL);
nvroot = fnvlist_lookup_nvlist(config,
ZPOOL_CONFIG_VDEV_TREE);
verify(nvlist_lookup_nvlist_array(nvroot, ZPOOL_CONFIG_CHILDREN,
&child, &children) == 0);
assert(prs->prs_removing_vdev < children);
vdev_name = zpool_vdev_name(g_zfs, zhp,
child[prs->prs_removing_vdev], B_TRUE);

(void) printf(gettext("remove: "));

start = prs->prs_start_time;
end = prs->prs_end_time;
zfs_nicenum(prs->prs_copied, copied_buf, sizeof (copied_buf));

/*
* Removal is finished or canceled.
*/
if (prs->prs_state == DSS_FINISHED) {
uint64_t minutes_taken = (end - start) / 60;

(void) printf(gettext("Removal of vdev %llu copied %s "
"in %lluh%um, completed on %s"),
(longlong_t)prs->prs_removing_vdev,
copied_buf,
(u_longlong_t)(minutes_taken / 60),
(uint_t)(minutes_taken % 60),
ctime((time_t *)&end));
} else if (prs->prs_state == DSS_CANCELED) {
(void) printf(gettext("Removal of %s canceled on %s"),
vdev_name, ctime(&end));
} else {
uint64_t copied, total, elapsed, mins_left, hours_left;
double fraction_done;
uint_t rate;

assert(prs->prs_state == DSS_SCANNING);

/*
* Removal is in progress.
*/
(void) printf(gettext(
"Evacuation of %s in progress since %s"),
vdev_name, ctime(&start));

copied = prs->prs_copied > 0 ? prs->prs_copied : 1;
total = prs->prs_to_copy;
fraction_done = (double)copied / total;

/* elapsed time for this pass */
elapsed = time(NULL) - prs->prs_start_time;
elapsed = elapsed > 0 ? elapsed : 1;
rate = copied / elapsed;
rate = rate > 0 ? rate : 1;
mins_left = ((total - copied) / rate) / 60;
hours_left = mins_left / 60;

zfs_nicenum(copied, examined_buf, sizeof (examined_buf));
zfs_nicenum(total, total_buf, sizeof (total_buf));
zfs_nicenum(rate, rate_buf, sizeof (rate_buf));

/*
* do not print estimated time if hours_left is more than
* 30 days
*/
(void) printf(gettext(" %s copied out of %s at %s/s, "
"%.2f%% done"),
examined_buf, total_buf, rate_buf, 100 * fraction_done);
if (hours_left < (30 * 24)) {
(void) printf(gettext(", %lluh%um to go\n"),
(u_longlong_t)hours_left, (uint_t)(mins_left % 60));
} else {
(void) printf(gettext(
", (copy is slow, no estimated time)\n"));
}
}

if (prs->prs_mapping_memory > 0) {
char mem_buf[7];
zfs_nicenum(prs->prs_mapping_memory, mem_buf, sizeof (mem_buf));
(void) printf(gettext(" %s memory used for "
"removed device mappings\n"),
mem_buf);
}
}

static void
print_error_log(zpool_handle_t *zhp)
{
Expand Down Expand Up @@ -6277,8 +6418,7 @@ status_callback(zpool_handle_t *zhp, void *data)
else
(void) printf("\n");

verify(nvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE,
&nvroot) == 0);
nvroot = fnvlist_lookup_nvlist(config, ZPOOL_CONFIG_VDEV_TREE);
verify(nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS,
(uint64_t **)&vs, &c) == 0);
health = zpool_state_to_name(vs->vs_state, vs->vs_aux);
Expand Down Expand Up @@ -6518,11 +6658,16 @@ status_callback(zpool_handle_t *zhp, void *data)
nvlist_t **spares, **l2cache;
uint_t nspares, nl2cache;
pool_scan_stat_t *ps = NULL;
pool_removal_stat_t *prs = NULL;

(void) nvlist_lookup_uint64_array(nvroot,
ZPOOL_CONFIG_SCAN_STATS, (uint64_t **)&ps, &c);
print_scan_status(ps);

(void) nvlist_lookup_uint64_array(nvroot,
ZPOOL_CONFIG_REMOVAL_STATS, (uint64_t **)&prs, &c);
print_removal_status(zhp, prs);

cbp->cb_namewidth = max_width(zhp, nvroot, 0, 0,
cbp->cb_name_flags | VDEV_NAME_TYPE_ID);
if (cbp->cb_namewidth < 10)
Expand Down
Loading

0 comments on commit 3d88404

Please sign in to comment.