Subversion Repositories sysadmin_scripts

Rev

Rev 170 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 170 Rev 171
Line 8... Line 8...
8
my $source = shift;
8
my $source = shift;
9
my $target = shift;
9
my $target = shift;
10
 
10
 
11
die "Usage: replicate source target\n" unless $source && $target;
11
die "Usage: replicate source target\n" unless $source && $target;
12
 
12
 
13
my $dryRun = 0; # if set, will only display the command to be executed
13
my $dryRun = 1; # if set, will only display the command to be executed
14
 
14
 
15
my $config = {
15
my $config = {
16
   # compile the regex
16
   # compile the regex
17
   'pattern' => qr/auto-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}/
17
   'pattern' => qr/(\d{4}.\d{2}.\d{2}.\d{2}.\d{2})/
18
   };
18
   };
19
 
19
 
20
sub parseDataSet {
20
sub parseDataSet {
21
   my $data = shift;
21
   my $data = shift;
22
   my %return;
22
   my %return;
Line 53... Line 53...
53
   my ($config,$pattern) = @_;
53
   my ($config,$pattern) = @_;
54
   my %return;
54
   my %return;
55
   # actual command to run to get all snapshots, recursively, of the dataset
55
   # actual command to run to get all snapshots, recursively, of the dataset
56
   my $command = 'zfs list -r -t snap ' . $config->{'dataset'};
56
   my $command = 'zfs list -r -t snap ' . $config->{'dataset'};
57
   $command = "ssh $config->{server} '$command'" if $config->{'server'};
57
   $command = "ssh $config->{server} '$command'" if $config->{'server'};
-
 
58
   #die "$command\n";
58
   my ($error, $output ) = &run( $command );
59
   my ($error, $output ) = &run( $command );
59
   die $output if $error;
60
   #die "Error running $command with output\n$output" if $error;
60
   my @snaps = split( "\n", $output );
61
   my @snaps = split( "\n", $output );
61
   chomp @snaps;
62
   chomp @snaps;
62
   for (my $i = 0; $i < @snaps; $i++ ) {
63
   for (my $i = 0; $i < @snaps; $i++ ) {
63
      # parse out the space delmited fields
64
      # parse out the space delmited fields
64
      my ($fullname, $used, $avail, $refer, $mount) = split( /\s+/, $snaps[$i] );
65
      my ($fullname, $used, $avail, $refer, $mount) = split( /\s+/, $snaps[$i] );
Line 66... Line 67...
66
      my ($dataset, $snap) = split( '@', $fullname );
67
      my ($dataset, $snap) = split( '@', $fullname );
67
      # remove the root dataset name
68
      # remove the root dataset name
68
      $dataset =~ s/^$config->{'dataset'}//;
69
      $dataset =~ s/^$config->{'dataset'}//;
69
      # skip anything not matching our regex
70
      # skip anything not matching our regex
70
      next unless $pattern && $snap && $snap =~ m/$pattern/;
71
      next unless $pattern && $snap && $snap =~ m/$pattern/;
-
 
72
      # grab the matched key
-
 
73
      $return{$dataset}{'snaps'}{$snap}{'key'} = $1;
-
 
74
      # and remove all non-numerics
-
 
75
      $return{$dataset}{'snaps'}{$snap}{'key'} =~ s/[^0-9]//g;
-
 
76
      # get the transfer size
71
      $return{$dataset}{'snaps'}{$snap}{'refer'} = $refer;
77
      $return{$dataset}{'snaps'}{$snap}{'refer'} = $refer;
-
 
78
      # get the actual disk space used
72
      $return{$dataset}{'snaps'}{$snap}{'used'} = $used;
79
      $return{$dataset}{'snaps'}{$snap}{'used'} = $used;
73
   }
80
   }
74
   return \%return;
81
   return \%return;
75
}
82
}
76
 
83
 
Line 122... Line 129...
122
   }
129
   }
123
   return 1;
130
   return 1;
124
}
131
}
125
   
132
   
