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;
25 use lib "@amperldir@";
27 use Installcheck::Config;
28 use Installcheck::Changer;
29 use Installcheck::Mock qw( setup_mock_mtx $mock_mtx_path );
30 use Amanda::Device qw( :constants );
34 use Amanda::Config qw( :init :getconf config_dir_relative );
37 # set up debugging so debug output doesn't interfere with test results
38 Amanda::Debug::dbopen("installcheck");
40 # and disable Debug's die() and warn() overrides
41 Amanda::Debug::disable_die_override();
42 Installcheck::log_test_output();
44 my $chg_state_file = "$Installcheck::TMP/chg-robot-state";
45 unlink($chg_state_file) if -f $chg_state_file;
47 my $mtx_state_file = setup_mock_mtx (
66 my ($chg, $barcodes, $next_step, $expected, $msg) = @_;
68 $chg->inventory(inventory_cb => make_cb(sub {
72 # strip barcodes from both $expected and $inv
74 for (@$expected, @$inv) {
75 delete $_->{'barcode'};
79 is_deeply($inv, $expected, $msg)
80 or diag("Got:\n" . Dumper($inv));
87 # test the "interface" package
90 my ($finished_cb) = @_;
91 my ($interface, $chg);
93 my $steps = define_steps
94 cb_ref => \$finished_cb;
97 my $testconf = Installcheck::Config->new();
98 $testconf->add_changer('robo', [
99 tpchanger => "\"chg-robot:$mtx_state_file\"",
100 changerfile => "\"$chg_state_file\"",
102 # point to the two vtape "drives" that mock/mtx will set up
103 property => "\"tape-device\" \"0=null:drive0\"",
105 # an point to the mock mtx
106 property => "\"mtx\" \"$mock_mtx_path\"",
110 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
111 if ($cfg_result != $CFGERR_OK) {
112 my ($level, @errors) = Amanda::Config::config_errors();
113 die(join "\n", @errors);
116 $chg = Amanda::Changer->new("robo");
117 die "$chg" if $chg->isa("Amanda::Changer::Error");
118 $interface = $chg->{'interface'};
120 $interface->inquiry($steps->{'inquiry_cb'});
123 step inquiry_cb => sub {
124 my ($error, $info) = @_;
126 die $error if $error;
129 'revision' => '0416',
130 'product id' => 'SSL2000 Series',
131 'attached changer' => 'No',
132 'vendor id' => 'COMPAQ',
133 'product type' => 'Medium Changer'
134 }, "robot::Interface inquiry() info is correct");
136 $steps->{'status1'}->();
139 step status1 => sub {
140 $interface->status(sub {
141 my ($error, $status) = @_;
143 die $error if $error;
151 1 => { 'barcode' => '11111', ie => 0 },
152 2 => { 'barcode' => '22222', ie => 0 },
153 3 => { 'barcode' => '33333', ie => 0 },
154 4 => { 'barcode' => '44444', ie => 0 },
155 5 => { empty => 1, ie => 0 },
156 6 => { empty => 1, ie => 1 },
158 }, "robot::Interface status() output is correct (no drives loaded)");
159 $steps->{'load0'}->();
164 $interface->load(2, 0, sub {
167 die $error if $error;
170 $steps->{'status2'}->();
174 step status2 => sub {
175 $interface->status(sub {
176 my ($error, $status) = @_;
178 die $error if $error;
182 0 => { barcode => '22222', 'orig_slot' => 2 },
186 1 => { 'barcode' => '11111', ie => 0 },
187 2 => { empty => 1, ie => 0 },
188 3 => { 'barcode' => '33333', ie => 0 },
189 4 => { 'barcode' => '44444', ie => 0 },
190 5 => { empty => 1, ie => 0 },
191 6 => { empty => 1, ie => 1 },
193 }, "robot::Interface status() output is correct (one drive loaded)");
195 $steps->{'load1'}->();
200 $interface->load(4, 1, sub {
203 die $error if $error;
206 $steps->{'status3'}->();
210 step status3 => sub {
211 $interface->status(sub {
212 my ($error, $status) = @_;
214 die $error if $error;
218 0 => { barcode => '22222', 'orig_slot' => 2 },
219 1 => { barcode => '44444', 'orig_slot' => 4 },
222 1 => { 'barcode' => '11111', ie => 0 },
223 2 => { empty => 1, ie => 0 },
224 3 => { 'barcode' => '33333', ie => 0 },
225 4 => { empty => 1, ie => 0 },
226 5 => { empty => 1, ie => 0 },
227 6 => { empty => 1, ie => 1 },
229 }, "robot::Interface status() output is correct (two drives loaded)");
231 $steps->{'transfer'}->();
235 step transfer => sub {
236 $interface->transfer(3, 6, sub {
239 die $error if $error;
242 $steps->{'status4'}->();
246 step status4 => sub {
247 $interface->status(sub {
248 my ($error, $status) = @_;
250 die $error if $error;
254 0 => { barcode => '22222', 'orig_slot' => 2 },
255 1 => { barcode => '44444', 'orig_slot' => 4 },
258 1 => { 'barcode' => '11111', ie => 0 },
259 2 => { empty => 1, ie => 0 },
260 3 => { empty => 1, ie => 0 },
261 4 => { empty => 1, ie => 0 },
262 5 => { empty => 1, ie => 0 },
263 6 => { 'barcode' => '33333', ie => 1 },
265 }, "robot::Interface status() output is correct after transfer");
271 test_interface(\&Amanda::MainLoop::quit);
272 Amanda::MainLoop::run();
275 my $testconf = Installcheck::Config->new();
276 $testconf->add_changer('bum-scsi-dev', [
277 tpchanger => "\"chg-robot:does/not/exist\"",
278 property => "\"tape-device\" \"0=null:foo\"",
279 changerfile => "\"$chg_state_file\"",
281 $testconf->add_changer('no-tape-device', [
282 tpchanger => "\"chg-robot:$mtx_state_file\"",
283 changerfile => "\"$chg_state_file\"",
285 $testconf->add_changer('bad-property', [
286 tpchanger => "\"chg-robot:$mtx_state_file\"",
287 changerfile => "\"$chg_state_file\"",
288 property => "\"fast-search\" \"maybe\"",
289 property => "\"tape-device\" \"0=null:foo\"",
291 $testconf->add_changer('no-fast-search', [
292 tpchanger => "\"chg-robot:$mtx_state_file\"",
293 changerfile => "\"$chg_state_file\"",
294 property => "\"use-slots\" \"1-3,9\"",
295 property => "append \"use-slots\" \"8,5-6\"",
296 property => "\"fast-search\" \"no\"",
297 property => "\"tape-device\" \"0=null:foo\"",
299 $testconf->add_changer('delays', [
300 tpchanger => "\"chg-robot:$mtx_state_file\"",
301 # no changerfile property
302 property => "\"tape-device\" \"0=null:foo\"",
303 property => "\"status-interval\" \"1m\"",
304 property => "\"eject-delay\" \"1s\"",
305 property => "\"unload-delay\" \"2M\"",
306 property => "\"load-poll\" \"2s POLl 3s uNtil 1m\"",
311 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
312 if ($cfg_result != $CFGERR_OK) {
313 my ($level, @errors) = Amanda::Config::config_errors();
314 die(join "\n", @errors);
317 # test the changer constructor and properties
318 my $err = Amanda::Changer->new("bum-scsi-dev");
320 { message => "'does/not/exist' not found",
322 "check for device existence works");
324 $err = Amanda::Changer->new("no-tape-device");
326 { message => "no 'tape-device' property specified",
328 "tape-device property is required");
330 $err = Amanda::Changer->new("bad-property");
332 { message => "invalid 'fast-search' value",
334 "invalid boolean value handled correctly");
336 my $chg = Amanda::Changer->new("delays");
337 die "$chg" if $chg->isa("Amanda::Changer::Error");
338 is($chg->{'status_interval'}, 60, "status-interval parsed");
339 is($chg->{'eject_delay'}, 1, "eject-delay parsed");
340 is($chg->{'unload_delay'}, 120, "unload-delay parsed");
341 is_deeply($chg->{'load_poll'}, [ 2, 3, 60 ], "load-poll parsed");
343 # check out the statefile filename generation
344 my $dashed_mtx_state_file = $mtx_state_file;
345 $dashed_mtx_state_file =~ tr/a-zA-Z0-9/-/cs;
346 $dashed_mtx_state_file =~ s/^-*//;
347 is($chg->{'statefile'}, "$localstatedir/amanda/chg-robot-$dashed_mtx_state_file",
348 "statefile calculated correctly");
350 # test no-fast-search
351 $chg = Amanda::Changer->new("no-fast-search");
352 die "$chg" if $chg->isa("Amanda::Changer::Error");
354 info => ['fast_search'],
355 info_cb => make_cb(info_cb => sub {
356 my ($err, %info) = @_;
357 ok(!$info{'fast_search'}, "fast-search property works");
358 Amanda::MainLoop::quit();
360 Amanda::MainLoop::run();
363 my @allowed = map { $chg->_is_slot_allowed($_) } (0 .. 10);
364 is_deeply([ @allowed ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0 ],
365 "_is_slot_allowed parses multiple properties and behaves as expected");
372 my ($mtx_config, $finished_cb) = @_;
374 my ($res1, $res2, $mtx_state_file);
375 my $pfx = "BC=$mtx_config->{barcodes}; TORIG=$mtx_config->{track_orig}";
376 my $vtape_root = "$Installcheck::TMP/chg-robot-vtapes";
378 my $steps = define_steps
379 cb_ref => \$finished_cb;
383 unlink($chg_state_file) if -f $chg_state_file;
384 %Amanda::Changer::changers_by_uri_cc = ();
391 $mtx_state_file = setup_mock_mtx (
402 6 => '66666', # slot 6 is full, but not in use-slots
407 vtape_root => $vtape_root,
410 my @ignore_barcodes = ( property => "\"ignore-barcodes\" \"y\"")
411 if ($mtx_config->{'barcodes'} == -1);
413 my $testconf = Installcheck::Config->new();
414 $testconf->add_changer('robo', [
415 tpchanger => "\"chg-robot:$mtx_state_file\"",
416 changerfile => "\"$chg_state_file\"",
418 # point to the two vtape "drives" that mock/mtx will set up
419 property => "\"tape-device\" \"0=file:$vtape_root/drive0\"",
420 property => "append \"tape-device\" \"1=file:$vtape_root/drive1\"",
421 property => "\"use-slots\" \"1-5\"",
422 property => "\"mtx\" \"$mock_mtx_path\"",
428 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
429 if ($cfg_result != $CFGERR_OK) {
430 my ($level, @errors) = Amanda::Config::config_errors();
431 die(join "\n", @errors);
434 $steps->{'start'}->();
438 $chg = Amanda::Changer->new("robo");
439 ok(!$chg->isa("Amanda::Changer::Error"),
440 "$pfx: Create working chg-robot instance")
441 or die("no sense going on: $chg");
443 $chg->info(info => [qw(vendor_string num_slots fast_search)], info_cb => $steps->{'info_cb'});
446 step info_cb => sub {
447 my ($err, %info) = @_;
450 is_deeply({ %info }, {
453 vendor_string => "COMPAQ SSL2000 Series",
454 }, "$pfx: info keys num_slots, fast_search, vendor_string are correct");
456 $steps->{'inventory1'}->();
459 step inventory1 => sub {
460 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_1'}, [
461 { slot => 1, state => Amanda::Changer::SLOT_FULL,
462 barcode => '11111', current => 1,
463 device_status => undef, f_type => undef, label => undef },
464 { slot => 2, state => Amanda::Changer::SLOT_FULL,
466 device_status => undef, f_type => undef, label => undef },
467 { slot => 3, state => Amanda::Changer::SLOT_FULL,
469 device_status => undef, f_type => undef, label => undef },
470 { slot => 4, state => Amanda::Changer::SLOT_FULL,
472 device_status => undef, f_type => undef, label => undef },
473 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
474 device_status => undef, f_type => undef, label => undef },
475 ], "$pfx: inventory is correct on start-up");
478 step load_slot_1 => sub {
479 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'});
482 step loaded_slot_1 => sub {
483 (my $err, $res1) = @_;
486 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
487 "$pfx: first load returns drive-0 device");
490 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
491 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
495 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct");
497 $steps->{'load_slot_2'}->();
500 step load_slot_2 => sub {
501 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'});
504 step loaded_slot_2 => sub {
505 (my $err, $res2) = @_;
508 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
509 "$pfx: second load returns drive-1 device");
512 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
513 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
517 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct");
520 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
521 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
525 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
527 $steps->{'check_loads'}->();
530 step check_loads => sub {
531 # peek into the interface to check that things are loaded correctly
532 $chg->{'interface'}->status(sub {
533 my ($error, $status) = @_;
535 die $error if $error;
537 # only perform these checks when barcodes are enabled
538 if ($mtx_config->{'barcodes'} > 0) {
539 is_deeply($status->{'drives'}, {
540 0 => { barcode => '11111', 'orig_slot' => 1 },
541 1 => { barcode => '22222', 'orig_slot' => 2 },
542 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded");
545 $steps->{'inventory2'}->();
549 step inventory2 => sub {
550 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [
551 { slot => 1, state => Amanda::Changer::SLOT_FULL,
552 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
553 device_status => undef, f_type => undef, label => undef },
554 { slot => 2, state => Amanda::Changer::SLOT_FULL,
555 barcode => '22222', reserved => 1, loaded_in => 1,
556 device_status => undef, f_type => undef, label => undef },
557 { slot => 3, state => Amanda::Changer::SLOT_FULL,
559 device_status => undef, f_type => undef, label => undef },
560 { slot => 4, state => Amanda::Changer::SLOT_FULL,
562 device_status => undef, f_type => undef, label => undef },
563 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
564 device_status => undef, f_type => undef, label => undef },
565 ], "$pfx: inventory is updated when slots are loaded");
568 step load_slot_3 => sub {
569 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'});
572 step loaded_slot_3 => sub {
573 my ($err, $no_res) = @_;
576 { message => "no drives available",
577 reason => 'driveinuse',
579 "$pfx: trying to load a third slot fails with 'no drives available'");
581 $steps->{'label_tape_1'}->();
584 step label_tape_1 => sub {
585 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef);
586 $res1->{'device'}->finish();
588 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'});
591 step label_tape_2 => sub {
595 pass("$pfx: labeled TAPE-1 in drive 0");
598 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
599 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
600 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'},
601 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
605 slot_label => 'TAPE-1',
606 drive_label => 'TAPE-1',
607 }, "$pfx: label is correctly reflected in changer state");
610 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
611 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
613 slot_2_loaded_in => 1,
614 slot_1_loaded_in => 2,
616 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
618 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef);
619 $res2->{'device'}->finish();
621 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'});
624 step release1 => sub {
628 pass("$pfx: labeled TAPE-2 in drive 1");
631 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
632 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
633 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
634 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
638 slot_label => 'TAPE-2',
639 drive_label => 'TAPE-2',
640 }, "$pfx: label is correctly reflected in changer state");
642 $res2->release(finished_cb => $steps->{'inventory3'});
645 step inventory3 => sub {
648 pass("$pfx: slot 2/drive 1 released");
650 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [
651 { slot => 1, state => Amanda::Changer::SLOT_FULL,
652 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
653 device_status => $DEVICE_STATUS_SUCCESS,
654 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
655 { slot => 2, state => Amanda::Changer::SLOT_FULL,
656 barcode => '22222', loaded_in => 1,
657 device_status => $DEVICE_STATUS_SUCCESS,
658 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
659 { slot => 3, state => Amanda::Changer::SLOT_FULL,
661 device_status => undef, f_type => undef, label => undef },
662 { slot => 4, state => Amanda::Changer::SLOT_FULL,
664 device_status => undef, f_type => undef, label => undef },
665 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
666 device_status => undef, f_type => undef, label => undef },
667 ], "$pfx: inventory is still up to date");
670 step check_state_after_release1 => sub {
671 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef,
672 "$pfx: drive is not reserved");
673 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2',
674 "$pfx: tape is still in drive");
676 $steps->{'load_current_1'}->();
679 step load_current_1 => sub {
680 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'});
683 step loaded_current_1 => sub {
684 my ($err, $res) = @_;
687 { message => "the requested volume is in use (drive 0)",
688 reason => 'volinuse',
690 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)");
692 $steps->{'load_slot_4'}->();
695 # this should unload what's in drive 1 and load the empty volume in slot 4
696 step load_slot_4 => sub {
697 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'});
700 step loaded_slot_4 => sub {
701 (my $err, $res2) = @_;
704 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
705 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)");
708 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
709 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
712 slot_label => 'TAPE-2',
713 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly");
716 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
717 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
721 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct");
724 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
725 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
729 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct");
731 $steps->{'label_tape_4'}->();
734 step label_tape_4 => sub {
735 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef);
736 $res2->{'device'}->finish();
738 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'});
741 step inventory4 => sub {
744 pass("$pfx: labeled TAPE-4 in drive 1");
746 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [
747 { slot => 1, state => Amanda::Changer::SLOT_FULL,
749 device_status => $DEVICE_STATUS_SUCCESS,
750 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1',
751 reserved => 1, loaded_in => 0 },
752 { slot => 2, state => Amanda::Changer::SLOT_FULL,
754 device_status => $DEVICE_STATUS_SUCCESS,
755 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
756 { slot => 3, state => Amanda::Changer::SLOT_FULL,
758 device_status => undef, f_type => undef, label => undef },
759 { slot => 4, state => Amanda::Changer::SLOT_FULL,
760 barcode => '44444', reserved => 1, loaded_in => 1, current => 1,
761 device_status => $DEVICE_STATUS_SUCCESS,
762 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
763 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
764 device_status => undef, f_type => undef, label => undef },
765 ], "$pfx: inventory is up to date after more labelings");
768 step release2 => sub {
770 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
771 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
772 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
773 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
777 slot_label => 'TAPE-4',
778 drive_label => 'TAPE-4',
779 }, "$pfx: label is correctly reflected in changer state");
781 $res1->release(finished_cb => $steps->{'release2_done'});
784 step release2_done => sub {
788 pass("$pfx: slot 1/drive 0 released");
790 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1',
791 "$pfx: tape is still in drive");
793 $steps->{'release3'}->();
796 step release3 => sub {
800 $res2->release(finished_cb => $steps->{'release3_done'});
803 step release3_done => sub {
807 pass("$pfx: slot 4/drive 0 released");
809 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'},
810 'TAPE-4', "$pfx: tape is still in drive");
812 $steps->{'load_preloaded_by_slot'}->();
815 # try loading a slot that's already in a drive
816 step load_preloaded_by_slot => sub {
817 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'});
820 step loaded_preloaded_by_slot => sub {
821 (my $err, $res1) = @_;
824 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
825 "$pfx: loading a tape (by slot) that's already in a drive returns that drive");
827 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'});
830 # try again, this time by label
831 step load_preloaded_by_label => sub {
832 pass("$pfx: slot 1/drive 0 released");
834 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'});
837 step loaded_preloaded_by_label => sub {
838 (my $err, $res1) = @_;
841 is($res1->{'device'}->device_name, "file:$vtape_root/drive1",
842 "$pfx: loading a tape (by label) that's already in a drive returns that drive");
844 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'});
847 # test out searching by label
848 step load_unloaded_by_label => sub {
852 pass("$pfx: slot 4/drive 1 released");
854 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'});
857 step loaded_unloaded_by_label => sub {
858 (my $err, $res1) = @_;
861 $res1->{'device'}->read_label();
862 is($res1->{'device'}->volume_label, "TAPE-2",
863 "$pfx: loading a tape (by label) that's *not* already in a drive returns " .
864 "the correct device");
866 $steps->{'release4'}->();
869 step release4 => sub {
870 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1);
873 step release4_done => sub {
877 pass("$pfx: slot 2/drive 0 released");
880 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
881 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
882 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
885 slot_label => 'TAPE-2',
886 drive_label => undef,
887 }, "$pfx: and TAPE-2 ejected");
889 $steps->{'load_current_2'}->();
892 step load_current_2 => sub {
893 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'});
896 step loaded_current_2 => sub {
897 (my $err, $res1) = @_;
900 $res1->{'device'}->read_label();
901 is($res1->{'device'}->volume_label, "TAPE-4",
902 "$pfx: loading 'current' returns the correct device");
904 $steps->{'release5'}->();
907 step release5 => sub {
908 $res1->release(finished_cb => $steps->{'load_slot_next'});
911 step load_slot_next => sub {
915 pass("$pfx: slot 4/drive 1 released");
917 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'});
920 step loaded_slot_next => sub {
921 (my $err, $res1) = @_;
924 $res1->{'device'}->read_label();
925 is($res1->{'device'}->volume_label, "TAPE-1",
926 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " .
927 "looping around to the beginning");
929 $steps->{'load_res1_next_slot'}->();
932 step load_res1_next_slot => sub {
933 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'},
934 res_cb => $steps->{'loaded_res1_next_slot'});
937 step loaded_res1_next_slot => sub {
938 (my $err, $res2) = @_;
941 $res2->{'device'}->read_label();
942 is($res2->{'device'}->volume_label, "TAPE-2",
943 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too");
944 if ($mtx_config->{'barcodes'} == 1) {
945 is($res2->{'barcode'}, '22222',
946 "$pfx: result has a barcode");
949 $steps->{'release6'}->();
952 step release6 => sub {
953 $res1->release(finished_cb => $steps->{'release7'});
956 step release7 => sub {
960 pass("$pfx: slot 1 released");
962 $res2->release(finished_cb => $steps->{'load_disallowed_slot'});
965 step load_disallowed_slot => sub {
969 pass("$pfx: slot 2 released");
971 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'});
974 step loaded_disallowed_slot => sub {
975 (my $err, $res1) = @_;
978 { message => "slot 6 not in use-slots (1-5)",
981 "$pfx: loading a disallowed slot fails propertly");
983 $steps->{'inventory5'}->();
986 step inventory5 => sub {
987 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [
988 { slot => 1, state => Amanda::Changer::SLOT_FULL,
989 barcode => '11111', loaded_in => 1,
990 device_status => $DEVICE_STATUS_SUCCESS,
991 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
992 { slot => 2, state => Amanda::Changer::SLOT_FULL,
993 barcode => '22222', loaded_in => 0,
994 device_status => $DEVICE_STATUS_SUCCESS,
995 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
996 { slot => 3, state => Amanda::Changer::SLOT_FULL,
998 device_status => undef, f_type => undef, label => undef },
999 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1000 barcode => '44444', current => 1,
1001 device_status => $DEVICE_STATUS_SUCCESS,
1002 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1003 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1004 device_status => undef, f_type => undef, label => undef },
1005 ], "$pfx: inventory still accurate");
1008 step try_update => sub {
1009 # first, add a label in slot 3, which hasn't been written
1011 my $dev = Amanda::Device->new("file:$vtape_root/slot3");
1012 die $dev->error_or_status()
1013 unless $dev->status == 0;
1014 die "error writing label"
1015 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef);
1018 # now update that slot
1019 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'});
1022 step update_finished => sub {
1026 # verify that slots 2, 3, and 4 have correct info now
1028 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1029 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'},
1030 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
1035 }, "$pfx: update correctly finds new label in slot 3");
1037 # and check barcodes otherwise
1038 if ($mtx_config->{'barcodes'} > 0) {
1040 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1041 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'},
1042 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'},
1044 barcode_2 => 'TAPE-2',
1045 barcode_3 => 'TAPE-3',
1046 barcode_4 => 'TAPE-4',
1047 }, "$pfx: bc2lb is correct, too");
1050 $steps->{'try_update2'}->();
1053 step try_update2 => sub {
1055 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'});
1058 step update_finished2 => sub {
1062 # verify the new slot info
1064 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1066 slot_2 => 'SURPRISE!',
1067 }, "$pfx: assignment-style update correctly sets new label in slot 2");
1069 # and check barcodes otherwise
1070 if ($mtx_config->{'barcodes'} > 0) {
1072 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1074 barcode_2 => 'SURPRISE!',
1075 }, "$pfx: bc2lb is correct, too");
1078 $steps->{'try_update3'}->();
1081 step try_update3 => sub {
1083 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'});
1086 step update_finished3 => sub {
1089 { message => "slot 5 is empty",
1090 reason => 'unknown',
1092 "$pfx: assignment-style update of an empty slot gives error");
1094 $steps->{'inventory6'}->();
1097 step inventory6 => sub {
1098 # note that the loading behavior of update() is not required, so the loaded_in
1099 # keys here may change if update() gets smarter
1100 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [
1101 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1103 device_status => $DEVICE_STATUS_SUCCESS,
1104 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1105 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1107 device_status => $DEVICE_STATUS_SUCCESS,
1108 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1109 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1110 barcode => '33333', loaded_in => 1,
1111 device_status => undef, f_type => undef, label => 'TAPE-3' },
1112 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1113 barcode => '44444', loaded_in => 0, current => 1,
1114 device_status => $DEVICE_STATUS_SUCCESS,
1115 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1116 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1117 device_status => undef, f_type => undef, label => undef },
1118 ], "$pfx: inventory reflects updates");
1122 # move to a full slot
1123 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'});
1126 step moved1 => sub {
1130 { message => "slot 1 is not empty",
1131 reason => 'invalid',
1133 "$pfx: moving to a full slot is an error");
1135 $steps->{'move2'}->();
1139 # move to a full slot that's loaded (so there's not *actually* a tape
1141 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'});
1144 step moved2 => sub {
1148 { message => "slot 3 is not empty",
1149 reason => 'invalid',
1151 "$pfx: moving to a full slot is an error even if that slot is loaded");
1153 $steps->{'move3'}->();
1157 # move from an empty slot
1158 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'});
1161 step moved3 => sub {
1165 { message => "slot 5 is empty", # note that this depends on the order of checks..
1166 reason => 'invalid',
1168 "$pfx: moving from an empty slot is an error");
1170 $steps->{'move4'}->();
1174 # move from a loaded slot to an empty slot
1175 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'});
1178 step moved4 => sub {
1182 pass("$pfx: move of a loaded volume succeeds");
1184 $steps->{'move5'}->();
1188 $chg->move(from_slot => 2, to_slot => 4, finished_cb => $steps->{'inventory7'});
1192 step inventory7 => sub {
1196 pass("$pfx: move succeeds");
1198 # note that the loading behavior of update() is not required, so the loaded_in
1199 # keys here may change if update() gets smarter
1200 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [
1201 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1203 device_status => $DEVICE_STATUS_SUCCESS,
1204 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1205 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1206 device_status => undef, f_type => undef, label => undef },
1207 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1208 barcode => '33333', loaded_in => 1,
1209 device_status => undef, f_type => undef, label => 'TAPE-3' },
1210 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1211 barcode => '22222', current => 1,
1212 device_status => $DEVICE_STATUS_SUCCESS,
1213 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1214 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1216 device_status => $DEVICE_STATUS_SUCCESS,
1217 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1218 ], "$pfx: inventory reflects the move");
1221 # test a scan, using except_slots
1224 step start_scan => sub {
1225 $chg->load(relative_slot => "current", except_slots => { %except_slots },
1226 res_cb => $steps->{'loaded_for_scan'});
1229 step loaded_for_scan => sub {
1230 (my $err, $res1) = @_;
1233 if ($err->notfound) {
1234 return $steps->{'scan_done'}->();
1235 } elsif ($err->volinuse and defined $err->{'slot'}) {
1236 $slot = $err->{'slot'};
1241 $slot = $res1->{'this_slot'};
1244 $except_slots{$slot} = 1;
1246 $res1->release(finished_cb => $steps->{'released_for_scan'});
1249 step released_for_scan => sub {
1253 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'},
1254 except_slots => { %except_slots },
1255 res_cb => $steps->{'loaded_for_scan'});
1258 step scan_done => sub {
1259 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 },
1260 "$pfx: scanning with except_slots works");
1261 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [
1262 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1263 barcode => '11111', loaded_in => 1,
1264 device_status => $DEVICE_STATUS_SUCCESS,
1265 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1266 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1267 device_status => undef, f_type => undef, label => undef },
1268 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1269 barcode => '33333', loaded_in => 0,
1270 device_status => undef, f_type => undef, label => 'TAPE-3' },
1271 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1272 barcode => '22222', current => 1,
1273 device_status => $DEVICE_STATUS_SUCCESS,
1274 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1275 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1277 device_status => $DEVICE_STATUS_SUCCESS,
1278 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1279 ], "$pfx: inventory before updates with unknown state");
1282 step update_unknown => sub {
1283 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'});
1286 step update_unknown_finished => sub {
1290 if ($mtx_config->{'barcodes'} > 0) {
1291 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1292 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1293 barcode => '11111', loaded_in => 1,
1294 device_status => $DEVICE_STATUS_SUCCESS,
1295 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1296 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1297 device_status => undef, f_type => undef, label => undef },
1298 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1299 barcode => '33333', loaded_in => 0,
1300 device_status => undef, f_type => undef, label => 'TAPE-3' },
1301 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1302 barcode => '22222', current => 1,
1303 device_status => $DEVICE_STATUS_SUCCESS,
1304 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1305 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1307 device_status => $DEVICE_STATUS_SUCCESS,
1308 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1309 ], "$pfx: inventory reflects updates with unknown state with barcodes");
1311 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1312 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1313 barcode => '11111', loaded_in => 1,
1314 device_status => $DEVICE_STATUS_SUCCESS,
1315 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1316 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1317 device_status => undef, f_type => undef, label => undef },
1318 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1319 barcode => '33333', loaded_in => 0,
1320 device_status => undef, f_type => undef, label => undef },
1321 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1322 barcode => '22222', current => 1,
1323 device_status => undef, f_type => undef, label => undef },
1324 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1326 device_status => $DEVICE_STATUS_SUCCESS,
1327 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1328 ], "$pfx: inventory reflects updates with unknown state without barcodes");
1333 unlink($chg_state_file) if -f $chg_state_file;
1334 unlink($mtx_state_file) if -f $mtx_state_file;
1335 rmtree($vtape_root);
1341 # These tests are run over a number of different mtx configurations, to ensure
1342 # that the behavior is identical regardless of the changer/mtx characteristics
1343 for my $mtx_config (
1344 { barcodes => 1, track_orig => 1, },
1345 { barcodes => 0, track_orig => 1, },
1346 { barcodes => 1, track_orig => -1, },
1347 { barcodes => 0, track_orig => 0, },
1348 { barcodes => -1, track_orig => 0, },
1350 test_changer($mtx_config, \&Amanda::MainLoop::quit);
1351 Amanda::MainLoop::run();