Subversion Repositories zfs_utils

Rev

Rev 5 | Rev 7 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 5 Rev 6
Line 17... Line 17...
17
   # the source, where we're coming from
17
   # the source, where we're coming from
18
   'source' => '',
18
   'source' => '',
19
   # the target, where we want to replicate to
19
   # the target, where we want to replicate to
20
   'target' => '',
20
   'target' => '',
21
   # compile the regex
21
   # compile the regex
22
   'filter' => qr/(\d{4}.\d{2}.\d{2}.\d{2}.\d{2})/,
22
   'filter' => '(\d{4}.\d{2}.\d{2}.\d{2}.\d{2})',
23
   # if non-zero, just display the commands we'd use, don't run them
23
   # if non-zero, just display the commands we'd use, don't run them
24
   'dryrun' => 0,
24
   'dryrun' => 0,
25
   # whether to do all child datasets also (default)
25
   # whether to do all child datasets also (default)
26
   'recurse' => 1,
26
   'recurse' => 0,
27
   # show more information
27
   # show more information
28
   'verbose' => 0
28
   'verbose' => 0
29
   };
29
   };
30
 
30
 
31
sub parseDataSet {
31
sub parseDataSet {
Line 90... Line 90...
90
      $return{$dataset}{'snaps'}{$snap}{'used'} = $used;
90
      $return{$dataset}{'snaps'}{$snap}{'used'} = $used;
91
   }
91
   }
92
   return \%return;
92
   return \%return;
93
}
93
}
94
 
94
 
95
#sub diffSnaps {
-
 
96
#   my ( $config->{'source'}, $config->{'target'} ) = @_;
-
 
97
#   my @source = sort keys %$config->{'source'};
95
# get tne number of bytes we will be syncing.
98
#   my @target = sort keys %$config->{'target'};
-
 
99
#   print "===Source\n" . join( "\n", @source ) . "\n===Target\n" . join( "\n", @target ) . "\n";
-
 
100
 
-
 
101
#   my $s = 0;
96
sub findSize {
102
#   my $t = 0;
97
   my $config = shift;
103
#   my %return;
-
 
104
#   $return{'deleteTarget'} = [];
98
   # check for new snapshots to sync. If they are equal, we are up to date
105
#   $return{'addTarget'} = [];
99
   if ( $config->{'source'}->{'lastSnap'} ne $config->{'target'}->{'lastSnap'} ) {
106
#   $return{'lastMatch'} = 0;
100
      # Build the source command
107
#   $return{'finalSync'} = 0;
101
      my $sourceCommand = sprintf( '%s@%s %s@%s', 
108
#   while ( $s < @source && $t < @target ) {
102
                               $config->{'source'}->{'dataset'},
109
#      if ( $config->{'source'}[$s] eq $config->{'target'}[$t] ) { # matchies, just keep going
103
                               $config->{'target'}->{'lastSnap'},
110
#         print "Source $s [$config->{'source'}[$s]] matches target $t [$config->{'target'}[$t]]\n";
104
                               $config->{'source'}->{'dataset'},
111
#         $return{'lastMatch'} = $config->{'source'}[$s]; # keep track of the largest match
105
                               $config->{'source'}->{'lastSnap'}
112
#         $s++; $t++;
106
                           );
113
#      } elsif ( $config->{'target'}[$t] ne $config->{'source'}[$s] ) { # we are processing stuff that needs to be deleted on target
107
      # prepend 'zfs send' and the flags. Note that verbose is only for the one which is local
-
 
108
      $sourceCommand = 'zfs send -' . 
114
#         push @{$return{'deleteTarget'}}, $config->{'target'}[$t];
109
                  ( $config->{'recurse'} ? 'R' : '' ) . # recurse if they asked for it
115
#         print "Adding delete target $t [$config->{'target'}[$t]]\n";
110
                  # turn on verbose if they asked for level 2 AND if source is local
116
#         $t++;
111
                  'Pwn' .
117
#      }
-
 
118
#   }
-
 
119
#   die "Could not reconcile snapshots, ran out of source too soon\n" if $s > @source;
112
                  # this is the part that asks for incremental
120
#   # put a value into finalSync to make sure there is one. If we do not have any sync
113
                  'I ' .
121
#   # to do, final and lastMatch will be the same
114
                  $sourceCommand;
122
#   $return{'finalSync'} = $return{'lastMatch'};
115
      # wrap the ssh call if this is remote
123
#   while ( $s < @source ) {
116
      $sourceCommand = "ssh $config->{source}->{server} '$sourceCommand'" if  $config->{'source'}->{'server'};
124
#      push @{$return{'addTarget'}}, $config->{'source'}[$s];
117
      print "Checking Size with\n$sourceCommand\n" if $config->{'verbose'} > 2;
125
#      $return{'finalSync'} = $config->{'source'}[$s];
118
      my ( $error, $output ) = &run( $sourceCommand );
126
#      $s++;
119
      return -1 if $error;
127
#   }
-
 
-
 
120
      # the size is the second column (tab separated) of the last line (\n separated) in $output
128
#   die Dumper( \%return );
121
      return ( 
129
#   return \%return;
122
               split( 
130
#}
-
 
131
   
-
 
132
#sub arrayEquals {
123
                  "\t",
133
#   my ($a, $b ) = @_;
124
                  (
134
#   return 0 unless @{$a} == @{$b}; # they are different sizes
125
                     split( "\n", $output )
135
#   for ( my $i = 0; $i < @$a; $i++ ) {
126
                  )[-1]
136
#      if ( $$a[$i] ne $$b[$i] ) {
127
               )
137
#         print STDERR "No Match!\n" . join( "\t", @$a ) . "\n" . join( "\t", @$b ) . "\n";
-
 
138
#         return 0;
128
            )[1];
-
 
129
   } else { # nothing to sync
139
#      }
130
      return 0;
140
#   }
131
   }
141
#   return 1;
-
 
142
#}
132
}
143
   
