X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=installcheck%2FAmanda_Changer.pl;h=a626e7370af878a86b1c6886a519a0ab23e6da67;hb=fd48f3e498442f0cbff5f3606c7c403d0566150e;hp=d2ce81b5ac8f31b49764c6b5de7012611d223102;hpb=96f35b20267e8b1a1c846d476f27fcd330e0b018;p=debian%2Famanda diff --git a/installcheck/Amanda_Changer.pl b/installcheck/Amanda_Changer.pl index d2ce81b..a626e73 100644 --- a/installcheck/Amanda_Changer.pl +++ b/installcheck/Amanda_Changer.pl @@ -1,4 +1,4 @@ -# Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved. +# Copyright (c) 2007, 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 @@ -13,11 +13,12 @@ # 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 => 35; +use Test::More tests => 47; use File::Path; +use Data::Dumper; use strict; use lib "@amperldir@"; @@ -31,6 +32,7 @@ use Amanda::Changer; # set up debugging so debug output doesn't interfere with test results Amanda::Debug::dbopen("installcheck"); +Installcheck::log_test_output(); # and disable Debug's die() and warn() overrides Amanda::Debug::disable_die_override(); @@ -48,9 +50,10 @@ $INC{'Amanda/Changer/test.pm'} = "Amanda_Changer"; sub new { my $class = shift; - my ($cc, $tpchanger) = @_; + my ($config, $tpchanger) = @_; my $self = { + config => $config, curslot => 0, slots => [ 'TAPE-00', 'TAPE-01', 'TAPE-02', 'TAPE-03' ], reserved_slots => [], @@ -98,10 +101,17 @@ sub load { } $cb->(undef, Amanda::Changer::test::Reservation->new($self, $slot, $label)); - } elsif (exists $params{'slot'}) { + } elsif (exists $params{'slot'} or exists $params{'relative_slot'}) { my $slot = $params{'slot'}; - $slot = $self->{'curslot'} - if ($slot eq "current"); + if (exists $params{'relative_slot'}) { + if ($params{'relative_slot'} eq "current") { + $slot = $self->{'curslot'}; + } elsif ($params{'relative_slot'} eq "next") { + $slot = ($self->{'curslot'} + 1) % (scalar @{$self->{'slots'}}); + } else { + die "invalid relative_slot"; + } + } if (grep { $_ == $slot } @{$self->{'reserved_slots'}}) { $cb->("Slot $slot is already in use", undef); @@ -120,15 +130,33 @@ sub load { } } +sub info_key { + my $self = shift; + my ($key, %params) = @_; + my %results; + + if ($key eq 'num_slots') { + $results{$key} = 13; + } elsif ($key eq 'mkerror1') { + return $self->make_error("failed", $params{'info_cb'}, + reason => "unknown", + message => "err1"); + } elsif ($key eq 'mkerror2') { + return $self->make_error("failed", $params{'info_cb'}, + reason => "unknown", + message => "err2"); + } + + $params{'info_cb'}->(undef, %results) if $params{'info_cb'}; +} + sub reset { my $self = shift; my %params = @_; $self->{'curslot'} = 0; - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}, undef); - } + $params{'finished_cb'}->(undef) if $params{'finished_cb'}; } sub clean { @@ -137,11 +165,24 @@ sub clean { $self->{'clean'} = 1; - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}, undef); - } + $params{'finished_cb'}->(undef) if $params{'finished_cb'}; } +sub inventory { + my $self = shift; + my %params = @_; + + Amanda::MainLoop::call_later($params{'inventory_cb'}, + undef, [ { + slot => 1, + empty => 0, + label => 'TAPE-99', + barcode => '09385A', + reserved => 0, + import_export => 0, + loaded_in => undef, + }]); +} package Amanda::Changer::test::Reservation; use vars qw( @ISA ); @@ -156,14 +197,13 @@ sub new { $self->{'slot'} = $slot; $self->{'label'} = $label; - $self->{'device_name'} = "test:slot-$slot"; + $self->{'device'} = Amanda::Device->new("null:slot-$slot"); $self->{'this_slot'} = $slot; - $self->{'next_slot'} = ($slot + 1) % (scalar @{$chg->{'slots'}}); return $self; } -sub release { +sub do_release { my $self = shift; my %params = @_; my $slot = $self->{'slot'}; @@ -171,9 +211,7 @@ sub release { $chg->{'reserved_slots'} = [ grep { $_ != $slot } @{$chg->{'reserved_slots'}} ]; - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}, undef); - } + $params{'finished_cb'}->(undef) if $params{'finished_cb'}; } sub set_label { @@ -185,9 +223,7 @@ sub set_label { $self->{'chg'}->{'slots'}->[$self->{'slot'}] = $params{'label'}; $self->{'label'} = $params{'label'}; - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}, undef); - } + $params{'finished_cb'}->(undef) if $params{'finished_cb'}; } # -------- @@ -201,6 +237,7 @@ my $testconf; $testconf = Installcheck::Config->new(); $testconf->add_changer("mychanger", [ 'tpchanger' => '"chg-test:/foo"', + 'property' => '"testprop" "testval"', ]); $testconf->write(); @@ -210,55 +247,75 @@ if ($cfg_result != $CFGERR_OK) { die(join "\n", @errors); } -# test loading by label - +# check out the relevant changer properties my $chg = Amanda::Changer->new("mychanger"); +is($chg->{'config'}->get_property("testprop"), "testval", + "changer properties are correctly represented"); + +# test loading by label { - my @labels = ( 'TAPE-02', 'TAPE-00', 'TAPE-03' ); - my @reservations = (); - my $getres; + my @labels; + my @reservations; + my ($getres, $rq_reserved, $relres); + + $getres = make_cb('getres' => sub { + if (!@labels) { + return $rq_reserved->(); + } - $getres = sub { my $label = pop @labels; $chg->load(label => $label, set_current => ($label eq "TAPE-02"), res_cb => sub { - my ($err, $reservation) = @_; + my ($err, $res) = @_; ok(!$err, "no error loading $label") or diag($err); # keep this reservation - if ($reservation) { - push @reservations, $reservation; - } + push @reservations, $res if $res; # and start on the next - if (@labels) { - $getres->(); - return; - } else { - # try to load an already-reserved volume - $chg->load(label => 'TAPE-00', - res_cb => sub { - my ($err, $reservation) = @_; - ok($err, "error when requesting already-reserved volume"); - Amanda::MainLoop::quit(); - }); - } + $getres->(); }); - }; + }); + + $rq_reserved = make_cb(rq_reserved => sub { + # try to load an already-reserved volume + $chg->load(label => 'TAPE-00', + res_cb => sub { + my ($err, $res) = @_; + ok($err, "error when requesting already-reserved volume"); + push @reservations, $res if $res; + + $relres->(); + }); + }); + + $relres = make_cb('relres' => sub { + if (!@reservations) { + return Amanda::MainLoop::quit(); + } + + my $res = pop @reservations; + $res->release(finished_cb => sub { + my ($err) = @_; + die $err if $err; + + $relres->(); + }); + }); # start the loop - Amanda::MainLoop::call_later($getres); + @labels = ( 'TAPE-02', 'TAPE-00', 'TAPE-03' ); + $getres->(); + Amanda::MainLoop::run(); + + $relres->(); Amanda::MainLoop::run(); - # ditch the reservations and do it all again - @reservations = (); @labels = ( 'TAPE-00', 'TAPE-01' ); - is_deeply($chg->{'reserved_slots'}, [], - "reservations are released when the Reservation object goes out of scope"); - Amanda::MainLoop::call_later($getres); + $getres->(); Amanda::MainLoop::run(); # explicitly release the reservations (without using the callback) @@ -269,29 +326,37 @@ my $chg = Amanda::Changer->new("mychanger"); # test loading by slot { - my ($start, $first_cb, $second_cb); + my ($start, $first_cb, $released, $second_cb, $quit); + my $slot; # reserves the current slot - $start = sub { - $chg->load(res_cb => $first_cb, slot => "current"); - }; + $start = make_cb('start' => sub { + $chg->load(res_cb => $first_cb, relative_slot => "current"); + }); # gets a reservation for the "current" slot - $first_cb = sub { + $first_cb = make_cb('first_cb' => sub { my ($err, $res) = @_; die $err if $err; is($res->{'this_slot'}, 2, "'current' slot loads slot 2"); - is($res->{'device_name'}, "test:slot-2", + is($res->{'device'}->device_name, "null:slot-2", "..device is correct"); - is($res->{'next_slot'}, 3, - "..and the next slot is slot 3"); - $chg->load(res_cb => $second_cb, slot => $res->{'next_slot'}, set_current => 1); - }; + + $slot = $res->{'this_slot'}; + $res->release(finished_cb => $released); + }); + + $released = make_cb(released => sub { + my ($err) = @_; + + $chg->load(res_cb => $second_cb, relative_slot => 'next', + slot => $slot, set_current => 1); + }); # gets a reservation for the "next" slot - $second_cb = sub { + $second_cb = make_cb('second_cb' => sub { my ($err, $res) = @_; die $err if $err; @@ -299,86 +364,161 @@ my $chg = Amanda::Changer->new("mychanger"); "next slot loads slot 3"); is($chg->{'curslot'}, 3, "..which is also now the current slot"); - is($res->{'next_slot'}, 0, - "..and the next slot is slot 0"); + + $res->release(finished_cb => $quit); + }); + + $quit = make_cb(quit => sub { + my ($err) = @_; + die $err if $err; Amanda::MainLoop::quit(); - }; + }); - Amanda::MainLoop::call_later($start); + $start->(); Amanda::MainLoop::run(); } # test set_label { - my ($start, $load1_cb, $set_cb, $load2_cb, $load3_cb); + my ($start, $load1_cb, $set_cb, $released, $load2_cb, $released2, $load3_cb); + my $res; # load TAPE-00 - $start = sub { + $start = make_cb('start' => sub { $chg->load(res_cb => $load1_cb, label => "TAPE-00"); - }; + }); # rename it to TAPE-99 - $load1_cb = sub { - my ($err, $res) = @_; + $load1_cb = make_cb('load1_cb' => sub { + (my $err, $res) = @_; die $err if $err; pass("loaded TAPE-00"); $res->set_label(label => "TAPE-99", finished_cb => $set_cb); - $res->release(); - }; + }); + + $set_cb = make_cb('set_cb' => sub { + my ($err) = @_; + + $res->release(finished_cb => $released); + }); # try to load TAPE-00 - $set_cb = sub { + $released = make_cb('released' => sub { my ($err) = @_; die $err if $err; pass("relabeled TAPE-00 to TAPE-99"); $chg->load(res_cb => $load2_cb, label => "TAPE-00"); - }; + }); # try to load TAPE-99 - $load2_cb = sub { - my ($err, $res) = @_; - + $load2_cb = make_cb('load2_cb' => sub { + (my $err, $res) = @_; ok($err, "loading TAPE-00 is now an error"); + $chg->load(res_cb => $load3_cb, label => "TAPE-99"); - }; + }); # check result - $load3_cb = sub { - my ($err, $res) = @_; + $load3_cb = make_cb('load3_cb' => sub { + (my $err, $res) = @_; die $err if $err; pass("but loading TAPE-99 is ok"); + $res->release(finished_cb => $released2); + }); + + $released2 = make_cb(released2 => sub { + my ($err) = @_; + die $err if $err; + Amanda::MainLoop::quit(); - }; + }); - Amanda::MainLoop::call_later($start); + $start->(); Amanda::MainLoop::run(); } -# test reset and clean -{ - my ($do_reset, $do_clean); +# test reset and clean and inventory +sub test_simple { + my ($finished_cb) = @_; + + my $steps = define_steps + cb_ref => \$finished_cb; - $do_reset = sub { + step do_reset => sub { $chg->reset(finished_cb => sub { is($chg->{'curslot'}, 0, "reset() resets to slot 0"); - $do_clean->(); + $steps->{'do_clean'}->(); }); }; - $do_clean = sub { + step do_clean => sub { $chg->clean(finished_cb => sub { ok($chg->{'clean'}, "clean 'cleaned' the changer"); - Amanda::MainLoop::quit(); + $steps->{'do_inventory'}->(); + }); + }; + + step do_inventory => sub { + $chg->inventory(inventory_cb => sub { + is_deeply($_[1], [ { + slot => 1, + empty => 0, + label => 'TAPE-99', + barcode => '09385A', + reserved => 0, + import_export => 0, + loaded_in => undef, + }], "inventory returns an inventory"); + $finished_cb->(); }); }; +} +test_simple(\&Amanda::MainLoop::quit); +Amanda::MainLoop::run(); - Amanda::MainLoop::call_later($do_reset); +# test info +{ + my ($do_info, $check_info, $do_info_err, $check_info_err); + + $do_info = make_cb('do_info' => sub { + $chg->info(info_cb => $check_info, + info => [ 'num_slots' ]); + }); + + $check_info = make_cb('check_info' => sub { + my ($err, %results) = @_; + die($err) if $err; + is_deeply(\%results, { 'num_slots' => 13 }, + "info() works"); + $do_info_err->(); + }); + + $do_info_err = make_cb('do_info_err' => sub { + $chg->info(info_cb => $check_info_err, + info => [ 'mkerror1', 'mkerror2' ]); + }); + + $check_info_err = make_cb('check_info_err' => sub { + my ($err, %results) = @_; + is($err, + "While getting info key 'mkerror1': err1; While getting info key 'mkerror2': err2", + "info errors are handled correctly"); + is($err->{'type'}, 'failed', "error has type 'failed'"); + ok($err->failed, "\$err->failed is true"); + ok(!$err->fatal, "\$err->fatal is false"); + is($err->{'reason'}, 'unknown', "\$err->{'reason'} is 'unknown'"); + ok($err->unknown, "\$err->unknown is true"); + ok(!$err->notimpl, "\$err->notimpl is false"); + Amanda::MainLoop::quit(); + }); + + $do_info->(); Amanda::MainLoop::run(); } @@ -386,13 +526,14 @@ my $chg = Amanda::Changer->new("mychanger"); # _new_from_uri so we can monitor the result sub my_new_from_uri { my ($uri, $cc, $name) = @_; + return $uri if (ref $uri and $uri->isa("Amanda::Changer::Error")); return [ $uri, $cc? "cc" : undef ]; } *saved_new_from_uri = *Amanda::Changer::_new_from_uri; *Amanda::Changer::_new_from_uri = *my_new_from_uri; sub loadconfig { - my ($global_tapedev, $global_tpchanger, $defn_tpchanger) = @_; + my ($global_tapedev, $global_tpchanger, $defn_tpchanger, $custom_defn) = @_; $testconf = Installcheck::Config->new(); @@ -410,6 +551,11 @@ sub loadconfig { ]); } + if (defined($custom_defn)) { + $testconf->add_changer("customchanger", $custom_defn); + $testconf->add_param('tpchanger', '"customchanger"'); + } + $testconf->write(); my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF'); @@ -420,66 +566,150 @@ sub loadconfig { } sub assert_invalid { - my ($global_tapedev, $global_tpchanger, $defn_tpchanger, $name, $msg) = @_; - loadconfig($global_tapedev, $global_tpchanger, $defn_tpchanger); - eval { Amanda::Changer->new($name); }; - ok($@, $msg); + my ($global_tapedev, $global_tpchanger, $defn_tpchanger, $custom_defn, + $name, $regexp, $msg) = @_; + loadconfig($global_tapedev, $global_tpchanger, $defn_tpchanger, $custom_defn); + my $err = Amanda::Changer->new($name); + if ($err->isa("Amanda::Changer::Error")) { + like($err->{'message'}, $regexp, $msg); + } else { + diag("Amanda::Changer->new did not return an Error object:"); + diag("".Dumper($err)); + fail($msg); + } } -assert_invalid(undef, undef, undef, undef, +assert_invalid(undef, undef, undef, undef, undef, + qr/You must specify one of 'tapedev' or 'tpchanger'/, "supplying a nothing is invalid"); -loadconfig(undef, "file:/foo", undef); +loadconfig(undef, "file:/foo", undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-single:file:/foo", undef ], "default changer with global tpchanger naming a device"); -loadconfig(undef, "chg-disk:/foo", undef); +loadconfig(undef, "chg-disk:/foo", undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-disk:/foo", undef ], "default changer with global tpchanger naming a changer"); -loadconfig(undef, "mychanger", "chg-disk:/bar"); +loadconfig(undef, "mychanger", "chg-disk:/bar", undef); is_deeply( Amanda::Changer->new(), [ "chg-disk:/bar", "cc" ], "default changer with global tpchanger naming a defined changer with a uri"); -loadconfig(undef, "mychanger", "chg-zd-mtx"); +loadconfig(undef, "mychanger", "chg-zd-mtx", undef); is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", "cc" ], "default changer with global tpchanger naming a defined changer with a compat script"); -loadconfig(undef, "chg-zd-mtx", undef); +loadconfig(undef, "chg-zd-mtx", undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", undef ], "default changer with global tpchanger naming a compat script"); -loadconfig("tape:/dev/foo", undef, undef); +loadconfig("tape:/dev/foo", undef, undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-single:tape:/dev/foo", undef ], "default changer with global tapedev naming a device and no tpchanger"); -assert_invalid("tape:/dev/foo", "tape:/dev/foo", undef, undef, +assert_invalid("tape:/dev/foo", "tape:/dev/foo", undef, undef, undef, + qr/Cannot specify both 'tapedev' and 'tpchanger'/, "supplying a device for both tpchanger and tapedev is invalid"); -assert_invalid("tape:/dev/foo", "chg-disk:/foo", undef, undef, +assert_invalid("tape:/dev/foo", "chg-disk:/foo", undef, undef, undef, + qr/Cannot specify both 'tapedev' and 'tpchanger'/, "supplying a device for tapedev and a changer for tpchanger is invalid"); -loadconfig("tape:/dev/foo", 'chg-zd-mtx', undef); +loadconfig("tape:/dev/foo", 'chg-zd-mtx', undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", undef ], "default changer with global tapedev naming a device and a global tpchanger naming a compat script"); -assert_invalid("chg-disk:/foo", "tape:/dev/foo", undef, undef, +assert_invalid("chg-disk:/foo", "tape:/dev/foo", undef, undef, undef, + qr/Cannot specify both 'tapedev' and 'tpchanger'/, "supplying a changer for tapedev and a device for tpchanger is invalid"); -loadconfig("chg-disk:/foo", undef, undef); +loadconfig("chg-disk:/foo", undef, undef, undef); is_deeply( Amanda::Changer->new(), [ "chg-disk:/foo", undef ], "default changer with global tapedev naming a device"); -loadconfig("mychanger", undef, "chg-disk:/bar"); +loadconfig("mychanger", undef, "chg-disk:/bar", undef); is_deeply( Amanda::Changer->new(), [ "chg-disk:/bar", "cc" ], "default changer with global tapedev naming a defined changer with a uri"); -loadconfig("mychanger", undef, "chg-zd-mtx"); +loadconfig("mychanger", undef, "chg-zd-mtx", undef); is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", "cc" ], "default changer with global tapedev naming a defined changer with a compat script"); -loadconfig(undef, undef, "chg-disk:/foo"); +loadconfig(undef, undef, "chg-disk:/foo", undef); is_deeply( Amanda::Changer->new("mychanger"), [ "chg-disk:/foo", "cc" ], "named changer loads the proper definition"); +loadconfig(undef, undef, undef, [ + tapedev => '"chg-disk:/foo"', +]); +is_deeply( Amanda::Changer->new(), [ "chg-disk:/foo", "cc" ], + "defined changer with tapedev loads the proper definition"); + +loadconfig(undef, undef, undef, [ + tpchanger => '"chg-disk:/bar"', +]); +is_deeply( Amanda::Changer->new(), [ "chg-disk:/bar", "cc" ], + "defined changer with tpchanger loads the proper definition"); + +assert_invalid(undef, undef, undef, [ + tpchanger => '"chg-disk:/bar"', + tapedev => '"file:/bar"', + ], undef, + qr/Cannot specify both 'tapedev' and 'tpchanger'/, + "supplying both a new tpchanger and tapedev in a definition is invalid"); + +assert_invalid(undef, undef, undef, [ + property => '"this" "will not work"', + ], undef, + qr/You must specify one of 'tapedev' or 'tpchanger'/, + "supplying neither a tpchanger nor tapedev in a definition is invalid"); + *Amanda::Changer::_new_from_uri = *saved_new_from_uri; + +# test with_locked_state *within* a process + +sub test_locked_state { + my ($finished_cb) = @_; + my $chg; + my $stfile = "$Installcheck::TMP/test-statefile"; + my $num_outstanding = 0; + + my $steps = define_steps + cb_ref => \$finished_cb; + + step start => sub { + $chg = Amanda::Changer->new("chg-null:"); + + for my $num (qw( one two three )) { + ++$num_outstanding; + $chg->with_locked_state($stfile, $steps->{'maybe_done'}, sub { + my ($state, $maybe_done) = @_; + + $state->{$num} = $num; + $state->{'count'}++; + + Amanda::MainLoop::call_after(50, $maybe_done, undef, $state); + }); + } + }; + + step maybe_done => sub { + my ($err, $state) = @_; + die $err if $err; + + return if (--$num_outstanding); + + is_deeply($state, { + one => "one", + two => "two", + three => "three", + count => 3, + }, "state is maintained correctly (within a process)"); + + unlink($stfile) if -f $stfile; + + $finished_cb->(); + }; +} +test_locked_state(\&Amanda::MainLoop::quit); +Amanda::MainLoop::run();