Subversion Repositories zfs_utils

Rev

Rev 7 | Rev 11 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
5
 
6
 
7
BEGIN {
8
   use FindBin;
9
   use File::Spec;
10
   # use libraries from the directory this script is in
11
   use Cwd 'abs_path';
12
   use File::Basename;
13
   use lib dirname( abs_path( __FILE__ ) );
14
}
15
 
16
use YAML::Tiny; # pkg install p5-YAML-Tiny-1.74
17
use Data::Dumper;
10 rodolico 18
#use Email::Simple; # cpan install Email::Simple
2 rodolico 19
 
20
my $cwd = $FindBin::RealBin;
21
my $configFileName = $cwd . '/sync.yaml';
22
my $replicateScript = $cwd . '/replicate';
23
 
24
my $configuration;
25
 
26
 
27
# load Configuration File
28
# read the config file and return it
29
sub readConfig {
30
   my $filename = shift;
31
   die "Config file $filename not found: $!" unless -f $filename;
32
   my $yaml = YAML::Tiny->new( {} );
33
   if ( -f $filename ) {
34
      $yaml = YAML::Tiny->read( $filename );
35
   }
36
   return $yaml->[0];
37
}
38
 
10 rodolico 39
sub logit {
40
   open LOG, ">>/tmp/replicate.log" or die "Could not open replicate.log: $!\n";
41
   print LOG join( "\n", @_ ) . "\n";
42
   close LOG;
43
}
2 rodolico 44
 
45
 
46
# this calls gshred which will overwrite the file 3 times, then
47
# remove it.
48
# NOTE: this will not work on ZFS, since ZFS is CopyOnWrite (COW)
49
# so assuming /tmp is a ramdisk
50
sub shredFile {
51
   my $filename = shift;
52
   `/usr/local/bin/gshred -u -f -s 32 $filename`;
53
}
54
 
55
 
56
# runs a command, redirecting stderr to stdout (which it ignores)
57
# then returns 0 on success.
58
# if error, returns string describing error
59
sub runCommand {
60
   my $command = shift;
10 rodolico 61
   #logit( $command );
2 rodolico 62
   my $output = qx/$command 2>&1/;
63
   if ($? == -1) {
64
      return (-1, "failed to execute: $!" );
65
   } elsif ($? & 127) {
66
      return (-1,sprintf "child died with signal %d, %s coredump",
67
        ($? & 127),  ($? & 128) ? 'with' : 'without');
68
   } else {
69
      return ( $?>>8, sprintf "child exited with value %d", $? >> 8 ) if $? >> 8;
70
   }
71
   return (0, $output);
72
}
73
 
74
# grabs the encryption key from the remote server, and uses it to unlock the 
75
# datasets, then mount the drives.
76
# a return of '' is success, anything else is an error
77
sub mountDrives {
78
   my $configuration = shift;
79
   return (0, 'No encrypted target found' ) unless defined( $configuration->{'target'}->{'encryptionKeyPath'} ) && $configuration->{'target'}->{'encryptionKeyPath'};
80
   # try to grab the file from the remote machine
81
   &runCommand( "scp $configuration->{remoteMachine}->{ip}:$configuration->{remoteMachine}->{encryptionKeyPath} $configuration->{localMachine}->{encryptionKeyPath}" );
82
   # If we do not have the encryption key, we need to abort
83
   return "Could not copy file $configuration->{remoteMachine}->{ip}:$configuration->{remoteMachine}->{encryptionKeyPath}, aborting" 
84
      unless -f $configuration->{'target'}->{'encryptionKeyPath'};
85
   my $error = '';
86
   my $output = '';
87
   # load the key into zfs and unlock all volumes
88
   ($error,$output) = &runCommand( "zfs load-key -a" );
89
   # finally, remount all of the zfs shares which need the key
90
   ($error,$output) = &runCommand( "zfs mount -a" ) unless $error;
91
   # if we succeeded, we want to shred the keyfile
92
   &shredFile( $configuration->{localMachine}->{encryptionKeyPath} ) if -f $configuration->{localMachine}->{encryptionKeyPath};
93
   return $error;
94
}
95
 
10 rodolico 96
# a very simple mailer, just send information to sendmail
2 rodolico 97
sub sendMail {
98
   my ($message, $configuration, $subject ) = @_;
10 rodolico 99
   if ( $message ) {
100
      open MAIL,"|sendmail -t" or die "Could not open sendmail: $!\n";
101
      print MAIL "To: $configuration->{email}->{notify}\n";
102
      print MAIL "From: $configuration->{email}->{from}\n";
103
      print MAIL "Subject: " . 
104
                 ($configuration->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ) ) .
105
                 "\n\n";
106
      print MAIL $message;
107
      close MAIL;
2 rodolico 108
   } else {
10 rodolico 109
      warn "no message in outgoing email\n";
2 rodolico 110
   }
