9 find_mogclient_or_skip();
11 my $sto = eval { temp_store(); };
13 plan skip_all => "Can't create temporary test database: $@";
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");
33 print "Waiting on usage...\n";
34 -e "$mogroot{1}/dev1/usage" && -e "$mogroot{2}/dev2/usage";
37 my $tmptrack = create_temp_tracker($sto);
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(
45 hosts => [ "127.0.0.1:7001" ],
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 {
62 my $was = $be->{timeout}; # can't use local on phash :(
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;
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;
86 use Digest::MD5 qw/md5_hex/;
88 # verify upload checksum
91 %opts = ( domain => "testdom", class => "1copy", key => $key );
92 $rv = $be->do_request("create_open", \%opts);
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");
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
112 %opts = ( domain => "testdom", class => "1copy", key => $key );
113 $rv = $be->do_request("create_open", \%opts);
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");
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);
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");
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
177 @paths = $mogc->get_paths($key);
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
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");
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");
224 @paths = $mogc->get_paths($key);
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);
243 $info = $mogc->file_info($key);
244 $info->{checksum} ne "MISSING";
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);
270 @fsck_log = $sto->fsck_log_rows;
271 scalar(@fsck_log) != 0;
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");
279 @paths = $mogc->get_paths($key);
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);
312 @fsck_log = $sto->fsck_log_rows;
313 scalar(@fsck_log) != 0;
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");
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';
345 @paths = $mogc->get_paths($key);
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);
372 @fsck_log = $sto->fsck_log_rows;
373 scalar(@fsck_log) != 0;
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);
400 @fsck_log = $sto->fsck_log_rows;
401 scalar(@fsck_log) != 0;
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
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);
431 $nr = $sto->file_queue_length(FSCK_QUEUE);
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);
449 @fsck_log = $sto->fsck_log_rows;
450 scalar(@fsck_log) != 0;
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");