| Line 497... |
Line 497... |
| 497 |
|
497 |
|
| 498 |
return 1;
|
498 |
return 1;
|
| 499 |
}
|
499 |
}
|
| 500 |
|
500 |
|
| 501 |
# make a bunch of replicate commands and return them to the caller as a list
|
501 |
# make a bunch of replicate commands and return them to the caller as a list
|
| - |
|
502 |
# $sourceSnapsRef - list of snapshots on source machine
|
| - |
|
503 |
# $targetSnapsRef - list of snapshots on target machine
|
| 502 |
# $rootDataSet - string, the root of the snapshots exclusive of the dataset itself
|
504 |
# $dataset - The name of the dataset we are working on (same on both source and target)
|
| - |
|
505 |
# $sourceParent - The parent dataset of $dataset on source
|
| - |
|
506 |
# $targetParent - The parent dataset of $dataset on target
|
| 503 |
# $sourceSnapsRef
|
507 |
# $newStatusRef - A place to put the updated $targetSnapsRef
|
| 504 |
# $statusRef
|
508 |
# returns hashref of commands to execute, of form
|
| 505 |
# $newStatusRef
|
509 |
# {$dataset} = "zfs send command"
|
| 506 |
# returns
|
510 |
# where $dataset above can be a child of $dataset
|
| 507 |
sub makeReplicateCommands {
|
511 |
sub makeReplicateCommands {
|
| 508 |
my ( $sourceSnapsRef, $statusRef, $newStatusRef) = @_;
|
512 |
my ( $sourceSnapsRef, $targetSnapsRef, $dataset, $sourceParent, $targetParent, $newStatusRef ) = @_;
|
| 509 |
$sourceSnapsRef ||= [];
|
513 |
$sourceSnapsRef ||= [];
|
| 510 |
$statusRef ||= [];
|
514 |
$targetSnapsRef ||= [];
|
| 511 |
$newStatusRef ||= [];
|
515 |
$newStatusRef ||= [];
|
| - |
|
516 |
$sourceParent //= '';
|
| - |
|
517 |
$sourceParent .= '/' unless $sourceParent eq '' or substr($sourceParent, -1) eq '/';
|
| - |
|
518 |
$targetParent //= '';
|
| - |
|
519 |
$targetParent .= '/' unless $targetParent eq '' or substr($targetParent, -1) eq '/';
|
| - |
|
520 |
|
| - |
|
521 |
my %commands; # this will hold the commands (and the dataset as key) for return
|
| - |
|
522 |
|
| - |
|
523 |
fatalError( "No dataset defined in makeReplicateCommands, can not continue") unless $dataset;
|
| - |
|
524 |
|
| - |
|
525 |
# filter only the target and source snapshots which have this dataset in them, then remove
|
| - |
|
526 |
# the parent of each.
|
| - |
|
527 |
my $targetSnaps = [ map{ s/^$targetParent//r } grep{ /$dataset/ } @$targetSnapsRef ];
|
| - |
|
528 |
my $sourceSnaps = [ map{ s/^$sourceParent//r } grep{ /$dataset/ } @$sourceSnapsRef ];
|
| - |
|
529 |
|
| - |
|
530 |
#print "Dataset => [$dataset]\nSource Parent => [$sourceParent]\nTarget Parent => [$targetParent]\n";
|
| - |
|
531 |
#print "Source Snaps\n" . Dumper( $sourceSnapsRef) . "\nTarget Snaps\n" . Dumper( $targetSnapsRef) . "\n";
|
| - |
|
532 |
|
| - |
|
533 |
#print Dumper( $targetSnaps ) . "\n" . Dumper( $sourceSnaps ) . "\n"; die;
|
| - |
|
534 |
#return \%commands;
|
| 512 |
|
535 |
|
| 513 |
# parse snapshots: each line is expected to have snapshot fullname as first token: pool/fs@snap ...
|
536 |
# parse snapshots: each line is expected to have snapshot fullname as first token: pool/fs@snap ...
|
| 514 |
my %snaps_by_fs;
|
537 |
my %snaps_by_fs;
|
| 515 |
foreach my $line (@$sourceSnapsRef) {
|
538 |
foreach my $line (@$sourceSnaps) {
|
| 516 |
next unless defined $line && $line =~ /\S/;
|
539 |
next unless defined $line && $line =~ /\S/;
|
| 517 |
my ($tok) = split /\s+/, $line;
|
540 |
my ($tok) = split /\s+/, $line;
|
| 518 |
next unless $tok && $tok =~ /@/;
|
541 |
next unless $tok && $tok =~ /@/;
|
| 519 |
my ($fs, $snap) = split /@/, $tok, 2;
|
542 |
my ($fs, $snap) = split /@/, $tok, 2;
|
| 520 |
push @{ $snaps_by_fs{$fs} }, $snap;
|
543 |
push @{ $snaps_by_fs{$fs} }, $snap;
|
| Line 522... |
Line 545... |
| 522 |
|
545 |
|
| 523 |
# nothing to do
|
546 |
# nothing to do
|
| 524 |
return [] unless keys %snaps_by_fs;
|
547 |
return [] unless keys %snaps_by_fs;
|
| 525 |
|
548 |
|
| 526 |
# figure root filesystem: first snapshot line's fs is the requested root
|
549 |
# figure root filesystem: first snapshot line's fs is the requested root
|
| 527 |
my ($first_line) = grep { defined $_ && $_ =~ /\S/ } @$sourceSnapsRef;
|
550 |
my ($first_line) = grep { defined $_ && $_ =~ /\S/ } @$sourceSnaps;
|
| 528 |
my ($root_fs) = $first_line ? (split(/\s+/, $first_line))[0] =~ /@/ ? (split(/@/, (split(/\s+/, $first_line))[0]))[0] : undef : undef;
|
551 |
my ($root_fs) = $first_line ? (split(/\s+/, $first_line))[0] =~ /@/ ? (split(/@/, (split(/\s+/, $first_line))[0]))[0] : undef : undef;
|
| 529 |
$root_fs ||= (sort keys %snaps_by_fs)[0];
|
552 |
$root_fs ||= (sort keys %snaps_by_fs)[0];
|
| 530 |
|
553 |
|
| 531 |
# helper: find last status entry for a filesystem (status lines contain full snapshot names pool/fs@snap)
|
554 |
# helper: find last status entry for a filesystem (status lines contain full snapshot names pool/fs@snap)
|
| 532 |
my %last_status_for;
|
555 |
my %last_status_for;
|
| 533 |
for my $s (@$statusRef) {
|
556 |
for my $s (@$targetSnaps) {
|
| 534 |
next unless $s && $s =~ /@/;
|
557 |
next unless $s && $s =~ /@/;
|
| 535 |
my ($fs, $snap) = split /@/, $s, 2;
|
558 |
my ($fs, $snap) = split /@/, $s, 2;
|
| 536 |
$last_status_for{$fs} = $snap; # later entries override earlier ones -> last occurrence kept
|
559 |
$last_status_for{$fs} = $snap; # later entries override earlier ones -> last occurrence kept
|
| 537 |
}
|
560 |
}
|
| 538 |
|
561 |
|
| Line 549... |
Line 572... |
| 549 |
# decide if we can do a single recursive send:
|
572 |
# decide if we can do a single recursive send:
|
| 550 |
# condition: all 'to' snapshot names are identical
|
573 |
# condition: all 'to' snapshot names are identical
|
| 551 |
my %to_names = map { $_ => 1 } values %to_for;
|
574 |
my %to_names = map { $_ => 1 } values %to_for;
|
| 552 |
my $single_to_name = (keys %to_names == 1) ? (keys %to_names)[0] : undef;
|
575 |
my $single_to_name = (keys %to_names == 1) ? (keys %to_names)[0] : undef;
|
| 553 |
|
576 |
|
| 554 |
my %commands;
|
- |
|
| 555 |
|
- |
|
| 556 |
if ($single_to_name) {
|
577 |
if ($single_to_name) {
|
| 557 |
# check whether any from is missing
|
578 |
# check whether any from is missing
|
| 558 |
my @from_values = map { $from_for{$_} } sort keys %from_for;
|
579 |
my @from_values = map { $from_for{$_} } sort keys %from_for;
|
| 559 |
my $any_from_missing = grep { !defined $_ } @from_values;
|
580 |
my $any_from_missing = grep { !defined $_ } @from_values;
|
| 560 |
my %from_names = map { $_ => 1 } grep { defined $_ } @from_values;
|
581 |
my %from_names = map { $_ => 1 } grep { defined $_ } @from_values;
|
| 561 |
my $single_from_name = (keys %from_names == 1) ? (keys %from_names)[0] : undef;
|
582 |
my $single_from_name = (keys %from_names == 1) ? (keys %from_names)[0] : undef;
|
| 562 |
|
583 |
|
| 563 |
if ($any_from_missing) {
|
584 |
if ($any_from_missing) {
|
| 564 |
# full recursive send from root
|
585 |
# full recursive send from root
|
| 565 |
$commands{$root_fs} = sprintf('zfs send -R %s@%s', $root_fs, $single_to_name);
|
586 |
$commands{$root_fs} = sprintf('zfs send -R %s%s@%s', $sourceParent, $root_fs, $single_to_name);
|
| 566 |
}
|
587 |
}
|
| 567 |
elsif ($single_from_name) {
|
588 |
elsif ($single_from_name) {
|
| 568 |
# incremental recursive send, but don't do it if they are the same
|
589 |
# incremental recursive send, but don't do it if they are the same
|
| 569 |
$commands{$root_fs} = sprintf('zfs send -R -I %s@%s %s@%s',
|
590 |
$commands{$root_fs} = sprintf('zfs send -R -I %s%s@%s %s%s@%s',
|
| 570 |
$root_fs, $single_from_name, $root_fs, $single_to_name)
|
591 |
$sourceParent, $root_fs, $single_from_name, $sourceParent, $root_fs, $single_to_name)
|
| 571 |
unless $single_from_name eq $single_to_name;
|
592 |
unless $single_from_name eq $single_to_name;
|
| 572 |
}
|
593 |
}
|
| 573 |
else {
|
594 |
else {
|
| 574 |
# from snapshots differ across children -> fall back to per-filesystem sends
|
595 |
# from snapshots differ across children -> fall back to per-filesystem sends
|
| 575 |
foreach my $fs (sort keys %to_for) {
|
596 |
foreach my $fs (sort keys %to_for) {
|
| 576 |
my $to = $to_for{$fs};
|
597 |
my $to = $to_for{$fs};
|
| 577 |
my $from = $from_for{$fs};
|
598 |
my $from = $from_for{$fs};
|
| 578 |
if ($from) {
|
599 |
if ($from) {
|
| 579 |
# if from and to are different, add it
|
600 |
# if from and to are different, add it
|
| 580 |
$commands{$fs} = sprintf('zfs send -I %s@%s %s@%s', $fs, $from, $fs, $to)
|
601 |
$commands{$fs} = sprintf('zfs send -I %s%s@%s %s%s@%s', $sourceParent, $fs, $from, $sourceParent, $fs, $to)
|
| 581 |
unless $from eq $to;
|
602 |
unless $from eq $to;
|
| 582 |
} else {
|
603 |
} else {
|
| 583 |
$commands{$fs} = sprintf('zfs send %s@%s', $fs, $to);
|
604 |
$commands{$fs} = sprintf('zfs send %s%s@%s', $sourceParent, $fs, $to);
|
| 584 |
}
|
605 |
}
|
| 585 |
}
|
606 |
}
|
| 586 |
}
|
607 |
}
|
| 587 |
|
608 |
|
| 588 |
# update new status: record newest snap for every filesystem
|
609 |
# update new status: record newest snap for every filesystem
|
| 589 |
foreach my $fs (keys %to_for) {
|
610 |
foreach my $fs (keys %to_for) {
|
| 590 |
push @$newStatusRef, sprintf('%s@%s', $fs, $to_for{$fs});
|
611 |
push @$newStatusRef, sprintf('%s%s@%s', $targetParent, $fs, $to_for{$fs});
|
| 591 |
}
|
612 |
}
|
| 592 |
} else {
|
613 |
} else {
|
| 593 |
# not all children share same newest snap -> per-filesystem sends
|
614 |
# not all children share same newest snap -> per-filesystem sends
|
| 594 |
foreach my $fs (sort keys %to_for) {
|
615 |
foreach my $fs (sort keys %to_for) {
|
| 595 |
my $to = $to_for{$fs};
|
616 |
my $to = $to_for{$fs};
|
| 596 |
my $from = $from_for{$fs};
|
617 |
my $from = $from_for{$fs};
|
| 597 |
if ($from) {
|
618 |
if ($from) {
|
| 598 |
$commands{$fs} = sprintf('zfs send -I %s@%s %s@%s', $fs, $from, $fs, $to);
|
619 |
$commands{$fs} = sprintf('zfs send -I %s%s@%s %s%s@%s', $sourceParent, $fs, $from, $sourceParent, $fs, $to);
|
| 599 |
} else {
|
620 |
} else {
|
| 600 |
$commands{$fs} = sprintf('zfs send %s@%s', $fs, $to);
|
621 |
$commands{$fs} = sprintf('zfs send %s%s@%s', $sourceParent, $fs, $to);
|
| 601 |
}
|
622 |
}
|
| 602 |
push @$newStatusRef, sprintf('%s@%s', $fs, $to);
|
623 |
push @$newStatusRef, sprintf('%s%s@%s', $targetParent, $fs, $to);
|
| 603 |
}
|
624 |
}
|
| 604 |
}
|
625 |
}
|
| 605 |
|
626 |
|
| 606 |
# return arrayref of commands (caller can iterate or join with pipes)
|
627 |
# return arrayref of commands (caller can iterate or join with pipes)
|
| 607 |
return \%commands;
|
628 |
return \%commands;
|