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