Subversion Repositories zfs_utils

Rev

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

Rev 37 Rev 42
Line 44... Line 44...
44
our $VERSION = '0.1';
44
our $VERSION = '0.1';
45
 
45
 
46
use FindBin;
46
use FindBin;
47
use lib "$FindBin::Bin/..";
47
use lib "$FindBin::Bin/..";
48
use Data::Dumper;
48
use Data::Dumper;
49
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel mountGeli runCmd sendReport $logFileName $displayLogsOnConsole);
49
use ZFS_Utils qw(loadConfig shredFile logMsg makeReplicateCommands mountDriveByLabel unmountDriveByLabel mountGeli runCmd sendReport fatalError cleanDirectory $logFileName $displayLogsOnConsole);
50
 
50
 
51
my $scriptDirectory = $FindBin::RealBin;
51
my $scriptDirectory = $FindBin::RealBin;
52
my $scriptFullPath = "$scriptDirectory/" . $FindBin::Script;
52
my $scriptFullPath = "$scriptDirectory/" . $FindBin::Script;
53
 
53
 
54
 
54
 
Line 83... Line 83...
83
   },
83
   },
84
   #information about target server
84
   #information about target server
85
   'target' => {
85
   'target' => {
86
      'hostname' => '', # used to see if we are on target
86
      'hostname' => '', # used to see if we are on target
87
      'poolname' => 'backup', # name of the ZFS pool to import
87
      'poolname' => 'backup', # name of the ZFS pool to import
-
 
88
      'shutdown_after_replication' => 0, # if set to 1, will shutdown the server after replication
88
      # if this is set, the dataset uses GELI, so we must decrypt and
89
      # if this is set, the dataset uses GELI, so we must decrypt and
89
      # mount it first
90
      # mount it first
90
      'geli' => {
91
      'geli' => {
91
         'secureKey ' => {
92
         'secureKey ' => {
92
            'label' => 'replica', # the GPT label of the key disk
93
            'label' => 'replica', # the GPT label of the key disk
Line 130... Line 131...
130
         'IV'     => '00000000000000000000000000000000',
131
         'IV'     => '00000000000000000000000000000000',
131
      },
132
      },
132
   },
133
   },
133
   'datasets' => {
134
   'datasets' => {
134
      'dataset1' => {
135
      'dataset1' => {
135
         'source' => 'pool/dataset1',
136
         'source' => 'pool', # the parent of the dataset on the source
136
         'target' => 'backup/dataset1',
137
         'target' => 'backup', # the parent of the dataset on the target
137
         'filename' => 'dataset1'
138
         'dataset' => 'dataset1', # the dataset name
138
      },
139
      },
139
      'files_share'  => {
140
      'files_share'  => {
140
         'source' => 'pool/files_share',
141
         'source' => 'pool',
141
         'target' => 'backup/files_share',
142
         'target' => 'backup',
142
         'filename' => 'files_share'
143
         'dataset' => 'files_share'.
143
      },
144
      },
144
   }
145
   }
145
};
146
};
146
 
147
 
147
# read the status file and return as list. If the file doesn't exits, returns an empty list
148
# read the status file and return as list. If the file doesn't exits, returns an empty list
Line 200... Line 201...
200
   my ($config, $statusList) = @_;
201
   my ($config, $statusList) = @_;
201
   my $newStatus = [];
202
   my $newStatus = [];
202
   foreach my $dataset ( sort keys %{$config->{datasets}} ) {
203
   foreach my $dataset ( sort keys %{$config->{datasets}} ) {
203
      logMsg("Processing dataset '$dataset'");
204
      logMsg("Processing dataset '$dataset'");
204
      # get list of all snapshots on dataset
205
      # get list of all snapshots on dataset
-
 
206
      my $root = $config->{datasets}->{$dataset}->{source} . '/' . $config->{datasets}->{$dataset}->{dataset};
205
      my $sourceList = [ runCmd( "zfs list -rt snap -H -o name $config->{datasets}->{$dataset}->{source} " ) ];
207
      my $sourceList = [ runCmd( "zfs list -rt snap -H -o name $root" ) ];
-
 
208
      # remove the parent part, leave the dataset itself
-
 
209
      $sourceList =~ s|$config->{datasets}->{$dataset}->{source}/||;
206
      # process dataset here
210
      # process dataset here
207
      my $commands = makeReplicateCommands($sourceList, $statusList, $newStatus );
211
      my $commands = makeReplicateCommands( $config->{datasets}->{$dataset}->{source}, $sourceList, $statusList, $newStatus );
208
      if ( %$commands ) {
212
      if ( %$commands ) {
209
         foreach my $cmd ( keys %$commands ) {
213
         foreach my $cmd ( keys %$commands ) {
210
            my $command = $commands->{$cmd};
214
            my $command = $commands->{$cmd};
211
            $command .= " | openssl enc -aes-256-cbc -K $config->{transport}->{encryption}->{key} -iv $config->{transport}->{encryption}->{IV} " if $config->{transport}->{encryption}->{key};
215
            $command .= " | openssl enc -aes-256-cbc -K $config->{transport}->{encryption}->{key} -iv $config->{transport}->{encryption}->{IV} " if $config->{transport}->{encryption}->{key};
212
            $command .= " > $config->{transport}->{mount_point}/" . replaceSlashWithDot($cmd);
216
            $command .= " > $config->{transport}->{mount_point}/" . replaceSlashWithDot($cmd);
Line 218... Line 222...
218
      }
222
      }
219
   }
223
   }
220
   return $newStatus;
224
   return $newStatus;
221
}
225
}
222
 
