1 # Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 use Test::More tests => 43;
25 use lib "@amperldir@";
27 use Installcheck::Config;
28 use Installcheck::Changer;
30 use Amanda::Device qw( :constants );
33 use Amanda::Config qw( :init :getconf config_dir_relative );
36 my $tapebase = "$Installcheck::TMP/Amanda_Changer_rait_test";
39 for my $root (1 .. 3) {
40 my $taperoot = "$tapebase/$root";
46 for my $slot (1 .. 4) {
47 mkdir("$taperoot/slot$slot")
48 or die("Could not mkdir: $!");
54 my ($root, $slot, $label) = @_;
55 mkpath("$tapebase/tmp");
56 symlink("$tapebase/$root/slot$slot", "$tapebase/tmp/data");
57 my $dev = Amanda::Device->new("file:$tapebase/tmp");
58 $dev->start($Amanda::Device::ACCESS_WRITE, $label, undef)
59 or die $dev->error_or_status();
61 or die $dev->error_or_status();
62 rmtree("$tapebase/tmp");
65 # set up debugging so debug output doesn't interfere with test results
66 Amanda::Debug::dbopen("installcheck");
67 Installcheck::log_test_output();
69 # and disable Debug's die() and warn() overrides
70 Amanda::Debug::disable_die_override();
72 my $testconf = Installcheck::Config->new();
75 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
76 if ($cfg_result != $CFGERR_OK) {
77 my ($level, @errors) = Amanda::Config::config_errors();
78 die(join "\n", @errors);
82 label_vtape(1,1,"mytape");
83 label_vtape(2,3,"mytape");
84 label_vtape(3,4,"mytape");
86 my $err = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/1");
88 { message => "chg-rait needs at least two child changers",
90 "single child device detected and handled");
92 $err = Amanda::Changer->new("chg-rait:chg-disk:{$tapebase/13,$tapebase/14}");
94 { message => qr/chg-disk.*13: directory.*; chg-disk.*14: directory.*/,
96 "constructor errors in child devices detected and handled");
100 my ($finished_cb) = @_;
102 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
103 pass("Create 3-way RAIT of vtapes");
105 my $steps = define_steps
106 cb_ref => \$finished_cb,
107 finalize => sub { $chg->quit() };
109 step get_info => sub {
110 $chg->info(info_cb => $steps->{'check_info'},
111 info => [ 'num_slots', 'vendor_string', 'fast_search' ]);
114 step check_info => sub {
115 my ($err, %results) = @_;
116 die($err) if defined($err);
118 is($results{'num_slots'}, 4,
119 "info() returns the correct num_slots");
120 is($results{'vendor_string'}, '{chg-disk,chg-disk,chg-disk}',
121 "info() returns the correct vendor string");
122 is($results{'fast_search'}, 1,
123 "info() returns the correct fast_search");
125 $steps->{'do_load_current'}->();
128 step do_load_current => sub {
129 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
132 step got_res_current => sub {
133 my ($err, $res) = @_;
134 ok(!$err, "no error loading slot 'current'")
136 is($res->{'device'}->device_name,
137 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
138 "returns correct device name");
139 is($res->{'this_slot'}, '{1,1,1}',
140 "returns correct 'this_slot' name");
142 $res->release(finished_cb => $steps->{'do_load_next'});
145 step do_load_next => sub {
149 # (use a slot-relative 'next', rather than relative to current)
150 $chg->load(relative_slot => "next", slot => '{1,1,1}', res_cb => $steps->{'got_res_next'});
153 step got_res_next => sub {
154 my ($err, $res) = @_;
155 ok(!$err, "no error loading slot 'next'")
157 is($res->{'device'}->device_name,
158 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
159 "returns correct device name");
160 is($res->{'this_slot'}, '{2,2,2}',
161 "returns correct 'this_slot' name");
163 $res->release(finished_cb => $steps->{'do_load_label'});
166 step do_load_label => sub {
170 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
173 step got_res_label => sub {
174 my ($err, $res) = @_;
175 ok(!$err, "no error loading slot 'label'")
177 is($res->{'device'}->device_name,
178 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
179 "returns correct device name");
180 is($res->{'this_slot'}, '{1,3,4}',
181 "returns correct 'this_slot' name, even with different slots");
183 $res->release(finished_cb => $steps->{'do_load_slot'});
186 step do_load_slot => sub {
190 $chg->load(slot => "{1,2,3}", res_cb => $steps->{'got_res_slot'});
193 step got_res_slot => sub {
194 my ($err, $res) = @_;
195 ok(!$err, "no error loading slot '{1,2,3}'")
197 is($res->{'device'}->device_name,
198 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
199 "returns correct device name");
200 is($res->{'this_slot'}, '{1,2,3}',
201 "returns the 'this_slot' I requested");
203 $res->release(finished_cb => $steps->{'do_load_slot_nobraces'});
206 step do_load_slot_nobraces => sub {
210 # test the shorthand "2" -> "{2,2,2}"
211 $chg->load(slot => "2", res_cb => $steps->{'got_res_slot_nobraces'});
214 step got_res_slot_nobraces => sub {
215 my ($err, $res) = @_;
216 ok(!$err, "no error loading slot '2'")
218 is($res->{'device'}->device_name,
219 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
220 "returns correct device name");
221 is($res->{'this_slot'}, '{2,2,2}',
222 "returns an expanded 'this_slot' of {2,2,2} in response to the shorthand '2'");
224 $res->release(finished_cb => $steps->{'do_load_slot_failure'});
227 step do_load_slot_failure => sub {
231 $chg->load(slot => "{1,99,1}", res_cb => $steps->{'got_res_slot_failure'});
234 step got_res_slot_failure => sub {
235 my ($err, $res) = @_;
237 { message => qr/from chg-disk.*2: Slot 99 not found/,
239 reason => 'invalid' },
240 "failure of a child to load a slot is correctly propagated");
242 $steps->{'do_load_slot_multifailure'}->();
245 step do_load_slot_multifailure => sub {
249 $chg->load(slot => "{99,1,99}", res_cb => $steps->{'got_res_slot_multifailure'});
252 step got_res_slot_multifailure => sub {
253 my ($err, $res) = @_;
255 { message => qr/from chg-disk.*1: Slot 99 not found; from chg-disk.*3: /,
257 reason => 'invalid' },
258 "failure of multiple chilren to load a slot is correctly propagated");
260 $steps->{'do_inventory'}->();
263 step do_inventory => sub {
264 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
267 step got_inventory => sub {
268 my ($err, $inv) = @_;
272 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
273 f_type => $Amanda::Header::F_EMPTY, label => undef, # undef because labels don't match
275 slot => '{1,1,1}', import_export => undef },
276 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
277 f_type => $Amanda::Header::F_EMPTY, label => undef, # all blank
279 slot => '{2,2,2}', import_export => undef },
280 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
281 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
283 slot => '{3,3,3}', import_export => undef },
284 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
285 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
287 slot => '{4,4,4}', import_export => undef } ,
288 ], "inventory is correct");
293 test_threeway(\&Amanda::MainLoop::quit);
294 Amanda::MainLoop::run();
296 sub test_threeway_error {
297 my ($finished_cb) = @_;
299 my $chg = Amanda::Changer->new("chg-rait:{chg-disk:$tapebase/1,chg-disk:$tapebase/2,ERROR}");
300 pass("Create 3-way RAIT of vtapes, with the third errored out");
301 is($chg->have_inventory(), '1', "changer have inventory");
303 my $steps = define_steps
304 cb_ref => \$finished_cb,
305 finalize => sub { $chg->quit() };
307 step get_info => sub {
308 $chg->info(info_cb => $steps->{'check_info'},
309 info => [ 'num_slots', 'fast_search' ]);
312 step check_info => sub {
315 die($err) if defined($err);
317 is($results{'num_slots'}, 4, "info() returns the correct num_slots");
318 is($results{'fast_search'}, 1, "info() returns the correct fast_search");
320 $steps->{'do_load_current'}->();
323 step do_load_current => sub {
324 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
327 step got_res_current => sub {
328 my ($err, $res) = @_;
329 ok(!$err, "no error loading slot 'current'")
331 is($res->{'device'}->device_name,
332 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
333 "returns correct device name");
334 is($res->{'this_slot'}, '{1,1,ERROR}',
335 "returns correct 'this_slot' name");
337 $res->release(finished_cb => $steps->{'do_load_label'});
340 step do_load_label => sub {
344 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
347 step got_res_label => sub {
348 my ($err, $res) = @_;
349 ok(!$err, "no error loading slot 'label'")
351 is($res->{'device'}->device_name,
352 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
353 "returns correct device name");
354 is($res->{'this_slot'}, '{1,3,ERROR}',
355 "returns correct 'this_slot' name, even with different slots");
357 $res->release(finished_cb => $steps->{'released'});
360 step released => sub {
364 $steps->{'do_reset'}->();
367 # unfortunately, reset, clean, and update are pretty boring with vtapes, so
368 # it's hard to test them effectively.
370 step do_reset => sub {
374 $chg->reset(finished_cb => $steps->{'finished_reset'});
377 step finished_reset => sub {
379 ok(!$err, "no error resetting");
384 test_threeway_error(\&Amanda::MainLoop::quit);
385 Amanda::MainLoop::run();
387 # test inventory under "normal" circumstances
388 sub test_normal_inventory {
389 my ($finished_cb) = @_;
391 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
392 pass("Create 3-way RAIT of vtapes with correctly-labeled children");
394 my $steps = define_steps
395 cb_ref => \$finished_cb,
396 finalize => sub { $chg->quit() };
400 label_vtape(1,1,"mytape-1");
401 label_vtape(2,1,"mytape-1");
402 label_vtape(3,1,"mytape-1");
403 label_vtape(1,2,"mytape-2");
404 label_vtape(2,2,"mytape-2");
405 label_vtape(3,2,"mytape-2");
407 $steps->{'do_inventory'}->();
410 step do_inventory => sub {
411 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
414 step got_inventory => sub {
415 my ($err, $inv) = @_;
419 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-1', reserved => 0,
420 slot => '{1,1,1}', import_export => undef },
421 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-2', reserved => 0,
422 slot => '{2,2,2}', import_export => undef },
423 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
424 slot => '{3,3,3}', import_export => undef },
425 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_VOLUME_UNLABELED, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
426 slot => '{4,4,4}', import_export => undef } ,
427 ], "second inventory is correct");
432 test_normal_inventory(\&Amanda::MainLoop::quit);
433 Amanda::MainLoop::run();
436 # Test configuring the device with device_property
438 $testconf = Installcheck::Config->new();
439 $testconf->add_changer("myrait", [
440 tpchanger => "\"chg-rait:chg-disk:$tapebase/{1,2,3}\"",
441 device_property => '"comment" "hello, world"',
446 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
447 if ($cfg_result != $CFGERR_OK) {
448 my ($level, @errors) = Amanda::Config::config_errors();
449 die(join "\n", @errors);
453 sub test_properties {
454 my ($finished_cb) = @_;
456 my $chg = Amanda::Changer->new("myrait");
457 ok($chg->isa("Amanda::Changer::rait"),
458 "Create RAIT device from a named config subsection");
460 my $steps = define_steps
461 cb_ref => \$finished_cb,
462 finalize => sub { $chg->quit() };
464 step do_load_1 => sub {
466 label_vtape(1,1,"mytape");
467 label_vtape(2,2,"mytape");
468 label_vtape(3,3,"mytape");
470 $chg->load(slot => "1", res_cb => $steps->{'got_res_1'});
473 step got_res_1 => sub {
474 my ($err, $res) = @_;
475 ok(!$err, "no error loading slot '1'")
477 is($res->{'device'}->device_name,
478 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
479 "returns correct (full) device name");
480 is($res->{'this_slot'}, '{1,1,1}',
481 "returns correct 'this_slot' name");
482 is($res->{'device'}->property_get("comment"), "hello, world",
483 "property from device_property appears on RAIT device");
485 $res->release(finished_cb => $steps->{'quit'});
495 test_properties(\&Amanda::MainLoop::quit);
496 Amanda::MainLoop::run();
498 # scan the changer using except_slots
499 sub test_except_slots {
500 my ($finished_cb) = @_;
505 my $steps = define_steps
506 cb_ref => \$finished_cb,
507 finalize => sub { $chg->quit() if defined $chg };
510 $chg = Amanda::Changer->new("myrait");
511 die "error creating" unless $chg->isa("Amanda::Changer::rait");
513 $chg->load(relative_slot => "current", except_slots => { %except_slots },
514 res_cb => $steps->{'loaded'});
518 my ($err, $res) = @_;
520 if ($err->notfound) {
521 # this means the scan is done
522 return $steps->{'quit'}->();
523 } elsif ($err->volinuse and defined $err->{'slot'}) {
524 $slot = $err->{'slot'};
529 $slot = $res->{'this_slot'};
532 $except_slots{$slot} = 1;
535 $res->release(finished_cb => $steps->{'released'});
537 $steps->{'released'}->();
541 step released => sub {
545 $chg->load(relative_slot => 'next', slot => $slot,
546 except_slots => { %except_slots },
547 res_cb => $steps->{'loaded'});
551 is_deeply({ %except_slots }, { "{1,1,1}"=>1, "{2,2,2}"=>1, "{3,3,3}"=>1, "{4,4,4}"=>1 },
552 "scanning with except_slots works");
556 test_except_slots(\&Amanda::MainLoop::quit);
557 Amanda::MainLoop::run();