1 # Copyright (c) 2010-2012 Zmanda, Inc. All Rights Reserved.
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.
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.
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.
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 package Amanda::Taper::Scan;
27 This is an abstract base class for taperscan algorithms.
29 # open the taperscan algorithm specified in the config
30 my $taperscan = Amanda::Taperscan->new(
33 my $result_cb = make_cb(result_cb => sub {
34 my ($err, $reservation, $label, $access_mode, $is_new) = @_;
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.
41 my $user_msg_fn = sub {
44 $taperscan->scan(result_cb => $result_cb, user_msg_fn => $user_msg_fn);
47 $taperscan->quit(); # also quit the changer
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
55 Call C<< Amanda::Taperscan->new() >> to create a new taperscan
56 algorithm. The constructor takes the following keyword arguments:
58 changer Amanda::Changer object to use (required)
59 algorithm Taperscan algorithm to instantiate
60 tapelist Amanda::Tapelist
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.
72 The autolabel option should look like the C<CNF_AUTOLABEL> hash - see
75 Subclasses must implement a single method: C<scan>. It takes only one mandatory
76 parameter, C<result_cb>:
79 result_cb => $my_result_cb,
83 If C<user_msg_fn> is specified, then it is called with user-oriented messages to
84 indicate the progress of the scan.
86 The C<result_cb> takes the following positional parameters:
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
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>.
97 To cleanly terminate an Amanda::Taper::Scan object:
101 It also terminate the changer by caller $chg->quit().
103 =head1 SUBCLASS UTILITIES
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.
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.
113 To see if a volume is reusable, call the C<is_reusable_volume> method. This takes
114 several keyword parameters:
116 $self->is_reusable_volume(
117 label => $label, # label to check
118 new_label_ok => $nlo, # count newly labeled vols as reusable?
121 Similarly, to calculate the oldest reusable volume, call
122 C<oldest_reusable_volume>:
124 $self->oldest_reusable_volume(
125 new_label_ok => $nlo, # count newly labeled vols as reusable?
130 This interface is temporary and will change in the next release.
132 Initiate a load by label:
134 user_msg_fn(search_label => 1,
137 The result of a load by label:
139 user_msg_fn(search_result => 1,
143 Initiate the scan of the slot $slot:
145 $self->user_msg_fn(scan_slot => 1,
148 Initiate the scan of the slot $slot which should have the label $label:
150 $self->user_msg_fn(scan_slot => 1,
154 The result of scanning slot $slot:
156 $self->user_msg_fn(slot_result => 1,
161 The result if the read label doesn't match the labelstr:
163 user_msg_fn(slot_result => 1,
164 does_not_match_labelstr => 1,
165 labelstr => $labelstr,
169 The result if the read label is not in the tapelist:
171 user_msg_fn(slot_result => 1,
172 not_in_tapelist => 1,
176 The result if the read label can't be used because it is active:
178 user_msg_fn(slot_result => 1,
183 The result if the volume can't be labeled because autolabel is not set:
185 user_msg_fn(slot_result => 1,
190 The result if the volume is empty and can't be labeled because autolabel setting:
192 user_msg_fn(slot_result => 1,
197 The result if the volume is a non-amanda volume and can't be labeled because autolabel setting:
199 user_msg_fn(slot_result => 1,
204 The result if the volume is in error and can't be labeled because autolabel setting:
206 user_msg_fn(slot_result => 1,
212 The result if the volume is in error and can't be labeled because autolabel setting:
214 user_msg_fn(slot_result => 1,
220 The scan has failed, possibly with some additional information as to what the
221 scan was looking for.
223 user_msg_fn(scan_failed => 1,
224 expected_label => $label, # optional
225 expected_new => 1); # optional
231 use Amanda::Config qw( :getconf );
232 use Amanda::Tapelist;
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'};
254 if (!defined $params{'algorithm'} or $params{'algorithm'} eq '') {
255 $params{'algorithm'} = "traditional";
256 $plugin = "traditional";
258 my $taperscan = Amanda::Config::lookup_taperscan($params{'algorithm'});
260 $plugin = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PLUGIN);
261 $params{'properties'} = Amanda::Config::taperscan_getconf($taperscan, $TAPERSCAN_PROPERTY);
263 $plugin = $params{'algorithm'};
267 my $pkgname = "Amanda::Taper::Scan::" . $plugin;
268 my $filename = $pkgname;
269 $filename =~ s|::|/|g;
271 if (!exists $INC{$filename}) {
272 eval "use $pkgname;";
274 # handle compile errors
275 die($@) if (exists $INC{$filename});
276 die("No such taperscan algorithm '$plugin'");
281 my $self = eval {$pkgname->new(%params);};
282 if ($@ || !defined $self) {
283 debug("Can't instantiate $pkgname");
284 die("Can't instantiate $pkgname");
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'};
303 die("Taper::Scan did not quit") if defined $self->{'changer'};
309 if (defined $self->{'chg'} && $self->{'chg'} != $self->{'initial_chg'}) {
310 $self->{'chg'}->quit();
312 $self->{'changer'}->quit() if defined $self->{'changer'};
313 foreach (keys %$self) {
322 $params{'result_cb'}->("scan not implemented");
328 $self->{'tapelist'}->reload();
331 sub oldest_reusable_volume {
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'};
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'};
348 return $best->{'label'};
351 sub is_reusable_volume {
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'};
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;