1 # Copyright (c) 2009-2012 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 => 324;
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,
95 finalize => sub { $chg->quit() };
98 my $testconf = Installcheck::Config->new();
99 $testconf->add_changer('robo', [
100 tpchanger => "\"chg-robot:$mtx_state_file\"",
101 changerfile => "\"$chg_state_file\"",
103 # point to the two vtape "drives" that mock/mtx will set up
104 property => "\"tape-device\" \"0=null:drive0\"",
106 # an point to the mock mtx
107 property => "\"mtx\" \"$mock_mtx_path\"",
111 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
112 if ($cfg_result != $CFGERR_OK) {
113 my ($level, @errors) = Amanda::Config::config_errors();
114 die(join "\n", @errors);
117 $chg = Amanda::Changer->new("robo");
118 die "$chg" if $chg->isa("Amanda::Changer::Error");
119 is($chg->have_inventory(), '1', "changer have inventory");
120 $interface = $chg->{'interface'};
122 $interface->inquiry($steps->{'inquiry_cb'});
125 step inquiry_cb => sub {
126 my ($error, $info) = @_;
128 die $error if $error;
131 'revision' => '0416',
132 'product id' => 'SSL2000 Series',
133 'attached changer' => 'No',
134 'vendor id' => 'COMPAQ',
135 'product type' => 'Medium Changer'
136 }, "robot::Interface inquiry() info is correct");
138 $steps->{'status1'}->();
141 step status1 => sub {
142 $interface->status(sub {
143 my ($error, $status) = @_;
145 die $error if $error;
153 1 => { 'barcode' => '11111', ie => 0 },
154 2 => { 'barcode' => '22222', ie => 0 },
155 3 => { 'barcode' => '33333', ie => 0 },
156 4 => { 'barcode' => '44444', ie => 0 },
157 5 => { empty => 1, ie => 0 },
158 6 => { empty => 1, ie => 1 },
160 }, "robot::Interface status() output is correct (no drives loaded)");
161 $steps->{'load0'}->();
166 $interface->load(2, 0, sub {
169 die $error if $error;
172 $steps->{'status2'}->();
176 step status2 => sub {
177 $interface->status(sub {
178 my ($error, $status) = @_;
180 die $error if $error;
184 0 => { barcode => '22222', 'orig_slot' => 2 },
188 1 => { 'barcode' => '11111', ie => 0 },
189 2 => { empty => 1, ie => 0 },
190 3 => { 'barcode' => '33333', ie => 0 },
191 4 => { 'barcode' => '44444', ie => 0 },
192 5 => { empty => 1, ie => 0 },
193 6 => { empty => 1, ie => 1 },
195 }, "robot::Interface status() output is correct (one drive loaded)");
197 $steps->{'load1'}->();
202 $interface->load(4, 1, sub {
205 die $error if $error;
208 $steps->{'status3'}->();
212 step status3 => sub {
213 $interface->status(sub {
214 my ($error, $status) = @_;
216 die $error if $error;
220 0 => { barcode => '22222', 'orig_slot' => 2 },
221 1 => { barcode => '44444', 'orig_slot' => 4 },
224 1 => { 'barcode' => '11111', ie => 0 },
225 2 => { empty => 1, ie => 0 },
226 3 => { 'barcode' => '33333', ie => 0 },
227 4 => { empty => 1, ie => 0 },
228 5 => { empty => 1, ie => 0 },
229 6 => { empty => 1, ie => 1 },
231 }, "robot::Interface status() output is correct (two drives loaded)");
233 $steps->{'transfer'}->();
237 step transfer => sub {
238 $interface->transfer(3, 6, sub {
241 die $error if $error;
244 $steps->{'status4'}->();
248 step status4 => sub {
249 $interface->status(sub {
250 my ($error, $status) = @_;
252 die $error if $error;
256 0 => { barcode => '22222', 'orig_slot' => 2 },
257 1 => { barcode => '44444', 'orig_slot' => 4 },
260 1 => { 'barcode' => '11111', ie => 0 },
261 2 => { empty => 1, ie => 0 },
262 3 => { empty => 1, ie => 0 },
263 4 => { empty => 1, ie => 0 },
264 5 => { empty => 1, ie => 0 },
265 6 => { 'barcode' => '33333', ie => 1 },
267 }, "robot::Interface status() output is correct after transfer");
273 test_interface(\&Amanda::MainLoop::quit);
274 Amanda::MainLoop::run();
277 my $testconf = Installcheck::Config->new();
278 $testconf->add_changer('bum-scsi-dev', [
279 tpchanger => "\"chg-robot:does/not/exist\"",
280 property => "\"tape-device\" \"0=null:foo\"",
281 changerfile => "\"$chg_state_file\"",
283 $testconf->add_changer('no-tape-device', [
284 tpchanger => "\"chg-robot:$mtx_state_file\"",
285 changerfile => "\"$chg_state_file\"",
287 $testconf->add_changer('bad-property', [
288 tpchanger => "\"chg-robot:$mtx_state_file\"",
289 changerfile => "\"$chg_state_file\"",
290 property => "\"fast-search\" \"maybe\"",
291 property => "\"tape-device\" \"0=null:foo\"",
293 $testconf->add_changer('no-fast-search', [
294 tpchanger => "\"chg-robot:$mtx_state_file\"",
295 changerfile => "\"$chg_state_file\"",
296 property => "\"use-slots\" \"1-3,9\"",
297 property => "append \"use-slots\" \"8,5-6\"",
298 property => "\"fast-search\" \"no\"",
299 property => "\"tape-device\" \"0=null:foo\"",
301 $testconf->add_changer('delays', [
302 tpchanger => "\"chg-robot:$mtx_state_file\"",
303 # no changerfile property
304 property => "\"tape-device\" \"0=null:foo\"",
305 property => "\"status-interval\" \"1m\"",
306 property => "\"eject-delay\" \"1s\"",
307 property => "\"unload-delay\" \"2M\"",
308 property => "\"load-poll\" \"2s POLl 3s uNtil 1m\"",
313 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
314 if ($cfg_result != $CFGERR_OK) {
315 my ($level, @errors) = Amanda::Config::config_errors();
316 die(join "\n", @errors);
319 # test the changer constructor and properties
320 my $err = Amanda::Changer->new("bum-scsi-dev");
322 { message => "'does/not/exist' not found",
324 "check for device existence works");
326 $err = Amanda::Changer->new("no-tape-device");
328 { message => "no 'tape-device' property specified",
330 "tape-device property is required");
332 $err = Amanda::Changer->new("bad-property");
334 { message => "invalid 'fast-search' value",
336 "invalid boolean value handled correctly");
338 my $chg = Amanda::Changer->new("delays");
339 die "$chg" if $chg->isa("Amanda::Changer::Error");
340 is($chg->have_inventory(), '1', "changer have inventory");
341 is($chg->{'status_interval'}, 60, "status-interval parsed");
342 is($chg->{'eject_delay'}, 1, "eject-delay parsed");
343 is($chg->{'unload_delay'}, 120, "unload-delay parsed");
344 is_deeply($chg->{'load_poll'}, [ 2, 3, 60 ], "load-poll parsed");
346 # check out the statefile filename generation
347 my $dashed_mtx_state_file = $mtx_state_file;
348 $dashed_mtx_state_file =~ tr/a-zA-Z0-9/-/cs;
349 $dashed_mtx_state_file =~ s/^-*//;
350 is($chg->{'statefile'}, "$localstatedir/amanda/chg-robot-$dashed_mtx_state_file",
351 "statefile calculated correctly");
354 # test no-fast-search
355 $chg = Amanda::Changer->new("no-fast-search");
356 die "$chg" if $chg->isa("Amanda::Changer::Error");
357 is($chg->have_inventory(), '1', "changer have inventory");
359 info => ['fast_search'],
360 info_cb => make_cb(info_cb => sub {
361 my ($err, %info) = @_;
362 ok(!$info{'fast_search'}, "fast-search property works");
363 Amanda::MainLoop::quit();
365 Amanda::MainLoop::run();
368 my @allowed = map { $chg->_is_slot_allowed($_) } (0 .. 10);
369 is_deeply([ @allowed ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0 ],
370 "_is_slot_allowed parses multiple properties and behaves as expected");
378 my ($mtx_config, $finished_cb) = @_;
380 my ($res1, $res2, $mtx_state_file);
381 my $pfx = "BC=$mtx_config->{barcodes}; TORIG=$mtx_config->{track_orig}";
382 my $vtape_root = "$Installcheck::TMP/chg-robot-vtapes";
384 my $steps = define_steps
385 cb_ref => \$finished_cb,
386 finalize => sub { $chg->quit() };
390 unlink($chg_state_file) if -f $chg_state_file;
397 $mtx_state_file = setup_mock_mtx (
408 6 => '66666', # slot 6 is full, but not in use-slots
413 vtape_root => $vtape_root,
416 my @ignore_barcodes = ( property => "\"ignore-barcodes\" \"y\"")
417 if ($mtx_config->{'barcodes'} == -1);
419 my $testconf = Installcheck::Config->new();
420 $testconf->add_changer('robo', [
421 tpchanger => "\"chg-robot:$mtx_state_file\"",
422 changerfile => "\"$chg_state_file\"",
424 # point to the two vtape "drives" that mock/mtx will set up
425 property => "\"tape-device\" \"0=file:$vtape_root/drive0\"",
426 property => "append \"tape-device\" \"1=file:$vtape_root/drive1\"",
427 property => "\"use-slots\" \"1-5\"",
428 property => "\"mtx\" \"$mock_mtx_path\"",
434 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
435 if ($cfg_result != $CFGERR_OK) {
436 my ($level, @errors) = Amanda::Config::config_errors();
437 die(join "\n", @errors);
440 $steps->{'start'}->();
444 $chg = Amanda::Changer->new("robo");
445 ok(!$chg->isa("Amanda::Changer::Error"),
446 "$pfx: Create working chg-robot instance")
447 or die("no sense going on: $chg");
449 $chg->info(info => [qw(vendor_string num_slots fast_search)], info_cb => $steps->{'info_cb'});
452 step info_cb => sub {
453 my ($err, %info) = @_;
456 is_deeply({ %info }, {
459 vendor_string => "COMPAQ SSL2000 Series",
460 }, "$pfx: info keys num_slots, fast_search, vendor_string are correct");
462 $steps->{'inventory1'}->();
465 step inventory1 => sub {
466 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_1'}, [
467 { slot => 1, state => Amanda::Changer::SLOT_FULL,
468 barcode => '11111', current => 1,
469 device_status => undef, device_error => undef,
470 f_type => undef, label => undef },
471 { slot => 2, state => Amanda::Changer::SLOT_FULL,
473 device_status => undef, device_error => undef,
474 f_type => undef, label => undef },
475 { slot => 3, state => Amanda::Changer::SLOT_FULL,
477 device_status => undef, device_error => undef,
478 f_type => undef, label => undef },
479 { slot => 4, state => Amanda::Changer::SLOT_FULL,
481 device_status => undef, device_error => undef,
482 f_type => undef, label => undef },
483 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
484 device_status => undef, device_error => undef,
485 f_type => undef, label => undef },
486 ], "$pfx: inventory is correct on start-up");
489 step load_slot_1 => sub {
490 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'});
493 step loaded_slot_1 => sub {
494 (my $err, $res1) = @_;
497 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
498 "$pfx: first load returns drive-0 device");
501 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
502 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
506 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct");
508 $steps->{'load_slot_2'}->();
511 step load_slot_2 => sub {
512 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'});
515 step loaded_slot_2 => sub {
516 (my $err, $res2) = @_;
519 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
520 "$pfx: second load returns drive-1 device");
523 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
524 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
528 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct");
531 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
532 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
536 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
538 $steps->{'check_loads'}->();
541 step check_loads => sub {
542 # peek into the interface to check that things are loaded correctly
543 $chg->{'interface'}->status(sub {
544 my ($error, $status) = @_;
546 die $error if $error;
548 # only perform these checks when barcodes are enabled
549 if ($mtx_config->{'barcodes'} > 0) {
550 is_deeply($status->{'drives'}, {
551 0 => { barcode => '11111', 'orig_slot' => 1 },
552 1 => { barcode => '22222', 'orig_slot' => 2 },
553 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded");
556 $steps->{'inventory2'}->();
560 step inventory2 => sub {
561 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [
562 { slot => 1, state => Amanda::Changer::SLOT_FULL,
563 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
564 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
565 device_error => undef,
566 f_type => undef, label => undef },
567 { slot => 2, state => Amanda::Changer::SLOT_FULL,
568 barcode => '22222', reserved => 1, loaded_in => 1,
569 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
570 device_error => undef,
571 f_type => undef, label => undef },
572 { slot => 3, state => Amanda::Changer::SLOT_FULL,
574 device_status => undef, device_error => undef,
575 f_type => undef, label => undef },
576 { slot => 4, state => Amanda::Changer::SLOT_FULL,
578 device_status => undef, device_error => undef,
579 f_type => undef, label => undef },
580 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
581 device_status => undef, device_error => undef,
582 f_type => undef, label => undef },
583 ], "$pfx: inventory is updated when slots are loaded");
586 step load_slot_3 => sub {
587 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'});
590 step loaded_slot_3 => sub {
591 my ($err, $no_res) = @_;
594 { message => "no drives available",
595 reason => 'driveinuse',
597 "$pfx: trying to load a third slot fails with 'no drives available'");
599 $steps->{'label_tape_1'}->();
602 step label_tape_1 => sub {
603 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef);
604 $res1->{'device'}->finish();
606 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'});
609 step label_tape_2 => sub {
613 pass("$pfx: labeled TAPE-1 in drive 0");
616 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
617 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
618 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'},
619 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
623 slot_label => 'TAPE-1',
624 drive_label => 'TAPE-1',
625 }, "$pfx: label is correctly reflected in changer state");
628 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
629 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
631 slot_2_loaded_in => 1,
632 slot_1_loaded_in => 2,
634 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
636 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef);
637 $res2->{'device'}->finish();
639 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'});
642 step release1 => sub {
646 pass("$pfx: labeled TAPE-2 in drive 1");
649 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
650 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
651 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
652 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
656 slot_label => 'TAPE-2',
657 drive_label => 'TAPE-2',
658 }, "$pfx: label is correctly reflected in changer state");
660 $res2->release(finished_cb => $steps->{'inventory3'});
663 step inventory3 => sub {
666 pass("$pfx: slot 2/drive 1 released");
668 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [
669 { slot => 1, state => Amanda::Changer::SLOT_FULL,
670 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
671 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
672 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
673 { slot => 2, state => Amanda::Changer::SLOT_FULL,
674 barcode => '22222', loaded_in => 1,
675 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
676 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
677 { slot => 3, state => Amanda::Changer::SLOT_FULL,
679 device_status => undef, device_error => undef,
680 f_type => undef, label => undef },
681 { slot => 4, state => Amanda::Changer::SLOT_FULL,
683 device_status => undef, device_error => undef,
684 f_type => undef, label => undef },
685 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
686 device_status => undef, device_error => undef,
687 f_type => undef, label => undef },
688 ], "$pfx: inventory is still up to date");
691 step check_state_after_release1 => sub {
692 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef,
693 "$pfx: drive is not reserved");
694 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2',
695 "$pfx: tape is still in drive");
697 $steps->{'load_current_1'}->();
700 step load_current_1 => sub {
701 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'});
704 step loaded_current_1 => sub {
705 my ($err, $res) = @_;
708 { message => "the requested volume is in use (drive 0)",
709 reason => 'volinuse',
711 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)");
713 $steps->{'load_slot_4'}->();
716 # this should unload what's in drive 1 and load the empty volume in slot 4
717 step load_slot_4 => sub {
718 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'});
721 step loaded_slot_4 => sub {
722 (my $err, $res2) = @_;
725 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
726 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)");
729 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
730 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
733 slot_label => 'TAPE-2',
734 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly");
737 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
738 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
742 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct");
745 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
746 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
750 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct");
752 $steps->{'label_tape_4'}->();
755 step label_tape_4 => sub {
756 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef);
757 $res2->{'device'}->finish();
759 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'});
762 step inventory4 => sub {
765 pass("$pfx: labeled TAPE-4 in drive 1");
767 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [
768 { slot => 1, state => Amanda::Changer::SLOT_FULL,
770 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
771 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1',
772 reserved => 1, loaded_in => 0 },
773 { slot => 2, state => Amanda::Changer::SLOT_FULL,
775 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
776 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
777 { slot => 3, state => Amanda::Changer::SLOT_FULL,
779 device_status => undef, device_error => undef,
780 f_type => undef, label => undef },
781 { slot => 4, state => Amanda::Changer::SLOT_FULL,
782 barcode => '44444', reserved => 1, loaded_in => 1, current => 1,
783 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
784 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
785 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
786 device_status => undef, device_error => undef,
787 f_type => undef, label => undef },
788 ], "$pfx: inventory is up to date after more labelings");
791 step release2 => sub {
793 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
794 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
795 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
796 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
800 slot_label => 'TAPE-4',
801 drive_label => 'TAPE-4',
802 }, "$pfx: label is correctly reflected in changer state");
804 $res1->release(finished_cb => $steps->{'release2_done'});
807 step release2_done => sub {
811 pass("$pfx: slot 1/drive 0 released");
813 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1',
814 "$pfx: tape is still in drive");
816 $steps->{'release3'}->();
819 step release3 => sub {
823 $res2->release(finished_cb => $steps->{'release3_done'});
826 step release3_done => sub {
830 pass("$pfx: slot 4/drive 0 released");
832 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'},
833 'TAPE-4', "$pfx: tape is still in drive");
835 $steps->{'load_preloaded_by_slot'}->();
838 # try loading a slot that's already in a drive
839 step load_preloaded_by_slot => sub {
840 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'});
843 step loaded_preloaded_by_slot => sub {
844 (my $err, $res1) = @_;
847 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
848 "$pfx: loading a tape (by slot) that's already in a drive returns that drive");
850 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'});
853 # try again, this time by label
854 step load_preloaded_by_label => sub {
855 pass("$pfx: slot 1/drive 0 released");
857 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'});
860 step loaded_preloaded_by_label => sub {
861 (my $err, $res1) = @_;
864 is($res1->{'device'}->device_name, "file:$vtape_root/drive1",
865 "$pfx: loading a tape (by label) that's already in a drive returns that drive");
867 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'});
870 # test out searching by label
871 step load_unloaded_by_label => sub {
875 pass("$pfx: slot 4/drive 1 released");
877 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'});
880 step loaded_unloaded_by_label => sub {
881 (my $err, $res1) = @_;
884 $res1->{'device'}->read_label();
885 is($res1->{'device'}->volume_label, "TAPE-2",
886 "$pfx: loading a tape (by label) that's *not* already in a drive returns " .
887 "the correct device");
889 $steps->{'release4'}->();
892 step release4 => sub {
893 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1);
896 step release4_done => sub {
900 pass("$pfx: slot 2/drive 0 released");
903 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
904 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
905 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
908 slot_label => 'TAPE-2',
909 drive_label => undef,
910 }, "$pfx: and TAPE-2 ejected");
912 $steps->{'load_current_2'}->();
915 step load_current_2 => sub {
916 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'});
919 step loaded_current_2 => sub {
920 (my $err, $res1) = @_;
923 $res1->{'device'}->read_label();
924 is($res1->{'device'}->volume_label, "TAPE-4",
925 "$pfx: loading 'current' returns the correct device");
927 $steps->{'release5'}->();
930 step release5 => sub {
931 $res1->release(finished_cb => $steps->{'load_slot_next'});
934 step load_slot_next => sub {
938 pass("$pfx: slot 4/drive 1 released");
940 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'});
943 step loaded_slot_next => sub {
944 (my $err, $res1) = @_;
947 $res1->{'device'}->read_label();
948 is($res1->{'device'}->volume_label, "TAPE-1",
949 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " .
950 "looping around to the beginning");
952 $steps->{'load_res1_next_slot'}->();
955 step load_res1_next_slot => sub {
956 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'},
957 res_cb => $steps->{'loaded_res1_next_slot'});
960 step loaded_res1_next_slot => sub {
961 (my $err, $res2) = @_;
964 $res2->{'device'}->read_label();
965 is($res2->{'device'}->volume_label, "TAPE-2",
966 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too");
967 if ($mtx_config->{'barcodes'} == 1) {
968 is($res2->{'barcode'}, '22222',
969 "$pfx: result has a barcode");
972 $steps->{'release6'}->();
975 step release6 => sub {
976 $res1->release(finished_cb => $steps->{'release7'});
979 step release7 => sub {
983 pass("$pfx: slot 1 released");
985 $res2->release(finished_cb => $steps->{'load_disallowed_slot'});
988 step load_disallowed_slot => sub {
992 pass("$pfx: slot 2 released");
994 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'});
997 step loaded_disallowed_slot => sub {
998 (my $err, $res1) = @_;
1001 { message => "slot 6 not in use-slots (1-5)",
1002 reason => 'invalid',
1004 "$pfx: loading a disallowed slot fails propertly");
1006 $steps->{'inventory5'}->();
1009 step inventory5 => sub {
1010 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [
1011 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1012 barcode => '11111', loaded_in => 1,
1013 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1014 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1015 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1016 barcode => '22222', loaded_in => 0,
1017 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1018 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1019 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1021 device_status => undef, device_error => undef,
1022 f_type => undef, label => undef },
1023 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1024 barcode => '44444', current => 1,
1025 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1026 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1027 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1028 device_status => undef, device_error => undef,
1029 f_type => undef, label => undef },
1030 ], "$pfx: inventory still accurate");
1033 step try_update => sub {
1034 # first, add a label in slot 3, which hasn't been written
1036 my $dev = Amanda::Device->new("file:$vtape_root/slot3");
1037 die $dev->error_or_status()
1038 unless $dev->status == 0;
1039 die "error writing label"
1040 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef);
1043 # now update that slot
1044 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'});
1047 step update_finished => sub {
1051 # verify that slots 2, 3, and 4 have correct info now
1053 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1054 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'},
1055 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
1060 }, "$pfx: update correctly finds new label in slot 3");
1062 # and check barcodes otherwise
1063 if ($mtx_config->{'barcodes'} > 0) {
1065 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1066 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'},
1067 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'},
1069 barcode_2 => 'TAPE-2',
1070 barcode_3 => 'TAPE-3',
1071 barcode_4 => 'TAPE-4',
1072 }, "$pfx: bc2lb is correct, too");
1075 $steps->{'try_update2'}->();
1078 step try_update2 => sub {
1080 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'});
1083 step update_finished2 => sub {
1087 # verify the new slot info
1089 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1091 slot_2 => 'SURPRISE!',
1092 }, "$pfx: assignment-style update correctly sets new label in slot 2");
1094 # and check barcodes otherwise
1095 if ($mtx_config->{'barcodes'} > 0) {
1097 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1099 barcode_2 => 'SURPRISE!',
1100 }, "$pfx: bc2lb is correct, too");
1103 $steps->{'try_update3'}->();
1106 step try_update3 => sub {
1108 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'});
1111 step update_finished3 => sub {
1114 { message => "slot 5 is empty",
1115 reason => 'unknown',
1117 "$pfx: assignment-style update of an empty slot gives error");
1119 $steps->{'inventory6'}->();
1122 step inventory6 => sub {
1123 # note that the loading behavior of update() is not required, so the loaded_in
1124 # keys here may change if update() gets smarter
1125 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [
1126 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1128 device_status => $DEVICE_STATUS_SUCCESS,
1129 device_error => undef,
1130 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1131 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1133 device_status => $DEVICE_STATUS_SUCCESS,
1134 device_error => undef,
1135 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1136 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1137 barcode => '33333', loaded_in => 1,
1138 device_status => $DEVICE_STATUS_SUCCESS,
1139 device_error => undef,
1140 f_type => undef, label => 'TAPE-3' },
1141 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1142 barcode => '44444', loaded_in => 0, current => 1,
1143 device_status => $DEVICE_STATUS_SUCCESS,
1144 device_error => undef,
1145 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1146 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1147 device_status => undef, device_error => undef,
1148 f_type => undef, label => undef },
1149 ], "$pfx: inventory reflects updates");
1153 # move to a full slot
1154 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'});
1157 step moved1 => sub {
1161 { message => "slot 1 is not empty",
1162 reason => 'invalid',
1164 "$pfx: moving to a full slot is an error");
1166 $steps->{'move2'}->();
1170 # move to a full slot that's loaded (so there's not *actually* a tape
1172 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'});
1175 step moved2 => sub {
1179 { message => "slot 3 is not empty",
1180 reason => 'invalid',
1182 "$pfx: moving to a full slot is an error even if that slot is loaded");
1184 $steps->{'move3'}->();
1188 # move from an empty slot
1189 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'});
1192 step moved3 => sub {
1196 { message => "slot 5 is empty", # note that this depends on the order of checks..
1197 reason => 'invalid',
1199 "$pfx: moving from an empty slot is an error");
1201 $steps->{'move4'}->();
1205 # move from a loaded slot to an empty slot
1206 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'});
1209 step moved4 => sub {
1213 pass("$pfx: move of a loaded volume succeeds");
1215 $steps->{'move5'}->();
1219 $chg->move(from_slot => 2, to_slot => 4, finished_cb => $steps->{'inventory7'});
1223 step inventory7 => sub {
1227 pass("$pfx: move succeeds");
1229 # note that the loading behavior of update() is not required, so the loaded_in
1230 # keys here may change if update() gets smarter
1231 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [
1232 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1234 device_status => $DEVICE_STATUS_SUCCESS,
1235 device_error => undef,
1236 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1237 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1238 device_status => undef, device_error => undef,
1239 f_type => undef, label => undef },
1240 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1241 barcode => '33333', loaded_in => 1,
1242 device_status => $DEVICE_STATUS_SUCCESS,
1243 device_error => undef,
1244 f_type => undef, label => 'TAPE-3' },
1245 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1246 barcode => '22222', current => 1,
1247 device_status => $DEVICE_STATUS_SUCCESS,
1248 device_error => undef,
1249 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1250 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1252 device_status => $DEVICE_STATUS_SUCCESS,
1253 device_error => undef,
1254 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1255 ], "$pfx: inventory reflects the move");
1258 # test a scan, using except_slots
1261 step start_scan => sub {
1262 $chg->load(relative_slot => "current", except_slots => { %except_slots },
1263 res_cb => $steps->{'loaded_for_scan'});
1266 step loaded_for_scan => sub {
1267 (my $err, $res1) = @_;
1270 if ($err->notfound) {
1271 return $steps->{'scan_done'}->();
1272 } elsif ($err->volinuse and defined $err->{'slot'}) {
1273 $slot = $err->{'slot'};
1278 $slot = $res1->{'this_slot'};
1281 $except_slots{$slot} = 1;
1283 $res1->release(finished_cb => $steps->{'released_for_scan'});
1286 step released_for_scan => sub {
1290 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'},
1291 except_slots => { %except_slots },
1292 res_cb => $steps->{'loaded_for_scan'});
1295 step scan_done => sub {
1296 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 },
1297 "$pfx: scanning with except_slots works");
1298 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [
1299 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1300 barcode => '11111', loaded_in => 1,
1301 device_status => $DEVICE_STATUS_SUCCESS,
1302 device_error => undef,
1303 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1304 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1305 device_status => undef, device_error => undef,
1306 f_type => undef, label => undef },
1307 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1308 barcode => '33333', loaded_in => 0,
1309 device_status => $DEVICE_STATUS_SUCCESS,
1310 device_error => undef,
1311 f_type => undef, label => 'TAPE-3' },
1312 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1313 barcode => '22222', current => 1,
1314 device_status => $DEVICE_STATUS_SUCCESS,
1315 device_error => undef,
1316 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1317 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1319 device_status => $DEVICE_STATUS_SUCCESS,
1320 device_error => undef,
1321 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1322 ], "$pfx: inventory before updates with unknown state");
1325 step update_unknown => sub {
1326 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'});
1329 step update_unknown_finished => sub {
1333 if ($mtx_config->{'barcodes'} > 0) {
1334 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1335 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1336 barcode => '11111', loaded_in => 1,
1337 device_status => $DEVICE_STATUS_SUCCESS,
1338 device_error => undef,
1339 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1340 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1341 device_status => undef, device_error => undef,
1342 f_type => undef, label => undef },
1343 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1344 barcode => '33333', loaded_in => 0,
1345 device_status => $DEVICE_STATUS_SUCCESS,
1346 device_error => undef,
1347 f_type => undef, label => 'TAPE-3' },
1348 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1349 barcode => '22222', current => 1,
1350 device_status => $DEVICE_STATUS_SUCCESS,
1351 device_error => undef,
1352 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1353 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1355 device_status => $DEVICE_STATUS_SUCCESS,
1356 device_error => undef,
1357 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1358 ], "$pfx: inventory reflects updates with unknown state with barcodes");
1360 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1361 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1362 barcode => '11111', loaded_in => 1,
1363 device_status => $DEVICE_STATUS_SUCCESS,
1364 device_error => undef,
1365 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1366 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1367 device_status => undef, device_error => undef,
1368 f_type => undef, label => undef },
1369 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1370 barcode => '33333', loaded_in => 0,
1371 device_status => undef, device_error => undef,
1372 f_type => undef, label => undef },
1373 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1374 barcode => '22222', current => 1,
1375 device_status => undef, device_error => undef,
1376 f_type => undef, label => undef },
1377 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1379 device_status => $DEVICE_STATUS_SUCCESS,
1380 device_error => undef,
1381 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1382 ], "$pfx: inventory reflects updates with unknown state without barcodes");
1387 unlink($chg_state_file) if -f $chg_state_file;
1388 unlink($mtx_state_file) if -f $mtx_state_file;
1389 rmtree($vtape_root);
1395 # These tests are run over a number of different mtx configurations, to ensure
1396 # that the behavior is identical regardless of the changer/mtx characteristics
1397 for my $mtx_config (
1398 { barcodes => 1, track_orig => 1, },
1399 { barcodes => 0, track_orig => 1, },
1400 { barcodes => 1, track_orig => -1, },
1401 { barcodes => 0, track_orig => 0, },
1402 { barcodes => -1, track_orig => 0, },
1404 test_changer($mtx_config, \&Amanda::MainLoop::quit);
1405 Amanda::MainLoop::run();