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)
40 my $user_msg_fn = sub {
43 $taperscan->scan(result_cb => $result_cb, user_msg_fn => $user_msg_fn);
47 C<Amanda::Taper::Scan> subclasses represent algorithms used by
48 C<Amanda::Taper::Scribe> (see L<Amanda::Taper::Scribe>) to scan for and select
51 Call C<Amanda::Taperscan->new()> to create a new taperscan
52 algorithm. The constructor takes the following keyword arguments:
54 changer Amanda::Changer object to use (required)
55 algorithm Taperscan algorithm to instantiate
61 The changer object must always be provided, but C<algorithm> may be omitted, in
62 which case the class specified by the user in the Amanda configuration file is
63 instantiated. The remaining options will be taken from the configuration file
64 if not specified. Default values for all of these options are applied before a
65 subclass's constructor is called.
67 Subclasses must implement a single method: C<scan>. It takes only one mandatory
68 parameter, C<result_cb>:
71 result_cb => $my_result_cb,
75 If C<user_msg_fn> is specified, then it is called with user-oriented messages to
76 indicate the progress of the scan.
78 The C<result_cb> takes the following positional parameters:
80 $error an error message, or undef on success
81 $reservation Amanda::Changer::Reservation object
82 $label label to apply to the volume
83 $access_mode access mode with which to start the volume
85 The error message can be a simple string or an C<Amanda::Changer::Error> object
86 (see L<Amanda::Changer>). The C<$label> and C<$access_mode> specify parameters
87 for starting the device contained in C<$reservation>.
89 =head1 SUBCLASS UTILITIES
91 There are a few common tasks for subclasses that are implemented as methods in
92 the parent class. Note that this class assumes subclasses will be implemented
93 as blessed hashrefs, and sets keys corresponding to the constructor arguments.
95 To read the tapelist, call C<read_tapelist>. This method caches the result in
96 C<< $self->{'tapelist'} >>, which will be used by the other functions here. In
97 general, call C<read_tapelist> at most once per C<scan()> invocation.
99 To see if a volume is reusable, call the C<is_reusable_volume> method. This takes
100 several keyword parameters:
102 $self->is_reusable_volume(
103 label => $label, # label to check
104 new_label_ok => $nlo, # count newly labeled vols as reusable?
107 Similarly, to calculate the oldest reusable volume, call
108 C<oldest_reusable_volume>:
110 $self->oldest_reusable_volume(
111 new_label_ok => $nlo, # count newly labeled vols as reusable?
114 Finally, to devise a new name for a volume, call C<make_new_tape_label>,
115 passing a tapelist, a labelstr, and a template. This will return C<undef>
116 if no label could be created.
118 $label = $self->make_new_tape_label(
119 labelstr => "foo-[0-9]+",
120 template => "foo-%%%%",
123 If no C<template> is provided, the function uses the value of
124 C<autolabel> specified when the object was constructed; similarly,
125 C<labelstr> defaults to the value specified at object construction.
129 This interface is temporary and will change in the next release.
131 Initiate a load by label:
133 user_msg_fn(search_label => 1,
136 The result of a load by label:
138 user_msg_fn(search_result => 1,
142 Initiate the scan of the slot $slot:
144 $self->user_msg_fn(scan_slot => 1,
147 Initiate the scan of the slot $slot which should have the label $label:
149 $self->user_msg_fn(scan_slot => 1,
153 The result of scanning slot $slot:
155 $self->user_msg_fn(slot_result => 1,
160 The result if the read label doesn't match the labelstr:
162 user_msg_fn(slot_result => 1,
163 does_not_match_labelstr => 1,
164 labelstr => $labelstr,
168 The result if the read label is not in the tapelist:
170 user_msg_fn(slot_result => 1,
171 not_in_tapelist => 1,
175 The result if the read label can't be used because it is active:
177 user_msg_fn(slot_result => 1,
182 The scan has failed, possibly with some additional information as to what the
183 scan was looking for.
185 user_msg_fn(scan_failed => 1,
186 expected_label => $label, # optional
187 expected_new => 1); # optional
193 use Amanda::Config qw( :getconf );
199 die "No changer given to Amanda::Taper::Scan->new"
200 unless exists $params{'changer'};
202 # fill in the optional parameters
203 $params{'algorithm'} = "traditional" # TODO: get from a configuration variable
204 unless exists $params{'algorithm'};
205 $params{'tapecycle'} = getconf($CNF_TAPECYCLE)
206 unless exists $params{'tapecycle'};
207 $params{'tapelist_filename'} =
208 Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST))
209 unless exists $params{'tapelist_filename'};
210 $params{'labelstr'} = getconf($CNF_LABELSTR)
211 unless exists $params{'labelstr'};
212 $params{'autolabel'} = getconf($CNF_AUTOLABEL)
213 unless exists $params{'autolabel'};
216 my $pkgname = "Amanda::Taper::Scan::" . $params{'algorithm'};
217 my $filename = $pkgname;
218 $filename =~ s|::|/|g;
220 if (!exists $INC{$filename}) {
221 eval "use $pkgname;";
223 # handle compile errors
224 die($@) if (exists $INC{$filename});
225 die("No such taperscan algorithm '$params{algorithm}'");
230 my $self = $pkgname->new(%params);
232 # and set the keys from the parameters
233 $self->{'changer'} = $params{'changer'};
234 $self->{'algorithm'} = $params{'algorithm'};
235 $self->{'tapecycle'} = $params{'tapecycle'};
236 $self->{'tapelist_filename'} = $params{'tapelist_filename'};
237 $self->{'labelstr'} = $params{'labelstr'};
238 $self->{'autolabel'} = $params{'autolabel'};
247 $params{'result_cb'}->("not implemented");
253 $self->{'tapelist'} = Amanda::Tapelist::read_tapelist($self->{'tapelist_filename'});
254 return $self->{'tapelist'};
257 sub oldest_reusable_volume {
262 my $num_acceptable = 0;
263 for my $tle (@{$self->{'tapelist'}}) {
264 next unless $tle->{'reuse'};
265 next if $tle->{'datestamp'} eq '0' and !$params{'new_label_ok'};
270 # if we didn't find at least $tapecycle reusable tapes, then
271 # there is no oldest reusable tape
272 return undef unless $num_acceptable >= $self->{'tapecycle'};
274 return $best->{'label'};
277 sub is_reusable_volume {
281 my $vol_tle = $self->{'tapelist'}->lookup_tapelabel($params{'label'});
282 return 0 unless $vol_tle;
283 return 0 unless $vol_tle->{'reuse'};
284 if ($vol_tle->{'datestamp'} eq '0') {
285 return $params{'new_label_ok'};
288 # see if it's in the collection of reusable volumes
289 my @tapelist = @{$self->{'tapelist'}};
290 my @reusable = @tapelist[$self->{'tapecycle'}-1 .. $#tapelist];
291 for my $tle (@reusable) {
292 return 1 if $tle eq $vol_tle;
299 sub make_new_tape_label {
302 my $template = exists $params{'template'}? $params{'template'} : $self->{'autolabel'}->{'template'};
303 my $labelstr = exists $params{'labelstr'}? $params{'labelstr'} : $self->{'labelstr'};
306 $template) =~ s/[^%]*(%+)[^%]*/length($1)/e;
307 my $nlabels = 10 ** $npercents;
309 # make up a sprintf pattern
311 $template) =~ s/(%+)/"%0" . length($1) . "d"/e;
313 my %existing_labels =
314 map { $_->{'label'} => 1 } @{$self->{'tapelist'}};
317 for ($i = 1; $i < $nlabels; $i++) {
318 $label = sprintf($sprintf_pat, $i);
319 last unless (exists $existing_labels{$label});
322 # bail out if we didn't find an unused label
323 return (undef, "Can't label unlabeled volume: All label used") if ($i >= $nlabels);
325 # verify $label matches $labelstr
326 if ($label !~ /$labelstr/) {
327 return (undef, "Newly-generated label '$label' does not match labelstr '$labelstr'");