root/trunk/omnipitr/lib/OmniPITR/Program/Archive.pm

Revision 86, 6.5 kB (checked in by depesz, 8 years ago)

Reorganization, to be able to factor out some common code

Line 
1 package OmniPITR::Program::Archive;
2 use strict;
3 use warnings;
4 use base qw( OmniPITR::Program );
5 use Carp;
6 use English qw( -no_match_vars );
7 use File::Basename;
8 use File::Spec;
9 use File::Path qw( make_path );
10 use File::Copy;
11 use Storable;
12 use Getopt::Long;
13 use Data::Dumper;
14 use Digest::MD5;
15
16 sub run {
17     my $self = shift;
18     $self->read_state();
19     $self->prepare_temp_directory();
20     $self->copy_segment_to_temp_dir();
21 }
22
23 sub copy_segment_to_temp_dir {
24     my $self = shift;
25     return if $self->segment_already_copied();
26     my $new_file = $self->get_temp_filename_for( 'none' );
27     unless ( copy( $self->{'segment'}, $new_file ) ) {
28         $self->log->fatal('Cannot copy %s to %s : %s', $self->{'segment'}, $new_file, $OS_ERROR );
29     }
30     my $has_md5 = $self->md5sum( $new_file );
31     $self->{'state'}->{'compressed'}->{'none'} = $has_md5;
32     $self->save_state();
33     return;
34 }
35
36 sub segment_already_copied {
37     my $self = shift;
38     return unless $self->{ 'state' }->{ 'compressed' }->{ 'none' };
39     my $want_md5 = $self->{ 'state' }->{ 'compressed' }->{ 'none' };
40
41     my $temp_file_name = $self->get_temp_filename_for( 'none' );
42     return unless -e $temp_file_name;
43
44     my $has_md5 = $self->md5sum( $temp_file_name );
45     if ( $has_md5 eq $want_md5 ) {
46         $self->log->log('Segment has been already copied to temp location.');
47         return 1;
48     }
49
50     unlink $temp_file_name;
51     $self->log->error( 'Segment already copied, but with bad MD5 ?!' );
52
53     return;
54 }
55
56 sub get_temp_filename_for {
57     my $self = shift;
58     my $type = shift;
59
60     return File::Spec->catfile( $self->{ 'temp-dir' }, $type );
61 }
62
63 sub md5sum {
64     my $self     = shift;
65     my $filename = shift;
66
67     my $ctx = Digest::MD5->new;
68
69     open my $fh, '<', $filename or $self->log->fatal( 'Cannot open file for md5summing %s : %s', $filename, $OS_ERROR );
70     $ctx->addfile( $fh );
71     my $md5 = $ctx->hexdigest();
72     close $fh;
73
74     return $md5;
75 }
76
77 sub prepare_temp_directory {
78     my $self = shift;
79     my $full_temp_dir = File::Spec->catfile( $self->{ 'temp-dir' }, basename( $PROGRAM_NAME ), basename( $self->{ 'segment' } ) );
80     make_path( $full_temp_dir );
81     $self->{ 'temp-dir' } = $full_temp_dir;
82     return;
83 }
84
85 sub read_state {
86     my $self = shift;
87     $self->{ 'state' } = {};
88
89     return unless $self->{ 'state-dir' };
90
91     $self->{ 'state-file' } = File::Spec->catfile( $self->{ 'state-dir' }, basename( $self->{ 'segment' } ) );
92     return unless -f $self->{ 'state-file' };
93     $self->{ 'state' } = retrieve( $self->{ 'state-file' } );
94     return;
95 }
96
97 sub save_state {
98     my $self = shift;
99
100     return unless $self->{ 'state-file' };
101
102     store( $self->{ 'state' }, $self->{ 'state-file' } );
103
104     return;
105 }
106
107 sub read_args {
108     my $self = shift;
109
110     my @argv_copy = @ARGV;
111
112     my %args = (
113         'data-dir' => '.',
114         'temp-dir' => $ENV{ 'TMPDIR' } || '/tmp',
115     );
116
117     croak( 'Error while reading command line arguments. Please check documentation in doc/omnipitr-archive.pod' )
118         unless GetOptions(
119         \%args,
120         'data-dir|D=s',
121         'dst-local|dl=s@',
122         'dst-remote|dr=s@',
123         'temp-dir|t=s',
124         'log|l=s',
125         'state-dir|s=s',
126         'pid-file=s',
127         'verbose|v'
128         );
129
130     croak( '--log was not provided - cannot continue.' ) unless $args{ 'log' };
131
132     for my $key ( qw( data-dir temp-dir state-dir pid-file verbose ) ) {
133         $self->{ $key } = $args{ $key };
134     }
135
136     for my $type ( qw( local remote ) ) {
137         my $D = [];
138         $self->{ 'destination' }->{ $type } = $D;
139
140         next unless defined $args{ 'dst-' . $type };
141
142         my %temp_for_uniq = ();
143         my @items = grep { !$temp_for_uniq{ $_ }++ } @{ $args{ 'dst-' . $type } };
144
145         for my $item ( @items ) {
146             my $current = { 'compression' => 'none', };
147             if ( $item =~ s/\A(gzip|bzip2|lzma)\%// ) {
148                 $current->{ 'compression' } = $1;
149             }
150             $current->{ 'path' } = $item;
151             push @{ $D }, $current;
152         }
153     }
154
155     # We do it here so it will actually work for reporing problems in validation
156     $self->{ 'log_template' } = $args{ 'log' };
157     $self->{ 'log' }          = OmniPITR::Log->new( $self->{ 'log_template' } );
158
159     # These could theoretically go into validation, but we need to check if we can get anything to {'segment'}
160     $self->log->fatal( 'WAL segment file name has not been given' ) if 0 == scalar @ARGV;
161     $self->log->fatal( 'More than 1 WAL segment file name has been given' ) if 1 < scalar @ARGV;
162
163     $self->{ 'segment' } = shift @ARGV;
164
165     $self->log->log( 'Called with parameters: %s', join( ' ', @argv_copy ) ) if $self->{ 'verbose' };
166
167     return;
168 }
169
170 sub validate_args {
171     my $self = shift;
172
173     $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' );
174
175     my $dst_count = scalar( @{ $self->{ 'destination' }->{ 'local' } } ) + scalar( @{ $self->{ 'destination' }->{ 'remote' } } );
176     $self->log->fatal( "No --dst-* has been provided!" ) if 0 == $dst_count;
177
178     if ( 1 < $dst_count ) {
179         $self->log->fatal( "More than 1 --dst-* has been provided, but no --state-dir!" ) if !$self->{ 'state-dir' };
180         $self->log->fatal( "Given --state-dir (%s) does not exist",     $self->{ 'state-dir' } ) unless -e $self->{ 'state-dir' };
181         $self->log->fatal( "Given --state-dir (%s) is not a directory", $self->{ 'state-dir' } ) unless -d $self->{ 'state-dir' };
182         $self->log->fatal( "Given --state-dir (%s) is not writable",    $self->{ 'state-dir' } ) unless -w $self->{ 'state-dir' };
183     }
184
185     $self->log->fatal( 'Given segment name is not valid (%s)', $self->{ 'segment' } ) unless basename( $self->{ 'segment' } ) =~ m{\A[a-f0-9]{24}\z};
186     my $segment_file_name = $self->{ 'segment' };
187     $segment_file_name = File::Spec->catfile( $self->{ 'data-dir' }, $self->{ 'segment' } ) unless $self->{ 'segment' } =~ m{^/};
188
189     $self->log->fatal( 'Given segment (%s) does not exist.',  $segment_file_name ) unless -e $segment_file_name;
190     $self->log->fatal( 'Given segment (%s) is not a file.',   $segment_file_name ) unless -f $segment_file_name;
191     $self->log->fatal( 'Given segment (%s) is not readable.', $segment_file_name ) unless -r $segment_file_name;
192
193     my $expected_size = 256**3;
194     my $file_size     = ( -s $segment_file_name );
195     $self->log->fatal( 'Given segment (%s) has incorrect size (%u vs %u).', $segment_file_name, $file_size, $expected_size ) unless $expected_size == $file_size;
196
197     $self->{ 'segment' } = $segment_file_name;
198     return;
199 }
200
201 1;
Note: See TracBrowser for help on using the browser.