Imported Upstream version 3.3.3
[debian/amanda] / perl / Amanda / Taper / Controller.pm
1 # Copyright (c) 2009-2012 Zmanda Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7 #
8 # This program 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 General Public License
11 # for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 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 =head1 NAME
21
22 Amanda::Taper::Controller
23
24 =head1 DESCRIPTION
25
26 This package is a component of the Amanda taper, and is not intended for use by
27 other scripts or applications.
28
29 The controller interfaces with the driver (via L<Amanda::Taper::Protocol>) and
30 controls one or more workers (L<Amanda::Taper::Worker>).
31
32 The controller create an L<Amanda::Taper::Worker> object for each
33 START_TAPER command it receive. It dispatch the following commands
34 to the correct worker.  
35
36 =cut
37
38 use lib '@amperldir@';
39 use strict;
40 use warnings;
41
42 package Amanda::Taper::Controller;
43
44 use POSIX qw( :errno_h );
45 use Amanda::Changer;
46 use Amanda::Config qw( :getconf config_dir_relative );
47 use Amanda::Header;
48 use Amanda::Holding;
49 use Amanda::MainLoop qw( :GIOCondition );
50 use Amanda::MainLoop;
51 use Amanda::Taper::Protocol;
52 use Amanda::Taper::Scan;
53 use Amanda::Taper::Worker;
54 use Amanda::Interactivity;
55 use Amanda::Logfile qw( :logtype_t log_add );
56 use Amanda::Xfer qw( :constants );
57 use Amanda::Util qw( quote_string );
58 use Amanda::Tapelist;
59 use File::Temp;
60
61 sub new {
62     my $class = shift;
63     my %params = @_;
64
65     my $self = bless {
66
67         # filled in at start
68         proto => undef,
69         tapelist => $params{'tapelist'},
70
71         worker => {},
72     }, $class;
73     return $self;
74 }
75
76 # The feedback object mediates between messages from the driver and the ongoing
77 # action with the taper.  This is made a little bit complicated because the
78 # driver conversation is fairly contextual, with some responses answering
79 # "questions" asked earlier.  This is modeled with the following taper
80 # "states":
81 #
82 # init:
83 #   waiting for START-TAPER command
84 # starting:
85 #   warming up devices; TAPER-OK not sent yet
86 # idle:
87 #   not currently dumping anything
88 # making_xfer:
89 #   setting up a transfer for a new dump
90 # getting_header:
91 #   getting the header before beginning a new dump
92 # writing:
93 #   in the middle of writing a file (self->{'handle'} set)
94 # error:
95 #   a fatal error has occurred, so this object won't do anything
96
97 sub start {
98     my $self = shift;
99
100     my $message_cb = make_cb(message_cb => sub {
101         my ($msgtype, %params) = @_;
102         my $msg;
103         if (defined $msgtype) {
104             $msg = "unhandled command '$msgtype'";
105         } else {
106             $msg = $params{'error'};
107         }
108         log_add($L_ERROR, "$msg");
109         print STDERR "$msg\n";
110         $self->{'proto'}->send(Amanda::Taper::Protocol::BAD_COMMAND,
111             message => $msg);
112     });
113     $self->{'proto'} = Amanda::Taper::Protocol->new(
114         rx_fh => *STDIN,
115         tx_fh => *STDOUT,
116         message_cb => $message_cb,
117         message_obj => $self,
118         debug => $Amanda::Config::debug_taper?'taper/driver':'',
119     );
120
121     my $changer = Amanda::Changer->new(undef, tapelist => $self->{'tapelist'});
122     if ($changer->isa("Amanda::Changer::Error")) {
123         # send a TAPE_ERROR right away
124         $self->{'proto'}->send(Amanda::Taper::Protocol::TAPE_ERROR,
125                 worker_name => "SETUP",
126                 message => "$changer");
127
128         # log the error (note that the message is intentionally not quoted)
129         log_add($L_ERROR, "no-tape error [$changer]");
130
131         # wait for it to be transmitted, then exit
132         $self->{'proto'}->stop(finished_cb => sub {
133             Amanda::MainLoop::quit();
134         });
135
136         # don't finish start()ing
137         return;
138     }
139
140     my $interactivity = Amanda::Interactivity->new(
141                                         name => getconf($CNF_INTERACTIVITY));
142     my $scan_name = getconf($CNF_TAPERSCAN);
143     $self->{'taperscan'} = Amanda::Taper::Scan->new(algorithm => $scan_name,
144                                             changer => $changer,
145                                             interactivity => $interactivity,
146                                             tapelist => $self->{'tapelist'});
147 }
148
149 sub quit {
150     my $self = shift;
151     my %params = @_;
152     my @errors = ();
153     my @worker = ();
154
155     my $steps = define_steps
156         cb_ref => \$params{'finished_cb'};
157
158     step init => sub {
159         @worker = values %{$self->{'worker'}};
160         delete $self->{'worker'};
161         $steps->{'quit_scribe'}->();
162     };
163
164     step quit_scribe => sub {
165         my $worker = shift @worker;
166         if (defined $worker and defined $worker->{'scribe'}) {
167             $worker->{'scribe'}->quit(finished_cb => sub {
168                 my ($err) = @_;
169                 push @errors, $err if ($err);
170
171                 $steps->{'quit_scribe'}->();
172             });
173         } else {
174             $steps->{'stop_proto'}->();
175         }
176     };
177
178     step stop_proto => sub {
179         $self->{'proto'}->stop(finished_cb => sub {
180             my ($err) = @_;
181             push @errors, $err if ($err);
182
183             $steps->{'done'}->();
184         });
185     };
186
187     step done => sub {
188         $self->{'taperscan'}->quit() if defined $self->{'taperscan'};
189         if (@errors) {
190             $params{'finished_cb'}->(join("; ", @errors));
191         } else {
192             $params{'finished_cb'}->();
193         }
194     };
195 }
196
197 ##
198 # Driver commands
199
200 sub msg_START_TAPER {
201     my $self = shift;
202     my ($msgtype, %params) = @_;
203
204     my $worker = new Amanda::Taper::Worker($params{'worker_name'}, $self,
205                                   $params{'timestamp'});
206
207     $self->{'worker'}->{$params{'worker_name'}} = $worker;
208
209     $self->{'timestamp'} = $params{'timestamp'};
210 }
211
212 # defer both PORT_ and FILE_WRITE to a common method
213 sub msg_FILE_WRITE {
214     my $self = shift;
215     my ($msgtype, %params) = @_;
216
217     my $worker = $self->{'worker'}->{$params{'worker_name'}};
218     $worker->FILE_WRITE(@_);
219 }
220
221 sub msg_PORT_WRITE {
222     my $self = shift;
223     my ($msgtype, %params) = @_;
224
225     my $worker = $self->{'worker'}->{$params{'worker_name'}};
226     $worker->PORT_WRITE(@_);
227 }
228
229 sub msg_START_SCAN {
230     my $self = shift;
231     my ($msgtype, %params) = @_;
232
233     my $worker = $self->{'worker'}->{$params{'worker_name'}};
234     $worker->START_SCAN(@_);
235 }
236
237 sub msg_NEW_TAPE {
238     my $self = shift;
239     my ($msgtype, %params) = @_;
240
241     my $worker = $self->{'worker'}->{$params{'worker_name'}};
242     $worker->NEW_TAPE(@_);
243 }
244
245 sub msg_NO_NEW_TAPE {
246     my $self = shift;
247     my ($msgtype, %params) = @_;
248
249     my $worker = $self->{'worker'}->{$params{'worker_name'}};
250     $worker->NO_NEW_TAPE(@_);
251 }
252
253 sub msg_DONE {
254     my $self = shift;
255     my ($msgtype, %params) = @_;
256
257     my $worker = $self->{'worker'}->{$params{'worker_name'}};
258     $worker->DONE(@_);
259 }
260
261 sub msg_FAILED {
262     my $self = shift;
263     my ($msgtype, %params) = @_;
264
265     my $worker = $self->{'worker'}->{$params{'worker_name'}};
266     $worker->FAILED(@_);
267 }
268
269 sub msg_CLOSE_VOLUME {
270     my $self = shift;
271     my ($msgtype, %params) = @_;
272
273     my $worker = $self->{'worker'}->{$params{'worker_name'}};
274     $worker->CLOSE_VOLUME(@_);
275 }
276
277 sub msg_TAKE_SCRIBE_FROM {
278     my $self = shift;
279     my ($msgtype, %params) = @_;
280
281     my $worker = $self->{'worker'}->{$params{'worker_name'}};
282     my $worker1 = $self->{'worker'}->{$params{'from_worker_name'}};
283     $worker->TAKE_SCRIBE_FROM($worker1, @_);
284     delete $self->{'worker'}->{$params{'from_worker_name'}};
285 }
286
287 sub msg_QUIT {
288     my $self = shift;
289     my ($msgtype, %params) = @_;
290     my $read_cb;
291
292     # because the driver hangs up on us immediately after sending QUIT,
293     # and EOF also means QUIT, we tend to get this command repeatedly.
294     # So check to make sure this is only called once
295     return if $self->{'quitting'};
296     $self->{'quitting'} = 1;
297
298     my $finished_cb = make_cb(finished_cb => sub {
299         my $err = shift;
300         if ($err) {
301             Amanda::Debug::debug("Quit error: $err");
302         }
303         Amanda::MainLoop::quit();
304     });
305     $self->quit(finished_cb => $finished_cb);
306 };
307
308 1;