worker: set monitor_has_run flag at initialization
[MogileFS-Server.git] / t / 50-checksum.t
blobc9450b6f9ae07245c0362a58dff182ea33ed9d3d
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 stop_replicate {
72     my ($admin) = @_;
73     syswrite($admin, "!want 0 replicate\r\n"); # disable replication
74     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "disabling replicate");
76     my $count;
77     try_for(30, sub {
78         $count = -1;
79         syswrite($admin, "!jobs\r\n");
80         MogileFS::Util::wait_for_readability(fileno($admin), 10);
81         while (1) {
82             my $line = <$admin>;
83             if ($line =~ /\Areplicate count (\d+)/) {
84                 $count = $1;
85             }
86             last if $line eq ".\r\n";
87         }
88         $count == 0;
89     });
90     is($count, 0, "replicate count is zero");
93 sub full_fsck {
94     my $tmptrack = shift;
96     ok($tmptrack->mogadm("fsck", "stop"), "stop fsck");
97     ok($tmptrack->mogadm("fsck", "clearlog"), "clear fsck log");
98     ok($tmptrack->mogadm("fsck", "reset"), "reset fsck");
99     ok($tmptrack->mogadm("fsck", "start"), "started fsck");
102 wait_for_monitor($be);
104 my ($req, $rv, %opts, @paths, @fsck_log);
105 my $ua = LWP::UserAgent->new;
107 use Data::Dumper;
108 use Digest::MD5 qw/md5_hex/;
110 # verify upload checksum
112     my $key = "ok";
113     %opts = ( domain => "testdom", class => "1copy", key => $key );
114     $rv = $be->do_request("create_open", \%opts);
115     %opts = %$rv;
116     ok($rv && $rv->{path}, "create_open succeeded");
117     $req = HTTP::Request->new(PUT => $rv->{path});
118     $req->content("blah");
119     $rv = $ua->request($req);
120     ok($rv->is_success, "PUT successful");
121     $opts{key} = $key;
122     $opts{domain} = "testdom";
123     $opts{checksum} = "MD5:".md5_hex('blah');
124     $opts{checksumverify} = 1;
125     $rv = $be->do_request("create_close", \%opts);
126     ok($rv, "checksum verified successfully");
127     is($sto->get_checksum($opts{fid}), undef, "checksum not saved");
128     ok($mogc->file_info($key), "file_info($key) is sane");
131 # corrupted upload checksum fails
133     my $key = 'corrupt';
134     %opts = ( domain => "testdom", class => "1copy", key => $key );
135     $rv = $be->do_request("create_open", \%opts);
136     %opts = %$rv;
137     ok($rv && $rv->{path}, "create_open succeeded");
138     $req = HTTP::Request->new(PUT => $rv->{path});
139     $req->content("blah");
140     $rv = $ua->request($req);
141     ok($rv->is_success, "PUT successful");
142     $opts{key} = $key;
143     $opts{domain} = "testdom";
145     $opts{checksumverify} = 1;
146     $opts{checksum} = "MD5:".md5_hex('fail');
147     $rv = $be->do_request("create_close", \%opts);
148     ok(!defined($rv), "checksum verify noticed mismatch");
149     my $hex = md5_hex('blah');
150     is('checksum_mismatch', $be->{lasterr}, "error code is correct");
151     ok($be->{lasterrstr} =~ /actual: MD5:$hex;/, "error message shows actual:");
152     is($sto->get_checksum($opts{fid}), undef, "checksum not saved");
153     is($mogc->file_info($key), undef, "$key not uploaded");
156 # enable saving MD5 checksums in "2copies" class
158     %opts = ( domain => "testdom", class => "2copies",
159               hashtype => "MD5", mindevcount => 2 );
160     ok($be->do_request("update_class", \%opts), "update class");
161     wait_for_monitor($be);
164 # save new row to checksum table
166     my $key = 'savecksum';
168     stop_replicate($admin);
170     %opts = ( domain => "testdom", class => "2copies", key => $key );
171     $rv = $be->do_request("create_open", \%opts);
172     %opts = %$rv;
173     ok($rv && $rv->{path}, "create_open succeeded");
174     $req = HTTP::Request->new(PUT => $rv->{path});
175     $req->content("blah");
176     $rv = $ua->request($req);
177     ok($rv->is_success, "PUT successful");
178     $opts{key} = $key;
179     $opts{domain} = "testdom";
180     $opts{checksum} = "MD5:".md5_hex('blah');
181     $opts{checksumverify} = 1;
182     $rv = $be->do_request("create_close", \%opts);
183     ok($rv, "checksum verified successfully");
184     my $row = $sto->get_checksum($opts{fid});
185     ok($row, "checksum saved");
186     my $info = $mogc->file_info($key);
187     ok($info, "file_info($key) is sane");
188     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum shows up');
189     is($sto->delete_checksum($info->{fid}), 1, "$key checksum row deleted");
190     $info = $mogc->file_info($key);
191     is($info->{checksum}, "MISSING", 'checksum is MISSING after delete');
193     syswrite($admin, "!want 1 replicate\n"); # disable replication
194     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "enabled replicate");
196     # wait for replicate to recreate checksum
197     try_for(30, sub {
198         @paths = $mogc->get_paths($key);
199         scalar(@paths) != 1;
200     });
201     is(scalar(@paths), 2, "replicate successfully with good checksum");
203     $info = $mogc->file_info($key);
204     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum recreated on repl');
207 # flip checksum classes around
209     my @classes;
210     %opts = ( domain => "testdom", class => "1copy", mindevcount => 1 );
212     $opts{hashtype} = "NONE";
213     ok($be->do_request("update_class", \%opts), "update class");
214     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
215     is($classes[0]->{hashtype}, undef, "hashtype unset");
217     $opts{hashtype} = "MD5";
218     ok($be->do_request("update_class", \%opts), "update class");
219     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
220     is($classes[0]->{hashtype}, 1, "hashtype is 1 (MD5)");
222     $opts{hashtype} = "NONE";
223     ok($be->do_request("update_class", \%opts), "update class");
224     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
225     is($classes[0]->{hashtype}, undef, "hashtype unset");
228 # save checksum on replicate, client didn't care to provide one
230     my $key = 'lazycksum';
232     stop_replicate($admin);
234     my $fh = $mogc->new_file($key, "2copies");
235     print $fh "lazy";
236     ok(close($fh), "closed file");
237     my $info = $mogc->file_info($key);
238     is($info->{checksum}, 'MISSING', 'checksum is MISSING');
240     syswrite($admin, "!want 1 replicate\n"); # disable replication
241     ok(<$admin> =~ /Now desiring/ && <$admin> eq ".\r\n", "enabled replicate");
243     try_for(30, sub {
244         @paths = $mogc->get_paths($key);
245         scalar(@paths) != 1;
246     });
247     is(scalar(@paths), 2, "replicate successfully with good checksum");
249     $info = $mogc->file_info($key);
250     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after repl');
253 # fsck recreates checksum when missing
255     my $key = 'lazycksum';
256     my $info = $mogc->file_info($key);
257     $sto->delete_checksum($info->{fid});
258     $info = $mogc->file_info($key);
259     is($info->{checksum}, "MISSING", "checksum is missing");
260     full_fsck($tmptrack);
262     try_for(30, sub {
263         $info = $mogc->file_info($key);
264         $info->{checksum} ne "MISSING";
265     });
266     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after fsck');
268     @fsck_log = $sto->fsck_log_rows;
269     is(scalar(@fsck_log), 1, "fsck log has one row");
270     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
271     is($fsck_log[0]->{evcode}, "NSUM", "missing checksum logged");
274 # fsck fixes a file corrupt file
276     my $key = 'lazycksum';
277     my $info = $mogc->file_info($key);
278     @paths = $mogc->get_paths($key);
279     is(scalar(@paths), 2, "2 paths for lazycksum");
280     $req = HTTP::Request->new(PUT => $paths[0]);
281     $req->content("LAZY");
282     $rv = $ua->request($req);
283     ok($rv->is_success, "upload to corrupt a file successful");
284     is($ua->get($paths[0])->content, "LAZY", "file successfully corrupted");
285     is($ua->get($paths[1])->content, "lazy", "paths[1] not corrupted");
287     full_fsck($tmptrack);
289     try_for(30, sub {
290         @fsck_log = $sto->fsck_log_rows;
291         scalar(@fsck_log) != 0;
292     });
294     is(scalar(@fsck_log), 1, "fsck log has one row");
295     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
296     is($fsck_log[0]->{evcode}, "REPL", "repl for mismatched checksum logged");
298     try_for(30, sub {
299         @paths = $mogc->get_paths($key);
300         scalar(@paths) >= 2;
301     });
303     is(scalar(@paths), 2, "2 paths for key after replication");
304     is($ua->get($paths[0])->content, "lazy", "paths[0] is correct");
305     is($ua->get($paths[1])->content, "lazy", "paths[1] is correct");
306     $info = $mogc->file_info($key);
307     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum unchanged after fsck');
310 # fsck notices when all files are corrupt
312     my $key = 'lazycksum';
313     my $info = $mogc->file_info($key);
314     @paths = $mogc->get_paths($key);
315     is(scalar(@paths), 2, "2 paths for lazycksum");
317     $req = HTTP::Request->new(PUT => $paths[0]);
318     $req->content("0000");
319     $rv = $ua->request($req);
320     ok($rv->is_success, "upload to corrupt a file successful");
321     is($ua->get($paths[0])->content, "0000", "successfully corrupted");
323     $req = HTTP::Request->new(PUT => $paths[1]);
324     $req->content("1111");
325     $rv = $ua->request($req);
326     ok($rv->is_success, "upload to corrupt a file successful");
327     is($ua->get($paths[1])->content, "1111", "successfully corrupted");
329     full_fsck($tmptrack);
331     try_for(30, sub {
332         @fsck_log = $sto->fsck_log_rows;
333         scalar(@fsck_log) != 0;
334     });
336     is(scalar(@fsck_log), 1, "fsck log has one row");
337     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
338     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
340     @paths = $mogc->get_paths($key);
341     is(scalar(@paths), 2, "2 paths for checksum");
342     @paths = sort( map { $ua->get($_)->content } @paths);
343     is(join(', ', @paths), "0000, 1111", "corrupted content preserved");
346 # reuploaded checksum row clobbers old checksum
348     my $key = 'lazycksum';
349     my $info = $mogc->file_info($key);
351     ok($sto->get_checksum($info->{fid}), "old checksum row exists");
353     my $fh = $mogc->new_file($key, "2copies");
354     print $fh "HAZY";
355     ok(close($fh), "closed replacement file (lazycksum => HAZY)");
357     try_for(30, sub { ! $sto->get_checksum($info->{fid}); });
358     is($sto->get_checksum($info->{fid}), undef, "old checksum is gone");
361 # completely corrupted files with no checksum row
363     my $key = 'lazycksum';
364     try_for(30, sub {
365         @paths = $mogc->get_paths($key);
366         scalar(@paths) >= 2;
367     });
368     is(scalar(@paths), 2, "replicated succesfully");
370     my $info = $mogc->file_info($key);
371     is($info->{checksum}, "MD5:".md5_hex("HAZY"), "checksum created on repl");
373     $req = HTTP::Request->new(PUT => $paths[0]);
374     $req->content("MAYB");
375     $rv = $ua->request($req);
376     ok($rv->is_success, "upload to corrupt a file successful");
377     is($ua->get($paths[0])->content, "MAYB", "successfully corrupted (MAYB)");
379     $req = HTTP::Request->new(PUT => $paths[1]);
380     $req->content("CRZY");
381     $rv = $ua->request($req);
382     ok($rv->is_success, "upload to corrupt a file successful");
383     is($ua->get($paths[1])->content, "CRZY", "successfully corrupted (CRZY)");
385     is($sto->delete_checksum($info->{fid}), 1, "nuke new checksum");
386     $info = $mogc->file_info($key);
387     is($info->{checksum}, "MISSING", "checksum is MISSING");
389     full_fsck($tmptrack);
391     try_for(30, sub {
392         @fsck_log = $sto->fsck_log_rows;
393         scalar(@fsck_log) != 0;
394     });
396     is(scalar(@fsck_log), 1, "fsck log has one row");
397     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
398     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
401 # disable MD5 checksums in "2copies" class
403     %opts = ( domain => "testdom", class => "2copies",
404               hashtype => "NONE", mindevcount => 2 );
405     ok($be->do_request("update_class", \%opts), "update class");
406     wait_for_monitor($be);
409 # use fsck_checksum=MD5 instead of per-class checksums
411     my $key = 'lazycksum';
412     my $info = $mogc->file_info($key);
413     $sto->delete_checksum($info->{fid});
415     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "MD5"), "enable fsck_checksum=MD5");
416     wait_for_monitor($be);
417     full_fsck($tmptrack);
419     try_for(30, sub {
420         @fsck_log = $sto->fsck_log_rows;
421         scalar(@fsck_log) != 0;
422     });
423     is(scalar(@fsck_log), 1, "fsck log has one row");
424     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
425     is($fsck_log[0]->{evcode}, "MSUM", "MSUM logged");
428 # ensure server setting is visible
429 use MogileFS::Admin;
431     my $settings = $moga->server_settings;
432     is($settings->{fsck_checksum}, 'MD5', "fsck_checksum server setting visible");
435 use MogileFS::Config;
437 # disable checksumming entirely, regardless of class setting
439     %opts = ( domain => "testdom", class => "2copies",
440               hashtype => "MD5", mindevcount => 2 );
441     ok($be->do_request("update_class", \%opts), "update class");
442     wait_for_monitor($be);
444     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "off"), "set fsck_checksum=off");
445     wait_for_monitor($be);
446     my $settings = $moga->server_settings;
447     is($settings->{fsck_checksum}, 'off', "fsck_checksum server setting visible");
448     full_fsck($tmptrack);
449     my $nr;
450     try_for(1000, sub {
451         $nr = $sto->file_queue_length(FSCK_QUEUE);
452         $nr eq '0';
453     });
454     is($nr, '0', "fsck finished");
455     @fsck_log = $sto->fsck_log_rows;
456     is(scalar(@fsck_log), 0, "fsck log is empty with fsck_checksum=off");
459 # set fsck_checksum=class and ensure that works again
461     my $info = $mogc->file_info('lazycksum');
462     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "class"), "set fsck_checksum=class");
463     wait_for_monitor($be);
464     my $settings = $moga->server_settings;
465     ok(! defined($settings->{fsck_checksum}), "fsck_checksum=class server setting hidden (default)");
466     full_fsck($tmptrack);
468     try_for(30, sub {
469         @fsck_log = $sto->fsck_log_rows;
470         scalar(@fsck_log) != 0;
471     });
473     is(scalar(@fsck_log), 1, "fsck log has one row");
474     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
475     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
478 done_testing();