1 # Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 use Test::More tests => 35;
23 use lib "@amperldir@";
24 use Installcheck::Config;
29 use Amanda::Config qw( :init :getconf config_dir_relative );
32 # set up debugging so debug output doesn't interfere with test results
33 Amanda::Debug::dbopen("installcheck");
35 # and disable Debug's die() and warn() overrides
36 Amanda::Debug::disable_die_override();
39 # define a "test" changer for purposes of this installcheck
41 package Amanda::Changer::test;
43 @ISA = qw( Amanda::Changer );
45 # monkey-patch our test changer into Amanda::Changer, and indicate that
46 # the module has already been required by adding a key to %INC
47 $INC{'Amanda/Changer/test.pm'} = "Amanda_Changer";
51 my ($cc, $tpchanger) = @_;
55 slots => [ 'TAPE-00', 'TAPE-01', 'TAPE-02', 'TAPE-03' ],
59 bless ($self, $class);
67 my $cb = $params{'res_cb'};
69 if (exists $params{'label'}) {
72 my $label = $params{'label'};
74 for my $i (0 .. $#{$self->{'slots'}}) {
75 if ($self->{'slots'}->[$i] eq $label) {
81 $cb->("No such label '$label'", undef);
85 # check that it's not in use
86 for my $used_slot (@{$self->{'reserved_slots'}}) {
87 if ($used_slot == $slot) {
88 $cb->("Volume with label '$label' is already in use", undef);
94 push @{$self->{'reserved_slots'}}, $slot;
96 if (exists $params{'set_current'} && $params{'set_current'}) {
97 $self->{'curslot'} = $slot;
100 $cb->(undef, Amanda::Changer::test::Reservation->new($self, $slot, $label));
101 } elsif (exists $params{'slot'}) {
102 my $slot = $params{'slot'};
103 $slot = $self->{'curslot'}
104 if ($slot eq "current");
106 if (grep { $_ == $slot } @{$self->{'reserved_slots'}}) {
107 $cb->("Slot $slot is already in use", undef);
110 my $label = $self->{'slots'}->[$slot];
111 push @{$self->{'reserved_slots'}}, $slot;
113 if (exists $params{'set_current'} && $params{'set_current'}) {
114 $self->{'curslot'} = $slot;
117 $cb->(undef, Amanda::Changer::test::Reservation->new($self, $slot, $label));
119 die "No label or slot parameter given";
127 $self->{'curslot'} = 0;
129 if (exists $params{'finished_cb'}) {
130 Amanda::MainLoop::call_later($params{'finished_cb'}, undef);
138 $self->{'clean'} = 1;
140 if (exists $params{'finished_cb'}) {
141 Amanda::MainLoop::call_later($params{'finished_cb'}, undef);
146 package Amanda::Changer::test::Reservation;
148 @ISA = qw( Amanda::Changer::Reservation );
152 my ($chg, $slot, $label) = @_;
153 my $self = Amanda::Changer::Reservation::new($class);
155 $self->{'chg'} = $chg;
156 $self->{'slot'} = $slot;
157 $self->{'label'} = $label;
159 $self->{'device_name'} = "test:slot-$slot";
160 $self->{'this_slot'} = $slot;
161 $self->{'next_slot'} = ($slot + 1) % (scalar @{$chg->{'slots'}});
169 my $slot = $self->{'slot'};
170 my $chg = $self->{'chg'};
172 $chg->{'reserved_slots'} = [ grep { $_ != $slot } @{$chg->{'reserved_slots'}} ];
174 if (exists $params{'finished_cb'}) {
175 Amanda::MainLoop::call_later($params{'finished_cb'}, undef);
182 my $slot = $self->{'slot'};
183 my $chg = $self->{'chg'};
185 $self->{'chg'}->{'slots'}->[$self->{'slot'}] = $params{'label'};
186 $self->{'label'} = $params{'label'};
188 if (exists $params{'finished_cb'}) {
189 Amanda::MainLoop::call_later($params{'finished_cb'}, undef);
194 # back to the perl tests..
198 # work against a config specifying our test changer, to work out the kinks
199 # when it opens devices to check their labels
201 $testconf = Installcheck::Config->new();
202 $testconf->add_changer("mychanger", [
203 'tpchanger' => '"chg-test:/foo"',
207 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
208 if ($cfg_result != $CFGERR_OK) {
209 my ($level, @errors) = Amanda::Config::config_errors();
210 die(join "\n", @errors);
213 # test loading by label
215 my $chg = Amanda::Changer->new("mychanger");
217 my @labels = ( 'TAPE-02', 'TAPE-00', 'TAPE-03' );
218 my @reservations = ();
222 my $label = pop @labels;
224 $chg->load(label => $label,
225 set_current => ($label eq "TAPE-02"),
227 my ($err, $reservation) = @_;
228 ok(!$err, "no error loading $label")
231 # keep this reservation
233 push @reservations, $reservation;
236 # and start on the next
241 # try to load an already-reserved volume
242 $chg->load(label => 'TAPE-00',
244 my ($err, $reservation) = @_;
245 ok($err, "error when requesting already-reserved volume");
246 Amanda::MainLoop::quit();
253 Amanda::MainLoop::call_later($getres);
254 Amanda::MainLoop::run();
256 # ditch the reservations and do it all again
258 @labels = ( 'TAPE-00', 'TAPE-01' );
259 is_deeply($chg->{'reserved_slots'}, [],
260 "reservations are released when the Reservation object goes out of scope");
261 Amanda::MainLoop::call_later($getres);
262 Amanda::MainLoop::run();
264 # explicitly release the reservations (without using the callback)
265 for my $res (@reservations) {
270 # test loading by slot
272 my ($start, $first_cb, $second_cb);
274 # reserves the current slot
276 $chg->load(res_cb => $first_cb, slot => "current");
279 # gets a reservation for the "current" slot
281 my ($err, $res) = @_;
284 is($res->{'this_slot'}, 2,
285 "'current' slot loads slot 2");
286 is($res->{'device_name'}, "test:slot-2",
287 "..device is correct");
288 is($res->{'next_slot'}, 3,
289 "..and the next slot is slot 3");
290 $chg->load(res_cb => $second_cb, slot => $res->{'next_slot'}, set_current => 1);
293 # gets a reservation for the "next" slot
295 my ($err, $res) = @_;
298 is($res->{'this_slot'}, 3,
299 "next slot loads slot 3");
300 is($chg->{'curslot'}, 3,
301 "..which is also now the current slot");
302 is($res->{'next_slot'}, 0,
303 "..and the next slot is slot 0");
305 Amanda::MainLoop::quit();
308 Amanda::MainLoop::call_later($start);
309 Amanda::MainLoop::run();
314 my ($start, $load1_cb, $set_cb, $load2_cb, $load3_cb);
318 $chg->load(res_cb => $load1_cb, label => "TAPE-00");
321 # rename it to TAPE-99
323 my ($err, $res) = @_;
326 pass("loaded TAPE-00");
327 $res->set_label(label => "TAPE-99", finished_cb => $set_cb);
331 # try to load TAPE-00
336 pass("relabeled TAPE-00 to TAPE-99");
337 $chg->load(res_cb => $load2_cb, label => "TAPE-00");
340 # try to load TAPE-99
342 my ($err, $res) = @_;
344 ok($err, "loading TAPE-00 is now an error");
345 $chg->load(res_cb => $load3_cb, label => "TAPE-99");
350 my ($err, $res) = @_;
353 pass("but loading TAPE-99 is ok");
355 Amanda::MainLoop::quit();
358 Amanda::MainLoop::call_later($start);
359 Amanda::MainLoop::run();
362 # test reset and clean
364 my ($do_reset, $do_clean);
367 $chg->reset(finished_cb => sub {
368 is($chg->{'curslot'}, 0,
369 "reset() resets to slot 0");
375 $chg->clean(finished_cb => sub {
376 ok($chg->{'clean'}, "clean 'cleaned' the changer");
377 Amanda::MainLoop::quit();
381 Amanda::MainLoop::call_later($do_reset);
382 Amanda::MainLoop::run();
385 # Test the various permutations of configuration setup, with a patched
386 # _new_from_uri so we can monitor the result
387 sub my_new_from_uri {
388 my ($uri, $cc, $name) = @_;
389 return [ $uri, $cc? "cc" : undef ];
391 *saved_new_from_uri = *Amanda::Changer::_new_from_uri;
392 *Amanda::Changer::_new_from_uri = *my_new_from_uri;
395 my ($global_tapedev, $global_tpchanger, $defn_tpchanger) = @_;
397 $testconf = Installcheck::Config->new();
399 if (defined($global_tapedev)) {
400 $testconf->add_param('tapedev', "\"$global_tapedev\"")
403 if (defined($global_tpchanger)) {
404 $testconf->add_param('tpchanger', "\"$global_tpchanger\"")
407 if (defined($defn_tpchanger)) {
408 $testconf->add_changer("mychanger", [
409 'tpchanger' => "\"$defn_tpchanger\"",
415 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
416 if ($cfg_result != $CFGERR_OK) {
417 my ($level, @errors) = Amanda::Config::config_errors();
418 die(join "\n", @errors);
423 my ($global_tapedev, $global_tpchanger, $defn_tpchanger, $name, $msg) = @_;
424 loadconfig($global_tapedev, $global_tpchanger, $defn_tpchanger);
425 eval { Amanda::Changer->new($name); };
429 assert_invalid(undef, undef, undef, undef,
430 "supplying a nothing is invalid");
432 loadconfig(undef, "file:/foo", undef);
433 is_deeply( Amanda::Changer->new(), [ "chg-single:file:/foo", undef ],
434 "default changer with global tpchanger naming a device");
436 loadconfig(undef, "chg-disk:/foo", undef);
437 is_deeply( Amanda::Changer->new(), [ "chg-disk:/foo", undef ],
438 "default changer with global tpchanger naming a changer");
440 loadconfig(undef, "mychanger", "chg-disk:/bar");
441 is_deeply( Amanda::Changer->new(), [ "chg-disk:/bar", "cc" ],
442 "default changer with global tpchanger naming a defined changer with a uri");
444 loadconfig(undef, "mychanger", "chg-zd-mtx");
445 is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", "cc" ],
446 "default changer with global tpchanger naming a defined changer with a compat script");
448 loadconfig(undef, "chg-zd-mtx", undef);
449 is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", undef ],
450 "default changer with global tpchanger naming a compat script");
452 loadconfig("tape:/dev/foo", undef, undef);
453 is_deeply( Amanda::Changer->new(), [ "chg-single:tape:/dev/foo", undef ],
454 "default changer with global tapedev naming a device and no tpchanger");
456 assert_invalid("tape:/dev/foo", "tape:/dev/foo", undef, undef,
457 "supplying a device for both tpchanger and tapedev is invalid");
459 assert_invalid("tape:/dev/foo", "chg-disk:/foo", undef, undef,
460 "supplying a device for tapedev and a changer for tpchanger is invalid");
462 loadconfig("tape:/dev/foo", 'chg-zd-mtx', undef);
463 is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", undef ],
464 "default changer with global tapedev naming a device and a global tpchanger naming a compat script");
466 assert_invalid("chg-disk:/foo", "tape:/dev/foo", undef, undef,
467 "supplying a changer for tapedev and a device for tpchanger is invalid");
469 loadconfig("chg-disk:/foo", undef, undef);
470 is_deeply( Amanda::Changer->new(), [ "chg-disk:/foo", undef ],
471 "default changer with global tapedev naming a device");
473 loadconfig("mychanger", undef, "chg-disk:/bar");
474 is_deeply( Amanda::Changer->new(), [ "chg-disk:/bar", "cc" ],
475 "default changer with global tapedev naming a defined changer with a uri");
477 loadconfig("mychanger", undef, "chg-zd-mtx");
478 is_deeply( Amanda::Changer->new(), [ "chg-compat:chg-zd-mtx", "cc" ],
479 "default changer with global tapedev naming a defined changer with a compat script");
481 loadconfig(undef, undef, "chg-disk:/foo");
482 is_deeply( Amanda::Changer->new("mychanger"), [ "chg-disk:/foo", "cc" ],
483 "named changer loads the proper definition");
485 *Amanda::Changer::_new_from_uri = *saved_new_from_uri;