tests: add "!want <count> <jobclass>" helper
[MogileFS-Server.git] / t / 50-checksum.t
blob8e7b0aa8ac2420e26a8ed7e5d54d8b6639abf21f
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     want($admin, 0, "replicate");
148     %opts = ( domain => "testdom", class => "2copies", key => $key );
149     $rv = $be->do_request("create_open", \%opts);
150     %opts = %$rv;
151     ok($rv && $rv->{path}, "create_open succeeded");
152     $req = HTTP::Request->new(PUT => $rv->{path});
153     $req->content("blah");
154     $rv = $ua->request($req);
155     ok($rv->is_success, "PUT successful");
156     $opts{key} = $key;
157     $opts{domain} = "testdom";
158     $opts{checksum} = "MD5:".md5_hex('blah');
159     $opts{checksumverify} = 1;
160     $rv = $be->do_request("create_close", \%opts);
161     ok($rv, "checksum verified successfully");
162     my $row = $sto->get_checksum($opts{fid});
163     ok($row, "checksum saved");
164     my $info = $mogc->file_info($key);
165     ok($info, "file_info($key) is sane");
166     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum shows up');
167     is($sto->delete_checksum($info->{fid}), 1, "$key checksum row deleted");
168     $info = $mogc->file_info($key);
169     is($info->{checksum}, "MISSING", 'checksum is MISSING after delete');
171     want($admin, 1, "replicate");
173     # wait for replicate to recreate checksum
174     try_for(30, sub {
175         @paths = $mogc->get_paths($key);
176         scalar(@paths) != 1;
177     });
178     is(scalar(@paths), 2, "replicate successfully with good checksum");
180     $info = $mogc->file_info($key);
181     is($info->{checksum}, "MD5:".md5_hex('blah'), 'checksum recreated on repl');
184 # flip checksum classes around
186     my @classes;
187     %opts = ( domain => "testdom", class => "1copy", mindevcount => 1 );
189     $opts{hashtype} = "NONE";
190     ok($be->do_request("update_class", \%opts), "update class");
191     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
192     is($classes[0]->{hashtype}, undef, "hashtype unset");
194     $opts{hashtype} = "MD5";
195     ok($be->do_request("update_class", \%opts), "update class");
196     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
197     is($classes[0]->{hashtype}, 1, "hashtype is 1 (MD5)");
199     $opts{hashtype} = "NONE";
200     ok($be->do_request("update_class", \%opts), "update class");
201     @classes = grep { $_->{classname} eq '1copy' } $sto->get_all_classes;
202     is($classes[0]->{hashtype}, undef, "hashtype unset");
205 # save checksum on replicate, client didn't care to provide one
207     my $key = 'lazycksum';
209     want($admin, 0, "replicate");
211     my $fh = $mogc->new_file($key, "2copies");
212     print $fh "lazy";
213     ok(close($fh), "closed file");
214     my $info = $mogc->file_info($key);
215     is($info->{checksum}, 'MISSING', 'checksum is MISSING');
217     want($admin, 1, "replicate");
219     try_for(30, sub {
220         @paths = $mogc->get_paths($key);
221         scalar(@paths) != 1;
222     });
223     is(scalar(@paths), 2, "replicate successfully with good checksum");
225     $info = $mogc->file_info($key);
226     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after repl');
229 # fsck recreates checksum when missing
231     my $key = 'lazycksum';
232     my $info = $mogc->file_info($key);
233     $sto->delete_checksum($info->{fid});
234     $info = $mogc->file_info($key);
235     is($info->{checksum}, "MISSING", "checksum is missing");
236     full_fsck($tmptrack);
238     try_for(30, sub {
239         $info = $mogc->file_info($key);
240         $info->{checksum} ne "MISSING";
241     });
242     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum is set after fsck');
244     @fsck_log = $sto->fsck_log_rows;
245     is(scalar(@fsck_log), 1, "fsck log has one row");
246     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
247     is($fsck_log[0]->{evcode}, "NSUM", "missing checksum logged");
250 # fsck fixes a file corrupt file
252     my $key = 'lazycksum';
253     my $info = $mogc->file_info($key);
254     @paths = $mogc->get_paths($key);
255     is(scalar(@paths), 2, "2 paths for lazycksum");
256     $req = HTTP::Request->new(PUT => $paths[0]);
257     $req->content("LAZY");
258     $rv = $ua->request($req);
259     ok($rv->is_success, "upload to corrupt a file successful");
260     is($ua->get($paths[0])->content, "LAZY", "file successfully corrupted");
261     is($ua->get($paths[1])->content, "lazy", "paths[1] not corrupted");
263     full_fsck($tmptrack);
265     try_for(30, sub {
266         @fsck_log = $sto->fsck_log_rows;
267         scalar(@fsck_log) != 0;
268     });
270     is(scalar(@fsck_log), 1, "fsck log has one row");
271     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
272     is($fsck_log[0]->{evcode}, "REPL", "repl for mismatched checksum logged");
274     try_for(30, sub {
275         @paths = $mogc->get_paths($key);
276         scalar(@paths) >= 2;
277     });
279     is(scalar(@paths), 2, "2 paths for key after replication");
280     is($ua->get($paths[0])->content, "lazy", "paths[0] is correct");
281     is($ua->get($paths[1])->content, "lazy", "paths[1] is correct");
282     $info = $mogc->file_info($key);
283     is($info->{checksum}, "MD5:".md5_hex("lazy"), 'checksum unchanged after fsck');
286 # fsck notices when all files are corrupt
288     my $key = 'lazycksum';
289     my $info = $mogc->file_info($key);
290     @paths = $mogc->get_paths($key);
291     is(scalar(@paths), 2, "2 paths for lazycksum");
293     $req = HTTP::Request->new(PUT => $paths[0]);
294     $req->content("0000");
295     $rv = $ua->request($req);
296     ok($rv->is_success, "upload to corrupt a file successful");
297     is($ua->get($paths[0])->content, "0000", "successfully corrupted");
299     $req = HTTP::Request->new(PUT => $paths[1]);
300     $req->content("1111");
301     $rv = $ua->request($req);
302     ok($rv->is_success, "upload to corrupt a file successful");
303     is($ua->get($paths[1])->content, "1111", "successfully corrupted");
305     full_fsck($tmptrack);
307     try_for(30, sub {
308         @fsck_log = $sto->fsck_log_rows;
309         scalar(@fsck_log) != 0;
310     });
312     is(scalar(@fsck_log), 1, "fsck log has one row");
313     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
314     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
316     @paths = $mogc->get_paths($key);
317     is(scalar(@paths), 2, "2 paths for checksum");
318     @paths = sort( map { $ua->get($_)->content } @paths);
319     is(join(', ', @paths), "0000, 1111", "corrupted content preserved");
322 # reuploaded checksum row clobbers old checksum
324     my $key = 'lazycksum';
325     my $info = $mogc->file_info($key);
327     ok($sto->get_checksum($info->{fid}), "old checksum row exists");
329     my $fh = $mogc->new_file($key, "2copies");
330     print $fh "HAZY";
331     ok(close($fh), "closed replacement file (lazycksum => HAZY)");
333     try_for(30, sub { ! $sto->get_checksum($info->{fid}); });
334     is($sto->get_checksum($info->{fid}), undef, "old checksum is gone");
337 # completely corrupted files with no checksum row
339     my $key = 'lazycksum';
340     try_for(30, sub {
341         @paths = $mogc->get_paths($key);
342         scalar(@paths) >= 2;
343     });
344     is(scalar(@paths), 2, "replicated succesfully");
346     my $info = $mogc->file_info($key);
347     is($info->{checksum}, "MD5:".md5_hex("HAZY"), "checksum created on repl");
349     $req = HTTP::Request->new(PUT => $paths[0]);
350     $req->content("MAYB");
351     $rv = $ua->request($req);
352     ok($rv->is_success, "upload to corrupt a file successful");
353     is($ua->get($paths[0])->content, "MAYB", "successfully corrupted (MAYB)");
355     $req = HTTP::Request->new(PUT => $paths[1]);
356     $req->content("CRZY");
357     $rv = $ua->request($req);
358     ok($rv->is_success, "upload to corrupt a file successful");
359     is($ua->get($paths[1])->content, "CRZY", "successfully corrupted (CRZY)");
361     is($sto->delete_checksum($info->{fid}), 1, "nuke new checksum");
362     $info = $mogc->file_info($key);
363     is($info->{checksum}, "MISSING", "checksum is MISSING");
365     full_fsck($tmptrack);
367     try_for(30, sub {
368         @fsck_log = $sto->fsck_log_rows;
369         scalar(@fsck_log) != 0;
370     });
372     is(scalar(@fsck_log), 1, "fsck log has one row");
373     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
374     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
377 # disable MD5 checksums in "2copies" class
379     %opts = ( domain => "testdom", class => "2copies",
380               hashtype => "NONE", mindevcount => 2 );
381     ok($be->do_request("update_class", \%opts), "update class");
382     wait_for_monitor($be);
385 # use fsck_checksum=MD5 instead of per-class checksums
387     my $key = 'lazycksum';
388     my $info = $mogc->file_info($key);
389     $sto->delete_checksum($info->{fid});
391     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "MD5"), "enable fsck_checksum=MD5");
392     wait_for_monitor($be);
393     full_fsck($tmptrack);
395     try_for(30, sub {
396         @fsck_log = $sto->fsck_log_rows;
397         scalar(@fsck_log) != 0;
398     });
399     is(scalar(@fsck_log), 1, "fsck log has one row");
400     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
401     is($fsck_log[0]->{evcode}, "MSUM", "MSUM logged");
404 # ensure server setting is visible
405 use MogileFS::Admin;
407     my $settings = $moga->server_settings;
408     is($settings->{fsck_checksum}, 'MD5', "fsck_checksum server setting visible");
411 use MogileFS::Config;
413 # disable checksumming entirely, regardless of class setting
415     %opts = ( domain => "testdom", class => "2copies",
416               hashtype => "MD5", mindevcount => 2 );
417     ok($be->do_request("update_class", \%opts), "update class");
418     wait_for_monitor($be);
420     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "off"), "set fsck_checksum=off");
421     wait_for_monitor($be);
422     my $settings = $moga->server_settings;
423     is($settings->{fsck_checksum}, 'off', "fsck_checksum server setting visible");
424     full_fsck($tmptrack);
425     my $nr;
426     try_for(1000, sub {
427         $nr = $sto->file_queue_length(FSCK_QUEUE);
428         $nr eq '0';
429     });
430     is($nr, '0', "fsck finished");
431     @fsck_log = $sto->fsck_log_rows;
432     is(scalar(@fsck_log), 0, "fsck log is empty with fsck_checksum=off");
435 # set fsck_checksum=class and ensure that works again
437     my $info = $mogc->file_info('lazycksum');
438     ok($tmptrack->mogadm("settings", "set", "fsck_checksum", "class"), "set fsck_checksum=class");
439     wait_for_monitor($be);
440     my $settings = $moga->server_settings;
441     ok(! defined($settings->{fsck_checksum}), "fsck_checksum=class server setting hidden (default)");
442     full_fsck($tmptrack);
444     try_for(30, sub {
445         @fsck_log = $sto->fsck_log_rows;
446         scalar(@fsck_log) != 0;
447     });
449     is(scalar(@fsck_log), 1, "fsck log has one row");
450     is($fsck_log[0]->{fid}, $info->{fid}, "fid matches in fsck log");
451     is($fsck_log[0]->{evcode}, "BSUM", "BSUM logged");
454 done_testing();