From ab09006beb3d1ff0c529ac859902c6763cdbbfea Mon Sep 17 00:00:00 2001 From: Tom Caputi Date: Thu, 18 Jul 2019 20:25:40 -0400 Subject: [PATCH] Add 'zfs send --partial' flag This commit adds the --partial (-S) to the 'zfs send' command. This flag allows a user to send a partially received dataset, which can be useful when migrating a backup server to new hardware. This flag is compatible with resumable receives, so even if the partial transfer is interrupted, it can be resumed. The flag does not require any user / kernel ABI changes or any new feature flags in the send stream format. Signed-off-by: Tom Caputi --- cmd/zfs/zfs_main.c | 47 ++++++++- include/libzfs.h | 3 + include/libzfs_core.h | 1 + include/sys/dmu_send.h | 8 +- lib/libzfs/libzfs_sendrecv.c | 2 + lib/libzfs_core/libzfs_core.c | 2 + man/man8/zfs.8 | 25 +++++ module/zfs/dmu_send.c | 98 ++++++++++++++++--- module/zfs/zfs_ioctl.c | 21 ++-- tests/runfiles/linux.run | 3 +- .../tests/functional/rsend/Makefile.am | 1 + .../functional/rsend/send_partial_dataset.ksh | 89 +++++++++++++++++ 12 files changed, 272 insertions(+), 28 deletions(-) create mode 100755 tests/zfs-tests/tests/functional/rsend/send_partial_dataset.ksh diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 0ebd16f6d6a5..adbef1cda8d5 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -306,7 +306,8 @@ get_usage(zfs_help_t idx) "\n" "\tsend [-DnPpvLec] [-i bookmark|snapshot] " "--redact \n" - "\tsend [-nvPe] -t \n")); + "\tsend [-nvPe] -t \n" + "\tsend [-PenvLw] -S [-i bookmark|snapshot] filesystem\n")); case HELP_SET: return (gettext("\tset ... " " ...\n")); @@ -4120,11 +4121,12 @@ zfs_do_send(int argc, char **argv) {"raw", no_argument, NULL, 'w'}, {"backup", no_argument, NULL, 'b'}, {"holds", no_argument, NULL, 'h'}, + {"partial", no_argument, NULL, 'S'}, {0, 0, 0, 0} }; /* check options */ - while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:", + while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S", long_options, NULL)) != -1) { switch (c) { case 'i': @@ -4184,6 +4186,9 @@ zfs_do_send(int argc, char **argv) flags.embed_data = B_TRUE; flags.largeblock = B_TRUE; break; + case 'S': + flags.partial = B_TRUE; + break; case ':': /* * If a parameter was not passed, optopt contains the @@ -4234,7 +4239,7 @@ zfs_do_send(int argc, char **argv) if (resume_token != NULL) { if (fromname != NULL || flags.replicate || flags.props || flags.backup || flags.dedup || flags.holds || - redactbook != NULL) { + flags.partial || redactbook != NULL) { (void) fprintf(stderr, gettext("invalid flags combined with -t\n")); usage(B_FALSE); @@ -4255,6 +4260,23 @@ zfs_do_send(int argc, char **argv) } } + if (flags.partial) { + if (flags.replicate || flags.props || flags.doall || + flags.backup || flags.dedup || flags.holds || + redactbook != NULL) { + (void) fprintf(stderr, + gettext("incompatible flags combined with partial " + "send flag\n")); + usage(B_FALSE); + } + if (strchr(argv[0], '@') != NULL) { + (void) fprintf(stderr, + gettext("partial send cannot send full " + "snapshots\n")); + usage(B_FALSE); + } + } + if (flags.raw && redactbook != NULL) { (void) fprintf(stderr, gettext("Error: raw sends may not be redacted.\n")); @@ -4278,6 +4300,7 @@ zfs_do_send(int argc, char **argv) */ if (!(flags.replicate || flags.doall)) { char frombuf[ZFS_MAX_DATASET_NAME_LEN]; + char tobuf[ZFS_MAX_DATASET_NAME_LEN + 6]; if (redactbook != NULL) { if (strchr(argv[0], '@') == NULL) { @@ -4287,7 +4310,23 @@ zfs_do_send(int argc, char **argv) } } - zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET); + /* + * If this is a partial send, the partial data will either + * live in the dataset itself (if the dataset did not + * previously exist) or it will live in the special '%recv' + * clone. To figure out which is correct we simply check both. + */ + char *open_ds = argv[0]; + if (flags.partial) { + (void) snprintf(tobuf, sizeof (tobuf), "%s/%%recv", + argv[0]); + if (zfs_dataset_exists(g_zfs, tobuf, + ZFS_TYPE_DATASET)) { + open_ds = tobuf; + } + } + + zhp = zfs_open(g_zfs, open_ds, ZFS_TYPE_DATASET); if (zhp == NULL) return (1); diff --git a/include/libzfs.h b/include/libzfs.h index 79e7692cdc0e..4ad22b699433 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -668,6 +668,9 @@ typedef struct sendflags { /* include snapshot holds in send stream */ boolean_t holds; + + /* stream represents a partially received dataset */ + boolean_t partial; } sendflags_t; typedef boolean_t (snapfilter_cb_t)(zfs_handle_t *, void *); diff --git a/include/libzfs_core.h b/include/libzfs_core.h index 04cc47781359..023229c6905c 100644 --- a/include/libzfs_core.h +++ b/include/libzfs_core.h @@ -79,6 +79,7 @@ enum lzc_send_flags { LZC_SEND_FLAG_LARGE_BLOCK = 1 << 1, LZC_SEND_FLAG_COMPRESS = 1 << 2, LZC_SEND_FLAG_RAW = 1 << 3, + LZC_SEND_FLAG_PARTIAL = 1 << 4, }; int lzc_send(const char *, const char *, int, enum lzc_send_flags); diff --git a/include/sys/dmu_send.h b/include/sys/dmu_send.h index 2f3dfc39fd32..3d212144e3f3 100644 --- a/include/sys/dmu_send.h +++ b/include/sys/dmu_send.h @@ -51,14 +51,16 @@ struct dmu_send_outparams; int dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok, boolean_t large_block_ok, boolean_t compressok, boolean_t rawok, - uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd, - offset_t *off, struct dmu_send_outparams *dsop); + boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff, + const char *redactbook, int outfd, offset_t *off, + struct dmu_send_outparams *dsop); int dmu_send_estimate_fast(struct dsl_dataset *ds, struct dsl_dataset *fromds, zfs_bookmark_phys_t *frombook, boolean_t stream_compressed, uint64_t *sizep); int dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap, boolean_t embedok, boolean_t large_block_ok, boolean_t compressok, - boolean_t rawok, int outfd, offset_t *off, struct dmu_send_outparams *dso); + boolean_t rawok, boolean_t partialok, int outfd, offset_t *off, + struct dmu_send_outparams *dso); typedef int (*dmu_send_outfunc_t)(objset_t *os, void *buf, int len, void *arg); typedef struct dmu_send_outparams { diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 9fdb990522d9..dc77de5cce09 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -1815,6 +1815,8 @@ lzc_flags_from_sendflags(const sendflags_t *flags) lzc_flags |= LZC_SEND_FLAG_COMPRESS; if (flags->raw) lzc_flags |= LZC_SEND_FLAG_RAW; + if (flags->partial) + lzc_flags |= LZC_SEND_FLAG_PARTIAL; return (lzc_flags); } diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c index d441f3655d03..b04f8115b02f 100644 --- a/lib/libzfs_core/libzfs_core.c +++ b/lib/libzfs_core/libzfs_core.c @@ -674,6 +674,8 @@ lzc_send_resume_redacted(const char *snapname, const char *from, int fd, fnvlist_add_boolean(args, "compressok"); if (flags & LZC_SEND_FLAG_RAW) fnvlist_add_boolean(args, "rawok"); + if (flags & LZC_SEND_FLAG_PARTIAL) + fnvlist_add_boolean(args, "partialok"); if (resumeobj != 0 || resumeoff != 0) { fnvlist_add_uint64(args, "resume_object", resumeobj); fnvlist_add_uint64(args, "resume_offset", resumeoff); diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 3fc142f5eb8c..c2d41850e26f 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -215,6 +215,12 @@ .Op Fl Penv .Fl t Ar receive_resume_token .Nm +.Cm send +.Op Fl PenvLw +.Oo Fl i Ar snapshot Ns | Ns Ar bookmark +.Oc +.Fl S Ar filesystem +.Nm .Cm receive .Op Fl Fhnsuv .Op Fl o Sy origin Ns = Ns Ar snapshot @@ -3970,6 +3976,25 @@ See the documentation for for more details. .It Xo .Nm +.Cm send +.Op Fl PenvLw +.Op Fl i Ar snapshot Ns | Ns Ar bookmark +.Fl S +.Ar filesystem +.Xc +Send a dataset that has been partially transferred via a resumable receive. +.Bl -tag -width "-L" +.It Fl S, -partial +This flag requires that the specified filesystem previously received a resumable +send that did not finish and was interrupted. In such scenarios this flag +enables us to send this partially received state. This flag is compatible with +the +.Fl i +flag, allowing the partial state of the dataset to be sent as an incremental +stream. +.El +.It Xo +.Nm .Cm receive .Op Fl Fhnsuv .Op Fl o Sy origin Ns = Ns Ar snapshot diff --git a/module/zfs/dmu_send.c b/module/zfs/dmu_send.c index 884be31bd226..6a618d53ef5e 100644 --- a/module/zfs/dmu_send.c +++ b/module/zfs/dmu_send.c @@ -1898,6 +1898,8 @@ struct dmu_send_params { boolean_t embedok; boolean_t large_block_ok; boolean_t compressok; + boolean_t rawok; + boolean_t partialok; uint64_t resumeobj; uint64_t resumeoff; zfs_bookmark_phys_t *redactbook; @@ -1907,7 +1909,6 @@ struct dmu_send_params { /* Stream progress params */ offset_t *off; int outfd; - boolean_t rawok; }; static int @@ -1995,7 +1996,24 @@ create_begin_record(struct dmu_send_params *dspp, objset_t *os, drr->drr_u.drr_begin.drr_flags |= DRR_FLAG_SPILL_BLOCK; dsl_dataset_name(to_ds, drrb->drr_toname); - if (!to_ds->ds_is_snapshot) { + if (dspp->partialok) { + objset_t *mos = dmu_objset_pool(os)->dp_meta_objset; + + /* + * If we're sending a partially received dataset + * we need to get the toguid and toname from the + * receive resume token since to_ds is a + * non-snapshotted, inconsistent dataset. + */ + VERIFY0(zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_TOGUID, sizeof (drrb->drr_toguid), 1, + &drrb->drr_toguid)); + VERIFY0(zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_TONAME, 1, sizeof (drrb->drr_toname), + drrb->drr_toname)); + + drrb->drr_flags &= ~DRR_FLAG_CLONE; + } else if (!to_ds->ds_is_snapshot) { (void) strlcat(drrb->drr_toname, "@--head--", sizeof (drrb->drr_toname)); } @@ -2315,6 +2333,40 @@ dmu_send_impl(struct dmu_send_params *dspp) dsl_pool_rele(dp, tag); return (err); } + + /* + * If we're sending partial state resume fields must exist and we will + * need to setup the dspp resume fields. + */ + if (dspp->partialok) { + struct drr_begin drrb; + objset_t *mos = dmu_objset_pool(os)->dp_meta_objset; + err = zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_TOGUID, sizeof (drrb.drr_toguid), 1, + &drrb.drr_toguid); + err += zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_TONAME, 1, sizeof (drrb.drr_toname), + drrb.drr_toname); + if (err != 0) { + dsl_pool_rele(dp, tag); + return (SET_ERROR(EINVAL)); + } + err = zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_OBJECT, sizeof (dspp->resumeobj), 1, + &dspp->resumeobj); + if (err != 0) { + dsl_pool_rele(dp, tag); + return (SET_ERROR(EINVAL)); + } + err = zap_lookup(mos, dmu_objset_id(os), + DS_FIELD_RESUME_OFFSET, sizeof (dspp->resumeoff), 1, + &dspp->resumeoff); + if (err != 0) { + dsl_pool_rele(dp, tag); + return (SET_ERROR(EINVAL)); + } + } + /* * If this is a non-raw send of an encrypted ds, we can ensure that * the objset_phys_t is authenticated. This is safe because this is @@ -2536,19 +2588,27 @@ dmu_send_impl(struct dmu_send_params *dspp) goto out; } - bzero(drr, sizeof (dmu_replay_record_t)); - drr->drr_type = DRR_END; - drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc; - drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid; + /* + * Send the DRR_END record if this is not a partial stream. + * Otherwise, the omitted DRR_END record will signal to + * the receive side that the stream is incomplete. + */ + if (!dspp->partialok) { + bzero(drr, sizeof (dmu_replay_record_t)); + drr->drr_type = DRR_END; + drr->drr_u.drr_end.drr_checksum = dsc.dsc_zc; + drr->drr_u.drr_end.drr_toguid = dsc.dsc_toguid; - if (dump_record(&dsc, NULL, 0) != 0) - err = dsc.dsc_err; + if (dump_record(&dsc, NULL, 0) != 0) + err = dsc.dsc_err; + } out: mutex_enter(&to_ds->ds_sendstream_lock); list_remove(&to_ds->ds_sendstreams, dssp); mutex_exit(&to_ds->ds_sendstream_lock); - VERIFY(err != 0 || (dsc.dsc_sent_begin && dsc.dsc_sent_end)); + VERIFY(err != 0 || (dsc.dsc_sent_begin && + (dsc.dsc_sent_end || dspp->partialok))); kmem_free(drr, sizeof (dmu_replay_record_t)); kmem_free(dssp, sizeof (dmu_sendstatus_t)); @@ -2574,7 +2634,8 @@ dmu_send_impl(struct dmu_send_params *dspp) int dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap, boolean_t embedok, boolean_t large_block_ok, boolean_t compressok, - boolean_t rawok, int outfd, offset_t *off, dmu_send_outparams_t *dsop) + boolean_t rawok, boolean_t partialok, int outfd, offset_t *off, + dmu_send_outparams_t *dsop) { int err; dsl_dataset_t *fromds; @@ -2588,6 +2649,7 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap, dspp.dso = dsop; dspp.tag = FTAG; dspp.rawok = rawok; + dspp.partialok = partialok; err = dsl_pool_hold(pool, FTAG, &dspp.dp); if (err != 0) @@ -2654,8 +2716,9 @@ dmu_send_obj(const char *pool, uint64_t tosnap, uint64_t fromsnap, int dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok, boolean_t large_block_ok, boolean_t compressok, boolean_t rawok, - uint64_t resumeobj, uint64_t resumeoff, const char *redactbook, int outfd, - offset_t *off, dmu_send_outparams_t *dsop) + boolean_t partialok, uint64_t resumeobj, uint64_t resumeoff, + const char *redactbook, int outfd, offset_t *off, + dmu_send_outparams_t *dsop) { int err = 0; ds_hold_flags_t dsflags = (rawok) ? 0 : DS_HOLD_FLAG_DECRYPT; @@ -2674,6 +2737,7 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok, dspp.resumeobj = resumeobj; dspp.resumeoff = resumeoff; dspp.rawok = rawok; + dspp.partialok = partialok; if (fromsnap != NULL && strpbrk(fromsnap, "@#") == NULL) return (SET_ERROR(EINVAL)); @@ -2681,13 +2745,19 @@ dmu_send(const char *tosnap, const char *fromsnap, boolean_t embedok, err = dsl_pool_hold(tosnap, FTAG, &dspp.dp); if (err != 0) return (err); + if (strchr(tosnap, '@') == NULL && spa_writeable(dspp.dp->dp_spa)) { /* * We are sending a filesystem or volume. Ensure * that it doesn't change by owning the dataset. */ - err = dsl_dataset_own(dspp.dp, tosnap, dsflags, FTAG, - &dspp.to_ds); + if (partialok) { + err = dsl_dataset_own_force(dspp.dp, tosnap, dsflags, + FTAG, &dspp.to_ds); + } else { + err = dsl_dataset_own(dspp.dp, tosnap, dsflags, + FTAG, &dspp.to_ds); + } owned = B_TRUE; } else { err = dsl_dataset_hold_flags(dspp.dp, tosnap, dsflags, FTAG, diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index c2b75cc98d60..dd8ab8368494 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -5259,6 +5259,7 @@ zfs_ioc_send(zfs_cmd_t *zc) boolean_t large_block_ok = (zc->zc_flags & 0x2); boolean_t compressok = (zc->zc_flags & 0x4); boolean_t rawok = (zc->zc_flags & 0x8); + boolean_t partialok = (zc->zc_flags & 0x10); if (zc->zc_obj != 0) { dsl_pool_t *dp; @@ -5325,8 +5326,8 @@ zfs_ioc_send(zfs_cmd_t *zc) out.dso_arg = fp->f_vnode; out.dso_dryrun = B_FALSE; error = dmu_send_obj(zc->zc_name, zc->zc_sendobj, - zc->zc_fromobj, embedok, large_block_ok, compressok, rawok, - zc->zc_cookie, &off, &out); + zc->zc_fromobj, embedok, large_block_ok, compressok, + rawok, partialok, zc->zc_cookie, &off, &out); if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0) fp->f_offset = off; @@ -6216,6 +6217,8 @@ zfs_ioc_space_snaps(const char *lastsnap, nvlist_t *innvl, nvlist_t *outnvl) * presence indicates compressed DRR_WRITE records are permitted * (optional) "rawok" -> (value ignored) * presence indicates raw encrypted records should be used. + * (optional) "partialok" -> (value ignored) + * presence indicates we should send a partially received snapshot * (optional) "resume_object" and "resume_offset" -> (uint64) * if present, resume send stream from specified object and offset. * (optional) "redactbook" -> (string) @@ -6232,6 +6235,7 @@ static const zfs_ioc_key_t zfs_keys_send_new[] = { {"embedok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL}, {"compressok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL}, {"rawok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL}, + {"partialok", DATA_TYPE_BOOLEAN, ZK_OPTIONAL}, {"resume_object", DATA_TYPE_UINT64, ZK_OPTIONAL}, {"resume_offset", DATA_TYPE_UINT64, ZK_OPTIONAL}, {"redactbook", DATA_TYPE_STRING, ZK_OPTIONAL}, @@ -6250,6 +6254,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) boolean_t embedok; boolean_t compressok; boolean_t rawok; + boolean_t partialok; uint64_t resumeobj = 0; uint64_t resumeoff = 0; char *redactbook = NULL; @@ -6262,6 +6267,7 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) embedok = nvlist_exists(innvl, "embedok"); compressok = nvlist_exists(innvl, "compressok"); rawok = nvlist_exists(innvl, "rawok"); + partialok = nvlist_exists(innvl, "partialok"); (void) nvlist_lookup_uint64(innvl, "resume_object", &resumeobj); (void) nvlist_lookup_uint64(innvl, "resume_offset", &resumeoff); @@ -6276,8 +6282,9 @@ zfs_ioc_send_new(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) out.dso_outfunc = dump_bytes; out.dso_arg = fp->f_vnode; out.dso_dryrun = B_FALSE; - error = dmu_send(snapname, fromname, embedok, largeblockok, compressok, - rawok, resumeobj, resumeoff, redactbook, fd, &off, &out); + error = dmu_send(snapname, fromname, embedok, largeblockok, + compressok, rawok, partialok, resumeobj, resumeoff, + redactbook, fd, &off, &out); if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0) fp->f_offset = off; @@ -6345,6 +6352,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) boolean_t embedok; boolean_t compressok; boolean_t rawok; + boolean_t partialok; uint64_t space = 0; boolean_t full_estimate = B_FALSE; uint64_t resumeobj = 0; @@ -6368,6 +6376,7 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) embedok = nvlist_exists(innvl, "embedok"); compressok = nvlist_exists(innvl, "compressok"); rawok = nvlist_exists(innvl, "rawok"); + partialok = nvlist_exists(innvl, "partialok"); boolean_t from = (nvlist_lookup_string(innvl, "from", &fromname) == 0); boolean_t altbook = (nvlist_lookup_string(innvl, "redactbook", &redactlist_book) == 0); @@ -6442,8 +6451,8 @@ zfs_ioc_send_space(const char *snapname, nvlist_t *innvl, nvlist_t *outnvl) dsl_dataset_rele(tosnap, FTAG); dsl_pool_rele(dp, FTAG); error = dmu_send(snapname, fromname, embedok, largeblockok, - compressok, rawok, resumeobj, resumeoff, redactlist_book, - fd, &off, &out); + compressok, rawok, partialok, resumeobj, resumeoff, + redactlist_book, fd, &off, &out); } else { error = dmu_send_estimate_fast(tosnap, fromsnap, (from && strchr(fromname, '#') != NULL ? &zbm : NULL), diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 43c7a748fcf6..b3ee2452890b 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -815,7 +815,8 @@ tests = ['rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'send_encrypted_props', 'send_encrypted_truncated_files', 'send_freeobjects', 'send_realloc_dnode_size', 'send_realloc_files', 'send_realloc_encrypted_files', 'send_spill_block', 'send_holds', - 'send_hole_birth', 'send_mixed_raw', 'send-wDR_encrypted_zvol'] + 'send_hole_birth', 'send_mixed_raw', 'send-wDR_encrypted_zvol', + 'send_partial_dataset'] tags = ['functional', 'rsend'] [tests/functional/scrub_mirror] diff --git a/tests/zfs-tests/tests/functional/rsend/Makefile.am b/tests/zfs-tests/tests/functional/rsend/Makefile.am index 585018ac2503..8e16f8847669 100644 --- a/tests/zfs-tests/tests/functional/rsend/Makefile.am +++ b/tests/zfs-tests/tests/functional/rsend/Makefile.am @@ -41,6 +41,7 @@ dist_pkgdata_SCRIPTS = \ send-c_zstreamdump.ksh \ send-cpL_varied_recsize.ksh \ send_freeobjects.ksh \ + send_partial_dataset.ksh \ send_realloc_dnode_size.ksh \ send_realloc_files.ksh \ send_realloc_encrypted_files.ksh \ diff --git a/tests/zfs-tests/tests/functional/rsend/send_partial_dataset.ksh b/tests/zfs-tests/tests/functional/rsend/send_partial_dataset.ksh new file mode 100755 index 000000000000..10ab9142aeae --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/send_partial_dataset.ksh @@ -0,0 +1,89 @@ +#!/bin/ksh + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version a.0. +# You may only use this file in accordance with the terms of version +# a.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) 2019 Datto Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# Description: +# Verify that a partially received dataset can be sent with +# 'zfs send --partial'. +# +# Strategy: +# 1. Setup a pool with partially received filesystem +# 2. Perform partial send without incremental +# 3. Perform partial send with incremental +# 4. Perform partial send with incremental, resuming from a token +# + +verify_runnable "both" + +log_assert "Verify that a partially received dataset can be sent with " \ + "'zfs send --partial'." + +function cleanup +{ + destroy_dataset $POOL/testfs2 "-r" + destroy_dataset $POOL/stream "-r" + destroy_dataset $POOL/recvfs "-r" + destroy_dataset $POOL/partialfs "-r" +} +log_onexit cleanup + +log_must zfs create $POOL/testfs2 +log_must zfs create $POOL/stream +mntpnt=$(get_prop mountpoint $POOL/testfs2) + +# Setup a pool with partially received filesystem +log_must mkfile 1m $mntpnt/filea +log_must zfs snap $POOL/testfs2@a +log_must mkfile 1m $mntpnt/fileb +log_must zfs snap $POOL/testfs2@b +log_must eval "zfs send $POOL/testfs2@a | zfs recv $POOL/recvfs" +log_must eval "zfs send -i $POOL/testfs2@a $POOL/testfs2@b > " \ + "/$POOL/stream/base.send" +mess_file /$POOL/stream/base.send +log_mustnot zfs recv -s $POOL/recvfs < /$POOL/stream/base.send + +# Perform partial send without incremental +log_mustnot eval "zfs send --partial $POOL/recvfs | zfs recv -s " \ + "$POOL/partialfs" +token=$(zfs get -Hp -o value receive_resume_token $POOL/partialfs) +log_must eval "zfs send -t $token | zfs recv -s $POOL/partialfs" +file_check $POOL/recvfs $POOL/partialfs +log_must zfs destroy -r $POOL/partialfs + +# Perform partial send with incremental +log_must eval "zfs send $POOL/recvfs@a | zfs recv $POOL/partialfs" +log_mustnot eval "zfs send --partial -i $POOL/recvfs@a $POOL/recvfs | " \ + "zfs recv -s $POOL/partialfs" +token=$(zfs get -Hp -o value receive_resume_token $POOL/partialfs) +log_must eval "zfs send -t $token | zfs recv -s $POOL/partialfs" +file_check $POOL/recvfs $POOL/partialfs +log_must zfs destroy -r $POOL/partialfs + +# Perform partial send with incremental, resuming from token +log_must eval "zfs send $POOL/recvfs@a | zfs recv $POOL/partialfs" +log_must eval "zfs send --partial -i $POOL/recvfs@a $POOL/recvfs > " \ + "/$POOL/stream/partial.send" +mess_file /$POOL/stream/partial.send +log_mustnot zfs recv -s $POOL/partialfs < /$POOL/stream/partial.send +token=$(zfs get -Hp -o value receive_resume_token $POOL/partialfs) +log_must eval "zfs send -t $token | zfs recv -s $POOL/partialfs" +file_check $POOL/recvfs $POOL/partialfs + +log_pass "A partially received dataset can be sent with 'zfs send --partial'."