1# Copyright (c) 2009-2013 Zmanda Inc. All Rights Reserved. 2# 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. 7# 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 11# for more details. 12# 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 16# 17# Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300 18# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com 19 20use Test::More tests => 324; 21use File::Path; 22use Data::Dumper; 23use strict; 24use warnings; 25 26use lib "@amperldir@"; 27use Installcheck; 28use Installcheck::Config; 29use Installcheck::Changer; 30use Installcheck::Mock qw( setup_mock_mtx $mock_mtx_path ); 31use Amanda::Device qw( :constants ); 32use Amanda::Debug; 33use Amanda::Paths; 34use Amanda::MainLoop; 35use Amanda::Config qw( :init :getconf config_dir_relative ); 36use Amanda::Changer; 37 38# set up debugging so debug output doesn't interfere with test results 39Amanda::Debug::dbopen("installcheck"); 40 41# and disable Debug's die() and warn() overrides 42Amanda::Debug::disable_die_override(); 43Installcheck::log_test_output(); 44 45my $chg_state_file = "$Installcheck::TMP/chg-robot-state"; 46unlink($chg_state_file) if -f $chg_state_file; 47 48my $mtx_state_file = setup_mock_mtx ( 49 num_slots => 5, 50 num_ie => 1, 51 barcodes => 1, 52 track_orig => 1, 53 num_drives => 2, 54 loaded_slots => { 55 1 => '11111', 56 2 => '22222', 57 3 => '33333', 58 4 => '44444', 59 # slot 5 is empty 60 }, 61 first_slot => 1, 62 first_drive => 0, 63 first_ie => 6, 64 ); 65 66sub check_inventory { 67 my ($chg, $barcodes, $next_step, $expected, $msg) = @_; 68 69 $chg->inventory(inventory_cb => make_cb(sub { 70 my ($err, $inv) = @_; 71 die $err if $err; 72 73 # strip barcodes from both $expected and $inv 74 if (!$barcodes) { 75 for (@$expected, @$inv) { 76 delete $_->{'barcode'}; 77 } 78 } 79 80 if (!is_deeply($inv, $expected, $msg)) { 81 diag("Got:\n" . Dumper($inv)); 82 exit(1); 83 } 84 85 $next_step->(); 86 })); 87} 88 89## 90# test the "interface" package 91 92sub test_interface { 93 my ($finished_cb) = @_; 94 my ($interface, $chg); 95 96 my $steps = define_steps 97 cb_ref => \$finished_cb, 98 finalize => sub { $chg->quit() }; 99 100 step start => sub { 101 my $testconf = Installcheck::Config->new(); 102 $testconf->add_changer('robo', [ 103 tpchanger => "\"chg-robot:$mtx_state_file\"", 104 changerfile => "\"$chg_state_file\"", 105 106 # point to the two vtape "drives" that mock/mtx will set up 107 property => "\"tape-device\" \"0=null:drive0\"", 108 109 # an point to the mock mtx 110 property => "\"mtx\" \"$mock_mtx_path\"", 111 ]); 112 $testconf->write(); 113 114 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF'); 115 if ($cfg_result != $CFGERR_OK) { 116 my ($level, @errors) = Amanda::Config::config_errors(); 117 die(join "\n", @errors); 118 } 119 120 $chg = Amanda::Changer->new("robo"); 121 die "$chg" if $chg->isa("Amanda::Changer::Error"); 122 is($chg->have_inventory(), '1', "changer have inventory"); 123 $interface = $chg->{'interface'}; 124 125 $interface->inquiry($steps->{'inquiry_cb'}); 126 }; 127 128 step inquiry_cb => sub { 129 my ($error, $info) = @_; 130 131 die $error if $error; 132 133 is_deeply($info, { 134 'revision' => '0416', 135 'product id' => 'SSL2000 Series', 136 'attached changer' => 'No', 137 'vendor id' => 'COMPAQ', 138 'product type' => 'Medium Changer' 139 }, "robot::Interface inquiry() info is correct"); 140 141 $steps->{'status1'}->(); 142 }; 143 144 step status1 => sub { 145 $interface->status(sub { 146 my ($error, $status) = @_; 147 148 die $error if $error; 149 150 is_deeply($status, { 151 drives => { 152 0 => undef, 153 1 => undef, 154 }, 155 slots => { 156 1 => { 'barcode' => '11111', ie => 0 }, 157 2 => { 'barcode' => '22222', ie => 0 }, 158 3 => { 'barcode' => '33333', ie => 0 }, 159 4 => { 'barcode' => '44444', ie => 0 }, 160 5 => { empty => 1, ie => 0 }, 161 6 => { empty => 1, ie => 1 }, 162 }, 163 }, "robot::Interface status() output is correct (no drives loaded)"); 164 $steps->{'load0'}->(); 165 }); 166 }; 167 168 step load0 => sub { 169 $interface->load(2, 0, sub { 170 my ($error) = @_; 171 172 die $error if $error; 173 174 pass("load"); 175 $steps->{'status2'}->(); 176 }); 177 }; 178 179 step status2 => sub { 180 $interface->status(sub { 181 my ($error, $status) = @_; 182 183 die $error if $error; 184 185 is_deeply($status, { 186 drives => { 187 0 => { barcode => '22222', 'orig_slot' => 2 }, 188 1 => undef, 189 }, 190 slots => { 191 1 => { 'barcode' => '11111', ie => 0 }, 192 2 => { empty => 1, ie => 0 }, 193 3 => { 'barcode' => '33333', ie => 0 }, 194 4 => { 'barcode' => '44444', ie => 0 }, 195 5 => { empty => 1, ie => 0 }, 196 6 => { empty => 1, ie => 1 }, 197 }, 198 }, "robot::Interface status() output is correct (one drive loaded)"); 199 200 $steps->{'load1'}->(); 201 }); 202 }; 203 204 step load1 => sub { 205 $interface->load(4, 1, sub { 206 my ($error) = @_; 207 208 die $error if $error; 209 210 pass("load"); 211 $steps->{'status3'}->(); 212 }); 213 }; 214 215 step status3 => sub { 216 $interface->status(sub { 217 my ($error, $status) = @_; 218 219 die $error if $error; 220 221 is_deeply($status, { 222 drives => { 223 0 => { barcode => '22222', 'orig_slot' => 2 }, 224 1 => { barcode => '44444', 'orig_slot' => 4 }, 225 }, 226 slots => { 227 1 => { 'barcode' => '11111', ie => 0 }, 228 2 => { empty => 1, ie => 0 }, 229 3 => { 'barcode' => '33333', ie => 0 }, 230 4 => { empty => 1, ie => 0 }, 231 5 => { empty => 1, ie => 0 }, 232 6 => { empty => 1, ie => 1 }, 233 }, 234 }, "robot::Interface status() output is correct (two drives loaded)"); 235 236 $steps->{'transfer'}->(); 237 }); 238 }; 239 240 step transfer => sub { 241 $interface->transfer(3, 6, sub { 242 my ($error) = @_; 243 244 die $error if $error; 245 246 pass("transfer"); 247 $steps->{'status4'}->(); 248 }); 249 }; 250 251 step status4 => sub { 252 $interface->status(sub { 253 my ($error, $status) = @_; 254 255 die $error if $error; 256 257 is_deeply($status, { 258 drives => { 259 0 => { barcode => '22222', 'orig_slot' => 2 }, 260 1 => { barcode => '44444', 'orig_slot' => 4 }, 261 }, 262 slots => { 263 1 => { 'barcode' => '11111', ie => 0 }, 264 2 => { empty => 1, ie => 0 }, 265 3 => { empty => 1, ie => 0 }, 266 4 => { empty => 1, ie => 0 }, 267 5 => { empty => 1, ie => 0 }, 268 6 => { 'barcode' => '33333', ie => 1 }, 269 }, 270 }, "robot::Interface status() output is correct after transfer"); 271 272 $finished_cb->(); 273 }); 274 }; 275} 276test_interface(\&Amanda::MainLoop::quit); 277Amanda::MainLoop::run(); 278 279{ 280 my $testconf = Installcheck::Config->new(); 281 $testconf->add_changer('bum-scsi-dev', [ 282 tpchanger => "\"chg-robot:does/not/exist\"", 283 property => "\"tape-device\" \"0=null:foo\"", 284 changerfile => "\"$chg_state_file\"", 285 ]); 286 $testconf->add_changer('no-tape-device', [ 287 tpchanger => "\"chg-robot:$mtx_state_file\"", 288 changerfile => "\"$chg_state_file\"", 289 ]); 290 $testconf->add_changer('bad-property', [ 291 tpchanger => "\"chg-robot:$mtx_state_file\"", 292 changerfile => "\"$chg_state_file\"", 293 property => "\"fast-search\" \"maybe\"", 294 property => "\"tape-device\" \"0=null:foo\"", 295 ]); 296 $testconf->add_changer('no-fast-search', [ 297 tpchanger => "\"chg-robot:$mtx_state_file\"", 298 changerfile => "\"$chg_state_file\"", 299 property => "\"use-slots\" \"1-3,9\"", 300 property => "append \"use-slots\" \"8,5-6\"", 301 property => "\"fast-search\" \"no\"", 302 property => "\"tape-device\" \"0=null:foo\"", 303 ]); 304 $testconf->add_changer('delays', [ 305 tpchanger => "\"chg-robot:$mtx_state_file\"", 306 # no changerfile property 307 property => "\"tape-device\" \"0=null:foo\"", 308 property => "\"status-interval\" \"1m\"", 309 property => "\"eject-delay\" \"1s\"", 310 property => "\"unload-delay\" \"2M\"", 311 property => "\"load-poll\" \"2s POLl 3s uNtil 1m\"", 312 ]); 313 $testconf->write(); 314 315 config_uninit(); 316 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF'); 317 if ($cfg_result != $CFGERR_OK) { 318 my ($level, @errors) = Amanda::Config::config_errors(); 319 die(join "\n", @errors); 320 } 321 322 # test the changer constructor and properties 323 my $err = Amanda::Changer->new("bum-scsi-dev"); 324 chg_err_like($err, 325 { message => "'does/not/exist' not found", 326 type => 'fatal' }, 327 "check for device existence works"); 328 329 $err = Amanda::Changer->new("no-tape-device"); 330 chg_err_like($err, 331 { message => "no 'tape-device' property specified", 332 type => 'fatal' }, 333 "tape-device property is required"); 334 335 $err = Amanda::Changer->new("bad-property"); 336 chg_err_like($err, 337 { message => "invalid 'fast-search' value", 338 type => 'fatal' }, 339 "invalid boolean value handled correctly"); 340 341 my $chg = Amanda::Changer->new("delays"); 342 die "$chg" if $chg->isa("Amanda::Changer::Error"); 343 is($chg->have_inventory(), '1', "changer have inventory"); 344 is($chg->{'status_interval'}, 60, "status-interval parsed"); 345 is($chg->{'eject_delay'}, 1, "eject-delay parsed"); 346 is($chg->{'unload_delay'}, 120, "unload-delay parsed"); 347 is_deeply($chg->{'load_poll'}, [ 2, 3, 60 ], "load-poll parsed"); 348 349 # check out the statefile filename generation 350 my $dashed_mtx_state_file = $mtx_state_file; 351 $dashed_mtx_state_file =~ tr/a-zA-Z0-9/-/cs; 352 $dashed_mtx_state_file =~ s/^-*//; 353 is($chg->{'statefile'}, "$localstatedir/amanda/chg-robot-$dashed_mtx_state_file", 354 "statefile calculated correctly"); 355 $chg->quit(); 356 357 # test no-fast-search 358 $chg = Amanda::Changer->new("no-fast-search"); 359 die "$chg" if $chg->isa("Amanda::Changer::Error"); 360 is($chg->have_inventory(), '1', "changer have inventory"); 361 $chg->info( 362 info => ['fast_search'], 363 info_cb => make_cb(info_cb => sub { 364 my ($err, %info) = @_; 365 ok(!$info{'fast_search'}, "fast-search property works"); 366 Amanda::MainLoop::quit(); 367 })); 368 Amanda::MainLoop::run(); 369 370 # test use-slots 371 my @allowed = map { $chg->_is_slot_allowed($_) } (0 .. 10); 372 is_deeply([ @allowed ], [ 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0 ], 373 "_is_slot_allowed parses multiple properties and behaves as expected"); 374 $chg->quit(); 375} 376 377## 378# Test the real deal 379 380sub test_changer { 381 my ($mtx_config, $finished_cb) = @_; 382 my $chg; 383 my ($res1, $res2, $mtx_state_file); 384 my $pfx = "BC=$mtx_config->{barcodes}; TORIG=$mtx_config->{track_orig}"; 385 my $vtape_root = "$Installcheck::TMP/chg-robot-vtapes"; 386 387 my $steps = define_steps 388 cb_ref => \$finished_cb, 389 finalize => sub { $chg->quit() }; 390 391 step setup => sub { 392 # clean up 393 unlink($chg_state_file) if -f $chg_state_file; 394 395 # set up some vtapes 396 rmtree($vtape_root); 397 mkpath($vtape_root); 398 399 # reset the mock mtx 400 $mtx_state_file = setup_mock_mtx ( 401 %$mtx_config, 402 num_slots => 6, 403 num_ie => 1, 404 num_drives => 2, 405 loaded_slots => { 406 1 => '11111', 407 2 => '22222', 408 3 => '33333', 409 4 => '44444', 410 # slot 5 is empty 411 6 => '66666', # slot 6 is full, but not in use-slots 412 }, 413 first_slot => 1, 414 first_drive => 0, 415 first_ie => 6, 416 vtape_root => $vtape_root, 417 ); 418 419 my @ignore_barcodes = ( property => "\"ignore-barcodes\" \"y\"") 420 if ($mtx_config->{'barcodes'} == -1); 421 422 my @broken_drive_loaded_slot = ( property => "\"broken-drive-loaded-slot\" \"y\"") 423 if ($mtx_config->{'track_orig'} != 1); 424 425 my $testconf = Installcheck::Config->new(); 426 $testconf->add_changer('robo', [ 427 tpchanger => "\"chg-robot:$mtx_state_file\"", 428 changerfile => "\"$chg_state_file\"", 429 430 # point to the two vtape "drives" that mock/mtx will set up 431 property => "\"tape-device\" \"0=file:$vtape_root/drive0\"", 432 property => "append \"tape-device\" \"1=file:$vtape_root/drive1\"", 433 property => "\"use-slots\" \"1-5\"", 434 property => "\"mtx\" \"$mock_mtx_path\"", 435 @broken_drive_loaded_slot, 436 @ignore_barcodes, 437 ]); 438 $testconf->write(); 439 440 config_uninit(); 441 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF'); 442 if ($cfg_result != $CFGERR_OK) { 443 my ($level, @errors) = Amanda::Config::config_errors(); 444 die(join "\n", @errors); 445 } 446 447 $steps->{'start'}->(); 448 }; 449 450 step start => sub { 451 $chg = Amanda::Changer->new("robo"); 452 ok(!$chg->isa("Amanda::Changer::Error"), 453 "$pfx: Create working chg-robot instance") 454 or die("no sense going on: $chg"); 455 456 $chg->info(info => [qw(vendor_string num_slots fast_search)], info_cb => $steps->{'info_cb'}); 457 }; 458 459 step info_cb => sub { 460 my ($err, %info) = @_; 461 die $err if $err; 462 463 is_deeply({ %info }, { 464 num_slots => 5, 465 fast_search => 1, 466 vendor_string => "COMPAQ SSL2000 Series", 467 }, "$pfx: info keys num_slots, fast_search, vendor_string are correct"); 468 469 $steps->{'inventory1'}->(); 470 }; 471 472 step inventory1 => sub { 473 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_1'}, [ 474 { slot => 1, state => Amanda::Changer::SLOT_FULL, 475 barcode => '11111', current => 1, 476 device_status => undef, device_error => undef, 477 f_type => undef, label => undef }, 478 { slot => 2, state => Amanda::Changer::SLOT_FULL, 479 barcode => '22222', 480 device_status => undef, device_error => undef, 481 f_type => undef, label => undef }, 482 { slot => 3, state => Amanda::Changer::SLOT_FULL, 483 barcode => '33333', 484 device_status => undef, device_error => undef, 485 f_type => undef, label => undef }, 486 { slot => 4, state => Amanda::Changer::SLOT_FULL, 487 barcode => '44444', 488 device_status => undef, device_error => undef, 489 f_type => undef, label => undef }, 490 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 491 device_status => undef, device_error => undef, 492 f_type => undef, label => undef }, 493 ], "$pfx: inventory is correct on start-up"); 494 }; 495 496 step load_slot_1 => sub { 497 $chg->load(slot => 1, res_cb => $steps->{'loaded_slot_1'}); 498 }; 499 500 step loaded_slot_1 => sub { 501 (my $err, $res1) = @_; 502 die $err if $err; 503 504 is($res1->{'device'}->device_name, "file:$vtape_root/drive0", 505 "$pfx: first load returns drive-0 device"); 506 507 is_deeply({ 508 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'}, 509 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'}, 510 }, { 511 loaded_in => 0, 512 orig_slot => 1, 513 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are correct"); 514 515 $steps->{'load_slot_2'}->(); 516 }; 517 518 step load_slot_2 => sub { 519 $chg->load(slot => 2, res_cb => $steps->{'loaded_slot_2'}); 520 }; 521 522 step loaded_slot_2 => sub { 523 (my $err, $res2) = @_; 524 die $err if $err; 525 526 is($res2->{'device'}->device_name, "file:$vtape_root/drive1", 527 "$pfx: second load returns drive-1 device"); 528 529 is_deeply({ 530 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'}, 531 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'}, 532 }, { 533 loaded_in => 0, 534 orig_slot => 1, 535 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are still correct"); 536 537 is_deeply({ 538 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'}, 539 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'}, 540 }, { 541 loaded_in => 1, 542 orig_slot => 2, 543 }, "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct"); 544 545 $steps->{'check_loads'}->(); 546 }; 547 548 step check_loads => sub { 549 # peek into the interface to check that things are loaded correctly 550 $chg->{'interface'}->status(sub { 551 my ($error, $status) = @_; 552 553 die $error if $error; 554 555 # only perform these checks when barcodes are enabled 556 if ($mtx_config->{'barcodes'} > 0) { 557 is_deeply($status->{'drives'}, { 558 0 => { barcode => '11111', 'orig_slot' => 1 }, 559 1 => { barcode => '22222', 'orig_slot' => 2 }, 560 }, "$pfx: double-check: loading drives with the changer gets the right drives loaded"); 561 } 562 563 $steps->{'inventory2'}->(); 564 }); 565 }; 566 567 step inventory2 => sub { 568 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'load_slot_3'}, [ 569 { slot => 1, state => Amanda::Changer::SLOT_FULL, 570 barcode => '11111', reserved => 1, loaded_in => 0, current => 1, 571 device_status => $DEVICE_STATUS_VOLUME_UNLABELED, 572 device_error => "File 0 not found", 573 f_type => $Amanda::Header::F_EMPTY, label => undef }, 574 { slot => 2, state => Amanda::Changer::SLOT_FULL, 575 barcode => '22222', reserved => 1, loaded_in => 1, 576 device_status => $DEVICE_STATUS_VOLUME_UNLABELED, 577 device_error => "File 0 not found", 578 f_type => $Amanda::Header::F_EMPTY, label => undef }, 579 { slot => 3, state => Amanda::Changer::SLOT_FULL, 580 barcode => '33333', 581 device_status => undef, device_error => undef, 582 f_type => undef, label => undef }, 583 { slot => 4, state => Amanda::Changer::SLOT_FULL, 584 barcode => '44444', 585 device_status => undef, device_error => undef, 586 f_type => undef, label => undef }, 587 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 588 device_status => undef, device_error => undef, 589 f_type => undef, label => undef }, 590 ], "$pfx: inventory is updated when slots are loaded"); 591 }; 592 593 step load_slot_3 => sub { 594 $chg->load(slot => 3, res_cb => $steps->{'loaded_slot_3'}); 595 }; 596 597 step loaded_slot_3 => sub { 598 my ($err, $no_res) = @_; 599 600 chg_err_like($err, 601 { message => "no drives available", 602 reason => 'driveinuse', 603 type => 'failed' }, 604 "$pfx: trying to load a third slot fails with 'no drives available'"); 605 606 $steps->{'label_tape_1'}->(); 607 }; 608 609 step label_tape_1 => sub { 610 $res1->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-1", undef); 611 $res1->{'device'}->finish(); 612 613 $res1->set_label(label => "TAPE-1", finished_cb => $steps->{'label_tape_2'}); 614 }; 615 616 step label_tape_2 => sub { 617 my ($err) = @_; 618 die $err if $err; 619 620 pass("$pfx: labeled TAPE-1 in drive 0"); 621 622 is_deeply({ 623 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'}, 624 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'}, 625 slot_label => $chg->{'__last_state'}->{'slots'}->{1}->{'label'}, 626 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 627 }, { 628 loaded_in => 0, 629 orig_slot => 1, 630 slot_label => 'TAPE-1', 631 drive_label => 'TAPE-1', 632 }, "$pfx: label is correctly reflected in changer state"); 633 634 is_deeply({ 635 slot_2_loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'}, 636 slot_1_loaded_in => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'}, 637 }, { 638 slot_2_loaded_in => 1, 639 slot_1_loaded_in => 2, 640 }, 641 "$pfx: slot 2 'loaded_in' and drive 1 'orig_slot' are correct"); 642 643 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-2", undef); 644 $res2->{'device'}->finish(); 645 646 $res2->set_label(label => "TAPE-2", finished_cb => $steps->{'release1'}); 647 }; 648 649 step release1 => sub { 650 my ($err) = @_; 651 die $err if $err; 652 653 pass("$pfx: labeled TAPE-2 in drive 1"); 654 655 is_deeply({ 656 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'}, 657 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'}, 658 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'}, 659 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 660 }, { 661 loaded_in => 1, 662 orig_slot => 2, 663 slot_label => 'TAPE-2', 664 drive_label => 'TAPE-2', 665 }, "$pfx: label is correctly reflected in changer state"); 666 667 $res2->release(finished_cb => $steps->{'inventory3'}); 668 }; 669 670 step inventory3 => sub { 671 my ($err) = @_; 672 die "$err" if $err; 673 pass("$pfx: slot 2/drive 1 released"); 674 675 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'check_state_after_release1'}, [ 676 { slot => 1, state => Amanda::Changer::SLOT_FULL, 677 barcode => '11111', reserved => 1, loaded_in => 0, current => 1, 678 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 679 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 680 { slot => 2, state => Amanda::Changer::SLOT_FULL, 681 barcode => '22222', loaded_in => 1, 682 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 683 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' }, 684 { slot => 3, state => Amanda::Changer::SLOT_FULL, 685 barcode => '33333', 686 device_status => undef, device_error => undef, 687 f_type => undef, label => undef }, 688 { slot => 4, state => Amanda::Changer::SLOT_FULL, 689 barcode => '44444', 690 device_status => undef, device_error => undef, 691 f_type => undef, label => undef }, 692 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 693 device_status => undef, device_error => undef, 694 f_type => undef, label => undef }, 695 ], "$pfx: inventory is still up to date"); 696 }; 697 698 step check_state_after_release1 => sub { 699 is($chg->{'__last_state'}->{'drives'}->{1}->{'res_info'}, undef, 700 "$pfx: drive is not reserved"); 701 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 'TAPE-2', 702 "$pfx: tape is still in drive"); 703 704 $steps->{'load_current_1'}->(); 705 }; 706 707 step load_current_1 => sub { 708 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_1'}); 709 }; 710 711 step loaded_current_1 => sub { 712 my ($err, $res) = @_; 713 714 chg_err_like($err, 715 { message => "the requested volume is in use (drive 0)", 716 reason => 'volinuse', 717 type => 'failed' }, 718 "$pfx: loading 'current' when set_current hasn't been used yet gets slot 1 (which is in use)"); 719 720 $steps->{'load_slot_4'}->(); 721 }; 722 723 # this should unload what's in drive 1 and load the empty volume in slot 4 724 step load_slot_4 => sub { 725 $chg->load(slot => 4, set_current => 1, res_cb => $steps->{'loaded_slot_4'}); 726 }; 727 728 step loaded_slot_4 => sub { 729 (my $err, $res2) = @_; 730 die "$err" if $err; 731 732 is($res2->{'device'}->device_name, "file:$vtape_root/drive1", 733 "$pfx: loaded slot 4 into drive 1 (and set current to slot 4)"); 734 735 is_deeply({ 736 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'}, 737 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'}, 738 }, { 739 loaded_in => undef, 740 slot_label => 'TAPE-2', 741 }, "$pfx: slot 2 (which was just unloaded) still tracked correctly"); 742 743 is_deeply({ 744 loaded_in => $chg->{'__last_state'}->{'slots'}->{1}->{'loaded_in'}, 745 orig_slot => $chg->{'__last_state'}->{'drives'}->{0}->{'orig_slot'}, 746 }, { 747 loaded_in => 0, 748 orig_slot => 1, 749 }, "$pfx: slot 1 'loaded_in' and drive 0 'orig_slot' are *still* correct"); 750 751 is_deeply({ 752 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'}, 753 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'}, 754 }, { 755 loaded_in => 1, 756 orig_slot => 4, 757 }, "$pfx: slot 4 'loaded_in' and drive 1 'orig_slot' are correct"); 758 759 $steps->{'label_tape_4'}->(); 760 }; 761 762 step label_tape_4 => sub { 763 $res2->{'device'}->start($Amanda::Device::ACCESS_WRITE, "TAPE-4", undef); 764 $res2->{'device'}->finish(); 765 766 $res2->set_label(label => "TAPE-4", finished_cb => $steps->{'inventory4'}); 767 }; 768 769 step inventory4 => sub { 770 my ($err) = @_; 771 die "$err" if $err; 772 pass("$pfx: labeled TAPE-4 in drive 1"); 773 774 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'release2'}, [ 775 { slot => 1, state => Amanda::Changer::SLOT_FULL, 776 barcode => '11111', 777 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 778 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1', 779 reserved => 1, loaded_in => 0 }, 780 { slot => 2, state => Amanda::Changer::SLOT_FULL, 781 barcode => '22222', 782 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 783 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' }, 784 { slot => 3, state => Amanda::Changer::SLOT_FULL, 785 barcode => '33333', 786 device_status => undef, device_error => undef, 787 f_type => undef, label => undef }, 788 { slot => 4, state => Amanda::Changer::SLOT_FULL, 789 barcode => '44444', reserved => 1, loaded_in => 1, current => 1, 790 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 791 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 792 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 793 device_status => undef, device_error => undef, 794 f_type => undef, label => undef }, 795 ], "$pfx: inventory is up to date after more labelings"); 796 }; 797 798 step release2 => sub { 799 is_deeply({ 800 loaded_in => $chg->{'__last_state'}->{'slots'}->{4}->{'loaded_in'}, 801 orig_slot => $chg->{'__last_state'}->{'drives'}->{1}->{'orig_slot'}, 802 slot_label => $chg->{'__last_state'}->{'slots'}->{4}->{'label'}, 803 drive_label => $chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 804 }, { 805 loaded_in => 1, 806 orig_slot => 4, 807 slot_label => 'TAPE-4', 808 drive_label => 'TAPE-4', 809 }, "$pfx: label is correctly reflected in changer state"); 810 811 $res1->release(finished_cb => $steps->{'release2_done'}); 812 }; 813 814 step release2_done => sub { 815 my ($err) = @_; 816 die $err if $err; 817 818 pass("$pfx: slot 1/drive 0 released"); 819 820 is($chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 'TAPE-1', 821 "$pfx: tape is still in drive"); 822 823 $steps->{'release3'}->(); 824 }; 825 826 step release3 => sub { 827 my ($err) = @_; 828 die $err if $err; 829 830 $res2->release(finished_cb => $steps->{'release3_done'}); 831 }; 832 833 step release3_done => sub { 834 my ($err) = @_; 835 die $err if $err; 836 837 pass("$pfx: slot 4/drive 0 released"); 838 839 is($chg->{'__last_state'}->{'drives'}->{1}->{'label'}, 840 'TAPE-4', "$pfx: tape is still in drive"); 841 842 $steps->{'load_preloaded_by_slot'}->(); 843 }; 844 845 # try loading a slot that's already in a drive 846 step load_preloaded_by_slot => sub { 847 $chg->load(slot => 1, res_cb => $steps->{'loaded_preloaded_by_slot'}); 848 }; 849 850 step loaded_preloaded_by_slot => sub { 851 (my $err, $res1) = @_; 852 die $err if $err; 853 854 is($res1->{'device'}->device_name, "file:$vtape_root/drive0", 855 "$pfx: loading a tape (by slot) that's already in a drive returns that drive"); 856 857 $res1->release(finished_cb => $steps->{'load_preloaded_by_label'}); 858 }; 859 860 # try again, this time by label 861 step load_preloaded_by_label => sub { 862 pass("$pfx: slot 1/drive 0 released"); 863 864 $chg->load(label => 'TAPE-4', res_cb => $steps->{'loaded_preloaded_by_label'}); 865 }; 866 867 step loaded_preloaded_by_label => sub { 868 (my $err, $res1) = @_; 869 die $err if $err; 870 871 is($res1->{'device'}->device_name, "file:$vtape_root/drive1", 872 "$pfx: loading a tape (by label) that's already in a drive returns that drive"); 873 874 $res1->release(finished_cb => $steps->{'load_unloaded_by_label'}); 875 }; 876 877 # test out searching by label 878 step load_unloaded_by_label => sub { 879 my ($err) = @_; 880 die $err if $err; 881 882 pass("$pfx: slot 4/drive 1 released"); 883 884 $chg->load(label => 'TAPE-2', res_cb => $steps->{'loaded_unloaded_by_label'}); 885 }; 886 887 step loaded_unloaded_by_label => sub { 888 (my $err, $res1) = @_; 889 die $err if $err; 890 891 $res1->{'device'}->read_label(); 892 is($res1->{'device'}->volume_label, "TAPE-2", 893 "$pfx: loading a tape (by label) that's *not* already in a drive returns " . 894 "the correct device"); 895 896 $steps->{'release4'}->(); 897 }; 898 899 step release4 => sub { 900 $res1->release(finished_cb => $steps->{'release4_done'}, eject => 1); 901 }; 902 903 step release4_done => sub { 904 my ($err) = @_; 905 die $err if $err; 906 907 pass("$pfx: slot 2/drive 0 released"); 908 909 is_deeply({ 910 loaded_in => $chg->{'__last_state'}->{'slots'}->{2}->{'loaded_in'}, 911 slot_label => $chg->{'__last_state'}->{'slots'}->{2}->{'label'}, 912 drive_label => $chg->{'__last_state'}->{'drives'}->{0}->{'label'}, 913 }, { 914 loaded_in => undef, 915 slot_label => 'TAPE-2', 916 drive_label => undef, 917 }, "$pfx: and TAPE-2 ejected"); 918 919 $steps->{'load_current_2'}->(); 920 }; 921 922 step load_current_2 => sub { 923 $chg->load(relative_slot => "current", res_cb => $steps->{'loaded_current_2'}); 924 }; 925 926 step loaded_current_2 => sub { 927 (my $err, $res1) = @_; 928 die $err if $err; 929 930 $res1->{'device'}->read_label(); 931 is($res1->{'device'}->volume_label, "TAPE-4", 932 "$pfx: loading 'current' returns the correct device"); 933 934 $steps->{'release5'}->(); 935 }; 936 937 step release5 => sub { 938 $res1->release(finished_cb => $steps->{'load_slot_next'}); 939 }; 940 941 step load_slot_next => sub { 942 my ($err) = @_; 943 die $err if $err; 944 945 pass("$pfx: slot 4/drive 1 released"); 946 947 $chg->load(relative_slot => "next", res_cb => $steps->{'loaded_slot_next'}); 948 }; 949 950 step loaded_slot_next => sub { 951 (my $err, $res1) = @_; 952 die $err if $err; 953 954 $res1->{'device'}->read_label(); 955 is($res1->{'device'}->volume_label, "TAPE-1", 956 "$pfx: loading 'next' returns the correct slot, skipping slot 5 and " . 957 "looping around to the beginning"); 958 959 $steps->{'load_res1_next_slot'}->(); 960 }; 961 962 step load_res1_next_slot => sub { 963 $chg->load(relative_slot => "next", slot => $res1->{'this_slot'}, 964 res_cb => $steps->{'loaded_res1_next_slot'}); 965 }; 966 967 step loaded_res1_next_slot => sub { 968 (my $err, $res2) = @_; 969 die $err if $err; 970 971 $res2->{'device'}->read_label(); 972 is($res2->{'device'}->volume_label, "TAPE-2", 973 "$pfx: \$res->{this_slot} + 'next' returns the correct slot, too"); 974 if ($mtx_config->{'barcodes'} == 1) { 975 is($res2->{'barcode'}, '22222', 976 "$pfx: result has a barcode"); 977 } 978 979 $steps->{'release6'}->(); 980 }; 981 982 step release6 => sub { 983 $res1->release(finished_cb => $steps->{'release7'}); 984 }; 985 986 step release7 => sub { 987 my ($err) = @_; 988 die "$err" if $err; 989 990 pass("$pfx: slot 1 released"); 991 992 $res2->release(finished_cb => $steps->{'load_disallowed_slot'}); 993 }; 994 995 step load_disallowed_slot => sub { 996 my ($err) = @_; 997 die $err if $err; 998 999 pass("$pfx: slot 2 released"); 1000 1001 $chg->load(slot => 6, res_cb => $steps->{'loaded_disallowed_slot'}); 1002 }; 1003 1004 step loaded_disallowed_slot => sub { 1005 (my $err, $res1) = @_; 1006 1007 chg_err_like($err, 1008 { message => "slot 6 not in use-slots (1-5)", 1009 reason => 'invalid', 1010 type => 'failed' }, 1011 "$pfx: loading a disallowed slot fails propertly"); 1012 1013 $steps->{'inventory5'}->(); 1014 }; 1015 1016 step inventory5 => sub { 1017 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'try_update'}, [ 1018 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1019 barcode => '11111', loaded_in => 1, 1020 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 1021 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1022 { slot => 2, state => Amanda::Changer::SLOT_FULL, 1023 barcode => '22222', loaded_in => 0, 1024 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 1025 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' }, 1026 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1027 barcode => '33333', 1028 device_status => undef, device_error => undef, 1029 f_type => undef, label => undef }, 1030 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1031 barcode => '44444', current => 1, 1032 device_status => $DEVICE_STATUS_SUCCESS, device_error => undef, 1033 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1034 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 1035 device_status => undef, device_error => undef, 1036 f_type => undef, label => undef }, 1037 ], "$pfx: inventory still accurate"); 1038 }; 1039 1040 step try_update => sub { 1041 # first, add a label in slot 3, which hasn't been written 1042 # to yet 1043 my $dev = Amanda::Device->new("file:$vtape_root/slot3"); 1044 die $dev->error_or_status() 1045 unless $dev->status == 0; 1046 die "error writing label" 1047 unless $dev->start($Amanda::Device::ACCESS_WRITE, "TAPE-3", undef); 1048 $dev->finish(); 1049 1050 # now update that slot 1051 $chg->update(changed => "2-4", finished_cb => $steps->{'update_finished'}); 1052 }; 1053 1054 step update_finished => sub { 1055 my ($err) = @_; 1056 die "$err" if $err; 1057 1058 # verify that slots 2, 3, and 4 have correct info now 1059 is_deeply({ 1060 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'}, 1061 slot_3 => $chg->{'__last_state'}->{'slots'}->{3}->{'label'}, 1062 slot_4 => $chg->{'__last_state'}->{'slots'}->{4}->{'label'}, 1063 }, { 1064 slot_2 => 'TAPE-2', 1065 slot_3 => 'TAPE-3', 1066 slot_4 => 'TAPE-4', 1067 }, "$pfx: update correctly finds new label in slot 3"); 1068 1069 # and check barcodes otherwise 1070 if ($mtx_config->{'barcodes'} > 0) { 1071 is_deeply({ 1072 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'}, 1073 barcode_3 => $chg->{'__last_state'}->{'bc2lb'}->{'33333'}, 1074 barcode_4 => $chg->{'__last_state'}->{'bc2lb'}->{'44444'}, 1075 }, { 1076 barcode_2 => 'TAPE-2', 1077 barcode_3 => 'TAPE-3', 1078 barcode_4 => 'TAPE-4', 1079 }, "$pfx: bc2lb is correct, too"); 1080 } 1081 1082 $steps->{'try_update2'}->(); 1083 }; 1084 1085 step try_update2 => sub { 1086 # lie about slot 2 1087 $chg->update(changed => "2=SURPRISE!", finished_cb => $steps->{'update_finished2'}); 1088 }; 1089 1090 step update_finished2 => sub { 1091 my ($err) = @_; 1092 die "$err" if $err; 1093 1094 # verify the new slot info 1095 is_deeply({ 1096 slot_2 => $chg->{'__last_state'}->{'slots'}->{2}->{'label'}, 1097 }, { 1098 slot_2 => 'SURPRISE!', 1099 }, "$pfx: assignment-style update correctly sets new label in slot 2"); 1100 1101 # and check barcodes otherwise 1102 if ($mtx_config->{'barcodes'} > 0) { 1103 is_deeply({ 1104 barcode_2 => $chg->{'__last_state'}->{'bc2lb'}->{'22222'}, 1105 }, { 1106 barcode_2 => 'SURPRISE!', 1107 }, "$pfx: bc2lb is correct, too"); 1108 } 1109 1110 $steps->{'try_update3'}->(); 1111 }; 1112 1113 step try_update3 => sub { 1114 # lie about slot 2 1115 $chg->update(changed => "5=NO!", finished_cb => $steps->{'update_finished3'}); 1116 }; 1117 1118 step update_finished3 => sub { 1119 my ($err) = @_; 1120 chg_err_like($err, 1121 { message => "slot 5 is empty", 1122 reason => 'unknown', 1123 type => 'failed' }, 1124 "$pfx: assignment-style update of an empty slot gives error"); 1125 1126 $steps->{'inventory6'}->(); 1127 }; 1128 1129 step inventory6 => sub { 1130 # note that the loading behavior of update() is not required, so the loaded_in 1131 # keys here may change if update() gets smarter 1132 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'move1'}, [ 1133 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1134 barcode => '11111', 1135 device_status => $DEVICE_STATUS_SUCCESS, 1136 device_error => undef, 1137 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1138 { slot => 2, state => Amanda::Changer::SLOT_FULL, 1139 barcode => '22222', 1140 device_status => $DEVICE_STATUS_SUCCESS, 1141 device_error => undef, 1142 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' }, 1143 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1144 barcode => '33333', loaded_in => 1, 1145 device_status => $DEVICE_STATUS_SUCCESS, 1146 device_error => undef, 1147 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' }, 1148 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1149 barcode => '44444', loaded_in => 0, current => 1, 1150 device_status => $DEVICE_STATUS_SUCCESS, 1151 device_error => undef, 1152 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1153 { slot => 5, state => Amanda::Changer::SLOT_EMPTY, 1154 device_status => undef, device_error => undef, 1155 f_type => undef, label => undef }, 1156 ], "$pfx: inventory reflects updates"); 1157 }; 1158 1159 step move1 => sub { 1160 # move to a full slot 1161 $chg->move(from_slot => 2, to_slot => 1, finished_cb => $steps->{'moved1'}); 1162 }; 1163 1164 step moved1 => sub { 1165 my ($err) = @_; 1166 1167 chg_err_like($err, 1168 { message => "slot 1 is not empty", 1169 reason => 'invalid', 1170 type => 'failed' }, 1171 "$pfx: moving to a full slot is an error"); 1172 1173 $steps->{'move2'}->(); 1174 }; 1175 1176 step move2 => sub { 1177 # move to a full slot that's loaded (so there's not *actually* a tape 1178 # in the slot) 1179 $chg->move(from_slot => 2, to_slot => 3, finished_cb => $steps->{'moved2'}); 1180 }; 1181 1182 step moved2 => sub { 1183 my ($err) = @_; 1184 1185 chg_err_like($err, 1186 { message => "slot 3 is not empty", 1187 reason => 'invalid', 1188 type => 'failed' }, 1189 "$pfx: moving to a full slot is an error even if that slot is loaded"); 1190 1191 $steps->{'move3'}->(); 1192 }; 1193 1194 step move3 => sub { 1195 # move from an empty slot 1196 $chg->move(from_slot => 5, to_slot => 3, finished_cb => $steps->{'moved3'}); 1197 }; 1198 1199 step moved3 => sub { 1200 my ($err) = @_; 1201 1202 chg_err_like($err, 1203 { message => "slot 5 is empty", # note that this depends on the order of checks.. 1204 reason => 'invalid', 1205 type => 'failed' }, 1206 "$pfx: moving from an empty slot is an error"); 1207 1208 $steps->{'move4'}->(); 1209 }; 1210 1211 step move4 => sub { 1212 # move from a loaded slot to an empty slot 1213 $chg->move(from_slot => 4, to_slot => 5, finished_cb => $steps->{'moved4'}); 1214 }; 1215 1216 step moved4 => sub { 1217 my ($err) = @_; 1218 die "$err" if $err; 1219 1220 pass("$pfx: move of a loaded volume succeeds"); 1221 1222 $steps->{'move5'}->(); 1223 }; 1224 1225 step move5 => sub { 1226 $chg->move(from_slot => 2, to_slot => 4, finished_cb => $steps->{'inventory7'}); 1227 }; 1228 1229 1230 step inventory7 => sub { 1231 my ($err) = @_; 1232 die $err if $err; 1233 1234 pass("$pfx: move succeeds"); 1235 1236 # note that the loading behavior of update() is not required, so the loaded_in 1237 # keys here may change if update() gets smarter 1238 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'start_scan'}, [ 1239 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1240 barcode => '11111', 1241 device_status => $DEVICE_STATUS_SUCCESS, 1242 device_error => undef, 1243 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1244 { slot => 2, state => Amanda::Changer::SLOT_EMPTY, 1245 device_status => undef, device_error => undef, 1246 f_type => undef, label => undef }, 1247 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1248 barcode => '33333', loaded_in => 1, 1249 device_status => $DEVICE_STATUS_SUCCESS, 1250 device_error => undef, 1251 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' }, 1252 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1253 barcode => '22222', current => 1, 1254 device_status => $DEVICE_STATUS_SUCCESS, 1255 device_error => undef, 1256 f_type => $Amanda::Header::F_TAPESTART, label => 'SURPRISE!' }, 1257 { slot => 5, state => Amanda::Changer::SLOT_FULL, 1258 barcode => '44444', 1259 device_status => $DEVICE_STATUS_SUCCESS, 1260 device_error => undef, 1261 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1262 ], "$pfx: inventory reflects the move"); 1263 }; 1264 1265 # test a scan, using except_slots 1266 my %except_slots; 1267 1268 step start_scan => sub { 1269 $chg->load(relative_slot => "current", except_slots => { %except_slots }, 1270 res_cb => $steps->{'loaded_for_scan'}); 1271 }; 1272 1273 step loaded_for_scan => sub { 1274 (my $err, $res1) = @_; 1275 my $slot; 1276 if ($err) { 1277 if ($err->notfound) { 1278 return $steps->{'scan_done'}->(); 1279 } elsif ($err->volinuse and defined $err->{'slot'}) { 1280 $slot = $err->{'slot'}; 1281 } else { 1282 die $err; 1283 } 1284 } else { 1285 $slot = $res1->{'this_slot'}; 1286 } 1287 1288 $except_slots{$slot} = 1; 1289 1290 $res1->release(finished_cb => $steps->{'released_for_scan'}); 1291 }; 1292 1293 step released_for_scan => sub { 1294 my ($err) = @_; 1295 die $err if $err; 1296 1297 $chg->load(relative_slot => 'next', slot => $res1->{'this_slot'}, 1298 except_slots => { %except_slots }, 1299 res_cb => $steps->{'loaded_for_scan'}); 1300 }; 1301 1302 step scan_done => sub { 1303 is_deeply({ %except_slots }, { 4=>1, 5=>1, 1=>1, 3=>1 }, 1304 "$pfx: scanning with except_slots works"); 1305 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'update_unknown'}, [ 1306 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1307 barcode => '11111', loaded_in => 1, 1308 device_status => $DEVICE_STATUS_SUCCESS, 1309 device_error => undef, 1310 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1311 { slot => 2, state => Amanda::Changer::SLOT_EMPTY, 1312 device_status => undef, device_error => undef, 1313 f_type => undef, label => undef }, 1314 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1315 barcode => '33333', loaded_in => 0, 1316 device_status => $DEVICE_STATUS_SUCCESS, 1317 device_error => undef, 1318 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-3' }, 1319 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1320 barcode => '22222', current => 1, 1321 device_status => $DEVICE_STATUS_SUCCESS, 1322 device_error => undef, 1323 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-2' }, 1324 { slot => 5, state => Amanda::Changer::SLOT_FULL, 1325 barcode => '44444', 1326 device_status => $DEVICE_STATUS_SUCCESS, 1327 device_error => undef, 1328 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1329 ], "$pfx: inventory before updates with unknown state"); 1330 }; 1331 1332 step update_unknown => sub { 1333 $chg->update(changed => "3-4=", finished_cb => $steps->{'update_unknown_finished'}); 1334 }; 1335 1336 step update_unknown_finished => sub { 1337 my ($err) = @_; 1338 die "$err" if $err; 1339 1340 if ($mtx_config->{'barcodes'} > 0) { 1341 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [ 1342 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1343 barcode => '11111', loaded_in => 1, 1344 device_status => $DEVICE_STATUS_SUCCESS, 1345 device_error => undef, 1346 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1347 { slot => 2, state => Amanda::Changer::SLOT_EMPTY, 1348 device_status => undef, device_error => undef, 1349 f_type => undef, label => undef }, 1350 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1351 barcode => '33333', loaded_in => 0, 1352 device_status => undef, 1353 device_error => undef, 1354 f_type => undef, label => undef }, 1355 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1356 barcode => '22222', current => 1, 1357 device_status => undef, 1358 device_error => undef, 1359 f_type => undef, label => undef }, 1360 { slot => 5, state => Amanda::Changer::SLOT_FULL, 1361 barcode => '44444', 1362 device_status => $DEVICE_STATUS_SUCCESS, 1363 device_error => undef, 1364 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1365 ], "$pfx: inventory reflects updates wrcodesith unknown state with barcodes"); 1366 } else { 1367 check_inventory($chg, $mtx_config->{'barcodes'} > 0, $steps->{'quit'}, [ 1368 { slot => 1, state => Amanda::Changer::SLOT_FULL, 1369 barcode => '11111', loaded_in => 1, 1370 device_status => $DEVICE_STATUS_SUCCESS, 1371 device_error => undef, 1372 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-1' }, 1373 { slot => 2, state => Amanda::Changer::SLOT_EMPTY, 1374 device_status => undef, device_error => undef, 1375 f_type => undef, label => undef }, 1376 { slot => 3, state => Amanda::Changer::SLOT_FULL, 1377 barcode => '33333', loaded_in => 0, 1378 device_status => undef, device_error => undef, 1379 f_type => undef, label => undef }, 1380 { slot => 4, state => Amanda::Changer::SLOT_FULL, 1381 barcode => '22222', current => 1, 1382 device_status => undef, device_error => undef, 1383 f_type => undef, label => undef }, 1384 { slot => 5, state => Amanda::Changer::SLOT_FULL, 1385 barcode => '44444', 1386 device_status => $DEVICE_STATUS_SUCCESS, 1387 device_error => undef, 1388 f_type => $Amanda::Header::F_TAPESTART, label => 'TAPE-4' }, 1389 ], "$pfx: inventory reflects updates with unknown state without barcodes"); 1390 } 1391 }; 1392 1393 step quit => sub { 1394 unlink($chg_state_file) if -f $chg_state_file; 1395 unlink($mtx_state_file) if -f $mtx_state_file; 1396 rmtree($vtape_root); 1397 1398 $finished_cb->(); 1399 }; 1400} 1401 1402# These tests are run over a number of different mtx configurations, to ensure 1403# that the behavior is identical regardless of the changer/mtx characteristics 1404for my $mtx_config ( 1405 { barcodes => 1, track_orig => 1, }, 1406 { barcodes => 0, track_orig => 1, }, 1407 { barcodes => 1, track_orig => -1, }, 1408 { barcodes => 0, track_orig => 0, }, 1409 { barcodes => -1, track_orig => 0, }, 1410 ) { 1411 test_changer($mtx_config, \&Amanda::MainLoop::quit); 1412 Amanda::MainLoop::run(); 1413} 1414