Allow zfs to send replication streams with missing snapshots

A tentative implementation and discussion was done in #5285.
According to it a send --skip-missing|-s flag has been added.
In a replication stream, when there are snapshots missing in
the hierarchy, if -s is provided print a warning and ignore
dataset (and its children) instead of throwing an error

Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Pablo Correa Gómez <ablocorrea@hotmail.com>
Closes #11710
This commit is contained in:
pablofsf 2021-04-11 21:05:35 +02:00 committed by GitHub
parent 2ec0b0dd71
commit 099fa7e475
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 126 additions and 18 deletions

View File

@ -4376,6 +4376,7 @@ zfs_do_send(int argc, char **argv)
struct option long_options[] = {
{"replicate", no_argument, NULL, 'R'},
{"skip-missing", no_argument, NULL, 's'},
{"redact", required_argument, NULL, 'd'},
{"props", no_argument, NULL, 'p'},
{"parsable", no_argument, NULL, 'P'},
@ -4394,7 +4395,7 @@ zfs_do_send(int argc, char **argv)
};
/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
while ((c = getopt_long(argc, argv, ":i:I:RsDpvnPLeht:cwbd:S",
long_options, NULL)) != -1) {
switch (c) {
case 'i':
@ -4411,6 +4412,9 @@ zfs_do_send(int argc, char **argv)
case 'R':
flags.replicate = B_TRUE;
break;
case 's':
flags.skipmissing = B_TRUE;
break;
case 'd':
redactbook = optarg;
break;
@ -4575,6 +4579,13 @@ zfs_do_send(int argc, char **argv)
resume_token));
}
if (flags.skipmissing && !flags.replicate) {
(void) fprintf(stderr,
gettext("skip-missing flag can only be used in "
"conjunction with replicate\n"));
usage(B_FALSE);
}
/*
* For everything except -R and -I, use the new, cleaner code path.
*/

View File

@ -666,6 +666,9 @@ typedef struct sendflags {
/* recursive send (ie, -R) */
boolean_t replicate;
/* for recursive send, skip sending missing snapshots */
boolean_t skipmissing;
/* for incrementals, do all intermediate snapshots */
boolean_t doall;

View File

@ -247,6 +247,7 @@ typedef struct send_data {
boolean_t raw;
boolean_t doall;
boolean_t replicate;
boolean_t skipmissing;
boolean_t verbose;
boolean_t backup;
boolean_t seenfrom;
@ -497,7 +498,8 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
* - skip sending the current dataset if it was created later than
* the parent tosnap
* - return error if the current dataset was created earlier than
* the parent tosnap
* the parent tosnap, unless --skip-missing specified. Then
* just print a warning
*/
if (sd->tosnap != NULL && tosnap_txg == 0) {
if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
@ -506,6 +508,11 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
"skipping dataset %s: snapshot %s does "
"not exist\n"), zhp->zfs_name, sd->tosnap);
}
} else if (sd->skipmissing) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"WARNING: skipping dataset %s and its children:"
" snapshot %s does not exist\n"),
zhp->zfs_name, sd->tosnap);
} else {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"cannot send %s@%s%s: snapshot %s@%s does not "
@ -649,8 +656,9 @@ out:
static int
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
boolean_t replicate, boolean_t verbose, boolean_t backup, boolean_t holds,
boolean_t props, nvlist_t **nvlp, avl_tree_t **avlp)
boolean_t replicate, boolean_t skipmissing, boolean_t verbose,
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
avl_tree_t **avlp)
{
zfs_handle_t *zhp;
send_data_t sd = { 0 };
@ -668,6 +676,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.raw = raw;
sd.doall = doall;
sd.replicate = replicate;
sd.skipmissing = skipmissing;
sd.verbose = verbose;
sd.backup = backup;
sd.holds = holds;
@ -1975,8 +1984,8 @@ send_conclusion_record(int fd, zio_cksum_t *zc)
static int
send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t backup,
boolean_t holds, boolean_t props, boolean_t doall,
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing,
boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall,
nvlist_t **fssp, avl_tree_t **fsavlp)
{
int err = 0;
@ -2022,8 +2031,8 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
}
if ((err = gather_nvlist(zhp->zfs_hdl, tofs,
from, tosnap, recursive, raw, doall, replicate, verbose,
backup, holds, props, &fss, fsavlp)) != 0) {
from, tosnap, recursive, raw, doall, replicate, skipmissing,
verbose, backup, holds, props, &fss, fsavlp)) != 0) {
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
errbuf));
}
@ -2160,8 +2169,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
err = send_prelim_records(tosnap, fromsnap, outfd,
flags->replicate || flags->props || flags->holds,
flags->replicate, flags->verbosity > 0, flags->dryrun,
flags->raw, flags->replicate, flags->backup, flags->holds,
flags->props, flags->doall, &fss, &fsavl);
flags->raw, flags->replicate, flags->skipmissing,
flags->backup, flags->holds, flags->props, flags->doall,
&fss, &fsavl);
zfs_close(tosnap);
if (err != 0)
goto err_out;
@ -2464,7 +2474,7 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
*/
err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
flags->verbosity > 0, flags->dryrun, flags->raw,
flags->replicate, flags->backup, flags->holds,
flags->replicate, B_FALSE, flags->backup, flags->holds,
flags->props, flags->doall, NULL, NULL);
if (err != 0)
return (err);
@ -3236,7 +3246,7 @@ again:
deleted = fnvlist_alloc();
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
return (error);
@ -4729,8 +4739,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*/
*cp = '\0';
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_TRUE,
&local_nv, &local_avl) == 0) {
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE,
B_TRUE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
fsavl_destroy(local_avl);

View File

@ -39,12 +39,12 @@
.Sh SYNOPSIS
.Nm zfs
.Cm send
.Op Fl DLPRbcehnpvw
.Op Fl DLPRsbcehnpvw
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
.Nm zfs
.Cm send
.Op Fl DLPRcenpvw
.Op Fl DLPRscenpvw
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
.Nm zfs
@ -139,6 +139,12 @@ do not exist on the sending side are destroyed. If the
flag is used to send encrypted datasets, then
.Fl w
must also be specified.
.It Fl s, -skip-missing
Allows sending a replication stream even when there are snapshots missing in the
hierarchy. When a snapshot is missing, instead of throwing an error and aborting
the send, a warning is printed to STDERR and the dataset to which it belongs
and its descendents are skipped. This flag can only be used in conjunction with
.Fl R .
.It Fl e, -embed
Generate a more compact stream by using
.Sy WRITE_EMBEDDED

View File

@ -258,7 +258,7 @@ tags = ['functional', 'cli_root', 'zfs_rollback']
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw',
'zfs_send_sparse', 'zfs_send-b']
'zfs_send_sparse', 'zfs_send-b', 'zfs_send_skip_missing']
tags = ['functional', 'cli_root', 'zfs_send']
[tests/functional/cli_root/zfs_set]