126
sub createCommands {
133
sub createCommands {
127
   my $config = shift;
134
   my ( $source, $target, $config ) = @_;
128
   my @return;
135
   my @return;
129
   # check for new snapshots to sync
136
   # check for new snapshots to sync
130
   if ( $config->{'actions'}->{'lastMatch'} ne $config->{'actions'}->{'finalSync'} ) {
137
   if ( $source ne $target ) {
131
      # first create the replicate command. The send command request recursion (-R)
138
      # first create the replicate command. The send command request recursion (-R)
132
      # and the range of snapshots including all intermediate ones (-I)
139
      # and the range of snapshots including all intermediate ones (-I)
133
      my $sourceCommand = 'zfs send -RI ';
140
      my $sourceCommand = 'zfs send -RI ';
134
      $sourceCommand .= $config->{'source'}->{'dataset'} . '@' . $config->{'actions'}->{'lastMatch'} . ' ';
141
      $sourceCommand .= $config->{'source'}->{'dataset'} . '@' . $target . ' ';
135
      $sourceCommand .= $config->{'source'}->{'dataset'} . '@' . $config->{'actions'}->{'finalSync'};
142
      $sourceCommand .= $config->{'source'}->{'dataset'} . '@' . $source;
136
      $sourceCommand = "ssh $config->{source}->{server} '$sourceCommand'" if $config->{'source'}->{'server'};
143
      $sourceCommand = "ssh $config->{source}->{server} '$sourceCommand'" if $config->{'source'}->{'server'};
137
 
144
 
138
      my $targetCommand = 'zfs receive -v ';
145
      my $targetCommand = 'zfs receive -v ';
139
      $targetCommand .= $config->{'target'}->{'dataset'};
146
      $targetCommand .= $config->{'target'}->{'dataset'};
140
      $targetCommand = "ssh $config->{target}->{server} '$sourceCommand'" if $config->{'target'}->{'server'};
147
      $targetCommand = "ssh $config->{target}->{server} '$sourceCommand'" if $config->{'target'}->{'server'};
141
      push @return, $sourceCommand . ' | ' . $targetCommand;
148
      push @return, $sourceCommand . ' | ' . $targetCommand;
142
   } else {
149
   } else {
143
      push @return, '# Nothing new to sync';
150
      push @return, '# Nothing new to sync';
144
   }
151
   }
145
   # now, check for snapshots to remove
152
   # now, check for snapshots to remove
146
   if ( $config->{'actions'}->{'deleteTarget'} ) {
153
   #if ( $config->{'actions'}->{'deleteTarget'} ) {
147
      my $delete = $config->{'actions'}->{'deleteTarget'};
154
   #   my $delete = $config->{'actions'}->{'deleteTarget'};
148
      foreach my $ds ( @$delete ) {
155
   #   foreach my $ds ( @$delete ) {
149
         push @return, "zfs destroy -r $config->{target}->{'dataset'}\@$ds";
156
   #      push @return, "zfs destroy -r $config->{target}->{'dataset'}\@$ds";
150
      }
157
   #   }
151
   } else {
158
   #} else {
152
      push @return, "# No old snapshots to be removed";
159
   #   push @return, "# No old snapshots to be removed";
153
   }
160
   #}
154
   return \@return;
161
   return \@return;
155
}
162
}
156
   
163
   
-
 
164
# find the last snapshot in a hash. The hash is assumed to have a subkey
-
 
165
# 'key'. look for the largest subkey, and return the key for it
-
 
166
sub getLastSnapshot {
-
 
167
   my $snapList = shift;
-
 
168
   my $lastKey = 0;
-
 
169
   my $lastSnap = '';
-
 
170
   foreach my $snap ( keys %$snapList ) {
-
 
171
      if ( $snapList->{$snap}->{'key'} > $lastKey ) {
-
 
172
         $lastKey = $snapList->{$snap}->{'key'};
-
 
173
         $lastSnap = $snap;
-
 
174
      }
-
 
175
   }
-
 
176
   return $lastSnap;
-
 
177
}
157
 
178
 
158
 
179
 
