Imported Upstream version 3.2.0
[debian/amanda] / installcheck / chunker.pl
1 # Copyright (c) 2010 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 60;
20
21 use warnings;
22 use strict;
23
24 use lib '@amperldir@';
25 use Installcheck::Run;
26 use Installcheck::Mock;
27 use IO::Handle;
28 use IPC::Open3;
29 use Data::Dumper;
30 use IO::Socket::INET;
31 use POSIX ":sys_wait_h";
32 use Cwd qw(abs_path);
33 use File::Path;
34
35 use Amanda::Paths;
36 use Amanda::Header qw( :constants );
37 use Amanda::Debug;
38 use Amanda::Holding;
39 use Amanda::Util;
40
41 # put the debug messages somewhere
42 Amanda::Debug::dbopen("installcheck");
43 Installcheck::log_test_output();
44
45 my $test_hdir = "$Installcheck::TMP/chunker-holding";
46 my $test_hfile = "$test_hdir/holder";
47 my $chunker_stderr_file = "$Installcheck::TMP/chunker-stderr";
48 my $debug = !exists $ENV{'HARNESS_ACTIVE'};
49
50 # information on the current run
51 my ($datestamp, $handle);
52 my ($chunker_pid, $chunker_in, $chunker_out, $last_chunker_reply, $chunker_reply_timeout);
53 my $writer_pid;
54
55 sub run_chunker {
56     my ($description, %params) = @_;
57
58     cleanup_chunker();
59
60     diag("******** $description") if $debug;
61
62     my $testconf = Installcheck::Run::setup();
63     $testconf->add_param('debug_chunker', 9);
64     $testconf->write();
65
66     if (exists $params{'ENOSPC_at'}) {
67         diag("setting CHUNKER_FAKE_ENOSPC_AT=$params{ENOSPC_at}") if $debug;
68         $ENV{'CHUNKER_FAKE_ENOSPC_AT'} = $params{'ENOSPC_at'};
69     } else {
70         delete $ENV{'CHUNKER_FAKE_ENOSPC'};
71     }
72
73     open(CHUNKER_ERR, ">", $chunker_stderr_file);
74     $chunker_in = $chunker_out = '';
75     $chunker_pid = open3($chunker_in, $chunker_out, ">&CHUNKER_ERR",
76         "$amlibexecdir/chunker", "TESTCONF");
77     close CHUNKER_ERR;
78     $chunker_in->blocking(1);
79     $chunker_out->autoflush();
80
81     pass("spawned new chunker for 'test $description'");
82
83     # define this to get the installcheck to wait and allow you to attach
84     # a gdb instance to the chunker
85     if ($params{'use_gdb'}) {
86         $chunker_reply_timeout = 0; # no timeouts while debugging
87         diag("attach debugger to pid $chunker_pid and press ENTER");
88         <>;
89     } else {
90         $chunker_reply_timeout = 120;
91     }
92
93     chunker_cmd("START $datestamp");
94 }
95
96 sub wait_for_exit {
97     if ($chunker_pid) {
98         waitpid($chunker_pid, 0);
99         $chunker_pid = undef;
100     }
101 }
102
103 sub cleanup_chunker {
104     -d $test_hdir and rmtree($test_hdir);
105     mkpath($test_hdir);
106
107     # make a small effort to collect zombies
108     if ($chunker_pid) {
109         if (waitpid($chunker_pid, WNOHANG) == $chunker_pid) {
110             $chunker_pid = undef;
111         }
112     }
113     if ($writer_pid) {
114         if (waitpid($writer_pid, WNOHANG) == $writer_pid) {
115             $writer_pid = undef;
116         }
117     }
118 }
119
120 sub wait_for_writer {
121     if ($writer_pid) {
122         if (waitpid($writer_pid, 0) == $writer_pid) {
123             $writer_pid = undef;
124         }
125     }
126 }
127
128 sub chunker_cmd {
129     my ($cmd) = @_;
130
131     diag(">>> $cmd") if $debug;
132     print $chunker_in "$cmd\n";
133 }
134
135 sub chunker_reply {
136     local $SIG{ALRM} = sub { die "Timeout while waiting for reply\n" };
137     alarm($chunker_reply_timeout);
138     $last_chunker_reply = $chunker_out->getline();
139     alarm(0);
140
141     if (!$last_chunker_reply) {
142         die("wrong pid") unless ($chunker_pid == waitpid($chunker_pid, 0));
143         my $exit_status = $?;
144
145         open(my $fh, "<", $chunker_stderr_file) or die("open $chunker_stderr_file: $!");
146         my $stderr = do { local $/; <$fh> };
147         close($fh);
148
149         diag("chunker stderr:\n$stderr") if $stderr;
150         die("chunker (pid $chunker_pid) died unexpectedly with status $exit_status");
151     }
152
153     # trim trailing whitespace -- C chunker outputs an extra ' ' after
154     # single-word replies
155     $last_chunker_reply =~ s/\s*$//;
156     diag("<<< $last_chunker_reply") if $debug;
157
158     return $last_chunker_reply;
159 }
160
161 sub check_logs {
162     my ($expected, $msg) = @_;
163     my $re;
164     my $line;
165
166     # must contain a pid line at the beginning and end
167     unshift @$expected, qr/^INFO chunker chunker pid \d+$/;
168     push @$expected, qr/^INFO chunker pid-done \d+$/;
169
170     open(my $logfile, "<", "$CONFIG_DIR/TESTCONF/log/log")
171         or die("opening log: $!");
172     my @logfile = grep(/^\S+ chunker /, <$logfile>);
173     close($logfile);
174
175     while (@logfile and @$expected) {
176         my $logline = shift @logfile;
177         my $expline = shift @$expected;
178         chomp $logline;
179         if ($logline !~ $expline) {
180             like($logline, $expline, $msg);
181             return;
182         }
183     }
184     if (@logfile) {
185         fail("$msg (extra trailing log lines)");
186         return;
187     }
188     if (@$expected) {
189         fail("$msg (logfile ends early)");
190         diag("first missing line should match ");
191         diag("".$expected->[0]);
192         return;
193     }
194
195     pass($msg);
196 }
197
198 sub check_holding_chunks {
199     my ($filename, $chunks, $host, $disk, $datestamp, $level) = @_;
200
201     my $msg = ".tmp holding chunk files";
202     my $exp_nchunks = @$chunks;
203     my $nchunks = 0;
204     while ($filename) {
205         $nchunks++;
206
207         my $filename_tmp = "$filename.tmp";
208         if (!-f $filename_tmp) {
209             fail($msg);
210             diag("file $filename_tmp doesn't exist");
211             diag(`ls -1l $test_hdir`);
212             return 0;
213         }
214
215         my $fh;
216         open($fh, "<", $filename_tmp) or die("opening $filename_tmp: $!");
217         my $hdr_str = Amanda::Util::full_read(fileno($fh), Amanda::Holding::DISK_BLOCK_BYTES);
218         close($fh);
219
220         my $hdr = Amanda::Header->from_string($hdr_str);
221         my $exp_type = ($nchunks == 1)? $F_DUMPFILE : $F_CONT_DUMPFILE;
222         if ($hdr->{'type'} != $exp_type) {
223             my ($exp, $got) = (Amanda::Header::filetype_t_to_string($exp_type),
224                                Amanda::Header::filetype_t_to_string($hdr->{'type'}));
225             fail($msg);
226             diag("file $filename_tmp has header type $got; expected $exp");
227             return 0;
228         }
229
230         my $ok = 1;
231         $ok &&= $hdr->{'name'} eq $host;
232         $ok &&= $hdr->{'disk'} eq $disk;
233         $ok &&= $hdr->{'datestamp'} eq $datestamp;
234         $ok &&= $hdr->{'dumplevel'} eq $level;
235         if (!$ok) {
236             fail($msg);
237             diag("file $filename_tmp header has unexpected values:\n" . $hdr->summary());
238             return 0;
239         }
240
241         my $data_size = (stat($filename_tmp))[7] - Amanda::Holding::DISK_BLOCK_BYTES;
242         my $exp_size = (shift @$chunks) * 1024;
243         if (defined $exp_size and $exp_size != $data_size) {
244             fail($msg);
245             diag("file $filename_tmp: expected $exp_size bytes, got $data_size");
246             return 0;
247         } # note: if @$exp_chunks is empty, the final is() will catch it
248
249         my $last_filename = $filename;
250         $filename = $hdr->{'cont_filename'};
251         die("header loop!") if $last_filename eq $filename;
252     }
253
254     return is($nchunks, $exp_nchunks, $msg);
255 }
256
257 sub cleanup_log {
258     my $logfile = "$CONFIG_DIR/TESTCONF/log/log";
259     -f $logfile and unlink($logfile);
260 }
261
262 # functions to create dumpfiles
263
264 sub write_dumpfile_header_to {
265     my ($fh, $size, $hostname, $disk, $expect_failure) = @_;
266
267     my $hdr = Amanda::Header->new();
268     $hdr->{'type'} = $Amanda::Header::F_DUMPFILE;
269     $hdr->{'datestamp'} = $datestamp;
270     $hdr->{'dumplevel'} = 0;
271     $hdr->{'compressed'} = 0;
272     $hdr->{'comp_suffix'} = ".foo";
273     $hdr->{'name'} = $hostname;
274     $hdr->{'disk'} = $disk;
275     $hdr->{'program'} = "INSTALLCHECK";
276     $hdr = $hdr->to_string(Amanda::Holding::DISK_BLOCK_BYTES,
277                            Amanda::Holding::DISK_BLOCK_BYTES);
278
279     $fh->write($hdr);
280 }
281
282 sub write_dumpfile_data_to {
283     my ($fh, $size, $hostname, $disk, $expect_failure) = @_;
284
285     my $bytes_to_write = $size;
286     my $bufbase = substr((('='x127)."\n".('-'x127)."\n") x 4, 8, -3) . "1K\n";
287     die length($bufbase) unless length($bufbase) == 1024-8;
288     my $k = 0;
289     while ($bytes_to_write > 0) {
290         my $buf = sprintf("%08x", $k++).$bufbase;
291         my $written = $fh->syswrite($buf, $bytes_to_write);
292         if (!defined($written)) {
293             die "writing: $!" unless $expect_failure;
294             exit;
295         }
296         $bytes_to_write -= $written;
297     }
298 }
299
300 # connect to the given port and write a dumpfile; this *will* create
301 # zombies, but it's OK -- installchecks aren't daemons.
302 sub write_to_port {
303     my ($port_cmd, $size, $hostname, $disk, $expect_error) = @_;
304
305     my ($header_port, $data_addr) =
306         ($last_chunker_reply =~ /^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+)/);
307
308     # just run this in the child
309     $writer_pid = fork();
310     return unless $writer_pid == 0;
311
312     my $sock = IO::Socket::INET->new(
313         PeerAddr => "127.0.0.1:$header_port",
314         Proto => "tcp",
315         ReuseAddr => 1,
316     );
317
318     write_dumpfile_header_to($sock, $size, $hostname, $disk, $expect_error);
319     close $sock;
320
321     $sock = IO::Socket::INET->new(
322         PeerAddr => $data_addr,
323         Proto => "tcp",
324         ReuseAddr => 1,
325     );
326
327     write_dumpfile_data_to($sock, $size, $hostname, $disk, $expect_error);
328     exit;
329 }
330
331 ########
332
333 ##
334 # A simple, two-chunk PORT-WRITE
335
336 $handle = "11-11111";
337 $datestamp = "20070102030405";
338 run_chunker("simple");
339 # note that features (ffff here) and options (ops) are ignored by the chunker
340 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /boot 0 $datestamp 512 INSTALLCHECK 10240 ops");
341 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
342         "got PORT with data address");
343 write_to_port($last_chunker_reply, 700*1024, "ghost", "/boot", 0);
344 wait_for_writer();
345 chunker_cmd("DONE $handle");
346 like(chunker_reply, qr/^DONE $handle 700 "\[sec [\d.]+ kb 700 kps [\d.]+\]"$/,
347         "got DONE") or die;
348 wait_for_exit();
349
350 check_logs([
351     qr(^SUCCESS chunker ghost /boot $datestamp 0 \[sec [\d.]+ kb 700 kps [\d.]+\]$),
352 ], "logs correct");
353
354 check_holding_chunks($test_hfile, [ 480, 220 ], "ghost", "/boot", $datestamp, 0);
355
356 ##
357 # A two-chunk PORT-WRITE that the dumper flags as a failure, but chunker as PARTIAL
358
359 $handle = "22-11111";
360 $datestamp = "20080808080808";
361 run_chunker("partial");
362 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /root 0 $datestamp 512 INSTALLCHECK 10240 ops");
363 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
364         "got PORT with data address");
365 write_to_port($last_chunker_reply, 768*1024, "ghost", "/root", 0);
366 wait_for_writer();
367 chunker_cmd("FAILED $handle");
368 like(chunker_reply, qr/^PARTIAL $handle 768 "\[sec [\d.]+ kb 768 kps [\d.]+\]"$/,
369         "got PARTIAL") or die;
370 wait_for_exit();
371
372 check_logs([
373     qr(^PARTIAL chunker ghost /root $datestamp 0 \[sec [\d.]+ kb 768 kps [\d.]+\]$),
374 ], "logs correct");
375
376 check_holding_chunks($test_hfile, [ 480, 288 ], "ghost", "/root", $datestamp, 0);
377
378 ##
379 # A two-chunk PORT-WRITE that the dumper flags as a failure and chunker
380 # does too, since no appreciatble bytes were transferred
381
382 $handle = "33-11111";
383 $datestamp = "20070202020202";
384 run_chunker("failed");
385 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /usr 0 $datestamp 512 INSTALLCHECK 10240 ops");
386 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
387         "got PORT with data address");
388 write_to_port($last_chunker_reply, 0, "ghost", "/usr", 0);
389 wait_for_writer();
390 chunker_cmd("FAILED $handle");
391 like(chunker_reply, qr/^FAILED $handle "\[dumper returned FAILED\]"$/,
392         "got FAILED") or die;
393 wait_for_exit();
394
395 check_logs([
396     qr(^FAIL chunker ghost /usr $datestamp 0 \[dumper returned FAILED\]$),
397 ], "logs correct");
398
399 check_holding_chunks($test_hfile, [ 0 ], "ghost", "/usr", $datestamp, 0);
400
401 cleanup_chunker();
402
403 ##
404 # A PORT-WRITE with a USE value smaller than the dump size, but an overly large
405 # chunksize
406
407 $handle = "44-11111";
408 $datestamp = "20040404040404";
409 run_chunker("more-than-use");
410 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /var 0 $datestamp 10240 INSTALLCHECK 512 ops");
411 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
412         "got PORT with data address");
413 write_to_port($last_chunker_reply, 700*1024, "ghost", "/var", 1);
414 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
415         "got RQ-MORE-DISK") or die;
416 chunker_cmd("CONTINUE $handle $test_hfile-u2 10240 512");
417 wait_for_writer();
418 chunker_cmd("DONE $handle");
419 like(chunker_reply, qr/^DONE $handle 700 "\[sec [\d.]+ kb 700 kps [\d.]+\]"$/,
420         "got DONE") or die;
421 wait_for_exit();
422
423 check_logs([
424     qr(^SUCCESS chunker ghost /var $datestamp 0 \[sec [\d.]+ kb 700 kps [\d.]+\]$),
425 ], "logs correct");
426
427 check_holding_chunks($test_hfile, [ 480, 220 ], "ghost", "/var", $datestamp, 0);
428
429 ##
430 # A PORT-WRITE with a USE value smaller than the dump size, and an even smaller
431 # chunksize, with a different chunksize on the second holding disk
432
433 $handle = "55-11111";
434 $datestamp = "20050505050505";
435 run_chunker("more-than-use-and-chunks");
436 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /var 0 $datestamp 96 INSTALLCHECK 160 ops");
437 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
438         "got PORT with data address");
439 write_to_port($last_chunker_reply, 400*1024, "ghost", "/var", 1);
440 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
441         "got RQ-MORE-DISK") or die;
442 chunker_cmd("CONTINUE $handle $test_hfile-u2 128 10240");
443 wait_for_writer();
444 chunker_cmd("DONE $handle");
445 like(chunker_reply, qr/^DONE $handle 400 "\[sec [\d.]+ kb 400 kps [\d.]+\]"$/,
446         "got DONE") or die;
447 wait_for_exit();
448
449 check_logs([
450     qr(^SUCCESS chunker ghost /var $datestamp 0 \[sec [\d.]+ kb 400 kps [\d.]+\]$),
451 ], "logs correct");
452
453 check_holding_chunks($test_hfile, [ 64, 32, 96, 96, 96, 16 ],
454     "ghost", "/var", $datestamp, 0);
455
456 cleanup_chunker();
457
458 ##
459 # A PORT-WRITE with a USE value smaller than the dump size, but with the CONTINUE
460 # giving the same filename, so that the dump continues in the same file
461
462 $handle = "55-22222";
463 $datestamp = "20050505050505";
464 run_chunker("use, continue on same file");
465 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /var/lib 0 $datestamp 10240 INSTALLCHECK 64 ops");
466 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
467         "got PORT with data address");
468 write_to_port($last_chunker_reply, 70*1024, "ghost", "/var/lib", 1);
469 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
470         "got RQ-MORE-DISK") or die;
471 chunker_cmd("CONTINUE $handle $test_hfile 10240 10240");
472 wait_for_writer();
473 chunker_cmd("DONE $handle");
474 like(chunker_reply, qr/^DONE $handle 70 "\[sec [\d.]+ kb 70 kps [\d.]+\]"$/,
475         "got DONE") or die;
476 wait_for_exit();
477
478 check_logs([
479     qr(^SUCCESS chunker ghost /var/lib $datestamp 0 \[sec [\d.]+ kb 70 kps [\d.]+\]$),
480 ], "logs correct");
481
482 check_holding_chunks($test_hfile, [ 70 ],
483     "ghost", "/var/lib", $datestamp, 0);
484
485 cleanup_chunker();
486
487 ##
488 # A PORT-WRITE with a USE value that will trigger in the midst of a header
489 # on the second chunk
490
491 $handle = "66-11111";
492 $datestamp = "20060606060606";
493 run_chunker("out-of-use-during-header");
494 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" ghost ffff /u01 0 $datestamp 96 INSTALLCHECK 120 ops");
495 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
496         "got PORT with data address");
497 write_to_port($last_chunker_reply, 400*1024, "ghost", "/u01", 1);
498 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
499         "got RQ-MORE-DISK") or die;
500 chunker_cmd("CONTINUE $handle $test_hfile-u2 128 10240");
501 wait_for_writer();
502 chunker_cmd("DONE $handle");
503 like(chunker_reply, qr/^DONE $handle 400 "\[sec [\d.]+ kb 400 kps [\d.]+\]"$/,
504         "got DONE") or die;
505 wait_for_exit();
506
507 check_logs([
508     qr(^SUCCESS chunker ghost /u01 $datestamp 0 \[sec [\d.]+ kb 400 kps [\d.]+\]$),
509 ], "logs for more-than-use-and-chunks PORT-WRITE");
510
511 check_holding_chunks($test_hfile, [ 64, 96, 96, 96, 48 ],
512     "ghost", "/u01", $datestamp, 0);
513
514 ##
515 # A two-disk PORT-WRITE, but with the DONE sent before the first byte of data
516 # arrives, to test the ability of the chunker to defer the DONE until it gets
517 # an EOF
518
519 $handle = "77-11111";
520 $datestamp = "20070707070707";
521 run_chunker("early-DONE");
522 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" roast ffff /boot 0 $datestamp 10240 INSTALLCHECK 128 ops");
523 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
524         "got PORT with data address");
525 chunker_cmd("DONE $handle");
526 write_to_port($last_chunker_reply, 180*1024, "roast", "/boot", 0);
527 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
528         "got RQ-MORE-DISK") or die;
529 chunker_cmd("CONTINUE $handle $test_hfile-u2 10240 10240");
530 wait_for_writer();
531 like(chunker_reply, qr/^DONE $handle 180 "\[sec [\d.]+ kb 180 kps [\d.]+\]"$/,
532         "got DONE") or die;
533 wait_for_exit();
534
535 check_logs([
536     qr(^SUCCESS chunker roast /boot $datestamp 0 \[sec [\d.]+ kb 180 kps [\d.]+\]$),
537 ], "logs for simple PORT-WRITE");
538
539 check_holding_chunks($test_hfile, [ 96, 84 ], "roast", "/boot", $datestamp, 0);
540
541 ##
542 # A two-disk PORT-WRITE, where the first disk runs out of space before it hits
543 # the USE limit.
544
545 $handle = "88-11111";
546 $datestamp = "20080808080808";
547 run_chunker("ENOSPC-1", ENOSPC_at => 90*1024);
548 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" roast ffff /boot 0 $datestamp 10240 INSTALLCHECK 10240 ops");
549 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
550         "got PORT with data address");
551 write_to_port($last_chunker_reply, 100*1024, "roast", "/boot", 0);
552 like(chunker_reply, qr/^NO-ROOM $handle 10150$/, # == 10240-90
553         "got NO-ROOM") or die;
554 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
555         "got RQ-MORE-DISK") or die;
556 chunker_cmd("CONTINUE $handle $test_hfile-u2 10240 10240");
557 wait_for_writer();
558 chunker_cmd("DONE $handle");
559 like(chunker_reply, qr/^DONE $handle 100 "\[sec [\d.]+ kb 100 kps [\d.]+\]"$/,
560         "got DONE") or die;
561 wait_for_exit();
562
563 check_logs([
564     qr(^SUCCESS chunker roast /boot $datestamp 0 \[sec [\d.]+ kb 100 kps [\d.]+\]$),
565 ], "logs for simple PORT-WRITE");
566
567 check_holding_chunks($test_hfile, [ 58, 42 ], "roast", "/boot", $datestamp, 0);
568
569 ##
570 # A two-chunk PORT-WRITE, where the second chunk gets ENOSPC in the header.  This
571 # also checks the behavior of rounding down the use value to the nearest multiple
572 # of 32k (with am_floor)
573
574 $handle = "88-22222";
575 $datestamp = "20080808080808";
576 run_chunker("ENOSPC-2", ENOSPC_at => 130*1024);
577 chunker_cmd("PORT-WRITE $handle \"$test_hfile\" roast ffff /boot 0 $datestamp 128 INSTALLCHECK 1000 ops");
578 like(chunker_reply, qr/^PORT (\d+) "?(\d+\.\d+\.\d+\.\d+:\d+;?)+"?$/,
579         "got PORT with data address");
580 write_to_port($last_chunker_reply, 128*1024, "roast", "/boot", 0);
581 like(chunker_reply, qr/^NO-ROOM $handle 864$/, # == am_floor(1000)-128
582         "got NO-ROOM") or die;
583 like(chunker_reply, qr/^RQ-MORE-DISK $handle$/,
584         "got RQ-MORE-DISK") or die;
585 chunker_cmd("CONTINUE $handle $test_hfile-u2 300 128");
586 wait_for_writer();
587 chunker_cmd("DONE $handle");
588 like(chunker_reply, qr/^DONE $handle 128 "\[sec [\d.]+ kb 128 kps [\d.]+\]"$/,
589         "got DONE") or die;
590 wait_for_exit();
591
592 check_logs([
593     qr(^SUCCESS chunker roast /boot $datestamp 0 \[sec [\d.]+ kb 128 kps [\d.]+\]$),
594 ], "logs for simple PORT-WRITE");
595
596 check_holding_chunks($test_hfile, [ 96, 32 ], "roast", "/boot", $datestamp, 0);
597 ok(!-f "$test_hfile.1.tmp",
598     "half-written header is deleted");
599
600 cleanup_chunker();