Imported Upstream version 3.3.3
[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
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11 # for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 #
17 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19
20 use Test::More tests => 541;
21 use File::Path qw( mkpath rmtree );
22 use Sys::Hostname;
23 use Carp;
24 use strict;
25 use warnings;
26
27 use lib "@amperldir@";
28 use Installcheck;
29 use Installcheck::Mock;
30 use Installcheck::Config;
31 use Amanda::Debug;
32 use Amanda::Device qw( :constants );
33 use Amanda::Config qw( :getconf :init );
34 use Amanda::Xfer qw( :constants );
35 use Amanda::Header qw( :constants );
36 use Amanda::Paths;
37 use Amanda::Constants;
38 use Amanda::Util;
39 use Amanda::MainLoop;
40 use IO::Socket;
41
42 my $dev;
43 my $dev_name;
44 my ($vtape1, $vtape2);
45 my ($input_filename, $output_filename) =
46     ( "$Installcheck::TMP/input.tmp", "$Installcheck::TMP/output.tmp" );
47 my $taperoot = "$Installcheck::TMP/Amanda_Device_test_tapes";
48 my $testconf;
49
50 # we'll need some vtapes..
51 sub mkvtape {
52     my ($num) = @_;
53
54     my $mytape = "$taperoot/$num";
55     if (-d $mytape) { rmtree($mytape); }
56     mkpath("$mytape/data");
57     return $mytape;
58 }
59
60
61 # make up a fake dumpfile_t to write with
62 my $dumpfile = Amanda::Header->new();
63 $dumpfile->{type} = $Amanda::Header::F_DUMPFILE;
64 $dumpfile->{datestamp} = "20070102030405";
65 $dumpfile->{dumplevel} = 0;
66 $dumpfile->{compressed} = 1;
67 $dumpfile->{name} = "localhost";
68 $dumpfile->{disk} = "/home";
69 $dumpfile->{program} = "INSTALLCHECK";
70
71 my $write_file_count = 5;
72 sub write_file {
73     my ($seed, $length, $filenum) = @_;
74
75     $dumpfile->{'datestamp'} = "2000010101010$filenum";
76
77     ok($dev->start_file($dumpfile),
78         "start file $filenum")
79         or diag($dev->error_or_status());
80
81     is($dev->file(), $filenum,
82         "Device has correct filenum");
83
84     croak ("selected file size $length is *way* too big")
85         unless ($length < 1024*1024*10);
86     ok(Amanda::Device::write_random_to_device($seed, $length, $dev),
87         "write random data");
88
89     if(ok($dev->in_file(),
90         "still in_file")) {
91         ok($dev->finish_file(),
92             "finish_file")
93             or diag($dev->error_or_status());
94     } else {
95         pass("not in file, so not calling finish_file");
96     }
97 }
98
99 my $verify_file_count = 4;
100 sub verify_file {
101     my ($seed, $length, $filenum) = @_;
102
103     ok(my $read_dumpfile = $dev->seek_file($filenum),
104         "seek to file $filenum")
105         or diag($dev->error_or_status());
106     is($dev->file(), $filenum,
107         "device is really at file $filenum");
108     ok(header_for($read_dumpfile, $filenum),
109         "header is correct")
110         or diag($dev->error_or_status());
111     ok(Amanda::Device::verify_random_from_device($seed, $length, $dev),
112         "verified file contents");
113 }
114
115 sub header_for {
116     my ($hdr, $filenum) = @_;
117     return ($hdr and $hdr->{'datestamp'} eq "2000010101010$filenum");
118 }
119
120 # properties test
121
122 my @common_properties = (
123     'appendable',
124     'block_size',
125     'canonical_name',
126     'concurrency',
127     'max_block_size',
128     'medium_access_type',
129     'min_block_size',
130     'partial_deletion',
131     'full_deletion',
132     'streaming',
133 );
134
135 sub properties_include {
136     my ($got, $should_include, $msg) = @_;
137     my %got = map { $_->{'name'}, 1 } @$got;
138     my @missing = grep { !defined($got{$_}) } @$should_include;
139     if (@missing) {
140         fail($msg);
141         diag(" Expected properties: " . join(", ", @$should_include));
142         diag("      Got properties: " . join(", ", @$got));
143         diag("  Missing properties: " . join(", ", @missing));
144     } else {
145         pass($msg);
146     }
147 }
148
149 ####
150 ## get stuff set up
151
152 $testconf = Installcheck::Config->new();
153 $testconf->write();
154 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
155     or die("Could not load configuration");
156
157 # put the debug messages somewhere
158 Amanda::Debug::dbopen("installcheck");
159 Installcheck::log_test_output();
160
161 ####
162 ## Test errors a little bit
163
164 $dev = Amanda::Device->new("foobar:");
165 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
166     "creation of a bogus 'foobar:' device fails");
167
168 $dev = Amanda::Device->new("rait:{{");
169 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
170     "creation of a bogus 'rait:{{' device fails");
171
172 $dev = Amanda::Device->new("rait:{a,b");
173 isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
174     "creation of a bogus 'rait:{a,b' device fails");
175
176 ####
177 ## first, test out the 'null' device.
178
179 $dev_name = "null:";
180
181 $dev = Amanda::Device->new($dev_name);
182 is($dev->status(), $DEVICE_STATUS_SUCCESS,
183     "create null device")
184     or diag $dev->error_or_status();
185 ok($dev->start($ACCESS_WRITE, "NULL1", "19780615010203"),
186     "start null device in write mode")
187     or diag $dev->error_or_status();
188
189 # try properties
190 properties_include([ $dev->property_list() ], [ @common_properties ],
191     "necessary properties listed on null device");
192 is($dev->property_get("canonical_name"), "null:",
193     "property_get(canonical_name) on null device");
194 is($dev->property_get("caNONical-name"), "null:",
195     "property_get(caNONical-name) on null device (case, dash-insensitivity)");
196 is_deeply([ $dev->property_get("canonical_name") ],
197     [ "null:", $PROPERTY_SURETY_GOOD, $PROPERTY_SOURCE_DEFAULT ],
198     "extended property_get returns correct surety/source");
199 for my $prop ($dev->property_list()) {
200     next unless $prop->{'name'} eq 'canonical_name';
201     is($prop->{'description'},
202         "The most reliable device name to use to refer to this device.",
203         "property info for canonical name is correct");
204 }
205 ok(!$dev->property_get("full_deletion"),
206     "property_get(full_deletion) on null device");
207 is($dev->property_get("comment"), undef,
208     "no comment by default");
209 ok($dev->property_set("comment", "well, that was silly"),
210     "set comment property");
211 is($dev->property_get("comment"), "well, that was silly",
212     "comment correctly stored");
213
214 # and write a file to it
215 write_file(0xabcde, 1024*256, 1);
216
217 # (don't finish the device, testing the finalize method's cleanup)
218
219 ####
220 ## Now some full device tests
221
222 ## VFS device
223
224 $vtape1 = mkvtape(1);
225 $dev_name = "file:$vtape1";
226
227 $dev = Amanda::Device->new($dev_name);
228 is($dev->status(), $DEVICE_STATUS_SUCCESS,
229     "$dev_name: create successful")
230     or diag($dev->error_or_status());
231
232 properties_include([ $dev->property_list() ],
233     [ @common_properties, 'max_volume_usage' ],
234     "necessary properties listed on vfs device");
235
236 # play with properties a little bit
237 ok($dev->property_set("comment", 16),
238     "set an string property to an integer");
239
240 ok($dev->property_set("comment", 16.0),
241     "set an string property to a float");
242
243 ok($dev->property_set("comment", "hi mom"),
244     "set an string property to a string");
245
246 ok($dev->property_set("comment", "32768"),
247     "set an integer property to a simple string");
248
249 ok($dev->property_set("comment", "32k"),
250     "set an integer property to a string with a unit");
251
252 ok($dev->property_set("block_size", 32768),
253     "set an integer property to an integer");
254
255 ok(!($dev->property_set("invalid-property-name", 32768)),
256     "set an invalid-property-name");
257
258 $dev->read_label();
259 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
260     "initially unlabeled")
261     or diag($dev->error_or_status());
262
263 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
264     "start in write mode")
265     or diag($dev->error_or_status());
266
267 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
268     "not unlabeled anymore")
269     or diag($dev->error_or_status());
270
271 for (my $i = 1; $i <= 3; $i++) {
272     write_file(0x2FACE, $dev->block_size()*10+17, $i);
273 }
274
275 ok($dev->finish(),
276     "finish device after write")
277     or diag($dev->error_or_status());
278
279 $dev->read_label();
280 ok(!($dev->status()),
281     "no error, at all, from read_label")
282     or diag($dev->error_or_status());
283
284 # append one more copy, to test ACCESS_APPEND
285
286 ok($dev->start($ACCESS_APPEND, undef, undef),
287     "start in append mode")
288     or diag($dev->error_or_status());
289
290 write_file(0xD0ED0E, $dev->block_size()*4, 4);
291
292 ok($dev->finish(),
293     "finish device after append")
294     or diag($dev->error_or_status());
295
296 # try reading the third file back, creating a new device
297 # object first, and skipping the read-label step.
298
299 $dev = undef;
300 $dev = Amanda::Device->new($dev_name);
301 is($dev->status(), $DEVICE_STATUS_SUCCESS,
302     "$dev_name: re-create successful")
303     or diag($dev->error_or_status());
304
305 ok($dev->start($ACCESS_READ, undef, undef),
306     "start in read mode")
307     or diag($dev->error_or_status());
308
309 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
310
311 {
312     # try two seek_file's in a row
313     my $hdr = $dev->seek_file(3);
314     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
315     $hdr = $dev->seek_file(3);
316     is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
317 }
318
319 ok($dev->finish(),
320     "finish device after read")
321     or diag($dev->error_or_status());
322
323 # test erase
324 ok($dev->erase(),
325    "erase device")
326     or diag($dev->error_or_status());
327
328 ok($dev->erase(),
329    "erase device (again)")
330     or diag($dev->error_or_status());
331
332 ok($dev->finish(),
333    "finish device after erase")
334     or diag($dev->error_or_status());
335
336 # test monitor_free_space property (testing the monitoring would require a
337 # dedicated partition for the tests - it's not worth it)
338
339 ok($dev->property_get("monitor_free_space"),
340     "monitor_free_space property is set by default");
341
342 ok($dev->property_set("monitor_free_space", 0),
343     "monitor_free_space property can be set to false");
344
345 ok(!$dev->property_get("monitor_free_space"),
346     "monitor_free_space property value 'sticks'");
347
348 # test the LEOM functionality
349
350 $dev = undef;
351 $dev = Amanda::Device->new($dev_name);
352 is($dev->status(), $DEVICE_STATUS_SUCCESS,
353     "$dev_name: re-create successful")
354     or diag($dev->error_or_status());
355 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
356     "set MAX_VOLUME_USAGE to test LEOM");
357 ok($dev->property_set("LEOM", 1),
358     "set LEOM");
359 ok($dev->property_set("ENFORCE_MAX_VOLUME_USAGE", 0),
360     "set ENFORCE_MAX_VOLUME_USAGE");
361
362 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
363     "start in write mode")
364     or diag($dev->error_or_status());
365
366 ok($dev->start_file($dumpfile),
367     "start file 1")
368     or diag($dev->error_or_status());
369
370 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
371     "write random data into the early-warning zone");
372
373 ok(!$dev->is_eom,
374     "device does not indicates LEOM after writing when ENFORCE_MAX_VOLUME_USAGE is FALSE");
375
376 ok($dev->finish_file(),
377     "..but a finish_file is allowed to complete")
378     or diag($dev->error_or_status());
379
380 ok($dev->finish(),
381    "finish device after LEOM test")
382     or diag($dev->error_or_status());
383
384 $dev = undef;
385 $dev = Amanda::Device->new($dev_name);
386 is($dev->status(), $DEVICE_STATUS_SUCCESS,
387     "$dev_name: re-create successful")
388     or diag($dev->error_or_status());
389 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
390     "set MAX_VOLUME_USAGE to test LEOM");
391 ok($dev->property_set("LEOM", 1),
392     "set LEOM");
393 ok($dev->property_set("ENFORCE_MAX_VOLUME_USAGE", 1),
394     "set ENFORCE_MAX_VOLUME_USAGE");
395
396 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
397     "start in write mode")
398     or diag($dev->error_or_status());
399
400 ok($dev->start_file($dumpfile),
401     "start file 1")
402     or diag($dev->error_or_status());
403
404 ok(!$dev->is_eom,
405     "device does not indicate LEOM before writing");
406
407 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
408     "write random data into the early-warning zone");
409
410 ok($dev->is_eom,
411     "device indicates LEOM after writing");
412
413 ok($dev->finish_file(),
414     "..but a finish_file is allowed to complete")
415     or diag($dev->error_or_status());
416
417 ok($dev->finish(),
418    "finish device after LEOM test")
419     or diag($dev->error_or_status());
420
421 $dev = undef;
422 $dev = Amanda::Device->new($dev_name);
423 is($dev->status(), $DEVICE_STATUS_SUCCESS,
424     "$dev_name: re-create successful")
425     or diag($dev->error_or_status());
426 ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
427     "set MAX_VOLUME_USAGE to test LEOM");
428 ok($dev->property_set("LEOM", 1),
429     "set LEOM");
430
431 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
432     "start in write mode")
433     or diag($dev->error_or_status());
434
435 ok($dev->start_file($dumpfile),
436     "start file 1")
437     or diag($dev->error_or_status());
438
439 ok(!$dev->is_eom,
440     "device does not indicate LEOM before writing");
441
442 ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
443     "write random data into the early-warning zone");
444
445 ok($dev->is_eom,
446     "device indicates LEOM after writing as default value of ENFORCE_MAX_VOLUME_USAGE is true for vfs device");
447
448 ok($dev->finish_file(),
449     "..but a finish_file is allowed to complete")
450     or diag($dev->error_or_status());
451
452 ok($dev->finish(),
453    "finish device after LEOM test")
454     or diag($dev->error_or_status());
455
456 $dev = undef;
457 $dev = Amanda::Device->new($dev_name);
458 is($dev->status(), $DEVICE_STATUS_SUCCESS,
459     "$dev_name: re-create successful")
460     or diag($dev->error_or_status());
461 ok($dev->property_set("MAX_VOLUME_USAGE", "160k"),
462     "set MAX_VOLUME_USAGE to test LEOM while writing the first header");
463 ok($dev->property_set("LEOM", 1),
464     "set LEOM");
465
466 ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
467     "start in write mode")
468     or diag($dev->error_or_status());
469
470 ok($dev->start_file($dumpfile),
471     "start file 1")
472     or diag($dev->error_or_status());
473
474 ok($dev->is_eom,
475     "device indicates LEOM after writing first header");
476
477 ok($dev->finish_file(),
478     "..but a finish_file is allowed to complete")
479     or diag($dev->error_or_status());
480
481 ok($dev->finish(),
482    "finish device after LEOM test")
483     or diag($dev->error_or_status());
484
485 ####
486 ## Test a RAIT device of two vfs devices.
487
488 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
489 $dev_name = "rait:file:{$vtape1,$vtape2}";
490
491 $dev = Amanda::Device->new($dev_name);
492 is($dev->status(), $DEVICE_STATUS_SUCCESS,
493    "$dev_name: create successful")
494     or diag($dev->error_or_status());
495
496 ok($dev->configure(1), "configure device");
497
498 properties_include([ $dev->property_list() ], [ @common_properties ],
499     "necessary properties listed on rait device");
500
501 is($dev->property_get("block_size"), 32768, # (RAIT default)
502     "rait device calculates a default block size correctly");
503
504 ok($dev->property_set("block_size", 32768*16),
505     "rait device accepts an explicit block size");
506
507 is($dev->property_get("block_size"), 32768*16,
508     "..and remembers it");
509
510 ok($dev->property_set("max_volume_usage", 32768*1000),
511     "rait device accepts property MAX_VOLUME_USAGE");
512
513 is($dev->property_get("max_volume_usage"), 32768*1000,
514     "..and remembers it");
515
516 $dev->read_label();
517 ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
518    "initially unlabeled")
519     or diag($dev->error_or_status());
520
521 ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
522    "start in write mode")
523     or diag($dev->error_or_status());
524
525 ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
526    "not unlabeled anymore")
527     or diag($dev->error_or_status());
528
529 for (my $i = 1; $i <= 3; $i++) {
530     write_file(0x2FACE, $dev->block_size()*10+17, $i);
531 }
532
533 ok($dev->finish(),
534    "finish device after write")
535     or diag($dev->error_or_status());
536
537 $dev->read_label();
538 ok(!($dev->status()),
539    "no error, at all, from read_label")
540     or diag($dev->error_or_status());
541
542 # append one more copy, to test ACCESS_APPEND
543
544 ok($dev->start($ACCESS_APPEND, undef, undef),
545    "start in append mode")
546     or diag($dev->error_or_status());
547
548 write_file(0xD0ED0E, $dev->block_size()*4, 4);
549
550 ok($dev->finish(),
551    "finish device after append")
552     or diag($dev->error_or_status());
553
554 # try reading the third file back, creating a new device
555 # object first, and skipping the read-label step.
556
557 $dev = undef;
558 $dev = Amanda::Device->new($dev_name);
559 is($dev->status(), $DEVICE_STATUS_SUCCESS,
560     "$dev_name: re-create successful")
561     or diag($dev->error_or_status());
562
563 ok($dev->start($ACCESS_READ, undef, undef),
564    "start in read mode")
565     or diag($dev->error_or_status());
566
567 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
568
569 ok($dev->finish(),
570    "finish device after read")
571     or diag($dev->error_or_status());
572
573 ok($dev->start($ACCESS_READ, undef, undef),
574    "start in read mode after missing volume")
575     or diag($dev->error_or_status());
576
577 # corrupt the device somehow and hope it keeps working
578 rmtree("$taperoot/1");
579
580 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
581 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
582 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
583
584 ok($dev->finish(),
585    "finish device read after missing volume")
586     or diag($dev->error_or_status());
587
588 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
589    "start in write mode fails with missing volume")
590     or diag($dev->error_or_status());
591
592 undef $dev;
593
594 $dev_name = "rait:{file:$vtape2,MISSING}";
595 $dev = Amanda::Device->new($dev_name);
596
597 ok($dev->start($ACCESS_READ, undef, undef),
598    "start in read mode with MISSING")
599     or diag($dev->error_or_status());
600
601 verify_file(0x2FACE, $dev->block_size()*10+17, 3);
602 verify_file(0xD0ED0E, $dev->block_size()*4, 4);
603 verify_file(0x2FACE, $dev->block_size()*10+17, 2);
604
605 ok($dev->finish(),
606    "finish device read with MISSING")
607     or diag($dev->error_or_status());
608
609 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
610    "start in write mode fails with MISSING")
611     or diag($dev->error_or_status());
612
613 undef $dev;
614
615 $dev = Amanda::Device->new_rait_from_children(
616     Amanda::Device->new("file:$vtape2"), undef);
617
618 ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
619    "start a RAIT device in write mode fails, when created with 'undef'")
620     or diag($dev->error_or_status());
621
622 # Make two devices with different labels, should get a
623 # message accordingly.
624 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
625
626 my $n = 13;
627 for $dev_name ("file:$vtape1", "file:$vtape2") {
628     my $dev = Amanda::Device->new($dev_name);
629     is($dev->status(), $DEVICE_STATUS_SUCCESS,
630        "$dev_name: Open successful")
631         or diag($dev->error_or_status());
632     ok($dev->start($ACCESS_WRITE, "TESTCONF$n", undef),
633         "wrote label 'TESTCONF$n'");
634     ok($dev->finish(), "finished device");
635     $n++;
636 }
637
638 $dev = Amanda::Device->new_rait_from_children(
639     Amanda::Device->new("file:$vtape1"),
640     Amanda::Device->new("file:$vtape2"));
641 is($dev->status(), $DEVICE_STATUS_SUCCESS,
642    "new_rait_from_children: Open successful")
643     or diag($dev->error_or_status());
644
645 $dev->read_label();
646 ok($dev->status() & $DEVICE_STATUS_VOLUME_ERROR,
647    "Label mismatch error handled correctly")
648     or diag($dev->error_or_status());
649
650 # Use some config to set a block size on a child device
651 ($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
652 $dev_name = "rait:{file:$vtape1,mytape2}";
653
654 $testconf = Installcheck::Config->new();
655 $testconf->add_device("mytape2", [
656     "tapedev" => "\"file:$vtape2\"",
657     "device_property" => "\"BLOCK_SIZE\" \"64k\""
658 ]);
659 $testconf->write();
660 config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
661     or die("Could not load configuration");
662
663 $dev = Amanda::Device->new($dev_name);
664 is($dev->status(), $DEVICE_STATUS_SUCCESS,
665    "$dev_name: create successful")
666     or diag($dev->error_or_status());
667
668 ok($dev->configure(1), "configure device");
669
670 is($dev->property_get("block_size"), 65536,
671     "rait device calculates a block size from its children correctly");
672
673 # Test an S3 device if the proper environment variables are set
674 my $S3_SECRET_KEY = $ENV{'INSTALLCHECK_S3_SECRET_KEY'};
675 my $S3_ACCESS_KEY = $ENV{'INSTALLCHECK_S3_ACCESS_KEY'};
676 my $DEVPAY_SECRET_KEY = $ENV{'INSTALLCHECK_DEVPAY_SECRET_KEY'};
677 my $DEVPAY_ACCESS_KEY = $ENV{'INSTALLCHECK_DEVPAY_ACCESS_KEY'};
678 my $DEVPAY_USER_TOKEN = $ENV{'INSTALLCHECK_DEVPAY_USER_TOKEN'};
679
680 my $run_s3_tests = defined $S3_SECRET_KEY && defined $S3_ACCESS_KEY;
681 my $run_devpay_tests = defined $DEVPAY_SECRET_KEY &&
682     defined $DEVPAY_ACCESS_KEY && $DEVPAY_USER_TOKEN;
683
684 my $s3_make_device_count = 7;
685 sub s3_make_device($$) {
686     my ($dev_name, $kind) = @_;
687     $dev = Amanda::Device->new($dev_name);
688     is($dev->status(), $DEVICE_STATUS_SUCCESS,
689        "$dev_name: create successful")
690         or diag($dev->error_or_status());
691
692     my @s3_props = ( 's3_access_key', 's3_secret_key' );
693     push @s3_props, 's3_user_token' if ($kind eq "devpay");
694     properties_include([ $dev->property_list() ], [ @common_properties, @s3_props ],
695         "necessary properties listed on s3 device");
696
697     ok($dev->property_set('BLOCK_SIZE', 32768*2),
698         "set block size")
699         or diag($dev->error_or_status());
700
701     # might as well save a few cents while testing this property..
702     ok($dev->property_set('S3_STORAGE_CLASS', 'REDUCED_REDUNDANCY'),
703         "set storage class")
704         or diag($dev->error_or_status());
705
706     if ($kind eq "s3") {
707         # use regular S3 credentials
708         ok($dev->property_set('S3_ACCESS_KEY', $S3_ACCESS_KEY),
709            "set S3 access key")
710         or diag($dev->error_or_status());
711
712         ok($dev->property_set('S3_SECRET_KEY', $S3_SECRET_KEY),
713            "set S3 secret key")
714             or diag($dev->error_or_status());
715
716         pass("(placeholder)");
717     } elsif ($kind eq "devpay") {
718         # use devpay credentials
719         ok($dev->property_set('S3_ACCESS_KEY', $DEVPAY_ACCESS_KEY),
720            "set devpay access key")
721         or diag($dev->error_or_status());
722
723         ok($dev->property_set('S3_SECRET_KEY', $DEVPAY_SECRET_KEY),
724            "set devpay secret key")
725             or diag($dev->error_or_status());
726
727         ok($dev->property_set('S3_USER_TOKEN', $DEVPAY_USER_TOKEN),
728            "set devpay user token")
729             or diag($dev->error_or_status());
730     } else {
731         croak("didn't recognize the device kind, so no credentials were set");
732     }
733     return $dev;
734 }
735
736 my $base_name;
737
738 SKIP: {
739     skip "define \$INSTALLCHECK_S3_{SECRET,ACCESS}_KEY to run S3 tests",
740             101 +
741             1 * $verify_file_count +
742             7 * $write_file_count +
743             13 * $s3_make_device_count
744         unless $run_s3_tests;
745
746     $dev_name = "s3:";
747     $dev = Amanda::Device->new($dev_name);
748     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
749          "creating $dev_name fails miserably");
750
751     $dev_name = "s3:foo";
752     $dev = Amanda::Device->new($dev_name);
753
754     ok($dev->property_get("full_deletion"),
755        "property_get(full_deletion) on s3 device");
756
757     ok($dev->property_get("leom"),
758        "property_get(leom) on s3 device");
759
760     # test parsing of boolean values
761     # (s3 is the only device driver that has a writable boolean property at the
762     # moment)
763
764     my @verbose_vals = (
765         {'val' => '1', 'true' => 1},
766         {'val' => '0', 'true' => 0},
767         {'val' => 't', 'true' => 1},
768         {'val' => 'true', 'true' => 1},
769         {'val' => 'f', 'true' => 0},
770         {'val' => 'false', 'true' => 0},
771         {'val' => 'y', 'true' => 1},
772         {'val' => 'yes', 'true' => 1},
773         {'val' => 'n', 'true' => 0},
774         {'val' => 'no', 'true' => 0},
775         {'val' => 'on', 'true' => 1},
776         {'val' => 'off', 'true' => 0},
777         {'val' => 'oFf', 'true' => 0},
778         );
779
780     foreach my $v (@verbose_vals) {
781         $dev_name = "s3:foo";
782         $dev = Amanda::Device->new($dev_name);
783
784         $testconf = Installcheck::Config->new();
785         $testconf->add_param("device_property", "\"verbose\" \"$v->{'val'}\"");
786         $testconf->write();
787         config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
788             or die("Could not load configuration");
789
790         ok($dev->configure(1),
791            "configured device with verbose set to $v->{'val'}")
792             or diag($dev->error_or_status());
793
794         my $get_val = $dev->property_get('verbose');
795         # see if truth-iness matches
796         my $expec = $v->{'true'}? "true" : "false";
797         is(!!$dev->property_get('verbose'), !!$v->{'true'},
798            "device_property 'VERBOSE' '$v->{'val'}' => property_get(verbose) returning $expec");
799     }
800
801     # test unparsable property
802     $dev_name = "s3:foo";
803     $dev = Amanda::Device->new($dev_name);
804
805     $testconf = Installcheck::Config->new();
806     $testconf->add_param("device_property", "\"verbose\" \"foo\"");
807     $testconf->write();
808     config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
809         or die("Could not load configuration");
810
811     ok(!$dev->configure(1),
812        "failed to configure device with verbose set to foo");
813
814     like($dev->error_or_status(), qr/'verbose'/,
815          "error message mentions property name");
816
817     like($dev->error_or_status(), qr/'foo'/,
818          "error message mentions property value");
819
820     like($dev->error_or_status(), qr/gboolean/,
821          "error message mentions property type");
822
823     my $hostname  = hostname();
824     $hostname =~ s/\./-/g;
825     $base_name = "$S3_ACCESS_KEY-installcheck-$hostname";
826     $dev_name = "s3:$base_name-s3";
827     $dev = s3_make_device($dev_name, "s3");
828     $dev->read_label();
829     my $status = $dev->status();
830     # this test appears very liberal, but catches the case where setup_handle fails without
831     # giving false positives
832     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
833        "status is either OK or possibly unlabeled")
834         or diag($dev->error_or_status());
835
836     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
837        "start in write mode")
838         or diag($dev->error_or_status());
839
840     ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
841        "it's labeled now")
842         or diag($dev->error_or_status());
843
844     for (my $i = 1; $i <= 3; $i++) {
845         write_file(0x2FACE, $dev->block_size()*10, $i);
846     }
847
848     ok($dev->finish(),
849        "finish device after write")
850         or diag($dev->error_or_status());
851
852     $dev->read_label();
853     ok(!($dev->status()),
854        "no error, at all, from read_label")
855         or diag($dev->error_or_status());
856
857     # append one more copy, to test ACCESS_APPEND
858
859     ok($dev->start($ACCESS_APPEND, undef, undef),
860        "start in append mode")
861         or diag($dev->error_or_status());
862
863     write_file(0xD0ED0E, $dev->block_size()*10, 4);
864
865     ok($dev->finish(),
866        "finish device after append")
867         or diag($dev->error_or_status());
868
869     # try reading the third file back
870
871     ok($dev->start($ACCESS_READ, undef, undef),
872        "start in read mode")
873         or diag($dev->error_or_status());
874
875     verify_file(0x2FACE, $dev->block_size()*10, 3);
876
877     # test EOT indications on reading
878     my $hdr = $dev->seek_file(4);
879     is($hdr->{'type'}, $Amanda::Header::F_DUMPFILE,
880         "file 4 has correct type F_DUMPFILE");
881
882     $hdr = $dev->seek_file(5);
883     is($hdr->{'type'}, $Amanda::Header::F_TAPEEND,
884         "file 5 has correct type F_TAPEEND");
885
886     $hdr = $dev->seek_file(6);
887     is($hdr, undef, "seek_file returns undef for file 6");
888
889     ok($dev->finish(),
890        "finish device after read")
891         or diag($dev->error_or_status());    # (note: we don't use write_max_size here,
892                                              # as the maximum for S3 is very large)
893
894     ok($dev->erase(),
895        "erase device")
896        or diag($dev->error_or_status());
897
898     ok($dev->erase(),
899        "erase device (again)")
900        or diag($dev->error_or_status());
901
902     ok($dev->finish(),
903        "finish device after erase")
904         or diag($dev->error_or_status());
905
906     $dev->read_label();
907     $status = $dev->status();
908     ok($status & $DEVICE_STATUS_VOLUME_UNLABELED,
909        "status is unlabeled after an erase")
910         or diag($dev->error_or_status());
911
912     $dev = s3_make_device($dev_name, "s3");
913
914     ok($dev->erase(),
915        "erase device right after creation")
916        or diag($dev->error_or_status());
917
918     $dev = s3_make_device($dev_name, "s3");
919
920     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=false
921     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
922        "set MAX_VOLUME_USAGE to test LEOM");
923
924     ok($dev->property_set("LEOM", 1),
925         "set LEOM");
926
927     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
928        "start in write mode")
929         or diag($dev->error_or_status());
930
931     write_file(0x2FACE, 440*1024, 1);
932
933     ok(!$dev->is_eom,
934         "device does not indicate LEOM after writing as property ENFORCE_MAX_VOLUME_USAGE not set and its default value is false");
935
936     ok($dev->finish(),
937        "finish device after LEOM test")
938        or diag($dev->error_or_status());
939     
940     ok($dev->erase(),
941        "erase device")
942        or diag($dev->error_or_status());
943     
944     $dev = s3_make_device($dev_name, "s3");
945
946     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=true
947     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
948        "set MAX_VOLUME_USAGE to test LEOM");
949
950     ok($dev->property_set('ENFORCE_MAX_VOLUME_USAGE', 1 ),
951        "set ENFORCE_MAX_VOLUME_USAGE");
952
953     ok($dev->property_set("LEOM", 1),
954         "set LEOM");
955
956     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
957        "start in write mode")
958         or diag($dev->error_or_status());
959
960     write_file(0x2FACE, 440*1024, 1);
961
962     ok($dev->is_eom,
963         "device indicates LEOM after writing, when property ENFORCE_MAX_VOLUME_USAGE set to true");
964
965     ok($dev->finish(),
966        "finish device after LEOM test")
967        or diag($dev->error_or_status());
968
969     ok($dev->erase(),
970        "erase device")
971        or diag($dev->error_or_status());
972     
973     $dev = s3_make_device($dev_name, "s3");
974
975     # set MAX_VOLUME_USAGE, LEOM=true, ENFORCE_MAX_VOLUME_USAGE=false
976     ok($dev->property_set('MAX_VOLUME_USAGE', "512k"),
977        "set MAX_VOLUME_USAGE to test LEOM");
978
979     ok($dev->property_set('ENFORCE_MAX_VOLUME_USAGE', 0 ),
980        "set ENFORCE_MAX_VOLUME_USAGE");
981
982     ok($dev->property_set("LEOM", 1),
983         "set LEOM");
984
985     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef), 
986        "start in write mode")
987         or diag($dev->error_or_status());
988
989     write_file(0x2FACE, 440*1024, 1);
990
991     ok(!$dev->is_eom,
992         "device does not indicate LEOM after writing, when property ENFORCE_MAX_VOLUME_USAGE set to false");
993
994     ok($dev->finish(),
995        "finish device after LEOM test")
996        or diag($dev->error_or_status());
997     
998     ok($dev->erase(),
999        "erase device")
1000        or diag($dev->error_or_status());
1001     
1002     # try with empty user token
1003     $dev_name = lc("s3:$base_name-s3");
1004     $dev = s3_make_device($dev_name, "s3");
1005     ok($dev->property_set('S3_USER_TOKEN', ''),
1006        "set devpay user token")
1007         or diag($dev->error_or_status());
1008
1009     $dev->read_label();
1010     $status = $dev->status();
1011     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1012        "status is either OK or possibly unlabeled")
1013         or diag($dev->error_or_status());
1014
1015     $dev->finish();
1016
1017     ok($dev->erase(),
1018        "erase device")
1019        or diag($dev->error_or_status());
1020
1021     # try a eu-constrained bucket
1022     $dev_name = lc("s3:$base_name-s3-eu");
1023     $dev = s3_make_device($dev_name, "s3");
1024     ok($dev->property_set('S3_BUCKET_LOCATION', 'EU'),
1025        "set S3 bucket location to 'EU'")
1026         or diag($dev->error_or_status());
1027
1028     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1029        "start in write mode")
1030         or diag($dev->error_or_status());
1031
1032     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1033        "status is OK")
1034         or diag($dev->error_or_status());
1035
1036     $dev->finish();
1037
1038     ok($dev->erase(),
1039        "erase device")
1040        or diag($dev->error_or_status());
1041
1042     # try a wildcard-constrained bucket
1043     $dev_name = lc("s3:$base_name-s3-wild");
1044     $dev = s3_make_device($dev_name, "s3");
1045     ok($dev->property_set('S3_BUCKET_LOCATION', '*'),
1046        "set S3 bucket location to ''")
1047         or diag($dev->error_or_status());
1048
1049     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1050        "start in write mode")
1051         or diag($dev->error_or_status());
1052
1053     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1054        "status is OK")
1055         or diag($dev->error_or_status());
1056
1057     $dev->finish();
1058
1059     # test again with invalid ca_info
1060     $dev = s3_make_device($dev_name, "s3");
1061     SKIP: {
1062         skip "SSL not supported; can't check SSL_CA_INFO", 2
1063             unless $dev->property_get('S3_SSL');
1064
1065         ok($dev->property_set('SSL_CA_INFO', '/dev/null'),
1066            "set invalid SSL/TLS CA certificate")
1067             or diag($dev->error_or_status());
1068
1069         ok(!$dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1070            "start in write mode")
1071             or diag($dev->error_or_status());
1072
1073         isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1074            "status is OK")
1075             or diag($dev->error_or_status());
1076
1077         $dev->finish();
1078     }
1079
1080     # test again with our own CA bundle
1081     $dev = s3_make_device($dev_name, "s3");
1082     SKIP: {
1083         skip "SSL not supported; can't check SSL_CA_INFO", 4
1084             unless $dev->property_get('S3_SSL');
1085         ok($dev->property_set('SSL_CA_INFO', "$srcdir/data/aws-bundle.crt"),
1086            "set our own SSL/TLS CA certificate bundle")
1087             or diag($dev->error_or_status());
1088
1089         ok($dev->erase(),
1090            "erase device")
1091             or diag($dev->error_or_status());
1092
1093         ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1094            "start in write mode")
1095             or diag($dev->error_or_status());
1096
1097         is($dev->status(), $DEVICE_STATUS_SUCCESS,
1098            "status is OK")
1099             or diag($dev->error_or_status());
1100
1101         $dev->finish();
1102     }
1103
1104     ok($dev->erase(),
1105        "erase device")
1106        or diag($dev->error_or_status());
1107
1108     # bucket names incompatible with location constraint
1109     $dev_name = "s3:-$base_name-s3-eu";
1110     $dev = s3_make_device($dev_name, "s3");
1111
1112     ok($dev->property_set('S3_BUCKET_LOCATION', ''),
1113        "should be able to set an empty S3 bucket location with an incompatible name")
1114         or diag($dev->error_or_status());
1115
1116     $dev_name = "s3:$base_name-s3.eu";
1117     $dev = s3_make_device($dev_name, "s3");
1118
1119     ok($dev->property_set('S3_BUCKET_LOCATION', ''),
1120        "should be able to set an empty S3 bucket location with an incompatible name")
1121         or diag($dev->error_or_status());
1122
1123     $dev_name = "s3:-$base_name-s3-eu";
1124     $dev = s3_make_device($dev_name, "s3");
1125
1126     ok(!$dev->property_set('S3_BUCKET_LOCATION', 'EU'),
1127        "should not be able to set S3 bucket location with an incompatible name")
1128         or diag($dev->error_or_status());
1129
1130     $dev_name = lc("s3:$base_name-s3-eu");
1131     $dev = s3_make_device($dev_name, "s3");
1132     ok($dev->property_set('S3_BUCKET_LOCATION', 'XYZ'),
1133        "should be able to set S3 bucket location with a compatible name")
1134         or diag($dev->error_or_status());
1135     $dev->read_label();
1136     $status = $dev->status();
1137     ok(($status == $DEVICE_STATUS_DEVICE_ERROR),
1138        "status is DEVICE_STATUS_DEVICE_ERROR")
1139         or diag($dev->error_or_status());
1140     my $error_msg = $dev->error_or_status();
1141     ok(($dev->error_or_status() == "While creating new S3 bucket: The specified location-constraint is not valid (Unknown) (HTTP 400)"),
1142        "invalid location-constraint")
1143        or diag("bad error: " . $dev->error_or_status());
1144
1145 }
1146
1147 SKIP: {
1148     # in this case, most of our code has already been exercised
1149     # just make sure that authentication works as a basic sanity check
1150     skip "skipping abbreviated devpay tests", $s3_make_device_count + 1
1151         unless $run_devpay_tests;
1152     $dev_name = "s3:$base_name-devpay";
1153     $dev = s3_make_device($dev_name, "devpay");
1154     $dev->read_label();
1155     my $status = $dev->status();
1156     # this test appears very liberal, but catches the case where setup_handle fails without
1157     # giving false positives
1158     ok(($status == 0) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1159        "status is either OK or possibly unlabeled")
1160         or diag($dev->error_or_status());
1161 }
1162
1163 # Test a tape device if the proper environment variables are set
1164 my $TAPE_DEVICE = $ENV{'INSTALLCHECK_TAPE_DEVICE'};
1165 my $run_tape_tests = defined $TAPE_DEVICE;
1166 SKIP: {
1167     skip "define \$INSTALLCHECK_TAPE_DEVICE to run tape tests",
1168             30 +
1169             7 * $verify_file_count +
1170             5 * $write_file_count
1171         unless $run_tape_tests;
1172
1173     $dev_name = "tape:$TAPE_DEVICE";
1174     $dev = Amanda::Device->new($dev_name);
1175     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1176         "$dev_name: create successful")
1177         or diag($dev->error_or_status());
1178
1179     my $status = $dev->read_label();
1180     ok(($status == $DEVICE_STATUS_SUCCESS) || (($status & $DEVICE_STATUS_VOLUME_UNLABELED) != 0),
1181        "status is either OK or possibly unlabeled")
1182         or diag($dev->error_or_status());
1183
1184     ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
1185         "start in write mode")
1186         or diag($dev->error_or_status());
1187
1188     ok(!($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED),
1189         "not unlabeled anymore")
1190         or diag($dev->error_or_status());
1191
1192     for (my $i = 1; $i <= 4; $i++) {
1193         write_file(0x2FACE+$i, $dev->block_size()*10+17, $i);
1194     }
1195
1196     ok($dev->finish(),
1197         "finish device after write")
1198         or diag($dev->error_or_status());
1199
1200     $dev->read_label();
1201     ok(!($dev->status()),
1202         "no error, at all, from read_label")
1203         or diag($dev->error_or_status());
1204
1205     is($dev->volume_label(), "TESTCONF13",
1206         "read_label reads the correct label")
1207         or diag($dev->error_or_status());
1208
1209     # append one more copy, to test ACCESS_APPEND
1210
1211     # if final_filemarks is 1, then the tape device will use F_NOOP,
1212     # inserting an extra file, and we'll be appending at file number 6.
1213     my $append_fileno = ($dev->property_get("FINAL_FILEMARKS") == 2)? 5:6;
1214
1215     SKIP: {
1216         skip "APPEND not supported", $write_file_count + 2
1217             unless $dev->property_get("APPENDABLE");
1218
1219         ok($dev->start($ACCESS_APPEND, undef, undef),
1220             "start in append mode")
1221             or diag($dev->error_or_status());
1222
1223         write_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1224
1225         ok($dev->finish(),
1226             "finish device after append")
1227             or diag($dev->error_or_status());
1228     }
1229
1230     # try reading the second and third files back, creating a new
1231     # device object first, and skipping the read-label step.
1232
1233     $dev = undef;
1234     $dev = Amanda::Device->new($dev_name);
1235     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1236         "$dev_name: re-create successful")
1237         or diag($dev->error_or_status());
1238
1239     # use a big read_block_size, checking that it's also settable
1240     # via read_buffer_size
1241     ok($dev->property_set("read_buffer_size", 256*1024),
1242         "can set read_buffer_size");
1243     is($dev->property_get("read_block_size"), 256*1024,
1244         "and its value is reflected in read_block_size");
1245     ok($dev->property_set("read_block_size", 32*1024),
1246         "can set read_block_size");
1247
1248     ok($dev->start($ACCESS_READ, undef, undef),
1249         "start in read mode")
1250         or diag($dev->error_or_status());
1251
1252     # now verify those files in a particular order to trigger all of the
1253     # seeking edge cases
1254
1255     verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1256     verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1257     verify_file(0x2FACE+4, $dev->block_size()*10+17, 4);
1258     verify_file(0x2FACE+3, $dev->block_size()*10+17, 3);
1259     verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
1260
1261     # try re-seeking to the same file
1262     ok(header_for($dev->seek_file(2), 2), "seek to file 2 the first time");
1263     verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
1264     ok(header_for($dev->seek_file(2), 2), "seek to file 2 the third time");
1265
1266     # and seek through the same pattern *without* reading to EOF
1267     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1268     ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1269     ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1270     ok(header_for($dev->seek_file(3), 3), "seek to file 3");
1271     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1272
1273     SKIP: {
1274         skip "APPEND not supported", $verify_file_count
1275             unless $dev->property_get("APPENDABLE");
1276         verify_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
1277     }
1278
1279     ok($dev->finish(),
1280         "finish device after read")
1281         or diag($dev->error_or_status());
1282
1283     # tickle a regression in improperly closing fd's
1284     ok($dev->finish(),
1285         "finish device again after read")
1286         or diag($dev->error_or_status());
1287
1288     ok($dev->read_label() == $DEVICE_STATUS_SUCCESS,
1289         "read_label after second finish (used to fail)")
1290         or diag($dev->error_or_status());
1291
1292     # finally, run the device with FSF and BSF set to "no", to test the
1293     # fallback schemes for this condition
1294
1295     $dev = undef;
1296     $dev = Amanda::Device->new($dev_name);
1297     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1298         "$dev_name: re-create successful")
1299         or diag($dev->error_or_status());
1300     $dev->property_set("fsf", "no");
1301     $dev->property_set("bsf", "no");
1302
1303     ok($dev->start($ACCESS_READ, undef, undef),
1304         "start in read mode")
1305         or diag($dev->error_or_status());
1306
1307     ok(header_for($dev->seek_file(1), 1), "seek to file 1");
1308     ok(header_for($dev->seek_file(4), 4), "seek to file 4");
1309     ok(header_for($dev->seek_file(2), 2), "seek to file 2");
1310
1311     ok($dev->finish(),
1312         "finish device after read")
1313         or diag($dev->error_or_status());
1314 }
1315
1316 SKIP: {
1317     skip "not built with ndmp and server", 26 unless
1318         Amanda::Util::built_with_component("ndmp") and
1319         Amanda::Util::built_with_component("server");
1320
1321     my $dev;
1322     my $testconf = Installcheck::Config->new();
1323     $testconf->write();
1324
1325     my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
1326     if ($cfg_result != $CFGERR_OK) {
1327         my ($level, @errors) = Amanda::Config::config_errors();
1328         die(join "\n", @errors);
1329     }
1330
1331     my $ndmp = Installcheck::Mock::NdmpServer->new();
1332     my $ndmp_port = $ndmp->{'port'};
1333     my $drive = $ndmp->{'drive'};
1334     pass("started ndmjob in daemon mode");
1335
1336     # set up a header for use below
1337     my $hdr = Amanda::Header->new();
1338     $hdr->{type} = $Amanda::Header::F_DUMPFILE;
1339     $hdr->{datestamp} = "20070102030405";
1340     $hdr->{dumplevel} = 0;
1341     $hdr->{compressed} = 1;
1342     $hdr->{name} = "localhost";
1343     $hdr->{disk} = "/home";
1344     $hdr->{program} = "INSTALLCHECK";
1345
1346     $dev = Amanda::Device->new("ndmp:127.0.0.1:9i1\@foo");
1347     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1348         "creation of an ndmp device fails with invalid port");
1349
1350     $dev = Amanda::Device->new("ndmp:127.0.0.1:90000\@foo");
1351     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1352         "creation of an ndmp device fails with too-large port");
1353
1354     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port");
1355     isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
1356         "creation of an ndmp device fails without ..\@device_name");
1357
1358     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1359     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1360         "creation of an ndmp device succeeds with correct syntax");
1361
1362     ok($dev->property_set("ndmp_username", "foo"),
1363         "set ndmp_username property");
1364     is($dev->property_get("ndmp_username"), "foo",
1365         "..and get the value back");
1366     ok($dev->property_set("ndmp_password", "bar"),
1367         "set ndmp_password property");
1368     is($dev->property_get("ndmp_password"), "bar",
1369         "..and get the value back");
1370
1371     ok($dev->property_set("verbose", 1),
1372         "set VERBOSE");
1373
1374     # set 'em back to the defaults
1375     $dev->property_set("ndmp_username", "ndmp");
1376     $dev->property_set("ndmp_password", "ndmp");
1377
1378     # use a big read_block_size, checking that it's also settable
1379     # via read_buffer_size
1380     ok($dev->property_set("read_block_size", 256*1024),
1381     "can set read_block_size");
1382     is($dev->property_get("read_block_size"), 256*1024,
1383     "and its value is reflected");
1384     ok($dev->property_set("read_block_size", 64*1024),
1385     "set read_block_size back to something smaller");
1386
1387     # ok, let's fire the thing up
1388     ok($dev->start($ACCESS_WRITE, "TEST1", "20090915000000"),
1389         "start device in write mode")
1390         or diag $dev->error_or_status();
1391
1392     ok($dev->start_file($hdr),
1393         "start_file");
1394
1395     {   # write to the file
1396         my $xfer = Amanda::Xfer->new([
1397                 Amanda::Xfer::Source::Random->new(32768*21, 0xBEEFEE00),
1398                 Amanda::Xfer::Dest::Device->new($dev, 0) ]);
1399         $xfer->start(make_cb(xmsg_cb => sub {
1400             my ($src, $msg, $xfer) = @_;
1401             if ($msg->{'type'} == $XMSG_ERROR) {
1402                 die $msg->{'elt'} . " failed: " . $msg->{'message'};
1403             } elsif ($msg->{'type'} == $XMSG_DONE) {
1404                 Amanda::MainLoop::quit();
1405             }
1406         }));
1407
1408         Amanda::MainLoop::run();
1409         pass("wrote 21 blocks");
1410     }
1411
1412     ok($dev->finish(),
1413         "finish device")
1414         or diag $dev->error_or_status();
1415
1416     is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1417         "read label from (same) device")
1418         or diag $dev->error_or_status();
1419
1420     is($dev->volume_label, "TEST1",
1421         "volume label read back correctly");
1422
1423     ## label a device and check the label, but open a new device in between
1424
1425     # Write a label
1426     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1427     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1428         "creation of an ndmp device succeeds with correct syntax");
1429     $dev->property_set("ndmp_username", "ndmp");
1430     $dev->property_set("ndmp_password", "ndmp");
1431     $dev->property_set("verbose", 1);
1432
1433     # Write the label
1434     ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
1435         "start device in write mode")
1436         or diag $dev->error_or_status();
1437     ok($dev->finish(),
1438         "finish device")
1439         or diag $dev->error_or_status();
1440
1441     # Read the label with a new device.
1442     $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
1443     is($dev->status(), $DEVICE_STATUS_SUCCESS,
1444         "creation of an ndmp device succeeds with correct syntax");
1445     $dev->property_set("ndmp_username", "ndmp");
1446     $dev->property_set("ndmp_password", "ndmp");
1447     $dev->property_set("verbose", 1);
1448
1449     # read the label
1450     is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
1451         "read label from device")
1452         or diag $dev->error_or_status();
1453     is($dev->volume_label, "TEST2",
1454         "volume label read back correctly");
1455     ok($dev->finish(),
1456         "finish device")
1457         or diag $dev->error_or_status();
1458
1459
1460     $ndmp->cleanup();
1461 }
1462 unlink($input_filename);
1463 unlink($output_filename);
1464 rmtree($taperoot);