10 rodolico 111
#   $message =~ s/\(/\\(/g;
112
#   $message =~ s/\)/\\)/g;
113
#   $configuration->{'email'}->{'notify'} = 'root' unless $configuration->{'email'}->{'notify'};
114
#   die "No message in outgoing message\n" unless $message;
115
#   my $email = Email::Simple->create(
116
#      header => [
117
#         To     => $configuration->{'email'}->{'notify'},
118
#         Subject=> $configuration->{'email'}->{'subject'} . ( $subject ? " - $subject" : '' ),
119
#         From   => $configuration->{'email'}->{'from'}
120
#      ],
121
#      body => $message
122
#   );
123
#   $message = $email->as_string;
124
#   #warn "Message is\n$message\n\n";
125
#   if ( $configuration->{'testing'} > 1 ) {
126
#      print "$message\n";
127
#   } else {
128
#      `echo '$message' | sendmail $configuration->{'email'}->{'notify'}`;
129
#   }
2 rodolico 130
}
131
 
132
# checks to see if we should be in maintenance mode
133
# if $remoteMachine->{'maintenanceMode'} exists, set mode
134
# otherwise, wait localMachine->{'waittime'} minutes, then check
135
# $localMachine->{'maintenanceMode'}.
136
# if neither exists, begin sync
137
sub checkMaintenance {
138
   my $configuration = shift;
139
   return 0 unless # exit if maintenanceFlag has not been set at all
140
     ( defined( $configuration->{'target'}->{'maintenanceFlag'} ) && $configuration->{'target'}->{'maintenanceFlag'} ) ||
141
     ( defined( $configuration->{'source'}->{'maintenanceFlag'} ) && $configuration->{'source'}->{'maintenanceFlag'} );
142
   # see if maintenance is set on remote. If so, simply return the message
143
   if ( $configuration->{'source'}->{'up'} ) {
144
      my ($error, $output) = &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'ls $configuration->{remoteMachine}->{maintenanceFlag}'" );
145
      if ( ! $error ) {
146
         # remove the file from the remote server
147
         &runCommand( "ssh $configuration->{remoteMachine}->{ip} 'rm $configuration->{remoteMachine}->{maintenanceFlag}'" );
148
         # create a valid return, which will exit the program
149
         return "Maintenance Flag found on remote machine";
150
      }
151
   }
152
   # not on remote machine, so give them waitTime seconds to put it here
153
   # we'll loop, checking every $sleepTime seconds until our wait time
154
   # ($configuration->{'target'}->{'waitTime'}) has expired
155
   my $sleepTime = 60;
156
   for ( my $i = $configuration->{'target'}->{'waitTime'}; $i > 0; $i -= $sleepTime ) {
157
      sleep $sleepTime;
158
      # then look for the maintenance flag file on the local machine
159
      return "Maintenance Flag found on local machine" if -f $configuration->{'target'}->{'maintenanceFlag'};
160
   }
161
   # no maintenance flags found, so return false
162
   return 0;
163
}
164
 
165
sub shutdownMachine {
166
   my $configuration = shift;
10 rodolico 167
   exit unless $configuration->{'shutdown'};
2 rodolico 168
   # do not actually shut down the server unless we are told to
169
   &runCommand( "poweroff" ) unless $configuration->{'testing'};
170
}
171
 
172
# returns the current time as a string
173
sub currentTime {
174
   my $format = shift;
175
   # default to YY-MM-DD HH-MM-SS
176
   $format = '%Y-%m-%d %H-%M-%S' unless $format;
177
   use POSIX;
178
   return POSIX::strftime( $format, localtime() );
179
}
180
 
7 rodolico 181
# verify a remote machine is up and running
2 rodolico 182
sub checkRemoteUp {
183
   my $configuration = shift;
184
   my $ip;
185
   if ( defined( $configuration->{'target'}->{'server'} ) && $configuration->{'target'}->{'server'} ) {
186
      $ip = $configuration->{'target'}->{'server'};
187
   } else {
188
      $ip = $configuration->{'source'}->{'server'};
189
   }
190
   my ($error, $message ) =  $ip ? &runCommand( "ping -c 1 -t 5 $ip" ) : (0,'No address defined for either target or server' );
7 rodolico 191
#   $message = "Checking IP $ip\n"  . $message;
2 rodolico 192
   #die "error is $error, message is $message for $ip\n";
193
   return ($error, $message);
194
}
195
 
196
my @status;   
197
my $error = 0;
198
my $output = '';
199
 
200
$configuration = &readConfig($configFileName);
201
 
202
# die Dumper( $configuration ) . "\n";
203
 
