├── OSS.pm
├── README.md
├── distro
└── archlinux
│ └── PKGBUILD
├── ossfs
└── test.pl
/OSS.pm:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | use strict;
4 | use warnings;
5 |
6 | package OSS;
7 |
8 | use LWP::UserAgent;
9 | use HTTP::Date;
10 | use MIME::Base64;
11 | use Digest::MD5 qw(md5_base64);
12 | use Digest::HMAC_SHA1;
13 | use XML::Simple;
14 |
15 | # for xml debug
16 | # use Data::Dumper;
17 | # print Dumper($xml);
18 |
19 | my $debug = 0;
20 |
21 | sub new {
22 | my $class = shift;
23 | my $access_id = shift;
24 | my $access_key = shift;
25 | my $host = shift;
26 |
27 | $host = "oss.aliyuncs.com" unless (defined($host));
28 | my $ua = LWP::UserAgent->new(agent => "ossfs");
29 | my $self = {
30 | access_id => $access_id,
31 | access_key => $access_key,
32 | host => $host,
33 | ua => $ua
34 | };
35 | return bless $self, $class;
36 | }
37 |
38 | sub sign {
39 | my $self = shift;
40 | my $req = shift;
41 | my $canonicalized_resource = shift;
42 |
43 | my $verb = $req->method;
44 | my $md5 = "";
45 | if ($req->content) {
46 | $md5 = md5_base64($req->content) . "==";
47 | $req->header("Content-Md5" => $md5);
48 | }
49 | my $type = $req->header("Content-Type");
50 | $type = "" unless (defined($type));
51 | my $date = $req->header("Date");
52 | unless (defined($date)) {
53 | $date = time2str(time);
54 | $req->header("Date" => $date);
55 | }
56 |
57 | my $hmac = Digest::HMAC_SHA1->new($self->{access_key});
58 | $hmac->add("$verb\n$md5\n$type\n$date\n");
59 |
60 | my %canonicalized_oss_headers;
61 | foreach my $key ( $req->headers->header_field_names ) {
62 | if ($key =~ /^x-oss-/i) {
63 | $canonicalized_oss_headers{lc $key} = $req->header($key);
64 | }
65 | }
66 | foreach my $key ( sort map { lc } keys %canonicalized_oss_headers ) {
67 | $hmac->add("$key:$canonicalized_oss_headers{$key}\n");
68 | }
69 | $hmac->add($canonicalized_resource);
70 |
71 | $req->header("Authorization" => sprintf("OSS %s:%s",
72 | $self->{access_id},
73 | encode_base64($hmac->digest)));
74 | return $req;
75 | }
76 |
77 | sub request {
78 | my $self = shift;
79 | my $req = shift;
80 |
81 | if ($debug) {
82 | print STDERR ">>> ", (caller(1))[3], "\n";
83 | print STDERR $req->as_string, "\n";
84 | }
85 | my $res = $self->{ua}->request($req);
86 | if ($debug) {
87 | print STDERR $res->as_string, "\n";
88 | print STDERR "<<<\n";
89 | }
90 | return $res;
91 | }
92 |
93 | # return 0|1 and a hash of { bucket_name => (creation_date, endpoint, internal_endpoint) }
94 | sub ListBucket {
95 | my $self = shift;
96 |
97 | my $req = $self->sign(
98 | HTTP::Request->new(GET => "http://$self->{host}/"), "/");
99 | my $res = $self->request($req);
100 | if ($res->is_success) {
101 | my %buckets;
102 | my $xml = XMLin($res->decoded_content, ForceArray => ['Bucket']);
103 | foreach ( @{ $xml->{Buckets}->{Bucket} } ) {
104 | $buckets{$_->{Name}}{'creation_date'} = str2time($_->{CreationDate});
105 | $buckets{$_->{Name}}{'endpoint'} = $_->{ExtranetEndpoint};
106 | $buckets{$_->{Name}}{'internal_endpoint'} = $_->{IntranetEndpoint};
107 | }
108 | return (1, %buckets);
109 | }
110 | return 0;
111 | }
112 | sub GetService {
113 | return ListBucket(@_);
114 | }
115 |
116 | # return string "private", "public-read" or "public-read-write" or 0 on error
117 | sub GetBucketACL {
118 | my $self = shift;
119 | my $bucket = shift;
120 |
121 | my $req = $self->sign(
122 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/?acl"),
123 | "/$bucket/?acl");
124 | my $res = $self->request($req);
125 | if ($res->is_success) {
126 | my $xml = XMLin($res->decoded_content);
127 | return $xml->{AccessControlList}->{Grant};
128 | }
129 | return 0;
130 | }
131 |
132 | # return array of (0|1, files and dirs)
133 | sub GetBucket {
134 | my $self = shift;
135 | my $bucket = shift;
136 | my %param = ( @_ );
137 |
138 | my @ret;
139 | my $param_str = "?";
140 | foreach my $key ( sort keys %param ) {
141 | $param_str .= "$key=$param{$key}&";
142 | }
143 | chop $param_str;
144 | my $req = $self->sign(
145 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/$param_str"),
146 | "/$bucket/");
147 | my $res = $self->request($req);
148 | if ($res->is_success) {
149 | my $xml = XMLin($res->decoded_content,
150 | ForceArray => ['Contents', 'CommonPrefixes']);
151 | # files
152 | foreach ( @{$xml->{Contents}} ) {
153 | my $f = $_->{Key};
154 | if (exists($param{"prefix"})) {
155 | $f = substr($f, length($param{"prefix"}));
156 | }
157 | push @ret, $f if ($f); # prefix itself should be removed
158 | }
159 | # dirs
160 | foreach ( @{$xml->{CommonPrefixes}} ) {
161 | my $d = $_->{Prefix};
162 | chop $d; # remove trailing "/"
163 | if (exists($param{"prefix"})) {
164 | $d = substr($d, length($param{"prefix"}));
165 | }
166 | push @ret, $d if ($d); # prefix itself should be removed
167 | }
168 | return (1, @ret);
169 | }
170 | return 0;
171 | }
172 |
173 | # return if success
174 | sub PutBucket {
175 | my $self = shift;
176 | my $bucket = shift;
177 | my $acl = shift;
178 | if (defined($acl)) {
179 | return 0 unless (grep {$_ eq $acl} ("public-read-write", "public-read", "private"));
180 | }
181 |
182 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/");
183 | $req->header("x-oss-acl", $acl) if defined($acl);
184 | $req = $self->sign($req, "/$bucket/");
185 | my $res = $self->request($req);
186 | return $res->is_success;
187 | }
188 |
189 | # return if success
190 | sub PutBucketACL {
191 | my $self = shift;
192 | my $bucket = shift;
193 | my $acl = shift;
194 | return 0 unless (grep {$_ eq $acl} ("public-read-write", "public-read", "private"));
195 |
196 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/?acl");
197 |
198 | $req->header("x-oss-acl", $acl);
199 | $req = $self->sign($req, "/$bucket/?acl");
200 | my $res = $self->request($req);
201 | return $res->is_success;
202 | }
203 |
204 | # return if success
205 | sub DeleteBucket {
206 | my $self = shift;
207 | my $bucket = shift;
208 |
209 | my $req = $self->sign(
210 | HTTP::Request->new(DELETE => "http://$bucket.$self->{host}/"),
211 | "/$bucket/");
212 | my $res = $self->request($req);
213 | return $res->is_success;
214 | }
215 |
216 | # return if success
217 | sub PutObject {
218 | my $self = shift;
219 | my $bucket = shift;
220 | my $object = shift;
221 | my $content = shift;
222 | my $type = shift;
223 |
224 | my $req = HTTP::Request->new(PUT => "http://$bucket.$self->{host}/$object");
225 | $req->header("Content-Type" => $type) if ($type);
226 | $req->header("Content-Length" => length($content));
227 | $req->content($content);
228 | $req = $self->sign($req, "/$bucket/$object");
229 | my $res = $self->request($req);
230 | return $res->is_success;
231 | }
232 |
233 | # return if success
234 | sub DeleteObject {
235 | my $self = shift;
236 | my $bucket = shift;
237 | my $object = shift;
238 |
239 | my $req = $self->sign(
240 | HTTP::Request->new(DELETE => "http://$bucket.$self->{host}/$object"),
241 | "/$bucket/$object");
242 | my $res = $self->request($req);
243 | return $res->is_success;
244 | }
245 |
246 | # return array of (0|1, last-modified, content-size, content-type)
247 | sub HeadObject {
248 | my $self = shift;
249 | my $bucket = shift;
250 | my $object = shift;
251 |
252 | my $req = $self->sign(
253 | HTTP::Request->new(HEAD => "http://$bucket.$self->{host}/$object"),
254 | "/$bucket/$object");
255 | my $res = $self->request($req);
256 | if ($res->is_success) {
257 | return (1,
258 | $res->header("Last-Modified"),
259 | $res->header("Content-Length"),
260 | $res->header("Content-Type"));
261 | }
262 | return 0;
263 | }
264 |
265 | # return string of content or undef on error
266 | sub GetObject {
267 | my $self = shift;
268 | my $bucket = shift;
269 | my $object = shift;
270 | my $begin = shift;
271 | my $end = shift;
272 |
273 | my $req = $self->sign(
274 | HTTP::Request->new(GET => "http://$bucket.$self->{host}/$object"),
275 | "/$bucket/$object");
276 | if (defined($begin) and defined($end)) {
277 | $req->header(Range => "$begin-$end");
278 | }
279 | my $res = $self->request($req);
280 | if ($res->is_success) {
281 | return $res->decoded_content;
282 | }
283 | return undef;
284 | }
285 |
286 | # return if success
287 | sub CopyObject {
288 | my $self = shift;
289 | my $src_bucket = shift;
290 | my $src_object = shift;
291 | my $dest_bucket = shift;
292 | my $dest_object = shift;
293 |
294 | my $req = HTTP::Request->new(PUT => "http://$dest_bucket.$self->{host}/$dest_object");
295 | $req->header("x-oss-copy-source", "/$src_bucket/$src_object");
296 | $req = $self->sign($req, "/$dest_bucket/$dest_object");
297 | my $res = $self->request($req);
298 | return $res->is_success;
299 | }
300 |
301 | # TODO: DeleteMultipleObjects
302 | # TODO: MultipartUpload
303 |
304 | 1;
305 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### **_IMPORTANT NOTE_**
2 | **This repository is to be considered _DEPRECATED_ and all efforts are going towards developing the next version of the platform in [aliyun/ossfs](https://github.com/aliyun/ossfs).**
3 |
4 | # ossfs
5 |
6 | ossfs is a fuse client to mount your [aliyun oss](http://oss.aliyun.com/) bucket under linux.
7 |
8 | ## installation
9 |
10 | ### archlinux
11 |
12 | run `makepkg` in distro/archlinux, and `pacmann -U ossfs-git-*.pkg.tar.xfz` to install
13 |
14 | ### ubuntu
15 |
16 | make sure at least dependencies are installed:
17 | - libhttp-date-perl
18 | - libfuse-perl
19 | - libxml-simple-perl
20 | - libdigest-hmac-perl
21 |
22 | ## developer
23 |
24 | ossfs is written in perl, please make sure these perl packages could be cound in your enviromnent:
25 |
26 | - Fuse
27 | - LWP::UserAgent
28 | - HTTP::Date
29 | - Digest::MD5
30 | - Digest::HMAC_SHA1
31 | - XML::Simple
32 |
33 | the OSS.pm, delivered with ossfs, is a simple perl oss sdk module, which could be useful to perl coders. test.pl is a unittest script for this module.
34 |
35 | ## road map
36 |
37 | - review code of rmdir and see if recursively remove object is needed
38 | - optimize memory usage when writing big files
39 | - make OSS.pm a CPAN module
40 | - OSS.pm support multiple part upload
41 | - basic read/write
42 |
43 | ## reference
44 |
45 | - [perl Fuse module](http://search.cpan.org/~dpavlin/Fuse-0.14/Fuse.pm)
46 | - [fuse documentation](http://fuse.sourceforge.net/doxygen/structfuse__operations.html#dc6dc71274f185de72217e38d62142c4)
47 | - [post on aliyun official forum](http://bbs.aliyun.com/read.php?tid=132627)
48 |
49 | ## author
50 |
51 | Li Ruibo
52 | - http://twitter.com/lymanrb
53 | - http://weibo.com/lymanrb
54 |
55 | ## licence
56 |
57 | licensed under the [BSD License](http://www.linfo.org/bsdlicense.html)
58 |
--------------------------------------------------------------------------------
/distro/archlinux/PKGBUILD:
--------------------------------------------------------------------------------
1 | # Contributor: Li Ruibo
2 |
3 | pkgname=ossfs-git
4 | pkgver=20151121
5 | pkgrel=1
6 | pkgdesc="a fuse client to mount ossfs bucket to local file system"
7 | arch=('any')
8 | url="https://github.com/alibaba/ossfs"
9 | license=('BSD')
10 | depends=('perl-fuse' 'perl-http-date' 'perl-libwww' 'perl-xml-simple' 'perl-digest-hmac')
11 | makedepends=('git')
12 | source=("$pkgname"::'git+https://github.com/alibaba/ossfs.git')
13 | md5sums=('SKIP')
14 |
15 | pkgver() {
16 | cd "$srcdir/$pkgname"
17 |
18 | # Use the tag of the last commit
19 | # git describe --long | sed -E 's/([^-]*-g)/r\1/;s/-/./g'
20 |
21 | # use last commit
22 | git log -1 | head -n 1 | cut -d ' ' -f 2 | head -c 8
23 | }
24 |
25 | build() {
26 | cd "$srcdir/$pkgname"
27 | }
28 |
29 | package() {
30 | cd "$srcdir/$pkgname"
31 | install -Dm644 OSS.pm "$pkgdir/usr/share/perl5/vendor_perl/OSS.pm"
32 | install -Dm755 ossfs "$pkgdir/usr/bin/ossfs"
33 | }
34 |
--------------------------------------------------------------------------------
/ossfs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | use strict;
4 | use warnings;
5 |
6 | use POSIX;
7 | use Errno qw(:POSIX);
8 | use Fcntl ':mode';
9 | use HTTP::Date;
10 | use Fuse;
11 | use Getopt::Long;
12 |
13 | push @INC, ".";
14 | use OSS;
15 |
16 | my $oss;
17 | my $default_host = "oss.aliyuncs.com";
18 |
19 | my $access_id = undef;
20 | my $access_key = undef;
21 | my $bucket = undef;
22 | my $host = undef;
23 |
24 | my %buckets = ();
25 |
26 | my $uid = $<;
27 | my ($gid) = split / /, $(;
28 |
29 | my %acl = ("private" => 0770,
30 | "public-read" => 0775,
31 | "public-read-write" => 0777);
32 | my $create = "private"; # default create acl
33 |
34 | my $debug = 0;
35 | my $bucket_mode;
36 | my $bucket_time;
37 |
38 | my %wbuf;
39 |
40 | # file to object, remove leading "/" of input string
41 | sub f2o {
42 | my $str = shift;
43 | $str =~ s#^/+##;
44 | return $str;
45 | }
46 |
47 | sub oss_dummy { 0; }
48 |
49 | sub oss_getattr {
50 | my $file = shift @_;
51 | if ($file eq "/") {
52 | return (0, 0, S_IFDIR | $bucket_mode, 1, $uid, $gid, 0, 0,
53 | $bucket_time,
54 | $bucket_time,
55 | $bucket_time, 0, 0);
56 | }
57 | # try if is file
58 | my ($ret, $time, $size) = $oss->HeadObject($bucket, f2o($file));
59 | if ($ret) {
60 | return (0, # dev
61 | 0, # inode
62 | S_IFREG | $bucket_mode, # mode
63 | 1, # nlink
64 | $uid,
65 | $gid,
66 | 0, # rdev
67 | $size, # size
68 | str2time($time), # atime
69 | str2time($time), # mtime
70 | str2time($time), # ctime
71 | 0, # blksize
72 | $size / 512); # blocks
73 | }
74 | # try if is dir
75 | ($ret, $time, $size) = $oss->HeadObject($bucket, f2o("$file/"));
76 | if ($ret) {
77 | return (0, # dev
78 | 0, # inode
79 | S_IFDIR | $bucket_mode, # mode
80 | 1, # nlink
81 | $uid,
82 | $gid,
83 | 0, # rdev
84 | $size, # size
85 | str2time($time), # atime
86 | str2time($time), # mtime
87 | str2time($time), # ctime
88 | 0, # blksize
89 | $size / 512); # blocks
90 | }
91 | # ok, file seems not exist
92 | return -ENOENT();
93 | }
94 |
95 | sub oss_mknod {
96 | my ($file, $mode, $dev) = @_;
97 | # TODO: file already exist?
98 | my $ret = $oss->PutObject($bucket, f2o($file), "");
99 | return 0 if ($ret);
100 | return -ENOENT();
101 | }
102 |
103 | sub oss_mkdir {
104 | my ($file, $mode) = @_;
105 | # TODO: dir already exist?
106 | my $ret = $oss->PutObject($bucket, f2o("$file/"), "");
107 | return 0 if ($ret);
108 | return -ENOENT();
109 | }
110 |
111 | sub oss_getdir {
112 | my $file = shift @_;
113 | my %param = ( "delimiter" => "/" );
114 | $param{"prefix"} = f2o("$file/");
115 | my ($ret, @list) = $oss->GetBucket($bucket, %param);
116 | return (@list, 0) if ($ret);
117 | return -ENOENT(); # TODO
118 | }
119 |
120 | sub oss_unlink {
121 | my $file = shift @_;
122 | my $ret = $oss->DeleteObject($bucket, f2o($file));
123 | return 0 if ($ret);
124 | # TODO: no such file
125 | # TODO: is a dir?
126 | return -ENOENT();
127 | }
128 |
129 | sub oss_rmdir {
130 | my $file = shift @_;
131 | # TODO: delete a whole tree
132 | my $ret = $oss->DeleteObject($bucket, f2o("$file/"));
133 | return 0 if ($ret);
134 | return -ENOENT();
135 | }
136 |
137 | sub oss_rename {
138 | my ($old, $new) = @_;
139 | my $ret = $oss->CopyObject($bucket, f2o($old), $bucket, f2o($new));
140 | return -ENOENT() unless ($ret);
141 | return unlink($old);
142 | }
143 |
144 | sub oss_open {
145 | my ($file, $flags, $fi_ref) = @_;
146 | # store file size for reading
147 | my ($ret, $time, $size) = $oss->HeadObject($bucket, f2o($file));
148 | if ($ret) {
149 | # file already exists
150 | # store file size for calc end in oss_read
151 | $$fi_ref{"filesize"} = $size;
152 | } else {
153 | # file not exist, create an empty one if necessary
154 | # TODO: refer to $flags here
155 | $ret = $oss->PutObject($bucket, f2o($file), "");
156 | $$fi_ref{"filesize"} = 0;
157 | return -EIO() unless ($ret);
158 | }
159 | return (0, $fi_ref);
160 | }
161 |
162 | sub oss_read {
163 | my ($file, $size, $offset, $fi_ref) = @_;
164 | my $end = $offset + $size - 1;
165 | $end = $$fi_ref{"filesize"} - 1
166 | if ($fi_ref and exists($$fi_ref{"filesize"})
167 | and $end >= $$fi_ref{"filesize"});
168 | my $content = $oss->GetObject($bucket, f2o($file), $offset, $end);
169 | return $content if (defined($content));
170 | return -ENOENT();
171 | }
172 |
173 | sub oss_write {
174 | my ($file, $buffer, $offset, $fi_ref) = @_;
175 | # create a write buffer if necessary
176 | $wbuf{$file} = "" unless (exists $wbuf{$file});
177 | my $len = length $wbuf{$file};
178 | # to prevent hole in file
179 | if ($offset > $len) {
180 | $wbuf{$file} .= '\0' x ($offset - $len);
181 | }
182 | $len = length $buffer;
183 | substr($wbuf{$file}, $offset, $len, $buffer);
184 | return $len;
185 | }
186 |
187 | sub oss_release {
188 | my ($file, $flags, $fi_ref) = @_;
189 | my $ret = 0;
190 | if (exists $wbuf{$file}) {
191 | $ret = $oss->PutObject($bucket, f2o($file), $wbuf{$file});
192 | delete $wbuf{$file};
193 | }
194 | # undef %$fi_ref;
195 | return $ret ? 0 : -EIO();
196 | }
197 |
198 | sub oss_create {
199 | my ($file, $flags) = @_;
200 | my $fi_ref = {};
201 | return oss_open($file, $flags, $fi_ref);
202 | }
203 |
204 | sub oss_ftruncate {
205 | my ($file, $offset, $fi_ref) = @_;
206 | if (exists $wbuf{$file}) {
207 | my $len = length $wbuf{$file};
208 | if ($offset > $len) {
209 | $wbuf{$file} .= '\0' x ($offset - $len);
210 | } elsif ($offset < $len) {
211 | $wbuf{$file} = substr($wbuf{$file}, 0, $offset);
212 | }
213 | }
214 | return 0;
215 | }
216 |
217 | sub print_usage {
218 | print < \$unmount,
244 | "b|bucket=s" => \$bucket,
245 | "c|create:s" => \$create,
246 | "host=s" => \$host,
247 | "h|help" => sub { &print_usage(); exit 0; },
248 | "d|debug" => \$debug,
249 | "o|mountopts=s" => \$mountopts,
250 | "i|id=s" => \$access_id,
251 | "k|key=s" => \$access_key,
252 | "f|file=s" => \$file)
253 | or exit 1;
254 |
255 | $mountopts = "" unless $mountopts;
256 | my $mountpoint = shift @ARGV;
257 | unless ($mountpoint) {
258 | print STDERR "mountpoint not specified.\n";
259 | &print_usage();
260 | exit 1;
261 | }
262 |
263 | if ($unmount) {
264 | exec("fusermount", "-u", "-z", $mountpoint);
265 | }
266 |
267 | if ($file) {
268 | open FILE, "<", $file or die "$!: $file\n";
269 | my $line1 = ;
270 | my $line2 = ;
271 | my $line3 = ;
272 | my $line4 = ;
273 | chomp($access_id = $line1) if (not $access_id and $line1);
274 | chomp($access_key = $line2) if (not $access_key and $line2);
275 | chomp($bucket = $line3) if (not $bucket and $line3);
276 | chomp($host = $line4) if (not $host and $line4);
277 | close FILE;
278 | }
279 |
280 | die "empty access id\n" unless ($access_id);
281 | die "empty access key\n" unless ($access_key);
282 | die "empry bucket\n" unless ($bucket);
283 |
284 | # special host 'internal'
285 | my $internal = 0;
286 | if ($host and $host eq 'internal') {
287 | $internal = 1;
288 | $host = undef;
289 | }
290 | $default_host = "oss-internal.aliyuncs.com" if ($internal);
291 |
292 | # get all buckets
293 | my $listed = 0;
294 | $oss = OSS->new($access_id, $access_key, $default_host);
295 | ($listed, %buckets) = $oss->ListBucket();
296 |
297 | # create bucket if necessary
298 | if ($create and $listed and !exists($buckets{$bucket})) {
299 | die "failed to create bucket $bucket\n"
300 | unless ($oss->PutBucket($create));
301 | # refresh buckets
302 | ($listed, %buckets) = $oss->ListBucket();
303 | }
304 |
305 | # use server specified endpoint for specified bucket
306 | if ($listed and not $host) {
307 | $host = $internal ? $buckets{$bucket}{'internal_endpoint'} : $buckets{$bucket}{'endpoint'};
308 | } else {
309 | $host = $default_host;
310 | }
311 |
312 | my $masked_key = substr($access_key, 0, 3) . '*' x (length($access_key) - 6) . substr($access_key, -3);
313 | print STDERR <new($access_id, $access_key, $host);
322 |
323 | # for current bucket
324 | $bucket_time = $buckets{$bucket};
325 | $bucket_mode = $oss->GetBucketACL($bucket);
326 | die "invalid bucket mode: $bucket_mode\n" unless exists $acl{$bucket_mode};
327 | $bucket_mode = $acl{$bucket_mode};
328 |
329 | # unmount first
330 | if ($debug) {
331 | system("fusermount -u -z $mountpoint");
332 | }
333 |
334 | Fuse::main( mountpoint => $mountpoint,
335 | mountopts => $mountopts,
336 | debug => $debug,
337 |
338 | getattr => "main::oss_getattr",
339 | readlink => "main::oss_dummy",
340 | getdir => "main::oss_getdir",
341 | mknod => "main::oss_mknod",
342 | mkdir => "main::oss_mkdir",
343 | unlink => "main::oss_unlink",
344 | rmdir => "main::oss_rmdir",
345 | symlink => "main::oss_dummy",
346 | rename => "main::oss_rename",
347 | link => "main::oss_dummy",
348 | chmod => "main::oss_dummy",
349 | chown => "main::oss_dummy",
350 | truncate => "main::oss_dummy",
351 | # utime => "main:oss_dummy", # deprecated
352 | open => "main::oss_open",
353 | read => "main::oss_read",
354 | write => "main::oss_write",
355 | statfs => "main::oss_dummy",
356 | flush => "main::oss_dummy",
357 | release => "main::oss_release",
358 | fsync => "main::oss_dummy",
359 | setxattr => "main::oss_dummy",
360 | getxattr => "main::oss_dummy",
361 | listxattr => "main::oss_dummy",
362 | removexattr => "main::oss_dummy",
363 | opendir => "main::oss_dummy",
364 | # readdir => "main::oss_readdir",
365 | releasedir => "main::oss_dummy",
366 | fsyncdir => "main::oss_dummy",
367 |
368 | # init => "main::oss_dummy",
369 | # destory => "main::oss_dummy",
370 |
371 | access => "main::oss_dummy",
372 | create => "main::oss_create",
373 | ftruncate => "main::ftruncate",
374 | fgetattr => "main::oss_getattr",
375 | # lock => "main::oss_dummy",
376 | utimens => "main::oss_dummy",
377 | # bmap => "main::oss_dummy",
378 | );
379 |
--------------------------------------------------------------------------------
/test.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | push @INC, ".";
4 |
5 | use strict;
6 | use warnings;
7 |
8 | use OSS;
9 |
10 | my $conf_file = shift @ARGV;
11 | unless (defined($conf_file)) {
12 | print "usage: $0 conf_file\n";
13 | print " conf_file should contain access id & key, each in a line.\n";
14 | exit 0;
15 | }
16 | my $access_id = undef;
17 | my $access_key = undef;
18 | open FILE, "<", $conf_file or die "$!: $conf_file\n";
19 | chomp($access_id = );
20 | chomp($access_key = );
21 | close FILE;
22 |
23 | my $case_id = 0;
24 | sub case {
25 | my $name = shift;
26 | $case_id++;
27 | print "\n$case_id: $name\n\n";
28 | }
29 |
30 | my $fail = 0;
31 | sub assert_eq {
32 | my $left = shift;
33 | my $right = shift;
34 | if ($left == $right) {
35 | print "OK, $right as expected.\n";
36 | } else {
37 | print "FAIL, $left expected but got $right\n";
38 | $fail++;
39 | }
40 | }
41 | sub assert_str_eq {
42 | my $left = shift;
43 | my $right = shift;
44 | if ($left eq $right) {
45 | print "OK, [$right] as expected.\n";
46 | } else {
47 | print "FAIL, [$left] expected but got [$right]\n";
48 | $fail++;
49 | }
50 | }
51 |
52 | my $oss = OSS->new($access_id, $access_key);
53 |
54 | my $bucket = "lyman-ossfs-unittest";
55 |
56 | case("PutBucket"); {
57 | # clear first
58 | $oss->DeleteBucket($bucket);
59 | assert_eq(1, $oss->PutBucket($bucket));
60 | }
61 |
62 | case("ListBucket");
63 | {
64 | my ($ret, %buckets) = $oss->ListBucket;
65 | assert_eq(1, $ret);
66 | foreach my $bucket ( keys %buckets ) {
67 | print "$bucket => $buckets{$bucket}{'creation_date'}, $buckets{$bucket}{'endpoint'}, $buckets{$bucket}{'internal_endpoint'}\n";
68 | }
69 | assert_eq(1, exists($buckets{$bucket}));
70 | }
71 |
72 | case("GetBucketACL");
73 | {
74 | assert_str_eq("private", $oss->GetBucketACL($bucket));
75 | }
76 |
77 | case("PutBucketACL as modifier");
78 | {
79 | assert_eq(1, $oss->PutBucketACL($bucket, "public-read"));
80 | assert_str_eq("public-read", $oss->GetBucketACL($bucket));
81 | }
82 |
83 | case("PutObject");
84 | {
85 | assert_eq(1, $oss->PutObject($bucket, "foo",
86 | "hello world!", "text/plain"));
87 | }
88 |
89 | case("GetObject");
90 | {
91 | assert_str_eq("hello world!", $oss->GetObject($bucket, "foo"));
92 | }
93 |
94 | case("PutObject empty file");
95 | {
96 | assert_eq(1, $oss->PutObject($bucket, "empty", ""));
97 | }
98 |
99 | case("HeadObject");
100 | {
101 | print "foo\n";
102 | my ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "foo");
103 | assert_eq(1, $ret);
104 | assert_eq(12, $size);
105 | assert_str_eq("text/plain", $type);
106 |
107 | print "empty\n";
108 | ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "empty");
109 | assert_eq(1, $ret);
110 | assert_eq(0, $size);
111 | print "$ctime\n";
112 | print "$type\n" if (defined($type));
113 | }
114 |
115 | case("CopyObject");
116 | {
117 | assert_eq(1, $oss->CopyObject($bucket, "foo", $bucket, "bar/copy"));
118 |
119 | assert_str_eq("hello world!", $oss->GetObject($bucket, "bar/copy"));
120 |
121 | my ($ret, $ctime, $size, $type) = $oss->HeadObject($bucket, "bar/copy");
122 | assert_eq(1, $ret);
123 | assert_eq(12, $size);
124 | assert_str_eq("text/plain", $type);
125 | }
126 |
127 | case("GetBucket");
128 | {
129 | my ($ret, @files) = $oss->GetBucket($bucket);
130 | assert_eq(1, $ret);
131 | assert_str_eq("foo", grep {$_ eq "foo"} @files);
132 | assert_str_eq("bar/copy", grep {$_ eq "bar/copy"} @files);
133 | assert_str_eq("empty", grep {$_ eq "empty"} @files);
134 | }
135 |
136 | case("GetBucket w/ prefix and delimiter");
137 | {
138 | my ($ret, @files) = $oss->GetBucket($bucket,
139 | prefix => "bar/",
140 | delimiter => "/");
141 | assert_eq(1, $ret);
142 | assert_eq(0, $#files);
143 | assert_str_eq("copy", $files[0]);
144 | }
145 |
146 | case("DeleteObject");
147 | {
148 | assert_eq(1, $oss->DeleteObject($bucket, "foo"));
149 |
150 | my ($ret, @files) = $oss->GetBucket($bucket);
151 | assert_eq(0, scalar grep {$_ eq "foo"} @files);
152 | assert_eq(1, $#files);
153 | assert_eq(1, $oss->DeleteObject($bucket, "empty"));
154 | assert_eq(1, $oss->DeleteObject($bucket, "bar/copy"));
155 | }
156 |
157 | case("DeleteBucket");
158 | {
159 | assert_eq(1, $oss->DeleteBucket($bucket));
160 | my ($ret, %buckets) = $oss->ListBucket;
161 | assert_eq(1, $ret);
162 | assert_eq(0, scalar grep {$_ eq $bucket} keys %buckets);
163 | }
164 |
165 | case("PutBucket as creator with acl");
166 | {
167 | my $acl = "public-read";
168 | my $bucket = "lyman-ossfs-unittest-1";
169 |
170 | # create
171 | $oss->DeleteBucket($bucket);
172 | assert_eq(1, $oss->PutBucket($bucket, $acl));
173 | # assert acl
174 | assert_str_eq($acl, $oss->GetBucketACL($bucket));
175 | # delete
176 | $oss->DeleteBucket($bucket);
177 | }
178 |
179 | if ($fail == 0) {
180 | print "\ndone.\n";
181 | exit 0;
182 | } else {
183 | print "\n$fail failed.\n";
184 | exit 1;
185 | }
186 |
--------------------------------------------------------------------------------