133
 
-
 
134
# create the command necessary to do the replication
144
sub createCommands {
135
sub createCommands {
145
   my ( $lastSource, $lastTarget, $config ) = @_;
-
 
146
   my @return;
136
   my $config = shift;
147
   # check for new snapshots to sync
137
   # check for new snapshots to sync. If they are equal, we are up to date
-
 
138
   if ( $config->{'source'}->{'lastSnap'} ne $config->{'target'}->{'lastSnap'} ) {
148
   if ( $lastSource ne $lastTarget ) {
139
      # Build the source command
149
      my $snapRange = $config->{'target'}->{'dataset'} . '@' . $config->{'target'};
140
      my $sourceCommand = sprintf( '%s@%s %s@%s', 
150
      my $flags = ( $config->{'recurse'} ? 'R' : '' ) .
141
                               $config->{'source'}->{'dataset'},
151
                  ( $config->{'verbose'} == 2 ? 'v' : '' );
142
                               $config->{'target'}->{'lastSnap'},
152
      my $source = $config->{'source'}->{'dataset'} . '@' . $lastSource;
143
                               $config->{'source'}->{'dataset'},
153
      my $target = $config->{'target'}->{'dataset'};
144
                               $config->{'source'}->{'lastSnap'}
154
      # first create the replicate command. The send command request recursion (-R)
145
                           );
155
      # and the range of snapshots including all intermediate ones (-I)
146
      # prepend 'zfs send' and the flags. Note that verbose is only for the one which is local
156
      my $command = 'zfs send -RI ';
147
      $sourceCommand = 'zfs send -' . 
157
      $command .= $config->{'target'}->{'dataset'} . '@' . $config->{'target'} . ' ';
148
                  ( $config->{'recurse'} ? 'R' : '' ) . # recurse if they asked for it
-
 
149
                  # turn on verbose if they asked for level 2 AND if source is local
158
      $command .= $config->{'source'}->{'dataset'} . '@' . $config->{'source'};
150
                  ( $config->{'verbose'} > 1 && ! $config->{'source'}->{'server'} ? 'v' : '' ) .
-
 
151
                  # this is the part that asks for incremental
-
 
152
                  'I ' .
-
 
153
                  $sourceCommand;
-
 
154
      # wrap the ssh call if this is remote
159
      $command = "ssh $config->{source}->{server} '$config->{'source'}Command'" if $config->{'source'}->{'server'};
155
      $sourceCommand = "ssh $config->{source}->{server} '$sourceCommand'" if  $config->{'source'}->{'server'};
160
 
-
 
-
 
156
      # Now, build the target command
161
      $command = 'zfs receive -v ';
157
      my $targetCommand = 'zfs receive ' . 
-
 
158
                          ( ! $config->{'target'}->{'server'} && $config->{'verbose'} > 1 ? '-v ' : '') .
162
      $command .= $config->{'target'}->{'dataset'};
159
                          $config->{'target'}->{'dataset'};
163
      $command = "ssh $config->{target}->{server} '$config->{'source'}Command'" if $config->{'target'}->{'server'};
160
      $targetCommand = "ssh $config->{target}->{server} '$targetCommand'" if  $config->{'target'}->{'server'};
-
 
161
      # return the command
164
      push @return, $command . ' | ' . $command;
162
      return $sourceCommand . ' | ' . $targetCommand;
165
   } else {
163
   } else { # source and target are in sync, so do nothing
166
      push @return, '# Nothing new to sync';
164
      return '# Nothing new to sync';
167
   }
165
   }
168
   # now, check for snapshots to remove
-
 
169
   #if ( $config->{'actions'}->{'deleteTarget'} ) {
-
 
170
   #   my $delete = $config->{'actions'}->{'deleteTarget'};
-
 
171
   #   foreach my $ds ( @$delete ) {
-
 
172
   #      push @return, "zfs destroy -r $config->{target}->{'dataset'}\@$ds";
-
 
173
   #   }
-
 
174
   #} else {
-
 
175
   #   push @return, "# No old snapshots to be removed";
-
 
176
   #}
-
 
177
   return \@return;
-
 
178
}
166
}
179
   
