smeserver-base/root/sbin/e-smith/console-menu-items/manageRAID.pl

304 lines
8.8 KiB
Perl

package esmith::console::manageDiskRedundancy;
use strict;
use warnings;
use esmith::console;
use Locale::gettext;
use Taint::Util;
use Data::Dumper;
use constant DEBUG_MANAGE_RAID => 0;
sub new
{
my $class = shift;
my $self = {
name => gettext("Manage disk redundancy"),
order => 45,
};
bless $self, $class;
return $self;
}
sub name
{
return $_[0]->{name};
}
sub order
{
return $_[0]->{order};
}
sub doit
{
my ($self, $console, $db) = @_;
my ($rc, $choice);
use POSIX qw(strftime);
SCAN:
my $today = strftime "%A %B %e, %Y %H:%M:%S", localtime;
my $title = gettext("Disk redundancy status as of") . " " . $today,
my $text = gettext("Current RAID status:") . "\n\n" .
join("", get_raid_status()) . "\n\n";
my %devices = get_raid_details();
warn $text if DEBUG_MANAGE_RAID;
warn "devices: " . Dumper(\%devices) . "\n" if DEBUG_MANAGE_RAID;
unless (scalar %devices)
{
$text = gettext("There are no RAID devices configured");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Determine the status of each array
my @unclean = ();
my @recovering = ();
my @failed = ();
my %used_disks = ();
for my $dev (keys %devices)
{
$used_disks{$_}++ for (@{$devices{$dev}{UsedDisks}});
if ($devices{$dev}{FailedDevices} > 0) {
push @failed, "$dev => " . $devices{$dev}{FailedDevices};
}
if ($devices{$dev}{State} =~ /recovering|resync/) {
push @recovering, "$dev => " . $devices{$dev}{State};
next;
}
next if ($devices{$dev}{State} =~ /^(clean|active)\s*$/);
push @unclean, "$dev => " . $devices{$dev}{State};
}
warn "unclean: @unclean\n" if DEBUG_MANAGE_RAID;
warn "recovering: @recovering\n" if DEBUG_MANAGE_RAID;
warn "failed: @failed\n" if DEBUG_MANAGE_RAID;
warn "used_disks: " . Dumper(\%used_disks) . "\n" if DEBUG_MANAGE_RAID;
# Check for any spare disks we could add
my %free_disks = map {$_ => 1} get_disks();
delete $free_disks{$_} for keys %used_disks;
warn "free_disks: " . Dumper(\%free_disks) . "\n" if DEBUG_MANAGE_RAID;
# Report status and return if recovering
if (scalar @recovering)
{
$text .= gettext("A RAID resynchronization is in progress.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Report status and return if arrays are inconsistent
if ((scalar @unclean && scalar @unclean != scalar keys %devices) || (scalar @failed && scalar @failed != scalar keys %devices))
{
$text .= gettext("Only some of the RAID devices are unclean or contain failed disks.") .
"\n\n" .
gettext("Manual intervention may be required.") . "\n\n";
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Report status if arrays are clean and continue if a spare disk is available or there's only one disk in the system
unless (scalar @unclean || scalar @failed)
{
$text .= gettext("All RAID devices are in a clean state.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return unless scalar keys %free_disks > 0 || scalar keys %used_disks == 1;
}
# Report status if all arrays are dirty and continue
if ((scalar @unclean && scalar @unclean == scalar keys %devices) || (scalar @failed && scalar @failed == scalar keys %devices))
{
$text .= gettext("All RAID devices are in an unclean state or contain failed disks.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
}
# Summarise disk assignments
my $disk_status = gettext("Current disk status:") . "\n\n";
$disk_status .= gettext("Installed disks") . ": " .
join(" ", get_disks()) . "\n";
$disk_status .= gettext("Used disks") . ": " .
join(" ", keys %used_disks) . "\n";
$disk_status .= gettext("Free disks") . ": " .
join(" ", keys %free_disks) . "\n";
# Spare disk scenarios
# Scenario 1 - single disk or degraded array with no spare - warn
if ((scalar @unclean || scalar @failed || scalar keys %used_disks == 1) && scalar keys %free_disks == 0)
{
$text = $disk_status .
"\n\n" .
gettext("To ensure continued redundancy, please shut down, install another drive of the same capacity and then return to this screen.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Scenario 2 - no spares and not degraded so something has gone wrong
if (scalar keys %free_disks == 0)
{
$text = $disk_status .
"\n\n" .
gettext("Your RAID devices are in an inconsistent state, and no spare drives were detected. You may need to manually remove a failed drive from your arrays using mdadm.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Scenario 3 - multiple spares
if (scalar keys %free_disks > 1)
{
$text = $disk_status .
"\n\n" .
gettext("Multiple spare drives have been detected. This utility can only add one drive at a time. Please either shut down and remove all but one of your spare drives, or configure your array manually.");
($rc, $choice) = $console->message_page(title => $title, text => $text);
return;
}
# Scenario 4 - single spare ready to add
$text = $disk_status .
"\n\n" .
gettext("There is an unused disk drive in your system. Do you want to add it to the existing RAID array(s)?") .
"\n\n" .
gettext("WARNING: ALL DATA ON THE NEW DISK WILL BE DESTROYED!") .
"\n";
($rc, $choice) = $console->yesno_page(title => $title, text => $text, defaultno => 1);
return unless ($rc == 0);
my @cmd = ("/sbin/e-smith/add_drive_to_raid", "-f", join("", keys %free_disks));
my $cmd_out = qx( @cmd 2>&1 );
untaint $cmd_out;
if ($? == 0) {
$text = "\nSuccessfully added /dev/" . join("", keys %free_disks) . " to RAID!";
} else {
$text = gettext("The command failed:") . " @cmd" .
"\n\n" . $cmd_out . "\n\n";
}
($rc, $choice) = $console->message_page(title => $title, text => $text);
goto SCAN;
}
sub get_raid_status
{
die gettext("Couldn't open") . " /proc/mdstat:$!\n"
unless (open(MDSTAT, "/proc/mdstat"));
my @mdstat;
while (<MDSTAT>)
{
push @mdstat, "$1\n" if (/(.*\w.*)/);
}
close MDSTAT;
return @mdstat;
}
sub get_raid_details
{
my @devices = ();
die gettext("Couldn't call") . " mdadm: $!\n"
unless open(MDADM, "/sbin/mdadm --detail --scan|");
while (<MDADM>)
{
push @devices, $1 if ( m:ARRAY (/dev/md/\w+): )
}
close MDADM;
my %devices;
for my $dev (@devices)
{
die gettext("Couldn't call") . " mdadm --detail $dev: $!\n"
unless open(MDADM, "/sbin/mdadm --detail $dev|");
while ( <MDADM> )
{
if ( /\s*(.*)\s+:\s+(\d+)\s+\(.*\)\s*/ )
{
my ($key, $value) = ($1, $2);
$key =~ s/\s//g;
# Allow for different mdadm output formats for DeviceSize
$key =~ s/UsedDevSize/DeviceSize/;
$devices{$dev}{$key} = $value;
}
elsif ( /\s*(.*)\s+:\s+(.*)\s*/ )
{
my ($key, $value) = ($1, $2);
$key =~ s/\s//g;
$devices{$dev}{$key} = $value;
}
if ( m:\s+(\d+)\s+(\d+)\s+(\d+).*/dev/([\w\/]+): )
{
$devices{$dev}{$1} = $_;
my $used_disk = $4;
if (/(rd|ida|cciss|i2o)\//) {
$used_disk =~ s/p\d+$//;
} else {
$used_disk =~ s/\d+//;
}
push (@{$devices{$dev}{UsedDisks}}, $used_disk);
}
}
close MDADM;
}
return %devices;
}
sub get_partitions
{
die gettext("Couldn't read") . " /proc/partitions: $!\n"
unless open (PARTITIONS, "/proc/partitions");
my %parts;
while (<PARTITIONS>)
{
if ( /\s+(\d+)\s+(\d+)\s+(\d+)\s+([\w\/]+)\s+/ )
{
my $name = $4;
$parts{$name}{major} = $1;
$parts{$name}{minor} = $2;
$parts{$name}{blocks} = $3;
}
}
close PARTITIONS;
return %parts;
}
sub get_disks
{
my %parts = get_partitions();
my @disks;
for (keys %parts)
{
push @disks, $_ unless (/[0-9]$/);
push @disks, $_ if (/(rd|ida|cciss|i2o)\// && ! /p\d+$/);
}
return @disks;
}
return new esmith::console::manageDiskRedundancy;