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