167
   
180
# find the last snapshot in a hash. The hash is assumed to have a subkey
168
# find the last snapshot in a hash. The hash is assumed to have a subkey
181
# 'key'. look for the largest subkey, and return the key for it
169
# 'key'. look for the largest subkey, and return the key for it
182
sub getLastSnapshot {
170
sub getLastSnapshot {
Line 227... Line 215...
227
   } else {
215
   } else {
228
      return( '','',\@warnings);
216
      return( '','',\@warnings);
229
   }
217
   }
230
} # sub calculate
218
} # sub calculate
231
 
219
 
-
 
220
sub help {
-
 
221
   use File::Basename;
-
 
222
   my $me = fileparse( $0 );
-
 
223
   my $helpMessage = <<"   EOF";
-
 
224
      $me [flags] [source [target]]
-
 
225
         Syncs source dataset to target dataset
-
 
226
      
-
 
227
      Parameters (optional)
-
 
228
         source - dataset syncing from
-
 
229
         target - dataset syncing to
-
 
230
         
-
 
231
      Flags
-
 
232
         --source|s  - Alternate way to pass source dataset
-
 
233
         --target|t  - Alternate way to pass target dataset
-
 
234
         --filter|f  - Filter (regex) to limit source snapshots to process
-
 
235
         --dryrun|n  - Only displays command(s) to be run
-
 
236
         --recurse|r - Process dataset and all child datasets
-
 
237
         --verbose|v - increase verbosity of output
-
 
238
      
-
 
239
      May use short flags with bundling, ie -nrvv is valid for 
-
 
240
      --dryrun --recurse --verbose --verbose
-
 
241
      
-
 
242
      Either source or target must contain a DNS name or IP address of a remote
-
 
243
      machine, separated from the dataset with a colon, ie
-
 
244
         --source fbsd:storage/mydata
-
 
245
      would use the dataset storage/mydata on the server fbsd. The other dataset
-
 
246
      is assumed to be the local machine
-
 
247
      
-
 
248
      filter is a string which is a valid regular expression. Only snapshots matching
-
 
249
      that string will be used from the source dataset
-
 
250
      
-
 
251
      By default, only error messages are displayed. verbose will display statistics
-
 
252
      on size and transfer time. Invoking twice will display entire output of
-
 
253
      send/receive (whichever is the local machine)
-
 
254
   EOF
-
 
255
   # get rid of indentation
-
 
256
   $helpMessage =~ s/^      //;
-
 
257
   $helpMessage =~ s/\n      /\n/g;
-
 
258
   print $helpMessage;
-
 
259
   exit 1;
-
 
260
} # help
-
 
261
   
-
 
262
 
232
GetOptions( $config,
263
GetOptions( $config,
233
   'source|s=s',
264
   'source|s=s',
234
   'target|t=s',
265
   'target|t=s',
235
   'filter|f=s',
266
   'filter|f=s',
236
   'dryrun|n',
267
   'dryrun|n',
237
   'recurse|r',
268
   'recurse|r',
238
   'verbose|v',
269
   'verbose|v+',
239
   'help|h'
270
   'help|h'
240
);
271
);
241
 
272
 
-
 
273
&help() if $config->{'help'};
242
# allow them to use positional, without flags, such as
274
# allow them to use positional, without flags, such as
243
# replicate source target --filter='regex' -n
275
# replicate source target --filter='regex' -n
244
$config->{'source'} = shift unless $config->{'source'};
276
$config->{'source'} = shift unless $config->{'source'};
245
$config->{'target'} = shift unless $config->{'target'};
277
$config->{'target'} = shift unless $config->{'target'};
246
die "You must enter a source and a target, at a minimum\n" unless $config->{'source'} && $config->{'target'};
278
die "You must enter a source and a target, at a minimum\n" unless $config->{'source'} && $config->{'target'};
247
 
