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 => 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, f_type => undef, label => undef },
470 { slot => 2, state => Amanda::Changer::SLOT_FULL,
472 device_status => undef, f_type => undef, label => undef },
473 { slot => 3, state => Amanda::Changer::SLOT_FULL,
475 device_status => undef, f_type => undef, label => undef },
476 { slot => 4, state => Amanda::Changer::SLOT_FULL,
478 device_status => undef, f_type => undef, label => undef },
479 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
480 device_status => undef, f_type => undef, label => undef },
481 ], "$pfx: inventory is correct on start-up");
484 step load_slot_1 => sub {
485 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'});
488 step loaded_slot_1 => sub {
489 (my $err, $res1) = @_;
492 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
493 "$pfx: first load returns drive-0 device");
496 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
497 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
501 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct");
503 $steps->{'load_slot_2'}->();
506 step load_slot_2 => sub {
507 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'});
510 step loaded_slot_2 => sub {
511 (my $err, $res2) = @_;
514 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
515 "$pfx: second load returns drive-1 device");
518 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
519 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
523 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct");
526 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
527 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
531 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
533 $steps->{'check_loads'}->();
536 step check_loads => sub {
537 # peek into the interface to check that things are loaded correctly
538 $chg->{'interface'}->status(sub {
539 my ($error, $status) = @_;
541 die $error if $error;
543 # only perform these checks when barcodes are enabled
544 if ($mtx_config->{'barcodes'} > 0) {
545 is_deeply($status->{'drives'}, {
546 0 => { barcode => '11111', 'orig_slot' => 1 },
547 1 => { barcode => '22222', 'orig_slot' => 2 },
548 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded");
551 $steps->{'inventory2'}->();
555 step inventory2 => sub {
556 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [
557 { slot => 1, state => Amanda::Changer::SLOT_FULL,
558 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
559 device_status => undef, f_type => undef, label => undef },
560 { slot => 2, state => Amanda::Changer::SLOT_FULL,
561 barcode => '22222', reserved => 1, loaded_in => 1,
562 device_status => undef, f_type => undef, label => undef },
563 { slot => 3, state => Amanda::Changer::SLOT_FULL,
565 device_status => undef, f_type => undef, label => undef },
566 { slot => 4, state => Amanda::Changer::SLOT_FULL,
568 device_status => undef, f_type => undef, label => undef },
569 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
570 device_status => undef, f_type => undef, label => undef },
571 ], "$pfx: inventory is updated when slots are loaded");
574 step load_slot_3 => sub {
575 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'});
578 step loaded_slot_3 => sub {
579 my ($err, $no_res) = @_;
582 { message => "no drives available",
583 reason => 'driveinuse',
585 "$pfx: trying to load a third slot fails with 'no drives available'");
587 $steps->{'label_tape_1'}->();
590 step label_tape_1 => sub {
591 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef);
592 $res1->{'device'}->finish();
594 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'});
597 step label_tape_2 => sub {
601 pass("$pfx: labeled TAPE-1 in drive 0");
604 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
605 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
606 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'},
607 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
611 slot_label => 'TAPE-1',
612 drive_label => 'TAPE-1',
613 }, "$pfx: label is correctly reflected in changer state");
616 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
617 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
619 slot_2_loaded_in => 1,
620 slot_1_loaded_in => 2,
622 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
624 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef);
625 $res2->{'device'}->finish();
627 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'});
630 step release1 => sub {
634 pass("$pfx: labeled TAPE-2 in drive 1");
637 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
638 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
639 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
640 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
644 slot_label => 'TAPE-2',
645 drive_label => 'TAPE-2',
646 }, "$pfx: label is correctly reflected in changer state");
648 $res2->release(finished_cb => $steps->{'inventory3'});
651 step inventory3 => sub {
654 pass("$pfx: slot 2/drive 1 released");
656 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [
657 { slot => 1, state => Amanda::Changer::SLOT_FULL,
658 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
659 device_status => $DEVICE_STATUS_SUCCESS,
660 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
661 { slot => 2, state => Amanda::Changer::SLOT_FULL,
662 barcode => '22222', loaded_in => 1,
663 device_status => $DEVICE_STATUS_SUCCESS,
664 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
665 { slot => 3, state => Amanda::Changer::SLOT_FULL,
667 device_status => undef, f_type => undef, label => undef },
668 { slot => 4, state => Amanda::Changer::SLOT_FULL,
670 device_status => undef, f_type => undef, label => undef },
671 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
672 device_status => undef, f_type => undef, label => undef },
673 ], "$pfx: inventory is still up to date");
676 step check_state_after_release1 => sub {
677 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef,
678 "$pfx: drive is not reserved");
679 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2',
680 "$pfx: tape is still in drive");
682 $steps->{'load_current_1'}->();
685 step load_current_1 => sub {
686 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'});
689 step loaded_current_1 => sub {
690 my ($err, $res) = @_;
693 { message => "the requested volume is in use (drive 0)",
694 reason => 'volinuse',
696 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)");
698 $steps->{'load_slot_4'}->();
701 # this should unload what's in drive 1 and load the empty volume in slot 4
702 step load_slot_4 => sub {
703 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'});
706 step loaded_slot_4 => sub {
707 (my $err, $res2) = @_;
710 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
711 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)");
714 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
715 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
718 slot_label => 'TAPE-2',
719 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly");
722 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
723 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
727 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct");
730 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
731 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
735 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct");
737 $steps->{'label_tape_4'}->();
740 step label_tape_4 => sub {
741 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef);
742 $res2->{'device'}->finish();
744 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'});
747 step inventory4 => sub {
750 pass("$pfx: labeled TAPE-4 in drive 1");
752 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [
753 { slot => 1, state => Amanda::Changer::SLOT_FULL,
755 device_status => $DEVICE_STATUS_SUCCESS,
756 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1',
757 reserved => 1, loaded_in => 0 },
758 { slot => 2, state => Amanda::Changer::SLOT_FULL,
760 device_status => $DEVICE_STATUS_SUCCESS,
761 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
762 { slot => 3, state => Amanda::Changer::SLOT_FULL,
764 device_status => undef, f_type => undef, label => undef },
765 { slot => 4, state => Amanda::Changer::SLOT_FULL,
766 barcode => '44444', reserved => 1, loaded_in => 1, current => 1,
767 device_status => $DEVICE_STATUS_SUCCESS,
768 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
769 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
770 device_status => undef, f_type => undef, label => undef },
771 ], "$pfx: inventory is up to date after more labelings");
774 step release2 => sub {
776 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
777 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
778 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
779 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
783 slot_label => 'TAPE-4',
784 drive_label => 'TAPE-4',
785 }, "$pfx: label is correctly reflected in changer state");
787 $res1->release(finished_cb => $steps->{'release2_done'});
790 step release2_done => sub {
794 pass("$pfx: slot 1/drive 0 released");
796 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1',
797 "$pfx: tape is still in drive");
799 $steps->{'release3'}->();
802 step release3 => sub {
806 $res2->release(finished_cb => $steps->{'release3_done'});
809 step release3_done => sub {
813 pass("$pfx: slot 4/drive 0 released");
815 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'},
816 'TAPE-4', "$pfx: tape is still in drive");
818 $steps->{'load_preloaded_by_slot'}->();
821 # try loading a slot that's already in a drive
822 step load_preloaded_by_slot => sub {
823 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'});
826 step loaded_preloaded_by_slot => sub {
827 (my $err, $res1) = @_;
830 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
831 "$pfx: loading a tape (by slot) that's already in a drive returns that drive");
833 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'});
836 # try again, this time by label
837 step load_preloaded_by_label => sub {
838 pass("$pfx: slot 1/drive 0 released");
840 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'});
843 step loaded_preloaded_by_label => sub {
844 (my $err, $res1) = @_;
847 is($res1->{'device'}->device_name, "file:$vtape_root/drive1",
848 "$pfx: loading a tape (by label) that's already in a drive returns that drive");
850 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'});
853 # test out searching by label
854 step load_unloaded_by_label => sub {
858 pass("$pfx: slot 4/drive 1 released");
860 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'});
863 step loaded_unloaded_by_label => sub {
864 (my $err, $res1) = @_;
867 $res1->{'device'}->read_label();
868 is($res1->{'device'}->volume_label, "TAPE-2",
869 "$pfx: loading a tape (by label) that's *not* already in a drive returns " .
870 "the correct device");
872 $steps->{'release4'}->();
875 step release4 => sub {
876 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1);
879 step release4_done => sub {
883 pass("$pfx: slot 2/drive 0 released");
886 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
887 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
888 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
891 slot_label => 'TAPE-2',
892 drive_label => undef,
893 }, "$pfx: and TAPE-2 ejected");
895 $steps->{'load_current_2'}->();
898 step load_current_2 => sub {
899 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'});
902 step loaded_current_2 => sub {
903 (my $err, $res1) = @_;
906 $res1->{'device'}->read_label();
907 is($res1->{'device'}->volume_label, "TAPE-4",
908 "$pfx: loading 'current' returns the correct device");
910 $steps->{'release5'}->();
913 step release5 => sub {
914 $res1->release(finished_cb => $steps->{'load_slot_next'});
917 step load_slot_next => sub {
921 pass("$pfx: slot 4/drive 1 released");
923 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'});
926 step loaded_slot_next => sub {
927 (my $err, $res1) = @_;
930 $res1->{'device'}->read_label();
931 is($res1->{'device'}->volume_label, "TAPE-1",
932 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " .
933 "looping around to the beginning");
935 $steps->{'load_res1_next_slot'}->();
938 step load_res1_next_slot => sub {
939 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'},
940 res_cb => $steps->{'loaded_res1_next_slot'});
943 step loaded_res1_next_slot => sub {
944 (my $err, $res2) = @_;
947 $res2->{'device'}->read_label();
948 is($res2->{'device'}->volume_label, "TAPE-2",
949 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too");
950 if ($mtx_config->{'barcodes'} == 1) {
951 is($res2->{'barcode'}, '22222',
952 "$pfx: result has a barcode");
955 $steps->{'release6'}->();
958 step release6 => sub {
959 $res1->release(finished_cb => $steps->{'release7'});
962 step release7 => sub {
966 pass("$pfx: slot 1 released");
968 $res2->release(finished_cb => $steps->{'load_disallowed_slot'});
971 step load_disallowed_slot => sub {
975 pass("$pfx: slot 2 released");
977 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'});
980 step loaded_disallowed_slot => sub {
981 (my $err, $res1) = @_;
984 { message => "slot 6 not in use-slots (1-5)",
987 "$pfx: loading a disallowed slot fails propertly");
989 $steps->{'inventory5'}->();
992 step inventory5 => sub {
993 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [
994 { slot => 1, state => Amanda::Changer::SLOT_FULL,
995 barcode => '11111', loaded_in => 1,
996 device_status => $DEVICE_STATUS_SUCCESS,
997 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
998 { slot => 2, state => Amanda::Changer::SLOT_FULL,
999 barcode => '22222', loaded_in => 0,
1000 device_status => $DEVICE_STATUS_SUCCESS,
1001 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1002 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1004 device_status => undef, f_type => undef, label => undef },
1005 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1006 barcode => '44444', current => 1,
1007 device_status => $DEVICE_STATUS_SUCCESS,
1008 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1009 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1010 device_status => undef, f_type => undef, label => undef },
1011 ], "$pfx: inventory still accurate");
1014 step try_update => sub {
1015 # first, add a label in slot 3, which hasn't been written
1017 my $dev = Amanda::Device->new("file:$vtape_root/slot3");
1018 die $dev->error_or_status()
1019 unless $dev->status == 0;
1020 die "error writing label"
1021 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef);
1024 # now update that slot
1025 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'});
1028 step update_finished => sub {
1032 # verify that slots 2, 3, and 4 have correct info now
1034 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1035 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'},
1036 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
1041 }, "$pfx: update correctly finds new label in slot 3");
1043 # and check barcodes otherwise
1044 if ($mtx_config->{'barcodes'} > 0) {
1046 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1047 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'},
1048 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'},
1050 barcode_2 => 'TAPE-2',
1051 barcode_3 => 'TAPE-3',
1052 barcode_4 => 'TAPE-4',
1053 }, "$pfx: bc2lb is correct, too");
1056 $steps->{'try_update2'}->();
1059 step try_update2 => sub {
1061 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'});
1064 step update_finished2 => sub {
1068 # verify the new slot info
1070 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1072 slot_2 => 'SURPRISE!',
1073 }, "$pfx: assignment-style update correctly sets new label in slot 2");
1075 # and check barcodes otherwise
1076 if ($mtx_config->{'barcodes'} > 0) {
1078 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1080 barcode_2 => 'SURPRISE!',
1081 }, "$pfx: bc2lb is correct, too");
1084 $steps->{'try_update3'}->();
1087 step try_update3 => sub {
1089 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'});
1092 step update_finished3 => sub {
1095 { message => "slot 5 is empty",
1096 reason => 'unknown',
1098 "$pfx: assignment-style update of an empty slot gives error");
1100 $steps->{'inventory6'}->();
1103 step inventory6 => sub {
1104 # note that the loading behavior of update() is not required, so the loaded_in
1105 # keys here may change if update() gets smarter
1106 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [
1107 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1109 device_status => $DEVICE_STATUS_SUCCESS,
1110 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1111 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1113 device_status => $DEVICE_STATUS_SUCCESS,
1114 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1115 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1116 barcode => '33333', loaded_in => 1,
1117 device_status => undef, f_type => undef, label => 'TAPE-3' },
1118 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1119 barcode => '44444', loaded_in => 0, current => 1,
1120 device_status => $DEVICE_STATUS_SUCCESS,
1121 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1122 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1123 device_status => undef, f_type => undef, label => undef },
1124 ], "$pfx: inventory reflects updates");
1128 # move to a full slot
1129 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'});
1132 step moved1 => sub {
1136 { message => "slot 1 is not empty",
1137 reason => 'invalid',
1139 "$pfx: moving to a full slot is an error");
1141 $steps->{'move2'}->();
1145 # move to a full slot that's loaded (so there's not *actually* a tape
1147 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'});
1150 step moved2 => sub {
1154 { message => "slot 3 is not empty",
1155 reason => 'invalid',
1157 "$pfx: moving to a full slot is an error even if that slot is loaded");
1159 $steps->{'move3'}->();
1163 # move from an empty slot
1164 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'});
1167 step moved3 => sub {
1171 { message => "slot 5 is empty", # note that this depends on the order of checks..
1172 reason => 'invalid',
1174 "$pfx: moving from an empty slot is an error");
1176 $steps->{'move4'}->();
1180 # move from a loaded slot to an empty slot
1181 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'});
1184 step moved4 => sub {
1188 pass("$pfx: move of a loaded volume succeeds");
1190 $steps->{'move5'}->();
1194 $chg->move(from_slot => 2, to_slot => 4, finished_cb => $steps->{'inventory7'});
1198 step inventory7 => sub {
1202 pass("$pfx: move succeeds");
1204 # note that the loading behavior of update() is not required, so the loaded_in
1205 # keys here may change if update() gets smarter
1206 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [
1207 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1209 device_status => $DEVICE_STATUS_SUCCESS,
1210 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1211 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1212 device_status => undef, f_type => undef, label => undef },
1213 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1214 barcode => '33333', loaded_in => 1,
1215 device_status => undef, f_type => undef, label => 'TAPE-3' },
1216 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1217 barcode => '22222', current => 1,
1218 device_status => $DEVICE_STATUS_SUCCESS,
1219 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1220 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1222 device_status => $DEVICE_STATUS_SUCCESS,
1223 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1224 ], "$pfx: inventory reflects the move");
1227 # test a scan, using except_slots
1230 step start_scan => sub {
1231 $chg->load(relative_slot => "current", except_slots => { %except_slots },
1232 res_cb => $steps->{'loaded_for_scan'});
1235 step loaded_for_scan => sub {
1236 (my $err, $res1) = @_;
1239 if ($err->notfound) {
1240 return $steps->{'scan_done'}->();
1241 } elsif ($err->volinuse and defined $err->{'slot'}) {
1242 $slot = $err->{'slot'};
1247 $slot = $res1->{'this_slot'};
1250 $except_slots{$slot} = 1;
1252 $res1->release(finished_cb => $steps->{'released_for_scan'});
1255 step released_for_scan => sub {
1259 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'},
1260 except_slots => { %except_slots },
1261 res_cb => $steps->{'loaded_for_scan'});
1264 step scan_done => sub {
1265 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 },
1266 "$pfx: scanning with except_slots works");
1267 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [
1268 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1269 barcode => '11111', loaded_in => 1,
1270 device_status => $DEVICE_STATUS_SUCCESS,
1271 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1272 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1273 device_status => undef, f_type => undef, label => undef },
1274 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1275 barcode => '33333', loaded_in => 0,
1276 device_status => undef, f_type => undef, label => 'TAPE-3' },
1277 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1278 barcode => '22222', current => 1,
1279 device_status => $DEVICE_STATUS_SUCCESS,
1280 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1281 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1283 device_status => $DEVICE_STATUS_SUCCESS,
1284 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1285 ], "$pfx: inventory before updates with unknown state");
1288 step update_unknown => sub {
1289 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'});
1292 step update_unknown_finished => sub {
1296 if ($mtx_config->{'barcodes'} > 0) {
1297 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1298 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1299 barcode => '11111', loaded_in => 1,
1300 device_status => $DEVICE_STATUS_SUCCESS,
1301 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1302 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1303 device_status => undef, f_type => undef, label => undef },
1304 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1305 barcode => '33333', loaded_in => 0,
1306 device_status => undef, f_type => undef, label => 'TAPE-3' },
1307 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1308 barcode => '22222', current => 1,
1309 device_status => $DEVICE_STATUS_SUCCESS,
1310 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1311 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1313 device_status => $DEVICE_STATUS_SUCCESS,
1314 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1315 ], "$pfx: inventory reflects updates with unknown state with barcodes");
1317 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1318 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1319 barcode => '11111', loaded_in => 1,
1320 device_status => $DEVICE_STATUS_SUCCESS,
1321 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1322 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1323 device_status => undef, f_type => undef, label => undef },
1324 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1325 barcode => '33333', loaded_in => 0,
1326 device_status => undef, f_type => undef, label => undef },
1327 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1328 barcode => '22222', current => 1,
1329 device_status => undef, f_type => undef, label => undef },
1330 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1332 device_status => $DEVICE_STATUS_SUCCESS,
1333 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1334 ], "$pfx: inventory reflects updates with unknown state without barcodes");
1339 unlink($chg_state_file) if -f $chg_state_file;
1340 unlink($mtx_state_file) if -f $mtx_state_file;
1341 rmtree($vtape_root);
1347 # These tests are run over a number of different mtx configurations, to ensure
1348 # that the behavior is identical regardless of the changer/mtx characteristics
1349 for my $mtx_config (
1350 { barcodes => 1, track_orig => 1, },
1351 { barcodes => 0, track_orig => 1, },
1352 { barcodes => 1, track_orig => -1, },
1353 { barcodes => 0, track_orig => 0, },
1354 { barcodes => -1, track_orig => 0, },
1356 test_changer($mtx_config, \&Amanda::MainLoop::quit);
1357 Amanda::MainLoop::run();