added sleep 0.1 s for netcat to start, verify snapshot creation
[sysadmin-cookbook] / recepies / zfs / zfs-replicate-pool.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4
5 use Net::OpenSSH;
6 use Data::Dump qw(dump);
7 use List::Util qw(first);
8 use Time::Hires;
9
10 my $arh = Net::OpenSSH->new('root@10.60.0.204');
11 my $dev = Net::OpenSSH->new('root@10.60.0.202');
12
13 sub on {
14         my ($ssh,$command) = @_;
15         warn "## ", $ssh->get_host, "> $command\n";
16         if ( $command =~ m/zfs list/ ) {
17                 map {
18                         chomp; $_;
19                 } $ssh->capture($command);
20         } else {
21                 $ssh->capture($command);
22         }
23 }
24
25 print on $arh => 'zpool status';
26 print on $dev => 'zpool status';
27
28 my @arh = on $arh => 'zfs list -H -o name';
29 my @dev = on $dev => 'zfs list -H -o name';
30
31 warn "# ",dump( \@arh, \@dev );
32
33 my $from_pool = $arh[0];
34 my $to_pool   = $dev[0];
35
36 sub snapshots_from {
37         my ($ssh) = @_;
38         my $host = $ssh->get_host;
39
40         my $snapshot;
41
42         my @snapshots = on $ssh => 'zfs list -H -t snapshot -o name';
43         die $ssh->error if $ssh->error;
44         foreach my $s (@snapshots) {
45                 my ($fs,$name) = split(/\@/,$s);
46                 push @{ $snapshot->{$fs} }, $name;
47         }
48
49 #       warn "snapshots_from $host ",dump($snapshot),$/;
50
51         return $snapshot;
52 }
53
54 foreach my $fs ( @arh ) {
55
56         my $name = $fs;
57         $name =~ s{^$from_pool/}{} || next; # FIXME skip top-level fs
58         warn "? $name";
59
60         my $arh_snapshot = snapshots_from $arh;
61         if ( ! exists( $arh_snapshot->{$fs} ) ) {
62
63                 my $snapshot = $fs . '@send';
64                 print on $arh => "zfs snapshot $snapshot";
65                 die $arh->error if $arh->error;
66                 $arh_snapshot = snapshots_from $arh;
67         }
68
69         my $max_snapshot = $#{ $arh_snapshot->{$fs} };
70         warn "$max_snapshot snapshots of $fs on arh\n";
71
72         my $to_dev = "$to_pool/$name";
73
74         foreach my $i ( 0 .. $max_snapshot ) {
75                 my $snap = $arh_snapshot->{$fs}->[$i] || die "no snap";
76
77                 my $dev_snapshot = snapshots_from $dev;
78                 if ( exists $dev_snapshot->{$to_dev} ) {
79                         if ( first { /^\Q$snap\E$/ } @{ $dev_snapshot->{$to_dev} } ) {
80                                 warn "+ $name exists\n";
81                                 next;
82                         } else {
83                                 warn "- $name missing\n";
84                         }
85                 } else {
86                         warn "$name not found on target yet";
87                 }
88
89                 my $snapshot;
90                 if ( $i == 0 ) {
91                         $snapshot = "$from_pool/$name\@$snap";
92                 } else {
93                         my $prev = $arh_snapshot->{$fs}->[$i-1] || die "no prev";
94                         $snapshot = "-i $from_pool/$name\@$prev $from_pool/$name\@$snap";
95                 }
96
97                 warn "zfs transfer $snapshot -> $to_dev";
98
99                 my $t = time();
100
101                 my $recv = "nc -w 5 -l -p 8888 | zfs receive $to_dev";
102                 warn ">> $recv\n";
103                 my ($rin1,$pid1) = $dev->pipe_in($recv);
104                 warn ">> pid: $pid1";
105
106                 sleep 0.1; # FIXME wait for netcat to start
107
108                 my $send = "zfs send $snapshot | nc -q 0 -w 5 10.60.0.202 8888";
109                 warn "<< $send\n";
110                 $arh->system($send);
111
112                 $t = time() - $t;
113                 warn "took $t seconds to complete\n";
114
115                 $dev->system("zfs set readonly=on $to_pool/$name") if $i == 0;
116                 die $dev->error if $dev->error;
117
118                 $dev_snapshot = snapshots_from $dev;
119                 die "can't find new snapshot $snap" unless $dev_snapshot->{$to_dev};
120
121         }
122
123 }