View File

@ -13,7 +13,8 @@ dist_pkgdata_SCRIPTS = \
zfs_send_encrypted_unloaded.ksh \
zfs_send_raw.ksh \
zfs_send_sparse.ksh \
zfs_send-b.ksh
zfs_send-b.ksh \
zfs_send_skip_missing.ksh
dist_pkgdata_DATA = \
zfs_send.cfg

View File

@ -0,0 +1,77 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright (c) 2016, loli10K. All rights reserved.
# Copyright (c) 2021, Pablo Correa Gómez. All rights reserved.
#
. $STF_SUITE/tests/functional/cli_root/cli_common.kshlib
. $STF_SUITE/tests/functional/cli_root/zfs_send/zfs_send.cfg
#
# DESCRIPTION:
# Verify 'zfs send' will avoid sending replication send
# streams when we're missing snapshots in the dataset
# hierarchy, unless -s|--skip-missing provided
#
# STRATEGY:
# 1. Create a parent and child fs and then only snapshot the parent
# 2. Verify sending with replication will fail
# 3. Verify sending with skip-missing will print a warning but succeed
#
verify_runnable "both"
function cleanup
{
snapexists $SNAP && log_must zfs destroy -f $SNAP
datasetexists $PARENT && log_must zfs destroy -rf $PARENT
[[ -e $WARNF ]] && log_must rm -f $WARNF
rm -f $TEST_BASE_DIR/devnull
}
log_assert "Verify 'zfs send -Rs' works as expected."
log_onexit cleanup
PARENT=$TESTPOOL/parent
CHILD=$PARENT/child
SNAP=$PARENT@snap
WARNF=$TEST_BASE_DIR/warn.2
log_note "Verify 'zfs send -R' fails to generate replication stream"\
" for datasets created before"
log_must zfs create $PARENT
log_must zfs create $CHILD
log_must zfs snapshot $SNAP
log_mustnot eval "zfs send -R $SNAP >$TEST_BASE_DIR/devnull"
log_note "Verify 'zfs send -Rs' warns about missing snapshots, "\
"but still succeeds"
log_must eval "zfs send -Rs $SNAP 2> $WARNF >$TEST_BASE_DIR/devnull"
log_must eval "[[ -s $WARNF ]]"
log_pass "Verify 'zfs send -Rs' works as expected."