Imported Upstream version 3.3.2
[debian/amanda] / installcheck / Amanda_Device.pl
1 # Copyright (c) 2008-2012 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 => 609;
20 use File::Path qw( mkpath rmtree );
21 use Sys::Hostname;
22 use Carp;
23 use strict;
24 use warnings;
25
26 use lib "@amperldir@";
27 use Installcheck;
28 use Installcheck::Mock;
29 use Installcheck::Config;
30 use Amanda::Debug;
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 );
35 use Amanda::Paths;
36 use Amanda::Constants;
37 use Amanda::Util;
38 use Amanda::MainLoop;
39 use IO::Socket;
40
41 my $dev;
42 my $dev_name;
43 my ($vtape1, $vtape2);
44 my ($input_filename, $output_filename) =
45     ( "$Installcheck::TMP/input.tmp", "$Installcheck::TMP/output.tmp" );
46 my $taperoot = "$Installcheck::TMP/Amanda_Device_test_tapes";
47 my $testconf;
48
49 # we'll need some vtapes..
50 sub mkvtape {
51     my ($num) = @_;
52
53     my $mytape = "$taperoot/$num";
54     if (-d $mytape) { rmtree($mytape); }
55     mkpath("$mytape/data");
56     return $mytape;
57 }
58
59
60 # make up a fake dumpfile_t to write with
61 my $dumpfile = Amanda::Header->new();
62 $dumpfile->{type} = $Amanda::Header::F_DUMPFILE;
63 $dumpfile->{datestamp} = "20070102030405";
64 $dumpfile->{dumplevel} = 0;
65 $dumpfile->{compressed} = 1;
66 $dumpfile->{name} = "localhost";
67 $dumpfile->{disk} = "/home";
68 $dumpfile->{program} = "INSTALLCHECK";
69
70 my $write_file_count = 5;
71 sub write_file {
72     my ($seed, $length, $filenum) = @_;
73
74     $dumpfile->{'datestamp'} = "2000010101010$filenum";
75
76     ok($dev->start_file($dumpfile),
77         "start file $filenum")
78         or diag($dev->error_or_status());
79
80     is($dev->file(), $filenum,
81         "Device has correct filenum");
82
83     croak ("selected file size $length is *way* too big")
84         unless ($length < 1024*1024*10);
85     ok(Amanda::Device::write_random_to_device($seed, $length, $dev),
86         "write random data");
87
88     if(ok($dev->in_file(),
89         "still in_file")) {
90         ok($dev->finish_file(),
91             "finish_file")
92             or diag($dev->error_or_status());
93     } else {
94         pass("not in file, so not calling finish_file");
95     }
96 }
97
98 my $verify_file_count = 4;
99 sub verify_file {
100     my ($seed, $length, $filenum) = @_;
101
102     ok(my $read_dumpfile = $dev->seek_file($filenum),
103         "seek to file $filenum")
104         or diag($dev->error_or_status());
105     is($dev->file(), $filenum,
106         "device is really at file $filenum");
107     ok(header_for($read_dumpfile, $filenum),
108         "header is correct")
109         or diag($dev->error_or_status());
110     ok(Amanda::Device::verify_random_from_device($seed, $length, $dev),
111         "verified file contents");
112 }
113
114 sub header_for {
115     my ($hdr, $filenum) = @_;
116     return ($hdr and $hdr->{'datestamp'} eq "2000010101010$filenum");
117 }
118
119 # properties test
120
121 my @common_properties = (
122     'appendable',
123     'block_size',
124     'canonical_name',
125     'concurrency',
126     'max_block_size',
127     'medium_access_type',
128     'min_block_size',
129     'partial_deletion',
130     'full_deletion',
131     'streaming',
132 );
133
134 sub properties_include {
135     my ($got, $should_include, $msg) = @_;
136     my %got = map { $_->{'name'}, 1 } @$got;
137     my @missing = grep { !defined($got{$_}) } @$should_include;
138     if (@missing) {
139         fail($msg);
140         diag(" Expected properties: " . join(", ", @$should_include));
141         diag("      Got properties: " . join(", ", @$got));
142         diag("  Missing properties: " . join(", ", @missing));
143     } else {
144         pass($msg);
145     }
146 }
147
148 ####
149 ## get stuff set up
150
151 $testconf = Installcheck::Config->new();
152 $testconf->write();
153 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
154     or die("Could not load configuration");
155
156 # put the debug messages somewhere
157 Amanda::Debug::dbopen("installcheck");
158 Installcheck::log_test_output();
159
160 ####
161 ## Test errors a little bit
162
163 $dev = Amanda::Device->new("foobar:");
164 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
165     "creation of a bogus 'foobar:' device fails");
166
167 $dev = Amanda::Device->new("rait:{{");
168 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
169     "creation of a bogus 'rait:{{' device fails");
170
171 $dev = Amanda::Device->new("rait:{a,b");
172 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
173     "creation of a bogus 'rait:{a,b' device fails");
174
175 ####
176 ## first, test out the 'null' device.
177
178 $dev_name = "null:";
179
180 $dev = Amanda::Device->new($dev_name);
181 is($dev->status(), $DEVICE_STATUS_SUCCESS,
182     "create null device")
183     or diag $dev->error_or_status();
184 ok($dev->start($ACCESS_WRITE, "NULL1", "19780615010203"),
185     "start null device in write mode")
186     or diag $dev->error_or_status();
187
188 # try properties
189 properties_include([ $dev->property_list() ], [ @common_properties ],
190     "necessary properties listed on null device");
191 is($dev->property_get("canonical_name"), "null:",
192     "property_get(canonical_name) on null device");
193 is($dev->property_get("caNONical-name"), "null:",
194     "property_get(caNONical-name) on null device (case, dash-insensitivity)");
195 is_deeply([ $dev->property_get("canonical_name") ],
196     [ "null:", $PROPERTY_SURETY_GOOD, $PROPERTY_SOURCE_DEFAULT ],
197     "extended property_get returns correct surety/source");
198 for my $prop ($dev->property_list()) {
199     next unless $prop->{'name'} eq 'canonical_name';
200     is($prop->{'description'},
201         "The most reliable device name to use to refer to this device.",
202         "property info for canonical name is correct");
203 }
204 ok(!$dev->property_get("full_deletion"),
205     "property_get(full_deletion) on null device");
206 is($dev->property_get("comment"), undef,
207     "no comment by default");
208 ok($dev->property_set("comment", "well, that was silly"),
209     "set comment property");
210 is($dev->property_get("comment"), "well, that was silly",
211     "comment correctly stored");
212
213 # and write a file to it
214 write_file(0xabcde, 1024*256, 1);
215
216 # (don't finish the device, testing the finalize method's cleanup)
217
218 ####
219 ## Now some full device tests
220
221 ## VFS device
222
223 $vtape1 = mkvtape(1);
224 $dev_name = "file:$vtape1";
225
226 $dev = Amanda::Device->new($dev_name);
227 is($dev->status(), $DEVICE_STATUS_SUCCESS,
228     "$dev_name: create successful")
229     or diag($dev->error_or_status());
230
231 properties_include([ $dev->property_list() ],
232     [ @common_properties, 'max_volume_usage' ],
233     "necessary properties listed on vfs device");
234
235 # play with properties a little bit
236 ok($dev->property_set("comment", 16),
237     "set an string property to an integer");
238
239 ok($dev->property_set("comment", 16.0),
240     "set an string property to a float");
241
242 ok($dev->property_set("comment", "hi mom"),
243     "set an string property to a string");
244
245 ok($dev->property_set("comment", "32768"),
246     "set an integer property to a simple string");
247
248 ok($dev->property_set("comment", "32k"),
249     "set an integer property to a string with a unit");
250
251 ok($dev->property_set("block_size", 32768),
252     "set an integer property to an integer");
253
254 ok(!($dev->property_set("invalid-property-name", 32768)),
255     "set an invalid-property-name");
256
257 $dev->read_label();
258 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
259     "initially unlabeled")
260     or diag($dev->error_or_status());
261
262 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
263     "start in write mode")
264     or diag($dev->error_or_status());
265
266 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
267     "not unlabeled anymore")
268     or diag($dev->error_or_status());
269
270 for (my $i = 1; $i <= 3; $i++) {
271     write_file(0x2FACE, $dev->block_size()*10+17, $i);
272 }
273
274 ok($dev->finish(),
275     "finish device after write")
276     or diag($dev->error_or_status());
277
278 $dev->read_label();
279 ok(!($dev->status()),
280     "no error, at all, from read_label")
281     or diag($dev->error_or_status());
282
283 # append one more copy, to test ACCESS_APPEND
284
285 ok($dev->start($ACCESS_APPEND, undef, undef),
286     "start in append mode")
287     or diag($dev->error_or_status());
288
289 write_file(0xD0ED0E, $dev->block_size()*4, 4);
290
291 ok($dev->finish(),
292     "finish device after append")
293     or diag($dev->error_or_status());
294
295 # try reading the third file back, creating a new device
296 # object first, and skipping the read-label step.
297
298 $dev = undef;
299 $dev = Amanda::Device->new($dev_name);
300 is($dev->status(), $DEVICE_STATUS_SUCCESS,
301     "$dev_name: re-create successful")
302     or diag($dev->error_or_status());
303
304 ok($dev->start($ACCESS_READ, undef, undef),
305     "start in read mode")
306     or diag($dev->error_or_status());
307
308 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
309
310 {
311     # try two seek_file's in a row
312     my $hdr = $dev->seek_file(3);
313     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
314     $hdr = $dev->seek_file(3);
315     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
316 }
317
318 ok($dev->finish(),
319     "finish device after read")
320     or diag($dev->error_or_status());
321
322 # test erase
323 ok($dev->erase(),
324    "erase device")
325     or diag($dev->error_or_status());
326
327 ok($dev->erase(),
328    "erase device (again)")
329     or diag($dev->error_or_status());
330
331 ok($dev->finish(),
332    "finish device after erase")
333     or diag($dev->error_or_status());
334
335 # test monitor_free_space property (testing the monitoring would require a
336 # dedicated partition for the tests - it's not worth it)
337
338 ok($dev->property_get("monitor_free_space"),
339     "monitor_free_space property is set by default");
340
341 ok($dev->property_set("monitor_free_space", 0),
342     "monitor_free_space property can be set to false");
343
344 ok(!$dev->property_get("monitor_free_space"),
345     "monitor_free_space property value 'sticks'");
346
347 # test the LEOM functionality
348
349 $dev = undef;
350 $dev = Amanda::Device->new($dev_name);
351 is($dev->status(), $DEVICE_STATUS_SUCCESS,
352     "$dev_name: re-create successful")
353     or diag($dev->error_or_status());
354 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
355     "set MAX_VOLUME_USAGE to test LEOM");
356 ok($dev->property_set("LEOM", 1),
357     "set LEOM");
358 ok($dev->property_set("ENFORCE_MAX_VOLUME_USAGE", 0),
359     "set ENFORCE_MAX_VOLUME_USAGE");
360
361 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
362     "start in write mode")
363     or diag($dev->error_or_status());
364
365 ok($dev->start_file($dumpfile),
366     "start file 1")
367     or diag($dev->error_or_status());
368
369 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
370     "write random data into the early-warning zone");
371
372 ok(!$dev->is_eom,
373     "device does not indicates LEOM after writing when ENFORCE_MAX_VOLUME_USAGE is FALSE");
374
375 ok($dev->finish_file(),
376     "..but a finish_file is allowed to complete")
377     or diag($dev->error_or_status());
378
379 ok($dev->finish(),
380    "finish device after LEOM test")
381     or diag($dev->error_or_status());
382
383 $dev = undef;
384 $dev = Amanda::Device->new($dev_name);
385 is($dev->status(), $DEVICE_STATUS_SUCCESS,
386     "$dev_name: re-create successful")
387     or diag($dev->error_or_status());
388 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
389     "set MAX_VOLUME_USAGE to test LEOM");
390 ok($dev->property_set("LEOM", 1),
391     "set LEOM");
392 ok($dev->property_set("ENFORCE_MAX_VOLUME_USAGE", 1),
393     "set ENFORCE_MAX_VOLUME_USAGE");
394
395 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
396     "start in write mode")
397     or diag($dev->error_or_status());
398
399 ok($dev->start_file($dumpfile),
400     "start file 1")
401     or diag($dev->error_or_status());
402
403 ok(!$dev->is_eom,
404     "device does not indicate LEOM before writing");
405
406 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
407     "write random data into the early-warning zone");
408
409 ok($dev->is_eom,
410     "device indicates LEOM after writing");
411
412 ok($dev->finish_file(),
413     "..but a finish_file is allowed to complete")
414     or diag($dev->error_or_status());
415
416 ok($dev->finish(),
417    "finish device after LEOM test")
418     or diag($dev->error_or_status());
419
420 $dev = undef;
421 $dev = Amanda::Device->new($dev_name);
422 is($dev->status(), $DEVICE_STATUS_SUCCESS,
423     "$dev_name: re-create successful")
424     or diag($dev->error_or_status());
425 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
426     "set MAX_VOLUME_USAGE to test LEOM");
427 ok($dev->property_set("LEOM", 1),
428     "set LEOM");
429
430 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
431     "start in write mode")
432     or diag($dev->error_or_status());
433
434 ok($dev->start_file($dumpfile),
435     "start file 1")
436     or diag($dev->error_or_status());
437
438 ok(!$dev->is_eom,
439     "device does not indicate LEOM before writing");
440
441 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
442     "write random data into the early-warning zone");
443
444 ok($dev->is_eom,
445     "device indicates LEOM after writing as default value of ENFORCE_MAX_VOLUME_USAGE is true for vfs device");
446
447 ok($dev->finish_file(),
448     "..but a finish_file is allowed to complete")
449     or diag($dev->error_or_status());
450
451 ok($dev->finish(),
452    "finish device after LEOM test")
453     or diag($dev->error_or_status());
454
455 $dev = undef;
456 $dev = Amanda::Device->new($dev_name);
457 is($dev->status(), $DEVICE_STATUS_SUCCESS,
458     "$dev_name: re-create successful")
459     or diag($dev->error_or_status());
460 ok($dev->property_set("MAX_VOLUME_USAGE", "160k"),
461     "set MAX_VOLUME_USAGE to test LEOM while writing the first header");
462 ok($dev->property_set("LEOM", 1),
463     "set LEOM");
464
465 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
466     "start in write mode")
467     or diag($dev->error_or_status());
468
469 ok($dev->start_file($dumpfile),
470     "start file 1")
471     or diag($dev->error_or_status());
472
473 ok($dev->is_eom,
474     "device indicates LEOM after writing first header");
475
476 ok($dev->finish_file(),
477     "..but a finish_file is allowed to complete")
478     or diag($dev->error_or_status());
479
480 ok($dev->finish(),
481    "finish device after LEOM test")
482     or diag($dev->error_or_status());
483
484 ####
485 ## Test a RAIT device of two vfs devices.
486
487 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
488 $dev_name = "rait:file:{$vtape1,$vtape2}";
489
490 $dev = Amanda::Device->new($dev_name);
491 is($dev->status(), $DEVICE_STATUS_SUCCESS,
492    "$dev_name: create successful")
493     or diag($dev->error_or_status());
494
495 ok($dev->configure(1), "configure device");
496
497 properties_include([ $dev->property_list() ], [ @common_properties ],
498     "necessary properties listed on rait device");
499
500 is($dev->property_get("block_size"), 32768, # (RAIT default)
501     "rait device calculates a default block size correctly");
502
503 ok($dev->property_set("block_size", 32768*16),
504     "rait device accepts an explicit block size");
505
506 is($dev->property_get("block_size"), 32768*16,
507     "..and remembers it");
508
509 ok($dev->property_set("max_volume_usage", 32768*1000),
510     "rait device accepts property MAX_VOLUME_USAGE");
511
512 is($dev->property_get("max_volume_usage"), 32768*1000,
513     "..and remembers it");
514
515 $dev->read_label();
516 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
517    "initially unlabeled")
518     or diag($dev->error_or_status());
519
520 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
521    "start in write mode")
522     or diag($dev->error_or_status());
523
524 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
525    "not unlabeled anymore")
526     or diag($dev->error_or_status());
527
528 for (my $i = 1; $i <= 3; $i++) {
529     write_file(0x2FACE, $dev->block_size()*10+17, $i);
530 }
531
532 ok($dev->finish(),
533    "finish device after write")
534     or diag($dev->error_or_status());
535
536 $dev->read_label();
537 ok(!($dev->status()),
538    "no error, at all, from read_label")
539     or diag($dev->error_or_status());
540
541 # append one more copy, to test ACCESS_APPEND
542
543 ok($dev->start($ACCESS_APPEND, undef, undef),
544    "start in append mode")
545     or diag($dev->error_or_status());
546
547 write_file(0xD0ED0E, $dev->block_size()*4, 4);
548
549 ok($dev->finish(),
550    "finish device after append")
551     or diag($dev->error_or_status());
552
553 # try reading the third file back, creating a new device
554 # object first, and skipping the read-label step.
555
556 $dev = undef;
557 $dev = Amanda::Device->new($dev_name);
558 is($dev->status(), $DEVICE_STATUS_SUCCESS,
559     "$dev_name: re-create successful")
560     or diag($dev->error_or_status());
561
562 ok($dev->start($ACCESS_READ, undef, undef),
563    "start in read mode")
564     or diag($dev->error_or_status());
565
566 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
567
568 ok($dev->finish(),
569    "finish device after read")
570     or diag($dev->error_or_status());
571
572 ok($dev->start($ACCESS_READ, undef, undef),
573    "start in read mode after missing volume")
574     or diag($dev->error_or_status());
575
576 # corrupt the device somehow and hope it keeps working
577 rmtree("$taperoot/1");
578
579 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
580 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
581 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
582
583 ok($dev->finish(),
584    "finish device read after missing volume")
585     or diag($dev->error_or_status());
586
587 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
588    "start in write mode fails with missing volume")
589     or diag($dev->error_or_status());
590
591 undef $dev;
592
593 $dev_name = "rait:{file:$vtape2,MISSING}";
594 $dev = Amanda::Device->new($dev_name);
595
596 ok($dev->start($ACCESS_READ, undef, undef),
597    "start in read mode with MISSING")
598     or diag($dev->error_or_status());
599
600 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
601 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
602 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
603
604 ok($dev->finish(),
605    "finish device read with MISSING")
606     or diag($dev->error_or_status());
607
608 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
609    "start in write mode fails with MISSING")
610     or diag($dev->error_or_status());
611
612 undef $dev;
613
614 $dev = Amanda::Device->new_rait_from_children(
615     Amanda::Device->new("file:$vtape2"), undef);
616
617 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
618    "start a RAIT device in write mode fails, when created with 'undef'")
619     or diag($dev->error_or_status());
620
621 # Make two devices with different labels, should get a
622 # message accordingly.
623 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
624
625 my $n = 13;
626 for $dev_name ("file:$vtape1", "file:$vtape2") {
627     my $dev = Amanda::Device->new($dev_name);
628     is($dev->status(), $DEVICE_STATUS_SUCCESS,
629        "$dev_name: Open successful")
630         or diag($dev->error_or_status());
631     ok($dev->start($ACCESS_WRITE, "TESTCONF$n", undef),
632         "wrote label 'TESTCONF$n'");
633     ok($dev->finish(), "finished device");
634     $n++;
635 }
636
637 $dev = Amanda::Device->new_rait_from_children(
638     Amanda::Device->new("file:$vtape1"),
639     Amanda::Device->new("file:$vtape2"));
640 is($dev->status(), $DEVICE_STATUS_SUCCESS,
641    "new_rait_from_children: Open successful")
642     or diag($dev->error_or_status());
643
644 $dev->read_label();
645 ok($dev->status() & $DEVICE_STATUS_VOLUME_ERROR,
646    "Label mismatch error handled correctly")
647     or diag($dev->error_or_status());
648
649 # Use some config to set a block size on a child device
650 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
651 $dev_name = "rait:{file:$vtape1,mytape2}";
652
653 $testconf = Installcheck::Config->new();
654 $testconf->add_device("mytape2", [
655     "tapedev" => "\"file:$vtape2\"",
656     "device_property" => "\"BLOCK_SIZE\" \"64k\""
657 ]);
658 $testconf->write();
659 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
660     or die("Could not load configuration");
661
662 $dev = Amanda::Device->new($dev_name);
663 is($dev->status(), $DEVICE_STATUS_SUCCESS,
664    "$dev_name: create successful")
665     or diag($dev->error_or_status());
666
667 ok($dev->configure(1), "configure device");
668
669 is($dev->property_get("block_size"), 65536,
670     "rait device calculates a block size from its children correctly");
671
672 # Test an S3 device if the proper environment variables are set
673 my $S3_SECRET_KEY = $ENV{'INSTALLCHECK_S3_SECRET_KEY'};
674 my $S3_ACCESS_KEY = $ENV{'INSTALLCHECK_S3_ACCESS_KEY'};
675 my $DEVPAY_SECRET_KEY = $ENV{'INSTALLCHECK_DEVPAY_SECRET_KEY'};
676 my $DEVPAY_ACCESS_KEY = $ENV{'INSTALLCHECK_DEVPAY_ACCESS_KEY'};
677 my $DEVPAY_USER_TOKEN = $ENV{'INSTALLCHECK_DEVPAY_USER_TOKEN'};
678
679 my $run_s3_tests = defined $S3_SECRET_KEY && defined $S3_ACCESS_KEY;
680 my $run_devpay_tests = defined $DEVPAY_SECRET_KEY &&
681     defined $DEVPAY_ACCESS_KEY && $DEVPAY_USER_TOKEN;
682
683 my $s3_make_device_count = 7;
684 sub s3_make_device($$) {
685     my ($dev_name, $kind) = @_;
686     $dev = Amanda::Device->new($dev_name);
687     is($dev->status(), $DEVICE_STATUS_SUCCESS,
688        "$dev_name: create successful")
689         or diag($dev->error_or_status());
690
691     my @s3_props = ( 's3_access_key', 's3_secret_key' );
692     push @s3_props, 's3_user_token' if ($kind eq "devpay");
693     properties_include([ $dev->property_list() ], [ @common_properties, @s3_props ],
694         "necessary properties listed on s3 device");
695
696     ok($dev->property_set('BLOCK_SIZE', 32768*2),
697         "set block size")
698         or diag($dev->error_or_status());
699
700     # might as well save a few cents while testing this property..
701     ok($dev->property_set('S3_STORAGE_CLASS', 'REDUCED_REDUNDANCY'),
702         "set storage class")
703         or diag($dev->error_or_status());
704
705     if ($kind eq "s3") {
706         # use regular S3 credentials
707         ok($dev->property_set('S3_ACCESS_KEY', $S3_ACCESS_KEY),
708            "set S3 access key")
709         or diag($dev->error_or_status());
710
711         ok($dev->property_set('S3_SECRET_KEY', $S3_SECRET_KEY),
712            "set S3 secret key")
713             or diag($dev->error_or_status());
714
715         pass("(placeholder)");
716     } elsif ($kind eq "devpay") {
717         # use devpay credentials
718         ok($dev->property_set('S3_ACCESS_KEY', $DEVPAY_ACCESS_KEY),
719            "set devpay access key")
720         or diag($dev->error_or_status());
721
722         ok($dev->property_set('S3_SECRET_KEY', $DEVPAY_SECRET_KEY),
723            "set devpay secret key")
724             or diag($dev->error_or_status());
725
726         ok($dev->property_set('S3_USER_TOKEN', $DEVPAY_USER_TOKEN),
727            "set devpay user token")
728             or diag($dev->error_or_status());
729     } else {
730         croak("didn't recognize the device kind, so no credentials were set");
731     }
732     return $dev;
733 }
734
735 my $base_name;
736
737 SKIP: {
738     skip "define \$INSTALLCHECK_S3_{SECRET,ACCESS}_KEY to run S3 tests",
739             101 +
740             1 * $verify_file_count +
741             7 * $write_file_count +
742             13 * $s3_make_device_count
743         unless $run_s3_tests;
744
745     $dev_name = "s3:";
746     $dev = Amanda::Device->new($dev_name);
747     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
748          "creating $dev_name fails miserably");
749
750     $dev_name = "s3:foo";
751     $dev = Amanda::Device->new($dev_name);
752
753     ok($dev->property_get("full_deletion"),
754        "property_get(full_deletion) on s3 device");
755
756     ok($dev->property_get("leom"),
757        "property_get(leom) on s3 device");
758
759     # test parsing of boolean values
760     # (s3 is the only device driver that has a writable boolean property at the
761     # moment)
762
763     my @verbose_vals = (
764         {'val' => '1', 'true' => 1},
765         {'val' => '0', 'true' => 0},
766         {'val' => 't', 'true' => 1},
767         {'val' => 'true', 'true' => 1},
768         {'val' => 'f', 'true' => 0},
769         {'val' => 'false', 'true' => 0},
770         {'val' => 'y', 'true' => 1},
771         {'val' => 'yes', 'true' => 1},
772         {'val' => 'n', 'true' => 0},
773         {'val' => 'no', 'true' => 0},
774         {'val' => 'on', 'true' => 1},
775         {'val' => 'off', 'true' => 0},
776         {'val' => 'oFf', 'true' => 0},
777         );
778
779     foreach my $v (@verbose_vals) {
780         $dev_name = "s3:foo";
781         $dev = Amanda::Device->new($dev_name);
782
783         $testconf = Installcheck::Config->new();
784         $testconf->add_param("device_property", "\"verbose\" \"$v->{'val'}\"");
785         $testconf->write();
786         config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
787             or die("Could not load configuration");
788
789         ok($dev->configure(1),
790            "configured device with verbose set to $v->{'val'}")
791             or diag($dev->error_or_status());
792
793         my $get_val = $dev->property_get('verbose');
794         # see if truth-iness matches
795         my $expec = $v->{'true'}? "true" : "false";
796         is(!!$dev->property_get('verbose'), !!$v->{'true'},
797            "device_property 'VERBOSE' '$v->{'val'}' => property_get(verbose) returning $expec");
798     }
799
800     # test unparsable property
801     $dev_name = "s3:foo";
802     $dev = Amanda::Device->new($dev_name);
803
804     $testconf = Installcheck::Config->new();
805     $testconf->add_param("device_property", "\"verbose\" \"foo\"");
806     $testconf->write();
807     config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
808         or die("Could not load configuration");
809
810     ok(!$dev->configure(1),
811        "failed to configure device with verbose set to foo");
812
813     like($dev->error_or_status(), qr/'verbose'/,
814          "error message mentions property name");
815
816     like($dev->error_or_status(), qr/'foo'/,
817          "error message mentions property value");
818
819     like($dev->error_or_status(), qr/gboolean/,
820          "error message mentions property type");
821
822     my $hostname  = hostname();
823     $hostname =~ s/\./-/g;
824     $base_name = "$S3_ACCESS_KEY-installcheck-$hostname";
825     $dev_name = "s3:$base_name-s3";
826     $dev = s3_make_device($dev_name, "s3");
827     $dev->read_label();
828     my $status = $dev->status();
829     # this test appears very liberal, but catches the case where setup_handle fails without
830     # giving false positives
831     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
832        "status is either OK or possibly unlabeled")
833         or diag($dev->error_or_status());
834
835     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
836        "start in write mode")
837         or diag($dev->error_or_status());
838
839     ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
840        "it's labeled now")
841         or diag($dev->error_or_status());
842
843     for (my $i = 1; $i <= 3; $i++) {
844         write_file(0x2FACE, $dev->block_size()*10, $i);
845     }
846
847     ok($dev->finish(),
848        "finish device after write")
849         or diag($dev->error_or_status());
850
851     $dev->read_label();
852     ok(!($dev->status()),
853        "no error, at all, from read_label")
854         or diag($dev->error_or_status());
855
856     # append one more copy, to test ACCESS_APPEND
857
858     ok($dev->start($ACCESS_APPEND, undef, undef),
859        "start in append mode")
860         or diag($dev->error_or_status());
861
862     write_file(0xD0ED0E, $dev->block_size()*10, 4);
863
864     ok($dev->finish(),
865        "finish device after append")
866         or diag($dev->error_or_status());
867
868     # try reading the third file back
869
870     ok($dev->start($ACCESS_READ, undef, undef),
871        "start in read mode")
872         or diag($dev->error_or_status());
873
874     verify_file(0x2FACE, $dev->block_size()*10, 3);
875
876     # test EOT indications on reading
877     my $hdr = $dev->seek_file(4);
878     is($hdr->{'type'}, $Amanda::Header::F_DUMPFILE,
879         "file 4 has correct type F_DUMPFILE");
880
881     $hdr = $dev->seek_file(5);
882     is($hdr->{'type'}, $Amanda::Header::F_TAPEEND,
883         "file 5 has correct type F_TAPEEND");
884
885     $hdr = $dev->seek_file(6);
886     is($hdr, undef, "seek_file returns undef for file 6");
887
888     ok($dev->finish(),
889        "finish device after read")
890         or diag($dev->error_or_status());    # (note: we don't use write_max_size here,
891                                              # as the maximum for S3 is very large)
892
893     ok($dev->erase(),
894        "erase device")
895        or diag($dev->error_or_status());
896
897     ok($dev->erase(),
898        "erase device (again)")
899        or diag($dev->error_or_status());
900
901     ok($dev->finish(),
902        "finish device after erase")
903         or diag($dev->error_or_status());
904
905     $dev->read_label();
906     $status = $dev->status();
907     ok($status & $DEVICE_STATUS_VOLUME_UNLABELED,
908        "status is unlabeled after an erase")
909         or diag($dev->error_or_status());
910
911     $dev = s3_make_device($dev_name, "s3");
912
913     ok($dev->erase(),
914        "erase device right after creation")
915        or diag($dev->error_or_status());
916
917     $dev = s3_make_device($dev_name, "s3");
918
919     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=false
920     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
921        "set MAX_VOLUME_USAGE to test LEOM");
922
923     ok($dev->property_set("LEOM", 1),
924         "set LEOM");
925
926     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
927        "start in write mode")
928         or diag($dev->error_or_status());
929
930     write_file(0x2FACE, 440*1024, 1);
931
932     ok(!$dev->is_eom,
933         "device does not indicate LEOM after writing as property ENFORCE_MAX_VOLUME_USAGE not set and its default value is false");
934
935     ok($dev->finish(),
936        "finish device after LEOM test")
937        or diag($dev->error_or_status());
938     
939     ok($dev->erase(),
940        "erase device")
941        or diag($dev->error_or_status());
942     
943     $dev = s3_make_device($dev_name, "s3");
944
945     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=true
946     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
947        "set MAX_VOLUME_USAGE to test LEOM");
948
949     ok($dev->property_set('ENFORCE_MAX_VOLUME_USAGE', 1 ),
950        "set ENFORCE_MAX_VOLUME_USAGE");
951
952     ok($dev->property_set("LEOM", 1),
953         "set LEOM");
954
955     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
956        "start in write mode")
957         or diag($dev->error_or_status());
958
959     write_file(0x2FACE, 440*1024, 1);
960
961     ok($dev->is_eom,
962         "device indicates LEOM after writing, when property ENFORCE_MAX_VOLUME_USAGE set to true");
963
964     ok($dev->finish(),
965        "finish device after LEOM test")
966        or diag($dev->error_or_status());
967
968     ok($dev->erase(),
969        "erase device")
970        or diag($dev->error_or_status());
971     
972     $dev = s3_make_device($dev_name, "s3");
973
974     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=false
975     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
976        "set MAX_VOLUME_USAGE to test LEOM");
977
978     ok($dev->property_set('ENFORCE_MAX_VOLUME_USAGE', 0 ),
979        "set ENFORCE_MAX_VOLUME_USAGE");
980
981     ok($dev->property_set("LEOM", 1),
982         "set LEOM");
983
984     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
985        "start in write mode")
986         or diag($dev->error_or_status());
987
988     write_file(0x2FACE, 440*1024, 1);
989
990     ok(!$dev->is_eom,
991         "device does not indicate LEOM after writing, when property ENFORCE_MAX_VOLUME_USAGE set to false");
992
993     ok($dev->finish(),
994        "finish device after LEOM test")
995        or diag($dev->error_or_status());
996     
997     ok($dev->erase(),
998        "erase device")
999        or diag($dev->error_or_status());
1000     
1001     # try with empty user token
1002     $dev_name = lc("s3:$base_name-s3");
1003     $dev = s3_make_device($dev_name, "s3");
1004     ok($dev->property_set('S3_USER_TOKEN', ''),
1005        "set devpay user token")
1006         or diag($dev->error_or_status());
1007
1008     $dev->read_label();
1009     $status = $dev->status();
1010     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1011        "status is either OK or possibly unlabeled")
1012         or diag($dev->error_or_status());
1013
1014     $dev->finish();
1015
1016     ok($dev->erase(),
1017        "erase device")
1018        or diag($dev->error_or_status());
1019
1020     # try a eu-constrained bucket
1021     $dev_name = lc("s3:$base_name-s3-eu");
1022     $dev = s3_make_device($dev_name, "s3");
1023     ok($dev->property_set('S3_BUCKET_LOCATION', 'EU'),
1024        "set S3 bucket location to 'EU'")
1025         or diag($dev->error_or_status());
1026
1027     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1028        "start in write mode")
1029         or diag($dev->error_or_status());
1030
1031     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1032        "status is OK")
1033         or diag($dev->error_or_status());
1034
1035     $dev->finish();
1036
1037     ok($dev->erase(),
1038        "erase device")
1039        or diag($dev->error_or_status());
1040
1041     # try a wildcard-constrained bucket
1042     $dev_name = lc("s3:$base_name-s3-wild");
1043     $dev = s3_make_device($dev_name, "s3");
1044     ok($dev->property_set('S3_BUCKET_LOCATION', '*'),
1045        "set S3 bucket location to ''")
1046         or diag($dev->error_or_status());
1047
1048     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1049        "start in write mode")
1050         or diag($dev->error_or_status());
1051
1052     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1053        "status is OK")
1054         or diag($dev->error_or_status());
1055
1056     $dev->finish();
1057
1058     # test again with invalid ca_info
1059     $dev = s3_make_device($dev_name, "s3");
1060     SKIP: {
1061         skip "SSL not supported; can't check SSL_CA_INFO", 2
1062             unless $dev->property_get('S3_SSL');
1063
1064         ok($dev->property_set('SSL_CA_INFO', '/dev/null'),
1065            "set invalid SSL/TLS CA certificate")
1066             or diag($dev->error_or_status());
1067
1068         ok(!$dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1069            "start in write mode")
1070             or diag($dev->error_or_status());
1071
1072         isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1073            "status is OK")
1074             or diag($dev->error_or_status());
1075
1076         $dev->finish();
1077     }
1078
1079     # test again with our own CA bundle
1080     $dev = s3_make_device($dev_name, "s3");
1081     SKIP: {
1082         skip "SSL not supported; can't check SSL_CA_INFO", 4
1083             unless $dev->property_get('S3_SSL');
1084         ok($dev->property_set('SSL_CA_INFO', "$srcdir/data/aws-bundle.crt"),
1085            "set our own SSL/TLS CA certificate bundle")
1086             or diag($dev->error_or_status());
1087
1088         ok($dev->erase(),
1089            "erase device")
1090             or diag($dev->error_or_status());
1091
1092         ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1093            "start in write mode")
1094             or diag($dev->error_or_status());
1095
1096         is($dev->status(), $DEVICE_STATUS_SUCCESS,
1097            "status is OK")
1098             or diag($dev->error_or_status());
1099
1100         $dev->finish();
1101     }
1102
1103     ok($dev->erase(),
1104        "erase device")
1105        or diag($dev->error_or_status());
1106
1107     # bucket names incompatible with location constraint
1108     $dev_name = "s3:-$base_name-s3-eu";
1109     $dev = s3_make_device($dev_name, "s3");
1110
1111     ok($dev->property_set('S3_BUCKET_LOCATION', ''),
1112        "should be able to set an empty S3 bucket location with an incompatible name")
1113         or diag($dev->error_or_status());
1114
1115     $dev_name = "s3:$base_name-s3.eu";
1116     $dev = s3_make_device($dev_name, "s3");
1117
1118     ok($dev->property_set('S3_BUCKET_LOCATION', ''),
1119        "should be able to set an empty S3 bucket location with an incompatible name")
1120         or diag($dev->error_or_status());
1121
1122     $dev_name = "s3:-$base_name-s3-eu";
1123     $dev = s3_make_device($dev_name, "s3");
1124
1125     ok(!$dev->property_set('S3_BUCKET_LOCATION', 'EU'),
1126        "should not be able to set S3 bucket location with an incompatible name")
1127         or diag($dev->error_or_status());
1128
1129     $dev_name = lc("s3:$base_name-s3-eu");
1130     $dev = s3_make_device($dev_name, "s3");
1131     ok($dev->property_set('S3_BUCKET_LOCATION', 'XYZ'),
1132        "should be able to set S3 bucket location with a compatible name")
1133         or diag($dev->error_or_status());
1134     $dev->read_label();
1135     $status = $dev->status();
1136     ok(($status == $DEVICE_STATUS_DEVICE_ERROR),
1137        "status is DEVICE_STATUS_DEVICE_ERROR")
1138         or diag($dev->error_or_status());
1139     my $error_msg = $dev->error_or_status();
1140     ok(($dev->error_or_status() == "While creating new S3 bucket: The specified location-constraint is not valid (Unknown) (HTTP 400)"),
1141        "invalid location-constraint")
1142        or diag("bad error: " . $dev->error_or_status());
1143
1144 }
1145
1146 SKIP: {
1147     # in this case, most of our code has already been exercised
1148     # just make sure that authentication works as a basic sanity check
1149     skip "skipping abbreviated devpay tests", $s3_make_device_count + 1
1150         unless $run_devpay_tests;
1151     $dev_name = "s3:$base_name-devpay";
1152     $dev = s3_make_device($dev_name, "devpay");
1153     $dev->read_label();
1154     my $status = $dev->status();
1155     # this test appears very liberal, but catches the case where setup_handle fails without
1156     # giving false positives
1157     ok(($status == 0) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1158        "status is either OK or possibly unlabeled")
1159         or diag($dev->error_or_status());
1160 }
1161
1162 # Test a tape device if the proper environment variables are set
1163 my $TAPE_DEVICE = $ENV{'INSTALLCHECK_TAPE_DEVICE'};
1164 my $run_tape_tests = defined $TAPE_DEVICE;
1165 SKIP: {
1166     skip "define \$INSTALLCHECK_TAPE_DEVICE to run tape tests",
1167             30 +
1168             7 * $verify_file_count +
1169             5 * $write_file_count
1170         unless $run_tape_tests;
1171
1172     $dev_name = "tape:$TAPE_DEVICE";
1173     $dev = Amanda::Device->new($dev_name);
1174     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1175         "$dev_name: create successful")
1176         or diag($dev->error_or_status());
1177
1178     my $status = $dev->read_label();
1179     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1180        "status is either OK or possibly unlabeled")
1181         or diag($dev->error_or_status());
1182
1183     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1184         "start in write mode")
1185         or diag($dev->error_or_status());
1186
1187     ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
1188         "not unlabeled anymore")
1189         or diag($dev->error_or_status());
1190
1191     for (my $i = 1; $i <= 4; $i++) {
1192         write_file(0x2FACE+$i, $dev->block_size()*10+17, $i);
1193     }
1194
1195     ok($dev->finish(),
1196         "finish device after write")
1197         or diag($dev->error_or_status());
1198
1199     $dev->read_label();
1200     ok(!($dev->status()),
1201         "no error, at all, from read_label")
1202         or diag($dev->error_or_status());
1203
1204     is($dev->volume_label(), "TESTCONF13",
1205         "read_label reads the correct label")
1206         or diag($dev->error_or_status());
1207
1208     # append one more copy, to test ACCESS_APPEND
1209
1210     # if final_filemarks is 1, then the tape device will use F_NOOP,
1211     # inserting an extra file, and we'll be appending at file number 6.
1212     my $append_fileno = ($dev->property_get("FINAL_FILEMARKS") == 2)? 5:6;
1213
1214     SKIP: {
1215         skip "APPEND not supported", $write_file_count + 2
1216             unless $dev->property_get("APPENDABLE");
1217
1218         ok($dev->start($ACCESS_APPEND, undef, undef),
1219             "start in append mode")
1220             or diag($dev->error_or_status());
1221
1222         write_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1223
1224         ok($dev->finish(),
1225             "finish device after append")
1226             or diag($dev->error_or_status());
1227     }
1228
1229     # try reading the second and third files back, creating a new
1230     # device object first, and skipping the read-label step.
1231
1232     $dev = undef;
1233     $dev = Amanda::Device->new($dev_name);
1234     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1235         "$dev_name: re-create successful")
1236         or diag($dev->error_or_status());
1237
1238     # use a big read_block_size, checking that it's also settable
1239     # via read_buffer_size
1240     ok($dev->property_set("read_buffer_size", 256*1024),
1241         "can set read_buffer_size");
1242     is($dev->property_get("read_block_size"), 256*1024,
1243         "and its value is reflected in read_block_size");
1244     ok($dev->property_set("read_block_size", 32*1024),
1245         "can set read_block_size");
1246
1247     ok($dev->start($ACCESS_READ, undef, undef),
1248         "start in read mode")
1249         or diag($dev->error_or_status());
1250
1251     # now verify those files in a particular order to trigger all of the
1252     # seeking edge cases
1253
1254     verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1255     verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1256     verify_file(0x2FACE+4, $dev->block_size()*10+17, 4);
1257     verify_file(0x2FACE+3, $dev->block_size()*10+17, 3);
1258     verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1259
1260     # try re-seeking to the same file
1261     ok(header_for($dev->seek_file(2), 2), "seek to file 2 the first time");
1262     verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1263     ok(header_for($dev->seek_file(2), 2), "seek to file 2 the third time");
1264
1265     # and seek through the same pattern *without* reading to EOF
1266     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1267     ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1268     ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1269     ok(header_for($dev->seek_file(3), 3), "seek to file 3");
1270     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1271
1272     SKIP: {
1273         skip "APPEND not supported", $verify_file_count
1274             unless $dev->property_get("APPENDABLE");
1275         verify_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1276     }
1277
1278     ok($dev->finish(),
1279         "finish device after read")
1280         or diag($dev->error_or_status());
1281
1282     # tickle a regression in improperly closing fd's
1283     ok($dev->finish(),
1284         "finish device again after read")
1285         or diag($dev->error_or_status());
1286
1287     ok($dev->read_label() == $DEVICE_STATUS_SUCCESS,
1288         "read_label after second finish (used to fail)")
1289         or diag($dev->error_or_status());
1290
1291     # finally, run the device with FSF and BSF set to "no", to test the
1292     # fallback schemes for this condition
1293
1294     $dev = undef;
1295     $dev = Amanda::Device->new($dev_name);
1296     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1297         "$dev_name: re-create successful")
1298         or diag($dev->error_or_status());
1299     $dev->property_set("fsf", "no");
1300     $dev->property_set("bsf", "no");
1301
1302     ok($dev->start($ACCESS_READ, undef, undef),
1303         "start in read mode")
1304         or diag($dev->error_or_status());
1305
1306     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1307     ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1308     ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1309
1310     ok($dev->finish(),
1311         "finish device after read")
1312         or diag($dev->error_or_status());
1313 }
1314
1315 SKIP: {
1316     skip "not built with ndmp and server", 94 unless
1317         Amanda::Util::built_with_component("ndmp") and
1318         Amanda::Util::built_with_component("server");
1319
1320     my $dev;
1321     my $testconf = Installcheck::Config->new();
1322     $testconf->write();
1323
1324     my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
1325     if ($cfg_result != $CFGERR_OK) {
1326         my ($level, @errors) = Amanda::Config::config_errors();
1327         die(join "\n", @errors);
1328     }
1329
1330     my $ndmp = Installcheck::Mock::NdmpServer->new();
1331     my $ndmp_port = $ndmp->{'port'};
1332     my $drive = $ndmp->{'drive'};
1333     pass("started ndmjob in daemon mode");
1334
1335     # set up a header for use below
1336     my $hdr = Amanda::Header->new();
1337     $hdr->{type} = $Amanda::Header::F_DUMPFILE;
1338     $hdr->{datestamp} = "20070102030405";
1339     $hdr->{dumplevel} = 0;
1340     $hdr->{compressed} = 1;
1341     $hdr->{name} = "localhost";
1342     $hdr->{disk} = "/home";
1343     $hdr->{program} = "INSTALLCHECK";
1344
1345     $dev = Amanda::Device->new("ndmp:127.0.0.1:9i1\@foo");
1346     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1347         "creation of an ndmp device fails with invalid port");
1348
1349     $dev = Amanda::Device->new("ndmp:127.0.0.1:90000\@foo");
1350     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1351         "creation of an ndmp device fails with too-large port");
1352
1353     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port");
1354     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1355         "creation of an ndmp device fails without ..\@device_name");
1356
1357     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1358     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1359         "creation of an ndmp device succeeds with correct syntax");
1360
1361     ok($dev->property_set("ndmp_username", "foo"),
1362         "set ndmp_username property");
1363     is($dev->property_get("ndmp_username"), "foo",
1364         "..and get the value back");
1365     ok($dev->property_set("ndmp_password", "bar"),
1366         "set ndmp_password property");
1367     is($dev->property_get("ndmp_password"), "bar",
1368         "..and get the value back");
1369
1370     ok($dev->property_set("verbose", 1),
1371         "set VERBOSE");
1372
1373     # set 'em back to the defaults
1374     $dev->property_set("ndmp_username", "ndmp");
1375     $dev->property_set("ndmp_password", "ndmp");
1376
1377     # use a big read_block_size, checking that it's also settable
1378     # via read_buffer_size
1379     ok($dev->property_set("read_block_size", 256*1024),
1380     "can set read_block_size");
1381     is($dev->property_get("read_block_size"), 256*1024,
1382     "and its value is reflected");
1383     ok($dev->property_set("read_block_size", 64*1024),
1384     "set read_block_size back to something smaller");
1385
1386     # ok, let's fire the thing up
1387     ok($dev->start($ACCESS_WRITE, "TEST1", "20090915000000"),
1388         "start device in write mode")
1389         or diag $dev->error_or_status();
1390
1391     ok($dev->start_file($hdr),
1392         "start_file");
1393
1394     {   # write to the file
1395         my $xfer = Amanda::Xfer->new([
1396                 Amanda::Xfer::Source::Random->new(32768*21, 0xBEEFEE00),
1397                 Amanda::Xfer::Dest::Device->new($dev, 0) ]);
1398         $xfer->start(make_cb(xmsg_cb => sub {
1399             my ($src, $msg, $xfer) = @_;
1400             if ($msg->{'type'} == $XMSG_ERROR) {
1401                 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1402             } elsif ($msg->{'type'} == $XMSG_DONE) {
1403                 Amanda::MainLoop::quit();
1404             }
1405         }));
1406
1407         Amanda::MainLoop::run();
1408         pass("wrote 21 blocks");
1409     }
1410
1411     ok($dev->finish(),
1412         "finish device")
1413         or diag $dev->error_or_status();
1414
1415     is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1416         "read label from (same) device")
1417         or diag $dev->error_or_status();
1418
1419     is($dev->volume_label, "TEST1",
1420         "volume label read back correctly");
1421
1422     ## label a device and check the label, but open a new device in between
1423
1424     # Write a label
1425     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1426     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1427         "creation of an ndmp device succeeds with correct syntax");
1428     $dev->property_set("ndmp_username", "ndmp");
1429     $dev->property_set("ndmp_password", "ndmp");
1430     $dev->property_set("verbose", 1);
1431
1432     # Write the label
1433     ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1434         "start device in write mode")
1435         or diag $dev->error_or_status();
1436     ok($dev->finish(),
1437         "finish device")
1438         or diag $dev->error_or_status();
1439
1440     # Read the label with a new device.
1441     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1442     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1443         "creation of an ndmp device succeeds with correct syntax");
1444     $dev->property_set("ndmp_username", "ndmp");
1445     $dev->property_set("ndmp_password", "ndmp");
1446     $dev->property_set("verbose", 1);
1447
1448     # read the label
1449     is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1450         "read label from device")
1451         or diag $dev->error_or_status();
1452     is($dev->volume_label, "TEST2",
1453         "volume label read back correctly");
1454     ok($dev->finish(),
1455         "finish device")
1456         or diag $dev->error_or_status();
1457
1458     #
1459     # test the directtcp-target implementation
1460     #
1461
1462     ok($dev->directtcp_supported(), "is a directtcp target");
1463     for my $dev_use ('initiator', 'listener') {
1464         my ($xfer, $addrs, $dest_elt);
1465         if ($dev_use eq 'listener') {
1466             $addrs = $dev->listen(1);
1467             ok($addrs, "listen returns successfully") or die($dev->error_or_status());
1468
1469             # set up an xfer to write to the device
1470             $dest_elt = Amanda::Xfer::Dest::DirectTCPConnect->new($addrs);
1471         } else {
1472             # set up an xfer to write to the device
1473             $dest_elt = Amanda::Xfer::Dest::DirectTCPListen->new();
1474         }
1475         $xfer = Amanda::Xfer->new([
1476                 Amanda::Xfer::Source::Random->new(32768*34, 0xB00),
1477                 $dest_elt,
1478             ]);
1479
1480         my @messages;
1481         $xfer->start(make_cb(xmsg_cb => sub {
1482             my ($src, $msg, $xfer) = @_;
1483             if ($msg->{'type'} == $XMSG_ERROR) {
1484                 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1485             } elsif ($msg->{'type'} == $XMSG_DONE) {
1486                 Amanda::MainLoop::quit();
1487             }
1488         }));
1489
1490         # write files from the connection until EOF
1491         my $num_files;
1492         my $conn;
1493         my ($finish_connection, $start_device, $write_file_cb);
1494
1495
1496         $finish_connection = make_cb(finish_connection => sub {
1497             if ($dev_use eq 'listener') {
1498                 $conn = $dev->accept();
1499             } else {
1500                 $addrs = $dest_elt->get_addrs();
1501                 $conn = $dev->connect(1, $addrs);
1502             }
1503             Amanda::MainLoop::call_later($start_device);
1504         });
1505
1506
1507         $start_device = make_cb(start_device => sub {
1508             ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1509                 "start device in write mode")
1510                 or diag $dev->error_or_status();
1511
1512             Amanda::MainLoop::call_later($write_file_cb);
1513         });
1514
1515         $write_file_cb = make_cb(write_file_cb => sub {
1516             ++$num_files < 20 or die "I seem to be in a loop!";
1517
1518             ok($dev->start_file($hdr), "start file $num_files for writing");
1519             is($dev->file, $num_files, "..file number is correct");
1520
1521             my ($ok, $size) = $dev->write_from_connection(32768*15);
1522             push @messages, sprintf("WRITE-%s-%d-%s-%s",
1523                 $ok?"OK":"ERR", $size,
1524                 $dev->is_eof()? "EOF":"!eof",
1525                 $dev->is_eom()? "EOM":"!eom");
1526             ok($ok, "..write from connection succeeds");
1527             my $eof = $dev->is_eof();
1528
1529             ok($dev->finish_file(), "..finish file after writing");
1530
1531             if (!$eof) {
1532                 Amanda::MainLoop::call_later($write_file_cb);
1533             }
1534         });
1535
1536         Amanda::MainLoop::call_later($finish_connection);
1537         Amanda::MainLoop::run();
1538         is_deeply([@messages], [
1539                 'WRITE-OK-491520-!eof-!eom',
1540                 'WRITE-OK-491520-!eof-!eom',
1541                 'WRITE-OK-131072-EOF-!eom',
1542             ],
1543             "a sequence of write_from_connection calls works correctly");
1544
1545         $dev->finish();
1546
1547         if (my $err = $conn->close()) {
1548             die $err;
1549         }
1550     }
1551
1552     #
1553     # Test indirecttcp
1554     # 
1555
1556     {
1557         ok($dev->directtcp_supported(), "is a directtcp target");
1558
1559         $dev->property_set("indirect", 1);
1560
1561         my $addrs = $dev->listen(1);
1562         ok($addrs, "listen returns successfully") or die($dev->error_or_status());
1563
1564         # fork off to evaluate the indirecttcp addresses and then set up an
1565         # xfer to write to the device
1566         if (POSIX::fork() == 0) {
1567             # allow other process to start listening.
1568             sleep 1;
1569             my $nc = $Amanda::Constants::NC;
1570             $nc = $Amanda::Constants::NC6 if !$nc;
1571             $nc = $Amanda::Constants::NETCAT if !$nc;
1572             my $sockresult = `$nc localhost $addrs->[0][1] < /dev/null`;
1573
1574             my @sockresult = map { [ split(/:/, $_) ] } split(/ /, $sockresult);
1575             $addrs = [ map { $_->[1] = 0 + $_->[1]; $_ } @sockresult ];
1576
1577             my $xfer = Amanda::Xfer->new([
1578                     Amanda::Xfer::Source::Random->new(32768*34, 0xB00),
1579                     Amanda::Xfer::Dest::DirectTCPConnect->new($addrs) ]);
1580
1581             $xfer->start(make_cb(xmsg_cb => sub {
1582                 my ($src, $msg, $xfer) = @_;
1583                 if ($msg->{'type'} == $XMSG_ERROR) {
1584                     die $msg->{'elt'} . " failed: " . $msg->{'message'};
1585                 } elsif ($msg->{'type'} == $XMSG_DONE) {
1586                     Amanda::MainLoop::quit();
1587                 }
1588             }));
1589
1590             Amanda::MainLoop::run();
1591             exit(0);
1592         }
1593
1594         # write files from the connection until EOF
1595         my @messages;
1596         my $num_files;
1597         my $conn;
1598         my ($call_accept, $start_device, $write_file_cb);
1599
1600         $call_accept = make_cb(call_accept => sub {
1601             $conn = $dev->accept();
1602             Amanda::MainLoop::call_later($start_device);
1603         });
1604
1605         $start_device = make_cb(start_device => sub {
1606             ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1607                 "start device in write mode")
1608                 or diag $dev->error_or_status();
1609
1610             Amanda::MainLoop::call_later($write_file_cb);
1611         });
1612
1613         $write_file_cb = make_cb(write_file_cb => sub {
1614             ++$num_files < 20 or die "I seem to be in a loop!";
1615
1616             ok($dev->start_file($hdr), "start file $num_files for writing");
1617             is($dev->file, $num_files, "..file number is correct");
1618
1619             my ($ok, $size) = $dev->write_from_connection(32768*15);
1620             push @messages, sprintf("WRITE-%s-%d-%s-%s",
1621                 $ok?"OK":"ERR", $size,
1622                 $dev->is_eof()? "EOF":"!eof",
1623                 $dev->is_eom()? "EOM":"!eom");
1624             ok($ok, "..write from connection succeeds");
1625             my $eof = $dev->is_eof();
1626
1627             ok($dev->finish_file(), "..finish file after writing");
1628
1629             if (!$eof) {
1630                 Amanda::MainLoop::call_later($write_file_cb);
1631             } else {
1632                 Amanda::MainLoop::quit();
1633             }
1634         });
1635
1636         Amanda::MainLoop::call_later($call_accept);
1637         Amanda::MainLoop::run();
1638         is_deeply([@messages], [
1639                 'WRITE-OK-491520-!eof-!eom',
1640                 'WRITE-OK-491520-!eof-!eom',
1641                 'WRITE-OK-131072-EOF-!eom',
1642             ],
1643             "a sequence of write_from_connection calls works correctly");
1644
1645         $dev->finish();
1646
1647         if (my $err = $conn->close()) {
1648             die $err;
1649         }
1650     }
1651
1652     # now try reading that back piece by piece
1653
1654     {
1655         my $filename = "$Installcheck::TMP/Amanda_Device_ndmp.tmp";
1656         open(my $dest_fh, ">", $filename);
1657
1658         ok($dev->start($ACCESS_READ, undef, undef),
1659             "start device in read mode")
1660             or diag $dev->error_or_status();
1661
1662         my $file;
1663         for ($file = 1; $file <= 3; $file++) {
1664             ok($dev->seek_file($file),
1665                 "seek_file $file");
1666             is($dev->file, $file, "..file num is correct");
1667             is($dev->block, 0, "..block num is correct");
1668
1669             # read the file, writing to our temp file.  We'll check that the byte
1670             # sequence is correct later
1671             my $xfer = Amanda::Xfer->new([
1672                     Amanda::Xfer::Source::Device->new($dev),
1673                     Amanda::Xfer::Dest::Fd->new($dest_fh) ]);
1674
1675             $xfer->start(make_cb(xmsg_cb => sub {
1676                 my ($src, $msg, $xfer) = @_;
1677                 if ($msg->{'type'} == $XMSG_ERROR) {
1678                     die $msg->{'elt'} . " failed: " . $msg->{'message'};
1679                 } elsif ($msg->{'type'} == $XMSG_DONE) {
1680                     Amanda::MainLoop::quit();
1681                 }
1682             }));
1683             Amanda::MainLoop::run();
1684
1685             pass("read back file " . $file);
1686         }
1687
1688         $dev->finish();
1689         close $dest_fh;
1690
1691         # now read back and verify that file
1692         open(my $src_fh, "<", $filename);
1693         my $xfer = Amanda::Xfer->new([
1694                 Amanda::Xfer::Source::Fd->new($src_fh),
1695                 Amanda::Xfer::Dest::Null->new(0xB00) ]);
1696
1697         $xfer->start(make_cb(xmsg_cb => sub {
1698             my ($src, $msg, $xfer) = @_;
1699             if ($msg->{'type'} == $XMSG_ERROR) {
1700                 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1701             } elsif ($msg->{'type'} == $XMSG_DONE) {
1702                 Amanda::MainLoop::quit();
1703             }
1704         }));
1705         Amanda::MainLoop::run();
1706
1707         pass("data in the three parts is correct");
1708         unlink $filename;
1709     }
1710
1711     ####
1712     # Test read_to_connection
1713     #
1714     # This requires something that can connect to a device and read from
1715     # it; the XFA does not have an XFER_MECH_DIRECTTCP_CONNECT, so we fake
1716     # it by manually connecting and then setting up an xfer with a regular
1717     # XferSourceFd.  This works because the NDMP server will accept an
1718     # incoming connection before the Device API accept() method is called;
1719     # this trick may not work with other DirectTCP-capable devices.  Also,
1720     # this doesn't work so well if there's an error in the xfer (e.g., a
1721     # random value mismatch).  But tests are supposed to succeed!
1722
1723     sub test_read2conn {
1724         my ($finished_cb) = @_;
1725         my @events;
1726         my $file = 1;
1727         my ($conn, $sock);
1728
1729         my $steps = define_steps
1730             cb_ref => \$finished_cb;
1731
1732         step setup => sub {
1733             my $addrs = $dev->listen(0);
1734
1735             # now connect to that
1736             $sock = IO::Socket::INET->new(
1737                 Proto => "tcp",
1738                 PeerHost => $addrs->[0][0],
1739                 PeerPort => $addrs->[0][1],
1740                 Blocking => 1,
1741             );
1742
1743             # and set up a transfer to read from that socket
1744             my $xfer = Amanda::Xfer->new([
1745                     Amanda::Xfer::Source::Fd->new($sock),
1746                     Amanda::Xfer::Dest::Null->new(0xB00) ]);
1747
1748             $xfer->start(make_cb(xmsg_cb => sub {
1749                 my ($src, $msg, $xfer) = @_;
1750                 if ($msg->{'type'} == $XMSG_ERROR) {
1751                     die $msg->{'elt'} . " failed: " . $msg->{'message'};
1752                 }
1753                 if ($msg->{'type'} == $XMSG_DONE) {
1754                     push @events, "DONE";
1755                     $steps->{'quit'}->();
1756                 }
1757             }));
1758
1759             $steps->{'accept'}->();
1760         };
1761
1762         step accept => sub {
1763             $conn = $dev->accept();
1764             die $dev->error_or_status() unless ($conn);
1765
1766             Amanda::MainLoop::call_later($steps->{'start_dev'});
1767         };
1768
1769         step start_dev => sub {
1770             ok($dev->start($ACCESS_READ, undef, undef),
1771                 "start device in read mode")
1772                 or diag $dev->error_or_status();
1773
1774             Amanda::MainLoop::call_later($steps->{'read_part_cb'});
1775         };
1776
1777         step read_part_cb => sub {
1778             my $hdr = $dev->seek_file($file);
1779             die $dev->error_or_status() unless ($hdr);
1780             my $size = $dev->read_to_connection(0);
1781             push @events, "READ-$size";
1782
1783             if (++$file <= 3) {
1784                 Amanda::MainLoop::call_later($steps->{'read_part_cb'});
1785             } else {
1786                 # close the connection, which will end the xfer, which will
1787                 # result in a call to finished_cb.  So there.
1788                 push @events, "CLOSE";
1789                 $conn->close();
1790             }
1791         };
1792
1793         step quit => sub {
1794             close $sock or die "close: $!";
1795
1796             is_deeply([@events],
1797                 [ "READ-491520", "READ-491520", "READ-131072", "CLOSE", "DONE" ],
1798                 "sequential read_to_connection operations read the right amounts " .
1799                 "and bytestream matches");
1800
1801             $finished_cb->();
1802         };
1803     }
1804     test_read2conn(\&Amanda::MainLoop::quit);
1805     Amanda::MainLoop::run();
1806
1807     # try two seek_file's in a row
1808     $hdr = $dev->seek_file(2);
1809     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
1810     $hdr = $dev->seek_file(2);
1811     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
1812
1813     ## test seek_file's handling of EOM
1814
1815     $hdr = $dev->seek_file(3);
1816     is($hdr->{type}, $Amanda::Header::F_DUMPFILE, "file 3 is a dumpfile");
1817     $hdr = $dev->seek_file(4);
1818     is($hdr->{type}, $Amanda::Header::F_TAPEEND, "file 4 is tapeend");
1819     $hdr = $dev->seek_file(5);
1820     is($hdr, undef, "file 5 is an error");
1821     $hdr = $dev->seek_file(6);
1822     is($hdr, undef, "file 6 is an error");
1823
1824     $ndmp->cleanup();
1825 }
1826 unlink($input_filename);
1827 unlink($output_filename);
1828 rmtree($taperoot);