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;
24 use lib "@amperldir@";
26 use Installcheck::Config;
27 use Installcheck::Changer;
29 use Amanda::Device qw( :constants );
32 use Amanda::Config qw( :init :getconf config_dir_relative );
35 my $tapebase = "$Installcheck::TMP/Amanda_Changer_rait_test";
38 for my $root (1 .. 3) {
39 my $taperoot = "$tapebase/$root";
45 for my $slot (1 .. 4) {
46 mkdir("$taperoot/slot$slot")
47 or die("Could not mkdir: $!");
53 my ($root, $slot, $label) = @_;
54 mkpath("$tapebase/tmp");
55 symlink("$tapebase/$root/slot$slot", "$tapebase/tmp/data");
56 my $dev = Amanda::Device->new("file:$tapebase/tmp");
57 $dev->start($Amanda::Device::ACCESS_WRITE, $label, undef)
58 or die $dev->error_or_status();
60 or die $dev->error_or_status();
61 rmtree("$tapebase/tmp");
64 # set up debugging so debug output doesn't interfere with test results
65 Amanda::Debug::dbopen("installcheck");
66 Installcheck::log_test_output();
68 # and disable Debug's die() and warn() overrides
69 Amanda::Debug::disable_die_override();
71 my $testconf = Installcheck::Config->new();
74 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
75 if ($cfg_result != $CFGERR_OK) {
76 my ($level, @errors) = Amanda::Config::config_errors();
77 die(join "\n", @errors);
81 label_vtape(1,1,"mytape");
82 label_vtape(2,3,"mytape");
83 label_vtape(3,4,"mytape");
85 my $err = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/1");
87 { message => "chg-rait needs at least two child changers",
89 "single child device detected and handled");
91 $err = Amanda::Changer->new("chg-rait:chg-disk:{$tapebase/13,$tapebase/14}");
93 { message => qr/chg-disk.*13: directory.*; chg-disk.*14: directory.*/,
95 "constructor errors in child devices detected and handled");
99 my ($finished_cb) = @_;
101 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
102 pass("Create 3-way RAIT of vtapes");
104 my $steps = define_steps
105 cb_ref => \$finished_cb;
107 step get_info => sub {
108 $chg->info(info_cb => $steps->{'check_info'},
109 info => [ 'num_slots', 'vendor_string', 'fast_search' ]);
112 step check_info => sub {
113 my ($err, %results) = @_;
114 die($err) if defined($err);
116 is($results{'num_slots'}, 4,
117 "info() returns the correct num_slots");
118 is($results{'vendor_string'}, '{chg-disk,chg-disk,chg-disk}',
119 "info() returns the correct vendor string");
120 is($results{'fast_search'}, 1,
121 "info() returns the correct fast_search");
123 $steps->{'do_load_current'}->();
126 step do_load_current => sub {
127 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
130 step got_res_current => sub {
131 my ($err, $res) = @_;
132 ok(!$err, "no error loading slot 'current'")
134 is($res->{'device'}->device_name,
135 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
136 "returns correct device name");
137 is($res->{'this_slot'}, '{1,1,1}',
138 "returns correct 'this_slot' name");
140 $res->release(finished_cb => $steps->{'do_load_next'});
143 step do_load_next => sub {
147 # (use a slot-relative 'next', rather than relative to current)
148 $chg->load(relative_slot => "next", slot => '{1,1,1}', res_cb => $steps->{'got_res_next'});
151 step got_res_next => sub {
152 my ($err, $res) = @_;
153 ok(!$err, "no error loading slot 'next'")
155 is($res->{'device'}->device_name,
156 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
157 "returns correct device name");
158 is($res->{'this_slot'}, '{2,2,2}',
159 "returns correct 'this_slot' name");
161 $res->release(finished_cb => $steps->{'do_load_label'});
164 step do_load_label => sub {
168 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
171 step got_res_label => sub {
172 my ($err, $res) = @_;
173 ok(!$err, "no error loading slot 'label'")
175 is($res->{'device'}->device_name,
176 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
177 "returns correct device name");
178 is($res->{'this_slot'}, '{1,3,4}',
179 "returns correct 'this_slot' name, even with different slots");
181 $res->release(finished_cb => $steps->{'do_load_slot'});
184 step do_load_slot => sub {
188 $chg->load(slot => "{1,2,3}", res_cb => $steps->{'got_res_slot'});
191 step got_res_slot => sub {
192 my ($err, $res) = @_;
193 ok(!$err, "no error loading slot '{1,2,3}'")
195 is($res->{'device'}->device_name,
196 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
197 "returns correct device name");
198 is($res->{'this_slot'}, '{1,2,3}',
199 "returns the 'this_slot' I requested");
201 $res->release(finished_cb => $steps->{'do_load_slot_nobraces'});
204 step do_load_slot_nobraces => sub {
208 # test the shorthand "2" -> "{2,2,2}"
209 $chg->load(slot => "2", res_cb => $steps->{'got_res_slot_nobraces'});
212 step got_res_slot_nobraces => sub {
213 my ($err, $res) = @_;
214 ok(!$err, "no error loading slot '2'")
216 is($res->{'device'}->device_name,
217 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
218 "returns correct device name");
219 is($res->{'this_slot'}, '{2,2,2}',
220 "returns an expanded 'this_slot' of {2,2,2} in response to the shorthand '2'");
222 $res->release(finished_cb => $steps->{'do_load_slot_failure'});
225 step do_load_slot_failure => sub {
229 $chg->load(slot => "{1,99,1}", res_cb => $steps->{'got_res_slot_failure'});
232 step got_res_slot_failure => sub {
233 my ($err, $res) = @_;
235 { message => qr/from chg-disk.*2: Slot 99 not found/,
237 reason => 'invalid' },
238 "failure of a child to load a slot is correctly propagated");
240 $steps->{'do_load_slot_multifailure'}->();
243 step do_load_slot_multifailure => sub {
247 $chg->load(slot => "{99,1,99}", res_cb => $steps->{'got_res_slot_multifailure'});
250 step got_res_slot_multifailure => sub {
251 my ($err, $res) = @_;
253 { message => qr/from chg-disk.*1: Slot 99 not found; from chg-disk.*3: /,
255 reason => 'invalid' },
256 "failure of multiple chilren to load a slot is correctly propagated");
258 $steps->{'do_inventory'}->();
261 step do_inventory => sub {
262 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
265 step got_inventory => sub {
266 my ($err, $inv) = @_;
270 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
271 f_type => $Amanda::Header::F_EMPTY, label => undef, # undef because labels don't match
273 slot => '{1,1,1}', import_export => undef },
274 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
275 f_type => $Amanda::Header::F_EMPTY, label => undef, # all blank
277 slot => '{2,2,2}', import_export => undef },
278 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
279 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
281 slot => '{3,3,3}', import_export => undef },
282 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS,
283 f_type => $Amanda::Header::F_EMPTY, label => undef, # mismatched labels
285 slot => '{4,4,4}', import_export => undef } ,
286 ], "inventory is correct");
291 test_threeway(\&Amanda::MainLoop::quit);
292 Amanda::MainLoop::run();
294 sub test_threeway_error {
295 my ($finished_cb) = @_;
297 my $chg = Amanda::Changer->new("chg-rait:{chg-disk:$tapebase/1,chg-disk:$tapebase/2,ERROR}");
298 pass("Create 3-way RAIT of vtapes, with the third errored out");
300 my $steps = define_steps
301 cb_ref => \$finished_cb;
303 step get_info => sub {
304 $chg->info(info_cb => $steps->{'check_info'},
305 info => [ 'num_slots', 'fast_search' ]);
308 step check_info => sub {
311 die($err) if defined($err);
313 is($results{'num_slots'}, 4, "info() returns the correct num_slots");
314 is($results{'fast_search'}, 1, "info() returns the correct fast_search");
316 $steps->{'do_load_current'}->();
319 step do_load_current => sub {
320 $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
323 step got_res_current => sub {
324 my ($err, $res) = @_;
325 ok(!$err, "no error loading slot 'current'")
327 is($res->{'device'}->device_name,
328 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
329 "returns correct device name");
330 is($res->{'this_slot'}, '{1,1,ERROR}',
331 "returns correct 'this_slot' name");
333 $res->release(finished_cb => $steps->{'do_load_label'});
336 step do_load_label => sub {
340 $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
343 step got_res_label => sub {
344 my ($err, $res) = @_;
345 ok(!$err, "no error loading slot 'label'")
347 is($res->{'device'}->device_name,
348 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,MISSING}",
349 "returns correct device name");
350 is($res->{'this_slot'}, '{1,3,ERROR}',
351 "returns correct 'this_slot' name, even with different slots");
353 $res->release(finished_cb => $steps->{'released'});
356 step released => sub {
360 $steps->{'do_reset'}->();
363 # unfortunately, reset, clean, and update are pretty boring with vtapes, so
364 # it's hard to test them effectively.
366 step do_reset => sub {
370 $chg->reset(finished_cb => $steps->{'finished_reset'});
373 step finished_reset => sub {
375 ok(!$err, "no error resetting");
380 test_threeway_error(\&Amanda::MainLoop::quit);
381 Amanda::MainLoop::run();
383 # test inventory under "normal" circumstances
384 sub test_normal_inventory {
385 my ($finished_cb) = @_;
387 my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
388 pass("Create 3-way RAIT of vtapes with correctly-labeled children");
390 my $steps = define_steps
391 cb_ref => \$finished_cb;
395 label_vtape(1,1,"mytape-1");
396 label_vtape(2,1,"mytape-1");
397 label_vtape(3,1,"mytape-1");
398 label_vtape(1,2,"mytape-2");
399 label_vtape(2,2,"mytape-2");
400 label_vtape(3,2,"mytape-2");
402 $steps->{'do_inventory'}->();
405 step do_inventory => sub {
406 $chg->inventory(inventory_cb => $steps->{'got_inventory'});
409 step got_inventory => sub {
410 my ($err, $inv) = @_;
414 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-1', reserved => 0,
415 slot => '{1,1,1}', import_export => undef },
416 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_TAPESTART, label => 'mytape-2', reserved => 0,
417 slot => '{2,2,2}', import_export => undef },
418 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
419 slot => '{3,3,3}', import_export => undef },
420 { state => Amanda::Changer::SLOT_FULL, device_status => $DEVICE_STATUS_SUCCESS, f_type => $Amanda::Header::F_EMPTY, label => undef, reserved => 0,
421 slot => '{4,4,4}', import_export => undef } ,
422 ], "second inventory is correct");
427 test_normal_inventory(\&Amanda::MainLoop::quit);
428 Amanda::MainLoop::run();
431 # Test configuring the device with device_property
433 $testconf = Installcheck::Config->new();
434 $testconf->add_changer("myrait", [
435 tpchanger => "\"chg-rait:chg-disk:$tapebase/{1,2,3}\"",
436 device_property => '"comment" "hello, world"',
441 $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);
448 sub test_properties {
449 my ($finished_cb) = @_;
451 my $chg = Amanda::Changer->new("myrait");
452 ok($chg->isa("Amanda::Changer::rait"),
453 "Create RAIT device from a named config subsection");
455 my $steps = define_steps
456 cb_ref => \$finished_cb;
458 step do_load_1 => sub {
460 label_vtape(1,1,"mytape");
461 label_vtape(2,2,"mytape");
462 label_vtape(3,3,"mytape");
464 $chg->load(slot => "1", res_cb => $steps->{'got_res_1'});
467 step got_res_1 => sub {
468 my ($err, $res) = @_;
469 ok(!$err, "no error loading slot '1'")
471 is($res->{'device'}->device_name,
472 "rait:{file:$tapebase/1/drive0,file:$tapebase/2/drive0,file:$tapebase/3/drive0}",
473 "returns correct (full) device name");
474 is($res->{'this_slot'}, '{1,1,1}',
475 "returns correct 'this_slot' name");
476 is($res->{'device'}->property_get("comment"), "hello, world",
477 "property from device_property appears on RAIT device");
479 $res->release(finished_cb => $steps->{'quit'});
489 test_properties(\&Amanda::MainLoop::quit);
490 Amanda::MainLoop::run();
492 # scan the changer using except_slots
493 sub test_except_slots {
494 my ($finished_cb) = @_;
499 my $steps = define_steps
500 cb_ref => \$finished_cb;
503 $chg = Amanda::Changer->new("myrait");
504 die "error creating" unless $chg->isa("Amanda::Changer::rait");
506 $chg->load(relative_slot => "current", except_slots => { %except_slots },
507 res_cb => $steps->{'loaded'});
511 my ($err, $res) = @_;
513 if ($err->notfound) {
514 # this means the scan is done
515 return $steps->{'quit'}->();
516 } elsif ($err->volinuse and defined $err->{'slot'}) {
517 $slot = $err->{'slot'};
522 $slot = $res->{'this_slot'};
525 $except_slots{$slot} = 1;
528 $res->release(finished_cb => $steps->{'released'});
530 $steps->{'released'}->();
534 step released => sub {
538 $chg->load(relative_slot => 'next', slot => $slot,
539 except_slots => { %except_slots },
540 res_cb => $steps->{'loaded'});
544 is_deeply({ %except_slots }, { "{1,1,1}"=>1, "{2,2,2}"=>1, "{3,3,3}"=>1, "{4,4,4}"=>1 },
545 "scanning with except_slots works");
549 test_except_slots(\&Amanda::MainLoop::quit);
550 Amanda::MainLoop::run();