root/trunk/lib/Brackup/Target/Amazon.pm @ 167

Revision 167, 7.1 kB (checked in by mart, 23 months ago)

added the aws_location option to set the datacenter location for
S3 targets.

The Amazon S3 target now depends on version 0.41 of Net::Amazon::S3.

Patch from Alessandro Ranellucci.

Line 
1package Brackup::Target::Amazon;
2use strict;
3use warnings;
4use base 'Brackup::Target';
5use Net::Amazon::S3 0.41;
6
7# fields in object:
8#   s3  -- Net::Amazon::S3
9#   access_key_id
10#   sec_access_key_id
11#   prefix
12#   location
13#   chunk_bucket : $self->{prefix} . "-chunks";
14#   backup_bucket : $self->{prefix} . "-backups";
15#
16
17sub new {
18    my ($class, $confsec) = @_;
19    my $self = $class->SUPER::new($confsec);
20
21    $self->{access_key_id}     = $confsec->value("aws_access_key_id")
22        or die "No 'aws_access_key_id'";
23    $self->{sec_access_key_id} = $confsec->value("aws_secret_access_key")
24        or die "No 'aws_secret_access_key'";
25    $self->{prefix} = $confsec->value("aws_prefix") || $self->{access_key_id};
26    $self->{location} = $confsec->value("aws_location") || undef;
27
28    $self->_common_s3_init;
29
30    my $s3      = $self->{s3};
31    my $buckets = $s3->buckets or die "Failed to get bucket list";
32
33    unless (grep { $_->{bucket} eq $self->{chunk_bucket} } @{ $buckets->{buckets} }) {
34        $s3->add_bucket({ bucket => $self->{chunk_bucket}, location_constraint => $self->{location} })
35            or die "Chunk bucket creation failed\n";
36    }
37
38    unless (grep { $_->{bucket} eq $self->{backup_bucket} } @{ $buckets->{buckets} }) {
39        $s3->add_bucket({ bucket => $self->{backup_bucket}, location_constraint => $self->{location} })
40            or die "Backup bucket creation failed\n";
41    }
42
43    return $self;
44}
45
46sub _common_s3_init {
47    my $self = shift;
48    $self->{chunk_bucket}  = $self->{prefix} . "-chunks";
49    $self->{backup_bucket} = $self->{prefix} . "-backups";
50    $self->{s3}            = Net::Amazon::S3->new({
51        aws_access_key_id     => $self->{access_key_id},
52        aws_secret_access_key => $self->{sec_access_key_id},
53    });
54}
55
56# ghetto
57sub _prompt {
58    my ($q) = @_;
59    print "$q";
60    my $ans = <STDIN>;
61    $ans =~ s/^\s+//;
62    $ans =~ s/\s+$//;
63    return $ans;
64}
65
66sub new_from_backup_header {
67    my ($class, $header) = @_;
68
69    my $accesskey     = ($ENV{'AWS_KEY'} || _prompt("Your Amazon AWS access key? "))
70        or die "Need your Amazon access key.\n";
71    my $sec_accesskey = ($ENV{'AWS_SEC_KEY'} || _prompt("Your Amazon AWS secret access key? "))
72        or die "Need your Amazon secret access key.\n";
73    my $prefix = ($ENV{'AWS_PREFIX'} || _prompt("Your Amazon AWS prefix? (Leave empty if none) "));
74
75    my $self = bless {}, $class;
76    $self->{access_key_id}     = $accesskey;
77    $self->{sec_access_key_id} = $sec_accesskey;
78    $self->{prefix}            = $prefix || $self->{access_key_id};
79    $self->_common_s3_init;
80    return $self;
81}
82
83sub has_chunk {
84    my ($self, $chunk) = @_;
85    my $dig = $chunk->backup_digest;   # "sha1:sdfsdf" format scalar
86
87    my $res = eval { $self->{s3}->head_key({ bucket => $self->{chunk_bucket}, key => $dig }); };
88    return 0 unless $res;
89    return 0 if $@ && $@ =~ /key not found/;
90    return 0 unless $res->{content_type} eq "x-danga/brackup-chunk";
91    return 1;
92}
93
94sub load_chunk {
95    my ($self, $dig) = @_;
96    my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
97
98    my $val = $bucket->get_key($dig)
99        or return 0;
100    return \ $val->{value};
101}
102
103sub store_chunk {
104    my ($self, $chunk) = @_;
105    my $dig = $chunk->backup_digest;
106    my $blen = $chunk->backup_length;
107    my $chunkref = $chunk->chunkref;
108
109    my $try = sub {
110        eval {
111            $self->{s3}->add_key({
112                bucket        => $self->{chunk_bucket},
113                key           => $dig,
114                value         => $$chunkref,
115                content_type  => 'x-danga/brackup-chunk',
116            });
117        };
118    };
119
120    my $rv;
121    my $n_fails = 0;
122    while (!$rv && $n_fails < 5) {
123        $rv = $try->();
124        last if $rv;
125
126        # transient failure?
127        $n_fails++;
128        warn "Error uploading chunk $chunk [$@]... will do retry \#$n_fails in 5 seconds ...\n";
129        sleep 5;
130    }
131    unless ($rv) {
132        warn "Error uploading chunk again: " . $self->{s3}->errstr . "\n";
133        return 0;
134    }
135    return 1;
136}
137
138sub delete_chunk {
139    my ($self, $dig) = @_;
140    my $bucket = $self->{s3}->bucket($self->{chunk_bucket});
141    return $bucket->delete_key($dig);
142}
143
144# returns a list of names of all chunks
145sub chunks {
146    my $self = shift;
147   
148    my $chunks = $self->{s3}->list_bucket_all({ bucket => $self->{chunk_bucket} });
149    return map { $_->{key} } @{ $chunks->{keys} };
150}
151
152sub store_backup_meta {
153    my ($self, $name, $file) = @_;
154
155    my $rv = eval { $self->{s3}->add_key({
156        bucket        => $self->{backup_bucket},
157        key           => $name,
158        value         => $file,
159        content_type  => 'x-danga/brackup-meta',
160    })};
161
162    return $rv;
163}
164
165sub backups {
166    my $self = shift;
167
168    my @ret;
169    my $backups = $self->{s3}->list_bucket_all({ bucket => $self->{backup_bucket} });
170    foreach my $backup (@{ $backups->{keys} }) {
171        push @ret, Brackup::TargetBackupStatInfo->new($self, $backup->{key},
172                                                      time => $backup->{last_modified},
173                                                      size => $backup->{size});
174    }
175    return @ret;
176}
177
178sub get_backup {
179    my $self = shift;
180    my ($name, $output_file) = @_;
181       
182    my $bucket = $self->{s3}->bucket($self->{backup_bucket});
183    my $val = $bucket->get_key($name)
184        or return 0;
185       
186        $output_file ||= "$name.brackup";
187    open(my $out, ">$output_file") or die "Failed to open $output_file: $!\n";
188    my $outv = syswrite($out, $val->{value});
189    die "download/write error" unless $outv == do { use bytes; length $val->{value} };
190    close $out;
191    return 1;
192}
193
194sub delete_backup {
195    my $self = shift;
196    my $name = shift;
197       
198    my $bucket = $self->{s3}->bucket($self->{backup_bucket});
199    return $bucket->delete_key($name);
200}
201
2021;
203
204=head1 NAME
205
206Brackup::Target::Amazon - backup to Amazon's S3 service
207
208=head1 EXAMPLE
209
210In your ~/.brackup.conf file:
211
212  [TARGET:amazon]
213  type = Amazon
214  aws_access_key_id  = ...
215  aws_secret_access_key =  ....
216  aws_prefix =  ....
217
218=head1 CONFIG OPTIONS
219
220All options may be omitted unless specified.
221
222=over
223
224=item B<type>
225
226I<(Mandatory.)> Must be "B<Amazon>".
227
228=item B<aws_access_key_id>
229
230I<(Mandatory.)> Your Amazon Web Services access key id.
231
232=item B<aws_secret_access_key>
233
234I<(Mandatory.)> Your Amazon Web Services secret password for the above access key.  (not your Amazon password)
235
236=item B<aws_prefix>
237
238If you want to setup multiple backup targets on a single Amazon account you can
239use different prefixes. This string is used to name the S3 buckets created by
240Brackup. If not specified it defaults to the AWS access key id.
241
242=item B<aws_location>
243
244Sets the location constraint of the new buckets. If left unspecified, the
245default S3 datacenter location will be used. Otherwise, you can set it
246to 'EU' for an AWS European data center - note that costs are different.
247This has only effect when your backup environment is initialized in S3 (i.e.
248when buckets are created). If you want to move an existing backup environment
249to another datacenter location, you have to delete its buckets before or create
250a new one by specifing a different I<aws_prefix>.
251
252=back
253
254=head1 SEE ALSO
255
256L<Brackup::Target>
257
258L<Net::Amazon::S3> -- required module to use Brackup::Target::Amazon
259
Note: See TracBrowser for help on using the browser.