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