1 # Copyright (c) 2010-2012 Zmanda, Inc. All Rights Reserved.
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.
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.
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.
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 package Amanda::Taper::Scan;
28 This is an abstract base class for taperscan algorithms.
30 # open the taperscan algorithm specified in the config
31 my $taperscan = Amanda::Taperscan->new(
34 my $result_cb = make_cb(result_cb => sub {
35 my ($err, $reservation, $label, $access_mode, $is_new) = @_;
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.
42 my $user_msg_fn = sub {
45 $taperscan->scan(result_cb => $result_cb, user_msg_fn => $user_msg_fn);
48 $taperscan->quit(); # also quit the changer
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
56 Call C<< Amanda::Taperscan->new() >> to create a new taperscan
57 algorithm. The constructor takes the following keyword arguments:
59 changer Amanda::Changer object to use (required)
60 algorithm Taperscan algorithm to instantiate
61 tapelist Amanda::Tapelist
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.
73 The autolabel option should look like the C<CNF_AUTOLABEL> hash - see
76 Subclasses must implement a single method: C<scan>. It takes only one mandatory
77 parameter, C<result_cb>:
80 result_cb => $my_result_cb,
84 If C<user_msg_fn> is specified, then it is called with user-oriented messages to
85 indicate the progress of the scan.
87 The C<result_cb> takes the following positional parameters:
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
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>.
98 To cleanly terminate an Amanda::Taper::Scan object:
102 It also terminate the changer by caller $chg->quit().
104 =head1 SUBCLASS UTILITIES
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.
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.
114 To see if a volume is reusable, call the C<is_reusable_volume> method. This takes
115 several keyword parameters:
117 $self->is_reusable_volume(
118 label => $label, # label to check
119 new_label_ok => $nlo, # count newly labeled vols as reusable?
122 Similarly, to calculate the oldest reusable volume, call
123 C<oldest_reusable_volume>:
125 $self->oldest_reusable_volume(
126 new_label_ok => $nlo, # count newly labeled vols as reusable?
131 This interface is temporary and will change in the next release.
133 Initiate a load by label:
135 user_msg_fn(search_label => 1,
138 The result of a load by label:
140 user_msg_fn(search_result => 1,
144 Initiate the scan of the slot $slot:
146 $self->user_msg_fn(scan_slot => 1,
149 Initiate the scan of the slot $slot which should have the label $label:
151 $self->user_msg_fn(scan_slot => 1,
155 The result of scanning slot $slot:
157 $self->user_msg_fn(slot_result => 1,
162 The result if the read label doesn't match the labelstr:
164 user_msg_fn(slot_result => 1,
165 does_not_match_labelstr => 1,
166 labelstr => $labelstr,
170 The result if the read label is not in the tapelist:
172 user_msg_fn(slot_result => 1,
173 not_in_tapelist => 1,
177 The result if the read label can't be used because it is active:
179 user_msg_fn(slot_result => 1,
184 The result if the volume can't be labeled because autolabel is not set:
186 user_msg_fn(slot_result => 1,
191 The result if the volume is empty and can't be labeled because autolabel setting:
193 user_msg_fn(slot_result => 1,
198 The result if the volume is a non-amanda volume and can't be labeled because autolabel setting:
200 user_msg_fn(slot_result => 1,
205 The result if the volume is in error and can't be labeled because autolabel setting:
207 user_msg_fn(slot_result => 1,
213 The result if the volume is in error and can't be labeled because autolabel setting:
215 user_msg_fn(slot_result => 1,
221 The scan has failed, possibly with some additional information as to what the
222 scan was looking for.
224 user_msg_fn(scan_failed => 1,
225 expected_label => $label, # optional
226 expected_new => 1); # optional
232 use Amanda::Config qw( :getconf );
233 use Amanda::Tapelist;
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'};
255 if (!defined $params{'algorithm'} or $params{'algorithm'} eq '') {
256 $params{'algorithm'} = "traditional";
257 $plugin = "traditional";
259 my $taperscan = Amanda::Config::lookup_taperscan($params{'algorithm'});
261 $plugin = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PLUGIN);
262 $params{'properties'} = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PROPERTY);
264 $plugin = $params{'algorithm'};
268 my $pkgname = "Amanda::Taper::Scan::" . $plugin;
269 my $filename = $pkgname;
270 $filename =~ s|::|/|g;
272 if (!exists $INC{$filename}) {
273 eval "use $pkgname;";
275 # handle compile errors
276 die($@) if (exists $INC{$filename});
277 die("No such taperscan algorithm '$plugin'");
282 my $self = eval {$pkgname->new(%params);};
283 if ($@ || !defined $self) {
284 debug("Can't instantiate $pkgname");
285 die("Can't instantiate $pkgname");
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'};
304 die("Taper::Scan did not quit") if defined $self->{'changer'};
310 if (defined $self->{'chg'} && $self->{'chg'} != $self->{'initial_chg'}) {
311 $self->{'chg'}->quit();
313 $self->{'changer'}->quit() if defined $self->{'changer'};
314 foreach (keys %$self) {
323 $params{'result_cb'}->("scan not implemented");
329 $self->{'tapelist'}->reload();
332 sub oldest_reusable_volume {
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'};
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'};
350 return $best->{'label'};
353 sub is_reusable_volume {
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'};
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;