Imported Upstream version 3.2.0
[debian/amanda] / perl / Amanda / Taper / Controller.pm
1 #! @PERL@
2 # Copyright (c) 2009, 2010 Zmanda Inc.  All Rights Reserved.
3 #
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
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::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
63     my $self = bless {
64
65         # filled in at start
66         proto => undef,
67
68         worker => {},
69     }, $class;
70     return $self;
71 }
72
73 # The feedback object mediates between messages from the driver and the ongoing
74 # action with the taper.  This is made a little bit complicated because the
75 # driver conversation is fairly contextual, with some responses answering
76 # "questions" asked earlier.  This is modeled with the following taper
77 # "states":
78 #
79 # init:
80 #   waiting for START-TAPER command
81 # starting:
82 #   warming up devices; TAPER-OK not sent yet
83 # idle:
84 #   not currently dumping anything
85 # making_xfer:
86 #   setting up a transfer for a new dump
87 # getting_header:
88 #   getting the header before beginning a new dump
89 # writing:
90 #   in the middle of writing a file (self->{'handle'} set)
91 # error:
92 #   a fatal error has occurred, so this object won't do anything
93
94 sub start {
95     my $self = shift;
96
97     my $message_cb = make_cb(message_cb => sub {
98         my ($msgtype, %params) = @_;
99         my $msg;
100         if (defined $msgtype) {
101             $msg = "unhandled command '$msgtype'";
102         } else {
103             $msg = $params{'error'};
104         }
105         log_add($L_ERROR, $msg);
106         print STDERR "$msg\n";
107         $self->{'proto'}->send(Amanda::Taper::Protocol::BAD_COMMAND,
108             message => $msg);
109     });
110     $self->{'proto'} = Amanda::Taper::Protocol->new(
111         rx_fh => *STDIN,
112         tx_fh => *STDOUT,
113         message_cb => $message_cb,
114         message_obj => $self,
115         debug => $Amanda::Config::debug_taper?'driver/taper':'',
116     );
117
118     my $changer = Amanda::Changer->new();
119     if ($changer->isa("Amanda::Changer::Error")) {
120         # send a TAPE_ERROR right away
121         $self->{'proto'}->send(Amanda::Taper::Protocol::TAPE_ERROR,
122                 worker_name => "SETUP",
123                 message => "$changer");
124
125         # log the error (note that the message is intentionally not quoted)
126         log_add($L_ERROR, "no-tape error [$changer]");
127
128         # wait for it to be transmitted, then exit
129         $self->{'proto'}->stop(finished_cb => sub {
130             Amanda::MainLoop::quit();
131         });
132
133         # don't finish start()ing
134         return;
135     }
136
137     $self->{'taperscan'} = Amanda::Taper::Scan->new(changer => $changer);
138 }
139
140 sub quit {
141     my $self = shift;
142     my %params = @_;
143     my @errors = ();
144     my @worker = ();
145
146     my $steps = define_steps
147         cb_ref => \$params{'finished_cb'};
148
149     step init => sub {
150         @worker = values %{$self->{'worker'}};
151         delete $self->{'worker'};
152         $steps->{'quit_scribe'}->();
153     };
154
155     step quit_scribe => sub {
156         my $worker = shift @worker;
157         if (defined $worker and defined $worker->{'scribe'}) {
158             $worker->{'scribe'}->quit(finished_cb => sub {
159                 my ($err) = @_;
160                 push @errors, $err if ($err);
161
162                 $steps->{'quit_scribe'}->();
163             });
164         } else {
165             $steps->{'stop_proto'}->();
166         }
167     };
168
169     step stop_proto => sub {
170         $self->{'proto'}->stop(finished_cb => sub {
171             my ($err) = @_;
172             push @errors, $err if ($err);
173
174             $steps->{'done'}->();
175         });
176     };
177
178     step done => sub {
179         if (@errors) {
180             $params{'finished_cb'}->(join("; ", @errors));
181         } else {
182             $params{'finished_cb'}->();
183         }
184     };
185 }
186
187 ##
188 # Driver commands
189
190 sub msg_START_TAPER {
191     my $self = shift;
192     my ($msgtype, %params) = @_;
193
194     my $worker = new Amanda::Taper::Worker($params{'worker_name'}, $self,
195                                   $params{'timestamp'});
196
197     $self->{'worker'}->{$params{'worker_name'}} = $worker;
198
199     $self->{'timestamp'} = $params{'timestamp'};
200 }
201
202 # defer both PORT_ and FILE_WRITE to a common method
203 sub msg_FILE_WRITE {
204     my $self = shift;
205     my ($msgtype, %params) = @_;
206
207     my $worker = $self->{'worker'}->{$params{'worker_name'}};
208     $worker->FILE_WRITE(@_);
209 }
210
211 sub msg_PORT_WRITE {
212     my $self = shift;
213     my ($msgtype, %params) = @_;
214
215     my $worker = $self->{'worker'}->{$params{'worker_name'}};
216     $worker->PORT_WRITE(@_);
217 }
218
219 sub msg_START_SCAN {
220     my $self = shift;
221     my ($msgtype, %params) = @_;
222
223     my $worker = $self->{'worker'}->{$params{'worker_name'}};
224     $worker->START_SCAN(@_);
225 }
226
227 sub msg_NEW_TAPE {
228     my $self = shift;
229     my ($msgtype, %params) = @_;
230
231     my $worker = $self->{'worker'}->{$params{'worker_name'}};
232     $worker->NEW_TAPE(@_);
233 }
234
235 sub msg_NO_NEW_TAPE {
236     my $self = shift;
237     my ($msgtype, %params) = @_;
238
239     my $worker = $self->{'worker'}->{$params{'worker_name'}};
240     $worker->NO_NEW_TAPE(@_);
241 }
242
243 sub msg_DONE {
244     my $self = shift;
245     my ($msgtype, %params) = @_;
246
247     my $worker = $self->{'worker'}->{$params{'worker_name'}};
248     $worker->DONE(@_);
249 }
250
251 sub msg_FAILED {
252     my $self = shift;
253     my ($msgtype, %params) = @_;
254
255     my $worker = $self->{'worker'}->{$params{'worker_name'}};
256     $worker->FAILED(@_);
257 }
258
259 sub msg_TAKE_SCRIBE_FROM {
260     my $self = shift;
261     my ($msgtype, %params) = @_;
262
263     my $worker = $self->{'worker'}->{$params{'worker_name'}};
264     my $worker1 = $self->{'worker'}->{$params{'from_worker_name'}};
265     $worker->TAKE_SCRIBE_FROM($worker1, @_);
266     delete $self->{'worker'}->{$params{'from_worker_name'}};
267 }
268
269 sub msg_QUIT {
270     my $self = shift;
271     my ($msgtype, %params) = @_;
272     my $read_cb;
273
274     # because the driver hangs up on us immediately after sending QUIT,
275     # and EOF also means QUIT, we tend to get this command repeatedly.
276     # So check to make sure this is only called once
277     return if $self->{'quitting'};
278     $self->{'quitting'} = 1;
279
280     my $finished_cb = make_cb(finished_cb => sub {
281         my $err = shift;
282         if ($err) {
283             Amanda::Debug::debug("Quit error: $err");
284         }
285         Amanda::MainLoop::quit();
286     });
287     $self->quit(finished_cb => $finished_cb);
288 };
289
290 1;