279
 
-
 
280
# keep track of when we started this run
-
 
281
$config->{'report'}->{'Start Time'} = time;
-
 
282
 
248
# WARNING: this converts source and targets from a string to a hash
283
# WARNING: this converts source and targets from a string to a hash
249
# '10.0.0.1:data/set' becomes ( 'server' => '10.0.0.1', 'dataset' => 'data/set')
284
# '10.0.0.1:data/set' becomes ( 'server' => '10.0.0.1', 'dataset' => 'data/set')
250
# and 'data/set' becomes ( 'server' => '', 'dataset' => 'data/set')
285
# and 'data/set' becomes ( 'server' => '', 'dataset' => 'data/set')
251
$config->{'source'} = &parseDataSet( $config->{'source'} );
286
$config->{'source'} = &parseDataSet( $config->{'source'} );
252
$config->{'target'} = &parseDataSet( $config->{'target'} );
287
$config->{'target'} = &parseDataSet( $config->{'target'} );
253
 
288
 
254
# both source and target can not have a server portion; one must be local
289
# both source and target can not have a server portion; one must be local
255
die "Source and Target can not both be remote\n" if $config->{'source'}->{'server'} && $config->{'target'}->{'server'};
290
die "Source and Target can not both be remote\n" if $config->{'source'}->{'server'} && $config->{'target'}->{'server'};
256
 
291
 
257
$config->{'source'}->{'snapshots'} = &getSnaps( $config->{'source'}, $config->{'filter'} );
292
# connect to servers and get all existing snapshots
258
$config->{'target'}->{'snapshots'} = &getSnaps( $config->{'target'}, $config->{'filter'} );
293
$config->{'target'}->{'snapshots'} = &getSnaps( $config->{'target'}, $config->{'filter'} );
-
 
294
$config->{'source'}->{'snapshots'} = &getSnaps( $config->{'source'}, $config->{'filter'} );
259
 
295
 
260
# we sync from last snap on target machine to last snap on source machine
296
# we sync from last snap on target machine to last snap on source machine. calculate simply
-
 
297
# finds the last snapshot on source and target
261
my ( $lastSource, $lastTarget ) = &calculate( $config );
298
( $config->{'source'}->{'lastSnap'}, $config->{'target'}->{'lastSnap'} ) = &calculate( $config );
262
 
299
 
-
 
300
# calculate transfer size if they want any feedback at all. Since this does take a few seconds
-
 
301
# to calculate, we won't run it unless they want a report
263
#print Dumper( $config ) . "\nSource = $lastSource\nTarget = $lastTarget\n"; die;
302
$config->{'report'}->{'Bytes Transferred'} = &findSize( $config ) if $config->{'verbose'};
264
 
303
 
265
# actually creates the commands to do the replicate
304
# actually creates the commands to do the replicate
266
my $commands = &createCommands( $lastSource, $lastTarget, $config );
305
my $commands = &createCommands( $config );
-
 
306
print "$commands\n" if $config->{'verbose'} or $config->{'dryrun'};
267
for ( my $i = 0; $i < @{$commands}; $i++ ) {
307
if ( $config->{'dryrun'} ) {
-
 
308
   print "Dry Run\n";
-
 
309
} else {
268
   print "$$commands[$i]\n" if $config->{'verbose'} or $config->{'dryrun'};
310
   print qx/$commands/ if $commands =~ m/^[a-zA-Z]/;
-
 
311
}
-
 
312
 
-
 
313
$config->{'report'}->{'End Time'} = time;
-
 
314
$config->{'report'}->{'Elapsed Time'} = $config->{'report'}->{'End Time'} - $config->{'report'}->{'Start Time'};
-
 
315
if ( $config->{'verbose'} ) {
269
   if ( $config->{'dryrun'} ) {
316
   if ( $config->{'dryrun'} ) {
270
      print "Dry Run\n";
317
      print "Would have transferred $config->{'report'}->{'Bytes Transferred'} bytes\n";
271
   } else {
318
   } else {
272
      print qx/$$commands[$i]/ if $$commands[$i] =~ m/^[a-zA-Z]/;
319
      print "Transferred $config->{'report'}->{'Bytes Transferred'} bytes in $config->{'report'}->{'Elapsed Time'} seconds\n";
273
   }
320
   }
274
}
321
}
275
 
-
 
276
#print Dumper( $config );
-
 
277
 
-
 
278
1;
322
1;