t/50-checksum.t: use common try_for() function
[MogileFS-Server.git] / t / 50-checksum.t
blobb67192416a92f5087643ade1c932b9e28380387c
1 # -*-perl-*-
2 use strict;
3 use warnings;
4 use Test::More;
5 use FindBin qw($Bin);
6 use MogileFS::Server;
7 use MogileFS::Test;
8 use HTTP::Request;
9 find_mogclient_or_skip();
11 my $sto = eval { temp_store(); };
12 if (!$sto) {
13     plan skip_all => "Can't create temporary test database: $@";
14     exit 0;
17 use File::Temp;
18 my %mogroot;
19 $mogroot{1} = File::Temp::tempdir( CLEANUP => 1 );
20 $mogroot{2} = File::Temp::tempdir( CLEANUP => 1 );
21 my $dev2host = { 1 => 1, 2 => 2, };
22 foreach (sort { $a <=> $b } keys %$dev2host) {
23     my $root = $mogroot{$dev2host->{$_}};
24     mkdir("$root/dev$_") or die "Failed to create dev$_ dir: $!";
27 my $ms1 = create_mogstored("127.0.1.1", $mogroot{1});
28 ok($ms1, "got mogstored1");
29 my $ms2 = create_mogstored("127.0.1.2", $mogroot{2});
30 ok($ms1, "got mogstored2");
32 try_for(30, sub {
33     print "Waiting on usage...\n";
34     -e "$mogroot{1}/dev1/usage" && -e "$mogroot{2}/dev2/usage";
35 });
37 my $tmptrack = create_temp_tracker($sto);
38 ok($tmptrack);
40 my $admin = IO::Socket::INET->new(PeerAddr => '127.0.0.1:7001');
41 $admin or die "failed to create admin socket: $!";
42 my $moga = MogileFS::Admin->new(hosts => [ "127.0.0.1:7001" ]);
43 my $mogc = MogileFS::Client->new(
44                                  domain => "testdom",
45                                  hosts  => [ "127.0.0.1:7001" ],
46                                  );
47 my $be = $mogc->{backend}; # gross, reaching inside of MogileFS::Client
49 # test some basic commands to backend
50 ok($tmptrack->mogadm("domain", "add", "testdom"), "created test domain");
51 ok($tmptrack->mogadm("class", "add", "testdom", "2copies", "--mindevcount=2"), "created 2copies class in testdom");
52 ok($tmptrack->mogadm("class", "add", "testdom", "1copy", "--mindevcount=1"), "created 1copy class in testdom");
54 ok($tmptrack->mogadm("host", "add", "hostA", "--ip=127.0.1.1", "--status=alive"), "created hostA");
55 ok($tmptrack->mogadm("host", "add", "hostB", "--ip=127.0.1.2", "--status=alive"), "created hostB");
57 ok($tmptrack->mogadm("device", "add", "hostA", 1), "created dev1 on hostA");
58 ok($tmptrack->mogadm("device", "add", "hostB", 2), "created dev2 on hostB");
60 sub wait_for_monitor {
61     my $be = shift;
62     my $was = $be->{timeout};  # can't use local on phash :(
63     $be->{timeout} = 10;
64     ok($be->do_request("clear_cache", {}), "waited for monitor")
65         or die "Failed to wait for monitor";
66     ok($be->do_request("clear_cache", {}), "waited for monitor")
67         or die "Failed to wait for monitor";
68     $be->{timeout} = $was;
71 sub full_fsck {
72     my $tmptrack = shift;
74     ok($tmptrack->mogadm("fsck", "stop"), "stop fsck");
75     ok($tmptrack->mogadm("fsck", "clearlog"), "clear fsck log");
76     ok($tmptrack->mogadm("fsck", "reset"), "reset fsck");
77     ok($tmptrack->mogadm("fsck", "start"), "started fsck");
80 wait_for_monitor($be);
82 my ($req, $rv, %opts, @paths, @fsck_log);
83 my $ua = LWP::UserAgent->new;
85 use Data::Dumper;
86 use Digest::MD5 qw/md5_hex/;
88 # verify upload checksum
90     my $key = "ok";
91     %opts = ( domain => "testdom", class => "1copy", key => $key );
92     $rv = $be->do_request("create_open", \%opts);
93     %opts = %$rv;
94     ok($rv && $rv->{path}, "create_open succeeded");
95     $req = HTTP::Request->new(PUT => $rv->{path});
96     $req->content("blah");
97     $rv = $ua->request($req);
98     ok($rv->is_success, "PUT successful");
99     $opts{key} = $key;
100     $opts{domain} = "testdom";
101     $opts{checksum} = "MD5:".md5_hex('blah');
102     $opts{checksumverify} = 1;
103     $rv = $be->do_request("create_close", \%opts);
104     ok($rv, "checksum verified successfully");
105     is($sto->get_checksum($opts{fid}), undef, "checksum not saved");
106     ok($mogc->file_info($key), "file_info($key) is sane");
109 # corrupted upload checksum fails
111     my $key = 'corrupt';
112     %opts = ( domain => "testdom", class => "1copy", key => $key );
113     $rv = $be->do_request("create_open", \%opts);
114     %opts = %$rv;
115     ok($rv && $rv->{path}, "create_open succeeded");
116     $req = HTTP::Request->new(PUT => $rv->{path});
117     $req->content("blah");
118     $rv = $ua->request($req);
119     ok($rv->is_success, "PUT successful");
120     $opts{key} = $key;
121     $opts{domain} = "testdom";
123     $opts{checksumverify} = 1;
124     $opts{checksum} = "MD5:".md5_hex('fail');
125     $rv = $be->do_request("create_close", \%opts);
126     ok(!defined($rv), "checksum verify noticed mismatch");
127     my $hex = md5_hex('blah');
128     is('checksum_mismatch', $be->{lasterr}, "error code is correct");
129     ok($be->{lasterrstr} =~ /actual: MD5:$hex;/, "error message shows actual:");
130     is($sto->get_checksum($opts{fid}), undef, "checksum not saved");
131     is($mogc->file_info($key), undef, "$key not uploaded");
134 # enable saving MD5 checksums in "2copies" class
136     %opts = ( domain => "testdom", class => "2copies",
137               hashtype => "MD5", mindevcount => 2 );
138     ok($be->do_request("update_class", \%opts), "update class");
139     wait_for_monitor($be);
142 # save new row to checksum table
144     my $key = 'savecksum';
146     syswrite($admin, "!want 0 replicate\n"); # disable replication
147     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "disabled replicate");
149     %opts = ( domain => "testdom", class => "2copies", key => $key );
150     $rv = $be->do_request("create_open", \%opts);
151     %opts = %$rv;
152     ok($rv && $rv->{path}, "create_open succeeded");
153     $req = HTTP::Request->new(PUT => $rv->{path});
154     $req->content("blah");
155     $rv = $ua->request($req);
156     ok($rv->is_success, "PUT successful");
157     $opts{key} = $key;
158     $opts{domain} = "testdom";
159     $opts{checksum} = "MD5:".md5_hex('blah');
160     $opts{checksumverify} = 1;
161     $rv = $be->do_request("create_close", \%opts);
162     ok($rv, "checksum verified successfully");
163     my $row = $sto->get_checksum($opts{fid});
164     ok($row, "checksum saved");
165     my $info = $mogc->file_info($key);
166     ok($info, "file_info($key) is sane");
167     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum shows up');
168     is($sto->delete_checksum($info->{fid}), 1, "$key checksum row deleted");
169     $info = $mogc->file_info($key);
170     is($info->{checksum}, "MISSING", 'checksum is MISSING after delete');
172     syswrite($admin, "!want 1 replicate\n"); # disable replication
173     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "enabled replicate");
175     # wait for replicate to recreate checksum
176     try_for(30, sub {
177         @paths = $mogc->get_paths($key);
178         scalar(@paths) != 1;
179     });
180     is(scalar(@paths), 2, "replicate successfully with good checksum");
182     $info = $mogc->file_info($key);
183     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum recreated on repl');
186 # flip checksum classes around
188     my @classes;
189     %opts = ( domain => "testdom", class => "1copy", mindevcount => 1 );
191     $opts{hashtype} = "NONE";
192     ok($be->do_request("update_class", \%opts), "update class");
193     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
194     is($classes[0]->{hashtype}, undef, "hashtype unset");
196     $opts{hashtype} = "MD5";
197     ok($be->do_request("update_class", \%opts), "update class");
198     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
199     is($classes[0]->{hashtype}, 1, "hashtype is 1 (MD5)");
201     $opts{hashtype} = "NONE";
202     ok($be->do_request("update_class", \%opts), "update class");
203     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
204     is($classes[0]->{hashtype}, undef, "hashtype unset");
207 # save checksum on replicate, client didn't care to provide one
209     my $key = 'lazycksum';
211     syswrite($admin, "!want 0 replicate\n"); # disable replication
212     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "disabled replicate");
214     my $fh = $mogc->new_file($key, "2copies");
215     print $fh "lazy";
216     ok(close($fh), "closed file");
217     my $info = $mogc->file_info($key);
218     is($info->{checksum}, 'MISSING', 'checksum is MISSING');
220     syswrite($admin, "!want 1 replicate\n"); # disable replication
221     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "enabled replicate");
223     try_for(30, sub {
224         @paths = $mogc->get_paths($key);
225         scalar(@paths) != 1;
226     });
227     is(scalar(@paths), 2, "replicate successfully with good checksum");
229     $info = $mogc->file_info($key);
230     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after repl');
233 # fsck recreates checksum when missing
235     my $key = 'lazycksum';
236     my $info = $mogc->file_info($key);
237     $sto->delete_checksum($info->{fid});
238     $info = $mogc->file_info($key);
239     is($info->{checksum}, "MISSING", "checksum is missing");
240     full_fsck($tmptrack);
242     try_for(30, sub {
243         $info = $mogc->file_info($key);
244         $info->{checksum} ne "MISSING";
245     });
246     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after fsck');
248     @fsck_log = $sto->fsck_log_rows;
249     is(scalar(@fsck_log), 1, "fsck log has one row");
250     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
251     is($fsck_log[0]->{evcode}, "NSUM", "missing checksum logged");
254 # fsck fixes a file corrupt file
256     my $key = 'lazycksum';
257     my $info = $mogc->file_info($key);
258     @paths = $mogc->get_paths($key);
259     is(scalar(@paths), 2, "2 paths for lazycksum");
260     $req = HTTP::Request->new(PUT => $paths[0]);
261     $req->content("LAZY");
262     $rv = $ua->request($req);
263     ok($rv->is_success, "upload to corrupt a file successful");
264     is($ua->get($paths[0])->content, "LAZY", "file successfully corrupted");
265     is($ua->get($paths[1])->content, "lazy", "paths[1] not corrupted");
267     full_fsck($tmptrack);
269     try_for(30, sub {
270         @fsck_log = $sto->fsck_log_rows;
271         scalar(@fsck_log) != 0;
272     });
274     is(scalar(@fsck_log), 1, "fsck log has one row");
275     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
276     is($fsck_log[0]->{evcode}, "REPL", "repl for mismatched checksum logged");
278     try_for(30, sub {
279         @paths = $mogc->get_paths($key);
280         scalar(@paths) >= 2;
281     });
283     is(scalar(@paths), 2, "2 paths for key after replication");
284     is($ua->get($paths[0])->content, "lazy", "paths[0] is correct");
285     is($ua->get($paths[1])->content, "lazy", "paths[1] is correct");
286     $info = $mogc->file_info($key);
287     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum unchanged after fsck');
290 # fsck notices when all files are corrupt
292     my $key = 'lazycksum';
293     my $info = $mogc->file_info($key);
294     @paths = $mogc->get_paths($key);
295     is(scalar(@paths), 2, "2 paths for lazycksum");
297     $req = HTTP::Request->new(PUT => $paths[0]);
298     $req->content("0000");
299     $rv = $ua->request($req);
300     ok($rv->is_success, "upload to corrupt a file successful");
301     is($ua->get($paths[0])->content, "0000", "successfully corrupted");
303     $req = HTTP::Request->new(PUT => $paths[1]);
304     $req->content("1111");
305     $rv = $ua->request($req);
306     ok($rv->is_success, "upload to corrupt a file successful");
307     is($ua->get($paths[1])->content, "1111", "successfully corrupted");
309     full_fsck($tmptrack);
311     try_for(30, sub {
312         @fsck_log = $sto->fsck_log_rows;
313         scalar(@fsck_log) != 0;
314     });
316     is(scalar(@fsck_log), 1, "fsck log has one row");
317     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
318     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
320     @paths = $mogc->get_paths($key);
321     is(scalar(@paths), 2, "2 paths for checksum");
322     @paths = sort( map { $ua->get($_)->content } @paths);
323     is(join(', ', @paths), "0000, 1111", "corrupted content preserved");
326 # reuploaded checksum row clobbers old checksum
328     my $key = 'lazycksum';
329     my $info = $mogc->file_info($key);
331     ok($sto->get_checksum($info->{fid}), "old checksum row exists");
333     my $fh = $mogc->new_file($key, "2copies");
334     print $fh "HAZY";
335     ok(close($fh), "closed replacement file (lazycksum => HAZY)");
337     try_for(30, sub { ! $sto->get_checksum($info->{fid}); });
338     is($sto->get_checksum($info->{fid}), undef, "old checksum is gone");
341 # completely corrupted files with no checksum row
343     my $key = 'lazycksum';
344     try_for(30, sub {
345         @paths = $mogc->get_paths($key);
346         scalar(@paths) >= 2;
347     });
348     is(scalar(@paths), 2, "replicated succesfully");
350     my $info = $mogc->file_info($key);
351     is($info->{checksum}, "MD5:".md5_hex("HAZY"), "checksum created on repl");
353     $req = HTTP::Request->new(PUT => $paths[0]);
354     $req->content("MAYB");
355     $rv = $ua->request($req);
356     ok($rv->is_success, "upload to corrupt a file successful");
357     is($ua->get($paths[0])->content, "MAYB", "successfully corrupted (MAYB)");
359     $req = HTTP::Request->new(PUT => $paths[1]);
360     $req->content("CRZY");
361     $rv = $ua->request($req);
362     ok($rv->is_success, "upload to corrupt a file successful");
363     is($ua->get($paths[1])->content, "CRZY", "successfully corrupted (CRZY)");
365     is($sto->delete_checksum($info->{fid}), 1, "nuke new checksum");
366     $info = $mogc->file_info($key);
367     is($info->{checksum}, "MISSING", "checksum is MISSING");
369     full_fsck($tmptrack);
371     try_for(30, sub {
372         @fsck_log = $sto->fsck_log_rows;
373         scalar(@fsck_log) != 0;
374     });
376     is(scalar(@fsck_log), 1, "fsck log has one row");
377     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
378     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
381 # disable MD5 checksums in "2copies" class
383     %opts = ( domain => "testdom", class => "2copies",
384               hashtype => "NONE", mindevcount => 2 );
385     ok($be->do_request("update_class", \%opts), "update class");
386     wait_for_monitor($be);
389 # use fsck_checksum=MD5 instead of per-class checksums
391     my $key = 'lazycksum';
392     my $info = $mogc->file_info($key);
393     $sto->delete_checksum($info->{fid});
395     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "MD5"), "enable fsck_checksum=MD5");
396     wait_for_monitor($be);
397     full_fsck($tmptrack);
399     try_for(30, sub {
400         @fsck_log = $sto->fsck_log_rows;
401         scalar(@fsck_log) != 0;
402     });
403     is(scalar(@fsck_log), 1, "fsck log has one row");
404     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
405     is($fsck_log[0]->{evcode}, "MSUM", "MSUM logged");
408 # ensure server setting is visible
409 use MogileFS::Admin;
411     my $settings = $moga->server_settings;
412     is($settings->{fsck_checksum}, 'MD5', "fsck_checksum server setting visible");
415 use MogileFS::Config;
417 # disable checksumming entirely, regardless of class setting
419     %opts = ( domain => "testdom", class => "2copies",
420               hashtype => "MD5", mindevcount => 2 );
421     ok($be->do_request("update_class", \%opts), "update class");
422     wait_for_monitor($be);
424     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "off"), "set fsck_checksum=off");
425     wait_for_monitor($be);
426     my $settings = $moga->server_settings;
427     is($settings->{fsck_checksum}, 'off', "fsck_checksum server setting visible");
428     full_fsck($tmptrack);
429     my $nr;
430     try_for(1000, sub {
431         $nr = $sto->file_queue_length(FSCK_QUEUE);
432         $nr eq '0';
433     });
434     is($nr, '0', "fsck finished");
435     @fsck_log = $sto->fsck_log_rows;
436     is(scalar(@fsck_log), 0, "fsck log is empty with fsck_checksum=off");
439 # set fsck_checksum=class and ensure that works again
441     my $info = $mogc->file_info('lazycksum');
442     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "class"), "set fsck_checksum=class");
443     wait_for_monitor($be);
444     my $settings = $moga->server_settings;
445     ok(! defined($settings->{fsck_checksum}), "fsck_checksum=class server setting hidden (default)");
446     full_fsck($tmptrack);
448     try_for(30, sub {
449         @fsck_log = $sto->fsck_log_rows;
450         scalar(@fsck_log) != 0;
451     });
453     is(scalar(@fsck_log), 1, "fsck log has one row");
454     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
455     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
458 done_testing();