226
 
-
 
227
# perform cleanup actions
-
 
228
# $config - configuration hashref
223
# clean all files from a directory, but not any subdirectories
229
# $message - optional message to include in the report
-
 
230
#
224
sub cleanDirectory {
231
sub cleanup{
225
   my $dirname = shift;
232
   my ( $config, $message ) = @_;
-
 
233
   # add disk space utilization information on transport to the log
-
 
234
   logMsg( "Disk space utilization on transport disk:\n" . runCmd( "df -h $config->{transport}->{mount_point}" ) . "\n" );
226
   logMsg( "Cleaning up $dirname of all files" );
235
   # add information about the server (zpools) to the log
-
 
236
   my $servername = `hostname -s`;
227
   # clean up a directory
237
   chomp $servername;
228
   opendir( my $dh, $dirname ) || fatalError( "Can not open $dirname: #!" );
238
   logMsg( "Zpools on server $servername:\n" . join( "\n", runCmd( "zpool list" ) ) . "\n" );
229
   # get all file names, but leave directories alone
239
   $config->{$config->{runningAs}}->{report}->{subject} //= "Replication Report for $config->{runningAs} server $servername";
230
   my @files = map{ $dirname . "/$_" } grep { -f "$dirname/$_" } readdir($dh);
240
   $message //= "Replication completed on $config->{runningAs} server $servername.";
231
   closedir $dh;
241
   # unmount the sneakernet drive
232
   foreach my $file (@files) {
242
   unmountDriveByLabel( $config->{transport} );
-
 
243
   sendReport( $config->{$config->{runningAs}}->{report}, $message, $config->{log_file} );
-
 
244
   # If they have requested shutdown, do it now
-
 
245
   if ( $config->{$config->{runningAs}}->{shutdown_after_replication} ) {
-
 
246
      logMsg( "Shutting down target server as per configuration" );
233
      unlink $file or warn "Could not unlink $file: #!\n";
247
      runCmd( "shutdown -p now" ) unless $DEBUG;
234
   }
248
   }
235
 }
-
 
236
    
-
 
237
 
-
 
238
 
-
 
239
# how to handle a fatal error
-
 
240
sub fatalError {
-
 
241
   my $message = shift;
-
 
242
   logMsg( $message );
-
 
243
   die;
-
 
244
}
249
}
245
 
250
 
-
 
251
# update the target datasets from the files on the transport drive
-
 
252
sub updateTarget {
-
 
253
   my $config = shift;
-
 
254
   my $files = getDirectoryList( $config->{transport}->{mount_point});
-
 
255
   foreach my $filename ( @$files ) {
-
 
256
     my $command = "cat $config->{output} | openssl enc -aes-256-cbc -d -K $config->{key} -iv $config->{IV}";
-
 
257
   }
-
 
258
}
246
 
259
 
247
##################### main program starts here #####################
260
##################### main program starts here #####################
248
# Example to create a random key for encryption/decryption:
261
# Example to create a random key for encryption/decryption:
249
# generate a random key with
262
# generate a random key with
250
# openssl rand 32 | xxd -p | tr -d '\n' > test.key
263
# openssl rand 32 | xxd -p | tr -d '\n' > test.key
251
 
264
 
252
# If a YAML config file exists next to the script, load and merge it
265
# If a YAML config file exists next to the script, load and merge it
253
$config = loadConfig($configFileName, $config );
266
$config = loadConfig($configFileName, $config );
-
 
267
exit 1 unless keys %$config;
254
 
268
 
