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