test: expose try_for() as a common test function
[MogileFS-Server.git] / t / 00-startup.t
blob263a185fd317c938d741f95e704d37a14a0d79b0
1 # -*-perl-*-
3 use strict;
4 use warnings;
5 use Test::More;
6 use FindBin qw($Bin);
8 use MogileFS::Server;
9 use MogileFS::Util qw(error_code);
10 use MogileFS::Test;
12 BEGIN {
13     $ENV{TESTING} = 1;
16 find_mogclient_or_skip();
18 # use mogadm to init it,
19 # mogstored on temp dir,
20 # register mogstored temp dir,
21 # mogilefsd startup,
22 # add file,
23 # etc
25 my $sto = eval { temp_store(); };
26 if (!$sto) {
27     plan skip_all => "Can't create temporary test database: $@";
28     exit 0;
31 my $dbh = $sto->dbh;
32 my $rv;
34 my ($hostA_ip, $hostB_ip, $hostC_ip) = (qw/127.0.1.1 127.0.1.2 127.0.1.3/);
36 use File::Temp;
37 my %mogroot;
38 $mogroot{1} = File::Temp::tempdir( CLEANUP => 1 );
39 $mogroot{2} = File::Temp::tempdir( CLEANUP => 1 );
40 $mogroot{3} = File::Temp::tempdir( CLEANUP => 1 );
41 my $dev2host = { 1 => 1, 2 => 1,
42                  3 => 2, 4 => 2,
43                  5 => 3, 6 => 3, };
44 foreach (sort { $a <=> $b } keys %$dev2host) {
45     my $root = $mogroot{$dev2host->{$_}};
46     mkdir("$root/dev$_") or die "Failed to create dev$_ dir: $!";
49 my $ms1 = create_mogstored($hostA_ip, $mogroot{1});
50 ok($ms1, "got mogstored1");
51 my $ms2 = create_mogstored($hostB_ip, $mogroot{2});
52 ok($ms2, "got mogstored2");
54 while (! -e "$mogroot{1}/dev1/usage" &&
55        ! -e "$mogroot{2}/dev4/usage") {
56     print "Waiting on usage...\n";
57     sleep 1;
60 my $tmptrack = create_temp_tracker($sto);
61 ok($tmptrack);
63 my $mogc = MogileFS::Client->new(
64                                  domain => "testdom",
65                                  hosts  => [ "127.0.0.1:7001" ],
66                                  );
67 my $be = $mogc->{backend}; # gross, reaching inside of MogileFS::Client
69 my $lasttime = 1167609600; # Mon Jan  1 00:00:00 UTC 2007
70 ok(try_for(3, sub {
71     my $timestamp = $dbh->selectrow_array("SELECT ".$sto->unix_timestamp);
72     # FIXME: Some databases might be pedantic about the FROM
73     # but having it on others means that if the table has no rows
74     # we won't get any results!
75     my $rv = $timestamp > $lasttime;
76     $lasttime = $timestamp;
77     return $rv;
78 }), "Store provides sane unix_timestamp");
80 # test some basic commands to backend
81 ok($be->do_request("test", {}), "test ping worked");
82 ok(!$be->do_request("test", {crash => 1}), "crash didn't");
83 ok($be->do_request("test", {}), "test ping again worked");
86     my $c = IO::Socket::INET->new(PeerAddr => '127.0.0.1:7001', Timeout => 3);
87     $c->syswrite("!want 1 queryworker\r\n");
88     my $res1 = <$c> . <$c>;
89     like($res1, qr/Now desiring 1 children doing 'queryworker'/, "set 1 queryworker");
91     my $expect = "ERR no_domain No+domain+provided\r\n" x 2;
93     # bad domain won't return twice
94     my $cmd = "list_keys domain=\r\n";
95     $c->syswrite($cmd x 2);
96     my $r;
97     my $resp = "";
98     do {
99         $r = $c->sysread(my $buf, 500);
100         if (defined $r && $r > 0) {
101             $resp .= $buf;
102         }
103     } while ($r && length($resp) != length($expect));
104     is($resp, $expect, "response matches expected");
106     $c->syswrite("!want 2 queryworker\r\n");
107     my $res2 = <$c> . <$c>;
108     like($res2, qr/Now desiring 2 children doing 'queryworker'/, "restored 2 queryworkers");
111 ok($tmptrack->mogadm("domain", "add", "todie"), "created todie domain");
112 ok($tmptrack->mogadm("domain", "delete", "todie"), "delete todie domain");
113 ok(!$tmptrack->mogadm("domain", "delete", "todie"), "didn't delete todie domain again");
115 ok($tmptrack->mogadm("domain", "add", "hasclass"), "created hasclass domain");
116 ok($tmptrack->mogadm("class", "add", "hasclass", "nodel"), "created nodel class");
117 ok(!$tmptrack->mogadm("domain", "delete", "hasclass"), "didn't delete hasclass domain");
118 ok($tmptrack->mogadm("class", "delete", "hasclass", "nodel"), "created nodel class");
119 ok($tmptrack->mogadm("domain", "delete", "hasclass"), "didn't delete hasclass domain");
121 ok($tmptrack->mogadm("domain", "add", "testdom"), "created test domain");
122 ok($tmptrack->mogadm("class", "add", "testdom", "1copy", "--mindevcount=1"), "created 1copy class in testdom");
123 ok($tmptrack->mogadm("class", "add", "testdom", "2copies", "--mindevcount=2"), "created 2copies class in testdom");
124 ok($tmptrack->mogadm("class", "add", "testdom", "poltest", "--replpolicy=MultipleHosts(3)"),
125     "created a specific policy class");
127 ok($tmptrack->mogadm("host", "add", "hostA", "--ip=$hostA_ip", "--status=alive"), "created hostA");
128 ok($tmptrack->mogadm("host", "add", "hostB", "--ip=$hostB_ip", "--status=alive"), "created hostB");
130 ok($tmptrack->mogadm("device", "add", "hostA", 1), "created dev1 on hostA");
131 ok($tmptrack->mogadm("device", "add", "hostA", 2), "created dev2 on hostA");
132 ok($tmptrack->mogadm("device", "add", "hostB", 3), "created dev3 on hostB");
133 ok($tmptrack->mogadm("device", "add", "hostB", 4), "created dev4 on hostB");
135 #ok($tmptrack->mogadm("device", "mark", "hostA", 1, "alive"), "dev1 alive");
136 #ok($tmptrack->mogadm("device", "mark", "hostA", 2, "alive"), "dev2 alive");
137 #ok($tmptrack->mogadm("device", "mark", "hostB", 3, "alive"), "dev3 alive");
138 #ok($tmptrack->mogadm("device", "mark", "hostB", 4, "alive"), "dev4 alive");
140 # wait for monitor
142     my $was = $be->{timeout};  # can't use local on phash :(
143     $be->{timeout} = 10;
144     ok($be->do_request("clear_cache", {}), "waited for monitor")
145         or die "Failed to wait for monitor";
146     ok($be->do_request("clear_cache", {}), "waited for monitor")
147         or die "Failed to wait for monitor";
148     $be->{timeout} = $was;
152     my $fh = $mogc->new_file('no_content', "2copies");
153     die "Error: " . $mogc->errstr unless $fh;
154     ok(close($fh), "closed file");
158     my $fh = $mogc->new_file('no_content', "2copies");
159     die "Error: " . $mogc->errstr unless $fh;
160     ok(close($fh), "closed file");
163 # wait for it to replicate
164 ok(try_for(10, sub {
165     my @urls = $mogc->get_paths("no_content");
166     my $nloc = @urls;
167     if ($nloc < 2) {
168         diag("no_content still only on $nloc devices");
169         return 0;
170     }
171     return 1;
172 }), "replicated to 2 paths");
174 ok(try_for(3, sub {
175     my $to_repl_rows = $dbh->selectrow_array("SELECT COUNT(*) FROM file_to_replicate");
176     return $to_repl_rows == 0;
177 }), "no more files to replicate");
179 # quick delete test
180 ok($mogc->delete("no_content"), "deleted no_content")
181     or die "Error: " . $mogc->errstr;
183 # create two sample files
184 my $data = "My test file.\n" x 1024;
185 foreach my $k (qw(file1 file2)) {
186     my $fh = $mogc->new_file($k, "2copies");
187     ok($fh, "got filehandle") or
188         die "Error: " . $mogc->errstr;
189     print $fh $data;
190     ok(close($fh), "closed file");
193 # quick delete test
194 ok($mogc->delete("file2"), "deleted file2")
195     or die "Error: " . $mogc->errstr;
197 # verify we can't delete the domain now
198 ok(!$tmptrack->mogadm("domain", "delete", "testdom"), "can't delete domain in use");
200 # wait for it to replicate
201 my @urls;
202 ok(try_for(10, sub {
203     @urls = $mogc->get_paths("file1");
204     my $nloc = @urls;
205     if ($nloc < 2) {
206         diag("file1 still only on $nloc devices");
207         return 0;
208     }
209     return 1;
210 }), "replicated to 2 paths");
212 ok(try_for(3, sub {
213     my $to_repl_rows = $dbh->selectrow_array("SELECT COUNT(*) FROM file_to_replicate");
214     return $to_repl_rows == 0;
215 }), "no more files to replicate");
217 my $p1 = MogPath->new($urls[0]);
218 my $p2 = MogPath->new($urls[1]);
219 isnt($p1->host, $p2->host, "host1 and host2 are different");
220 my $path1 = $mogroot{$dev2host->{$p1->device}} . $p1->path;
221 my $path2 = $mogroot{$dev2host->{$p2->device}} . $p2->path;
222 is(-s $path1, length($data), "right length on disk for path1");
223 is(-s $path2, length($data), "right length on disk for path2");
225 ok(unlink($path1), "deleted path $path1");
226 my $dead_url = $urls[0];
227 for (1..10) {
228     @urls = $mogc->get_paths("file1");
229     isnt($urls[0], $dead_url, "didn't return dead url first (try $_)");
232 # Tests for updateclass command
234     my $fh = $mogc->new_file('file1copy', "1copy");
235     ok($fh, "got filehandle") or
236         die "Error: " . $mogc->errstr;
237     print $fh 'EXAMPLE DATA';
238     ok(close($fh), "closed file");
240     is scalar($mogc->get_paths("file1copy")), 1, 'File is on 1 device';
242     $mogc->update_class('2copies');
244     # wait for it to replicate
245     ok(try_for(10, sub {
246         my @urls = $mogc->get_paths("file1copy");
247         my $nloc = @urls;
248         if ($nloc < 1) {
249             diag("no_content still only on $nloc devices");
250             return 0;
251         }
252         return 1;
253     }), "replicated to 2 paths");
255     ok($mogc->delete("file1copy"), "deleted updateclass testfile file1copy")
256         or die "Error: " . $mogc->errstr;
259 ok($be->do_request("rename", {
260     from_key => "file1",
261     to_key   => "file1renamed",
262     domain   => "testdom",
263 }), "renamed file1 to file1renamed");
265 ok($be->do_request("delete", {
266     key    => "file1renamed",
267     domain => "testdom",
268 }), "deleted file1renamed");
270 # create a couple hundred files now
271 my $n_files = 100;
272 diag("Creating $n_files files...");
273 for my $n (1..$n_files) {
274     my $fh = $mogc->new_file("manyhundred_$n", "2copies")
275         or die "Failed to create manyhundred_$n: " . $mogc->errstr;
276     my $data = "File number $n.\n" x 512;
277     print $fh $data;
278     close($fh) or die "Failed to close manyhundred_$n";
279     diag("created $n/$n_files") if $n % 10 == 0;
281 pass("Created a ton of files");
283 # wait for replication to go down
285     my $iters = 30;
286     my $to_repl_rows;
287     while ($iters) {
288         $iters--;
289         $to_repl_rows = $dbh->selectrow_array("SELECT COUNT(*) FROM file_to_replicate");
290         last if ! $to_repl_rows;
291         diag("Files to replicate: $to_repl_rows");
292         sleep 1;
293     }
294     die "Failed to replicate all $n_files files" if $to_repl_rows;
295     pass("Replicated all $n_files files");
298 # now let's delete a host, which should fail hard, because there are still devices attached to it
300     die "Can't delete an active host" if
301         $tmptrack->mogadm("host", "delete", "hostB");
302     pass("didn't delete hostB");
305 # create a new host and device, for when we start killing some devices
306 my $ms3 = create_mogstored($hostC_ip, $mogroot{3});
307 ok($ms3, "got mogstored3");
308 ok($tmptrack->mogadm("host", "add", "hostC", "--ip=$hostC_ip", "--status=alive"), "created hostC");
309 ok($tmptrack->mogadm("device", "add", "hostC", 5), "created dev5 on hostC");
310 ok($tmptrack->mogadm("device", "add", "hostC", 6), "created dev6 on hostC");
312 ok($tmptrack->mogadm("device", "mark", "hostB", 3, "dead"), "marked device B/3 dead");
313 ok($tmptrack->mogadm("device", "mark", "hostB", 4, "dead"), "marked device B/4 dead");
315 ok(try_for(40, sub {
316     my %has;
317     my $sth = $dbh->prepare("SELECT devid, COUNT(*) FROM file_on GROUP BY devid");
318     $sth->execute;
319     while (my ($devid, $ct) = $sth->fetchrow_array) {
320         $has{$devid} = $ct;
321     }
322     diag("Replication update: " . join(", ", map { "dev$_: " . sprintf("%3d", ($has{$_}||0)) } (1..6)));
323     return 0 if $has{3} || $has{4};
324     return $has{1} && $has{1} && $has{5} && $has{6};
325 }), "files replicated to hostC from hostB");
327 # kill hostB now
328 # hosts are no longer able to be nuked even if they have deleted devices.
329 # this saves us from some subtle bugs.
330 #ok($tmptrack->mogadm("host", "delete", "hostB"), "killed hostB");
332 # delete them all, see if they go away.
333 for my $n (1..$n_files) {
334     my $rv = $mogc->delete("manyhundred_$n")
335         or die "Failed to delete manyhundred_$n";
337 pass("deleted all $n_files files");
339 ok(try_for(25, sub {
340     my @files;
341     foreach my $hn (1, 3) {
342         my @lfiles = `find $mogroot{$hn} -type f -name '*.fid'`;
343         push @files, @lfiles;
344         diag("files on host $hn = " . scalar(@lfiles));
345     }
346     return @files == 0;
347 }), "and they're gone from filesystem");
349 foreach my $t (qw(file file_on file_to_delete)) {
350     ok(try_for(5, sub {
351         return $dbh->selectrow_array("SELECT COUNT(*) FROM $t") == 0;
352     }), "table $t is empty");
355 # Test some broken client modes.
357     my $c = IO::Socket::INET->new(PeerAddr => '127.0.0.1:7001',
358         Timeout => 3);
359     die "Failed to connect to test tracker" unless $c;
360     # Pretend to upload a file, then tell the server weird things.
361     # Not trying to be defensable to all sorts of things, but ensuring we're
362     # safe against double close, bad destdev, etc.
363     print $c "create_open "
364         . "domain=testdom&fid=0&class=&multi_dest=1&key=fufufu\n";
365     my $res = <$c>;
366     my $fidid;
367     ok($res =~ m/fid=(\d+)/, "bare create_open worked");
368     $fidid = $1;
369     # Pretend we uploaded something.
370     print $c "create_close "
371         . "domain=testdom&fid=$fidid&devid=4&size=0&key=fufufu"
372         . "&path=http://127.0.1.2:7500/dev4/0/000/000/0000000$fidid.fid\n";
373     my $res2 = <$c>;
374     ok($res2 =~ m/invalid_destdev/, "cannot upload to unlisted destdev");
376     # TODO: test double closing, etc.
379 # give an explicit fid, to prevent bugs like the reason behind
380 # commit ac5534a0c3d046e660fa7581c9173857f182bd81
381 # This is functionality is a bad idea otherwise.
383     my $expfid = 2147483632;
384     my $opts = { fid => $expfid };
385     my $fh = $mogc->new_file("explicit_fid", "1copy", 2, $opts);
386     die "Error: " . $mogc->errstr unless $fh;
387     ok((print $fh "hi" ), "wrote 2 bytes");
388     ok(close($fh), "closed file");
389     my $info = $mogc->file_info("explicit_fid");
391     is($info->{devcount}, 1, "devcount is 1");
392     is($info->{fid}, $opts->{fid}, "explicit fid is correctly set");
395 done_testing();