1 # Copyright (c) 2009-2012 Zmanda Inc. All Rights Reserved.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use Test::More tests => 324;
26 use lib "@amperldir@";
28 use Installcheck::Config;
29 use Installcheck::Changer;
30 use Installcheck::Mock qw( setup_mock_mtx $mock_mtx_path );
31 use Amanda::Device qw( :constants );
35 use Amanda::Config qw( :init :getconf config_dir_relative );
38 # set up debugging so debug output doesn't interfere with test results
39 Amanda::Debug::dbopen("installcheck");
41 # and disable Debug's die() and warn() overrides
42 Amanda::Debug::disable_die_override();
43 Installcheck::log_test_output();
45 my $chg_state_file = "$Installcheck::TMP/chg-robot-state";
46 unlink($chg_state_file) if -f $chg_state_file;
48 my $mtx_state_file = setup_mock_mtx (
67 my ($chg, $barcodes, $next_step, $expected, $msg) = @_;
69 $chg->inventory(inventory_cb => make_cb(sub {
73 # strip barcodes from both $expected and $inv
75 for (@$expected, @$inv) {
76 delete $_->{'barcode'};
80 is_deeply($inv, $expected, $msg)
81 or diag("Got:\n" . Dumper($inv));
88 # test the "interface" package
91 my ($finished_cb) = @_;
92 my ($interface, $chg);
94 my $steps = define_steps
95 cb_ref => \$finished_cb,
96 finalize => sub { $chg->quit() };
99 my $testconf = Installcheck::Config->new();
100 $testconf->add_changer('robo', [
101 tpchanger => "\"chg-robot:$mtx_state_file\"",
102 changerfile => "\"$chg_state_file\"",
104 # point to the two vtape "drives" that mock/mtx will set up
105 property => "\"tape-device\" \"0=null:drive0\"",
107 # an point to the mock mtx
108 property => "\"mtx\" \"$mock_mtx_path\"",
112 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
113 if ($cfg_result != $CFGERR_OK) {
114 my ($level, @errors) = Amanda::Config::config_errors();
115 die(join "\n", @errors);
118 $chg = Amanda::Changer->new("robo");
119 die "$chg" if $chg->isa("Amanda::Changer::Error");
120 is($chg->have_inventory(), '1', "changer have inventory");
121 $interface = $chg->{'interface'};
123 $interface->inquiry($steps->{'inquiry_cb'});
126 step inquiry_cb => sub {
127 my ($error, $info) = @_;
129 die $error if $error;
132 'revision' => '0416',
133 'product id' => 'SSL2000 Series',
134 'attached changer' => 'No',
135 'vendor id' => 'COMPAQ',
136 'product type' => 'Medium Changer'
137 }, "robot::Interface inquiry() info is correct");
139 $steps->{'status1'}->();
142 step status1 => sub {
143 $interface->status(sub {
144 my ($error, $status) = @_;
146 die $error if $error;
154 1 => { 'barcode' => '11111', ie => 0 },
155 2 => { 'barcode' => '22222', ie => 0 },
156 3 => { 'barcode' => '33333', ie => 0 },
157 4 => { 'barcode' => '44444', ie => 0 },
158 5 => { empty => 1, ie => 0 },
159 6 => { empty => 1, ie => 1 },
161 }, "robot::Interface status() output is correct (no drives loaded)");
162 $steps->{'load0'}->();
167 $interface->load(2, 0, sub {
170 die $error if $error;
173 $steps->{'status2'}->();
177 step status2 => sub {
178 $interface->status(sub {
179 my ($error, $status) = @_;
181 die $error if $error;
185 0 => { barcode => '22222', 'orig_slot' => 2 },
189 1 => { 'barcode' => '11111', ie => 0 },
190 2 => { empty => 1, ie => 0 },
191 3 => { 'barcode' => '33333', ie => 0 },
192 4 => { 'barcode' => '44444', ie => 0 },
193 5 => { empty => 1, ie => 0 },
194 6 => { empty => 1, ie => 1 },
196 }, "robot::Interface status() output is correct (one drive loaded)");
198 $steps->{'load1'}->();
203 $interface->load(4, 1, sub {
206 die $error if $error;
209 $steps->{'status3'}->();
213 step status3 => sub {
214 $interface->status(sub {
215 my ($error, $status) = @_;
217 die $error if $error;
221 0 => { barcode => '22222', 'orig_slot' => 2 },
222 1 => { barcode => '44444', 'orig_slot' => 4 },
225 1 => { 'barcode' => '11111', ie => 0 },
226 2 => { empty => 1, ie => 0 },
227 3 => { 'barcode' => '33333', ie => 0 },
228 4 => { empty => 1, ie => 0 },
229 5 => { empty => 1, ie => 0 },
230 6 => { empty => 1, ie => 1 },
232 }, "robot::Interface status() output is correct (two drives loaded)");
234 $steps->{'transfer'}->();
238 step transfer => sub {
239 $interface->transfer(3, 6, sub {
242 die $error if $error;
245 $steps->{'status4'}->();
249 step status4 => sub {
250 $interface->status(sub {
251 my ($error, $status) = @_;
253 die $error if $error;
257 0 => { barcode => '22222', 'orig_slot' => 2 },
258 1 => { barcode => '44444', 'orig_slot' => 4 },
261 1 => { 'barcode' => '11111', ie => 0 },
262 2 => { empty => 1, ie => 0 },
263 3 => { empty => 1, ie => 0 },
264 4 => { empty => 1, ie => 0 },
265 5 => { empty => 1, ie => 0 },
266 6 => { 'barcode' => '33333', ie => 1 },
268 }, "robot::Interface status() output is correct after transfer");
274 test_interface(\&Amanda::MainLoop::quit);
275 Amanda::MainLoop::run();
278 my $testconf = Installcheck::Config->new();
279 $testconf->add_changer('bum-scsi-dev', [
280 tpchanger => "\"chg-robot:does/not/exist\"",
281 property => "\"tape-device\" \"0=null:foo\"",
282 changerfile => "\"$chg_state_file\"",
284 $testconf->add_changer('no-tape-device', [
285 tpchanger => "\"chg-robot:$mtx_state_file\"",
286 changerfile => "\"$chg_state_file\"",
288 $testconf->add_changer('bad-property', [
289 tpchanger => "\"chg-robot:$mtx_state_file\"",
290 changerfile => "\"$chg_state_file\"",
291 property => "\"fast-search\" \"maybe\"",
292 property => "\"tape-device\" \"0=null:foo\"",
294 $testconf->add_changer('no-fast-search', [
295 tpchanger => "\"chg-robot:$mtx_state_file\"",
296 changerfile => "\"$chg_state_file\"",
297 property => "\"use-slots\" \"1-3,9\"",
298 property => "append \"use-slots\" \"8,5-6\"",
299 property => "\"fast-search\" \"no\"",
300 property => "\"tape-device\" \"0=null:foo\"",
302 $testconf->add_changer('delays', [
303 tpchanger => "\"chg-robot:$mtx_state_file\"",
304 # no changerfile property
305 property => "\"tape-device\" \"0=null:foo\"",
306 property => "\"status-interval\" \"1m\"",
307 property => "\"eject-delay\" \"1s\"",
308 property => "\"unload-delay\" \"2M\"",
309 property => "\"load-poll\" \"2s POLl 3s uNtil 1m\"",
314 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
315 if ($cfg_result != $CFGERR_OK) {
316 my ($level, @errors) = Amanda::Config::config_errors();
317 die(join "\n", @errors);
320 # test the changer constructor and properties
321 my $err = Amanda::Changer->new("bum-scsi-dev");
323 { message => "'does/not/exist' not found",
325 "check for device existence works");
327 $err = Amanda::Changer->new("no-tape-device");
329 { message => "no 'tape-device' property specified",
331 "tape-device property is required");
333 $err = Amanda::Changer->new("bad-property");
335 { message => "invalid 'fast-search' value",
337 "invalid boolean value handled correctly");
339 my $chg = Amanda::Changer->new("delays");
340 die "$chg" if $chg->isa("Amanda::Changer::Error");
341 is($chg->have_inventory(), '1', "changer have inventory");
342 is($chg->{'status_interval'}, 60, "status-interval parsed");
343 is($chg->{'eject_delay'}, 1, "eject-delay parsed");
344 is($chg->{'unload_delay'}, 120, "unload-delay parsed");
345 is_deeply($chg->{'load_poll'}, [ 2, 3, 60 ], "load-poll parsed");
347 # check out the statefile filename generation
348 my $dashed_mtx_state_file = $mtx_state_file;
349 $dashed_mtx_state_file =~ tr/a-zA-Z0-9/-/cs;
350 $dashed_mtx_state_file =~ s/^-*//;
351 is($chg->{'statefile'}, "$localstatedir/amanda/chg-robot-$dashed_mtx_state_file",
352 "statefile calculated correctly");
355 # test no-fast-search
356 $chg = Amanda::Changer->new("no-fast-search");
357 die "$chg" if $chg->isa("Amanda::Changer::Error");
358 is($chg->have_inventory(), '1', "changer have inventory");
360 info => ['fast_search'],
361 info_cb => make_cb(info_cb => sub {
362 my ($err, %info) = @_;
363 ok(!$info{'fast_search'}, "fast-search property works");
364 Amanda::MainLoop::quit();
366 Amanda::MainLoop::run();
369 my @allowed = map { $chg->_is_slot_allowed($_) } (0 .. 10);
370 is_deeply([ @allowed ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0 ],
371 "_is_slot_allowed parses multiple properties and behaves as expected");
379 my ($mtx_config, $finished_cb) = @_;
381 my ($res1, $res2, $mtx_state_file);
382 my $pfx = "BC=$mtx_config->{barcodes}; TORIG=$mtx_config->{track_orig}";
383 my $vtape_root = "$Installcheck::TMP/chg-robot-vtapes";
385 my $steps = define_steps
386 cb_ref => \$finished_cb,
387 finalize => sub { $chg->quit() };
391 unlink($chg_state_file) if -f $chg_state_file;
398 $mtx_state_file = setup_mock_mtx (
409 6 => '66666', # slot 6 is full, but not in use-slots
414 vtape_root => $vtape_root,
417 my @ignore_barcodes = ( property => "\"ignore-barcodes\" \"y\"")
418 if ($mtx_config->{'barcodes'} == -1);
420 my $testconf = Installcheck::Config->new();
421 $testconf->add_changer('robo', [
422 tpchanger => "\"chg-robot:$mtx_state_file\"",
423 changerfile => "\"$chg_state_file\"",
425 # point to the two vtape "drives" that mock/mtx will set up
426 property => "\"tape-device\" \"0=file:$vtape_root/drive0\"",
427 property => "append \"tape-device\" \"1=file:$vtape_root/drive1\"",
428 property => "\"use-slots\" \"1-5\"",
429 property => "\"mtx\" \"$mock_mtx_path\"",
435 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
436 if ($cfg_result != $CFGERR_OK) {
437 my ($level, @errors) = Amanda::Config::config_errors();
438 die(join "\n", @errors);
441 $steps->{'start'}->();
445 $chg = Amanda::Changer->new("robo");
446 ok(!$chg->isa("Amanda::Changer::Error"),
447 "$pfx: Create working chg-robot instance")
448 or die("no sense going on: $chg");
450 $chg->info(info => [qw(vendor_string num_slots fast_search)], info_cb => $steps->{'info_cb'});
453 step info_cb => sub {
454 my ($err, %info) = @_;
457 is_deeply({ %info }, {
460 vendor_string => "COMPAQ SSL2000 Series",
461 }, "$pfx: info keys num_slots, fast_search, vendor_string are correct");
463 $steps->{'inventory1'}->();
466 step inventory1 => sub {
467 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_1'}, [
468 { slot => 1, state => Amanda::Changer::SLOT_FULL,
469 barcode => '11111', current => 1,
470 device_status => undef, device_error => undef,
471 f_type => undef, label => undef },
472 { slot => 2, state => Amanda::Changer::SLOT_FULL,
474 device_status => undef, device_error => undef,
475 f_type => undef, label => undef },
476 { slot => 3, state => Amanda::Changer::SLOT_FULL,
478 device_status => undef, device_error => undef,
479 f_type => undef, label => undef },
480 { slot => 4, state => Amanda::Changer::SLOT_FULL,
482 device_status => undef, device_error => undef,
483 f_type => undef, label => undef },
484 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
485 device_status => undef, device_error => undef,
486 f_type => undef, label => undef },
487 ], "$pfx: inventory is correct on start-up");
490 step load_slot_1 => sub {
491 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'});
494 step loaded_slot_1 => sub {
495 (my $err, $res1) = @_;
498 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
499 "$pfx: first load returns drive-0 device");
502 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
503 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
507 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct");
509 $steps->{'load_slot_2'}->();
512 step load_slot_2 => sub {
513 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'});
516 step loaded_slot_2 => sub {
517 (my $err, $res2) = @_;
520 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
521 "$pfx: second load returns drive-1 device");
524 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
525 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
529 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct");
532 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
533 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
537 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
539 $steps->{'check_loads'}->();
542 step check_loads => sub {
543 # peek into the interface to check that things are loaded correctly
544 $chg->{'interface'}->status(sub {
545 my ($error, $status) = @_;
547 die $error if $error;
549 # only perform these checks when barcodes are enabled
550 if ($mtx_config->{'barcodes'} > 0) {
551 is_deeply($status->{'drives'}, {
552 0 => { barcode => '11111', 'orig_slot' => 1 },
553 1 => { barcode => '22222', 'orig_slot' => 2 },
554 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded");
557 $steps->{'inventory2'}->();
561 step inventory2 => sub {
562 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [
563 { slot => 1, state => Amanda::Changer::SLOT_FULL,
564 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
565 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
566 device_error => "File 0 not found",
567 f_type => $Amanda::Header::F_EMPTY, label => undef },
568 { slot => 2, state => Amanda::Changer::SLOT_FULL,
569 barcode => '22222', reserved => 1, loaded_in => 1,
570 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
571 device_error => "File 0 not found",
572 f_type => $Amanda::Header::F_EMPTY, label => undef },
573 { slot => 3, state => Amanda::Changer::SLOT_FULL,
575 device_status => undef, device_error => undef,
576 f_type => undef, label => undef },
577 { slot => 4, state => Amanda::Changer::SLOT_FULL,
579 device_status => undef, device_error => undef,
580 f_type => undef, label => undef },
581 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
582 device_status => undef, device_error => undef,
583 f_type => undef, label => undef },
584 ], "$pfx: inventory is updated when slots are loaded");
587 step load_slot_3 => sub {
588 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'});
591 step loaded_slot_3 => sub {
592 my ($err, $no_res) = @_;
595 { message => "no drives available",
596 reason => 'driveinuse',
598 "$pfx: trying to load a third slot fails with 'no drives available'");
600 $steps->{'label_tape_1'}->();
603 step label_tape_1 => sub {
604 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef);
605 $res1->{'device'}->finish();
607 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'});
610 step label_tape_2 => sub {
614 pass("$pfx: labeled TAPE-1 in drive 0");
617 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
618 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
619 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'},
620 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
624 slot_label => 'TAPE-1',
625 drive_label => 'TAPE-1',
626 }, "$pfx: label is correctly reflected in changer state");
629 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
630 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
632 slot_2_loaded_in => 1,
633 slot_1_loaded_in => 2,
635 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct");
637 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef);
638 $res2->{'device'}->finish();
640 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'});
643 step release1 => sub {
647 pass("$pfx: labeled TAPE-2 in drive 1");
650 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
651 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
652 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
653 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
657 slot_label => 'TAPE-2',
658 drive_label => 'TAPE-2',
659 }, "$pfx: label is correctly reflected in changer state");
661 $res2->release(finished_cb => $steps->{'inventory3'});
664 step inventory3 => sub {
667 pass("$pfx: slot 2/drive 1 released");
669 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [
670 { slot => 1, state => Amanda::Changer::SLOT_FULL,
671 barcode => '11111', reserved => 1, loaded_in => 0, current => 1,
672 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
673 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
674 { slot => 2, state => Amanda::Changer::SLOT_FULL,
675 barcode => '22222', loaded_in => 1,
676 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
677 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
678 { slot => 3, state => Amanda::Changer::SLOT_FULL,
680 device_status => undef, device_error => undef,
681 f_type => undef, label => undef },
682 { slot => 4, state => Amanda::Changer::SLOT_FULL,
684 device_status => undef, device_error => undef,
685 f_type => undef, label => undef },
686 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
687 device_status => undef, device_error => undef,
688 f_type => undef, label => undef },
689 ], "$pfx: inventory is still up to date");
692 step check_state_after_release1 => sub {
693 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef,
694 "$pfx: drive is not reserved");
695 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2',
696 "$pfx: tape is still in drive");
698 $steps->{'load_current_1'}->();
701 step load_current_1 => sub {
702 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'});
705 step loaded_current_1 => sub {
706 my ($err, $res) = @_;
709 { message => "the requested volume is in use (drive 0)",
710 reason => 'volinuse',
712 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)");
714 $steps->{'load_slot_4'}->();
717 # this should unload what's in drive 1 and load the empty volume in slot 4
718 step load_slot_4 => sub {
719 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'});
722 step loaded_slot_4 => sub {
723 (my $err, $res2) = @_;
726 is($res2->{'device'}->device_name, "file:$vtape_root/drive1",
727 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)");
730 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
731 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
734 slot_label => 'TAPE-2',
735 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly");
738 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'},
739 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'},
743 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct");
746 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
747 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
751 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct");
753 $steps->{'label_tape_4'}->();
756 step label_tape_4 => sub {
757 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef);
758 $res2->{'device'}->finish();
760 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'});
763 step inventory4 => sub {
766 pass("$pfx: labeled TAPE-4 in drive 1");
768 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [
769 { slot => 1, state => Amanda::Changer::SLOT_FULL,
771 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
772 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1',
773 reserved => 1, loaded_in => 0 },
774 { slot => 2, state => Amanda::Changer::SLOT_FULL,
776 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
777 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
778 { slot => 3, state => Amanda::Changer::SLOT_FULL,
780 device_status => undef, device_error => undef,
781 f_type => undef, label => undef },
782 { slot => 4, state => Amanda::Changer::SLOT_FULL,
783 barcode => '44444', reserved => 1, loaded_in => 1, current => 1,
784 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
785 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
786 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
787 device_status => undef, device_error => undef,
788 f_type => undef, label => undef },
789 ], "$pfx: inventory is up to date after more labelings");
792 step release2 => sub {
794 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'},
795 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'},
796 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
797 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'},
801 slot_label => 'TAPE-4',
802 drive_label => 'TAPE-4',
803 }, "$pfx: label is correctly reflected in changer state");
805 $res1->release(finished_cb => $steps->{'release2_done'});
808 step release2_done => sub {
812 pass("$pfx: slot 1/drive 0 released");
814 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1',
815 "$pfx: tape is still in drive");
817 $steps->{'release3'}->();
820 step release3 => sub {
824 $res2->release(finished_cb => $steps->{'release3_done'});
827 step release3_done => sub {
831 pass("$pfx: slot 4/drive 0 released");
833 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'},
834 'TAPE-4', "$pfx: tape is still in drive");
836 $steps->{'load_preloaded_by_slot'}->();
839 # try loading a slot that's already in a drive
840 step load_preloaded_by_slot => sub {
841 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'});
844 step loaded_preloaded_by_slot => sub {
845 (my $err, $res1) = @_;
848 is($res1->{'device'}->device_name, "file:$vtape_root/drive0",
849 "$pfx: loading a tape (by slot) that's already in a drive returns that drive");
851 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'});
854 # try again, this time by label
855 step load_preloaded_by_label => sub {
856 pass("$pfx: slot 1/drive 0 released");
858 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'});
861 step loaded_preloaded_by_label => sub {
862 (my $err, $res1) = @_;
865 is($res1->{'device'}->device_name, "file:$vtape_root/drive1",
866 "$pfx: loading a tape (by label) that's already in a drive returns that drive");
868 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'});
871 # test out searching by label
872 step load_unloaded_by_label => sub {
876 pass("$pfx: slot 4/drive 1 released");
878 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'});
881 step loaded_unloaded_by_label => sub {
882 (my $err, $res1) = @_;
885 $res1->{'device'}->read_label();
886 is($res1->{'device'}->volume_label, "TAPE-2",
887 "$pfx: loading a tape (by label) that's *not* already in a drive returns " .
888 "the correct device");
890 $steps->{'release4'}->();
893 step release4 => sub {
894 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1);
897 step release4_done => sub {
901 pass("$pfx: slot 2/drive 0 released");
904 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'},
905 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
906 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'},
909 slot_label => 'TAPE-2',
910 drive_label => undef,
911 }, "$pfx: and TAPE-2 ejected");
913 $steps->{'load_current_2'}->();
916 step load_current_2 => sub {
917 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'});
920 step loaded_current_2 => sub {
921 (my $err, $res1) = @_;
924 $res1->{'device'}->read_label();
925 is($res1->{'device'}->volume_label, "TAPE-4",
926 "$pfx: loading 'current' returns the correct device");
928 $steps->{'release5'}->();
931 step release5 => sub {
932 $res1->release(finished_cb => $steps->{'load_slot_next'});
935 step load_slot_next => sub {
939 pass("$pfx: slot 4/drive 1 released");
941 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'});
944 step loaded_slot_next => sub {
945 (my $err, $res1) = @_;
948 $res1->{'device'}->read_label();
949 is($res1->{'device'}->volume_label, "TAPE-1",
950 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " .
951 "looping around to the beginning");
953 $steps->{'load_res1_next_slot'}->();
956 step load_res1_next_slot => sub {
957 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'},
958 res_cb => $steps->{'loaded_res1_next_slot'});
961 step loaded_res1_next_slot => sub {
962 (my $err, $res2) = @_;
965 $res2->{'device'}->read_label();
966 is($res2->{'device'}->volume_label, "TAPE-2",
967 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too");
968 if ($mtx_config->{'barcodes'} == 1) {
969 is($res2->{'barcode'}, '22222',
970 "$pfx: result has a barcode");
973 $steps->{'release6'}->();
976 step release6 => sub {
977 $res1->release(finished_cb => $steps->{'release7'});
980 step release7 => sub {
984 pass("$pfx: slot 1 released");
986 $res2->release(finished_cb => $steps->{'load_disallowed_slot'});
989 step load_disallowed_slot => sub {
993 pass("$pfx: slot 2 released");
995 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'});
998 step loaded_disallowed_slot => sub {
999 (my $err, $res1) = @_;
1002 { message => "slot 6 not in use-slots (1-5)",
1003 reason => 'invalid',
1005 "$pfx: loading a disallowed slot fails propertly");
1007 $steps->{'inventory5'}->();
1010 step inventory5 => sub {
1011 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [
1012 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1013 barcode => '11111', loaded_in => 1,
1014 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1015 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1016 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1017 barcode => '22222', loaded_in => 0,
1018 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1019 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1020 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1022 device_status => undef, device_error => undef,
1023 f_type => undef, label => undef },
1024 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1025 barcode => '44444', current => 1,
1026 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef,
1027 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1028 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1029 device_status => undef, device_error => undef,
1030 f_type => undef, label => undef },
1031 ], "$pfx: inventory still accurate");
1034 step try_update => sub {
1035 # first, add a label in slot 3, which hasn't been written
1037 my $dev = Amanda::Device->new("file:$vtape_root/slot3");
1038 die $dev->error_or_status()
1039 unless $dev->status == 0;
1040 die "error writing label"
1041 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef);
1044 # now update that slot
1045 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'});
1048 step update_finished => sub {
1052 # verify that slots 2, 3, and 4 have correct info now
1054 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1055 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'},
1056 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'},
1061 }, "$pfx: update correctly finds new label in slot 3");
1063 # and check barcodes otherwise
1064 if ($mtx_config->{'barcodes'} > 0) {
1066 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1067 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'},
1068 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'},
1070 barcode_2 => 'TAPE-2',
1071 barcode_3 => 'TAPE-3',
1072 barcode_4 => 'TAPE-4',
1073 }, "$pfx: bc2lb is correct, too");
1076 $steps->{'try_update2'}->();
1079 step try_update2 => sub {
1081 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'});
1084 step update_finished2 => sub {
1088 # verify the new slot info
1090 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'},
1092 slot_2 => 'SURPRISE!',
1093 }, "$pfx: assignment-style update correctly sets new label in slot 2");
1095 # and check barcodes otherwise
1096 if ($mtx_config->{'barcodes'} > 0) {
1098 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'},
1100 barcode_2 => 'SURPRISE!',
1101 }, "$pfx: bc2lb is correct, too");
1104 $steps->{'try_update3'}->();
1107 step try_update3 => sub {
1109 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'});
1112 step update_finished3 => sub {
1115 { message => "slot 5 is empty",
1116 reason => 'unknown',
1118 "$pfx: assignment-style update of an empty slot gives error");
1120 $steps->{'inventory6'}->();
1123 step inventory6 => sub {
1124 # note that the loading behavior of update() is not required, so the loaded_in
1125 # keys here may change if update() gets smarter
1126 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [
1127 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1129 device_status => $DEVICE_STATUS_SUCCESS,
1130 device_error => undef,
1131 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1132 { slot => 2, state => Amanda::Changer::SLOT_FULL,
1134 device_status => $DEVICE_STATUS_SUCCESS,
1135 device_error => undef,
1136 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1137 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1138 barcode => '33333', loaded_in => 1,
1139 device_status => $DEVICE_STATUS_SUCCESS,
1140 device_error => undef,
1141 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' },
1142 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1143 barcode => '44444', loaded_in => 0, current => 1,
1144 device_status => $DEVICE_STATUS_SUCCESS,
1145 device_error => undef,
1146 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1147 { slot => 5, state => Amanda::Changer::SLOT_EMPTY,
1148 device_status => undef, device_error => undef,
1149 f_type => undef, label => undef },
1150 ], "$pfx: inventory reflects updates");
1154 # move to a full slot
1155 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'});
1158 step moved1 => sub {
1162 { message => "slot 1 is not empty",
1163 reason => 'invalid',
1165 "$pfx: moving to a full slot is an error");
1167 $steps->{'move2'}->();
1171 # move to a full slot that's loaded (so there's not *actually* a tape
1173 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'});
1176 step moved2 => sub {
1180 { message => "slot 3 is not empty",
1181 reason => 'invalid',
1183 "$pfx: moving to a full slot is an error even if that slot is loaded");
1185 $steps->{'move3'}->();
1189 # move from an empty slot
1190 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'});
1193 step moved3 => sub {
1197 { message => "slot 5 is empty", # note that this depends on the order of checks..
1198 reason => 'invalid',
1200 "$pfx: moving from an empty slot is an error");
1202 $steps->{'move4'}->();
1206 # move from a loaded slot to an empty slot
1207 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'});
1210 step moved4 => sub {
1214 pass("$pfx: move of a loaded volume succeeds");
1216 $steps->{'move5'}->();
1220 $chg->move(from_slot => 2, to_slot => 4, finished_cb => $steps->{'inventory7'});
1224 step inventory7 => sub {
1228 pass("$pfx: move succeeds");
1230 # note that the loading behavior of update() is not required, so the loaded_in
1231 # keys here may change if update() gets smarter
1232 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [
1233 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1235 device_status => $DEVICE_STATUS_SUCCESS,
1236 device_error => undef,
1237 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1238 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1239 device_status => undef, device_error => undef,
1240 f_type => undef, label => undef },
1241 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1242 barcode => '33333', loaded_in => 1,
1243 device_status => $DEVICE_STATUS_SUCCESS,
1244 device_error => undef,
1245 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' },
1246 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1247 barcode => '22222', current => 1,
1248 device_status => $DEVICE_STATUS_SUCCESS,
1249 device_error => undef,
1250 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' },
1251 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1253 device_status => $DEVICE_STATUS_SUCCESS,
1254 device_error => undef,
1255 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1256 ], "$pfx: inventory reflects the move");
1259 # test a scan, using except_slots
1262 step start_scan => sub {
1263 $chg->load(relative_slot => "current", except_slots => { %except_slots },
1264 res_cb => $steps->{'loaded_for_scan'});
1267 step loaded_for_scan => sub {
1268 (my $err, $res1) = @_;
1271 if ($err->notfound) {
1272 return $steps->{'scan_done'}->();
1273 } elsif ($err->volinuse and defined $err->{'slot'}) {
1274 $slot = $err->{'slot'};
1279 $slot = $res1->{'this_slot'};
1282 $except_slots{$slot} = 1;
1284 $res1->release(finished_cb => $steps->{'released_for_scan'});
1287 step released_for_scan => sub {
1291 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'},
1292 except_slots => { %except_slots },
1293 res_cb => $steps->{'loaded_for_scan'});
1296 step scan_done => sub {
1297 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 },
1298 "$pfx: scanning with except_slots works");
1299 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [
1300 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1301 barcode => '11111', loaded_in => 1,
1302 device_status => $DEVICE_STATUS_SUCCESS,
1303 device_error => undef,
1304 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1305 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1306 device_status => undef, device_error => undef,
1307 f_type => undef, label => undef },
1308 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1309 barcode => '33333', loaded_in => 0,
1310 device_status => $DEVICE_STATUS_SUCCESS,
1311 device_error => undef,
1312 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' },
1313 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1314 barcode => '22222', current => 1,
1315 device_status => $DEVICE_STATUS_SUCCESS,
1316 device_error => undef,
1317 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1318 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1320 device_status => $DEVICE_STATUS_SUCCESS,
1321 device_error => undef,
1322 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1323 ], "$pfx: inventory before updates with unknown state");
1326 step update_unknown => sub {
1327 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'});
1330 step update_unknown_finished => sub {
1334 if ($mtx_config->{'barcodes'} > 0) {
1335 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1336 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1337 barcode => '11111', loaded_in => 1,
1338 device_status => $DEVICE_STATUS_SUCCESS,
1339 device_error => undef,
1340 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1341 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1342 device_status => undef, device_error => undef,
1343 f_type => undef, label => undef },
1344 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1345 barcode => '33333', loaded_in => 0,
1346 device_status => $DEVICE_STATUS_SUCCESS,
1347 device_error => undef,
1348 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' },
1349 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1350 barcode => '22222', current => 1,
1351 device_status => $DEVICE_STATUS_SUCCESS,
1352 device_error => undef,
1353 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' },
1354 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1356 device_status => $DEVICE_STATUS_SUCCESS,
1357 device_error => undef,
1358 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1359 ], "$pfx: inventory reflects updates wrcodesith unknown state with barcodes");
1361 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [
1362 { slot => 1, state => Amanda::Changer::SLOT_FULL,
1363 barcode => '11111', loaded_in => 1,
1364 device_status => $DEVICE_STATUS_SUCCESS,
1365 device_error => undef,
1366 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' },
1367 { slot => 2, state => Amanda::Changer::SLOT_EMPTY,
1368 device_status => undef, device_error => undef,
1369 f_type => undef, label => undef },
1370 { slot => 3, state => Amanda::Changer::SLOT_FULL,
1371 barcode => '33333', loaded_in => 0,
1372 device_status => undef, device_error => undef,
1373 f_type => undef, label => undef },
1374 { slot => 4, state => Amanda::Changer::SLOT_FULL,
1375 barcode => '22222', current => 1,
1376 device_status => undef, device_error => undef,
1377 f_type => undef, label => undef },
1378 { slot => 5, state => Amanda::Changer::SLOT_FULL,
1380 device_status => $DEVICE_STATUS_SUCCESS,
1381 device_error => undef,
1382 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' },
1383 ], "$pfx: inventory reflects updates with unknown state without barcodes");
1388 unlink($chg_state_file) if -f $chg_state_file;
1389 unlink($mtx_state_file) if -f $mtx_state_file;
1390 rmtree($vtape_root);
1396 # These tests are run over a number of different mtx configurations, to ensure
1397 # that the behavior is identical regardless of the changer/mtx characteristics
1398 for my $mtx_config (
1399 { barcodes => 1, track_orig => 1, },
1400 { barcodes => 0, track_orig => 1, },
1401 { barcodes => 1, track_orig => -1, },
1402 { barcodes => 0, track_orig => 0, },
1403 { barcodes => -1, track_orig => 0, },
1405 test_changer($mtx_config, \&Amanda::MainLoop::quit);
1406 Amanda::MainLoop::run();