1 # Copyright (c) 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
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.
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
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
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 use Test::More tests => 505;
20 use File::Path qw( mkpath rmtree );
26 use lib "@amperldir@";
28 use Installcheck::Mock;
29 use Installcheck::Config;
31 use Amanda::Device qw( :constants );
32 use Amanda::Config qw( :getconf :init );
33 use Amanda::Xfer qw( :constants );
34 use Amanda::Header qw( :constants );
42 my ($vtape1, $vtape2);
43 my ($input_filename, $output_filename) =
44 ( "$Installcheck::TMP/input.tmp", "$Installcheck::TMP/output.tmp" );
45 my $taperoot = "$Installcheck::TMP/Amanda_Device_test_tapes";
48 # we'll need some vtapes..
52 my $mytape = "$taperoot/$num";
53 if (-d $mytape) { rmtree($mytape); }
54 mkpath("$mytape/data");
59 # make up a fake dumpfile_t to write with
60 my $dumpfile = Amanda::Header->new();
61 $dumpfile->{type} = $Amanda::Header::F_DUMPFILE;
62 $dumpfile->{datestamp} = "20070102030405";
63 $dumpfile->{dumplevel} = 0;
64 $dumpfile->{compressed} = 1;
65 $dumpfile->{name} = "localhost";
66 $dumpfile->{disk} = "/home";
67 $dumpfile->{program} = "INSTALLCHECK";
69 my $write_file_count = 5;
71 my ($seed, $length, $filenum) = @_;
73 $dumpfile->{'datestamp'} = "2000010101010$filenum";
75 ok($dev->start_file($dumpfile),
76 "start file $filenum")
77 or diag($dev->error_or_status());
79 is($dev->file(), $filenum,
80 "Device has correct filenum");
82 croak ("selected file size $length is *way* too big")
83 unless ($length < 1024*1024*10);
84 ok(Amanda::Device::write_random_to_device($seed, $length, $dev),
87 if(ok($dev->in_file(),
89 ok($dev->finish_file(),
91 or diag($dev->error_or_status());
93 pass("not in file, so not calling finish_file");
97 my $verify_file_count = 4;
99 my ($seed, $length, $filenum) = @_;
101 ok(my $read_dumpfile = $dev->seek_file($filenum),
102 "seek to file $filenum")
103 or diag($dev->error_or_status());
104 is($dev->file(), $filenum,
105 "device is really at file $filenum");
106 ok(header_for($read_dumpfile, $filenum),
108 or diag($dev->error_or_status());
109 ok(Amanda::Device::verify_random_from_device($seed, $length, $dev),
110 "verified file contents");
114 my ($hdr, $filenum) = @_;
115 return ($hdr and $hdr->{'datestamp'} eq "2000010101010$filenum");
120 my @common_properties = (
126 'medium_access_type',
133 sub properties_include {
134 my ($got, $should_include, $msg) = @_;
135 my %got = map { $_->{'name'}, 1 } @$got;
136 my @missing = grep { !defined($got{$_}) } @$should_include;
139 diag(" Expected properties: " . join(", ", @$should_include));
140 diag(" Got properties: " . join(", ", @$got));
141 diag(" Missing properties: " . join(", ", @missing));
150 $testconf = Installcheck::Config->new();
152 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
153 or die("Could not load configuration");
155 # put the debug messages somewhere
156 Amanda::Debug::dbopen("installcheck");
157 Installcheck::log_test_output();
160 ## Test errors a little bit
162 $dev = Amanda::Device->new("foobar:");
163 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
164 "creation of a bogus 'foobar:' device fails");
166 $dev = Amanda::Device->new("rait:{{");
167 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
168 "creation of a bogus 'rait:{{' device fails");
170 $dev = Amanda::Device->new("rait:{a,b");
171 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
172 "creation of a bogus 'rait:{a,b' device fails");
175 ## first, test out the 'null' device.
179 $dev = Amanda::Device->new($dev_name);
180 is($dev->status(), $DEVICE_STATUS_SUCCESS,
181 "create null device")
182 or diag $dev->error_or_status();
183 ok($dev->start($ACCESS_WRITE, "NULL1", "19780615010203"),
184 "start null device in write mode")
185 or diag $dev->error_or_status();
188 properties_include([ $dev->property_list() ], [ @common_properties ],
189 "necessary properties listed on null device");
190 is($dev->property_get("canonical_name"), "null:",
191 "property_get(canonical_name) on null device");
192 is($dev->property_get("caNONical-name"), "null:",
193 "property_get(caNONical-name) on null device (case, dash-insensitivity)");
194 is_deeply([ $dev->property_get("canonical_name") ],
195 [ "null:", $PROPERTY_SURETY_GOOD, $PROPERTY_SOURCE_DEFAULT ],
196 "extended property_get returns correct surety/source");
197 for my $prop ($dev->property_list()) {
198 next unless $prop->{'name'} eq 'canonical_name';
199 is($prop->{'description'},
200 "The most reliable device name to use to refer to this device.",
201 "property info for canonical name is correct");
203 ok(!$dev->property_get("full_deletion"),
204 "property_get(full_deletion) on null device");
205 is($dev->property_get("comment"), undef,
206 "no comment by default");
207 ok($dev->property_set("comment", "well, that was silly"),
208 "set comment property");
209 is($dev->property_get("comment"), "well, that was silly",
210 "comment correctly stored");
212 # and write a file to it
213 write_file(0xabcde, 1024*256, 1);
215 # (don't finish the device, testing the finalize method's cleanup)
218 ## Now some full device tests
222 $vtape1 = mkvtape(1);
223 $dev_name = "file:$vtape1";
225 $dev = Amanda::Device->new($dev_name);
226 is($dev->status(), $DEVICE_STATUS_SUCCESS,
227 "$dev_name: create successful")
228 or diag($dev->error_or_status());
230 properties_include([ $dev->property_list() ],
231 [ @common_properties, 'max_volume_usage' ],
232 "necessary properties listed on vfs device");
234 # play with properties a little bit
235 ok($dev->property_set("comment", 16),
236 "set an string property to an integer");
238 ok($dev->property_set("comment", 16.0),
239 "set an string property to a float");
241 ok($dev->property_set("comment", "hi mom"),
242 "set an string property to a string");
244 ok($dev->property_set("comment", "32768"),
245 "set an integer property to a simple string");
247 ok($dev->property_set("comment", "32k"),
248 "set an integer property to a string with a unit");
250 ok($dev->property_set("block_size", 32768),
251 "set an integer property to an integer");
254 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
255 "initially unlabeled")
256 or diag($dev->error_or_status());
258 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
259 "start in write mode")
260 or diag($dev->error_or_status());
262 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
263 "not unlabeled anymore")
264 or diag($dev->error_or_status());
266 for (my $i = 1; $i <= 3; $i++) {
267 write_file(0x2FACE, $dev->block_size()*10+17, $i);
271 "finish device after write")
272 or diag($dev->error_or_status());
275 ok(!($dev->status()),
276 "no error, at all, from read_label")
277 or diag($dev->error_or_status());
279 # append one more copy, to test ACCESS_APPEND
281 ok($dev->start($ACCESS_APPEND, undef, undef),
282 "start in append mode")
283 or diag($dev->error_or_status());
285 write_file(0xD0ED0E, $dev->block_size()*4, 4);
288 "finish device after append")
289 or diag($dev->error_or_status());
291 # try reading the third file back, creating a new device
292 # object first, and skipping the read-label step.
295 $dev = Amanda::Device->new($dev_name);
296 is($dev->status(), $DEVICE_STATUS_SUCCESS,
297 "$dev_name: re-create successful")
298 or diag($dev->error_or_status());
300 ok($dev->start($ACCESS_READ, undef, undef),
301 "start in read mode")
302 or diag($dev->error_or_status());
304 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
307 # try two seek_file's in a row
308 my $hdr = $dev->seek_file(3);
309 is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
310 $hdr = $dev->seek_file(3);
311 is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
315 "finish device after read")
316 or diag($dev->error_or_status());
321 or diag($dev->error_or_status());
324 "erase device (again)")
325 or diag($dev->error_or_status());
328 "finish device after erase")
329 or diag($dev->error_or_status());
331 # test monitor_free_space property (testing the monitoring would require a
332 # dedicated partition for the tests - it's not worth it)
334 ok($dev->property_get("monitor_free_space"),
335 "monitor_free_space property is set by default");
337 ok($dev->property_set("monitor_free_space", 0),
338 "monitor_free_space property can be set to false");
340 ok(!$dev->property_get("monitor_free_space"),
341 "monitor_free_space property value 'sticks'");
343 # test the LEOM functionality
346 $dev = Amanda::Device->new($dev_name);
347 is($dev->status(), $DEVICE_STATUS_SUCCESS,
348 "$dev_name: re-create successful")
349 or diag($dev->error_or_status());
350 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
351 "set MAX_VOLUME_USAGE to test LEOM");
352 ok($dev->property_set("LEOM", 1),
355 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
356 "start in write mode")
357 or diag($dev->error_or_status());
359 ok($dev->start_file($dumpfile),
361 or diag($dev->error_or_status());
364 "device does not indicate LEOM before writing");
366 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
367 "write random data into the early-warning zone");
370 "device indicates LEOM after writing");
372 ok($dev->finish_file(),
373 "..but a finish_file is allowed to complete")
374 or diag($dev->error_or_status());
377 "finish device after LEOM test")
378 or diag($dev->error_or_status());
381 $dev = Amanda::Device->new($dev_name);
382 is($dev->status(), $DEVICE_STATUS_SUCCESS,
383 "$dev_name: re-create successful")
384 or diag($dev->error_or_status());
385 ok($dev->property_set("MAX_VOLUME_USAGE", "160k"),
386 "set MAX_VOLUME_USAGE to test LEOM while writing the first header");
387 ok($dev->property_set("LEOM", 1),
390 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
391 "start in write mode")
392 or diag($dev->error_or_status());
394 ok($dev->start_file($dumpfile),
396 or diag($dev->error_or_status());
399 "device indicates LEOM after writing first header");
401 ok($dev->finish_file(),
402 "..but a finish_file is allowed to complete")
403 or diag($dev->error_or_status());
406 "finish device after LEOM test")
407 or diag($dev->error_or_status());
410 ## Test a RAIT device of two vfs devices.
412 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
413 $dev_name = "rait:file:{$vtape1,$vtape2}";
415 $dev = Amanda::Device->new($dev_name);
416 is($dev->status(), $DEVICE_STATUS_SUCCESS,
417 "$dev_name: create successful")
418 or diag($dev->error_or_status());
420 ok($dev->configure(1), "configure device");
422 properties_include([ $dev->property_list() ], [ @common_properties ],
423 "necessary properties listed on rait device");
425 is($dev->property_get("block_size"), 32768, # (RAIT default)
426 "rait device calculates a default block size correctly");
428 ok($dev->property_set("block_size", 32768*16),
429 "rait device accepts an explicit block size");
431 is($dev->property_get("block_size"), 32768*16,
432 "..and remembers it");
434 ok($dev->property_set("max_volume_usage", 32768*1000),
435 "rait device accepts property MAX_VOLUME_USAGE");
437 is($dev->property_get("max_volume_usage"), 32768*1000,
438 "..and remembers it");
441 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
442 "initially unlabeled")
443 or diag($dev->error_or_status());
445 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
446 "start in write mode")
447 or diag($dev->error_or_status());
449 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
450 "not unlabeled anymore")
451 or diag($dev->error_or_status());
453 for (my $i = 1; $i <= 3; $i++) {
454 write_file(0x2FACE, $dev->block_size()*10+17, $i);
458 "finish device after write")
459 or diag($dev->error_or_status());
462 ok(!($dev->status()),
463 "no error, at all, from read_label")
464 or diag($dev->error_or_status());
466 # append one more copy, to test ACCESS_APPEND
468 ok($dev->start($ACCESS_APPEND, undef, undef),
469 "start in append mode")
470 or diag($dev->error_or_status());
472 write_file(0xD0ED0E, $dev->block_size()*4, 4);
475 "finish device after append")
476 or diag($dev->error_or_status());
478 # try reading the third file back, creating a new device
479 # object first, and skipping the read-label step.
482 $dev = Amanda::Device->new($dev_name);
483 is($dev->status(), $DEVICE_STATUS_SUCCESS,
484 "$dev_name: re-create successful")
485 or diag($dev->error_or_status());
487 ok($dev->start($ACCESS_READ, undef, undef),
488 "start in read mode")
489 or diag($dev->error_or_status());
491 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
494 "finish device after read")
495 or diag($dev->error_or_status());
497 ok($dev->start($ACCESS_READ, undef, undef),
498 "start in read mode after missing volume")
499 or diag($dev->error_or_status());
501 # corrupt the device somehow and hope it keeps working
502 rmtree("$taperoot/1");
504 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
505 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
506 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
509 "finish device read after missing volume")
510 or diag($dev->error_or_status());
512 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
513 "start in write mode fails with missing volume")
514 or diag($dev->error_or_status());
518 $dev_name = "rait:{file:$vtape2,MISSING}";
519 $dev = Amanda::Device->new($dev_name);
521 ok($dev->start($ACCESS_READ, undef, undef),
522 "start in read mode with MISSING")
523 or diag($dev->error_or_status());
525 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
526 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
527 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
530 "finish device read with MISSING")
531 or diag($dev->error_or_status());
533 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
534 "start in write mode fails with MISSING")
535 or diag($dev->error_or_status());
539 $dev = Amanda::Device->new_rait_from_children(
540 Amanda::Device->new("file:$vtape2"), undef);
542 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
543 "start a RAIT device in write mode fails, when created with 'undef'")
544 or diag($dev->error_or_status());
546 # Make two devices with different labels, should get a
547 # message accordingly.
548 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
551 for $dev_name ("file:$vtape1", "file:$vtape2") {
552 my $dev = Amanda::Device->new($dev_name);
553 is($dev->status(), $DEVICE_STATUS_SUCCESS,
554 "$dev_name: Open successful")
555 or diag($dev->error_or_status());
556 ok($dev->start($ACCESS_WRITE, "TESTCONF$n", undef),
557 "wrote label 'TESTCONF$n'");
558 ok($dev->finish(), "finished device");
562 $dev = Amanda::Device->new_rait_from_children(
563 Amanda::Device->new("file:$vtape1"),
564 Amanda::Device->new("file:$vtape2"));
565 is($dev->status(), $DEVICE_STATUS_SUCCESS,
566 "new_rait_from_children: Open successful")
567 or diag($dev->error_or_status());
570 ok($dev->status() & $DEVICE_STATUS_VOLUME_ERROR,
571 "Label mismatch error handled correctly")
572 or diag($dev->error_or_status());
574 # Use some config to set a block size on a child device
575 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
576 $dev_name = "rait:{file:$vtape1,mytape2}";
578 $testconf = Installcheck::Config->new();
579 $testconf->add_device("mytape2", [
580 "tapedev" => "\"file:$vtape2\"",
581 "device_property" => "\"BLOCK_SIZE\" \"64k\""
584 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
585 or die("Could not load configuration");
587 $dev = Amanda::Device->new($dev_name);
588 is($dev->status(), $DEVICE_STATUS_SUCCESS,
589 "$dev_name: create successful")
590 or diag($dev->error_or_status());
592 ok($dev->configure(1), "configure device");
594 is($dev->property_get("block_size"), 65536,
595 "rait device calculates a block size from its children correctly");
597 # Test an S3 device if the proper environment variables are set
598 my $S3_SECRET_KEY = $ENV{'INSTALLCHECK_S3_SECRET_KEY'};
599 my $S3_ACCESS_KEY = $ENV{'INSTALLCHECK_S3_ACCESS_KEY'};
600 my $DEVPAY_SECRET_KEY = $ENV{'INSTALLCHECK_DEVPAY_SECRET_KEY'};
601 my $DEVPAY_ACCESS_KEY = $ENV{'INSTALLCHECK_DEVPAY_ACCESS_KEY'};
602 my $DEVPAY_USER_TOKEN = $ENV{'INSTALLCHECK_DEVPAY_USER_TOKEN'};
604 my $run_s3_tests = defined $S3_SECRET_KEY && defined $S3_ACCESS_KEY;
605 my $run_devpay_tests = defined $DEVPAY_SECRET_KEY &&
606 defined $DEVPAY_ACCESS_KEY && $DEVPAY_USER_TOKEN;
608 my $s3_make_device_count = 7;
609 sub s3_make_device($$) {
610 my ($dev_name, $kind) = @_;
611 $dev = Amanda::Device->new($dev_name);
612 is($dev->status(), $DEVICE_STATUS_SUCCESS,
613 "$dev_name: create successful")
614 or diag($dev->error_or_status());
616 my @s3_props = ( 's3_access_key', 's3_secret_key' );
617 push @s3_props, 's3_user_token' if ($kind eq "devpay");
618 properties_include([ $dev->property_list() ], [ @common_properties, @s3_props ],
619 "necessary properties listed on s3 device");
621 ok($dev->property_set('BLOCK_SIZE', 32768*2),
623 or diag($dev->error_or_status());
625 # might as well save a few cents while testing this property..
626 ok($dev->property_set('S3_STORAGE_CLASS', 'REDUCED_REDUNDANCY'),
628 or diag($dev->error_or_status());
631 # use regular S3 credentials
632 ok($dev->property_set('S3_ACCESS_KEY', $S3_ACCESS_KEY),
634 or diag($dev->error_or_status());
636 ok($dev->property_set('S3_SECRET_KEY', $S3_SECRET_KEY),
638 or diag($dev->error_or_status());
640 pass("(placeholder)");
641 } elsif ($kind eq "devpay") {
642 # use devpay credentials
643 ok($dev->property_set('S3_ACCESS_KEY', $DEVPAY_ACCESS_KEY),
644 "set devpay access key")
645 or diag($dev->error_or_status());
647 ok($dev->property_set('S3_SECRET_KEY', $DEVPAY_SECRET_KEY),
648 "set devpay secret key")
649 or diag($dev->error_or_status());
651 ok($dev->property_set('S3_USER_TOKEN', $DEVPAY_USER_TOKEN),
652 "set devpay user token")
653 or diag($dev->error_or_status());
655 croak("didn't recognize the device kind, so no credentials were set");
663 skip "define \$INSTALLCHECK_S3_{SECRET,ACCESS}_KEY to run S3 tests",
665 1 * $verify_file_count +
666 4 * $write_file_count +
667 10 * $s3_make_device_count
668 unless $run_s3_tests;
671 $dev = Amanda::Device->new($dev_name);
672 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
673 "creating $dev_name fails miserably");
675 $dev_name = "s3:foo";
676 $dev = Amanda::Device->new($dev_name);
678 ok($dev->property_get("full_deletion"),
679 "property_get(full_deletion) on s3 device");
681 ok($dev->property_get("leom"),
682 "property_get(leom) on s3 device");
684 # test parsing of boolean values
685 # (s3 is the only device driver that has a writable boolean property at the
689 {'val' => '1', 'true' => 1},
690 {'val' => '0', 'true' => 0},
691 {'val' => 't', 'true' => 1},
692 {'val' => 'true', 'true' => 1},
693 {'val' => 'f', 'true' => 0},
694 {'val' => 'false', 'true' => 0},
695 {'val' => 'y', 'true' => 1},
696 {'val' => 'yes', 'true' => 1},
697 {'val' => 'n', 'true' => 0},
698 {'val' => 'no', 'true' => 0},
699 {'val' => 'on', 'true' => 1},
700 {'val' => 'off', 'true' => 0},
701 {'val' => 'oFf', 'true' => 0},
704 foreach my $v (@verbose_vals) {
705 $dev_name = "s3:foo";
706 $dev = Amanda::Device->new($dev_name);
708 $testconf = Installcheck::Config->new();
709 $testconf->add_param("device_property", "\"verbose\" \"$v->{'val'}\"");
711 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
712 or die("Could not load configuration");
714 ok($dev->configure(1),
715 "configured device with verbose set to $v->{'val'}")
716 or diag($dev->error_or_status());
718 my $get_val = $dev->property_get('verbose');
719 # see if truth-iness matches
720 my $expec = $v->{'true'}? "true" : "false";
721 is(!!$dev->property_get('verbose'), !!$v->{'true'},
722 "device_property 'VERBOSE' '$v->{'val'}' => property_get(verbose) returning $expec");
725 # test unparsable property
726 $dev_name = "s3:foo";
727 $dev = Amanda::Device->new($dev_name);
729 $testconf = Installcheck::Config->new();
730 $testconf->add_param("device_property", "\"verbose\" \"foo\"");
732 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
733 or die("Could not load configuration");
735 ok(!$dev->configure(1),
736 "failed to configure device with verbose set to foo");
738 like($dev->error_or_status(), qr/'verbose'/,
739 "error message mentions property name");
741 like($dev->error_or_status(), qr/'foo'/,
742 "error message mentions property value");
744 like($dev->error_or_status(), qr/gboolean/,
745 "error message mentions property type");
747 my $hostname = hostname();
748 $hostname =~ s/\./-/g;
749 $base_name = "$S3_ACCESS_KEY-installcheck-$hostname";
750 $dev_name = "s3:$base_name-s3";
751 $dev = s3_make_device($dev_name, "s3");
753 my $status = $dev->status();
754 # this test appears very liberal, but catches the case where setup_handle fails without
755 # giving false positives
756 ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
757 "status is either OK or possibly unlabeled")
758 or diag($dev->error_or_status());
760 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
761 "start in write mode")
762 or diag($dev->error_or_status());
764 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
766 or diag($dev->error_or_status());
768 for (my $i = 1; $i <= 3; $i++) {
769 write_file(0x2FACE, $dev->block_size()*10, $i);
773 "finish device after write")
774 or diag($dev->error_or_status());
777 ok(!($dev->status()),
778 "no error, at all, from read_label")
779 or diag($dev->error_or_status());
781 # append one more copy, to test ACCESS_APPEND
783 ok($dev->start($ACCESS_APPEND, undef, undef),
784 "start in append mode")
785 or diag($dev->error_or_status());
787 write_file(0xD0ED0E, $dev->block_size()*10, 4);
790 "finish device after append")
791 or diag($dev->error_or_status());
793 # try reading the third file back
795 ok($dev->start($ACCESS_READ, undef, undef),
796 "start in read mode")
797 or diag($dev->error_or_status());
799 verify_file(0x2FACE, $dev->block_size()*10, 3);
801 # test EOT indications on reading
802 my $hdr = $dev->seek_file(4);
803 is($hdr->{'type'}, $Amanda::Header::F_DUMPFILE,
804 "file 4 has correct type F_DUMPFILE");
806 $hdr = $dev->seek_file(5);
807 is($hdr->{'type'}, $Amanda::Header::F_TAPEEND,
808 "file 5 has correct type F_TAPEEND");
810 $hdr = $dev->seek_file(6);
811 is($hdr, undef, "seek_file returns undef for file 6");
814 "finish device after read")
815 or diag($dev->error_or_status()); # (note: we don't use write_max_size here,
816 # as the maximum for S3 is very large)
820 or diag($dev->error_or_status());
823 "erase device (again)")
824 or diag($dev->error_or_status());
827 "finish device after erase")
828 or diag($dev->error_or_status());
831 $status = $dev->status();
832 ok($status & $DEVICE_STATUS_VOLUME_UNLABELED,
833 "status is unlabeled after an erase")
834 or diag($dev->error_or_status());
836 $dev = s3_make_device($dev_name, "s3");
839 "erase device right after creation")
840 or diag($dev->error_or_status());
842 # try with empty user token
843 $dev_name = lc("s3:$base_name-s3");
844 $dev = s3_make_device($dev_name, "s3");
845 ok($dev->property_set('S3_USER_TOKEN', ''),
846 "set devpay user token")
847 or diag($dev->error_or_status());
850 $status = $dev->status();
851 ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
852 "status is either OK or possibly unlabeled")
853 or diag($dev->error_or_status());
859 or diag($dev->error_or_status());
861 # try a eu-constrained bucket
862 $dev_name = lc("s3:$base_name-s3-eu");
863 $dev = s3_make_device($dev_name, "s3");
864 ok($dev->property_set('S3_BUCKET_LOCATION', 'EU'),
865 "set S3 bucket location to 'EU'")
866 or diag($dev->error_or_status());
868 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
869 "start in write mode")
870 or diag($dev->error_or_status());
872 is($dev->status(), $DEVICE_STATUS_SUCCESS,
874 or diag($dev->error_or_status());
880 or diag($dev->error_or_status());
882 # try a wildcard-constrained bucket
883 $dev_name = lc("s3:$base_name-s3-wild");
884 $dev = s3_make_device($dev_name, "s3");
885 ok($dev->property_set('S3_BUCKET_LOCATION', '*'),
886 "set S3 bucket location to ''")
887 or diag($dev->error_or_status());
889 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
890 "start in write mode")
891 or diag($dev->error_or_status());
893 is($dev->status(), $DEVICE_STATUS_SUCCESS,
895 or diag($dev->error_or_status());
899 # test again with invalid ca_info
900 $dev = s3_make_device($dev_name, "s3");
902 skip "SSL not supported; can't check SSL_CA_INFO", 2
903 unless $dev->property_get('S3_SSL');
905 ok($dev->property_set('SSL_CA_INFO', '/dev/null'),
906 "set invalid SSL/TLS CA certificate")
907 or diag($dev->error_or_status());
909 ok(!$dev->start($ACCESS_WRITE, "TESTCONF13", undef),
910 "start in write mode")
911 or diag($dev->error_or_status());
913 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
915 or diag($dev->error_or_status());
920 # test again with our own CA bundle
921 $dev = s3_make_device($dev_name, "s3");
923 skip "SSL not supported; can't check SSL_CA_INFO", 4
924 unless $dev->property_get('S3_SSL');
925 ok($dev->property_set('SSL_CA_INFO', "$srcdir/data/aws-bundle.crt"),
926 "set our own SSL/TLS CA certificate bundle")
927 or diag($dev->error_or_status());
931 or diag($dev->error_or_status());
933 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
934 "start in write mode")
935 or diag($dev->error_or_status());
937 is($dev->status(), $DEVICE_STATUS_SUCCESS,
939 or diag($dev->error_or_status());
946 or diag($dev->error_or_status());
948 # bucket names incompatible with location constraint
949 $dev_name = "s3:-$base_name-s3-eu";
950 $dev = s3_make_device($dev_name, "s3");
952 ok($dev->property_set('S3_BUCKET_LOCATION', ''),
953 "should be able to set an empty S3 bucket location with an incompatible name")
954 or diag($dev->error_or_status());
956 $dev_name = "s3:$base_name-s3.eu";
957 $dev = s3_make_device($dev_name, "s3");
959 ok($dev->property_set('S3_BUCKET_LOCATION', ''),
960 "should be able to set an empty S3 bucket location with an incompatible name")
961 or diag($dev->error_or_status());
963 $dev_name = "s3:-$base_name-s3-eu";
964 $dev = s3_make_device($dev_name, "s3");
966 ok(!$dev->property_set('S3_BUCKET_LOCATION', 'EU'),
967 "should not be able to set S3 bucket location with an incompatible name")
968 or diag($dev->error_or_status());
972 # in this case, most of our code has already been exercised
973 # just make sure that authentication works as a basic sanity check
974 skip "skipping abbreviated devpay tests", $s3_make_device_count + 1
975 unless $run_devpay_tests;
976 $dev_name = "s3:$base_name-devpay";
977 $dev = s3_make_device($dev_name, "devpay");
979 my $status = $dev->status();
980 # this test appears very liberal, but catches the case where setup_handle fails without
981 # giving false positives
982 ok(($status == 0) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
983 "status is either OK or possibly unlabeled")
984 or diag($dev->error_or_status());
987 # Test a tape device if the proper environment variables are set
988 my $TAPE_DEVICE = $ENV{'INSTALLCHECK_TAPE_DEVICE'};
989 my $run_tape_tests = defined $TAPE_DEVICE;
991 skip "define \$INSTALLCHECK_TAPE_DEVICE to run tape tests",
993 7 * $verify_file_count +
994 5 * $write_file_count
995 unless $run_tape_tests;
997 $dev_name = "tape:$TAPE_DEVICE";
998 $dev = Amanda::Device->new($dev_name);
999 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1000 "$dev_name: create successful")
1001 or diag($dev->error_or_status());
1003 my $status = $dev->read_label();
1004 ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1005 "status is either OK or possibly unlabeled")
1006 or diag($dev->error_or_status());
1008 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1009 "start in write mode")
1010 or diag($dev->error_or_status());
1012 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
1013 "not unlabeled anymore")
1014 or diag($dev->error_or_status());
1016 for (my $i = 1; $i <= 4; $i++) {
1017 write_file(0x2FACE+$i, $dev->block_size()*10+17, $i);
1021 "finish device after write")
1022 or diag($dev->error_or_status());
1025 ok(!($dev->status()),
1026 "no error, at all, from read_label")
1027 or diag($dev->error_or_status());
1029 is($dev->volume_label(), "TESTCONF13",
1030 "read_label reads the correct label")
1031 or diag($dev->error_or_status());
1033 # append one more copy, to test ACCESS_APPEND
1035 # if final_filemarks is 1, then the tape device will use F_NOOP,
1036 # inserting an extra file, and we'll be appending at file number 6.
1037 my $append_fileno = ($dev->property_get("FINAL_FILEMARKS") == 2)? 5:6;
1040 skip "APPEND not supported", $write_file_count + 2
1041 unless $dev->property_get("APPENDABLE");
1043 ok($dev->start($ACCESS_APPEND, undef, undef),
1044 "start in append mode")
1045 or diag($dev->error_or_status());
1047 write_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1050 "finish device after append")
1051 or diag($dev->error_or_status());
1054 # try reading the second and third files back, creating a new
1055 # device object first, and skipping the read-label step.
1058 $dev = Amanda::Device->new($dev_name);
1059 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1060 "$dev_name: re-create successful")
1061 or diag($dev->error_or_status());
1063 # use a big read_block_size, checking that it's also settable
1064 # via read_buffer_size
1065 ok($dev->property_set("read_buffer_size", 256*1024),
1066 "can set read_buffer_size");
1067 is($dev->property_get("read_block_size"), 256*1024,
1068 "and its value is reflected in read_block_size");
1069 ok($dev->property_set("read_block_size", 32*1024),
1070 "can set read_block_size");
1072 ok($dev->start($ACCESS_READ, undef, undef),
1073 "start in read mode")
1074 or diag($dev->error_or_status());
1076 # now verify those files in a particular order to trigger all of the
1077 # seeking edge cases
1079 verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1080 verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1081 verify_file(0x2FACE+4, $dev->block_size()*10+17, 4);
1082 verify_file(0x2FACE+3, $dev->block_size()*10+17, 3);
1083 verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1085 # try re-seeking to the same file
1086 ok(header_for($dev->seek_file(2), 2), "seek to file 2 the first time");
1087 verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1088 ok(header_for($dev->seek_file(2), 2), "seek to file 2 the third time");
1090 # and seek through the same pattern *without* reading to EOF
1091 ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1092 ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1093 ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1094 ok(header_for($dev->seek_file(3), 3), "seek to file 3");
1095 ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1098 skip "APPEND not supported", $verify_file_count
1099 unless $dev->property_get("APPENDABLE");
1100 verify_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1104 "finish device after read")
1105 or diag($dev->error_or_status());
1107 # tickle a regression in improperly closing fd's
1109 "finish device again after read")
1110 or diag($dev->error_or_status());
1112 ok($dev->read_label() == $DEVICE_STATUS_SUCCESS,
1113 "read_label after second finish (used to fail)")
1114 or diag($dev->error_or_status());
1116 # finally, run the device with FSF and BSF set to "no", to test the
1117 # fallback schemes for this condition
1120 $dev = Amanda::Device->new($dev_name);
1121 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1122 "$dev_name: re-create successful")
1123 or diag($dev->error_or_status());
1124 $dev->property_set("fsf", "no");
1125 $dev->property_set("bsf", "no");
1127 ok($dev->start($ACCESS_READ, undef, undef),
1128 "start in read mode")
1129 or diag($dev->error_or_status());
1131 ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1132 ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1133 ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1136 "finish device after read")
1137 or diag($dev->error_or_status());
1141 skip "not built with ndmp and server", 78 unless
1142 Amanda::Util::built_with_component("ndmp") and
1143 Amanda::Util::built_with_component("server");
1146 my $testconf = Installcheck::Config->new();
1149 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
1150 if ($cfg_result != $CFGERR_OK) {
1151 my ($level, @errors) = Amanda::Config::config_errors();
1152 die(join "\n", @errors);
1155 my $ndmp = Installcheck::Mock::NdmpServer->new();
1156 my $ndmp_port = $ndmp->{'port'};
1157 my $drive = $ndmp->{'drive'};
1158 pass("started ndmjob in daemon mode");
1160 # set up a header for use below
1161 my $hdr = Amanda::Header->new();
1162 $hdr->{type} = $Amanda::Header::F_DUMPFILE;
1163 $hdr->{datestamp} = "20070102030405";
1164 $hdr->{dumplevel} = 0;
1165 $hdr->{compressed} = 1;
1166 $hdr->{name} = "localhost";
1167 $hdr->{disk} = "/home";
1168 $hdr->{program} = "INSTALLCHECK";
1170 $dev = Amanda::Device->new("ndmp:127.0.0.1:9i1\@foo");
1171 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1172 "creation of an ndmp device fails with invalid port");
1174 $dev = Amanda::Device->new("ndmp:127.0.0.1:90000\@foo");
1175 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1176 "creation of an ndmp device fails with too-large port");
1178 $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port");
1179 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1180 "creation of an ndmp device fails without ..\@device_name");
1182 $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1183 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1184 "creation of an ndmp device succeeds with correct syntax");
1186 ok($dev->property_set("ndmp_username", "foo"),
1187 "set ndmp_username property");
1188 is($dev->property_get("ndmp_username"), "foo",
1189 "..and get the value back");
1190 ok($dev->property_set("ndmp_password", "bar"),
1191 "set ndmp_password property");
1192 is($dev->property_get("ndmp_password"), "bar",
1193 "..and get the value back");
1195 ok($dev->property_set("verbose", 1),
1198 # set 'em back to the defaults
1199 $dev->property_set("ndmp_username", "ndmp");
1200 $dev->property_set("ndmp_password", "ndmp");
1202 # use a big read_block_size, checking that it's also settable
1203 # via read_buffer_size
1204 ok($dev->property_set("read_block_size", 256*1024),
1205 "can set read_block_size");
1206 is($dev->property_get("read_block_size"), 256*1024,
1207 "and its value is reflected");
1208 ok($dev->property_set("read_block_size", 64*1024),
1209 "set read_block_size back to something smaller");
1211 # ok, let's fire the thing up
1212 ok($dev->start($ACCESS_WRITE, "TEST1", "20090915000000"),
1213 "start device in write mode")
1214 or diag $dev->error_or_status();
1216 ok($dev->start_file($hdr),
1219 { # write to the file
1220 my $xfer = Amanda::Xfer->new([
1221 Amanda::Xfer::Source::Random->new(32768*21, 0xBEEFEE00),
1222 Amanda::Xfer::Dest::Device->new($dev, 0) ]);
1223 $xfer->start(make_cb(xmsg_cb => sub {
1224 my ($src, $msg, $xfer) = @_;
1225 if ($msg->{'type'} == $XMSG_ERROR) {
1226 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1227 } elsif ($msg->{'type'} == $XMSG_DONE) {
1228 Amanda::MainLoop::quit();
1232 Amanda::MainLoop::run();
1233 pass("wrote 21 blocks");
1238 or diag $dev->error_or_status();
1240 is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1241 "read label from (same) device")
1242 or diag $dev->error_or_status();
1244 is($dev->volume_label, "TEST1",
1245 "volume label read back correctly");
1247 ## label a device and check the label, but open a new device in between
1250 $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1251 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1252 "creation of an ndmp device succeeds with correct syntax");
1253 $dev->property_set("ndmp_username", "ndmp");
1254 $dev->property_set("ndmp_password", "ndmp");
1255 $dev->property_set("verbose", 1);
1258 ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1259 "start device in write mode")
1260 or diag $dev->error_or_status();
1263 or diag $dev->error_or_status();
1265 # Read the label with a new device.
1266 $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1267 is($dev->status(), $DEVICE_STATUS_SUCCESS,
1268 "creation of an ndmp device succeeds with correct syntax");
1269 $dev->property_set("ndmp_username", "ndmp");
1270 $dev->property_set("ndmp_password", "ndmp");
1271 $dev->property_set("verbose", 1);
1274 is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1275 "read label from device")
1276 or diag $dev->error_or_status();
1277 is($dev->volume_label, "TEST2",
1278 "volume label read back correctly");
1281 or diag $dev->error_or_status();
1284 # test the directtcp-target implementation
1287 ok($dev->directtcp_supported(), "is a directtcp target");
1288 for my $dev_use ('initiator', 'listener') {
1289 my ($xfer, $addrs, $dest_elt);
1290 if ($dev_use eq 'listener') {
1291 $addrs = $dev->listen(1);
1292 ok($addrs, "listen returns successfully") or die($dev->error_or_status());
1294 # set up an xfer to write to the device
1295 $dest_elt = Amanda::Xfer::Dest::DirectTCPConnect->new($addrs);
1297 # set up an xfer to write to the device
1298 $dest_elt = Amanda::Xfer::Dest::DirectTCPListen->new();
1300 $xfer = Amanda::Xfer->new([
1301 Amanda::Xfer::Source::Random->new(32768*34, 0xB00),
1306 $xfer->start(make_cb(xmsg_cb => sub {
1307 my ($src, $msg, $xfer) = @_;
1308 if ($msg->{'type'} == $XMSG_ERROR) {
1309 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1310 } elsif ($msg->{'type'} == $XMSG_DONE) {
1311 Amanda::MainLoop::quit();
1315 # write files from the connection until EOF
1318 my ($finish_connection, $start_device, $write_file_cb);
1321 $finish_connection = make_cb(finish_connection => sub {
1322 if ($dev_use eq 'listener') {
1323 $conn = $dev->accept();
1325 $addrs = $dest_elt->get_addrs();
1326 $conn = $dev->connect(1, $addrs);
1328 Amanda::MainLoop::call_later($start_device);
1332 $start_device = make_cb(start_device => sub {
1333 ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1334 "start device in write mode")
1335 or diag $dev->error_or_status();
1337 Amanda::MainLoop::call_later($write_file_cb);
1340 $write_file_cb = make_cb(write_file_cb => sub {
1341 ++$num_files < 20 or die "I seem to be in a loop!";
1343 ok($dev->start_file($hdr), "start file $num_files for writing");
1344 is($dev->file, $num_files, "..file number is correct");
1346 my ($ok, $size) = $dev->write_from_connection(32768*15);
1347 push @messages, sprintf("WRITE-%s-%d-%s-%s",
1348 $ok?"OK":"ERR", $size,
1349 $dev->is_eof()? "EOF":"!eof",
1350 $dev->is_eom()? "EOM":"!eom");
1351 ok($ok, "..write from connection succeeds");
1352 my $eof = $dev->is_eof();
1354 ok($dev->finish_file(), "..finish file after writing");
1357 Amanda::MainLoop::call_later($write_file_cb);
1361 Amanda::MainLoop::call_later($finish_connection);
1362 Amanda::MainLoop::run();
1363 is_deeply([@messages], [
1364 'WRITE-OK-491520-!eof-!eom',
1365 'WRITE-OK-491520-!eof-!eom',
1366 'WRITE-OK-131072-EOF-!eom',
1368 "a sequence of write_from_connection calls works correctly");
1372 if (my $err = $conn->close()) {
1377 # now try reading that back piece by piece
1380 my $filename = "$Installcheck::TMP/Amanda_Device_ndmp.tmp";
1381 open(my $dest_fh, ">", $filename);
1383 ok($dev->start($ACCESS_READ, undef, undef),
1384 "start device in read mode")
1385 or diag $dev->error_or_status();
1388 for ($file = 1; $file <= 3; $file++) {
1389 ok($dev->seek_file($file),
1391 is($dev->file, $file, "..file num is correct");
1392 is($dev->block, 0, "..block num is correct");
1394 # read the file, writing to our temp file. We'll check that the byte
1395 # sequence is correct later
1396 my $xfer = Amanda::Xfer->new([
1397 Amanda::Xfer::Source::Device->new($dev),
1398 Amanda::Xfer::Dest::Fd->new($dest_fh) ]);
1400 $xfer->start(make_cb(xmsg_cb => sub {
1401 my ($src, $msg, $xfer) = @_;
1402 if ($msg->{'type'} == $XMSG_ERROR) {
1403 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1404 } elsif ($msg->{'type'} == $XMSG_DONE) {
1405 Amanda::MainLoop::quit();
1408 Amanda::MainLoop::run();
1410 pass("read back file " . $file);
1416 # now read back and verify that file
1417 open(my $src_fh, "<", $filename);
1418 my $xfer = Amanda::Xfer->new([
1419 Amanda::Xfer::Source::Fd->new($src_fh),
1420 Amanda::Xfer::Dest::Null->new(0xB00) ]);
1422 $xfer->start(make_cb(xmsg_cb => sub {
1423 my ($src, $msg, $xfer) = @_;
1424 if ($msg->{'type'} == $XMSG_ERROR) {
1425 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1426 } elsif ($msg->{'type'} == $XMSG_DONE) {
1427 Amanda::MainLoop::quit();
1430 Amanda::MainLoop::run();
1432 pass("data in the three parts is correct");
1437 # Test read_to_connection
1439 # This requires something that can connect to a device and read from
1440 # it; the XFA does not have an XFER_MECH_DIRECTTCP_CONNECT, so we fake
1441 # it by manually connecting and then setting up an xfer with a regular
1442 # XferSourceFd. This works because the NDMP server will accept an
1443 # incoming connection before the Device API accept() method is called;
1444 # this trick may not work with other DirectTCP-capable devices. Also,
1445 # this doesn't work so well if there's an error in the xfer (e.g., a
1446 # random value mismatch). But tests are supposed to succeed!
1448 sub test_read2conn {
1449 my ($finished_cb) = @_;
1454 my $steps = define_steps
1455 cb_ref => \$finished_cb;
1458 my $addrs = $dev->listen(0);
1460 # now connect to that
1461 $sock = IO::Socket::INET->new(
1463 PeerHost => $addrs->[0][0],
1464 PeerPort => $addrs->[0][1],
1468 # and set up a transfer to read from that socket
1469 my $xfer = Amanda::Xfer->new([
1470 Amanda::Xfer::Source::Fd->new($sock),
1471 Amanda::Xfer::Dest::Null->new(0xB00) ]);
1473 $xfer->start(make_cb(xmsg_cb => sub {
1474 my ($src, $msg, $xfer) = @_;
1475 if ($msg->{'type'} == $XMSG_ERROR) {
1476 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1478 if ($msg->{'type'} == $XMSG_DONE) {
1479 push @events, "DONE";
1480 $steps->{'quit'}->();
1484 $steps->{'accept'}->();
1487 step accept => sub {
1488 $conn = $dev->accept();
1489 die $dev->error_or_status() unless ($conn);
1491 Amanda::MainLoop::call_later($steps->{'start_dev'});
1494 step start_dev => sub {
1495 ok($dev->start($ACCESS_READ, undef, undef),
1496 "start device in read mode")
1497 or diag $dev->error_or_status();
1499 Amanda::MainLoop::call_later($steps->{'read_part_cb'});
1502 step read_part_cb => sub {
1503 my $hdr = $dev->seek_file($file);
1504 die $dev->error_or_status() unless ($hdr);
1505 my $size = $dev->read_to_connection(0);
1506 push @events, "READ-$size";
1509 Amanda::MainLoop::call_later($steps->{'read_part_cb'});
1511 # close the connection, which will end the xfer, which will
1512 # result in a call to finished_cb. So there.
1513 push @events, "CLOSE";
1519 close $sock or die "close: $!";
1521 is_deeply([@events],
1522 [ "READ-491520", "READ-491520", "READ-131072", "CLOSE", "DONE" ],
1523 "sequential read_to_connection operations read the right amounts " .
1524 "and bytestream matches");
1529 test_read2conn(\&Amanda::MainLoop::quit);
1530 Amanda::MainLoop::run();
1532 # try two seek_file's in a row
1533 $hdr = $dev->seek_file(2);
1534 is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
1535 $hdr = $dev->seek_file(2);
1536 is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
1538 ## test seek_file's handling of EOM
1540 $hdr = $dev->seek_file(3);
1541 is($hdr->{type}, $Amanda::Header::F_DUMPFILE, "file 3 is a dumpfile");
1542 $hdr = $dev->seek_file(4);
1543 is($hdr->{type}, $Amanda::Header::F_TAPEEND, "file 4 is tapeend");
1544 $hdr = $dev->seek_file(5);
1545 is($hdr, undef, "file 5 is an error");
1546 $hdr = $dev->seek_file(6);
1547 is($hdr, undef, "file 6 is an error");
1551 unlink($input_filename);
1552 unlink($output_filename);