File Coverage

blib/lib/WebService/CEPH/FileShadow.pm
Criterion Covered Total %
statement 78 78 100.0
branch 39 50 78.0
condition 7 8 87.5
subroutine 17 17 100.0
pod 8 8 100.0
total 149 161 92.5


line stmt bran cond sub pod time code
1             =encoding utf8
2              
3              
4              
5             =head1 WebService::CEPH::FileShadow
6              
7             Child class of WebService::CEPH.
8              
9             Constructor parameters are the same as for WebService::CEPH, plus there are mode and fs_shadow_path parameters:
10             mode - 's3' or 's3-fs' or 'fs'
11              
12             fs_shadow_path - path to the file system, points to the directory, the final slash is optional.
13              
14             In s3 mode, everything works like WebService :: CEPH, the files are downloaded and uploaded to the CEPH using the s3 protocol.
15              
16             In the 's3-fs' mode when uploading a file, a copy of file is created in the file system.
17             First, the file is uploaded to s3, then to the file system and if an exception was thrown at that time, the previous step would not be canceled.
18             If a download fails in 's3-fs' mode, no failover on the file system is made.
19             In the 'fs' mode, file upload and download is made using only file system, not S3.
20              
21             Metainformation (x-amz-meta-md5, content-type in the file system is not saved).
22             In the download_to_file, upload_from_file methods in the fs mode, working with local files is done as much as possible
23             compatible with the 's3' mode (umask permissions when creating files, truncate and seek modes when working with filehandles).
24              
25             The object key name can not contain characters that are insecure for the file system (for example '../', '/ ..')
26             otherwise there will be an exception thrown.
27             However, it is the caller that is really responsible for security.
28              
29             =cut
30              
31             package WebService::CEPH::FileShadow;
32              
33             our $VERSION = '0.017'; # VERSION
34              
35 1     1   178043 use strict;
  1         12  
  1         29  
36 1     1   6 use warnings;
  1         2  
  1         24  
37 1     1   5 use Carp;
  1         2  
  1         51  
38 1     1   6 use Fcntl qw/:seek/;
  1         11  
  1         104  
39 1     1   581 use File::Copy;
  1         2374  
  1         62  
40 1     1   541 use File::Slurp qw/read_file/;
  1         5436  
  1         61  
41 1     1   7 use File::Path qw(make_path);
  1         2  
  1         49  
42 1     1   6 use parent qw( WebService::CEPH );
  1         1  
  1         5  
43              
44              
45             sub new {
46 4     4 1 23902 my ($class, %options) = @_;
47 4         9 my %new_options;
48              
49 4         22 $new_options{$_} = delete $options{$_} for (qw/fs_shadow_path mode/);
50              
51 4   66     25 !defined or m!/$! or $_ .= '/' for $new_options{fs_shadow_path};
      100        
52             confess "mode should be 's3', 's3-fs' or 'fs', but it is '$new_options{mode}'"
53 4 50       25 unless $new_options{mode} =~ /^(s3|s3\-fs|fs)$/;
54              
55             confess "you specified mode to work with filesystem ($new_options{mode}), please define fs_shadow_path then"
56 4 100 100     487 if $new_options{mode} =~ /fs/ && !$new_options{fs_shadow_path};
57              
58 2         21 my $self = $class->SUPER::new(%options);
59              
60 2         13174 $self->{$_} = $new_options{$_} for keys %new_options;
61              
62 2         9 $self;
63             }
64              
65             sub _filepath {
66 16     16   8916 my ($self, $key, $should_mkpath) = @_;
67 16 50       46 confess "key expected" unless defined $key;
68 16 50       42 confess "unsecure key" if $key eq '.';
69 16 100       194 confess "unsecure key" if $key =~ m!\.\./!;
70 15 100       133 confess "unsecure key" if $key =~ m!/\.\.!;
71 14 50       69 confess "constructor should normalize path" unless $self->{fs_shadow_path} =~ m!/$!;
72 14         40 my $dir = $self->{fs_shadow_path}.$self->{bucket}."/";
73 14 100       730 make_path($dir) if ($should_mkpath);
74 14         156 $dir.$key;
75             }
76             sub upload {
77 6     6 1 9331 my ($self, $key) = (shift, shift);
78              
79 6 100       25 if ($self->{mode} =~ /s3/) {
80 5         37 $self->SUPER::upload($key, $_[0], $_[1], $_[2]);
81             }
82 6 100       207 if ($self->{mode} =~ /fs/) {
83 2         7 my $path = $self->_filepath($key, 1);
84 2 50       153 open my $f, ">", $path or confess;
85 2         13 binmode $f;
86 2 50       47 print $f $_[0] or confess;
87 2 50       92 close $f or confess;
88             }
89             }
90              
91             sub upload_from_file {
92 7     7 1 14828 my ($self, $key, $fh_or_filename, $content_type, $acl ) = @_;
93              
94 7 100       31 if ($self->{mode} =~ /s3/) {
95 5         36 $self->SUPER::upload_from_file($key, $fh_or_filename, $content_type, $acl );
96             }
97 7 100       211 if ($self->{mode} =~ /fs/) {
98 3         9 my $path = $self->_filepath($key, 1);
99 3 100       18 seek($fh_or_filename, 0, SEEK_SET) if (ref $fh_or_filename);
100 3         14 copy($fh_or_filename, $path);
101             }
102             }
103              
104             sub download {
105 3     3 1 7144 my ($self, $key) = @_;
106              
107 3 100       21 if ($self->{mode} =~ /s3/) {
    50          
108 2         15 return $self->SUPER::download($key);
109             }
110             elsif ($self->{mode} =~ /fs/) {
111 1         8 return scalar read_file( $self->_filepath($key), binmode => ':raw' )
112             }
113             }
114              
115             sub download_to_file {
116 4     4 1 8237 my ($self, $key, $fh_or_filename) = @_;
117              
118 4 100       25 if ($self->{mode} =~ /s3/) {
    50          
119 2         14 $self->SUPER::download_to_file($key, $fh_or_filename);
120             }
121             elsif ($self->{mode} =~ /fs/) {
122 2         8 copy( $self->_filepath($key), $fh_or_filename );
123             }
124             }
125              
126             sub size {
127 3     3 1 5365 my ($self, $key) = @_;
128              
129 3 100       20 if ($self->{mode} =~ /s3/) {
    50          
130 2         16 return $self->SUPER::size($key);
131             }
132             elsif ($self->{mode} =~ /fs/) {
133 1         4 return -s $self->_filepath($key);
134             }
135             }
136              
137             sub delete {
138 3     3 1 6135 my ($self, $key) = @_;
139              
140 3 100       23 if ($self->{mode} =~ /s3/) {
    50          
141 2         17 $self->SUPER::delete($key);
142             }
143             elsif ($self->{mode} =~ /fs/) {
144 1         5 unlink($self->_filepath($key));
145             }
146             }
147              
148             sub query_string_authentication_uri {
149 3     3 1 5244 my ($self, $key, $expires) = @_;
150              
151 3 100       13 if ($self->{mode} =~ /s3/) {
152 2         15 $self->SUPER::query_string_authentication_uri($key, $expires);
153             }
154             else {
155 1         178 confess "Unimplemented in fs mode";
156             }
157             }
158              
159             1;