Imported Upstream version 3.3.2
[debian/amanda] / installcheck / Amanda_Changer_disk.pl
1 # Copyright (c) 2008-2012 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 => 19;
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 is($chg->have_inventory(), '1', "changer have inventory");
88
89 sub test_reserved {
90     my ($finished_cb, @slots) = @_;
91     my @reservations = ();
92     my $slot;
93
94     my $steps = define_steps
95         cb_ref => \$finished_cb;
96
97     step getres => sub {
98         $slot = pop @slots;
99         if (!defined $slot) {
100             return $steps->{'tryreserved'}->();
101         }
102
103         $chg->load(slot => $slot,
104                    set_current => ($slot == 5),
105                    res_cb => make_cb($steps->{'loaded'}));
106     };
107
108     step loaded => sub {
109         my ($err, $reservation) = @_;
110         ok(!$err, "no error loading slot $slot")
111             or diag($err);
112
113         # keep this reservation
114         if ($reservation) {
115             push @reservations, $reservation;
116         }
117
118         # and start on the next
119         $steps->{'getres'}->();
120     };
121
122     step tryreserved => sub {
123         # try to load an already-reserved slot
124         $chg->load(slot => 3,
125                    res_cb => sub {
126             my ($err, $reservation) = @_;
127             chg_err_like($err,
128                 { message => qr/Slot 3 is already in use by drive/,
129                   type => 'failed',
130                   reason => 'volinuse' },
131                 "error when requesting already-reserved slot");
132             $steps->{'release'}->();
133         });
134     };
135
136     step release => sub {
137         my $res = pop @reservations;
138         if (!defined $res) {
139             return Amanda::MainLoop::quit();
140         }
141
142         $res->release(finished_cb => sub {
143             my ($err) = @_;
144             die $err if $err;
145             $steps->{'release'}->();
146         });
147     };
148 }
149
150 # start the loop
151 test_reserved(\&Amanda::MainLoop::quit, 1, 3, 5);
152 Amanda::MainLoop::run();
153
154 # and try it with some different slots, just to see
155 test_reserved(\&Amanda::MainLoop::quit, 4, 2, 3);
156 Amanda::MainLoop::run();
157
158 # check relative slot ("current" and "next") functionality
159 sub test_relative_slot {
160     my ($finished_cb) = @_;
161     my $slot;
162
163     my $steps = define_steps
164         cb_ref => \$finished_cb;
165
166     # load the "current" slot, which should be 3
167     step load_current => sub {
168         $chg->load(relative_slot => "current", res_cb => $steps->{'check_current_cb'});
169     };
170
171     step check_current_cb => sub {
172         my ($err, $res) = @_;
173         die $err if $err;
174
175         is_pointing_to($res, 5, "'current' is slot 5");
176         $slot = $res->{'this_slot'};
177
178         $res->release(finished_cb => $steps->{'released1'});
179     };
180
181     step released1 => sub {
182         my ($err) = @_;
183         die $err if $err;
184
185         $chg->load(relative_slot => 'next', slot => $slot,
186                    res_cb => $steps->{'check_next_cb'});
187     };
188
189     step check_next_cb => sub {
190         my ($err, $res) = @_;
191         die $err if $err;
192
193         is_pointing_to($res, 1, "'next' from there is slot 1");
194
195         $res->release(finished_cb => $steps->{'released2'});
196     };
197
198     step released2 => sub {
199         my ($err) = @_;
200         die $err if $err;
201
202         $chg->reset(finished_cb => $steps->{'reset_finished_cb'});
203     };
204
205     step reset_finished_cb => sub {
206         my ($err) = @_;
207         die $err if $err;
208
209         $chg->load(relative_slot => "current", res_cb => $steps->{'check_reset_cb'});
210     };
211
212     step check_reset_cb => sub {
213         my ($err, $res) = @_;
214         die $err if $err;
215
216         is_pointing_to($res, 1, "after reset, 'current' is slot 1");
217
218         $res->release(finished_cb => $steps->{'released3'});
219     };
220
221     step released3 => sub {
222         my ($err) = @_;
223         die $err if $err;
224
225         $finished_cb->();
226     };
227 }
228 test_relative_slot(\&Amanda::MainLoop::quit);
229 Amanda::MainLoop::run();
230
231 # test loading relative_slot "next"
232 sub test_relative_next {
233     my ($finished_cb) = @_;
234
235     my $steps = define_steps
236         cb_ref => \$finished_cb;
237
238     step load_next => sub {
239         $chg->load(relative_slot => "next",
240             res_cb => sub {
241                 my ($err, $res) = @_;
242                 die $err if $err;
243
244                 is_pointing_to($res, 2, "loading relative slot 'next' loads the correct slot");
245
246                 $steps->{'release'}->($res);
247             }
248         );
249     };
250
251     step release => sub {
252         my ($res) = @_;
253
254         $res->release(finished_cb => sub {
255             my ($err) = @_;
256             die $err if $err;
257
258             $finished_cb->();
259         });
260     };
261 }
262 test_relative_next(\&Amanda::MainLoop::quit);
263 Amanda::MainLoop::run();
264
265 # scan the changer using except_slots
266 sub test_except_slots {
267     my ($finished_cb) = @_;
268     my $slot;
269     my %except_slots;
270
271     my $steps = define_steps
272         cb_ref => \$finished_cb;
273
274     step start => sub {
275         $chg->load(slot => "5", except_slots => { %except_slots },
276                    res_cb => $steps->{'loaded'});
277     };
278
279     step loaded => sub {
280         my ($err, $res) = @_;
281         if ($err) {
282             if ($err->notfound) {
283                 # this means the scan is done
284                 return $steps->{'quit'}->();
285             } elsif ($err->volinuse and defined $err->{'slot'}) {
286                 $slot = $err->{'slot'};
287             } else {
288                 die $err;
289             }
290         } else {
291             $slot = $res->{'this_slot'};
292         }
293
294         $except_slots{$slot} = 1;
295
296         if ($res) {
297             $res->release(finished_cb => $steps->{'released'});
298         } else {
299             $steps->{'released'}->();
300         }
301     };
302
303     step released => sub {
304         my ($err) = @_;
305         die $err if $err;
306
307         $chg->load(relative_slot => 'next', slot => $slot,
308                    except_slots => { %except_slots },
309                    res_cb => $steps->{'loaded'});
310     };
311
312     step quit => sub {
313         is_deeply({ %except_slots }, { 5=>1, 1=>1, 2=>1, 3=>1, 4=>1 },
314                 "scanning with except_slots works");
315         $finished_cb->();
316     };
317 }
318 test_except_slots(\&Amanda::MainLoop::quit);
319 Amanda::MainLoop::run();
320
321 # eject is not implemented
322 {
323     my $try_eject = make_cb('try_eject' => sub {
324         $chg->eject(finished_cb => make_cb(sub {
325             my ($err, $res) = @_;
326             chg_err_like($err,
327                 { type => 'failed', reason => 'notimpl' },
328                 "eject returns a failed/notimpl error");
329
330             Amanda::MainLoop::quit();
331         }));
332     });
333
334     $try_eject->();
335     Amanda::MainLoop::run();
336 }
337
338 # check num_slots and loading by label
339 {
340     my ($get_info, $load_label, $check_load_cb) = @_;
341
342     $get_info = make_cb('get_info' => sub {
343         $chg->info(info_cb => $load_label, info => [ 'num_slots', 'fast_search' ]);
344     });
345
346     $load_label = make_cb('load_label' => sub {
347         my $err = shift;
348         my %results = @_;
349         die($err) if defined($err);
350
351         is_deeply({ %results },
352             { num_slots => 5, fast_search => 1 },
353             "info() returns the correct num_slots and fast_search");
354
355         # note use of a glob metacharacter in the label name
356         $chg->load(label => "FOO?BAR", res_cb => $check_load_cb);
357     });
358
359     $check_load_cb = make_cb('check_load_cb' => sub {
360         my ($err, $res) = @_;
361         die $err if $err;
362
363         is_pointing_to($res, 4, "labeled volume found in slot 4");
364
365         $res->release(finished_cb => sub {
366             my ($err) = @_;
367             die $err if $err;
368
369             Amanda::MainLoop::quit();
370         });
371     });
372
373     # label slot 4, using our own symlink
374     mkpath("$taperoot/tmp");
375     symlink("../slot4", "$taperoot/tmp/data") or die "While symlinking: $!";
376     my $dev = Amanda::Device->new("file:$taperoot/tmp");
377     $dev->start($Amanda::Device::ACCESS_WRITE, "FOO?BAR", undef)
378         or die $dev->error_or_status();
379     $dev->finish()
380         or die $dev->error_or_status();
381     rmtree("$taperoot/tmp");
382
383     $get_info->();
384     Amanda::MainLoop::run();
385 }
386
387 # inventory is pretty cool
388 {
389     my $try_inventory = make_cb('try_inventory' => sub {
390         $chg->inventory(inventory_cb => make_cb(sub {
391             my ($err, $inv) = @_;
392             die $err if $err;
393
394             is_deeply($inv, [
395               { slot => 1, state => Amanda::Changer::SLOT_FULL,
396                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
397                 f_type => $Amanda::Header::F_EMPTY, label => undef,
398                 reserved => 0,  current => 1},
399               { slot => 2, state => Amanda::Changer::SLOT_FULL,
400                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
401                 f_type => $Amanda::Header::F_EMPTY, label => undef,
402                 reserved => 0 },
403               { slot => 3, state => Amanda::Changer::SLOT_FULL,
404                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
405                 f_type => $Amanda::Header::F_EMPTY, label => undef,
406                 reserved => 0 },
407               { slot => 4, state => Amanda::Changer::SLOT_FULL,
408                 device_status => $DEVICE_STATUS_SUCCESS,
409                 f_type => $Amanda::Header::F_TAPESTART, label => "FOO?BAR",
410                 reserved => 0 },
411               { slot => 5, state => Amanda::Changer::SLOT_FULL,
412                 device_status => $DEVICE_STATUS_VOLUME_UNLABELED,
413                 f_type => $Amanda::Header::F_EMPTY, label => undef,
414                 reserved => 0 },
415                 ], "inventory finds the labeled tape");
416
417             Amanda::MainLoop::quit();
418         }));
419     });
420
421     $try_inventory->();
422     Amanda::MainLoop::run();
423 }
424
425 $chg->quit();
426 rmtree($taperoot);