204
my $servername = `hostname`;
205
chomp $servername;
206
 
7 rodolico 207
push @status, "Replication on $servername has been started at " . &currentTime();
2 rodolico 208
&sendMail( "Replication on $servername has been started, " . &currentTime(), $configuration, "Replication on $servername started" );
209
 
210
# see if remote machine is up by sending one ping. Expect response in 5 seconds
211
( $error,$output) = &checkRemoteUp( $configuration );
212
$configuration->{'up'} = ! $error;
213
push @status, "remote machine is " . ( $configuration->{'up'} ? 'Up' : 'Down' ) . "\n";
10 rodolico 214
if ( ! $configuration->{'up'} ) {
215
   # we can not connect to the remote server, so just shut down
216
   sendMail( join( "\n", @status ), $configuration, "No connection to remote machine" );
217
   &shutdownMachine( $configuration );
218
}
2 rodolico 219
 
220
# check for maintenance flags, exit if we should go into mainteance mode
221
if ( my $result = &checkMaintenance( $configuration ) ) {
222
   push @status,$result;
223
   &sendMail( join( "\n", @status), $configuration, "Maintenance Mode" );
224
   die;
225
}
226
 
227
# try to mount the datasets if they are encrypted
228
($error,$output) = &mountDrives( $configuration );
229
if ( $error ) { # could not mount datasets
10 rodolico 230
   push @status, $output;
231
   &sendMail( join( "\n", @status ), $configuration, "Mount Drive Error: [$output]" );
232
   &shutdownMachine( $configuration );
2 rodolico 233
}
234
 
10 rodolico 235
#&sendMail( "Backup has been started at " . &currentTime(), $configuration, "Backup Starting" );
7 rodolico 236
push @status, &currentTime() . ' Backup started';
2 rodolico 237
 
238
$configuration->{'source'}->{'server'} = $configuration->{'source'}->{'server'} ? $configuration->{'source'}->{'server'} . ':' : '';
239
$configuration->{'target'}->{'server'} = $configuration->{'target'}->{'server'} ? $configuration->{'target'}->{'server'} . ':' : '';
240
 
7 rodolico 241
my @flags;
242
push @flags, '--dryrun' if $configuration->{'dryrun'};
243
push @flags, '--recurse' if $configuration->{'recurse'};
244
push @flags, '--verbose' if $configuration->{'verbose'};
245
push @flags, '--verbose' if $configuration->{'verbose'} > 1;
10 rodolico 246
push @flags, "--bwlimit=$configuration->{bandwidth}" if $configuration->{'bandwidth'};
7 rodolico 247
push @flags, "--filter='$configuration->{filter}'" if $configuration->{'filter'};
248
 
2 rodolico 249
# For each dataset, let's find the snapshots we need
250
foreach my $sourceDir ( keys %{$configuration->{'source'}->{'dataset'}} ) {
10 rodolico 251
   #print "Working on $sourceDir\n";
7 rodolico 252
   print "Looking for $sourceDir\n" if $configuration->{'testing'} > 2;
253
   print "syncing to $configuration->{target}->{dataset}\n" if $configuration->{'testing'} > 2;
254
   my $command = $replicateScript . ' ' . join( ' ', @flags ) . ' ' .
255
                 '--source=' .
2 rodolico 256
                 $configuration->{'source'}->{'server'} . 
257
                 $configuration->{'source'}->{'dataset'}->{$sourceDir} . '/' . $sourceDir . ' ' .
7 rodolico 258
                 '--target=' .
2 rodolico 259
                 $configuration->{'target'}->{'server'} . 
7 rodolico 260
                 $configuration->{'target'}->{'dataset'} . '/' . $sourceDir;
10 rodolico 261
   #print "Command is $command\n";
7 rodolico 262
   push @status, &currentTime() . " Running $command";
263
   if ( ! $configuration->{'testing'} ) {
264
      ($error, $output) = &runCommand( $command );
265
      push @status, $output;
266
   }
267
   push @status, &currentTime() . " Completed command, with status $error";
2 rodolico 268
}
269
 
10 rodolico 270
#print "Finished processing\n";
271
#print "testing is " . $configuration->{'testing'} . "\n";
272
 
7 rodolico 273
push @status, &currentTime() . ' Backup finished';
2 rodolico 274
 
275
if ($configuration->{'testing'}) {
276
   print join( "\n", @status ) . "\n";
277
} else {
10 rodolico 278
   #print "Sending final email\n";
279
   &sendMail( join( "\n", @status ), $configuration, "Backup Complete" );
280
   #print "Running shutdown\n";
281
   &shutdownMachine( $configuration ) if $configuration->{'shutdown'};
2 rodolico 282
}
283
 
284
1;