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 => 43;
26 use lib "@amperldir@";
28 use Installcheck::Config;
29 use Installcheck::Changer;
31 use Amanda::Device qw( :constants );
34 use Amanda::Config qw( :init :getconf config_dir_relative );
37 my $tapebase = "$Installcheck::TMP/Amanda_Changer_rait_test";
40 for my $root (1 .. 3) {
41 my $taperoot = "$tapebase/$root";
47 for my $slot (1 .. 4) {
48 mkdir("$taperoot/slot$slot")
49 or die("Could not mkdir: $!");
55 my ($root, $slot, $label) = @_;
56 mkpath("$tapebase/tmp");
57 symlink("$tapebase/$root/slot$slot", "$tapebase/tmp/data");
58 my $dev = Amanda::Device->new("file:$tapebase/tmp");
59 $dev->start($Amanda::Device::ACCESS_WRITE, $label, undef)
60 or die $dev->error_or_status();
62 or die $dev->error_or_status();
63 rmtree("$tapebase/tmp");
66 # set up debugging so debug output doesn't interfere with test results
67 Amanda::Debug::dbopen("installcheck");
68 Installcheck::log_test_output();
70 # and disable Debug's die() and warn() overrides
71 Amanda::Debug::disable_die_override();
73 my $testconf = Installcheck::Config->new();
76 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
77 if ($cfg_result != $CFGERR_OK) {
78 my ($level, @errors) = Amanda::Config::config_errors();
79 die(join "\n", @errors);
83 label_vtape(1,1,"mytape");
84 label_vtape(2,3,"mytape");
85 label_vtape(3,4,"mytape");
87 my $err = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/1");
89 { message => "chg-rait needs at least two child changers",
91 "single child device detected and handled");
93 $err = Amanda::Changer->new("chg-rait:chg-disk:{$tapebase/13,$tapebase/14}");
95 { message => qr/chg-disk.*13: directory.*; chg-disk.*14: directory.*/,
97 "constructor errors in child devices detected and handled");
101 my ($finished_cb) = @_;
103 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
104 pass("Create 3-way RAIT of vtapes");
106 my $steps = define_steps
107 cb_ref => \$finished_cb,
108 finalize => sub { $chg->quit() };
110 step get_info => sub {
111 $chg->info(info_cb => $steps->{'check_info'},
112 info => [ 'num_slots', 'vendor_string', 'fast_search' ]);
115 step check_info => sub {
116 my ($err, %results) = @_;
117 die($err) if defined($err);
119 is($results{'num_slots'}, 4,
120 "info() returns the correct num_slots");
121 is($results{'vendor_string'}, '{chg-disk,chg-disk,chg-disk}',
122 "info() returns the correct vendor string");
123 is($results{'fast_search'}, 1,
124 "info() returns the correct fast_search");
126 $steps->{'do_load_current'}->();
129 step do_load_current => sub {
130 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
133 step got_res_current => sub {
134 my ($err, $res) = @_;
135 ok(!$err, "no error loading slot 'current'")
137 is($res->{'device'}->device_name,
138 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
139 "returns correct device name");
140 is($res->{'this_slot'}, '{1,1,1}',
141 "returns correct 'this_slot' name");
143 $res->release(finished_cb => $steps->{'do_load_next'});
146 step do_load_next => sub {
150 # (use a slot-relative 'next', rather than relative to current)
151 $chg->load(relative_slot => "next", slot => '{1,1,1}', res_cb => $steps->{'got_res_next'});
154 step got_res_next => sub {
155 my ($err, $res) = @_;
156 ok(!$err, "no error loading slot 'next'")
158 is($res->{'device'}->device_name,
159 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
160 "returns correct device name");
161 is($res->{'this_slot'}, '{2,2,2}',
162 "returns correct 'this_slot' name");
164 $res->release(finished_cb => $steps->{'do_load_label'});
167 step do_load_label => sub {
171 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
174 step got_res_label => sub {
175 my ($err, $res) = @_;
176 ok(!$err, "no error loading slot 'label'")
178 is($res->{'device'}->device_name,
179 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
180 "returns correct device name");
181 is($res->{'this_slot'}, '{1,3,4}',
182 "returns correct 'this_slot' name, even with different slots");
184 $res->release(finished_cb => $steps->{'do_load_slot'});
187 step do_load_slot => sub {
191 $chg->load(slot => "{1,2,3}", res_cb => $steps->{'got_res_slot'});
194 step got_res_slot => sub {
195 my ($err, $res) = @_;
196 ok(!$err, "no error loading slot '{1,2,3}'")
198 is($res->{'device'}->device_name,
199 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
200 "returns correct device name");
201 is($res->{'this_slot'}, '{1,2,3}',
202 "returns the 'this_slot' I requested");
204 $res->release(finished_cb => $steps->{'do_load_slot_nobraces'});
207 step do_load_slot_nobraces => sub {
211 # test the shorthand "2" -> "{2,2,2}"
212 $chg->load(slot => "2", res_cb => $steps->{'got_res_slot_nobraces'});
215 step got_res_slot_nobraces => sub {
216 my ($err, $res) = @_;
217 ok(!$err, "no error loading slot '2'")
219 is($res->{'device'}->device_name,
220 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
221 "returns correct device name");
222 is($res->{'this_slot'}, '{2,2,2}',
223 "returns an expanded 'this_slot' of {2,2,2} in response to the shorthand '2'");
225 $res->release(finished_cb => $steps->{'do_load_slot_failure'});
228 step do_load_slot_failure => sub {
232 $chg->load(slot => "{1,99,1}", res_cb => $steps->{'got_res_slot_failure'});
235 step got_res_slot_failure => sub {
236 my ($err, $res) = @_;
238 { message => qr/from chg-disk.*2: Slot 99 not found/,
240 reason => 'invalid' },
241 "failure of a child to load a slot is correctly propagated");
243 $steps->{'do_load_slot_multifailure'}->();
246 step do_load_slot_multifailure => sub {
250 $chg->load(slot => "{99,1,99}", res_cb => $steps->{'got_res_slot_multifailure'});
253 step got_res_slot_multifailure => sub {
254 my ($err, $res) = @_;
256 { message => qr/from chg-disk.*1: Slot 99 not found; from chg-disk.*3: /,
258 reason => 'invalid' },
259 "failure of multiple chilren to load a slot is correctly propagated");
261 $steps->{'do_inventory'}->();
264 step do_inventory => sub {
265 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
268 step got_inventory => sub {
269 my ($err, $inv) = @_;
273 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
274 f_type => $Amanda::Header::F_EMPTY, label => undef, # undef because labels don't match
276 slot => '{1,1,1}', import_export => undef },
277 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
278 f_type => $Amanda::Header::F_EMPTY, label => undef, # all blank
280 slot => '{2,2,2}', import_export => undef },
281 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
282 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
284 slot => '{3,3,3}', import_export => undef },
285 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
286 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
288 slot => '{4,4,4}', import_export => undef } ,
289 ], "inventory is correct");
294 test_threeway(\&Amanda::MainLoop::quit);
295 Amanda::MainLoop::run();
297 sub test_threeway_error {
298 my ($finished_cb) = @_;
300 my $chg = Amanda::Changer->new("chg-rait:{chg-disk:$tapebase/1,chg-disk:$tapebase/2,ERROR}");
301 pass("Create 3-way RAIT of vtapes, with the third errored out");
302 is($chg->have_inventory(), '1', "changer have inventory");
304 my $steps = define_steps
305 cb_ref => \$finished_cb,
306 finalize => sub { $chg->quit() };
308 step get_info => sub {
309 $chg->info(info_cb => $steps->{'check_info'},
310 info => [ 'num_slots', 'fast_search' ]);
313 step check_info => sub {
316 die($err) if defined($err);
318 is($results{'num_slots'}, 4, "info() returns the correct num_slots");
319 is($results{'fast_search'}, 1, "info() returns the correct fast_search");
321 $steps->{'do_load_current'}->();
324 step do_load_current => sub {
325 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
328 step got_res_current => sub {
329 my ($err, $res) = @_;
330 ok(!$err, "no error loading slot 'current'")
332 is($res->{'device'}->device_name,
333 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
334 "returns correct device name");
335 is($res->{'this_slot'}, '{1,1,ERROR}',
336 "returns correct 'this_slot' name");
338 $res->release(finished_cb => $steps->{'do_load_label'});
341 step do_load_label => sub {
345 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
348 step got_res_label => sub {
349 my ($err, $res) = @_;
350 ok(!$err, "no error loading slot 'label'")
352 is($res->{'device'}->device_name,
353 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
354 "returns correct device name");
355 is($res->{'this_slot'}, '{1,3,ERROR}',
356 "returns correct 'this_slot' name, even with different slots");
358 $res->release(finished_cb => $steps->{'released'});
361 step released => sub {
365 $steps->{'do_reset'}->();
368 # unfortunately, reset, clean, and update are pretty boring with vtapes, so
369 # it's hard to test them effectively.
371 step do_reset => sub {
375 $chg->reset(finished_cb => $steps->{'finished_reset'});
378 step finished_reset => sub {
380 ok(!$err, "no error resetting");
385 test_threeway_error(\&Amanda::MainLoop::quit);
386 Amanda::MainLoop::run();
388 # test inventory under "normal" circumstances
389 sub test_normal_inventory {
390 my ($finished_cb) = @_;
392 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
393 pass("Create 3-way RAIT of vtapes with correctly-labeled children");
395 my $steps = define_steps
396 cb_ref => \$finished_cb,
397 finalize => sub { $chg->quit() };
401 label_vtape(1,1,"mytape-1");
402 label_vtape(2,1,"mytape-1");
403 label_vtape(3,1,"mytape-1");
404 label_vtape(1,2,"mytape-2");
405 label_vtape(2,2,"mytape-2");
406 label_vtape(3,2,"mytape-2");
408 $steps->{'do_inventory'}->();
411 step do_inventory => sub {
412 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
415 step got_inventory => sub {
416 my ($err, $inv) = @_;
420 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-1', reserved => 0,
421 slot => '{1,1,1}', import_export => undef },
422 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-2', reserved => 0,
423 slot => '{2,2,2}', import_export => undef },
424 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
425 slot => '{3,3,3}', import_export => undef },
426 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
427 slot => '{4,4,4}', import_export => undef } ,
428 ], "second inventory is correct");
433 test_normal_inventory(\&Amanda::MainLoop::quit);
434 Amanda::MainLoop::run();
437 # Test configuring the device with device_property
439 $testconf = Installcheck::Config->new();
440 $testconf->add_changer("myrait", [
441 tpchanger => "\"chg-rait:chg-disk:$tapebase/{1,2,3}\"",
442 device_property => '"comment" "hello, world"',
447 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
448 if ($cfg_result != $CFGERR_OK) {
449 my ($level, @errors) = Amanda::Config::config_errors();
450 die(join "\n", @errors);
454 sub test_properties {
455 my ($finished_cb) = @_;
457 my $chg = Amanda::Changer->new("myrait");
458 ok($chg->isa("Amanda::Changer::rait"),
459 "Create RAIT device from a named config subsection");
461 my $steps = define_steps
462 cb_ref => \$finished_cb,
463 finalize => sub { $chg->quit() };
465 step do_load_1 => sub {
467 label_vtape(1,1,"mytape");
468 label_vtape(2,2,"mytape");
469 label_vtape(3,3,"mytape");
471 $chg->load(slot => "1", res_cb => $steps->{'got_res_1'});
474 step got_res_1 => sub {
475 my ($err, $res) = @_;
476 ok(!$err, "no error loading slot '1'")
478 is($res->{'device'}->device_name,
479 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
480 "returns correct (full) device name");
481 is($res->{'this_slot'}, '{1,1,1}',
482 "returns correct 'this_slot' name");
483 is($res->{'device'}->property_get("comment"), "hello, world",
484 "property from device_property appears on RAIT device");
486 $res->release(finished_cb => $steps->{'quit'});
496 test_properties(\&Amanda::MainLoop::quit);
497 Amanda::MainLoop::run();
499 # scan the changer using except_slots
500 sub test_except_slots {
501 my ($finished_cb) = @_;
506 my $steps = define_steps
507 cb_ref => \$finished_cb,
508 finalize => sub { $chg->quit() if defined $chg };
511 $chg = Amanda::Changer->new("myrait");
512 die "error creating" unless $chg->isa("Amanda::Changer::rait");
514 $chg->load(relative_slot => "current", except_slots => { %except_slots },
515 res_cb => $steps->{'loaded'});
519 my ($err, $res) = @_;
521 if ($err->notfound) {
522 # this means the scan is done
523 return $steps->{'quit'}->();
524 } elsif ($err->volinuse and defined $err->{'slot'}) {
525 $slot = $err->{'slot'};
530 $slot = $res->{'this_slot'};
533 $except_slots{$slot} = 1;
536 $res->release(finished_cb => $steps->{'released'});
538 $steps->{'released'}->();
542 step released => sub {
546 $chg->load(relative_slot => 'next', slot => $slot,
547 except_slots => { %except_slots },
548 res_cb => $steps->{'loaded'});
552 is_deeply({ %except_slots }, { "{1,1,1}"=>1, "{2,2,2}"=>1, "{3,3,3}"=>1, "{4,4,4}"=>1 },
553 "scanning with except_slots works");
557 test_except_slots(\&Amanda::MainLoop::quit);
558 Amanda::MainLoop::run();