159
sub calculate {
180
sub calculate {
160
   my $config = shift;
181
   my $config = shift;
-
 
182
 
161
   my $return;
183
   my @warnings;
162
   my $allMatch;
184
   
-
 
185
   # find the last snapshot date in each dataset, on each target
163
   my $lastMatch;
186
   foreach my $machine ( 'source', 'target' ) {
-
 
187
      $config->{$machine}->{'last'} = 0; # track the last entry in all children in dataset
-
 
188
      $config->{$machine}->{'allOk'} = 1; # assumed to be true, becomes false if some children do not have snapshots
164
   foreach my $dataset ( sort keys %{$config->{'source'}->{'snapshots'}} ) {
189
      foreach my $child ( keys %{ $config->{$machine}->{'snapshots'} } ) {
165
      next unless exists $config->{'source'}->{'snapshots'}->{$dataset};
190
         $config->{$machine}->{'snapshots'}->{$child}->{'last'} = 
166
      die "No matching target for $dataset\n" unless $config->{'target'}->{'snapshots'}->{$dataset};
191
            &getLastSnapshot( $config->{$machine}->{'snapshots'}->{$child}->{'snaps'} );
167
      $return->{$dataset} = &diffSnaps( 
192
         # set the machine last if we haven't done so yet
-
 
193
         $config->{$machine}->{'last'} = $config->{$machine}->{'snapshots'}->{$child}->{'last'} unless $config->{$machine}->{'last'};
168
         $config->{'source'}->{'snapshots'}->{$dataset}->{'snaps'},
194
         # keep track of the last snapshot for each set
169
         $config->{'target'}->{'snapshots'}->{$dataset}->{'snaps'}
195
         if ( $config->{$machine}->{'last'} ne $config->{$machine}->{'snapshots'}->{$child}->{'last'} ) {
170
      );
-
 
171
      $allMatch = $return->{$dataset} unless $allMatch;
196
            $config->{$machine}->{'allOk'} = 0;
172
#      die Dumper( $allMatch ) . "\n";
197
            push @warnings, "Warning: $machine does not have consistent snapshots at $child";;
173
      next;
198
         }
174
      unless ( 
199
      }
-
 
200
   }
-
 
201
   # make sure the source has a corresponding snap for target->last
175
         &arrayEquals( $return->{'allMatch'}->{'deleteTarget'}, $return->{$dataset}->{'deleteTarget'} ) &&
202
   foreach my $child ( keys %{ $config->{'target'}->{'snapshots'} } ) {
176
         &arrayEquals( $return->{'allMatch'}->{'addTarget'},    $return->{$dataset}->{'addTarget'} )
203
      if (! exists ($config->{'source'}->{'snapshots'}->{$child}->{'snaps'}->{$config->{'target'}->{'snapshots'}->{$child}->{'last'}} ) ) {
177
         ) {
204
         $config->{'source'}->{'allOk'} = 0;
178
         warn "Warning: dataset $dataset does not match\n";
205
         push @warnings, "Warning: We  do not have consistent snapshots";
179
         last;
-
 
180
      }
206
      }
181
   }
207
   }
182
   #print Dumper( $allMatch );
208
   my $return;
183
   $return->{'lastMatch'} = $allMatch->{'lastMatch'};
-
 
184
   $return->{'finalSync'} = $allMatch->{'finalSync'};
-
 
185
#   die Dumper( $allMatch->{'deleteTarget'} ) . "\n" ;
209
   if ( $config->{'source'}->{'allOk'} and $config->{'target'}->{'allOk'} ) { # whew, they match
186
   $return->{'deleteTarget'} = $allMatch->{'deleteTarget'};
210
      return( $config->{'source'}->{'last'}, $config->{'target'}->{'last'}, \@warnings );
187
#   print Dumper( $return ) . "\n"; die;
-
 
188
   return $return;
211
   } else {
189
   #print Dumper( $return ) . "\n"; die;
212
      return( '','',\@warnings);
-
 
213
   }
190
}
214
}
191
 
215
 
192
$config->{'source'} = &parseDataSet( $source );
216
$config->{'source'} = &parseDataSet( $source );
193
$config->{'target'} = &parseDataSet( $target );
217
$config->{'target'} = &parseDataSet( $target );
194
 
218
 
Line 196... Line 220...
196
die "Source and Target can not both be remote\n" if $config->{'source'}->{'server'} && $config->{'target'}->{'server'};
220
die "Source and Target can not both be remote\n" if $config->{'source'}->{'server'} && $config->{'target'}->{'server'};
197
 
221
 
198
$config->{'source'}->{'snapshots'} = &getSnaps( $config->{'source'}, $config->{'pattern'} );
222
$config->{'source'}->{'snapshots'} = &getSnaps( $config->{'source'}, $config->{'pattern'} );
199
$config->{'target'}->{'snapshots'} = &getSnaps( $config->{'target'}, $config->{'pattern'} );
223
$config->{'target'}->{'snapshots'} = &getSnaps( $config->{'target'}, $config->{'pattern'} );
200
 
224
 
201
$config->{'actions'} = &calculate( $config );
225
# $config->{'actions'} = &calculate( $config );
-
 
226
my ( $lastSource, $lastTarget ) = &calculate( $config );
202
 
227
 
203
#print Dumper( $config ); die;
228
#print Dumper( $config ) . "\nSource = $lastSource\nTarget = $lastTarget\n"; die;
204
 
229
 
205
my $commands = &createCommands( $config );
230
my $commands = &createCommands( $lastSource, $lastTarget, $config );
206
for ( my $i = 0; $i < @{$commands}; $i++ ) {
231
for ( my $i = 0; $i < @{$commands}; $i++ ) {
207
   print "$$commands[$i]\n";
232
   print "$$commands[$i]\n";
208
   if ( $dryRun ) {
233
   if ( $dryRun ) {
209
      print "Dry Run\n";
234
      print "Dry Run\n";
210
   } else {
235
   } else {
Line 212... Line 237...
212
   }
237
   }
213
}
238
}
214
 
239
 
215
#print Dumper( $config );
240
#print Dumper( $config );
216
 
241
 
217
1;
-
 
218
 
242
1;
-
 
243