1 # Copyright (c) 2010 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);
48 C<Amanda::Taper::Scan> subclasses represent algorithms used by
49 C<Amanda::Taper::Scribe> (see L<Amanda::Taper::Scribe>) to scan for and select
52 Call C<< Amanda::Taperscan->new() >> to create a new taperscan
53 algorithm. The constructor takes the following keyword arguments:
55 changer Amanda::Changer object to use (required)
56 algorithm Taperscan algorithm to instantiate
62 The changer object must always be provided, but C<algorithm> may be omitted, in
63 which case the class specified by the user in the Amanda configuration file is
64 instantiated. The remaining options will be taken from the configuration file
65 if not specified. Default values for all of these options are applied before a
66 subclass's constructor is called.
68 The autolabel option should look like the C<CNF_AUTOLABEL> hash - see
71 Subclasses must implement a single method: C<scan>. It takes only one mandatory
72 parameter, C<result_cb>:
75 result_cb => $my_result_cb,
79 If C<user_msg_fn> is specified, then it is called with user-oriented messages to
80 indicate the progress of the scan.
82 The C<result_cb> takes the following positional parameters:
84 $error an error message, or undef on success
85 $reservation Amanda::Changer::Reservation object
86 $label label to apply to the volume
87 $access_mode access mode with which to start the volume
89 The error message can be a simple string or an C<Amanda::Changer::Error> object
90 (see L<Amanda::Changer>). The C<$label> and C<$access_mode> specify parameters
91 for starting the device contained in C<$reservation>.
93 =head1 SUBCLASS UTILITIES
95 There are a few common tasks for subclasses that are implemented as methods in
96 the parent class. Note that this class assumes subclasses will be implemented
97 as blessed hashrefs, and sets keys corresponding to the constructor arguments.
99 To read the tapelist, call C<read_tapelist>. This method caches the result in
100 C<< $self->{'tapelist'} >>, which will be used by the other functions here. In
101 general, call C<read_tapelist> at most once per C<scan()> invocation.
103 To see if a volume is reusable, call the C<is_reusable_volume> method. This takes
104 several keyword parameters:
106 $self->is_reusable_volume(
107 label => $label, # label to check
108 new_label_ok => $nlo, # count newly labeled vols as reusable?
111 Similarly, to calculate the oldest reusable volume, call
112 C<oldest_reusable_volume>:
114 $self->oldest_reusable_volume(
115 new_label_ok => $nlo, # count newly labeled vols as reusable?
118 Finally, to devise a new name for a volume, call C<make_new_tape_label>,
119 passing a tapelist, a labelstr, and a template. This will return C<undef>
120 if no label could be created.
122 $label = $self->make_new_tape_label(
123 labelstr => "foo-[0-9]+",
124 template => "foo-%%%%",
127 If no C<template> is provided, the function uses the value of
128 C<autolabel> specified when the object was constructed; similarly,
129 C<labelstr> defaults to the value specified at object construction.
133 This interface is temporary and will change in the next release.
135 Initiate a load by label:
137 user_msg_fn(search_label => 1,
140 The result of a load by label:
142 user_msg_fn(search_result => 1,
146 Initiate the scan of the slot $slot:
148 $self->user_msg_fn(scan_slot => 1,
151 Initiate the scan of the slot $slot which should have the label $label:
153 $self->user_msg_fn(scan_slot => 1,
157 The result of scanning slot $slot:
159 $self->user_msg_fn(slot_result => 1,
164 The result if the read label doesn't match the labelstr:
166 user_msg_fn(slot_result => 1,
167 does_not_match_labelstr => 1,
168 labelstr => $labelstr,
172 The result if the read label is not in the tapelist:
174 user_msg_fn(slot_result => 1,
175 not_in_tapelist => 1,
179 The result if the read label can't be used because it is active:
181 user_msg_fn(slot_result => 1,
186 The scan has failed, possibly with some additional information as to what the
187 scan was looking for.
189 user_msg_fn(scan_failed => 1,
190 expected_label => $label, # optional
191 expected_new => 1); # optional
197 use Amanda::Config qw( :getconf );
198 use Amanda::Tapelist;
204 die "No changer given to Amanda::Taper::Scan->new"
205 unless exists $params{'changer'};
207 # fill in the optional parameters
208 $params{'algorithm'} = "traditional" # TODO: get from a configuration variable
209 unless exists $params{'algorithm'};
210 $params{'tapecycle'} = getconf($CNF_TAPECYCLE)
211 unless exists $params{'tapecycle'};
212 $params{'tapelist_filename'} =
213 Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST))
214 unless exists $params{'tapelist_filename'};
215 $params{'labelstr'} = getconf($CNF_LABELSTR)
216 unless exists $params{'labelstr'};
217 $params{'autolabel'} = getconf($CNF_AUTOLABEL)
218 unless exists $params{'autolabel'};
221 my $pkgname = "Amanda::Taper::Scan::" . $params{'algorithm'};
222 my $filename = $pkgname;
223 $filename =~ s|::|/|g;
225 if (!exists $INC{$filename}) {
226 eval "use $pkgname;";
228 # handle compile errors
229 die($@) if (exists $INC{$filename});
230 die("No such taperscan algorithm '$params{algorithm}'");
235 my $self = $pkgname->new(%params);
237 # and set the keys from the parameters
238 $self->{'changer'} = $params{'changer'};
239 $self->{'algorithm'} = $params{'algorithm'};
240 $self->{'tapecycle'} = $params{'tapecycle'};
241 $self->{'tapelist_filename'} = $params{'tapelist_filename'};
242 $self->{'labelstr'} = $params{'labelstr'};
243 $self->{'autolabel'} = $params{'autolabel'};
244 $self->{'tapelist'} = Amanda::Tapelist->new($self->{'tapelist_filename'});
253 $params{'result_cb'}->("not implemented");
259 $self->{'tapelist'}->reload();
262 sub oldest_reusable_volume {
267 my $num_acceptable = 0;
268 for my $tle (@{$self->{'tapelist'}->{'tles'}}) {
269 next unless $tle->{'reuse'};
270 next if $tle->{'datestamp'} eq '0' and !$params{'new_label_ok'};
275 # if we didn't find at least $tapecycle reusable tapes, then
276 # there is no oldest reusable tape
277 return undef unless $num_acceptable >= $self->{'tapecycle'};
279 return $best->{'label'};
282 sub is_reusable_volume {
286 my $vol_tle = $self->{'tapelist'}->lookup_tapelabel($params{'label'});
287 return 0 unless $vol_tle;
288 return 0 unless $vol_tle->{'reuse'};
289 if ($vol_tle->{'datestamp'} eq '0') {
290 return $params{'new_label_ok'};
293 # see if it's in the collection of reusable volumes
294 my @tapelist = @{$self->{'tapelist'}->{'tles'}};
295 my @reusable = @tapelist[$self->{'tapecycle'}-1 .. $#tapelist];
296 for my $tle (@reusable) {
297 return 1 if $tle eq $vol_tle;
304 sub make_new_tape_label {
308 my $tl = exists $params{'tapelist'}? $params{'tapelist'} : $self->{'tapelist'};
309 my $template = exists $params{'template'}? $params{'template'} : $self->{'autolabel'}->{'template'};
310 my $labelstr = exists $params{'labelstr'}? $params{'labelstr'} : $self->{'labelstr'};
313 $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
314 my $nlabels = 10 ** $npercents;
316 # make up a sprintf pattern
318 $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
320 my %existing_labels =
321 map { $_->{'label'} => 1 } @{$tl->{'tles'}};
324 for ($i = 1; $i < $nlabels; $i++) {
325 $label = sprintf($sprintf_pat, $i);
326 last unless (exists $existing_labels{$label});
329 # bail out if we didn't find an unused label
330 return (undef, "Can't label unlabeled volume: All label used") if ($i >= $nlabels);
332 # verify $label matches $labelstr
333 if ($label !~ /$labelstr/) {
334 return (undef, "Newly-generated label '$label' does not match labelstr '$labelstr'");