X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=installcheck%2FAmanda_Changer_compat.pl;h=29c69525679ad53d5874eee78f6e47b15c16b418;hb=949b8910a5e23c4285d0b1aedacfc82a14dc97a5;hp=2480204b4aa712b05d34602303c94414a23b26dc;hpb=2627875b7d18858bc1f9f7652811e4d8c15a23eb;p=debian%2Famanda diff --git a/installcheck/Amanda_Changer_compat.pl b/installcheck/Amanda_Changer_compat.pl index 2480204..29c6952 100644 --- a/installcheck/Amanda_Changer_compat.pl +++ b/installcheck/Amanda_Changer_compat.pl @@ -1,4 +1,4 @@ -# Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved. +# Copyright (c) 2008-2012 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,31 +13,35 @@ # 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 => 17; +use Test::More tests => 31; use File::Path; use strict; use warnings; use lib "@amperldir@"; +use Installcheck; use Installcheck::Config; use Installcheck::Run; +use Installcheck::Changer; use Amanda::Paths; use Amanda::Device; -use Amanda::Debug; +use Amanda::Debug qw( :logging ); use Amanda::MainLoop; use Amanda::Config qw( :init :getconf config_dir_relative ); 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(); -my $changer_filename = "$AMANDA_TMPDIR/chg-test"; +my $changer_filename = "$Installcheck::TMP/chg-test"; +my $result_file = "$Installcheck::TMP/chg-test.result"; # Set up a 'test' changer; several of these are defined below. sub setup_changer { @@ -45,7 +49,7 @@ sub setup_changer { open my $chg_test, ">", $changer_filename or die("Could not create test changer"); - $changer_script =~ s/\$AMANDA_TMPDIR/$AMANDA_TMPDIR/g; + $changer_script =~ s/\$Installcheck::TMP/$Installcheck::TMP/g; print $chg_test "#! /bin/sh\n"; print $chg_test $changer_script; @@ -54,57 +58,87 @@ sub setup_changer { chmod 0755, $changer_filename; } +# slurp the $result_file +sub slurp_result { + return '' unless (-r $result_file); + + open(my $fh, "<", $result_file) or die("open $result_file: $!"); + my $result = do { local $/; <$fh> }; + close($fh); + + return $result; +} + # Functions to invoke the changer and later verify the result +my ($check_res_cb, $check_finished_cb); { - my $expected_err_re; + my $expected_err_info; my $expected_dev; my $msg; + my $quit; - sub check_res_cb { + $check_res_cb = make_cb('check_res_cb' => sub { my ($err, $res) = @_; - Amanda::MainLoop::quit(); if ($err) { - if (defined($expected_err_re)) { - like($err, $expected_err_re, $msg); + if (defined($expected_err_info)) { + chg_err_like($err, $expected_err_info, $msg); } else { fail($msg); - debug("Unexpected error: $err"); + diag("Unexpected error: $err"); } } else { if (defined($expected_dev)) { - is($res->{'device_name'}, $expected_dev, $msg); + is($res->{'device'}->device_name, $expected_dev, $msg); } else { fail($msg); diag("Unexpected reservation"); } } - } - sub check_finished_cb { - my ($err) = @_; - Amanda::MainLoop::quit(); + if ($res) { + $res->release(finished_cb => $quit); + } else { + $quit->(); + } + }); + + $check_finished_cb = make_cb('check_finished_cb' => sub { + my ($err, $res) = @_; if ($err) { - if (defined($expected_err_re)) { - like($err, $expected_err_re, $msg); + if (defined($expected_err_info)) { + chg_err_like($err, $expected_err_info, $msg); } else { fail($msg); diag("Unexpected error: $err"); } } else { - if (!defined($expected_err_re)) { + if (!defined($expected_err_info)) { pass($msg); } else { fail($msg); diag("Unexpected success"); } } - } + + if ($res) { + $res->release(finished_cb => $quit); + } else { + $quit->(); + } + }); + + $quit = make_cb(quit => sub { + my ($err) = @_; + die $err if $err; + + Amanda::MainLoop::quit(); + }); sub try_run_changer { my $sub; - ($sub, $expected_err_re, $expected_dev, $msg) = @_; + ($sub, $expected_err_info, $expected_dev, $msg) = @_; Amanda::MainLoop::call_later($sub); Amanda::MainLoop::run(); @@ -116,13 +150,23 @@ setup_changer <<'EOC'; case "${1}" in -slot) case "${2}" in - 1) echo "1 fake:1"; exit 0;; + 1) echo "1 null:fake1"; exit 0;; 2) echo " slot 2 is empty"; exit 1;; 3) echo "1"; exit 0;; # test missing 'device' portion + 4) echo "1 bogus:dev"; exit 0;; + 5) echo " multiline error"; echo "line 2"; exit 1;; + current) echo "1 null:current"; exit 0;; + next) echo "1 null:next"; exit 0;; esac;; - -reset) echo "reset ignored";; - -eject) echo "eject ignored";; - -clean) echo "clean ignored";; + -reset) + echo "reset" > $Installcheck::TMP/chg-test.result + echo "reset ignored";; + -eject) + echo "eject" > $Installcheck::TMP/chg-test.result + echo "eject ignored";; + -clean) + echo "clean" > $Installcheck::TMP/chg-test.result + echo "clean ignored";; -label) case "${2}" in foo?bar) echo "1 ok"; exit 0;; @@ -131,7 +175,8 @@ case "${1}" in -info) echo "7 10 1 1"; exit 0;; -search) case "${2}" in - TAPE?01) echo "5 fakedev"; exit 0;; + TAPE?01) echo "5 null:fakedev"; exit 0;; + fatal) echo " game over"; exit 2;; *) echo " not found"; exit 1;; esac;; esac @@ -150,36 +195,103 @@ if ($cfg_result != $CFGERR_OK) { } my $chg = Amanda::Changer->new(); +die($chg) if $chg->isa("Amanda::Changer::Error"); + +is($chg->have_inventory(), '', "changer have inventory"); + +try_run_changer( + sub { $chg->load(label => 'TAPE-01', res_cb => $check_res_cb); }, + undef, + "null:fakedev", + "search by label succeeds"); + +try_run_changer( + sub { $chg->load(label => 'TAPE-99', res_cb => $check_res_cb); }, + { message => "not found", type => 'failed', reason => 'notfound' }, + undef, + "search by label; nonexistent tape"); + +try_run_changer( + sub { $chg->load(slot => '1', res_cb => $check_res_cb); }, + undef, + "null:fake1", + "search by slot"); + +try_run_changer( + sub { $chg->load(slot => '2', res_cb => $check_res_cb); }, + { message => "slot 2 is empty", type => 'failed', reason => 'notfound' }, + undef, + "search by slot; empty slot"); + +try_run_changer( + sub { $chg->load(slot => '3', res_cb => $check_res_cb); }, + { message => "changer script did not provide a device name", type => 'fatal' }, + undef, + "search by slot; no device in response"); + +try_run_changer( + sub { $chg->load(slot => '1', res_cb => $check_res_cb); }, + { message => "changer script did not provide a device name", type => 'fatal' }, + undef, + "fatal error is sticky"); + +$chg->{'fatal_error'} = undef; # reset the fatal error + try_run_changer( - sub { $chg->load(label => 'TAPE-01', res_cb => \&check_res_cb); }, - undef, "fakedev", "search by label"); + sub { $chg->load(slot => '4', res_cb => $check_res_cb); }, + { message => "opening 'bogus:dev': Device type bogus is not known.", + type => 'failed', + reason => 'device' }, + undef, + "search by slot; bogus device leads to 'failed' error"); + +$chg->{'fatal_error'} = undef; # reset the fatal error + +try_run_changer( + sub { $chg->load(slot => '5', res_cb => $check_res_cb); }, + { message => "multiline error\nline 2", + type => 'failed', + reason => 'notfound' }, + undef, + "multiline error response captured in its entirety"); + +$chg->{'fatal_error'} = undef; # reset the fatal error try_run_changer( - sub { $chg->load(label => 'TAPE-99', res_cb => \&check_res_cb); }, - qr/^not found$/, undef, "search by label; nonexistent tape"); + sub { $chg->load(label => 'fatal', res_cb => $check_res_cb); }, + { message => "game over", type => 'fatal' }, + undef, + "search by label with fatal error"); + +# reset the fatal error +$chg->{'fatal_error'} = undef; try_run_changer( - sub { $chg->load(slot => '1', res_cb => \&check_res_cb); }, - undef, "fake:1", "search by slot"); + sub { $chg->eject(finished_cb => $check_finished_cb); }, + undef, undef, "chg->eject doesn't fail"); +like(slurp_result(), qr/eject/, ".. and calls chg-test -eject"); try_run_changer( - sub { $chg->load(slot => '2', res_cb => \&check_res_cb); }, - qr/^slot 2 is empty$/, undef, "search by slot; empty slot"); + sub { $chg->reset(finished_cb => $check_finished_cb); }, + undef, undef, "chg->reset doesn't fail"); +like(slurp_result(), qr/reset/, ".. and calls chg-test -reset"); -# TODO: what *should* happen here? -#try_run_changer( -# sub { $chg->load(slot => '3', res_cb => \&check_res_cb); }, -# undef, undef, "search by slot; invalid response"); +try_run_changer( + sub { $chg->clean(finished_cb => $check_finished_cb); }, + undef, undef, "chg->clean doesn't fail"); +like(slurp_result(), qr/clean/, ".. and calls chg-test -clean"); try_run_changer( - sub { $chg->reset(finished_cb => \&check_finished_cb); }, - undef, undef, "reset doesn't fail"); + sub { $chg->update(finished_cb => $check_finished_cb); }, + undef, undef, "chg->update doesn't fail"); try_run_changer( - sub { $chg->clean(finished_cb => \&check_finished_cb); }, - undef, undef, "clean doesn't fail"); + sub { $chg->inventory(inventory_cb => $check_finished_cb); }, + { message => "'chg-compat:' does not support inventory", + type => 'failed', reason => 'notimpl' }, + undef, + "inventory not implemented"); -# TODO test update() # make sure only one reservation can be held at once { @@ -187,11 +299,11 @@ try_run_changer( my ($load_1, $load_2, $check_load_2, $check_eject); - $load_1 = sub { + $load_1 = make_cb('load_1' => sub { $chg->load(slot => 1, res_cb => $load_2); - }; + }); - $load_2 = sub { + $load_2 = make_cb('load_2' => sub { my ($err, $res) = @_; die $err if ($err); @@ -199,33 +311,35 @@ try_run_changer( $first_res = $res; $chg->load(slot => 2, res_cb => $check_load_2); - }; + }); - $check_load_2 = sub { + $check_load_2 = make_cb('check_load_2' => sub { my ($err, $res) = @_; like($err, qr/Changer is already reserved/, "mulitple simultaneous reservations not alowed"); $first_res->release(eject => 1, finished_cb => $check_eject); - }; + }); - $check_eject = sub { + $check_eject = make_cb('check_eject' => sub { my ($err) = @_; ok(!defined $err, "release with eject succeeds"); + like(slurp_result(), qr/eject/, "..and calls chg-test -eject"); + Amanda::MainLoop::quit(); - }; + }); - Amanda::MainLoop::call_later($load_1); + $load_1->(); Amanda::MainLoop::run(); } ## check chg-disk # Installcheck::Run sets up the whole chg-disk thing for us -$testconf = Installcheck::Run->setup(); +$testconf = Installcheck::Run::setup(); $testconf->write(); $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF'); @@ -234,86 +348,197 @@ if ($cfg_result != $CFGERR_OK) { die(join "\n", @errors); } +$chg->quit(); $chg = Amanda::Changer->new(); +die($chg) if $chg->isa("Amanda::Changer::Error"); { + my $res; my ($get_info, $load_current, $label_current, $load_next, - $release_next, $load_by_label, $check_by_label); + $released1, $release_next, $load_by_label, $check_by_label); - $get_info = sub { - $chg->info(info_cb => $load_current, info => [ 'num_slots' ]); - }; + $get_info = make_cb('get_info' => sub { + $chg->info(info_cb => $load_current, info => [ 'num_slots', 'fast_search' ]); + }); - $load_current = sub { + $load_current = make_cb('load_current' => sub { my $err = shift; my %results = @_; die($err) if defined($err); - is($results{'num_slots'}, 15, "info() returns the correct num_slots"); + is_deeply({ %results }, + { num_slots => 3, fast_search => 0 }, # old chg-disk is not searchable + "info() returns the correct num_slots and fast_search"); $chg->load(slot => "1", res_cb => $label_current); - }; + }); - $label_current = sub { - my ($err, $res) = @_; + $label_current = make_cb('label_current' => sub { + (my $err, $res) = @_; die $err if ($err); pass("seek to current slot succeeded"); - my $dev = Amanda::Device->new($res->{'device_name'}); + my $dev = $res->{'device'}; $dev->start($Amanda::Device::ACCESS_WRITE, "TESTCONF18", undef) or die $dev->error_or_status(); $dev->finish() or die $dev->error_or_status(); is($res->{'this_slot'}, "1", "this slot is '1'"); - is($res->{'next_slot'}, "next", "next slot is 'next'"); $res->set_label(label => "TESTCONF18", finished_cb => $load_next); - }; + }); - $load_next = sub { + $load_next = make_cb('load_next' => sub { my ($err) = @_; die $err if ($err); pass("set_label succeeded"); - $chg->load(slot => "next", res_cb => $release_next); - }; + $res->release(finished_cb => $released1); + }); - $release_next = sub { - my ($err, $res) = @_; + $released1 = make_cb(released1 => sub { + my ($err) = @_; + die $err if $err; + + $chg->load(relative_slot => "next", res_cb => $release_next); + }); + + $release_next = make_cb('release_next' => sub { + (my $err, $res) = @_; die $err if ($err); - pass("load 'next' succeeded"); + pass("load relative slot 'next' succeeded"); $res->release(finished_cb => $load_by_label); - }; + }); - $load_by_label = sub { + $load_by_label = make_cb('load_by_label' => sub { my ($err) = @_; die $err if ($err); pass("release loaded"); $chg->load(label => "TESTCONF18", res_cb => $check_by_label); - }; + }); - $check_by_label = sub { - my ($err, $res) = @_; + $check_by_label = make_cb('check_by_label' => sub { + (my $err, $res) = @_; die $err if ($err); pass("load by label succeeded"); - my $dev = Amanda::Device->new($res->{'device_name'}); + my $dev = $res->{'device'}; $dev->read_label() == 0 or die $dev->error_or_status(); is($dev->volume_label(), "TESTCONF18", "..and finds the right volume"); - Amanda::MainLoop::quit(); - }; + $res->release(finished_cb => sub { + my ($err) = @_; + die $err if $err; + + Amanda::MainLoop::quit(); + }); + }); - Amanda::MainLoop::call_later($get_info); + $get_info->(); Amanda::MainLoop::run(); } +$chg->quit(); + +# test two simultaneous invocations of info() + +$chg = Amanda::Changer->new(); +die($chg) if $chg->isa("Amanda::Changer::Error"); + +sub test_get_infos { + my ($finished_cb) = @_; + my $n_info_results = 0; + + my $steps = define_steps + cb_ref => \$finished_cb; + + step get_infos => sub { + # convince the changer that it has not gotten any info yet + $chg->{'got_info'} = 0; + + $chg->info(info_cb => $steps->{'got_info_result'}, info => [ 'num_slots' ]); + $chg->info(info_cb => $steps->{'got_info_result'}, info => [ 'fast_search' ]); + }; + + step got_info_result => sub { + my ($err, %info) = @_; + die $err if $err; + ++$n_info_results; + if ($n_info_results >= 2) { + pass("two simultaneous info() invocations are successful"); + $finished_cb->(); + } + }; +} +test_get_infos(\&Amanda::MainLoop::quit); +Amanda::MainLoop::run(); + +# scan the changer using except_slots +sub test_except_slots { + my ($finished_cb) = @_; + my $slot; + my %except_slots; + + my $steps = define_steps + cb_ref => \$finished_cb; + + step start => sub { + $chg->load(relative_slot => "current", + except_slots => { %except_slots }, + res_cb => $steps->{'loaded'}); + }; + + step loaded => sub { + my ($err, $res) = @_; + if ($err) { + if ($err->notfound) { + # this means the scan is done + return $steps->{'quit'}->(); + } elsif ($err->volinuse and defined $err->{'slot'}) { + $slot = $err->{'slot'}; + } else { + die $err; + } + } else { + $slot = $res->{'this_slot'}; + } + + $except_slots{$slot} = 1; + + if ($res) { + $res->release(finished_cb => $steps->{'released'}); + } else { + $steps->{'released'}->(); + } + }; + + step released => sub { + my ($err) = @_; + die $err if $err; + + $chg->load(relative_slot => 'next', slot => $slot, + except_slots => { %except_slots }, + res_cb => $steps->{'loaded'}); + }; + + step quit => sub { + is_deeply({ %except_slots }, { 1=>1, 2=>1, 3=>1 }, + "scanning with except_slots works"); + $finished_cb->(); + }; +} +test_except_slots(\&Amanda::MainLoop::quit); +Amanda::MainLoop::run(); +$chg->quit(); + +unlink($changer_filename); +unlink($result_file);