Imported Upstream version 3.2.1
[debian/amanda] / installcheck / Amanda_Changer_disk.pl
1 # Copyright (c) 2008, 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 => 18;
20 use File::Path;
21 use strict;
22 use warnings;
23
24 use lib "@amperldir@";
25 use Installcheck;
26 use Installcheck::Config;
27 use Installcheck::Changer;
28 use Amanda::Paths;
29 use Amanda::Device qw( :constants );
30 use Amanda::Debug;
31 use Amanda::MainLoop;
32 use Amanda::Config qw( :init :getconf config_dir_relative );
33 use Amanda::Changer;
34
35 # set up debugging so debug output doesn't interfere with test results
36 Amanda::Debug::dbopen("installcheck");
37 Installcheck::log_test_output();
38
39 # and disable Debug's die() and warn() overrides
40 Amanda::Debug::disable_die_override();
41
42 my $taperoot = "$Installcheck::TMP/Amanda_Changer_Disk_test";
43
44 sub reset_taperoot {
45     my ($nslots) = @_;
46
47     if (-d $taperoot) {
48         rmtree($taperoot);
49     }
50     mkpath($taperoot);
51
52     for my $slot (1 .. $nslots) {
53         mkdir("$taperoot/slot$slot")
54             or die("Could not mkdir: $!");
55     }
56 }
57
58 sub is_pointing_to {
59     my ($res, $slot, $msg) = @_;
60
61     my ($datalink) = ($res->{'device'}->device_name =~ /file:(.*)/);
62     $datalink .= "/data";
63     is(readlink($datalink), "../slot$slot", $msg);
64 }
65
66 # Build a configuration that specifies Amanda::Changer::Disk
67 my $testconf = Installcheck::Config->new();
68 $testconf->write();
69
70 my $cfg_result = config_init($CONFIG_INIT_EXPLICIT_NAME, 'TESTCONF');
71 if ($cfg_result != $CFGERR_OK) {
72     my ($level, @errors) = Amanda::Config::config_errors();
73     die(join "\n", @errors);
74 }
75
76 reset_taperoot(5);
77
78 # first try an error
79 my $chg = Amanda::Changer->new("chg-disk:$taperoot/foo");
80 chg_err_like($chg,
81     { message => qr/directory '.*' does not exist/,
82       type => 'fatal' },
83     "detects nonexistent directory");
84
85 $chg = Amanda::Changer->new("chg-disk:$taperoot");
86 die($chg) if $chg->isa("Amanda::Changer::Error");
87
88 sub test_reserved {
89     my ($finished_cb, @slots) = @_;
90     my @reservations = ();
91     my $slot;
92
93     my $steps = define_steps
94         cb_ref => \$finished_cb;
95
96     step getres => sub {
97         $slot = pop @slots;
98         if (!defined $slot) {
99             return $steps->{'tryreserved'}->();
100         }
101
102         $chg->load(slot => $slot,
103                    set_current => ($slot == 5),
104                    res_cb => make_cb($steps->{'loaded'}));
105     };
106
107     step loaded => sub {
108         my ($err, $reservation) = @_;
109         ok(!$err, "no error loading slot $slot")
110             or diag($err);
111
112         # keep this reservation
113         if ($reservation) {
114             push @reservations, $reservation;
115         }
116
117         # and start on the next
118         $steps->{'getres'}->();
119     };
120
121     step tryreserved => sub {
122         # try to load an already-reserved slot
123         $chg->load(slot => 3,
124                    res_cb => sub {
125             my ($err, $reservation) = @_;
126             chg_err_like($err,
127                 { message => qr/Slot 3 is already in use by drive/,
128                   type => 'failed',
129                   reason => 'volinuse' },
130                 "error when requesting already-reserved slot");
131             $steps->{'release'}->();
132         });
133     };
134
135     step release => sub {
136         my $res = pop @reservations;
137         if (!defined $res) {
138             return Amanda::MainLoop::quit();
139         }
140
141         $res->release(finished_cb => sub {
142             my ($err) = @_;
143             die $err if $err;
144             $steps->{'release'}->();
145         });
146     };
147 }
148
149 # start the loop
150 test_reserved(\&Amanda::MainLoop::quit, 1, 3, 5);
151 Amanda::MainLoop::run();
152
153 # and try it with some different slots, just to see
154 test_reserved(\&Amanda::MainLoop::quit, 4, 2, 3);
155 Amanda::MainLoop::run();
156
157 # check relative slot ("current" and "next") functionality
158 sub test_relative_slot {
159     my ($finished_cb) = @_;
160     my $slot;
161
162     my $steps = define_steps
163         cb_ref => \$finished_cb;
164
165     # load the "current" slot, which should be 3
166     step load_current => sub {
167         $chg->load(relative_slot => "current", res_cb => $steps->{'check_current_cb'});
168     };
169
170     step check_current_cb => sub {
171         my ($err, $res) = @_;
172         die $err if $err;
173
174         is_pointing_to($res, 5, "'current' is slot 5");
175         $slot = $res->{'this_slot'};
176
177         $res->release(finished_cb => $steps->{'released1'});
178     };
179
180     step released1 => sub {
181         my ($err) = @_;
182         die $err if $err;
183
184         $chg->load(relative_slot => 'next', slot => $slot,
185                    res_cb => $steps->{'check_next_cb'});
186     };
187
188     step check_next_cb => sub {
189         my ($err, $res) = @_;
190         die $err if $err;
191
192         is_pointing_to($res, 1, "'next' from there is slot 1");
193
194         $res->release(finished_cb => $steps->{'released2'});
195     };
196
197     step released2 => sub {
198         my ($err) = @_;
199         die $err if $err;
200
201         $chg->reset(finished_cb => $steps->{'reset_finished_cb'});
202     };
203
204     step reset_finished_cb => sub {
205         my ($err) = @_;
206         die $err if $err;
207
208         $chg->load(relative_slot => "current", res_cb => $steps->{'check_reset_cb'});
209     };
210
211     step check_reset_cb => sub {
212         my ($err, $res) = @_;
213         die $err if $err;
214
215         is_pointing_to($res, 1, "after reset, 'current' is slot 1");
216
217         $res->release(finished_cb => $steps->{'released3'});
218     };
219
220     step released3 => sub {
221         my ($err) = @_;
222         die $err if $err;
223
224         $finished_cb->();
225     };
226 }
227 test_relative_slot(\&Amanda::MainLoop::quit);
228 Amanda::MainLoop::run();
229
230 # test loading relative_slot "next"
231 sub test_relative_next {
232     my ($finished_cb) = @_;
233
234     my $steps = define_steps
235         cb_ref => \$finished_cb;
236
237     step load_next => sub {
238         $chg->load(relative_slot => "next",
239             res_cb => sub {
240                 my ($err, $res) = @_;
241                 die $err if $err;
242
243                 is_pointing_to($res, 2, "loading relative slot 'next' loads the correct slot");
244
245                 $steps->{'release'}->($res);
246             }
247         );
248     };
249
250     step release => sub {
251         my ($res) = @_;
252
253         $res->release(finished_cb => sub {
254             my ($err) = @_;
255             die $err if $err;
256
257             $finished_cb->();
258         });
259     };
260 }
261 test_relative_next(\&Amanda::MainLoop::quit);
262 Amanda::MainLoop::run();
263
264 # scan the changer using except_slots
265 sub test_except_slots {
266     my ($finished_cb) = @_;
267     my $slot;
268     my %except_slots;
269
270     my $steps = define_steps
271         cb_ref => \$finished_cb;
272
273     step start => sub {
274         $chg->load(slot => "5", except_slots => { %except_slots },
275                    res_cb => $steps->{'loaded'});
276     };
277
278     step loaded => sub {
279         my ($err, $res) = @_;
280         if ($err) {
281             if ($err->notfound) {
282                 # this means the scan is done
283                 return $steps->{'quit'}->();
284             } elsif ($err->volinuse and defined $err->{'slot'}) {
285                 $slot = $err->{'slot'};
286             } else {
287                 die $err;
288             }
289         } else {
290             $slot = $res->{'this_slot'};
291         }
292
293         $except_slots{$slot} = 1;
294
295         if ($res) {
296             $res->release(finished_cb => $steps->{'released'});
297         } else {
298             $steps->{'released'}->();
299         }
300     };
301
302     step released => sub {
303         my ($err) = @_;
304         die $err if $err;
305
306         $chg->load(relative_slot => 'next', slot => $slot,
307                    except_slots => { %except_slots },
308                    res_cb => $steps->{'loaded'});
309     };
310
311     step quit => sub {
312         is_deeply({ %except_slots }, { 5=>1, 1=>1, 2=>1, 3=>1, 4=>1 },
313                 "scanning with except_slots works");
314         $finished_cb->();
315     };
316 }
317 test_except_slots(\&Amanda::MainLoop::quit);
318 Amanda::MainLoop::run();
319
320 # eject is not implemented
321 {
322     my $try_eject = make_cb('try_eject' => sub {
323         $chg->eject(finished_cb => make_cb(sub {
324             my ($err, $res) = @_;
325             chg_err_like($err,
326                 { type => 'failed', reason => 'notimpl' },
327                 "eject returns a failed/notimpl error");
328
329             Amanda::MainLoop::quit();
330         }));
331     });
332
333     $try_eject->();
334     Amanda::MainLoop::run();
335 }
336
337 # check num_slots and loading by label
338 {
339     my ($get_info, $load_label, $check_load_cb) = @_;
340
341     $get_info = make_cb('get_info' => sub {
342         $chg->info(info_cb => $load_label, info => [ 'num_slots', 'fast_search' ]);
343     });
344
345     $load_label = make_cb('load_label' => sub {
346         my $err = shift;
347         my %results = @_;
348         die($err) if defined($err);
349
350         is_deeply({ %results },
351             { num_slots => 5, fast_search => 1 },
352             "info() returns the correct num_slots and fast_search");
353
354         # note use of a glob metacharacter in the label name
355         $chg->load(label => "FOO?BAR", res_cb => $check_load_cb);
356     });
357
358     $check_load_cb = make_cb('check_load_cb' => sub {
359         my ($err, $res) = @_;
360         die $err if $err;
361
362         is_pointing_to($res, 4, "labeled volume found in slot 4");
363
364         $res->release(finished_cb => sub {
365             my ($err) = @_;
366             die $err if $err;
367
368             Amanda::MainLoop::quit();
369         });
370     });
371
372     # label slot 4, using our own symlink
373     mkpath("$taperoot/tmp");
374     symlink("../slot4", "$taperoot/tmp/data") or die "While symlinking: $!";
375     my $dev = Amanda::Device->new("file:$taperoot/tmp");
376     $dev->start($Amanda::Device::ACCESS_WRITE, "FOO?BAR", undef)
377         or die $dev->error_or_status();
378     $dev->finish()
379         or die $dev->error_or_status();
380     rmtree("$taperoot/tmp");
381
382     $get_info->();
383     Amanda::MainLoop::run();
384 }
385
386 # inventory is pretty cool
387 {
388     my $try_inventory = make_cb('try_inventory' => sub {
389         $chg->inventory(inventory_cb => make_cb(sub {
390             my ($err, $inv) = @_;
391             die $err if $err;
392
393             is_deeply($inv, [
394               { slot => 1, state => Amanda::Changer::SLOT_FULL,
395                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
396                 f_type => $Amanda::Header::F_EMPTY, label => undef,
397                 reserved => 0,  current => 1},
398               { slot => 2, state => Amanda::Changer::SLOT_FULL,
399                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
400                 f_type => $Amanda::Header::F_EMPTY, label => undef,
401                 reserved => 0 },
402               { slot => 3, state => Amanda::Changer::SLOT_FULL,
403                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
404                 f_type => $Amanda::Header::F_EMPTY, label => undef,
405                 reserved => 0 },
406               { slot => 4, state => Amanda::Changer::SLOT_FULL,
407                 device_status => $DEVICE_STATUS_SUCCESS,
408                 f_type => $Amanda::Header::F_TAPESTART, label => "FOO?BAR",
409                 reserved => 0 },
410               { slot => 5, state => Amanda::Changer::SLOT_FULL,
411                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
412                 f_type => $Amanda::Header::F_EMPTY, label => undef,
413                 reserved => 0 },
414                 ], "inventory finds the labeled tape");
415
416             Amanda::MainLoop::quit();
417         }));
418     });
419
420     $try_inventory->();
421     Amanda::MainLoop::run();
422 }
423
424 rmtree($taperoot);