-# Copyright (c) 2009,2010 Zmanda, Inc. All Rights Reserved.
+# Copyright (c) 2009-2012 Zmanda, Inc. All Rights Reserved.
#
-# This library is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License version 2.1 as
-# published by the Free Software Foundation.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+#* License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
use Data::Dumper;
use File::Path;
use Amanda::Paths;
+use Amanda::Config qw( :init );
use Amanda::MainLoop qw( :GIOCondition make_cb define_steps step );
use Amanda::Config qw( :getconf );
use Amanda::Debug qw( debug warning );
# drives - see below
# drive_lru - recently used drives, least recent first
# bc2lb - hash mapping known barcodes to known labels
-# current_slot - the current slot
+# current_slot - hash of the current slot
# last_operation_time - time the last operation finished
# last_operation_delay - required delay for that operation
# last_status - last time a 'status' command finished
# as values. Each slot's hash has keys
# state - SLOT_FULL/SLOT_EMPTY/SLOT_UNKNOWN
# device_status - the status of the device
+# device_error - the error message of the device
# f_type - The f_type of the header
# label - volume label, if known
# barcode - volume barcode, if available
# label - volume label
# barcode - volume barcode
# orig_slot - slot from which this tape was loaded
-
+#
+## The 'current_slot' key is also a hash by config name, the values of
+# which are the current slot for the config.
+# #
# LOCKING
#
# This package uses Amanda::Changer's with_locked_state to lock a statefile and
# set below from properties
statefile => undef,
+ 'lock-timeout' => undef,
drive2device => {}, # { drive => device name }
driveorder => [], # order of tape-device properties
drive_choice => 'lru',
$self->{'statefile'} = "$localstatedir/amanda/$safe_filename";
}
$self->_debug("using statefile '$self->{statefile}'");
-
+ $self->{'lock-timeout'} = $config->{'lock-timeout'};
# figure out the drive number to device name mapping
if (exists $config->{'tapedev'}
and $config->{'tapedev'} ne ''
}
# eject-before-unload
- my $ebu = $self->get_boolean_property($self->{'config'},
+ my $ebu = $self->{'config'}->get_boolean_property(
"eject-before-unload", 0);
if (!defined $ebu) {
return Amanda::Changer->make_error("fatal", undef,
$self->{'eject_before_unload'} = $ebu;
# fast-search
- my $fast_search = $self->get_boolean_property($self->{'config'},
+ my $fast_search = $self->{'config'}->get_boolean_property(
"fast-search", 1);
if (!defined $fast_search) {
return Amanda::Changer->make_error("fatal", undef,
}
# status-interval, eject-delay, unload-delay
- for my $propname qw(status-interval eject-delay unload-delay) {
+ for my $propname (qw(status-interval eject-delay unload-delay)) {
next unless exists $config->{'properties'}->{$propname};
if (@{$config->{'properties'}->{$propname}->{'values'}} > 1) {
return Amanda::Changer->make_error("fatal", undef,
$self->{$key} = $time;
}
- my $ignore_barcodes = $self->get_boolean_property($self->{'config'},
+ my $ignore_barcodes = $self->{'config'}->get_boolean_property(
"ignore-barcodes", 0);
if (!defined $ignore_barcodes) {
return Amanda::Changer->make_error("fatal", undef,
if (exists $params{'relative_slot'}) {
if ($params{'relative_slot'} eq "next") {
if (exists $params{'slot'}) {
- $slot = $self->_get_next_slot($state, $params{'slot'});
- $self->_debug("loading next relative to $params{slot}: $slot");
+ $slot = $self->_get_next_slot($state, $params{'slot'}, $params{'except_slots'});
+ if (defined $slot) {
+ $self->_debug("loading next relative to $params{slot}: $slot");
+ } else {
+ $self->_debug("no next slot relative to $params{slot}");
+ }
} else {
- $slot = $self->_get_next_slot($state, $state->{'current_slot'});
- $self->_debug("loading next relative to current slot: $slot");
+ $slot = $self->_get_next_slot($state, $state->{'current_slot'}{get_config_name()}, $params{'except_slots'});
+ if (defined $slot) {
+ $self->_debug("loading next relative to current slot: $slot");
+ } else {
+ $self->_debug("no next relative to current slot");
+ }
}
- if ($slot == -1) {
+ if (defined $slot && $slot == -1) {
return $self->make_error("failed", $params{'res_cb'},
reason => "invalid",
message => "could not find next slot");
}
} elsif ($params{'relative_slot'} eq "current") {
- $slot = $state->{'current_slot'};
- if ($slot == -1) {
+ $slot = $state->{'current_slot'}{get_config_name()};
+ if (defined $slot && $slot == -1) {
# seek to the first slot
- $slot = $self->_get_next_slot($state, $state->{'current_slot'});
+ $slot = $self->_get_next_slot($state, $state->{'current_slot'}{get_config_name()}, $params{'except_slots'});
}
- if ($slot == -1) {
+ if (defined $slot && $slot == -1) {
return $self->make_error("failed", $params{'res_cb'},
reason => "invalid",
message => "no current slot");
message => "no 'slot' or 'label' specified to load()");
}
+ if (defined $slot and !exists $state->{'slots'}->{$slot}) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "invalid",
+ message => "invalid slot '$slot'");
+ }
+
+ if (!defined $slot) {
+ my $all_empty = 1;
+ if (exists $params{'except_slots'}) {
+ for my $xslot (keys %{ $params{'except_slots'} }) {
+ if ($state->{'slots'}->{$xslot}->{'state'} ne Amanda::Changer::SLOT_EMPTY) {
+ $all_empty = 0;
+ }
+ }
+ if ($all_empty) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "all slots are empty");
+ }
+ }
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "all slots have been loaded");
+ }
if (!$self->_is_slot_allowed($slot)) {
if (exists $params{'label'}) {
return $self->make_error("failed", $params{'res_cb'},
}
if (exists $params{'except_slots'} and exists $params{'except_slots'}->{$slot}) {
- return $self->make_error("failed", $params{'res_cb'},
- reason => "notfound",
- message => "all slots have been loaded");
+ # if all slots in except_slots are EMPTY
+ my $all_empty = 1;
+ for my $xslot (keys %{ $params{'except_slots'} }) {
+ if ($state->{'slots'}->{$xslot}->{'state'} ne Amanda::Changer::SLOT_EMPTY) {
+ $all_empty = 0;
+ }
+ }
+ if ($all_empty) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "all slots are empty");
+ } else {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "notfound",
+ message => "all slots have been loaded");
+ }
}
if ($state->{'slots'}->{$slot}->{'state'} eq Amanda::Changer::SLOT_EMPTY) {
return $self->make_error("failed", $params{'res_cb'},
- reason => "notfound",
+ reason => "empty",
+ slot => $slot,
message => "slot $slot is empty");
}
@check_order = (@{$self->{'driveorder'}});
} else {
# the constructor should detect this circumstance
- die "invalid drive_choice";
+ confess "invalid drive_choice";
}
my %checked;
step check_device => sub {
my $device_name = $self->{'drive2device'}->{$drive};
- die "drive $drive not found in drive2device" unless $device_name; # shouldn't happen
+ confess "drive $drive not found in drive2device" unless $device_name; # shouldn't happen
$self->_debug("polling '$device_name' to see if it's ready");
} elsif ($device->status & $DEVICE_STATUS_VOLUME_UNLABELED) {
$label = undef;
} else {
- return $self->make_error("fatal", $params{'res_cb'},
- message => "while waiting for '$device_name' to become ready: "
- . $device->error_or_status());
+ $label = undef;
}
# success!
# update metadata with this new information
$state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
$state->{'slots'}->{$slot}->{'device_status'} = $device->status;
- if (defined $device->{'volume_header'}) {
- $state->{'slots'}->{$slot}->{'f_type'} = $device->{'volume_header'}->{type};
+ if ($device->status == $DEVICE_STATUS_SUCCESS) {
+ $state->{'slots'}->{$slot}->{'device_error'} = undef;
+ } else {
+ $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+ }
+ if (defined $device->volume_header) {
+ $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
} else {
$state->{'slots'}->{$slot}->{'f_type'} = undef;
}
$state->{'slots'}->{$slot}->{'label'} = $label;
if ($state->{'slots'}->{$slot}->{'barcode'}) {
- $state->{'bc2lb'}->{$state->{'slots'}->{$slot}->{'barcode'}} = $label;
+ my $barcode = $state->{'slots'}->{$slot}->{'barcode'};
+ my $old_label = $state->{'bc2lb'}->{$barcode};
+ if ($label ne $old_label) {
+ $self->_debug("make_res: slot $slot");
+ $self->_debug("update label '$label' for barcode '$barcode', old label was '$old_label'");
+ }
+ $state->{'bc2lb'}->{$barcode} = $label;
}
return $self->make_error("failed", $params{'res_cb'},
# update metadata with this new information
$state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
$state->{'slots'}->{$slot}->{'device_status'} = $device->status;
- if (defined $device->{'volume_header'}) {
- $state->{'slots'}->{$slot}->{'f_type'} = $device->{'volume_header'}->{type};
+ if ($device->status == $DEVICE_STATUS_SUCCESS) {
+ $state->{'slots'}->{$slot}->{'device_error'} = undef;
+ } else {
+ $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+ }
+ if (defined $device->volume_header) {
+ $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
} else {
$state->{'slots'}->{$slot}->{'f_type'} = undef;
}
message => "Found unlabeled tape while looking for '$params{label}'");
}
+ if (defined $self->{'tapelist'}) {
+ my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
+ if (defined $tle and defined $tle->{'barcode'} and
+ defined $state->{'slots'}->{$slot}->{'barcode'} and
+ $state->{'slots'}->{$slot}->{'barcode'} ne $tle->{'barcode'}) {
+ return $self->make_error("failed", $params{'res_cb'},
+ reason => "device",
+ message => "Slot $slot, label '$label', mismatch barcode between changer '$state->{'slots'}->{$slot}->{'barcode'}' and tapelist file '$tle->{'barcode'}'");
+ }
+ }
my $res = Amanda::Changer::robot::Reservation->new($self, $slot, $drive,
$device, $state->{'slots'}->{$slot}->{'barcode'});
$state->{'drives'}->{$drive}->{'label'} = $label;
$state->{'drives'}->{$drive}->{'state'} = Amanda::Changer::SLOT_FULL;
$state->{'drives'}->{$drive}->{'barcode'} = $state->{'slots'}->{$slot}->{'barcode'};
- #$state->{'slots'}->{$slot}->{'device_status'} = 9;
- if ($label and $state->{'slots'}->{$slot}->{'barcode'}) {
- $state->{'bc2lb'}->{$state->{'slots'}->{$slot}->{'barcode'}} = $label;
+ $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
+ if ($device->status == $DEVICE_STATUS_SUCCESS) {
+ $state->{'slots'}->{$slot}->{'device_error'} = undef;
+ } else {
+ $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+ }
+ if (defined $device->volume_header) {
+ $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
+ } else {
+ $state->{'slots'}->{$slot}->{'f_type'} = undef;
+ }
+ my $barcode = $state->{'slots'}->{$slot}->{'barcode'};
+ if ($label and $barcode) {
+ my $old_label = $state->{'bc2lb'}->{$barcode};
+ if (defined $old_label and $old_label ne $label) {
+ $self->_debug("load drive $drive slot $slot");
+ $self->_debug("update label '$label' for barcode '$barcode', old label was '$old_label'");
+ }
+ $state->{'bc2lb'}->{$barcode} = $label;
}
if ($params{'set_current'}) {
- $self->_debug("setting current slot to $slot");
- $state->{'current_slot'} = $slot;
+ $self->_debug("setting current slot to $slot");
+ $state->{'current_slot'}{get_config_name()} = $slot;
}
return $params{'res_cb'}->(undef, $res);
my $device = Amanda::Device->new($device_name);
if ($device->status != $DEVICE_STATUS_SUCCESS) {
return Amanda::Changer->make_error("fatal", undef,
- reason => "unknown",
+ reason => "device",
message => "opening '$device_name': " . $device->error_or_status());
}
if (my $err = $self->{'config'}->configure_device($device)) {
return Amanda::Changer->make_error("fatal", undef,
- reason => "unknown",
+ reason => "device",
message => $err);
}
$state->{'drives'}->{$drive}->{'label'} = $label;
if (defined $slot) {
- delete $state->{'slots'}->{$slot}->{'unkknown_state'};
$state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
$state->{'slots'}->{$slot}->{'device_status'} = "".$dev->status;
+ if ($dev->status != $DEVICE_STATUS_SUCCESS) {
+ $state->{'slots'}->{$slot}->{'device_error'} = $dev->error;
+ } else {
+ $state->{'slots'}->{$slot}->{'device_error'} = undef;
+ }
my $volume_header = $dev->volume_header;
if (defined $volume_header) {
$state->{'slots'}->{$slot}->{'f_type'} = "".$volume_header->{type};
$state->{'slots'}->{$slot}->{'label'} = $label;
}
if (defined $barcode) {
+ if (defined $state->{'bc2lb'}->{$barcode} and
+ $state->{'bc2lb'}->{$barcode} ne $label) {
+ my $old_label = $state->{'bc2lb'}->{$barcode};
+ $self->_debug("update barcode '$barcode' to label '$label', old label was '$old_label'");
+ }
$state->{'bc2lb'}->{$barcode} = $label;
}
my %params = @_;
my $state = $params{'state'};
- $state->{'current_slot'} = $self->_get_next_slot($state, -1);
+ $state->{'current_slot'}{get_config_name()} = $self->_get_next_slot($state, -1);
$params{'finished_cb'}->();
}
while (my ($sl, $inf) = each %{$state->{'slots'}}) {
if ($inf->{'label'} and $inf->{'label'} eq $label) {
delete $inf->{'device_status'};
+ delete $inf->{'device_error'};
delete $inf->{'f_type'};
delete $inf->{'label'};
}
if (!defined $state->{'slots'}->{$slot}->{'barcode'}) {
$state->{'slots'}->{$slot}->{'label'} = undef;
$state->{'slots'}->{$slot}->{'device_status'} = undef;
+ $state->{'slots'}->{$slot}->{'device_error'} = undef;
$state->{'slots'}->{$slot}->{'f_type'} = undef;
if (defined $state->{'slots'}->{$slot}->{'loaded_in'}) {
my $drive = $state->{'slots'}->{$slot}->{'loaded_in'};
$i->{'slot'} = $slot_name;
$i->{'state'} = $slot->{'state'};
$i->{'device_status'} = $slot->{'device_status'};
+ $i->{'device_error'} = $slot->{'device_error'};
$i->{'f_type'} = $slot->{'f_type'};
$i->{'label'} = $slot->{'label'};
$i->{'barcode'} = $slot->{'barcode'}
if $slot->{'ie'};
$i->{'current'} = 1
- if $slot_name eq $state->{'current_slot'};
+ if $slot_name eq $state->{'current_slot'}{get_config_name()};
push @inv, $i;
}
# the changer status has been updated)
sub _get_next_slot {
my $self = shift;
- my ($state, $slot) = @_;
+ my ($state, $slot, $except_slots) = @_;
my @nonempty = sort { $a <=> $b } grep {
$state->{'slots'}->{$_}->{'state'} == Amanda::Changer::SLOT_FULL
and $self->_is_slot_allowed($_)
+ and (!$except_slots || !$except_slots->{$_})
} keys(%{$state->{'slots'}});
my @higher = grep { $_ > $slot } @nonempty;
return 0;
}
-# add a prefix and call Amanda::Debug::debug
sub _debug {
my $self = shift;
my ($msg) = @_;
$state->{'drives'} = {};
$state->{'drive_lru'} = [];
$state->{'bc2lb'} = {};
- $state->{'current_slot'} = -1;
+ $state->{'current_slot'}{get_config_name()} = -1;
+ }
+
+ if (defined $state->{'current_slot'} &&
+ ref(\$state->{'current_slot'}) eq "SCALAR") {
+ my $current_slot = $state->{'current_slot'};
+ $state->{'current_slot'} = {get_config_name() => $current_slot};
}
# this is for testing ONLY!
$new_slots->{$slot} = {
state => Amanda::Changer::SLOT_EMPTY,
device_status => undef,
+ device_error => undef,
f_type => undef,
label => undef,
barcode => undef,
$new_slots->{$slot} = {
state => Amanda::Changer::SLOT_FULL,
device_status => $state->{'slots'}->{$slot}->{device_status},
+ device_error => $state->{'slots'}->{$slot}->{device_error},
f_type => $state->{'slots'}->{$slot}->{f_type},
label => $label,
barcode => $info->{'barcode'},
$new_slots->{$slot} = {
state => Amanda::Changer::SLOT_FULL,
device_status => undef,
+ device_error => undef,
f_type => undef,
label => undef,
barcode => undef,
$state->{'slots'}->{$info->{'orig_slot'}} = {
state => $info->{'state'},
device_status => $old_state->{'device_status'},
+ device_error => $old_state->{'device_error'},
f_type => $old_state->{'f_type'},
label => $info->{'label'},
barcode => $info->{'barcode'},
}
}
- if ($state->{'current_slot'} == -1) {
- $state->{'current_slot'} = $self->_get_next_slot($state, -1);
+ if (!defined $state->{'current_slot'}{get_config_name()} ||
+ $state->{'current_slot'}{get_config_name()} == -1) {
+ $state->{'current_slot'}{get_config_name()} = $self->_get_next_slot($state, -1);
}
$steps->{'done'}->();
synchronized($self->{'lock'}, $status_cb, sub {
my ($status_cb) = @_;
+ my ($counter) = 120;
+
+ my $sys_cb;
+ my $run_mtx = make_cb(run_mtx => sub {
+ my @nobarcode = ('nobarcode') if $self->{'ignore_barcodes'};
+ $self->_run_system_command($sys_cb,
+ $self->{'mtx'}, "-f", $self->{'device_name'}, @nobarcode,
+ 'status');
+ });
- my $sys_cb = make_cb(sys_cb => sub {
+ $sys_cb = make_cb(sys_cb => sub {
my ($exitstatus, $output) = @_;
if ($exitstatus != 0) {
my $err = $output;
# if it's a regular SCSI error, just show the sense key
my ($sensekey) = ($err =~ /mtx: Request Sense: Sense Key=(.*)\n/);
$err = "SCSI error; Sense Key=$sensekey" if $sensekey;
+ $counter--;
+ if ($sensekey eq "Not Ready" and $counter > 0) {
+ debug("$output");
+ return Amanda::MainLoop::call_after(1000, $run_mtx);
+ }
return $status_cb->("error from mtx: " . $err, {});
} else {
my %status;
for my $line (split '\n', $output) {
+ debug("mtx: $line");
my ($slot, $ie, $slinfo);
# drives (data transfer elements)
}
});
- my @nobarcode = ('nobarcode') if $self->{'ignore_barcodes'};
- $self->_run_system_command($sys_cb,
- $self->{'mtx'}, "-f", $self->{'device_name'}, @nobarcode, 'status');
+ $run_mtx->();
});
}
my ($readfd, $writefd) = POSIX::pipe();
if (!defined($writefd)) {
- die("Error creating pipe: $!");
+ confess("Error creating pipe: $!");
}
my $pid = fork();
if (!defined($pid) or $pid < 0) {
- die("Can't fork to run changer script: $!");
+ confess("Can't fork to run changer script: $!");
}
if (!$pid) {