1 # Copyright (c) 2009, 2010 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. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 use Test::More tests => 321;
24 use lib "@amperldir@";
26 use Installcheck::Config;
27 use Installcheck::Changer;
28 use Installcheck::Mock qw( setup_mock_mtx $mock_mtx_path );
29 use Amanda::Device qw( :constants );
33 use Amanda::Config qw( :init :getconf config_dir_relative );
36 # set up debugging so debug output doesn't interfere with test results
37 Amanda::Debug::dbopen("installcheck");
39 # and disable Debug's die() and warn() overrides
40 Amanda::Debug::disable_die_override();
41 Installcheck::log_test_output();
43 my $chg_state_file = "$Installcheck::TMP/chg-robot-state";
44 unlink($chg_state_file) if -f $chg_state_file;
46 my $mtx_state_file = setup_mock_mtx (
65 my ($chg, $barcodes, $next_step, $expected, $msg) = @_;
67 $chg->inventory(inventory_cb => make_cb(sub {
71 # strip barcodes from both $expected and $inv
73 for (@$expected, @$inv) {
74 delete $_->{'barcode'};
78 is_deeply($inv, $expected, $msg)
79 or diag("Got:\n" . Dumper($inv));
86 # test the "interface" package
89 my ($finished_cb) = @_;
90 my ($interface, $chg);
92 my $steps = define_steps
93 cb_ref => \$finished_cb;
96 my $testconf = Installcheck::Config->new();
97 $testconf->add_changer('robo', [
98 tpchanger => "\"chg-robot:$mtx_state_file\"",
99 changerfile => "\"$chg_state_file\"",
101 # point to the two vtape "drives" that mock/mtx will set up
102 property => "\"tape-device\" \"0=null:drive0\"",
104 # an point to the mock mtx
105 property => "\"mtx\" \"$mock_mtx_path\"",
109 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
110 if ($cfg_result != $CFGERR_OK) {
111 my ($level, @errors) = Amanda::Config::config_errors();
112 die(join "\n", @errors);
115 $chg = Amanda::Changer->new("robo");
116 die "$chg" if $chg->isa("Amanda::Changer::Error");
117 $interface = $chg->{'interface'};
119 $interface->inquiry($steps->{'inquiry_cb'});
122 step inquiry_cb => sub {
123 my ($error, $info) = @_;
125 die $error if $error;
128 'revision' => '0416',
129 'product id' => 'SSL2000 Series',
130 'attached changer' => 'No',
131 'vendor id' => 'COMPAQ',
132 'product type' => 'Medium Changer'
133 }, "robot::Interface inquiry() info is correct");
135 $steps->{'status1'}->();
138 step status1 => sub {
139 $interface->status(sub {
140 my ($error, $status) = @_;
142 die $error if $error;
150 1 => { 'barcode' => '11111', ie => 0 },
151 2 => { 'barcode' => '22222', ie => 0 },
152 3 => { 'barcode' => '33333', ie => 0 },
153 4 => { 'barcode' => '44444', ie => 0 },
154 5 => { empty => 1, ie => 0 },
155 6 => { empty => 1, ie => 1 },
157 }, "robot::Interface status() output is correct (no drives loaded)");
158 $steps->{'load0'}->();
163 $interface->load(2, 0, sub {
166 die $error if $error;
169 $steps->{'status2'}->();
173 step status2 => sub {
174 $interface->status(sub {
175 my ($error, $status) = @_;
177 die $error if $error;
181 0 => { barcode => '22222', 'orig_slot' => 2 },
185 1 => { 'barcode' => '11111', ie => 0 },
186 2 => { empty => 1, ie => 0 },
187 3 => { 'barcode' => '33333', ie => 0 },
188 4 => { 'barcode' => '44444', ie => 0 },
189 5 => { empty => 1, ie => 0 },
190 6 => { empty => 1, ie => 1 },
192 }, "robot::Interface status() output is correct (one drive loaded)");
194 $steps->{'load1'}->();
199 $interface->load(4, 1, sub {
202 die $error if $error;
205 $steps->{'status3'}->();
209 step status3 => sub {
210 $interface->status(sub {
211 my ($error, $status) = @_;
213 die $error if $error;
217 0 => { barcode => '22222', 'orig_slot' => 2 },
218 1 => { barcode => '44444', 'orig_slot' => 4 },
221 1 => { 'barcode' => '11111', ie => 0 },
222 2 => { empty => 1, ie => 0 },
223 3 => { 'barcode' => '33333', ie => 0 },
224 4 => { empty => 1, ie => 0 },
225 5 => { empty => 1, ie => 0 },
226 6 => { empty => 1, ie => 1 },
228 }, "robot::Interface status() output is correct (two drives loaded)");
230 $steps->{'transfer'}->();
234 step transfer => sub {
235 $interface->transfer(3, 6, sub {
238 die $error if $error;
241 $steps->{'status4'}->();
245 step status4 => sub {
246 $interface->status(sub {
247 my ($error, $status) = @_;
249 die $error if $error;
253 0 => { barcode => '22222', 'orig_slot' => 2 },
254 1 => { barcode => '44444', 'orig_slot' => 4 },
257 1 => { 'barcode' => '11111', ie => 0 },
258 2 => { empty => 1, ie => 0 },
259 3 => { empty => 1, ie => 0 },
260 4 => { empty => 1, ie => 0 },
261 5 => { empty => 1, ie => 0 },
262 6 => { 'barcode' => '33333', ie => 1 },
264 }, "robot::Interface status() output is correct after transfer");
270 test_interface(\&Amanda::MainLoop::quit);
271 Amanda::MainLoop::run();
274 my $testconf = Installcheck::Config->new();
275 $testconf->add_changer('bum-scsi-dev', [
276 tpchanger => "\"chg-robot:does/not/exist\"",
277 property => "\"tape-device\" \"0=null:foo\"",
278 changerfile => "\"$chg_state_file\"",
280 $testconf->add_changer('no-tape-device', [
281 tpchanger => "\"chg-robot:$mtx_state_file\"",
282 changerfile => "\"$chg_state_file\"",
284 $testconf->add_changer('bad-property', [
285 tpchanger => "\"chg-robot:$mtx_state_file\"",
286 changerfile => "\"$chg_state_file\"",
287 property => "\"fast-search\" \"maybe\"",
288 property => "\"tape-device\" \"0=null:foo\"",
290 $testconf->add_changer('no-fast-search', [
291 tpchanger => "\"chg-robot:$mtx_state_file\"",
292 changerfile => "\"$chg_state_file\"",
293 property => "\"use-slots\" \"1-3,9\"",
294 property => "append \"use-slots\" \"8,5-6\"",
295 property => "\"fast-search\" \"no\"",
296 property => "\"tape-device\" \"0=null:foo\"",
298 $testconf->add_changer('delays', [
299 tpchanger => "\"chg-robot:$mtx_state_file\"",
300 # no changerfile property
301 property => "\"tape-device\" \"0=null:foo\"",
302 property => "\"status-interval\" \"1m\"",
303 property => "\"eject-delay\" \"1s\"",
304 property => "\"unload-delay\" \"2M\"",
305 property => "\"load-poll\" \"2s POLl 3s uNtil 1m\"",
310 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
311 if ($cfg_result != $CFGERR_OK) {
312 my ($level, @errors) = Amanda::Config::config_errors();
313 die(join "\n", @errors);
316 # test the changer constructor and properties
317 my $err = Amanda::Changer->new("bum-scsi-dev");
319 { message => "'does/not/exist' not found",
321 "check for device existence works");
323 $err = Amanda::Changer->new("no-tape-device");
325 { message => "no 'tape-device' property specified",
327 "tape-device property is required");
329 $err = Amanda::Changer->new("bad-property");
331 { message => "invalid 'fast-search' value",
333 "invalid boolean value handled correctly");
335 my $chg = Amanda::Changer->new("delays");
336 die "$chg" if $chg->isa("Amanda::Changer::Error");
337 is($chg->{'status_interval'}, 60, "status-interval parsed");
338 is($chg->{'eject_delay'}, 1, "eject-delay parsed");
339 is($chg->{'unload_delay'}, 120, "unload-delay parsed");
340 is_deeply($chg->{'load_poll'}, [ 2, 3, 60 ], "load-poll parsed");
342 # check out the statefile filename generation
343 my $dashed_mtx_state_file = $mtx_state_file;
344 $dashed_mtx_state_file =~ tr/a-zA-Z0-9/-/cs;
345 $dashed_mtx_state_file =~ s/^-*//;
346 is($chg->{'statefile'}, "$localstatedir/amanda/chg-robot-$dashed_mtx_state_file",
347 "statefile calculated correctly");
349 # test no-fast-search
350 $chg = Amanda::Changer->new("no-fast-search");
351 die "$chg" if $chg->isa("Amanda::Changer::Error");
353 info => ['fast_search'],
354 info_cb => make_cb(info_cb => sub {
355 my ($err, %info) = @_;
356 ok(!$info{'fast_search'}, "fast-search property works");
357 Amanda::MainLoop::quit();
359 Amanda::MainLoop::run();
362 my @allowed = map { $chg->_is_slot_allowed($_) } (0 .. 10);
363 is_deeply([ @allowed ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0 ],
364 "_is_slot_allowed parses multiple properties and behaves as expected");
371 my ($mtx_config, $finished_cb) = @_;
373 my ($res1, $res2, $mtx_state_file);
374 my $pfx = "BC=$mtx_config->{barcodes}; TORIG=$mtx_config->{track_orig}";
375 my $vtape_root = "$Installcheck::TMP/chg-robot-vtapes";
377 my $steps = define_steps
378 cb_ref => \$finished_cb;
382 unlink($chg_state_file) if -f $chg_state_file;
383 %Amanda::Changer::changers_by_uri_cc = ();
390 $mtx_state_file = setup_mock_mtx (
401 6 => '66666', # slot 6 is full, but not in use-slots
406 vtape_root => $vtape_root,
409 my @ignore_barcodes = ( property => "\"ignore-barcodes\" \"y\"")
410 if ($mtx_config->{'barcodes'} == -1);
412 my $testconf = Installcheck::Config->new();
413 $testconf->add_changer('robo', [
414 tpchanger => "\"chg-robot:$mtx_state_file\"",
415 changerfile => "\"$chg_state_file\"",
417 # point to the two vtape "drives" that mock/mtx will set up
418 property => "\"tape-device\" \"0=file:$vtape_root/drive0\"",
419 property => "append \"tape-device\" \"1=file:$vtape_root/drive1\"",
420 property => "\"use-slots\" \"1-5\"",
421 property => "\"mtx\" \"$mock_mtx_path\"",
427 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
428 if ($cfg_result != $CFGERR_OK) {
429 my ($level, @errors) = Amanda::Config::config_errors();
430 die(join "\n", @errors);
433 $steps->{'start'}->();
437 $chg = Amanda::Changer->new("robo");
438 ok(!$chg->isa("Amanda::Changer::Error"),
439 "$pfx: Create working chg-robot instance")
440 or die("no sense going on: $chg");
442 $chg->info(info => [qw(vendor_string num_slots fast_search)], info_cb => $steps->{'info_cb'});
445 step info_cb => sub {
446 my ($err, %info) = @_;
449 is_deeply({ %info }, {
452 vendor_string => "COMPAQ SSL2000 Series",
453 }, "$pfx: info keys num_slots, fast_search, vendor_string are correct");
455 $steps->{'inventory1'}->();
458 step inventory1 => sub {
459 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_1'}, [
460 { slot => 1, state => Amanda::Changer::SLOT_FULL,
461 barcode => '11111', current => 1,
462 device_status => undef, f_type => undef, label => undef },
463 { slot => 2, state => Amanda::Changer::SLOT_FULL,
465 device_status => undef, f_type => undef, label => undef },
466 { slot => 3, state => Amanda::Changer::SLOT_FULL,
468 device_status => undef, f_type => undef, label => undef },
469 { slot => 4, state => Amanda::Changer::SLOT_FULL,
471 device_status => undef, f_type => undef, label => undef },
472 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
473 device_status => undef, f_type => undef, label => undef },
474 ], "$pfx: inventory is correct on start-up");
477 step load_slot_1 => sub {
478 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'});
481 step loaded_slot_1 => sub {
482 (my $err, $res1) = @_;
485 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
486 "$pfx: first load returns drive-0 device");
489 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
490 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
494 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct");
496 $steps->{'load_slot_2'}->();
499 step load_slot_2 => sub {
500 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'});
503 step loaded_slot_2 => sub {
504 (my $err, $res2) = @_;
507 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
508 "$pfx: second load returns drive-1 device");
511 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
512 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
516 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct");
519 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
520 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
524 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
526 $steps->{'check_loads'}->();
529 step check_loads => sub {
530 # peek into the interface to check that things are loaded correctly
531 $chg->{'interface'}->status(sub {
532 my ($error, $status) = @_;
534 die $error if $error;
536 # only perform these checks when barcodes are enabled
537 if ($mtx_config->{'barcodes'} > 0) {
538 is_deeply($status->{'drives'}, {
539 0 => { barcode => '11111', 'orig_slot' => 1 },
540 1 => { barcode => '22222', 'orig_slot' => 2 },
541 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded");
544 $steps->{'inventory2'}->();
548 step inventory2 => sub {
549 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [
550 { slot => 1, state => Amanda::Changer::SLOT_FULL,
551 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
552 device_status => undef, f_type => undef, label => undef },
553 { slot => 2, state => Amanda::Changer::SLOT_FULL,
554 barcode => '22222', reserved => 1, loaded_in => 1,
555 device_status => undef, f_type => undef, label => undef },
556 { slot => 3, state => Amanda::Changer::SLOT_FULL,
558 device_status => undef, f_type => undef, label => undef },
559 { slot => 4, state => Amanda::Changer::SLOT_FULL,
561 device_status => undef, f_type => undef, label => undef },
562 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
563 device_status => undef, f_type => undef, label => undef },
564 ], "$pfx: inventory is updated when slots are loaded");
567 step load_slot_3 => sub {
568 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'});
571 step loaded_slot_3 => sub {
572 my ($err, $no_res) = @_;
575 { message => "no drives available",
576 reason => 'driveinuse',
578 "$pfx: trying to load a third slot fails with 'no drives available'");
580 $steps->{'label_tape_1'}->();
583 step label_tape_1 => sub {
584 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef);
585 $res1->{'device'}->finish();
587 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'});
590 step label_tape_2 => sub {
594 pass("$pfx: labeled TAPE-1 in drive 0");
597 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
598 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
599 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'},
600 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
604 slot_label => 'TAPE-1',
605 drive_label => 'TAPE-1',
606 }, "$pfx: label is correctly reflected in changer state");
609 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
610 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
612 slot_2_loaded_in => 1,
613 slot_1_loaded_in => 2,
615 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
617 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef);
618 $res2->{'device'}->finish();
620 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'});
623 step release1 => sub {
627 pass("$pfx: labeled TAPE-2 in drive 1");
630 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
631 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
632 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
633 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
637 slot_label => 'TAPE-2',
638 drive_label => 'TAPE-2',
639 }, "$pfx: label is correctly reflected in changer state");
641 $res2->release(finished_cb => $steps->{'inventory3'});
644 step inventory3 => sub {
647 pass("$pfx: slot 2/drive 1 released");
649 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [
650 { slot => 1, state => Amanda::Changer::SLOT_FULL,
651 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
652 device_status => $DEVICE_STATUS_SUCCESS,
653 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
654 { slot => 2, state => Amanda::Changer::SLOT_FULL,
655 barcode => '22222', loaded_in => 1,
656 device_status => $DEVICE_STATUS_SUCCESS,
657 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
658 { slot => 3, state => Amanda::Changer::SLOT_FULL,
660 device_status => undef, f_type => undef, label => undef },
661 { slot => 4, state => Amanda::Changer::SLOT_FULL,
663 device_status => undef, f_type => undef, label => undef },
664 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
665 device_status => undef, f_type => undef, label => undef },
666 ], "$pfx: inventory is still up to date");
669 step check_state_after_release1 => sub {
670 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef,
671 "$pfx: drive is not reserved");
672 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2',
673 "$pfx: tape is still in drive");
675 $steps->{'load_current_1'}->();
678 step load_current_1 => sub {
679 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'});
682 step loaded_current_1 => sub {
683 my ($err, $res) = @_;
686 { message => "the requested volume is in use (drive 0)",
687 reason => 'volinuse',
689 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)");
691 $steps->{'load_slot_4'}->();
694 # this should unload what's in drive 1 and load the empty volume in slot 4
695 step load_slot_4 => sub {
696 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'});
699 step loaded_slot_4 => sub {
700 (my $err, $res2) = @_;
703 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
704 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)");
707 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
708 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
711 slot_label => 'TAPE-2',
712 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly");
715 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
716 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
720 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct");
723 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
724 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
728 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct");
730 $steps->{'label_tape_4'}->();
733 step label_tape_4 => sub {
734 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef);
735 $res2->{'device'}->finish();
737 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'});
740 step inventory4 => sub {
743 pass("$pfx: labeled TAPE-4 in drive 1");
745 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [
746 { slot => 1, state => Amanda::Changer::SLOT_FULL,
748 device_status => $DEVICE_STATUS_SUCCESS,
749 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1',
750 reserved => 1, loaded_in => 0 },
751 { slot => 2, state => Amanda::Changer::SLOT_FULL,
753 device_status => $DEVICE_STATUS_SUCCESS,
754 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
755 { slot => 3, state => Amanda::Changer::SLOT_FULL,
757 device_status => undef, f_type => undef, label => undef },
758 { slot => 4, state => Amanda::Changer::SLOT_FULL,
759 barcode => '44444', reserved => 1, loaded_in => 1, current => 1,
760 device_status => $DEVICE_STATUS_SUCCESS,
761 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
762 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
763 device_status => undef, f_type => undef, label => undef },
764 ], "$pfx: inventory is up to date after more labelings");
767 step release2 => sub {
769 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
770 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
771 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
772 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
776 slot_label => 'TAPE-4',
777 drive_label => 'TAPE-4',
778 }, "$pfx: label is correctly reflected in changer state");
780 $res1->release(finished_cb => $steps->{'release2_done'});
783 step release2_done => sub {
787 pass("$pfx: slot 1/drive 0 released");
789 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1',
790 "$pfx: tape is still in drive");
792 $steps->{'release3'}->();
795 step release3 => sub {
799 $res2->release(finished_cb => $steps->{'release3_done'});
802 step release3_done => sub {
806 pass("$pfx: slot 4/drive 0 released");
808 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'},
809 'TAPE-4', "$pfx: tape is still in drive");
811 $steps->{'load_preloaded_by_slot'}->();
814 # try loading a slot that's already in a drive
815 step load_preloaded_by_slot => sub {
816 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'});
819 step loaded_preloaded_by_slot => sub {
820 (my $err, $res1) = @_;
823 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
824 "$pfx: loading a tape (by slot) that's already in a drive returns that drive");
826 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'});
829 # try again, this time by label
830 step load_preloaded_by_label => sub {
831 pass("$pfx: slot 1/drive 0 released");
833 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'});
836 step loaded_preloaded_by_label => sub {
837 (my $err, $res1) = @_;
840 is($res1->{'device'}->device_name, "file:$vtape_root/drive1",
841 "$pfx: loading a tape (by label) that's already in a drive returns that drive");
843 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'});
846 # test out searching by label
847 step load_unloaded_by_label => sub {
851 pass("$pfx: slot 4/drive 1 released");
853 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'});
856 step loaded_unloaded_by_label => sub {
857 (my $err, $res1) = @_;
860 $res1->{'device'}->read_label();
861 is($res1->{'device'}->volume_label, "TAPE-2",
862 "$pfx: loading a tape (by label) that's *not* already in a drive returns " .
863 "the correct device");
865 $steps->{'release4'}->();
868 step release4 => sub {
869 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1);
872 step release4_done => sub {
876 pass("$pfx: slot 2/drive 0 released");
879 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
880 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
881 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
884 slot_label => 'TAPE-2',
885 drive_label => undef,
886 }, "$pfx: and TAPE-2 ejected");
888 $steps->{'load_current_2'}->();
891 step load_current_2 => sub {
892 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'});
895 step loaded_current_2 => sub {
896 (my $err, $res1) = @_;
899 $res1->{'device'}->read_label();
900 is($res1->{'device'}->volume_label, "TAPE-4",
901 "$pfx: loading 'current' returns the correct device");
903 $steps->{'release5'}->();
906 step release5 => sub {
907 $res1->release(finished_cb => $steps->{'load_slot_next'});
910 step load_slot_next => sub {
914 pass("$pfx: slot 4/drive 1 released");
916 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'});
919 step loaded_slot_next => sub {
920 (my $err, $res1) = @_;
923 $res1->{'device'}->read_label();
924 is($res1->{'device'}->volume_label, "TAPE-1",
925 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " .
926 "looping around to the beginning");
928 $steps->{'load_res1_next_slot'}->();
931 step load_res1_next_slot => sub {
932 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'},
933 res_cb => $steps->{'loaded_res1_next_slot'});
936 step loaded_res1_next_slot => sub {
937 (my $err, $res2) = @_;
940 $res2->{'device'}->read_label();
941 is($res2->{'device'}->volume_label, "TAPE-2",
942 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too");
943 if ($mtx_config->{'barcodes'} == 1) {
944 is($res2->{'barcode'}, '22222',
945 "$pfx: result has a barcode");
948 $steps->{'release6'}->();
951 step release6 => sub {
952 $res1->release(finished_cb => $steps->{'release7'});
955 step release7 => sub {
959 pass("$pfx: slot 1 released");
961 $res2->release(finished_cb => $steps->{'load_disallowed_slot'});
964 step load_disallowed_slot => sub {
968 pass("$pfx: slot 2 released");
970 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'});
973 step loaded_disallowed_slot => sub {
974 (my $err, $res1) = @_;
977 { message => "slot 6 not in use-slots (1-5)",
980 "$pfx: loading a disallowed slot fails propertly");
982 $steps->{'inventory5'}->();
985 step inventory5 => sub {
986 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [
987 { slot => 1, state => Amanda::Changer::SLOT_FULL,
988 barcode => '11111', loaded_in => 1,
989 device_status => $DEVICE_STATUS_SUCCESS,
990 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
991 { slot => 2, state => Amanda::Changer::SLOT_FULL,
992 barcode => '22222', loaded_in => 0,
993 device_status => $DEVICE_STATUS_SUCCESS,
994 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
995 { slot => 3, state => Amanda::Changer::SLOT_FULL,
997 device_status => undef, f_type => undef, label => undef },
998 { slot => 4, state => Amanda::Changer::SLOT_FULL,
999 barcode => '44444', current => 1,
1000 device_status => $DEVICE_STATUS_SUCCESS,
1001 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1002 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1003 device_status => undef, f_type => undef, label => undef },
1004 ], "$pfx: inventory still accurate");
1007 step try_update => sub {
1008 # first, add a label in slot 3, which hasn't been written
1010 my $dev = Amanda::Device->new("file:$vtape_root/slot3");
1011 die $dev->error_or_status()
1012 unless $dev->status == 0;
1013 die "error writing label"
1014 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef);
1017 # now update that slot
1018 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'});
1021 step update_finished => sub {
1025 # verify that slots 2, 3, and 4 have correct info now
1027 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1028 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'},
1029 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
1034 }, "$pfx: update correctly finds new label in slot 3");
1036 # and check barcodes otherwise
1037 if ($mtx_config->{'barcodes'} > 0) {
1039 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1040 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'},
1041 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'},
1043 barcode_2 => 'TAPE-2',
1044 barcode_3 => 'TAPE-3',
1045 barcode_4 => 'TAPE-4',
1046 }, "$pfx: bc2lb is correct, too");
1049 $steps->{'try_update2'}->();
1052 step try_update2 => sub {
1054 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'});
1057 step update_finished2 => sub {
1061 # verify the new slot info
1063 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1065 slot_2 => 'SURPRISE!',
1066 }, "$pfx: assignment-style update correctly sets new label in slot 2");
1068 # and check barcodes otherwise
1069 if ($mtx_config->{'barcodes'} > 0) {
1071 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1073 barcode_2 => 'SURPRISE!',
1074 }, "$pfx: bc2lb is correct, too");
1077 $steps->{'try_update3'}->();
1080 step try_update3 => sub {
1082 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'});
1085 step update_finished3 => sub {
1088 { message => "slot 5 is empty",
1089 reason => 'unknown',
1091 "$pfx: assignment-style update of an empty slot gives error");
1093 $steps->{'inventory6'}->();
1096 step inventory6 => sub {
1097 # note that the loading behavior of update() is not required, so the loaded_in
1098 # keys here may change if update() gets smarter
1099 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [
1100 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1102 device_status => $DEVICE_STATUS_SUCCESS,
1103 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1104 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1106 device_status => $DEVICE_STATUS_SUCCESS,
1107 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1108 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1109 barcode => '33333', loaded_in => 1,
1110 device_status => undef, f_type => undef, label => 'TAPE-3' },
1111 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1112 barcode => '44444', loaded_in => 0, current => 1,
1113 device_status => $DEVICE_STATUS_SUCCESS,
1114 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1115 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1116 device_status => undef, f_type => undef, label => undef },
1117 ], "$pfx: inventory reflects updates");
1121 # move to a full slot
1122 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'});
1125 step moved1 => sub {
1129 { message => "slot 1 is not empty",
1130 reason => 'invalid',
1132 "$pfx: moving to a full slot is an error");
1134 $steps->{'move2'}->();
1138 # move to a full slot that's loaded (so there's not *actually* a tape
1140 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'});
1143 step moved2 => sub {
1147 { message => "slot 3 is not empty",
1148 reason => 'invalid',
1150 "$pfx: moving to a full slot is an error even if that slot is loaded");
1152 $steps->{'move3'}->();
1156 # move from an empty slot
1157 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'});
1160 step moved3 => sub {
1164 { message => "slot 5 is empty", # note that this depends on the order of checks..
1165 reason => 'invalid',
1167 "$pfx: moving from an empty slot is an error");
1169 $steps->{'move4'}->();
1173 # move from a loaded slot to an empty slot
1174 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'});
1177 step moved4 => sub {
1181 { message => "slot 4 is currently loaded",
1182 reason => 'invalid',
1184 "$pfx: moving from a loaded slot is an error");
1186 $steps->{'move5'}->();
1190 $chg->move(from_slot => 2, to_slot => 5, finished_cb => $steps->{'inventory7'});
1194 step inventory7 => sub {
1198 pass("$pfx: move succeeds");
1200 # note that the loading behavior of update() is not required, so the loaded_in
1201 # keys here may change if update() gets smarter
1202 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [
1203 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1205 device_status => $DEVICE_STATUS_SUCCESS,
1206 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1207 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1208 device_status => undef, f_type => undef, label => undef },
1209 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1210 barcode => '33333', loaded_in => 1,
1211 device_status => undef, f_type => undef, label => 'TAPE-3' },
1212 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1213 barcode => '44444', loaded_in => 0, current => 1,
1214 device_status => $DEVICE_STATUS_SUCCESS,
1215 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1216 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1218 device_status => $DEVICE_STATUS_SUCCESS,
1219 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1220 ], "$pfx: inventory reflects the move");
1223 # test a scan, using except_slots
1226 step start_scan => sub {
1227 $chg->load(relative_slot => "current", except_slots => { %except_slots },
1228 res_cb => $steps->{'loaded_for_scan'});
1231 step loaded_for_scan => sub {
1232 (my $err, $res1) = @_;
1235 if ($err->notfound) {
1236 return $steps->{'scan_done'}->();
1237 } elsif ($err->volinuse and defined $err->{'slot'}) {
1238 $slot = $err->{'slot'};
1243 $slot = $res1->{'this_slot'};
1246 $except_slots{$slot} = 1;
1248 $res1->release(finished_cb => $steps->{'released_for_scan'});
1251 step released_for_scan => sub {
1255 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'},
1256 except_slots => { %except_slots },
1257 res_cb => $steps->{'loaded_for_scan'});
1260 step scan_done => sub {
1261 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 },
1262 "$pfx: scanning with except_slots works");
1263 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [
1264 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1265 barcode => '11111', loaded_in => 0,
1266 device_status => $DEVICE_STATUS_SUCCESS,
1267 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1268 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1269 device_status => undef, f_type => undef, label => undef },
1270 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1271 barcode => '33333', loaded_in => 1,
1272 device_status => undef, f_type => undef, label => 'TAPE-3' },
1273 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1274 barcode => '44444', current => 1,
1275 device_status => $DEVICE_STATUS_SUCCESS,
1276 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1277 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1279 device_status => $DEVICE_STATUS_SUCCESS,
1280 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1281 ], "$pfx: inventory before updates with unknown state");
1284 step update_unknown => sub {
1285 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'});
1288 step update_unknown_finished => sub {
1292 if ($mtx_config->{'barcodes'} > 0) {
1293 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1294 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1295 barcode => '11111', loaded_in => 0,
1296 device_status => $DEVICE_STATUS_SUCCESS,
1297 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1298 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1299 device_status => undef, f_type => undef, label => undef },
1300 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1301 barcode => '33333', loaded_in => 1,
1302 device_status => undef, f_type => undef, label => 'TAPE-3' },
1303 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1304 barcode => '44444', current => 1,
1305 device_status => $DEVICE_STATUS_SUCCESS,
1306 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1307 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1309 device_status => $DEVICE_STATUS_SUCCESS,
1310 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1311 ], "$pfx: inventory reflects updates with unknown state with barcodes");
1313 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1314 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1315 barcode => '11111', loaded_in => 0,
1316 device_status => $DEVICE_STATUS_SUCCESS,
1317 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1318 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1319 device_status => undef, f_type => undef, label => undef },
1320 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1321 barcode => '33333', loaded_in => 1,
1322 device_status => undef, f_type => undef, label => undef },
1323 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1324 barcode => '44444', current => 1,
1325 device_status => undef, f_type => undef, label => undef },
1326 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1328 device_status => $DEVICE_STATUS_SUCCESS,
1329 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1330 ], "$pfx: inventory reflects updates with unknown state without barcodes");
1335 unlink($chg_state_file) if -f $chg_state_file;
1336 unlink($mtx_state_file) if -f $mtx_state_file;
1337 rmtree($vtape_root);
1343 # These tests are run over a number of different mtx configurations, to ensure
1344 # that the behavior is identical regardless of the changer/mtx characteristics
1345 for my $mtx_config (
1346 { barcodes => 1, track_orig => 1, },
1347 { barcodes => 0, track_orig => 1, },
1348 { barcodes => 1, track_orig => -1, },
1349 { barcodes => 0, track_orig => 0, },
1350 { barcodes => -1, track_orig => 0, },
1352 test_changer($mtx_config, \&Amanda::MainLoop::quit);
1353 Amanda::MainLoop::run();