-# Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
+# Copyright (c) 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-# Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
+# Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
-use Test::More tests => 301;
+use Test::More tests => 505;
use File::Path qw( mkpath rmtree );
use Sys::Hostname;
use Carp;
use strict;
+use warnings;
use lib "@amperldir@";
+use Installcheck;
+use Installcheck::Mock;
use Installcheck::Config;
use Amanda::Debug;
use Amanda::Device qw( :constants );
use Amanda::Config qw( :getconf :init );
-use Amanda::Types;
+use Amanda::Xfer qw( :constants );
+use Amanda::Header qw( :constants );
use Amanda::Paths;
-use Amanda::Tests;
+use Amanda::Util;
+use Amanda::MainLoop;
+use IO::Socket;
my $dev;
my $dev_name;
my ($vtape1, $vtape2);
my ($input_filename, $output_filename) =
- ( "$AMANDA_TMPDIR/input.tmp", "$AMANDA_TMPDIR/output.tmp" );
-my $taperoot = "$AMANDA_TMPDIR/Amanda_Device_test_tapes";
+ ( "$Installcheck::TMP/input.tmp", "$Installcheck::TMP/output.tmp" );
+my $taperoot = "$Installcheck::TMP/Amanda_Device_test_tapes";
my $testconf;
-my $queue_fd;
# we'll need some vtapes..
sub mkvtape {
# make up a fake dumpfile_t to write with
-my $dumpfile = Amanda::Types::dumpfile_t->new();
-$dumpfile->{type} = $Amanda::Types::F_DUMPFILE;
+my $dumpfile = Amanda::Header->new();
+$dumpfile->{type} = $Amanda::Header::F_DUMPFILE;
$dumpfile->{datestamp} = "20070102030405";
$dumpfile->{dumplevel} = 0;
$dumpfile->{compressed} = 1;
$dumpfile->{disk} = "/home";
$dumpfile->{program} = "INSTALLCHECK";
-# function to set up a queue_fd for a filename
-sub make_queue_fd {
- my ($filename, $mode) = @_;
-
- open(my $fd, $mode, $filename) or die("Could not open $filename: $!");
- return $fd, Amanda::Device::queue_fd_t->new(fileno($fd));
-}
-
my $write_file_count = 5;
sub write_file {
my ($seed, $length, $filenum) = @_;
- croak ("selected file size $length is *way* too big")
- unless ($length < 1024*1024*10);
- Amanda::Tests::write_random_file($seed, $length, $input_filename);
+ $dumpfile->{'datestamp'} = "2000010101010$filenum";
ok($dev->start_file($dumpfile),
"start file $filenum")
is($dev->file(), $filenum,
"Device has correct filenum");
- my ($input, $queue_fd) = make_queue_fd($input_filename, "<");
- ok($dev->write_from_fd($queue_fd),
- "write some data")
- or diag($dev->error_or_status());
- close($input) or die("Error closing $input_filename");
+ croak ("selected file size $length is *way* too big")
+ unless ($length < 1024*1024*10);
+ ok(Amanda::Device::write_random_to_device($seed, $length, $dev),
+ "write random data");
if(ok($dev->in_file(),
"still in_file")) {
}
}
-my $verify_file_count = 5;
+my $verify_file_count = 4;
sub verify_file {
my ($seed, $length, $filenum) = @_;
or diag($dev->error_or_status());
is($dev->file(), $filenum,
"device is really at file $filenum");
- is($read_dumpfile->{name}, "localhost",
- "header looks vaguely familiar")
+ ok(header_for($read_dumpfile, $filenum),
+ "header is correct")
or diag($dev->error_or_status());
-
- my ($output, $queue_fd) = make_queue_fd($output_filename, ">");
- ok($dev->read_to_fd($queue_fd),
- "read data from file $filenum")
- or diag($dev->error_or_status());
- close($output) or die("Error closing $output_filename");
-
- ok(Amanda::Tests::verify_random_file($seed, $length, $output_filename, 0),
+ ok(Amanda::Device::verify_random_from_device($seed, $length, $dev),
"verified file contents");
}
+sub header_for {
+ my ($hdr, $filenum) = @_;
+ return ($hdr and $hdr->{'datestamp'} eq "2000010101010$filenum");
+}
+
# properties test
my @common_properties = (
'medium_access_type',
'min_block_size',
'partial_deletion',
+ 'full_deletion',
'streaming',
);
# put the debug messages somewhere
Amanda::Debug::dbopen("installcheck");
+Installcheck::log_test_output();
####
## Test errors a little bit
"The most reliable device name to use to refer to this device.",
"property info for canonical name is correct");
}
+ok(!$dev->property_get("full_deletion"),
+ "property_get(full_deletion) on null device");
+is($dev->property_get("comment"), undef,
+ "no comment by default");
+ok($dev->property_set("comment", "well, that was silly"),
+ "set comment property");
+is($dev->property_get("comment"), "well, that was silly",
+ "comment correctly stored");
# and write a file to it
write_file(0xabcde, 1024*256, 1);
[ @common_properties, 'max_volume_usage' ],
"necessary properties listed on vfs device");
+# play with properties a little bit
+ok($dev->property_set("comment", 16),
+ "set an string property to an integer");
+
+ok($dev->property_set("comment", 16.0),
+ "set an string property to a float");
+
+ok($dev->property_set("comment", "hi mom"),
+ "set an string property to a string");
+
+ok($dev->property_set("comment", "32768"),
+ "set an integer property to a simple string");
+
+ok($dev->property_set("comment", "32k"),
+ "set an integer property to a string with a unit");
+
+ok($dev->property_set("block_size", 32768),
+ "set an integer property to an integer");
+
$dev->read_label();
ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
"initially unlabeled")
verify_file(0x2FACE, $dev->block_size()*10+17, 3);
+{
+ # try two seek_file's in a row
+ my $hdr = $dev->seek_file(3);
+ is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
+ $hdr = $dev->seek_file(3);
+ is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
+}
+
ok($dev->finish(),
"finish device after read")
or diag($dev->error_or_status());
+# test erase
+ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
+
+ok($dev->erase(),
+ "erase device (again)")
+ or diag($dev->error_or_status());
+
+ok($dev->finish(),
+ "finish device after erase")
+ or diag($dev->error_or_status());
+
+# test monitor_free_space property (testing the monitoring would require a
+# dedicated partition for the tests - it's not worth it)
+
+ok($dev->property_get("monitor_free_space"),
+ "monitor_free_space property is set by default");
+
+ok($dev->property_set("monitor_free_space", 0),
+ "monitor_free_space property can be set to false");
+
+ok(!$dev->property_get("monitor_free_space"),
+ "monitor_free_space property value 'sticks'");
+
+# test the LEOM functionality
+
+$dev = undef;
+$dev = Amanda::Device->new($dev_name);
+is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "$dev_name: re-create successful")
+ or diag($dev->error_or_status());
+ok($dev->property_set("MAX_VOLUME_USAGE", "512k"),
+ "set MAX_VOLUME_USAGE to test LEOM");
+ok($dev->property_set("LEOM", 1),
+ "set LEOM");
+
+ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ok($dev->start_file($dumpfile),
+ "start file 1")
+ or diag($dev->error_or_status());
+
+ok(!$dev->is_eom,
+ "device does not indicate LEOM before writing");
+
+ok(Amanda::Device::write_random_to_device(0xCAFE, 440*1024, $dev),
+ "write random data into the early-warning zone");
+
+ok($dev->is_eom,
+ "device indicates LEOM after writing");
+
+ok($dev->finish_file(),
+ "..but a finish_file is allowed to complete")
+ or diag($dev->error_or_status());
+
+ok($dev->finish(),
+ "finish device after LEOM test")
+ or diag($dev->error_or_status());
+
+$dev = undef;
+$dev = Amanda::Device->new($dev_name);
+is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "$dev_name: re-create successful")
+ or diag($dev->error_or_status());
+ok($dev->property_set("MAX_VOLUME_USAGE", "160k"),
+ "set MAX_VOLUME_USAGE to test LEOM while writing the first header");
+ok($dev->property_set("LEOM", 1),
+ "set LEOM");
+
+ok($dev->start($ACCESS_WRITE, 'TESTCONF23', undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ok($dev->start_file($dumpfile),
+ "start file 1")
+ or diag($dev->error_or_status());
+
+ok($dev->is_eom,
+ "device indicates LEOM after writing first header");
+
+ok($dev->finish_file(),
+ "..but a finish_file is allowed to complete")
+ or diag($dev->error_or_status());
+
+ok($dev->finish(),
+ "finish device after LEOM test")
+ or diag($dev->error_or_status());
+
####
## Test a RAIT device of two vfs devices.
is($dev->property_get("block_size"), 32768*16,
"..and remembers it");
+ok($dev->property_set("max_volume_usage", 32768*1000),
+ "rait device accepts property MAX_VOLUME_USAGE");
+
+is($dev->property_get("max_volume_usage"), 32768*1000,
+ "..and remembers it");
+
$dev->read_label();
ok($dev->status() & $DEVICE_STATUS_VOLUME_UNLABELED,
"initially unlabeled")
undef $dev;
-$dev_name = "rait:{MISSING,file:$vtape2}";
+$dev_name = "rait:{file:$vtape2,MISSING}";
$dev = Amanda::Device->new($dev_name);
ok($dev->start($ACCESS_READ, undef, undef),
undef $dev;
+$dev = Amanda::Device->new_rait_from_children(
+ Amanda::Device->new("file:$vtape2"), undef);
+
+ok(!($dev->start($ACCESS_WRITE, "TESTCONF29", undef)),
+ "start a RAIT device in write mode fails, when created with 'undef'")
+ or diag($dev->error_or_status());
+
# Make two devices with different labels, should get a
# message accordingly.
($vtape1, $vtape2) = (mkvtape(1), mkvtape(2));
$n++;
}
-$dev_name = "rait:{file:$vtape1,file:$vtape2}";
-$dev = Amanda::Device->new($dev_name);
+$dev = Amanda::Device->new_rait_from_children(
+ Amanda::Device->new("file:$vtape1"),
+ Amanda::Device->new("file:$vtape2"));
is($dev->status(), $DEVICE_STATUS_SUCCESS,
- "$dev_name: Open successful")
+ "new_rait_from_children: Open successful")
or diag($dev->error_or_status());
$dev->read_label();
my $DEVPAY_USER_TOKEN = $ENV{'INSTALLCHECK_DEVPAY_USER_TOKEN'};
my $run_s3_tests = defined $S3_SECRET_KEY && defined $S3_ACCESS_KEY;
-my $run_devpay_tests = defined $DEVPAY_SECRET_KEY &&
+my $run_devpay_tests = defined $DEVPAY_SECRET_KEY &&
defined $DEVPAY_ACCESS_KEY && $DEVPAY_USER_TOKEN;
-my $dev_base_name;
-my $hostname = hostname();
-
-my $s3_make_device_count = 6;
-sub s3_make_device($) {
- my $dev_name = shift @_;
+my $s3_make_device_count = 7;
+sub s3_make_device($$) {
+ my ($dev_name, $kind) = @_;
$dev = Amanda::Device->new($dev_name);
is($dev->status(), $DEVICE_STATUS_SUCCESS,
"$dev_name: create successful")
or diag($dev->error_or_status());
my @s3_props = ( 's3_access_key', 's3_secret_key' );
- push @s3_props, 's3_user_token' if ($dev_name =~ /^s3zmanda:/);
+ push @s3_props, 's3_user_token' if ($kind eq "devpay");
properties_include([ $dev->property_list() ], [ @common_properties, @s3_props ],
"necessary properties listed on s3 device");
"set block size")
or diag($dev->error_or_status());
- if ($dev_name =~ /^s3:/) {
+ # might as well save a few cents while testing this property..
+ ok($dev->property_set('S3_STORAGE_CLASS', 'REDUCED_REDUNDANCY'),
+ "set storage class")
+ or diag($dev->error_or_status());
+
+ if ($kind eq "s3") {
# use regular S3 credentials
ok($dev->property_set('S3_ACCESS_KEY', $S3_ACCESS_KEY),
"set S3 access key")
or diag($dev->error_or_status());
pass("(placeholder)");
- } elsif ($dev_name =~ /^s3zmanda:/) {
- # use s3zmanda credentials
+ } elsif ($kind eq "devpay") {
+ # use devpay credentials
ok($dev->property_set('S3_ACCESS_KEY', $DEVPAY_ACCESS_KEY),
- "set s3zmanda access key")
+ "set devpay access key")
or diag($dev->error_or_status());
ok($dev->property_set('S3_SECRET_KEY', $DEVPAY_SECRET_KEY),
- "set s3zmanda secret key")
+ "set devpay secret key")
or diag($dev->error_or_status());
ok($dev->property_set('S3_USER_TOKEN', $DEVPAY_USER_TOKEN),
- "set s3zmanda user token")
+ "set devpay user token")
or diag($dev->error_or_status());
} else {
- croak("didn't recognize the device scheme, so no credentials were set");
+ croak("didn't recognize the device kind, so no credentials were set");
}
return $dev;
}
-my $s3_run_main_tests_count = 12
- + 4 * $write_file_count
- + 1 * $verify_file_count
- + 3 * $s3_make_device_count;
-sub s3_run_main_tests($$) {
- my ($dev_scheme, $base_name) = @_;
- $dev_name = "$dev_scheme:$base_name-$dev_scheme";
- $dev = s3_make_device($dev_name);
+my $base_name;
+
+SKIP: {
+ skip "define \$INSTALLCHECK_S3_{SECRET,ACCESS}_KEY to run S3 tests",
+ 71 +
+ 1 * $verify_file_count +
+ 4 * $write_file_count +
+ 10 * $s3_make_device_count
+ unless $run_s3_tests;
+
+ $dev_name = "s3:";
+ $dev = Amanda::Device->new($dev_name);
+ isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creating $dev_name fails miserably");
+
+ $dev_name = "s3:foo";
+ $dev = Amanda::Device->new($dev_name);
+
+ ok($dev->property_get("full_deletion"),
+ "property_get(full_deletion) on s3 device");
+
+ ok($dev->property_get("leom"),
+ "property_get(leom) on s3 device");
+
+ # test parsing of boolean values
+ # (s3 is the only device driver that has a writable boolean property at the
+ # moment)
+
+ my @verbose_vals = (
+ {'val' => '1', 'true' => 1},
+ {'val' => '0', 'true' => 0},
+ {'val' => 't', 'true' => 1},
+ {'val' => 'true', 'true' => 1},
+ {'val' => 'f', 'true' => 0},
+ {'val' => 'false', 'true' => 0},
+ {'val' => 'y', 'true' => 1},
+ {'val' => 'yes', 'true' => 1},
+ {'val' => 'n', 'true' => 0},
+ {'val' => 'no', 'true' => 0},
+ {'val' => 'on', 'true' => 1},
+ {'val' => 'off', 'true' => 0},
+ {'val' => 'oFf', 'true' => 0},
+ );
+
+ foreach my $v (@verbose_vals) {
+ $dev_name = "s3:foo";
+ $dev = Amanda::Device->new($dev_name);
+
+ $testconf = Installcheck::Config->new();
+ $testconf->add_param("device_property", "\"verbose\" \"$v->{'val'}\"");
+ $testconf->write();
+ config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
+ or die("Could not load configuration");
+
+ ok($dev->configure(1),
+ "configured device with verbose set to $v->{'val'}")
+ or diag($dev->error_or_status());
+
+ my $get_val = $dev->property_get('verbose');
+ # see if truth-iness matches
+ my $expec = $v->{'true'}? "true" : "false";
+ is(!!$dev->property_get('verbose'), !!$v->{'true'},
+ "device_property 'VERBOSE' '$v->{'val'}' => property_get(verbose) returning $expec");
+ }
+
+ # test unparsable property
+ $dev_name = "s3:foo";
+ $dev = Amanda::Device->new($dev_name);
+
+ $testconf = Installcheck::Config->new();
+ $testconf->add_param("device_property", "\"verbose\" \"foo\"");
+ $testconf->write();
+ config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF') == $CFGERR_OK
+ or die("Could not load configuration");
+
+ ok(!$dev->configure(1),
+ "failed to configure device with verbose set to foo");
+
+ like($dev->error_or_status(), qr/'verbose'/,
+ "error message mentions property name");
+
+ like($dev->error_or_status(), qr/'foo'/,
+ "error message mentions property value");
+
+ like($dev->error_or_status(), qr/gboolean/,
+ "error message mentions property type");
+
+ my $hostname = hostname();
+ $hostname =~ s/\./-/g;
+ $base_name = "$S3_ACCESS_KEY-installcheck-$hostname";
+ $dev_name = "s3:$base_name-s3";
+ $dev = s3_make_device($dev_name, "s3");
$dev->read_label();
my $status = $dev->status();
# this test appears very liberal, but catches the case where setup_handle fails without
verify_file(0x2FACE, $dev->block_size()*10, 3);
+ # test EOT indications on reading
+ my $hdr = $dev->seek_file(4);
+ is($hdr->{'type'}, $Amanda::Header::F_DUMPFILE,
+ "file 4 has correct type F_DUMPFILE");
+
+ $hdr = $dev->seek_file(5);
+ is($hdr->{'type'}, $Amanda::Header::F_TAPEEND,
+ "file 5 has correct type F_TAPEEND");
+
+ $hdr = $dev->seek_file(6);
+ is($hdr, undef, "seek_file returns undef for file 6");
+
ok($dev->finish(),
"finish device after read")
- or diag($dev->error_or_status()); # (note: we don't use write_max_size here, as the maximum for S3 is very large)
+ or diag($dev->error_or_status()); # (note: we don't use write_max_size here,
+ # as the maximum for S3 is very large)
- # try a constrained bucket
- $dev_name = lc("$dev_scheme:$base_name-$dev_scheme-eu");
- $dev = s3_make_device($dev_name);
- ok($dev->property_set('S3_BUCKET_LOCATION', 'EU'),
- "set S3 bucket location")
+ ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
+
+ ok($dev->erase(),
+ "erase device (again)")
+ or diag($dev->error_or_status());
+
+ ok($dev->finish(),
+ "finish device after erase")
+ or diag($dev->error_or_status());
+
+ $dev->read_label();
+ $status = $dev->status();
+ ok($status & $DEVICE_STATUS_VOLUME_UNLABELED,
+ "status is unlabeled after an erase")
+ or diag($dev->error_or_status());
+
+ $dev = s3_make_device($dev_name, "s3");
+
+ ok($dev->erase(),
+ "erase device right after creation")
+ or diag($dev->error_or_status());
+
+ # try with empty user token
+ $dev_name = lc("s3:$base_name-s3");
+ $dev = s3_make_device($dev_name, "s3");
+ ok($dev->property_set('S3_USER_TOKEN', ''),
+ "set devpay user token")
or diag($dev->error_or_status());
$dev->read_label();
"status is either OK or possibly unlabeled")
or diag($dev->error_or_status());
- # bucket name incompatible with location constraint
- $dev_name = "$dev_scheme:-$base_name-$dev_scheme-eu";
- $dev = s3_make_device($dev_name);
+ $dev->finish();
- ok(!$dev->property_set('S3_BUCKET_LOCATION', 'EU'),
- "should not be able to set S3 bucket location with an incompatible name")
+ ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
+
+ # try a eu-constrained bucket
+ $dev_name = lc("s3:$base_name-s3-eu");
+ $dev = s3_make_device($dev_name, "s3");
+ ok($dev->property_set('S3_BUCKET_LOCATION', 'EU'),
+ "set S3 bucket location to 'EU'")
or diag($dev->error_or_status());
-}
-SKIP: {
- skip "define \$INSTALLCHECK_S3_{SECRET,ACCESS}_KEY to run S3 tests",
- 1 + $s3_run_main_tests_count + $s3_make_device_count
- unless $run_s3_tests;
+ ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "status is OK")
+ or diag($dev->error_or_status());
+
+ $dev->finish();
+
+ ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
+
+ # try a wildcard-constrained bucket
+ $dev_name = lc("s3:$base_name-s3-wild");
+ $dev = s3_make_device($dev_name, "s3");
+ ok($dev->property_set('S3_BUCKET_LOCATION', '*'),
+ "set S3 bucket location to ''")
+ or diag($dev->error_or_status());
+
+ ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "status is OK")
+ or diag($dev->error_or_status());
+
+ $dev->finish();
- # XXX for best results, the bucket should already exist (Amazon doesn't create
- # buckets quickly enough to pass subsequent tests), but should be empty (so that
- # the device appears unlabeled)
- $dev_base_name = "$S3_ACCESS_KEY-installcheck-$hostname";
+ # test again with invalid ca_info
+ $dev = s3_make_device($dev_name, "s3");
+ SKIP: {
+ skip "SSL not supported; can't check SSL_CA_INFO", 2
+ unless $dev->property_get('S3_SSL');
+
+ ok($dev->property_set('SSL_CA_INFO', '/dev/null'),
+ "set invalid SSL/TLS CA certificate")
+ or diag($dev->error_or_status());
+
+ ok(!$dev->start($ACCESS_WRITE, "TESTCONF13", undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "status is OK")
+ or diag($dev->error_or_status());
+
+ $dev->finish();
+ }
+
+ # test again with our own CA bundle
+ $dev = s3_make_device($dev_name, "s3");
+ SKIP: {
+ skip "SSL not supported; can't check SSL_CA_INFO", 4
+ unless $dev->property_get('S3_SSL');
+ ok($dev->property_set('SSL_CA_INFO', "$srcdir/data/aws-bundle.crt"),
+ "set our own SSL/TLS CA certificate bundle")
+ or diag($dev->error_or_status());
+
+ ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
- s3_run_main_tests('s3', $dev_base_name);
+ ok($dev->start($ACCESS_WRITE, "TESTCONF13", undef),
+ "start in write mode")
+ or diag($dev->error_or_status());
+
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "status is OK")
+ or diag($dev->error_or_status());
+
+ $dev->finish();
+ }
+
+ ok($dev->erase(),
+ "erase device")
+ or diag($dev->error_or_status());
+
+ # bucket names incompatible with location constraint
+ $dev_name = "s3:-$base_name-s3-eu";
+ $dev = s3_make_device($dev_name, "s3");
- # can't set user token without devpay
- $dev_name = "s3:$dev_base_name";
- $dev = s3_make_device($dev_name);
- ok(!$dev->property_set('S3_USER_TOKEN', '123'),
- "set user token, but that shouldn't be possible (not using DevPay)")
+ ok($dev->property_set('S3_BUCKET_LOCATION', ''),
+ "should be able to set an empty S3 bucket location with an incompatible name")
or diag($dev->error_or_status());
+ $dev_name = "s3:$base_name-s3.eu";
+ $dev = s3_make_device($dev_name, "s3");
+
+ ok($dev->property_set('S3_BUCKET_LOCATION', ''),
+ "should be able to set an empty S3 bucket location with an incompatible name")
+ or diag($dev->error_or_status());
+
+ $dev_name = "s3:-$base_name-s3-eu";
+ $dev = s3_make_device($dev_name, "s3");
+
+ ok(!$dev->property_set('S3_BUCKET_LOCATION', 'EU'),
+ "should not be able to set S3 bucket location with an incompatible name")
+ or diag($dev->error_or_status());
}
SKIP: {
# in this case, most of our code has already been exercised
# just make sure that authentication works as a basic sanity check
- skip "skipping abbreviated s3zmanda tests", $s3_make_device_count + 1
- unless ($run_s3_tests and $run_devpay_tests);
- $dev_name = "s3zmanda:$dev_base_name";
- $dev = s3_make_device($dev_name);
+ skip "skipping abbreviated devpay tests", $s3_make_device_count + 1
+ unless $run_devpay_tests;
+ $dev_name = "s3:$base_name-devpay";
+ $dev = s3_make_device($dev_name, "devpay");
$dev->read_label();
my $status = $dev->status();
# this test appears very liberal, but catches the case where setup_handle fails without
or diag($dev->error_or_status());
}
-SKIP: {
- # if we're running devpay tests and not S3 tests, then we do the whole suite with devpay
- skip "define \$INSTALLCHECK_DEVPAY_{SECRET_KEY,ACCESS_KEY,USER_TOKEN} to run full s3zmanda tests", $s3_run_main_tests_count
- unless (!$run_s3_tests and $run_devpay_tests);
- s3_run_main_tests('s3zmanda', $dev_base_name);
-}
-
# Test a tape device if the proper environment variables are set
my $TAPE_DEVICE = $ENV{'INSTALLCHECK_TAPE_DEVICE'};
my $run_tape_tests = defined $TAPE_DEVICE;
SKIP: {
- skip "define \$INSTALLCHECK_TAPE_DEVICE to run tape tests", 37
+ skip "define \$INSTALLCHECK_TAPE_DEVICE to run tape tests",
+ 30 +
+ 7 * $verify_file_count +
+ 5 * $write_file_count
unless $run_tape_tests;
$dev_name = "tape:$TAPE_DEVICE";
"not unlabeled anymore")
or diag($dev->error_or_status());
- for (my $i = 1; $i <= 3; $i++) {
- write_file(0x2FACE, $dev->block_size()*10+17, $i);
+ for (my $i = 1; $i <= 4; $i++) {
+ write_file(0x2FACE+$i, $dev->block_size()*10+17, $i);
}
ok($dev->finish(),
# append one more copy, to test ACCESS_APPEND
+ # if final_filemarks is 1, then the tape device will use F_NOOP,
+ # inserting an extra file, and we'll be appending at file number 6.
+ my $append_fileno = ($dev->property_get("FINAL_FILEMARKS") == 2)? 5:6;
+
SKIP: {
skip "APPEND not supported", $write_file_count + 2
unless $dev->property_get("APPENDABLE");
+
ok($dev->start($ACCESS_APPEND, undef, undef),
"start in append mode")
or diag($dev->error_or_status());
- write_file(0xD0ED0E, $dev->block_size()*4, 4);
+ write_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
ok($dev->finish(),
"finish device after append")
or diag($dev->error_or_status());
}
- # try reading the third file back, creating a new device
- # object first, and skipping the read-label step.
+ # try reading the second and third files back, creating a new
+ # device object first, and skipping the read-label step.
$dev = undef;
$dev = Amanda::Device->new($dev_name);
"$dev_name: re-create successful")
or diag($dev->error_or_status());
+ # use a big read_block_size, checking that it's also settable
+ # via read_buffer_size
+ ok($dev->property_set("read_buffer_size", 256*1024),
+ "can set read_buffer_size");
+ is($dev->property_get("read_block_size"), 256*1024,
+ "and its value is reflected in read_block_size");
+ ok($dev->property_set("read_block_size", 32*1024),
+ "can set read_block_size");
+
ok($dev->start($ACCESS_READ, undef, undef),
"start in read mode")
or diag($dev->error_or_status());
- verify_file(0x2FACE, $dev->block_size()*10+17, 3);
+ # now verify those files in a particular order to trigger all of the
+ # seeking edge cases
+
+ verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
+ verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
+ verify_file(0x2FACE+4, $dev->block_size()*10+17, 4);
+ verify_file(0x2FACE+3, $dev->block_size()*10+17, 3);
+ verify_file(0x2FACE+1, $dev->block_size()*10+17, 1);
+
+ # try re-seeking to the same file
+ ok(header_for($dev->seek_file(2), 2), "seek to file 2 the first time");
+ verify_file(0x2FACE+2, $dev->block_size()*10+17, 2);
+ ok(header_for($dev->seek_file(2), 2), "seek to file 2 the third time");
+
+ # and seek through the same pattern *without* reading to EOF
+ ok(header_for($dev->seek_file(1), 1), "seek to file 1");
+ ok(header_for($dev->seek_file(2), 2), "seek to file 2");
+ ok(header_for($dev->seek_file(4), 4), "seek to file 4");
+ ok(header_for($dev->seek_file(3), 3), "seek to file 3");
+ ok(header_for($dev->seek_file(1), 1), "seek to file 1");
+
+ SKIP: {
+ skip "APPEND not supported", $verify_file_count
+ unless $dev->property_get("APPENDABLE");
+ verify_file(0xD0ED0E, $dev->block_size()*4, $append_fileno);
+ }
ok($dev->finish(),
"finish device after read")
or diag($dev->error_or_status());
+ # tickle a regression in improperly closing fd's
+ ok($dev->finish(),
+ "finish device again after read")
+ or diag($dev->error_or_status());
+
+ ok($dev->read_label() == $DEVICE_STATUS_SUCCESS,
+ "read_label after second finish (used to fail)")
+ or diag($dev->error_or_status());
+
+ # finally, run the device with FSF and BSF set to "no", to test the
+ # fallback schemes for this condition
+
+ $dev = undef;
+ $dev = Amanda::Device->new($dev_name);
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "$dev_name: re-create successful")
+ or diag($dev->error_or_status());
+ $dev->property_set("fsf", "no");
+ $dev->property_set("bsf", "no");
+
+ ok($dev->start($ACCESS_READ, undef, undef),
+ "start in read mode")
+ or diag($dev->error_or_status());
+
+ ok(header_for($dev->seek_file(1), 1), "seek to file 1");
+ ok(header_for($dev->seek_file(4), 4), "seek to file 4");
+ ok(header_for($dev->seek_file(2), 2), "seek to file 2");
+
+ ok($dev->finish(),
+ "finish device after read")
+ or diag($dev->error_or_status());
}
+SKIP: {
+ skip "not built with ndmp and server", 78 unless
+ Amanda::Util::built_with_component("ndmp") and
+ Amanda::Util::built_with_component("server");
+
+ my $dev;
+ my $testconf = Installcheck::Config->new();
+ $testconf->write();
+
+ my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
+ if ($cfg_result != $CFGERR_OK) {
+ my ($level, @errors) = Amanda::Config::config_errors();
+ die(join "\n", @errors);
+ }
+
+ my $ndmp = Installcheck::Mock::NdmpServer->new();
+ my $ndmp_port = $ndmp->{'port'};
+ my $drive = $ndmp->{'drive'};
+ pass("started ndmjob in daemon mode");
+
+ # set up a header for use below
+ my $hdr = Amanda::Header->new();
+ $hdr->{type} = $Amanda::Header::F_DUMPFILE;
+ $hdr->{datestamp} = "20070102030405";
+ $hdr->{dumplevel} = 0;
+ $hdr->{compressed} = 1;
+ $hdr->{name} = "localhost";
+ $hdr->{disk} = "/home";
+ $hdr->{program} = "INSTALLCHECK";
+
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:9i1\@foo");
+ isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device fails with invalid port");
+
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:90000\@foo");
+ isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device fails with too-large port");
+
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port");
+ isnt($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device fails without ..\@device_name");
+
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device succeeds with correct syntax");
+
+ ok($dev->property_set("ndmp_username", "foo"),
+ "set ndmp_username property");
+ is($dev->property_get("ndmp_username"), "foo",
+ "..and get the value back");
+ ok($dev->property_set("ndmp_password", "bar"),
+ "set ndmp_password property");
+ is($dev->property_get("ndmp_password"), "bar",
+ "..and get the value back");
+
+ ok($dev->property_set("verbose", 1),
+ "set VERBOSE");
+
+ # set 'em back to the defaults
+ $dev->property_set("ndmp_username", "ndmp");
+ $dev->property_set("ndmp_password", "ndmp");
+
+ # use a big read_block_size, checking that it's also settable
+ # via read_buffer_size
+ ok($dev->property_set("read_block_size", 256*1024),
+ "can set read_block_size");
+ is($dev->property_get("read_block_size"), 256*1024,
+ "and its value is reflected");
+ ok($dev->property_set("read_block_size", 64*1024),
+ "set read_block_size back to something smaller");
+
+ # ok, let's fire the thing up
+ ok($dev->start($ACCESS_WRITE, "TEST1", "20090915000000"),
+ "start device in write mode")
+ or diag $dev->error_or_status();
+
+ ok($dev->start_file($hdr),
+ "start_file");
+
+ { # write to the file
+ my $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Random->new(32768*21, 0xBEEFEE00),
+ Amanda::Xfer::Dest::Device->new($dev, 0) ]);
+ $xfer->start(make_cb(xmsg_cb => sub {
+ my ($src, $msg, $xfer) = @_;
+ if ($msg->{'type'} == $XMSG_ERROR) {
+ die $msg->{'elt'} . " failed: " . $msg->{'message'};
+ } elsif ($msg->{'type'} == $XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ }));
+
+ Amanda::MainLoop::run();
+ pass("wrote 21 blocks");
+ }
+
+ ok($dev->finish(),
+ "finish device")
+ or diag $dev->error_or_status();
+
+ is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
+ "read label from (same) device")
+ or diag $dev->error_or_status();
+
+ is($dev->volume_label, "TEST1",
+ "volume label read back correctly");
+
+ ## label a device and check the label, but open a new device in between
+
+ # Write a label
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device succeeds with correct syntax");
+ $dev->property_set("ndmp_username", "ndmp");
+ $dev->property_set("ndmp_password", "ndmp");
+ $dev->property_set("verbose", 1);
+
+ # Write the label
+ ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
+ "start device in write mode")
+ or diag $dev->error_or_status();
+ ok($dev->finish(),
+ "finish device")
+ or diag $dev->error_or_status();
+
+ # Read the label with a new device.
+ $dev = Amanda::Device->new("ndmp:127.0.0.1:$ndmp_port\@$drive");
+ is($dev->status(), $DEVICE_STATUS_SUCCESS,
+ "creation of an ndmp device succeeds with correct syntax");
+ $dev->property_set("ndmp_username", "ndmp");
+ $dev->property_set("ndmp_password", "ndmp");
+ $dev->property_set("verbose", 1);
+
+ # read the label
+ is($dev->read_label(), $DEVICE_STATUS_SUCCESS,
+ "read label from device")
+ or diag $dev->error_or_status();
+ is($dev->volume_label, "TEST2",
+ "volume label read back correctly");
+ ok($dev->finish(),
+ "finish device")
+ or diag $dev->error_or_status();
+
+ #
+ # test the directtcp-target implementation
+ #
+
+ ok($dev->directtcp_supported(), "is a directtcp target");
+ for my $dev_use ('initiator', 'listener') {
+ my ($xfer, $addrs, $dest_elt);
+ if ($dev_use eq 'listener') {
+ $addrs = $dev->listen(1);
+ ok($addrs, "listen returns successfully") or die($dev->error_or_status());
+
+ # set up an xfer to write to the device
+ $dest_elt = Amanda::Xfer::Dest::DirectTCPConnect->new($addrs);
+ } else {
+ # set up an xfer to write to the device
+ $dest_elt = Amanda::Xfer::Dest::DirectTCPListen->new();
+ }
+ $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Random->new(32768*34, 0xB00),
+ $dest_elt,
+ ]);
+
+ my @messages;
+ $xfer->start(make_cb(xmsg_cb => sub {
+ my ($src, $msg, $xfer) = @_;
+ if ($msg->{'type'} == $XMSG_ERROR) {
+ die $msg->{'elt'} . " failed: " . $msg->{'message'};
+ } elsif ($msg->{'type'} == $XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ }));
+
+ # write files from the connection until EOF
+ my $num_files;
+ my $conn;
+ my ($finish_connection, $start_device, $write_file_cb);
+
+
+ $finish_connection = make_cb(finish_connection => sub {
+ if ($dev_use eq 'listener') {
+ $conn = $dev->accept();
+ } else {
+ $addrs = $dest_elt->get_addrs();
+ $conn = $dev->connect(1, $addrs);
+ }
+ Amanda::MainLoop::call_later($start_device);
+ });
+
+
+ $start_device = make_cb(start_device => sub {
+ ok($dev->start($ACCESS_WRITE, "TEST2", "20090915000000"),
+ "start device in write mode")
+ or diag $dev->error_or_status();
+
+ Amanda::MainLoop::call_later($write_file_cb);
+ });
+
+ $write_file_cb = make_cb(write_file_cb => sub {
+ ++$num_files < 20 or die "I seem to be in a loop!";
+
+ ok($dev->start_file($hdr), "start file $num_files for writing");
+ is($dev->file, $num_files, "..file number is correct");
+
+ my ($ok, $size) = $dev->write_from_connection(32768*15);
+ push @messages, sprintf("WRITE-%s-%d-%s-%s",
+ $ok?"OK":"ERR", $size,
+ $dev->is_eof()? "EOF":"!eof",
+ $dev->is_eom()? "EOM":"!eom");
+ ok($ok, "..write from connection succeeds");
+ my $eof = $dev->is_eof();
+
+ ok($dev->finish_file(), "..finish file after writing");
+
+ if (!$eof) {
+ Amanda::MainLoop::call_later($write_file_cb);
+ }
+ });
+
+ Amanda::MainLoop::call_later($finish_connection);
+ Amanda::MainLoop::run();
+ is_deeply([@messages], [
+ 'WRITE-OK-491520-!eof-!eom',
+ 'WRITE-OK-491520-!eof-!eom',
+ 'WRITE-OK-131072-EOF-!eom',
+ ],
+ "a sequence of write_from_connection calls works correctly");
+
+ $dev->finish();
+
+ if (my $err = $conn->close()) {
+ die $err;
+ }
+ }
+
+ # now try reading that back piece by piece
+
+ {
+ my $filename = "$Installcheck::TMP/Amanda_Device_ndmp.tmp";
+ open(my $dest_fh, ">", $filename);
+
+ ok($dev->start($ACCESS_READ, undef, undef),
+ "start device in read mode")
+ or diag $dev->error_or_status();
+
+ my $file;
+ for ($file = 1; $file <= 3; $file++) {
+ ok($dev->seek_file($file),
+ "seek_file $file");
+ is($dev->file, $file, "..file num is correct");
+ is($dev->block, 0, "..block num is correct");
+
+ # read the file, writing to our temp file. We'll check that the byte
+ # sequence is correct later
+ my $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Device->new($dev),
+ Amanda::Xfer::Dest::Fd->new($dest_fh) ]);
+
+ $xfer->start(make_cb(xmsg_cb => sub {
+ my ($src, $msg, $xfer) = @_;
+ if ($msg->{'type'} == $XMSG_ERROR) {
+ die $msg->{'elt'} . " failed: " . $msg->{'message'};
+ } elsif ($msg->{'type'} == $XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ }));
+ Amanda::MainLoop::run();
+
+ pass("read back file " . $file);
+ }
+
+ $dev->finish();
+ close $dest_fh;
+
+ # now read back and verify that file
+ open(my $src_fh, "<", $filename);
+ my $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Fd->new($src_fh),
+ Amanda::Xfer::Dest::Null->new(0xB00) ]);
+
+ $xfer->start(make_cb(xmsg_cb => sub {
+ my ($src, $msg, $xfer) = @_;
+ if ($msg->{'type'} == $XMSG_ERROR) {
+ die $msg->{'elt'} . " failed: " . $msg->{'message'};
+ } elsif ($msg->{'type'} == $XMSG_DONE) {
+ Amanda::MainLoop::quit();
+ }
+ }));
+ Amanda::MainLoop::run();
+
+ pass("data in the three parts is correct");
+ unlink $filename;
+ }
+
+ ####
+ # Test read_to_connection
+ #
+ # This requires something that can connect to a device and read from
+ # it; the XFA does not have an XFER_MECH_DIRECTTCP_CONNECT, so we fake
+ # it by manually connecting and then setting up an xfer with a regular
+ # XferSourceFd. This works because the NDMP server will accept an
+ # incoming connection before the Device API accept() method is called;
+ # this trick may not work with other DirectTCP-capable devices. Also,
+ # this doesn't work so well if there's an error in the xfer (e.g., a
+ # random value mismatch). But tests are supposed to succeed!
+
+ sub test_read2conn {
+ my ($finished_cb) = @_;
+ my @events;
+ my $file = 1;
+ my ($conn, $sock);
+
+ my $steps = define_steps
+ cb_ref => \$finished_cb;
+
+ step setup => sub {
+ my $addrs = $dev->listen(0);
+
+ # now connect to that
+ $sock = IO::Socket::INET->new(
+ Proto => "tcp",
+ PeerHost => $addrs->[0][0],
+ PeerPort => $addrs->[0][1],
+ Blocking => 1,
+ );
+
+ # and set up a transfer to read from that socket
+ my $xfer = Amanda::Xfer->new([
+ Amanda::Xfer::Source::Fd->new($sock),
+ Amanda::Xfer::Dest::Null->new(0xB00) ]);
+
+ $xfer->start(make_cb(xmsg_cb => sub {
+ my ($src, $msg, $xfer) = @_;
+ if ($msg->{'type'} == $XMSG_ERROR) {
+ die $msg->{'elt'} . " failed: " . $msg->{'message'};
+ }
+ if ($msg->{'type'} == $XMSG_DONE) {
+ push @events, "DONE";
+ $steps->{'quit'}->();
+ }
+ }));
+
+ $steps->{'accept'}->();
+ };
+
+ step accept => sub {
+ $conn = $dev->accept();
+ die $dev->error_or_status() unless ($conn);
+
+ Amanda::MainLoop::call_later($steps->{'start_dev'});
+ };
+
+ step start_dev => sub {
+ ok($dev->start($ACCESS_READ, undef, undef),
+ "start device in read mode")
+ or diag $dev->error_or_status();
+
+ Amanda::MainLoop::call_later($steps->{'read_part_cb'});
+ };
+
+ step read_part_cb => sub {
+ my $hdr = $dev->seek_file($file);
+ die $dev->error_or_status() unless ($hdr);
+ my $size = $dev->read_to_connection(0);
+ push @events, "READ-$size";
+
+ if (++$file <= 3) {
+ Amanda::MainLoop::call_later($steps->{'read_part_cb'});
+ } else {
+ # close the connection, which will end the xfer, which will
+ # result in a call to finished_cb. So there.
+ push @events, "CLOSE";
+ $conn->close();
+ }
+ };
+
+ step quit => sub {
+ close $sock or die "close: $!";
+
+ is_deeply([@events],
+ [ "READ-491520", "READ-491520", "READ-131072", "CLOSE", "DONE" ],
+ "sequential read_to_connection operations read the right amounts " .
+ "and bytestream matches");
+
+ $finished_cb->();
+ };
+ }
+ test_read2conn(\&Amanda::MainLoop::quit);
+ Amanda::MainLoop::run();
+
+ # try two seek_file's in a row
+ $hdr = $dev->seek_file(2);
+ is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the first time");
+ $hdr = $dev->seek_file(2);
+ is($hdr? $hdr->{'type'} : -1, $Amanda::Header::F_DUMPFILE, "seek_file the second time");
+
+ ## test seek_file's handling of EOM
+
+ $hdr = $dev->seek_file(3);
+ is($hdr->{type}, $Amanda::Header::F_DUMPFILE, "file 3 is a dumpfile");
+ $hdr = $dev->seek_file(4);
+ is($hdr->{type}, $Amanda::Header::F_TAPEEND, "file 4 is tapeend");
+ $hdr = $dev->seek_file(5);
+ is($hdr, undef, "file 5 is an error");
+ $hdr = $dev->seek_file(6);
+ is($hdr, undef, "file 6 is an error");
+
+ $ndmp->cleanup();
+}
unlink($input_filename);
unlink($output_filename);
rmtree($taperoot);