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:
parent
f8631d0fe0
commit
07d64c07e0
|
@ -4376,6 +4376,7 @@ zfs_do_send(int argc, char **argv)
|
||||||
|
|
||||||
struct option long_options[] = {
|
struct option long_options[] = {
|
||||||
{"replicate", no_argument, NULL, 'R'},
|
{"replicate", no_argument, NULL, 'R'},
|
||||||
|
{"skip-missing", no_argument, NULL, 's'},
|
||||||
{"redact", required_argument, NULL, 'd'},
|
{"redact", required_argument, NULL, 'd'},
|
||||||
{"props", no_argument, NULL, 'p'},
|
{"props", no_argument, NULL, 'p'},
|
||||||
{"parsable", no_argument, NULL, 'P'},
|
{"parsable", no_argument, NULL, 'P'},
|
||||||
|
@ -4394,7 +4395,7 @@ zfs_do_send(int argc, char **argv)
|
||||||
};
|
};
|
||||||
|
|
||||||
/* check options */
|
/* 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) {
|
long_options, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'i':
|
case 'i':
|
||||||
|
@ -4411,6 +4412,9 @@ zfs_do_send(int argc, char **argv)
|
||||||
case 'R':
|
case 'R':
|
||||||
flags.replicate = B_TRUE;
|
flags.replicate = B_TRUE;
|
||||||
break;
|
break;
|
||||||
|
case 's':
|
||||||
|
flags.skipmissing = B_TRUE;
|
||||||
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
redactbook = optarg;
|
redactbook = optarg;
|
||||||
break;
|
break;
|
||||||
|
@ -4575,6 +4579,13 @@ zfs_do_send(int argc, char **argv)
|
||||||
resume_token));
|
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.
|
* For everything except -R and -I, use the new, cleaner code path.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -666,6 +666,9 @@ typedef struct sendflags {
|
||||||
/* recursive send (ie, -R) */
|
/* recursive send (ie, -R) */
|
||||||
boolean_t replicate;
|
boolean_t replicate;
|
||||||
|
|
||||||
|
/* for recursive send, skip sending missing snapshots */
|
||||||
|
boolean_t skipmissing;
|
||||||
|
|
||||||
/* for incrementals, do all intermediate snapshots */
|
/* for incrementals, do all intermediate snapshots */
|
||||||
boolean_t doall;
|
boolean_t doall;
|
||||||
|
|
||||||
|
|
|
@ -247,6 +247,7 @@ typedef struct send_data {
|
||||||
boolean_t raw;
|
boolean_t raw;
|
||||||
boolean_t doall;
|
boolean_t doall;
|
||||||
boolean_t replicate;
|
boolean_t replicate;
|
||||||
|
boolean_t skipmissing;
|
||||||
boolean_t verbose;
|
boolean_t verbose;
|
||||||
boolean_t backup;
|
boolean_t backup;
|
||||||
boolean_t seenfrom;
|
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
|
* - skip sending the current dataset if it was created later than
|
||||||
* the parent tosnap
|
* the parent tosnap
|
||||||
* - return error if the current dataset was created earlier than
|
* - 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 != NULL && tosnap_txg == 0) {
|
||||||
if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
|
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 "
|
"skipping dataset %s: snapshot %s does "
|
||||||
"not exist\n"), zhp->zfs_name, sd->tosnap);
|
"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 {
|
} else {
|
||||||
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
|
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
|
||||||
"cannot send %s@%s%s: snapshot %s@%s does not "
|
"cannot send %s@%s%s: snapshot %s@%s does not "
|
||||||
|
@ -649,8 +656,9 @@ out:
|
||||||
static int
|
static int
|
||||||
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
|
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
|
||||||
const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
|
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 replicate, boolean_t skipmissing, boolean_t verbose,
|
||||||
boolean_t props, nvlist_t **nvlp, avl_tree_t **avlp)
|
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
|
||||||
|
avl_tree_t **avlp)
|
||||||
{
|
{
|
||||||
zfs_handle_t *zhp;
|
zfs_handle_t *zhp;
|
||||||
send_data_t sd = { 0 };
|
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.raw = raw;
|
||||||
sd.doall = doall;
|
sd.doall = doall;
|
||||||
sd.replicate = replicate;
|
sd.replicate = replicate;
|
||||||
|
sd.skipmissing = skipmissing;
|
||||||
sd.verbose = verbose;
|
sd.verbose = verbose;
|
||||||
sd.backup = backup;
|
sd.backup = backup;
|
||||||
sd.holds = holds;
|
sd.holds = holds;
|
||||||
|
@ -1975,8 +1984,8 @@ send_conclusion_record(int fd, zio_cksum_t *zc)
|
||||||
static int
|
static int
|
||||||
send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
|
send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
|
||||||
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
|
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
|
||||||
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t backup,
|
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing,
|
||||||
boolean_t holds, boolean_t props, boolean_t doall,
|
boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall,
|
||||||
nvlist_t **fssp, avl_tree_t **fsavlp)
|
nvlist_t **fssp, avl_tree_t **fsavlp)
|
||||||
{
|
{
|
||||||
int err = 0;
|
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,
|
if ((err = gather_nvlist(zhp->zfs_hdl, tofs,
|
||||||
from, tosnap, recursive, raw, doall, replicate, verbose,
|
from, tosnap, recursive, raw, doall, replicate, skipmissing,
|
||||||
backup, holds, props, &fss, fsavlp)) != 0) {
|
verbose, backup, holds, props, &fss, fsavlp)) != 0) {
|
||||||
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
|
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
|
||||||
errbuf));
|
errbuf));
|
||||||
}
|
}
|
||||||
|
@ -2160,8 +2169,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
|
||||||
err = send_prelim_records(tosnap, fromsnap, outfd,
|
err = send_prelim_records(tosnap, fromsnap, outfd,
|
||||||
flags->replicate || flags->props || flags->holds,
|
flags->replicate || flags->props || flags->holds,
|
||||||
flags->replicate, flags->verbosity > 0, flags->dryrun,
|
flags->replicate, flags->verbosity > 0, flags->dryrun,
|
||||||
flags->raw, flags->replicate, flags->backup, flags->holds,
|
flags->raw, flags->replicate, flags->skipmissing,
|
||||||
flags->props, flags->doall, &fss, &fsavl);
|
flags->backup, flags->holds, flags->props, flags->doall,
|
||||||
|
&fss, &fsavl);
|
||||||
zfs_close(tosnap);
|
zfs_close(tosnap);
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
goto err_out;
|
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,
|
err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
|
||||||
flags->verbosity > 0, flags->dryrun, flags->raw,
|
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);
|
flags->props, flags->doall, NULL, NULL);
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
return (err);
|
return (err);
|
||||||
|
@ -3236,7 +3246,7 @@ again:
|
||||||
deleted = fnvlist_alloc();
|
deleted = fnvlist_alloc();
|
||||||
|
|
||||||
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
|
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)
|
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
|
||||||
return (error);
|
return (error);
|
||||||
|
|
||||||
|
@ -4729,8 +4739,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
|
||||||
*/
|
*/
|
||||||
*cp = '\0';
|
*cp = '\0';
|
||||||
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
|
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
|
||||||
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_TRUE,
|
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE,
|
||||||
&local_nv, &local_avl) == 0) {
|
B_TRUE, &local_nv, &local_avl) == 0) {
|
||||||
*cp = '@';
|
*cp = '@';
|
||||||
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
|
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
|
||||||
fsavl_destroy(local_avl);
|
fsavl_destroy(local_avl);
|
||||||
|
|
|
@ -39,12 +39,12 @@
|
||||||
.Sh SYNOPSIS
|
.Sh SYNOPSIS
|
||||||
.Nm zfs
|
.Nm zfs
|
||||||
.Cm send
|
.Cm send
|
||||||
.Op Fl DLPRbcehnpvw
|
.Op Fl DLPRsbcehnpvw
|
||||||
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
|
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
|
||||||
.Ar snapshot
|
.Ar snapshot
|
||||||
.Nm zfs
|
.Nm zfs
|
||||||
.Cm send
|
.Cm send
|
||||||
.Op Fl DLPRcenpvw
|
.Op Fl DLPRscenpvw
|
||||||
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
|
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
|
||||||
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
|
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
|
||||||
.Nm zfs
|
.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
|
flag is used to send encrypted datasets, then
|
||||||
.Fl w
|
.Fl w
|
||||||
must also be specified.
|
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
|
.It Fl e, -embed
|
||||||
Generate a more compact stream by using
|
Generate a more compact stream by using
|
||||||
.Sy WRITE_EMBEDDED
|
.Sy WRITE_EMBEDDED
|
||||||
|
|
|
@ -258,7 +258,7 @@ tags = ['functional', 'cli_root', 'zfs_rollback']
|
||||||
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
|
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_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
|
||||||
'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw',
|
'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']
|
tags = ['functional', 'cli_root', 'zfs_send']
|
||||||
|
|
||||||
[tests/functional/cli_root/zfs_set]
|
[tests/functional/cli_root/zfs_set]
|
||||||
|
|
|
@ -13,7 +13,8 @@ dist_pkgdata_SCRIPTS = \
|
||||||
zfs_send_encrypted_unloaded.ksh \
|
zfs_send_encrypted_unloaded.ksh \
|
||||||
zfs_send_raw.ksh \
|
zfs_send_raw.ksh \
|
||||||
zfs_send_sparse.ksh \
|
zfs_send_sparse.ksh \
|
||||||
zfs_send-b.ksh
|
zfs_send-b.ksh \
|
||||||
|
zfs_send_skip_missing.ksh
|
||||||
|
|
||||||
dist_pkgdata_DATA = \
|
dist_pkgdata_DATA = \
|
||||||
zfs_send.cfg
|
zfs_send.cfg
|
||||||
|
|
|
@ -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."
|
Loading…
Reference in New Issue