Changeset 101

Show
Ignore:
Timestamp:
04/07/10 21:50:31 (8 years ago)
Author:
depesz
Message:

restore is nearly done. only removal of old files is left to be added

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/omnipitr/doc/omnipitr-restore.pod

    r91 r101  
    6060 
    6161Passed segment will always be uncompressed. 
     62 
     63=item --temp-dir (-t) 
     64 
     65Where to create temporary files (defaults to /tmp or I<$TMPDIR> environment 
     66variable location). This is only used when using pre-removal-processing. 
    6267 
    6368=item --log (-l) 
     
    7984Log verbosely what is happening. 
    8085 
     86=item --gzip-path (-gp) 
     87 
     88Full path to gzip program - in case you can't set proper PATH environment 
     89variable. 
     90 
     91=item --bzip2-path (-bp) 
     92 
     93Full path to bzip2 program - in case you can't set proper PATH environment 
     94variable. 
     95 
     96=item --lzma-path (-lp) 
     97 
     98Full path to lzma program - in case you can't set proper PATH environment 
     99variable. 
     100 
     101=item --pgcontroldata-path (-pp) 
     102 
     103Full path to pg_controldata program - in case you can't set proper PATH 
     104environment variable. 
     105 
    81106=back 
    82107 
     
    234259recovery procedure. 
    235260 
    236 2 signals are supported: 
     261Only 1 signals are supported: 
    237262 
    238263=over 
     
    240265=item * SIGUSR1 
    241266 
    242 makes the finish I<smart> 
    243  
    244 =item * SIGUSR2 
    245  
    246267makes the finish I<immediate> 
    247268 
    248269=back 
     270 
     271It is currently not possible to forcing 'smart' finishing by signal, due to 
     272the fact that L<omnipitr-restore> is restarted after every segment. 
    249273 
    250274=back 
  • trunk/omnipitr/lib/OmniPITR/Program/Restore.pm

    r86 r101  
    33use warnings; 
    44 
     5use base qw( OmniPITR::Program ); 
     6 
     7use Carp; 
     8use OmniPITR::Tools qw( :all ); 
     9use English qw( -no_match_vars ); 
     10use File::Basename; 
     11use File::Spec; 
     12use File::Path qw( make_path remove_tree ); 
     13use File::Copy; 
     14use Storable; 
     15use Getopt::Long; 
     16 
     17=head1 run() 
     18 
     19Main function, called by actual script in bin/, wraps all work done by script with the sole exception of reading and validating command line arguments. 
     20 
     21These tasks (reading and validating arguments) are in this module, but they are called from L<OmniPITR::Program::new()> 
     22 
     23Name of called method should be self explanatory, and if you need further information - simply check doc for the method you have questions about. 
     24 
     25=cut 
     26 
     27sub run { 
     28    my $self = shift; 
     29 
     30    $SIG{ 'USR1' } = sub { 
     31        $self->{ 'finish' } = 'immediate'; 
     32        return; 
     33    }; 
     34 
     35    while ( 1 ) { 
     36        $self->try_to_restore_and_exit(); 
     37        sleep 1; 
     38        next if $self->{ 'finish' }; 
     39        $self->check_for_trigger_file(); 
     40        next if $self->{ 'finish' }; 
     41        $self->do_some_removal(); 
     42    } 
     43} 
     44 
     45sub do_some_removal { 
     46    my $self = shift; 
     47    return unless $self->{ 'remove-unneeded' }; 
     48    if ( $self->{ 'removal-pause-trigger' } ) { 
     49        return if -e $self->{ 'removal-pause-trigger' }; 
     50    } 
     51} 
     52 
     53sub try_to_restore_and_exit { 
     54    my $self = shift; 
     55 
     56    if ( $self->{ 'finish' } eq 'immediate' ) { 
     57        $self->log->error( 'Got immediate finish request. Dying.' ); 
     58        $self->exit_with_status( 1 ); 
     59    } 
     60 
     61    my $wanted_file = File::Spec->catfile( $self->{ 'source' }->{ 'path' }, $self->{ 'segment' } ); 
     62    $wanted_file .= ext_for_compression( $self->{ 'source' }->{ 'compression' } ) if $self->{ 'source' }->{ 'compression' }; 
     63 
     64    unless ( -e $wanted_file ) { 
     65        if ( $self->{ 'finish' } ) { 
     66            $self->log->error( 'Got finish request. Dying.' ); 
     67            $self->exit_with_status( 1 ); 
     68        } 
     69    } 
     70 
     71    if (   ( $self->{ 'recovery-delay' } ) 
     72        && ( !$self->{ 'finish' } ) ) 
     73    { 
     74        my @file_info  = stat( $wanted_file ); 
     75        my $file_mtime = $file_info[ 9 ]; 
     76        my $ok_since   = time - $self->{ 'recovery-delay' }; 
     77        if ( $ok_since > $file_mtime ) { 
     78            if ( $self->{ 'verbose' } ) { 
     79                unless ( $self->{ 'logged_delay' } ) { 
     80                    $self->log->log( 'Segment %s found, but it is too fresh (mtime = %u, accepted since %u)', $self->{ 'segment' }, $file_mtime, $ok_since ); 
     81                    $self->{ 'logged_delay' } = 1; 
     82                } 
     83            } 
     84            return; 
     85        } 
     86    } 
     87 
     88    my $full_destination = File::Spec->catfile( $self->{ 'data-dir' }, $self->{ 'segment_destination' } ); 
     89 
     90    unless ( $self->{ 'source' }->{ 'compression' } ) { 
     91        if ( copy( $wanted_file, $full_destination ) ) { 
     92            $self->log->log( 'Segment %s restored, from non-compressed source.', $self->{ 'segment' } ); 
     93            $self->exit_with_status( 0 ); 
     94        } 
     95        $self->log->error( 'Segment %s restoration failed: %s.', $self->{ 'segment' }, $OS_ERROR ); 
     96        $self->exit_with_status( 1 ); 
     97    } 
     98 
     99    my $compression = $self->{ 'source' }->{ 'compression' }; 
     100    my $command = sprintf '%s --decompress --stdout %s > %s', quotemeta( $self->{ "$compression-path" } ), quotemeta( $wanted_file ), quotemeta( $full_destination ); 
     101 
     102    $self->prepare_temp_directory(); 
     103 
     104    my $response = run_command( $self->{ 'temp-dir' }, 'bash', '-c', $command ); 
     105 
     106    if ( $response->{ 'error_code' } ) { 
     107        $self->log->error( 'Error while decompressing with %s : %s', $compression, Dumper( $response ) ); 
     108        $self->exit_with_status( 1 ); 
     109    } 
     110 
     111    $self->log->log( 'Segment %s restored, from %s compressed source.', $self->{ 'segment' }, $compression, ); 
     112    $self->exit_with_status( 0 ); 
     113} 
     114 
     115sub check_for_trigger_file { 
     116    my $self = shift; 
     117 
     118    return unless $self->{ 'finish-trigger' }; 
     119    return unless -e $self->{ 'finish-trigger' }; 
     120 
     121    if ( open my $fh, '<', $self->{ 'finish-trigger' } ) { 
     122        local $INPUT_RECORD_SEPARATOR = undef; 
     123        my $content = <$fh>; 
     124        close $fh; 
     125 
     126        $self->{ 'finish' } = $content =~ m{\ANOW\n?\z} ? 'immediate' : 'smart'; 
     127 
     128        $self->log->log( 'Finish trigger found, %s mode.', $self->{ 'finish' } ); 
     129        return; 
     130    } 
     131    $self->log->fatal( 'Finish trigger (%s) exists, but cannot be open?! : %s', $self->{ 'finish-trigger' }, $OS_ERROR ); 
     132} 
     133 
     134=head1 exit_with_status() 
     135 
     136Exit function, doing cleanup (remove temp-dir), and exiting with given status. 
     137 
     138=cut 
     139 
     140sub exit_with_status { 
     141    my $self = shift; 
     142    my $code = shift; 
     143 
     144    remove_tree( $self->{ 'temp-dir' } ) if $self->{ 'temp-dir-prepared' }; 
     145 
     146    exit( $code ); 
     147} 
     148 
     149=head1 prepare_temp_directory() 
     150 
     151Helper function, which builds path for temp directory, and creates it. 
     152 
     153Path is generated by using given temp-dir and 'omnipitr-restore' name. 
     154 
     155For example, for temp-dir '/tmp', actual, used temp directory would be /tmp/omnipitr-restore/. 
     156 
     157=cut 
     158 
     159sub prepare_temp_directory { 
     160    my $self = shift; 
     161    return if $self->{ 'temp-dir-prepared' }; 
     162    my $full_temp_dir = File::Spec->catfile( $self->{ 'temp-dir' }, basename( $PROGRAM_NAME ) ); 
     163    make_path( $full_temp_dir ); 
     164    $self->{ 'temp-dir' }          = $full_temp_dir; 
     165    $self->{ 'temp-dir-prepared' } = 1; 
     166    return; 
     167} 
     168 
     169=head1 read_args() 
     170 
     171Function which does all the parsing, and transformation of command line arguments. 
     172 
     173It also verified base facts about passed WAL segment name, but all other validations, are being done in separate function: L<validate_args()>. 
     174 
     175=cut 
     176 
     177sub read_args { 
     178    my $self = shift; 
     179 
     180    my @argv_copy = @ARGV; 
     181 
     182    my %args = ( 
     183        'bzip2-path'         => 'bzip2', 
     184        'data-dir'           => '.', 
     185        'gzip-path'          => 'gzip', 
     186        'lzma-path'          => 'lzma', 
     187        'pgcontroldata-path' => 'pg_controldata', 
     188        'temp-dir'           => $ENV{ 'TMPDIR' } || '/tmp', 
     189    ); 
     190 
     191    croak( 'Error while reading command line arguments. Please check documentation in doc/omnipitr-archive.pod' ) 
     192        unless GetOptions( 
     193        \%args, 
     194        'bzip2-path|bp=s', 
     195        'data-dir|D=s', 
     196        'finish-trigger|f=s', 
     197        'gzip-path|gp=s', 
     198        'log|l=s', 
     199        'lzma-path|lp=s', 
     200        'pgcontroldata-path|pp=s', 
     201        'pid-file=s', 
     202        'pre-removal-processing|h=s', 
     203        'recovery-delay|w=i', 
     204        'removal-pause-trigger|p=s', 
     205        'remove-unneeded|r=s', 
     206        'source|s=s', 
     207        'temp-dir|t=s', 
     208        'verbose|v', 
     209        ); 
     210 
     211    croak( '--log was not provided - cannot continue.' ) unless $args{ 'log' }; 
     212    $args{ 'log' } =~ tr/^/%/; 
     213 
     214    for my $key ( keys %args ) { 
     215        next if $key =~ m{ \A (?: source | log ) \z }x;    # Skip those, not needed in $self 
     216        $self->{ $key } = $args{ $key }; 
     217    } 
     218 
     219    # We do it here so it will actually work for reporing problems in validation 
     220    $self->{ 'log_template' } = $args{ 'log' }; 
     221    $self->{ 'log' }          = OmniPITR::Log->new( $self->{ 'log_template' } ); 
     222 
     223    $self->log->fatal( 'Source path not provided!' ) unless $args{ 'source' }; 
     224 
     225    if ( $args{ 'source' } =~ s/\A(gzip|bzip2|lzma)=// ) { 
     226        $self->{ 'source' }->{ 'compression' } = $1; 
     227    } 
     228    $self->{ 'source' }->{ 'path' } = $args{ 'source' }; 
     229 
     230    # These could theoretically go into validation, but we need to check if we can get anything to put in segment* keys in $self 
     231    $self->log->fatal( 'WAL segment file name and/or destination have not been given' ) if 2 > scalar @ARGV; 
     232    $self->log->fatal( 'Too many arguments given.' ) if 2 < scalar @ARGV; 
     233 
     234    @{ $self }{ qw( segment segment_destination ) } = @ARGV; 
     235 
     236    $self->log->log( 'Called with parameters: %s', join( ' ', @argv_copy ) ) if $self->{ 'verbose' }; 
     237 
     238    $self->{ 'finish' } = ''; 
     239 
     240    return; 
     241} 
     242 
     243=head1 validate_args() 
     244 
     245Does all necessary validation of given command line arguments. 
     246 
     247One exception is for compression programs paths - technically, it could be validated in here, but benefit would be pretty limited, and code to do so relatively complex, as compression program path 
     248might, but doesn't have to be actual file path - it might be just program name (without path), which is the default. 
     249 
     250=cut 
     251 
     252sub validate_args { 
     253    my $self = shift; 
     254 
     255    $self->log->fatal( 'Given data-dir (%s) is not valid', $self->{ 'data-dir' } ) unless -d $self->{ 'data-dir' } && -f File::Spec->catfile( $self->{ 'data-dir' }, 'PG_VERSION' ); 
     256 
     257    $self->log->fatal( 'Given segment name is not valid (%s)', $self->{ 'segment' } ) unless $self->{ 'segment' } =~ m{\A[a-f0-9]{24}\z}; 
     258 
     259    $self->log->fatal( 'Given source (%s) is not a directory', $self->{ 'source' }->{ 'path' } ) unless -d $self->{ 'source' }->{ 'path' }; 
     260    $self->log->fatal( 'Given source (%s) is not readable',    $self->{ 'source' }->{ 'path' } ) unless -r $self->{ 'source' }->{ 'path' }; 
     261    $self->log->fatal( 'Given source (%s) is not writable',    $self->{ 'source' }->{ 'path' } ) unless -w $self->{ 'source' }->{ 'path' }; 
     262 
     263    return; 
     264} 
     265 
    52661; 
    6