2 # Copyright (c) 2009, 2010 Zmanda Inc. All Rights Reserved.
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.
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
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
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
22 Amanda::Taper::Controller
26 This package is a component of the Amanda taper, and is not intended for use by
27 other scripts or applications.
29 The controller interfaces with the driver (via L<Amanda::Taper::Protocol>) and
30 controls one or more workers (L<Amanda::Taper::Worker>).
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.
38 use lib '@amperldir@';
42 package Amanda::Taper::Controller;
44 use POSIX qw( :errno_h );
46 use Amanda::Config qw( :getconf config_dir_relative );
49 use Amanda::MainLoop qw( :GIOCondition );
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 );
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
80 # waiting for START-TAPER command
82 # warming up devices; TAPER-OK not sent yet
84 # not currently dumping anything
86 # setting up a transfer for a new dump
88 # getting the header before beginning a new dump
90 # in the middle of writing a file (self->{'handle'} set)
92 # a fatal error has occurred, so this object won't do anything
97 my $message_cb = make_cb(message_cb => sub {
98 my ($msgtype, %params) = @_;
100 if (defined $msgtype) {
101 $msg = "unhandled command '$msgtype'";
103 $msg = $params{'error'};
105 log_add($L_ERROR, $msg);
106 print STDERR "$msg\n";
107 $self->{'proto'}->send(Amanda::Taper::Protocol::BAD_COMMAND,
110 $self->{'proto'} = Amanda::Taper::Protocol->new(
113 message_cb => $message_cb,
114 message_obj => $self,
115 debug => $Amanda::Config::debug_taper?'driver/taper':'',
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");
125 # log the error (note that the message is intentionally not quoted)
126 log_add($L_ERROR, "no-tape error [$changer]");
128 # wait for it to be transmitted, then exit
129 $self->{'proto'}->stop(finished_cb => sub {
130 Amanda::MainLoop::quit();
133 # don't finish start()ing
137 $self->{'taperscan'} = Amanda::Taper::Scan->new(changer => $changer);
146 my $steps = define_steps
147 cb_ref => \$params{'finished_cb'};
150 @worker = values %{$self->{'worker'}};
151 delete $self->{'worker'};
152 $steps->{'quit_scribe'}->();
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 {
160 push @errors, $err if ($err);
162 $steps->{'quit_scribe'}->();
165 $steps->{'stop_proto'}->();
169 step stop_proto => sub {
170 $self->{'proto'}->stop(finished_cb => sub {
172 push @errors, $err if ($err);
174 $steps->{'done'}->();
180 $params{'finished_cb'}->(join("; ", @errors));
182 $params{'finished_cb'}->();
190 sub msg_START_TAPER {
192 my ($msgtype, %params) = @_;
194 my $worker = new Amanda::Taper::Worker($params{'worker_name'}, $self,
195 $params{'timestamp'});
197 $self->{'worker'}->{$params{'worker_name'}} = $worker;
199 $self->{'timestamp'} = $params{'timestamp'};
202 # defer both PORT_ and FILE_WRITE to a common method
205 my ($msgtype, %params) = @_;
207 my $worker = $self->{'worker'}->{$params{'worker_name'}};
208 $worker->FILE_WRITE(@_);
213 my ($msgtype, %params) = @_;
215 my $worker = $self->{'worker'}->{$params{'worker_name'}};
216 $worker->PORT_WRITE(@_);
221 my ($msgtype, %params) = @_;
223 my $worker = $self->{'worker'}->{$params{'worker_name'}};
224 $worker->START_SCAN(@_);
229 my ($msgtype, %params) = @_;
231 my $worker = $self->{'worker'}->{$params{'worker_name'}};
232 $worker->NEW_TAPE(@_);
235 sub msg_NO_NEW_TAPE {
237 my ($msgtype, %params) = @_;
239 my $worker = $self->{'worker'}->{$params{'worker_name'}};
240 $worker->NO_NEW_TAPE(@_);
245 my ($msgtype, %params) = @_;
247 my $worker = $self->{'worker'}->{$params{'worker_name'}};
253 my ($msgtype, %params) = @_;
255 my $worker = $self->{'worker'}->{$params{'worker_name'}};
259 sub msg_TAKE_SCRIBE_FROM {
261 my ($msgtype, %params) = @_;
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'}};
271 my ($msgtype, %params) = @_;
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;
280 my $finished_cb = make_cb(finished_cb => sub {
283 Amanda::Debug::debug("Quit error: $err");
285 Amanda::MainLoop::quit();
287 $self->quit(finished_cb => $finished_cb);