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 => 42;
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;
108 step get_info => sub {
109 $chg->info(info_cb => $steps->{'check_info'},
110 info => [ 'num_slots', 'vendor_string', 'fast_search' ]);
113 step check_info => sub {
114 my ($err, %results) = @_;
115 die($err) if defined($err);
117 is($results{'num_slots'}, 4,
118 "info() returns the correct num_slots");
119 is($results{'vendor_string'}, '{chg-disk,chg-disk,chg-disk}',
120 "info() returns the correct vendor string");
121 is($results{'fast_search'}, 1,
122 "info() returns the correct fast_search");
124 $steps->{'do_load_current'}->();
127 step do_load_current => sub {
128 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
131 step got_res_current => sub {
132 my ($err, $res) = @_;
133 ok(!$err, "no error loading slot 'current'")
135 is($res->{'device'}->device_name,
136 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
137 "returns correct device name");
138 is($res->{'this_slot'}, '{1,1,1}',
139 "returns correct 'this_slot' name");
141 $res->release(finished_cb => $steps->{'do_load_next'});
144 step do_load_next => sub {
148 # (use a slot-relative 'next', rather than relative to current)
149 $chg->load(relative_slot => "next", slot => '{1,1,1}', res_cb => $steps->{'got_res_next'});
152 step got_res_next => sub {
153 my ($err, $res) = @_;
154 ok(!$err, "no error loading slot 'next'")
156 is($res->{'device'}->device_name,
157 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
158 "returns correct device name");
159 is($res->{'this_slot'}, '{2,2,2}',
160 "returns correct 'this_slot' name");
162 $res->release(finished_cb => $steps->{'do_load_label'});
165 step do_load_label => sub {
169 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
172 step got_res_label => sub {
173 my ($err, $res) = @_;
174 ok(!$err, "no error loading slot 'label'")
176 is($res->{'device'}->device_name,
177 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
178 "returns correct device name");
179 is($res->{'this_slot'}, '{1,3,4}',
180 "returns correct 'this_slot' name, even with different slots");
182 $res->release(finished_cb => $steps->{'do_load_slot'});
185 step do_load_slot => sub {
189 $chg->load(slot => "{1,2,3}", res_cb => $steps->{'got_res_slot'});
192 step got_res_slot => sub {
193 my ($err, $res) = @_;
194 ok(!$err, "no error loading slot '{1,2,3}'")
196 is($res->{'device'}->device_name,
197 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
198 "returns correct device name");
199 is($res->{'this_slot'}, '{1,2,3}',
200 "returns the 'this_slot' I requested");
202 $res->release(finished_cb => $steps->{'do_load_slot_nobraces'});
205 step do_load_slot_nobraces => sub {
209 # test the shorthand "2" -> "{2,2,2}"
210 $chg->load(slot => "2", res_cb => $steps->{'got_res_slot_nobraces'});
213 step got_res_slot_nobraces => sub {
214 my ($err, $res) = @_;
215 ok(!$err, "no error loading slot '2'")
217 is($res->{'device'}->device_name,
218 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
219 "returns correct device name");
220 is($res->{'this_slot'}, '{2,2,2}',
221 "returns an expanded 'this_slot' of {2,2,2} in response to the shorthand '2'");
223 $res->release(finished_cb => $steps->{'do_load_slot_failure'});
226 step do_load_slot_failure => sub {
230 $chg->load(slot => "{1,99,1}", res_cb => $steps->{'got_res_slot_failure'});
233 step got_res_slot_failure => sub {
234 my ($err, $res) = @_;
236 { message => qr/from chg-disk.*2: Slot 99 not found/,
238 reason => 'invalid' },
239 "failure of a child to load a slot is correctly propagated");
241 $steps->{'do_load_slot_multifailure'}->();
244 step do_load_slot_multifailure => sub {
248 $chg->load(slot => "{99,1,99}", res_cb => $steps->{'got_res_slot_multifailure'});
251 step got_res_slot_multifailure => sub {
252 my ($err, $res) = @_;
254 { message => qr/from chg-disk.*1: Slot 99 not found; from chg-disk.*3: /,
256 reason => 'invalid' },
257 "failure of multiple chilren to load a slot is correctly propagated");
259 $steps->{'do_inventory'}->();
262 step do_inventory => sub {
263 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
266 step got_inventory => sub {
267 my ($err, $inv) = @_;
271 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
272 f_type => $Amanda::Header::F_EMPTY, label => undef, # undef because labels don't match
274 slot => '{1,1,1}', import_export => undef },
275 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
276 f_type => $Amanda::Header::F_EMPTY, label => undef, # all blank
278 slot => '{2,2,2}', import_export => undef },
279 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
280 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
282 slot => '{3,3,3}', import_export => undef },
283 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
284 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
286 slot => '{4,4,4}', import_export => undef } ,
287 ], "inventory is correct");
292 test_threeway(\&Amanda::MainLoop::quit);
293 Amanda::MainLoop::run();
295 sub test_threeway_error {
296 my ($finished_cb) = @_;
298 my $chg = Amanda::Changer->new("chg-rait:{chg-disk:$tapebase/1,chg-disk:$tapebase/2,ERROR}");
299 pass("Create 3-way RAIT of vtapes, with the third errored out");
301 my $steps = define_steps
302 cb_ref => \$finished_cb;
304 step get_info => sub {
305 $chg->info(info_cb => $steps->{'check_info'},
306 info => [ 'num_slots', 'fast_search' ]);
309 step check_info => sub {
312 die($err) if defined($err);
314 is($results{'num_slots'}, 4, "info() returns the correct num_slots");
315 is($results{'fast_search'}, 1, "info() returns the correct fast_search");
317 $steps->{'do_load_current'}->();
320 step do_load_current => sub {
321 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
324 step got_res_current => sub {
325 my ($err, $res) = @_;
326 ok(!$err, "no error loading slot 'current'")
328 is($res->{'device'}->device_name,
329 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
330 "returns correct device name");
331 is($res->{'this_slot'}, '{1,1,ERROR}',
332 "returns correct 'this_slot' name");
334 $res->release(finished_cb => $steps->{'do_load_label'});
337 step do_load_label => sub {
341 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
344 step got_res_label => sub {
345 my ($err, $res) = @_;
346 ok(!$err, "no error loading slot 'label'")
348 is($res->{'device'}->device_name,
349 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
350 "returns correct device name");
351 is($res->{'this_slot'}, '{1,3,ERROR}',
352 "returns correct 'this_slot' name, even with different slots");
354 $res->release(finished_cb => $steps->{'released'});
357 step released => sub {
361 $steps->{'do_reset'}->();
364 # unfortunately, reset, clean, and update are pretty boring with vtapes, so
365 # it's hard to test them effectively.
367 step do_reset => sub {
371 $chg->reset(finished_cb => $steps->{'finished_reset'});
374 step finished_reset => sub {
376 ok(!$err, "no error resetting");
381 test_threeway_error(\&Amanda::MainLoop::quit);
382 Amanda::MainLoop::run();
384 # test inventory under "normal" circumstances
385 sub test_normal_inventory {
386 my ($finished_cb) = @_;
388 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
389 pass("Create 3-way RAIT of vtapes with correctly-labeled children");
391 my $steps = define_steps
392 cb_ref => \$finished_cb;
396 label_vtape(1,1,"mytape-1");
397 label_vtape(2,1,"mytape-1");
398 label_vtape(3,1,"mytape-1");
399 label_vtape(1,2,"mytape-2");
400 label_vtape(2,2,"mytape-2");
401 label_vtape(3,2,"mytape-2");
403 $steps->{'do_inventory'}->();
406 step do_inventory => sub {
407 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
410 step got_inventory => sub {
411 my ($err, $inv) = @_;
415 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-1', reserved => 0,
416 slot => '{1,1,1}', import_export => undef },
417 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-2', reserved => 0,
418 slot => '{2,2,2}', import_export => undef },
419 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
420 slot => '{3,3,3}', import_export => undef },
421 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
422 slot => '{4,4,4}', import_export => undef } ,
423 ], "second inventory is correct");
428 test_normal_inventory(\&Amanda::MainLoop::quit);
429 Amanda::MainLoop::run();
432 # Test configuring the device with device_property
434 $testconf = Installcheck::Config->new();
435 $testconf->add_changer("myrait", [
436 tpchanger => "\"chg-rait:chg-disk:$tapebase/{1,2,3}\"",
437 device_property => '"comment" "hello, world"',
442 $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
443 if ($cfg_result != $CFGERR_OK) {
444 my ($level, @errors) = Amanda::Config::config_errors();
445 die(join "\n", @errors);
449 sub test_properties {
450 my ($finished_cb) = @_;
452 my $chg = Amanda::Changer->new("myrait");
453 ok($chg->isa("Amanda::Changer::rait"),
454 "Create RAIT device from a named config subsection");
456 my $steps = define_steps
457 cb_ref => \$finished_cb;
459 step do_load_1 => sub {
461 label_vtape(1,1,"mytape");
462 label_vtape(2,2,"mytape");
463 label_vtape(3,3,"mytape");
465 $chg->load(slot => "1", res_cb => $steps->{'got_res_1'});
468 step got_res_1 => sub {
469 my ($err, $res) = @_;
470 ok(!$err, "no error loading slot '1'")
472 is($res->{'device'}->device_name,
473 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
474 "returns correct (full) device name");
475 is($res->{'this_slot'}, '{1,1,1}',
476 "returns correct 'this_slot' name");
477 is($res->{'device'}->property_get("comment"), "hello, world",
478 "property from device_property appears on RAIT device");
480 $res->release(finished_cb => $steps->{'quit'});
490 test_properties(\&Amanda::MainLoop::quit);
491 Amanda::MainLoop::run();
493 # scan the changer using except_slots
494 sub test_except_slots {
495 my ($finished_cb) = @_;
500 my $steps = define_steps
501 cb_ref => \$finished_cb;
504 $chg = Amanda::Changer->new("myrait");
505 die "error creating" unless $chg->isa("Amanda::Changer::rait");
507 $chg->load(relative_slot => "current", except_slots => { %except_slots },
508 res_cb => $steps->{'loaded'});
512 my ($err, $res) = @_;
514 if ($err->notfound) {
515 # this means the scan is done
516 return $steps->{'quit'}->();
517 } elsif ($err->volinuse and defined $err->{'slot'}) {
518 $slot = $err->{'slot'};
523 $slot = $res->{'this_slot'};
526 $except_slots{$slot} = 1;
529 $res->release(finished_cb => $steps->{'released'});
531 $steps->{'released'}->();
535 step released => sub {
539 $chg->load(relative_slot => 'next', slot => $slot,
540 except_slots => { %except_slots },
541 res_cb => $steps->{'loaded'});
545 is_deeply({ %except_slots }, { "{1,1,1}"=>1, "{2,2,2}"=>1, "{3,3,3}"=>1, "{4,4,4}"=>1 },
546 "scanning with except_slots works");
550 test_except_slots(\&Amanda::MainLoop::quit);
551 Amanda::MainLoop::run();