Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Taper / Scan.pm
1 # Copyright (c) 2010-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This library is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU Lesser General Public License version 2.1 as
5 # published by the Free Software Foundation.
6 #
7 # This library 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 Lesser General Public
10 # License for more details.
11 #
12 # You should have received a copy of the GNU Lesser General Public License
13 # along with this library; if not, write to the Free Software Foundation,
14 # Inc., 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 package Amanda::Taper::Scan;
20
21 =head1 NAME
22
23 Amanda::Taper::Scan
24
25 =head1 SYNOPSIS
26
27 This is an abstract base class for taperscan algorithms.
28
29   # open the taperscan algorithm specified in the config
30   my $taperscan = Amanda::Taperscan->new(
31         changer => $changer);
32
33   my $result_cb = make_cb(result_cb => sub {
34     my ($err, $reservation, $label, $access_mode, $is_new) = @_;
35     die $err if $err;
36     # write to $reservation->{'device'}, using label $label, and opening
37     # the device with $access_mode (one of $ACCESS_WRITE or $ACCESS_APPEND)
38     # $is_new is set to 1 if the volume is not already labeled.
39     # ..
40   });
41   my $user_msg_fn = sub {
42     print "$_[0]\n";
43   };
44   $taperscan->scan(result_cb => $result_cb, user_msg_fn => $user_msg_fn);
45
46   # later ..
47   $taperscan->quit(); # also quit the changer
48
49 =head1 OVERVIEW
50
51 C<Amanda::Taper::Scan> subclasses represent algorithms used by
52 C<Amanda::Taper::Scribe> (see L<Amanda::Taper::Scribe>) to scan for and select
53 volumes for writing.
54
55 Call C<< Amanda::Taperscan->new() >> to create a new taperscan
56 algorithm.  The constructor takes the following keyword arguments:
57
58     changer       Amanda::Changer object to use (required)
59     algorithm     Taperscan algorithm to instantiate
60     tapelist      Amanda::Tapelist
61     tapecycle
62     labelstr
63     autolabel
64     meta_autolabel
65
66 The changer object must always be provided, but C<algorithm> may be omitted, in
67 which case the class specified by the user in the Amanda configuration file is
68 instantiated.  The remaining options will be taken from the configuration file
69 if not specified.  Default values for all of these options are applied before a
70 subclass's constructor is called.
71
72 The autolabel option should look like the C<CNF_AUTOLABEL> hash - see
73 L<Amanda::Config>.
74
75 Subclasses must implement a single method: C<scan>.  It takes only one mandatory
76 parameter, C<result_cb>:
77
78   $taperscan->scan(
79     result_cb => $my_result_cb,
80     user_msg_fn => $fn,
81     );
82
83 If C<user_msg_fn> is specified, then it is called with user-oriented messages to
84 indicate the progress of the scan.
85
86 The C<result_cb> takes the following positional parameters:
87
88   $error        an error message, or undef on success
89   $reservation  Amanda::Changer::Reservation object
90   $label        label to apply to the volume
91   $access_mode  access mode with which to start the volume
92
93 The error message can be a simple string or an C<Amanda::Changer::Error> object
94 (see L<Amanda::Changer>).  The C<$label> and C<$access_mode> specify parameters
95 for starting the device contained in C<$reservation>.
96
97 To cleanly terminate an Amanda::Taper::Scan object:
98
99   $taperscan->quit()
100
101 It also terminate the changer by caller $chg->quit().
102
103 =head1 SUBCLASS UTILITIES
104
105 There are a few common tasks for subclasses that are implemented as methods in
106 the parent class.  Note that this class assumes subclasses will be implemented
107 as blessed hashrefs, and sets keys corresponding to the constructor arguments.
108
109 To read the tapelist, call C<read_tapelist>.  This method caches the result in
110 C<< $self->{'tapelist'} >>, which will be used by the other functions here.  In
111 general, call C<read_tapelist> at most once per C<scan()> invocation.
112
113 To see if a volume is reusable, call the C<is_reusable_volume> method.  This takes
114 several keyword parameters:
115
116     $self->is_reusable_volume(
117         label => $label,         # label to check
118         new_label_ok => $nlo,    # count newly labeled vols as reusable?
119     );
120
121 Similarly, to calculate the oldest reusable volume, call
122 C<oldest_reusable_volume>:
123
124     $self->oldest_reusable_volume(
125         new_label_ok => $nlo,    # count newly labeled vols as reusable?
126     );
127
128 =head2 user_msg_fn
129
130 This interface is temporary and will change in the next release.
131
132 Initiate a load by label:
133
134   user_msg_fn(search_label => 1,
135                    label        => $label);
136
137 The result of a load by label:
138
139   user_msg_fn(search_result => 1,
140                    res           => $res,
141                    err           => $err);
142
143 Initiate the scan of the slot $slot:
144
145   $self->user_msg_fn(scan_slot => 1,
146                      slot      => $slot);
147
148 Initiate the scan of the slot $slot which should have the label $label:
149
150   $self->user_msg_fn(scan_slot => 1,
151                      slot      => $slot,
152                      label     => $label);
153
154 The result of scanning slot $slot:
155
156   $self->user_msg_fn(slot_result => 1,
157                      slot        => $slot,
158                      err         => $err,
159                      res         => $res);
160
161 The result if the read label doesn't match the labelstr:
162
163   user_msg_fn(slot_result             => 1,
164                    does_not_match_labelstr => 1,
165                    labelstr                => $labelstr,
166                    slot                    => $slot,
167                    res                     => $res);
168
169 The result if the read label is not in the tapelist:
170
171   user_msg_fn(slot_result     => 1,
172                    not_in_tapelist => 1,
173                    slot            => $slot,
174                    res             => $res);
175
176 The result if the read label can't be used because it is active:
177
178   user_msg_fn(slot_result => 1,
179                    active      => 1,
180                    slot        => $slot,
181                    res         => $res);
182
183 The result if the volume can't be labeled because autolabel is not set:
184
185   user_msg_fn(slot_result => 1,
186                    not_autolabel => 1,
187                    slot          => $slot,
188                    res           => $res);
189
190 The result if the volume is empty and can't be labeled because autolabel setting:
191
192   user_msg_fn(slot_result => 1,
193                    empty         => 1,
194                    slot          => $slot,
195                    res           => $res);
196
197 The result if the volume is a non-amanda volume and can't be labeled because autolabel setting:
198
199   user_msg_fn(slot_result => 1,
200                    non_amanda    => 1,
201                    slot          => $slot,
202                    res           => $res);
203
204 The result if the volume is in error and can't be labeled because autolabel setting:
205
206   user_msg_fn(slot_result => 1,
207                    volume_error  => 1,
208                    err           => $err,
209                    slot          => $slot,
210                    res           => $res);
211
212 The result if the volume is in error and can't be labeled because autolabel setting:
213
214   user_msg_fn(slot_result => 1,
215                    not_success   => 1,
216                    err           => $err,
217                    slot          => $slot,
218                    res           => $res);
219
220 The scan has failed, possibly with some additional information as to what the
221 scan was looking for.
222
223   user_msg_fn(scan_failed => 1,
224               expected_label => $label, # optional
225               expected_new => 1); # optional
226
227 =cut
228
229 use strict;
230 use warnings;
231 use Amanda::Config qw( :getconf );
232 use Amanda::Tapelist;
233 use Amanda::Debug;
234
235 sub new {
236     my $class = shift;
237     my %params = @_;
238
239     die "No changer given to Amanda::Taper::Scan->new"
240         unless exists $params{'changer'};
241     # fill in the optional parameters
242     $params{'algorithm'} = "traditional"
243         unless defined $params{'algorithm'} and $params{'algorithm'} ne '';
244     $params{'tapecycle'} = getconf($CNF_TAPECYCLE)
245         unless exists $params{'tapecycle'};
246     $params{'labelstr'} = getconf($CNF_LABELSTR)
247         unless exists $params{'labelstr'};
248     $params{'autolabel'} = getconf($CNF_AUTOLABEL)
249         unless exists $params{'autolabel'};
250     $params{'meta_autolabel'} = getconf($CNF_META_AUTOLABEL)
251         unless exists $params{'meta_autolabel'};
252
253     my $plugin;
254     if (!defined $params{'algorithm'} or $params{'algorithm'} eq '') {
255         $params{'algorithm'} = "traditional";
256         $plugin = "traditional";
257     } else {
258         my $taperscan = Amanda::Config::lookup_taperscan($params{'algorithm'});
259         if ($taperscan) {
260             $plugin = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PLUGIN);
261             $params{'properties'} = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PROPERTY);
262         } else {
263             $plugin = $params{'algorithm'};
264         }
265     }
266     # load the package
267     my $pkgname = "Amanda::Taper::Scan::" . $plugin;
268     my $filename = $pkgname;
269     $filename =~ s|::|/|g;
270     $filename .= '.pm';
271     if (!exists $INC{$filename}) {
272         eval "use $pkgname;";
273         if ($@) {
274             # handle compile errors
275             die($@) if (exists $INC{$filename});
276             die("No such taperscan algorithm '$plugin'");
277         }
278     }
279
280     # instantiate it
281     my $self = eval {$pkgname->new(%params);};
282     if ($@ || !defined $self) {
283         debug("Can't instantiate $pkgname");
284         die("Can't instantiate $pkgname");
285     }
286
287     # and set the keys from the parameters
288     $self->{'changer'} = $params{'changer'};
289     $self->{'algorithm'} = $params{'algorithm'};
290     $self->{'plugin'} = $params{'plugin'};
291     $self->{'tapecycle'} = $params{'tapecycle'};
292     $self->{'labelstr'} = $params{'labelstr'};
293     $self->{'autolabel'} = $params{'autolabel'};
294     $self->{'meta_autolabel'} = $params{'meta_autolabel'};
295     $self->{'tapelist'} = $params{'tapelist'};
296
297     return $self;
298 }
299
300 sub DESTROY {
301     my $self = shift;
302
303     die("Taper::Scan did not quit") if defined $self->{'changer'};
304 }
305
306 sub quit {
307     my $self = shift;
308
309     if (defined $self->{'chg'} && $self->{'chg'} != $self->{'initial_chg'}) {
310         $self->{'chg'}->quit();
311     }
312     $self->{'changer'}->quit() if defined $self->{'changer'};
313     foreach (keys %$self) {
314         delete $self->{$_};
315     }
316 }
317
318 sub scan {
319     my $self = shift;
320     my %params = @_;
321
322     $params{'result_cb'}->("scan not implemented");
323 }
324
325 sub read_tapelist {
326     my $self = shift;
327
328     $self->{'tapelist'}->reload();
329 }
330
331 sub oldest_reusable_volume {
332     my $self = shift;
333     my %params = @_;
334
335     my $best = undef;
336     my $num_acceptable = 0;
337     for my $tle (@{$self->{'tapelist'}->{'tles'}}) {
338         next unless $tle->{'reuse'};
339         next if $tle->{'datestamp'} eq '0' and !$params{'new_label_ok'};
340         $num_acceptable++;
341         $best = $tle;
342     }
343
344     # if we didn't find at least $tapecycle reusable tapes, then
345     # there is no oldest reusable tape
346     return undef unless $num_acceptable >= $self->{'tapecycle'};
347
348     return $best->{'label'};
349 }
350
351 sub is_reusable_volume {
352     my $self = shift;
353     my %params = @_;
354
355     my $vol_tle = $self->{'tapelist'}->lookup_tapelabel($params{'label'});
356     return 0 unless $vol_tle;
357     return 0 unless $vol_tle->{'reuse'};
358     if ($vol_tle->{'datestamp'} eq '0') {
359         return $params{'new_label_ok'};
360     }
361
362     # see if it's in the collection of reusable volumes
363     my @tapelist = @{$self->{'tapelist'}->{'tles'}};
364     my @reusable = @tapelist[$self->{'tapecycle'}-1 .. $#tapelist];
365     for my $tle (@reusable) {
366         return 1 if $tle eq $vol_tle;
367     }
368
369     return 0;
370 }
371
372 1;