255
# set some defaults
269
# set some defaults
256
$config->{'status_file'} //= "$scriptFullPath.status";
270
$config->{'status_file'} //= "$scriptFullPath.status";
257
# set log file name for sub logMsg in ZFS_Utils, and remove the old log if it exists
271
# set log file name for sub logMsg in ZFS_Utils, and remove the old log if it exists
258
# Log file is only valid for one run
272
# Log file is only valid for one run
259
$logFileName = $config->{'log_file'} //= "$scriptFullPath.log";
273
$logFileName = $config->{'log_file'} //= "$scriptFullPath.log";
260
# log only for one run
274
# log only for one run
261
unlink ( $logFileName ) if -f $logFileName;
275
unlink ( $logFileName ) if -f $logFileName;
262
 
276
 
263
fatalError( "Invalid config file: missing source and/or target server" )
277
fatalError( "Invalid config file: missing source and/or target server", $config, \&cleanup )
264
    unless (defined $config->{source} && defined $config->{target});
278
    unless (defined $config->{source} && defined $config->{target});
265
 
279
 
266
# mount the transport drive, fatal error if we can not find it
-
 
267
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}" )
-
 
268
   unless $config->{transport}->{mount_point} =  mountDriveByLabel( $config->{transport} );
-
 
269
 
-
 
270
my $servername = `hostname -s`;
280
my $servername = `hostname -s`;
271
chomp $servername;
281
chomp $servername;
272
my $runningAs = $servername eq $config->{source}->{hostname} ? 'source' :
282
$config->{runningAs} = $servername eq $config->{source}->{hostname} ? 'source' :
273
                $servername eq $config->{target}->{hostname} ? 'target' : 'unknown';
283
                $servername eq $config->{target}->{hostname} ? 'target' : 'unknown';
274
 
284
 
-
 
285
#cleanup( $config, "Testing" );
-
 
286
 
-
 
287
# mount the transport drive, fatal error if we can not find it
-
 
288
fatalError( "Unable to mount tranport drive with label $config->{transport}->{disk_label}", $config, \&cleanup )
-
 
289
   unless $config->{transport}->{mount_point} =  mountDriveByLabel( $config->{transport} );
-
 
290
 
-
 
291
# mail program logic
275
if ( $runningAs eq 'source' ) {
292
if ( $config->{runningAs} eq 'source' ) {
276
    logMsg "Running as source server";
293
    logMsg "Running as source server";
277
    # remove all files from transport disk, but leave all subdirectories alone
294
    # remove all files from transport disk, but leave all subdirectories alone
-
 
295
   fatalError( "Failed to clean transport directory $config->{transport}->{mount_point}", $config, \&cleanup )
278
    cleanDirectory( $config->{transport}->{mount_point} );
296
      unless cleanDirectory( $config->{transport}->{mount_point} );
279
    my $statusList = getStatusFile($config->{status_file});
297
    my $statusList = getStatusFile($config->{status_file});
280
    $statusList = doSourceReplication($config, $statusList); 
298
    $statusList = doSourceReplication($config, $statusList); 
281
    writeStatusFile($config->{status_file}, $statusList);
299
    writeStatusFile($config->{status_file}, $statusList);
282
} elsif ( $runningAs eq 'target' ) {
300
} elsif ( $config->{runningAs} eq 'target' ) {
283
    logMsg "Running as target server";
301
    logMsg "Running as target server";
284
    mountGeli( $config->{target}->{geli} ) if ( defined $config->{target}->{geli} );
302
    mountGeli( $config->{target}->{geli} ) if ( defined $config->{target}->{geli} );
-
 
303
    updateTarget( $config );
285
} else {
304
} else {
286
    fatalError( "This server ($servername) is neither source nor target server as per config\n" );
305
    fatalError( "This server ($servername) is neither source nor target server as per config\n" );
287
}
306
}
288
 
307
 
289
# add disk space utilization information on transport to the log
-
 
290
logMsg( "Disk space utilization on transport disk:\n" . runCmd( "df -h $config->{transport}->{mount_point}" ) . "\n" );
-
 
291
# add information about the server (zpools) to the log
-
 
292
logMsg( "Zpools on server $servername:\n" . join( "\n", runCmd( "zpool list" ) ) . "\n" );
-
 
293
 
-
 
294
# unmount the sneakernet drive
-
 
295
`umount $config->{transport}->{mount_point}`;
-
 
296
# and remove the directory
308
cleanup( $config );
297
rmdir $config->{transport}->{mount_point};
-
 
298
 
-
 
299
 
309
 
300
1;
310
1;
301
 
-
 
302
 
-
 
303
#`cat $config->{input} | openssl enc -aes-256-cbc -K $config->{key} -iv $config->{IV} > $config->{output}`;
-
 
304
 
-
 
305
# this will decrypt $config->{output} to stdout
-
 
306
#`cat $config->{output} | openssl enc -aes-256-cbc -d -K $config->{key} -iv $config->{IV} > test.out`;
-