Imported Upstream version 3.2.0
[debian/amanda] / installcheck / Amanda_Changer_rait.pl
1 # Copyright (c) 2009, 2010 Zmanda, Inc.  All Rights Reserved.
2 #
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.
6 #
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
10 # for more details.
11 #
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
15 #
16 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
18
19 use Test::More tests => 42;
20 use File::Path;
21 use Data::Dumper;
22 use strict;
23 use warnings;
24
25 use lib "@amperldir@";
26 use Installcheck;
27 use Installcheck::Config;
28 use Installcheck::Changer;
29 use Amanda::Paths;
30 use Amanda::Device qw( :constants );
31 use Amanda::Debug;
32 use Amanda::MainLoop;
33 use Amanda::Config qw( :init :getconf config_dir_relative );
34 use Amanda::Changer;
35
36 my $tapebase = "$Installcheck::TMP/Amanda_Changer_rait_test";
37
38 sub reset_taperoot {
39     for my $root (1 .. 3) {
40         my $taperoot = "$tapebase/$root";
41         if (-d $taperoot) {
42             rmtree($taperoot);
43         }
44         mkpath($taperoot);
45
46         for my $slot (1 .. 4) {
47             mkdir("$taperoot/slot$slot")
48                 or die("Could not mkdir: $!");
49         }
50     }
51 }
52
53 sub label_vtape {
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();
60     $dev->finish()
61         or die $dev->error_or_status();
62     rmtree("$tapebase/tmp");
63 }
64
65 # set up debugging so debug output doesn't interfere with test results
66 Amanda::Debug::dbopen("installcheck");
67 Installcheck::log_test_output();
68
69 # and disable Debug's die() and warn() overrides
70 Amanda::Debug::disable_die_override();
71
72 my $testconf = Installcheck::Config->new();
73 $testconf->write();
74
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);
79 }
80
81 reset_taperoot();
82 label_vtape(1,1,"mytape");
83 label_vtape(2,3,"mytape");
84 label_vtape(3,4,"mytape");
85 {
86     my $err = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/1");
87     chg_err_like($err,
88         { message => "chg-rait needs at least two child changers",
89           type => 'fatal' },
90         "single child device detected and handled");
91
92     $err = Amanda::Changer->new("chg-rait:chg-disk:{$tapebase/13,$tapebase/14}");
93     chg_err_like($err,
94         { message => qr/chg-disk.*13: directory.*; chg-disk.*14: directory.*/,
95           type => 'fatal' },
96         "constructor errors in child devices detected and handled");
97 }
98
99 sub test_threeway {
100     my ($finished_cb) = @_;
101
102     my $chg = Amanda::Changer->new("chg-rait:chg-disk:$tapebase/{1,2,3}");
103     pass("Create 3-way RAIT of vtapes");
104
105     my $steps = define_steps
106         cb_ref => \$finished_cb;
107
108     step get_info => sub {
109         $chg->info(info_cb => $steps->{'check_info'},
110             info => [ 'num_slots', 'vendor_string', 'fast_search' ]);
111     };
112
113     step check_info => sub {
114         my ($err, %results) = @_;
115         die($err) if defined($err);
116
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");
123
124         $steps->{'do_load_current'}->();
125     };
126
127     step do_load_current => sub {
128         $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
129     };
130
131     step got_res_current => sub {
132         my ($err, $res) = @_;
133         ok(!$err, "no error loading slot 'current'")
134             or diag($err);
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");
140
141         $res->release(finished_cb => $steps->{'do_load_next'});
142     };
143
144     step do_load_next => sub {
145         my ($err) = @_;
146         die $err if $err;
147
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'});
150     };
151
152     step got_res_next => sub {
153         my ($err, $res) = @_;
154         ok(!$err, "no error loading slot 'next'")
155             or diag($err);
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");
161
162         $res->release(finished_cb => $steps->{'do_load_label'});
163     };
164
165     step do_load_label => sub {
166         my ($err) = @_;
167         die $err if $err;
168
169         $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
170     };
171
172     step got_res_label => sub {
173         my ($err, $res) = @_;
174         ok(!$err, "no error loading slot 'label'")
175             or diag($err);
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");
181
182         $res->release(finished_cb => $steps->{'do_load_slot'});
183     };
184
185     step do_load_slot => sub {
186         my ($err) = @_;
187         die $err if $err;
188
189         $chg->load(slot => "{1,2,3}", res_cb => $steps->{'got_res_slot'});
190     };
191
192     step got_res_slot => sub {
193         my ($err, $res) = @_;
194         ok(!$err, "no error loading slot '{1,2,3}'")
195             or diag($err);
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");
201
202         $res->release(finished_cb => $steps->{'do_load_slot_nobraces'});
203     };
204
205     step do_load_slot_nobraces => sub {
206         my ($err) = @_;
207         die $err if $err;
208
209         # test the shorthand "2" -> "{2,2,2}"
210         $chg->load(slot => "2", res_cb => $steps->{'got_res_slot_nobraces'});
211     };
212
213     step got_res_slot_nobraces => sub {
214         my ($err, $res) = @_;
215         ok(!$err, "no error loading slot '2'")
216             or diag($err);
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'");
222
223         $res->release(finished_cb => $steps->{'do_load_slot_failure'});
224     };
225
226     step do_load_slot_failure => sub {
227         my ($err) = @_;
228         die $err if $err;
229
230         $chg->load(slot => "{1,99,1}", res_cb => $steps->{'got_res_slot_failure'});
231     };
232
233     step got_res_slot_failure => sub {
234         my ($err, $res) = @_;
235         chg_err_like($err,
236             { message => qr/from chg-disk.*2: Slot 99 not found/,
237               type => 'failed',
238               reason => 'invalid' },
239             "failure of a child to load a slot is correctly propagated");
240
241         $steps->{'do_load_slot_multifailure'}->();
242     };
243
244     step do_load_slot_multifailure => sub {
245         my ($err) = @_;
246         die $err if $err;
247
248         $chg->load(slot => "{99,1,99}", res_cb => $steps->{'got_res_slot_multifailure'});
249     };
250
251     step got_res_slot_multifailure => sub {
252         my ($err, $res) = @_;
253         chg_err_like($err,
254             { message => qr/from chg-disk.*1: Slot 99 not found; from chg-disk.*3: /,
255               type => 'failed',
256               reason => 'invalid' },
257             "failure of multiple chilren to load a slot is correctly propagated");
258
259         $steps->{'do_inventory'}->();
260     };
261
262     step do_inventory => sub {
263         $chg->inventory(inventory_cb => $steps->{'got_inventory'});
264     };
265
266     step got_inventory => sub {
267         my ($err, $inv) = @_;
268         die $err if $err;
269
270         is_deeply($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
273             reserved => 0,
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
277             reserved => 0,
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
281             reserved => 0,
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
285             reserved => 0,
286             slot => '{4,4,4}', import_export => undef } ,
287         ], "inventory is correct");
288
289         $finished_cb->();
290     };
291 }
292 test_threeway(\&Amanda::MainLoop::quit);
293 Amanda::MainLoop::run();
294
295 sub test_threeway_error {
296     my ($finished_cb) = @_;
297
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");
300
301     my $steps = define_steps
302         cb_ref => \$finished_cb;
303
304     step get_info => sub {
305         $chg->info(info_cb => $steps->{'check_info'},
306             info => [ 'num_slots', 'fast_search' ]);
307     };
308
309     step check_info => sub {
310         my $err = shift;
311         my %results = @_;
312         die($err) if defined($err);
313
314         is($results{'num_slots'}, 4, "info() returns the correct num_slots");
315         is($results{'fast_search'}, 1, "info() returns the correct fast_search");
316
317         $steps->{'do_load_current'}->();
318     };
319
320     step do_load_current => sub {
321         $chg->load(relative_slot => "current", res_cb => $steps->{'got_res_current'});
322     };
323
324     step got_res_current => sub {
325         my ($err, $res) = @_;
326         ok(!$err, "no error loading slot 'current'")
327             or diag($err);
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");
333
334         $res->release(finished_cb => $steps->{'do_load_label'});
335     };
336
337     step do_load_label => sub {
338         my ($err) = @_;
339         die $err if $err;
340
341         $chg->load(label => "mytape", res_cb => $steps->{'got_res_label'});
342     };
343
344     step got_res_label => sub {
345         my ($err, $res) = @_;
346         ok(!$err, "no error loading slot 'label'")
347             or diag($err);
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");
353
354         $res->release(finished_cb => $steps->{'released'});
355     };
356
357     step released => sub {
358         my ($err) = @_;
359         die $err if $err;
360
361         $steps->{'do_reset'}->();
362     };
363
364     # unfortunately, reset, clean, and update are pretty boring with vtapes, so
365     # it's hard to test them effectively.
366
367     step do_reset => sub {
368         my ($err) = @_;
369         die $err if $err;
370
371         $chg->reset(finished_cb => $steps->{'finished_reset'});
372     };
373
374     step finished_reset => sub {
375         my ($err) = @_;
376         ok(!$err, "no error resetting");
377
378         $finished_cb->();
379     };
380 }
381 test_threeway_error(\&Amanda::MainLoop::quit);
382 Amanda::MainLoop::run();
383
384 # test inventory under "normal" circumstances
385 sub test_normal_inventory {
386     my ($finished_cb) = @_;
387
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");
390
391     my $steps = define_steps
392         cb_ref => \$finished_cb;
393
394     step setup => sub {
395         reset_taperoot();
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");
402
403         $steps->{'do_inventory'}->();
404     };
405
406     step do_inventory => sub {
407         $chg->inventory(inventory_cb => $steps->{'got_inventory'});
408     };
409
410     step got_inventory => sub {
411         my ($err, $inv) = @_;
412         die $err if $err;
413
414         is_deeply($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");
424
425         $finished_cb->();
426     };
427 }
428 test_normal_inventory(\&Amanda::MainLoop::quit);
429 Amanda::MainLoop::run();
430
431 ##
432 # Test configuring the device with device_property
433
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"',
438 ]);
439 $testconf->write();
440
441 config_uninit();
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);
446 }
447
448
449 sub test_properties {
450     my ($finished_cb) = @_;
451
452     my $chg = Amanda::Changer->new("myrait");
453     ok($chg->isa("Amanda::Changer::rait"),
454         "Create RAIT device from a named config subsection");
455
456     my $steps = define_steps
457         cb_ref => \$finished_cb;
458
459     step do_load_1 => sub {
460         reset_taperoot();
461         label_vtape(1,1,"mytape");
462         label_vtape(2,2,"mytape");
463         label_vtape(3,3,"mytape");
464
465         $chg->load(slot => "1", res_cb => $steps->{'got_res_1'});
466     };
467
468     step got_res_1 => sub {
469         my ($err, $res) = @_;
470         ok(!$err, "no error loading slot '1'")
471             or diag($err);
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");
479
480         $res->release(finished_cb => $steps->{'quit'});
481     };
482
483     step quit => sub {
484         my ($err) = @_;
485         die $err if $err;
486
487         $finished_cb->();
488     };
489 }
490 test_properties(\&Amanda::MainLoop::quit);
491 Amanda::MainLoop::run();
492
493 # scan the changer using except_slots
494 sub test_except_slots {
495     my ($finished_cb) = @_;
496     my $slot;
497     my %except_slots;
498     my $chg;
499
500     my $steps = define_steps
501         cb_ref => \$finished_cb;
502
503     step start => sub {
504         $chg = Amanda::Changer->new("myrait");
505         die "error creating" unless $chg->isa("Amanda::Changer::rait");
506
507         $chg->load(relative_slot => "current", except_slots => { %except_slots },
508                    res_cb => $steps->{'loaded'});
509     };
510
511     step loaded => sub {
512         my ($err, $res) = @_;
513         if ($err) {
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'};
519             } else {
520                 die $err;
521             }
522         } else {
523             $slot = $res->{'this_slot'};
524         }
525
526         $except_slots{$slot} = 1;
527
528         if ($res) {
529             $res->release(finished_cb => $steps->{'released'});
530         } else {
531             $steps->{'released'}->();
532         }
533     };
534
535     step released => sub {
536         my ($err) = @_;
537         die $err if $err;
538
539         $chg->load(relative_slot => 'next', slot => $slot,
540                    except_slots => { %except_slots },
541                    res_cb => $steps->{'loaded'});
542     };
543
544     step quit => sub {
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");
547         $finished_cb->();
548     };
549 }
550 test_except_slots(\&Amanda::MainLoop::quit);
551 Amanda::MainLoop::run();
552
553 rmtree($tapebase);