Imported Upstream version 3.1.0
[debian/amanda] / installcheck / Amanda_Changer_compat.pl
index 6e75f7c1acf446ce957fae6a3a2c33d3e26bd572..5d786d0a2d60894fc92f26338e3bbc43af467180 100644 (file)
@@ -1,4 +1,4 @@
-# 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 => 22;
+use Test::More tests => 30;
 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 $result_file = "$AMANDA_TMPDIR/chg-test.result";
+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 {
@@ -46,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;
@@ -67,56 +70,75 @@ sub slurp_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();
@@ -128,18 +150,22 @@ 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 "<ignored> slot 2 is empty"; exit 1;;
             3) echo "1"; exit 0;; # test missing 'device' portion
+           4) echo "1 bogus:dev"; exit 0;;
+           5) echo "<error> 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" > @AMANDA_TMPDIR@/chg-test.result
+       echo "reset" > $Installcheck::TMP/chg-test.result
        echo "reset ignored";;
     -eject)
-       echo "eject" > @AMANDA_TMPDIR@/chg-test.result
+       echo "eject" > $Installcheck::TMP/chg-test.result
        echo "eject ignored";;
     -clean)
-       echo "clean" > @AMANDA_TMPDIR@/chg-test.result
+       echo "clean" > $Installcheck::TMP/chg-test.result
        echo "clean ignored";;
     -label)
         case "${2}" in
@@ -149,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 "<error> game over"; exit 2;;
             *) echo "<error> not found"; exit 1;;
         esac;;
 esac
@@ -168,43 +195,101 @@ if ($cfg_result != $CFGERR_OK) {
 }
 
 my $chg = Amanda::Changer->new();
+die($chg) if $chg->isa("Amanda::Changer::Error");
+
+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(label => 'TAPE-01', res_cb => \&check_res_cb); },
-    undef, "fakedev", "search by label");
+    sub { $chg->load(slot => '1', res_cb => $check_res_cb); },
+    undef,
+    "null:fake1",
+    "search by slot");
 
 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(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 => '1', res_cb => \&check_res_cb); },
-    undef, "fake:1", "search by slot");
+    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 => '2', res_cb => \&check_res_cb); },
-    qr/^slot 2 is empty$/, undef, "search by slot; empty slot");
+    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");
 
-# 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");
+$chg->{'fatal_error'} = undef; # reset the fatal error
 
 try_run_changer(
-    sub { $chg->eject(finished_cb => \&check_finished_cb); },
+    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 => '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->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->reset(finished_cb => \&check_finished_cb); },
+    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");
 
 try_run_changer(
-    sub { $chg->clean(finished_cb => \&check_finished_cb); },
+    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");
 
-# TODO test update()
+try_run_changer(
+    sub { $chg->update(finished_cb => $check_finished_cb); },
+    undef, undef, "chg->update doesn't fail");
+
+try_run_changer(
+    sub { $chg->inventory(inventory_cb => $check_finished_cb); },
+    { message => "'chg-compat:' does not support inventory",
+           type => 'failed', reason => 'notimpl' },
+    undef,
+    "inventory not implemented");
+
 
 # make sure only one reservation can be held at once
 {
@@ -212,11 +297,11 @@ like(slurp_result(), qr/clean/, ".. and calls chg-test -clean");
 
     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);
 
@@ -224,18 +309,18 @@ like(slurp_result(), qr/clean/, ".. and calls chg-test -clean");
         $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");
@@ -243,9 +328,9 @@ like(slurp_result(), qr/clean/, ".. and calls chg-test -clean");
        like(slurp_result(), qr/eject/, "..and calls chg-test -eject");
 
         Amanda::MainLoop::quit();
-    };
+    });
 
-    Amanda::MainLoop::call_later($load_1);
+    $load_1->();
     Amanda::MainLoop::run();
 }
 
@@ -262,85 +347,193 @@ if ($cfg_result != $CFGERR_OK) {
 }
 
 $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::call_later($get_info);
+           Amanda::MainLoop::quit();
+       });
+    });
+
+    $get_info->();
     Amanda::MainLoop::run();
 }
+
+# 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();
+
+unlink($changer_filename);
+unlink($result_file);