-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add 'zfs send --saved' flag #9007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
correct. ill have more documentation soon, still fleshing it out. |
This PR should now be ready for review |
f5845a0
to
8fcdf61
Compare
It seems like it would require a resumable receive. I.e. the workflow would be:
I think we could document this more clearly in the manpage. I.e. the --partial sends the partially resumed state associated with the specified filesystem. We should probably have a separate summary line for this, since I don't think you should be able to do |
I think that Somewhat similarly, I think we need to take into account I'm not sure about the other |
@ahrens thanks for the review, I've updated the manpages to be clearer. |
@alek-p Let's say you are doing an incremental receive, and it is interrupted at some object/offset. Then you do |
ab09006
to
4c3d0f3
Compare
I updated and rebased the code based on the feed back I got. This should be ready for another review. |
@tcaputi it looks like the newly added test case may not be entirely reliable, see the CentOS 7 failure. |
cmd/zfs/zfs_main.c
Outdated
@@ -4357,10 +4380,40 @@ zfs_do_send(int argc, char **argv) | |||
} | |||
} | |||
|
|||
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET); | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we implemented this logic and the logic to find the incremental source of the partial send in libzfs? That way other programs could take advantage of it, and we wouldn't have to do this logic here, in the cmd code. We could add a libzfs_partial_send function that takes argv[0] and figures out everything else, and then invokes the send api directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I see your point here, but I'm a bit conflicted because I also want the interface to libzfs to be as consistent as possible. zfs_send_one()
takes a zhp so having a separate interface that takes argv[0]
seems strange. I think in an ideal world, zfs_main.c would not be opening zhp's at all, but I'm not sure this is the place to make that change. Thoughts @pcd1193182 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point, but I think the value of making this partial send logic accessible to programs not using the command line is valuable enough to be worth being a little different here. That's just my opinion, though, but I understand if others feel strongly the other way.
538875f
to
3de7547
Compare
@behlendorf @ahrens I have updated the implementation to use the existing resumable receive code. It should be ready for review again. |
3de7547
to
5313887
Compare
Codecov Report
@@ Coverage Diff @@
## master #9007 +/- ##
========================================
+ Coverage 79% 80% +<1%
========================================
Files 385 385
Lines 121492 121610 +118
========================================
+ Hits 96511 96898 +387
+ Misses 24981 24712 -269
Continue to review full report at Codecov.
|
5313887
to
52cf371
Compare
@behlendorf this patch has been sitting here for a little while. I just rebased it. Assuming the tests come back ok, is there anything we can do to get it over the finish line? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updating this. From my perspective it sounds like there's general agreement that this would be a useful feature.
One thing we haven't hashed out if the option should be renamed --saved
instead of --partial
as @ahrens suggested. For the reasons mentioned in that comment, I'd personally prefer we go with --saved
, but I could be convinced otherwise. We'd want to update the code as well to match the new name which does make this a bit more work.
Additionally, there are still a handful of minor review comments to address. The only major one being resolving the documentation conflict. Once we wrap up that remaining work I think we're in good shape. The code itself functionally looks correct to me.
cmd/zfs/zfs_main.c
Outdated
@@ -4190,11 +4191,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'}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I was similarly confused, I think switching the long opt to --saved
would be clearer.
cmd/zfs/zfs_main.c
Outdated
flags.compress || flags.raw || redactbook != NULL) { | ||
(void) fprintf(stderr, | ||
gettext("incompatible flags combined with partial " | ||
"send flag\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I'd suggest moving the gettext(...
up to fit this on two lines.
cmd/zfs/zfs_main.c
Outdated
if (strchr(argv[0], '@') != NULL) { | ||
(void) fprintf(stderr, | ||
gettext("partial send cannot send full " | ||
"snapshots\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
lib/libzfs/libzfs_sendrecv.c
Outdated
zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, | ||
"'%s' used in the initial send no " | ||
"longer exists"), | ||
toname); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can be moved to the previous line
lib/libzfs/libzfs_sendrecv.c
Outdated
goto out; | ||
|
||
partial_nvl = zfs_send_resume_token_to_nvlist(hdl, token_buf); | ||
if (partial_nvl == NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to zfs_send_resume()
I think this comment is worth adding.
/*
* zfs_error_aux has already been set by
* zfs_send_resume_token_to_nvlist
*/
lib/libzfs/libzfs_sendrecv.c
Outdated
|
||
/* | ||
* If a resume token is provided we use the object and offset | ||
* from that instead of the default, which starts frm the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/frm/from
man/man8/zfs.8
Outdated
@@ -40,6 +40,313 @@ | |||
.Nm | |||
.Fl ?V | |||
.Nm | |||
<<<<<<< 8221bcf1e4efc40d92d1354de710048c70eb709b | |||
======= |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recently the man pages were split by subcommand, you'll want to update man/man8/zfs-send.8
52cf371
to
6b9642c
Compare
@ahrens @behlendorf I have addressed your review comments. The feature is now called |
6b9642c
to
1b3d0e1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. The code looks good to me, and I verified it works as intended in my local testing.
@behlendorf Can we get this pushed through at some point? |
@tcaputi I have this on my to-do list to take another look at the code this week. Thanks for your patience! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a look at the dmu_send
code.
The handling of if (err == ENOENT) {
when trying to hold the .../%recv
filesystem deserves attention or an explanatory comment IMHO.
module/zfs/dmu_send.c
Outdated
char name[ZFS_MAX_DATASET_NAME_LEN + 6]; | ||
|
||
(void) snprintf(name, sizeof (name), | ||
"%s/%%recv", tosnap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably use recv_clone_name
constant instead of %%recv
(and compute the size of name
dependent on recv_clone_name
's length)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we allocate ZFS_MAX_DATASET_NAME_LEN on the stack in several other places, but it looks like you could easily use kmem_asprintf()
to allocate it on the heap here.
err = dsl_dataset_own_force(dspp.dp, name, dsflags, | ||
FTAG, &dspp.to_ds); | ||
if (err == ENOENT) { | ||
err = dsl_dataset_own_force(dspp.dp, tosnap, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as far as I can tell, we reach the body of this if stmt iff savedok == true
but there is no saved state (i.e. no .../%recv
dataset). Is that correct?
If above is correct, the code that should execute after dsl_dataset_own_force(dspp.dp, tosnap,...
should be identical to what happens if !savedok
in line 2732 (outer else branch below).
dsflags, FTAG, &dspp.to_ds); | ||
} | ||
|
||
if (err == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if err = ENOENT
above, we still take this branch. Is that correct? Because we don't execute the code in this branch after line 2732
Is DS_FIELD_RESUME_TOGUID
even defined for the tosnap
's (as opposed to the name
dataset)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tcaputi can probably speak to this better, but my understanding is that when you do zfs send --saved pool/fs
, there are 3 cases:
- there is no saved state, give an error
- there was an incremental receive on an existing fs in progress, therefore we have saved state on
pool/fs/%recv
- there was receive that created a new fs in progress (typically a full receive, but could be an incremental which creates a clone), therefore we have saved state on
pool/fs
(which has no snapshots or children)
I think this code is handling these correctly, but would appreciate your verification.
} | ||
|
||
if (err == 0) { | ||
err = zap_lookup(dspp.dp->dp_meta_objset, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
cmd/zfs/zfs_main.c
Outdated
@@ -306,7 +306,8 @@ get_usage(zfs_help_t idx) | |||
"<filesystem|volume|snapshot>\n" | |||
"\tsend [-DnPpvLec] [-i bookmark|snapshot] " | |||
"--redact <bookmark> <snapshot>\n" | |||
"\tsend [-nvPe] -t <receive_resume_token>\n")); | |||
"\tsend [-nvPe] -t <receive_resume_token>\n" | |||
"\tsend [-Pnv] -S filesystem\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be more "discoverable" to document it as --saved
here, rather than -S
.
cmd/zfs/zfs_main.c
Outdated
(void) fprintf(stderr, gettext("saved send cannot " | ||
"send full snapshots\n")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This error message could be a little confusing. How about something like, saved send: must specify the filesystem with partially-received state
module/zfs/dmu_send.c
Outdated
char name[ZFS_MAX_DATASET_NAME_LEN + 6]; | ||
|
||
(void) snprintf(name, sizeof (name), | ||
"%s/%%recv", tosnap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we allocate ZFS_MAX_DATASET_NAME_LEN on the stack in several other places, but it looks like you could easily use kmem_asprintf()
to allocate it on the heap here.
module/zfs/dmu_send.c
Outdated
*/ | ||
if (!ds->ds_is_snapshot) { | ||
dsl_dataset_name(origds, dsname); | ||
(void) strcat(dsname, "/%recv"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same constant "%recv"
here. Also, I think dsname
can be declared inside the if
.
module/zfs/dmu_send.c
Outdated
/* | ||
* If this is a saved send we may actually be sending | ||
* from the %recv clone used for resuming. | ||
*/ | ||
if (!ds->ds_is_snapshot) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be getting a flag to indicate that this is a saved send? Otherwise, it seems like we could be taking on %recv
when they are actually estimating a send to the filesystem itself (which I think is allowed as long as it isn't mounted writeable).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hasn't been allowed in the past. The old code just did:
if (!ds->ds_is_snapshot)
return (SET_ERROR(EINVAL));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spoke to Brian about this offline and we don't believe that this change should be needed. We came to the conclusion that the send estimate code shouldn't really have to care about whether this is a saved send or not. Let me know if you have any other thoughts.
c38e1fb
to
2543042
Compare
This commit adds the --saved (-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 saved send 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 <[email protected]>
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.
Types of changes
Checklist:
Signed-off-by
.