????
| Current Path : /usr/lib/raider/Raider/Info/ |
| Current File : //usr/lib/raider/Raider/Info/FusionMPTSAS2.pm |
use strict;
use warnings;
package Raider::Info::FusionMPTSAS2;
use base qw( Raider::Info );
use Raider::Jobs::FusionMPTSAS2;
use JSON::Tiny qw(decode_json encode_json);
=head1 NAME
Raider::Info::FusionMPTSAS2 - FusionMPTSAS2 specific instructions for get-info
=head1 DESCRIPTION
FusionMPTSAS2 specific methods to gather information required for populating the .info
file.
=head1 USAGE
use Raider::Info::FusionMPTSAS2;
my $infoFusionMPTSAS2 = Raider::Info::FusionMPTSAS2->new();
=head1 METHODS
=head2 get_info()
Initiates all needed steps to generate a finished FusionMPTSAS2 info file.
=head2 get_disk_block_device(\%)
Return given serials block device.
=head2 extract_LD_info(\%)
Return info on a LD.
=head2 get_base_cntrl_info(\%args)
Return base controller info.
=head2 host_supports_info()
Given no args, return whether or not this physical host supports get-info collection.
Returns:
BOOL (1 or 0)
=cut
my $icmd = '/usr/bin/sas2ircu';
sub get_info {
my $self = shift;
my $opts = shift;
my $jobsFusionMPTSAS2 = Raider::Jobs::FusionMPTSAS2->new();
$jobsFusionMPTSAS2->icmd_in_path({ icmd => $icmd });
my $controller_list_ref = $jobsFusionMPTSAS2->get_controller_list();
my @controller_list = @$controller_list_ref;
my $d_struct_disks = { };
my $d_struct_controllers = { };
unless ($self->host_supports_info()) {
$self->logger({cat => 'w', msg => "this host doesnt support get-info collection for FusionMPTSAS2 devices; disabling"});
return ($d_struct_controllers,$d_struct_disks,$opts->{cntrl_cnt},$opts->{disk_cnt});
}
my $d_struct_map_file_new;
for my $controller ( @controller_list ) {
my $cntrl_info = `$icmd $controller display`;
my $base_cntrl_info = $self->get_base_cntrl_info({ c => $controller, cntrl_info => $cntrl_info });
my $cntrl_model = $base_cntrl_info->{'cntrl_model'};
my $cntrl_serial = $base_cntrl_info->{'cntrl_serial'};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'identifier'} = "$cntrl_model\:::$cntrl_serial";
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'make'} = 'LSI';
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'model'} = $cntrl_model;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'firmware'} = $base_cntrl_info->{'cntrl_firmware'};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $base_cntrl_info->{'cntrl_numdrives'};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $base_cntrl_info->{'cntrl_numarrays'};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'serial'} = $cntrl_serial;
if ( ! defined($d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }) ) {
$d_struct_map_file_new = { "$cntrl_model\:::$cntrl_serial" => {} };
}
# Populate physical disks array.
my $pd_list = $self->get_pds({ cntrl_info => $cntrl_info });
# Populate logical disks array.
my $unit_list = $self->get_lds({ cntrl_info => $cntrl_info });
##########################
## Physical Disks ##
##########################
my $pd_info_store_ld = {};
for my $each_pd ( @{ $pd_list } ) {
# Get detailed info on pd
my $pd_info = $self->get_pd_info({
cntrl_info => $cntrl_info,
pd => $each_pd,
cntrl_model => $cntrl_model,
cntrl_serial => $cntrl_serial
});
# If we got back valid PD data, update our MAP file.
if ( defined($pd_info->{'model'}) && defined($pd_info->{'serial'}) && defined($pd_info->{'type'}) ) {
$d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }{ "$pd_info->{'channel'}\:::$pd_info->{'port'}" } = {
model => $pd_info->{'model'},
serial => $pd_info->{'serial'},
disk_type => $pd_info->{'type'}
};
}
$d_struct_disks->{ $opts->{disk_cnt} } = {
identifier => "$pd_info->{'model'}\:::$pd_info->{'serial'}",
type => $pd_info->{'type'},
size_mb => $pd_info->{'size_mb'},
size_unparsed => $pd_info->{'size_unparsed'},
port => $pd_info->{'port'},
channel => $pd_info->{'channel'},
model => $pd_info->{'model'},
firmware => $pd_info->{'firmware'},
serial => $pd_info->{'serial'},
state => $pd_info->{'state'},
block_device => $pd_info->{'block_device'}
};
# Attempt to get the PD's SMART attributes.
my $smart_attribs = 'unsupported';
$smart_attribs = $self->get_smart_attribs({
device => $pd_info->{'block_device'},
});
$smart_attribs = 'unsupported' if ( defined($smart_attribs->{error}) );
# Record PD's SMART attributes if supported.
if ( $smart_attribs ne 'unsupported' ) {
$d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $smart_attribs;
}
$pd_info_store_ld->{ $each_pd } = $d_struct_disks->{ $opts->{disk_cnt} };
# Increment our passed disks counter.
$opts->{disk_cnt}++;
}
##########################
## Logical Disks ##
##########################
# Make sure pd_list and unit_list are same size. If not, make unit_list
# equal pd_list. This is a "temporaryish" thing, full explanation look at
# comments in $self->get_lds().
# --ssullivan Oct 29th, 2013
if ( scalar @{ $pd_list } != scalar @{ $unit_list } ) {
my $pd_list_size = @{ $pd_list };
$pd_list_size--; # start at 0
$unit_list = [ 0..$pd_list_size ];
}
my $unit_pass = 0;
for my $each_unit ( @{ $unit_list } ) {
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass }->{physical_disks} = [ "$pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{model}\:::$pd_info_store_ld->{ @{ $pd_list}[$unit_pass] }->{serial}" ];
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass }->{size_mb} = $pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{size_mb};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $unit_pass }->{block_device} = $pd_info_store_ld->{ @{ $pd_list }[$unit_pass] }->{block_device};
$unit_pass++;
}
# Increment our passed controller counter.
$opts->{cntrl_cnt}++;
}
if ( defined($d_struct_map_file_new) ) {
$self->write_json({data => $d_struct_map_file_new, device => 'FusionMPTSAS2_device_map'});
}
return ($d_struct_controllers,$d_struct_disks,$opts->{cntrl_cnt},$opts->{disk_cnt});
}
sub get_pd_info {
my $self = shift;
my $opts = shift;
if ( ! defined($opts->{pd}) || ! defined($opts->{cntrl_info}) || ! defined($opts->{cntrl_model}) || ! defined($opts->{cntrl_serial}) ) {
$self->logger({ cat => 'c', msg => "get_pd_info() missing required args!" });
}
my ($enc, $slot) = split(/:/,$opts->{pd});
my $pd_store = {};
my $pd_store_tmp = {};
my $pd_section = 0;
my $my_pd_section = 0;
# Based on our given controller output, attempt to collect the dataz..
for my $line ( split /^/, $opts->{cntrl_info} ) {
# Each PD section starts with this
if ( $line =~ /Device\s+is\s+a\s+Hard\s+disk/i ) {
$pd_section = 1;
# Clear out tmp data, this is a new PD.
$pd_store_tmp = {};
}
# Don't even bother checking $line unless we're in PD section of output.
if ( $pd_section ) {
if ( $line =~ /^\s+Enclosure\s+#(\s+)?:\s+(\d+)/i ) {
$pd_store_tmp->{'enc'} = $2;
}
elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+(\d+)/i ) {
$pd_store_tmp->{'slot'} = $2;
}
# If we have both enc and slot for this PD...
if ( defined($pd_store_tmp->{'enc'}) && defined($pd_store_tmp->{'slot'}) ) {
# if enc and slot match our given query PD..
if ( $pd_store_tmp->{'enc'} == $enc && $pd_store_tmp->{'slot'} == $slot ) {
# this is our given PDs section
$my_pd_section = 1;
# Update our channel/port data.
$pd_store->{'port'} = $pd_store_tmp->{'slot'};
$pd_store->{'channel'} = $pd_store_tmp->{'enc'};
}
else {
# this is not our given PDs section
$my_pd_section = 0;
}
}
# If this is our given PDs section..
if ( $my_pd_section ) {
if ( $line =~ /^\s+Drive\s+Type(\s+)?:\s+(\S+)/i ) {
$pd_store->{'type'} = $2;
}
elsif ( $line =~ /^\s+Size\s+\S+\s+MB.+:\s(\d+)\/\d+/i ) {
$pd_store->{'size_mb'} = $1;
}
elsif ( $line =~ /^\s+Size\s+.+:(.+)/i ) {
$pd_store->{'size_unparsed'} = $1;
}
elsif ( $line =~ /^\s+Model\s+Number(\s+)?:\s+(.+)/i ) {
my $model_stripped = $2;
$model_stripped =~ tr/ //ds;
$pd_store->{'model'} = $model_stripped;
}
elsif ( $line =~ /^\s+Firmware\s+Revision(\s+)?:\s+(\S+)/i ) {
$pd_store->{'firmware'} = $2;
}
elsif ( $line =~ /^\s+Serial\s+No(\s+)?:\s+(\S+)/i ) {
$pd_store->{'serial'} = $2;
}
elsif ( $line =~ /^\s+State(\s+)?:\s+(\S+)/i ) {
$pd_store->{'state'} = $2;
}
# If we haven't yet gotten the block_device..
if ( ! defined($pd_store->{'block_device'}) ) {
# But we have our serial, retrieve our block_device
if ( defined($pd_store->{'serial'}) ) {
$pd_store->{'block_device'} = $self->get_disk_block_device({ serial => $pd_store->{'serial'} });
}
}
}
}
}
# If our given data wasn't nice, and we couldn't determine the disks model,
# serial, or type, let's check our MAP file. Yes, sometimes this happens.
# Usually you see this when the given PD is dead, the vendor tool will often
# no longer display its model or serial, or type. We attempt to get this
# data in this case from the MAP file, so the JSON data collected looks more
# sane. This is especially true for Mr. RADAR..
# --ssullivan Oct 29th, 2013
if ( ! defined($pd_store->{'model'}) || ! defined($pd_store->{'serial'}) || ! defined($pd_store->{'type'}) ) {
$self->logger({ cat => 'i', msg => "Unable to extract model-serial-type from PD [$pd_store->{'channel'}:$pd_store->{'port'}]" });
# MAP file fallback
if ( $self->map_file_exists({ device => 'FusionMPTSAS2_device_map' }) ) {
$self->logger({ cat => 'i', msg => "RAIDER MAP entry file exists." });
# Open file, read contents.
my $device_map_file = "$Raider::Base::base_conf{'data_path'}/FusionMPTSAS2_device_map.info";
open FILE, "<$device_map_file";
my $device_map_file_text = do { local $/; <FILE> };
close(FILE);
# Decode.
my $d_struct_map_file;
eval {
$d_struct_map_file = decode_json($device_map_file_text);
};
if ( $@ ) {
$self->logger({ cat => 'w', msg => "Invalid JSON: [$device_map_file] Cannot decode MAP file!" });
}
else {
# Check and see if this device is identified in our decoded MAP file.
if ( defined($d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }) ) {
$pd_store->{'model'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{model};
$pd_store->{'serial'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{serial};
$pd_store->{'type'} = $d_struct_map_file->{ "$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'}" }->{ "$pd_store->{'channel'}\:::$pd_store->{'port'}" }->{type};
$self->logger({ cat => 'i', msg => "FusionMPTSAS2: Got model+serial from MAP file." });
}
else {
$self->logger({
cat => 'w',
msg => "No device entry found in MAP file for [$opts->{'cntrl_model'}\:::$opts->{'cntrl_serial'} -> $pd_store->{'channel'}\:::$pd_store->{'port'}]. Disk will not identify properly!"
});
}
}
}
}
return $pd_store;
}
sub get_pds {
my $self = shift;
my $opts = shift;
my @pd_list = ();
my $pd_store = {};
my $pd_section = 0;
my $first_pass = 1;
for my $line ( split /^/, $opts->{cntrl_info} ) {
# Each PD section starts with this
if ( $line =~ /Device\s+is\s+a\s+Hard\s+disk/i ) {
# If this is not the first pass..
if ( ! $first_pass ) {
# consider this an error if we don't have enc and slot recorded.
if ( ! defined($pd_store->{'enc'}) || ! defined($pd_store->{'slot'}) ) {
$self->logger({ cat => 'c', msg => "Unable to find either enclosure [$pd_store->{'enc'}] or slot [$pd_store->{'slot'}]" });
}
else {
# We have enc and slot. Add to pd_list.
push(@pd_list, "$pd_store->{'enc'}:$pd_store->{'slot'}");
}
}
else {
$first_pass = 0;
}
# Since this is the start of a new PD section, clear out collected enc
# and slot info.
$pd_store = {};
$pd_section = 1;
}
if ( $pd_section ) {
if ( $line =~ /^\s+Enclosure\s+#(\s+)?:\s+(\d+)/i ) {
$pd_store->{'enc'} = $2;
$pd_store->{'last'}->{'enc'} = $2;
}
elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+(\d+)/i ) {
$pd_store->{'slot'} = $2;
$pd_store->{'last'}->{'slot'} = $2;
}
}
}
# Don't leave out the last discovered PD
push(@pd_list, "$pd_store->{'last'}->{'enc'}:$pd_store->{'last'}->{'slot'}");
return \@pd_list;
}
sub get_lds {
my $self = shift;
my $opts = shift;
# Right now, we just show every PD as a LD. This isn't really right, since
# some FusionMPTSAS2 cards can support RAID (healthchecks are supported in
# Jobs/FusionMPTSAS2). However, that setup we are not currently using anywhere
# within LiquidWeb, so I have no test hardware available. If we start
# offering such setups, we will probably want to update this method to list
# RAID LD's, and the disks they contain. However doing this with no test
# hardware available is far to error prone, so for now every PD will show as
# its own LD (like JBOD, which is typically what these HBA cards are used
# for anyways).
# --ssullivan Oct 29th, 2013
my @unit_list = ();
my $ld_num = 0;
for my $line ( split /^/, $opts->{'cntrl_info'} ) {
if ( $line =~ /Device is a Hard disk/i ) {
push(@unit_list, $ld_num);
$ld_num++;
}
}
return \@unit_list;
}
sub get_disk_block_device {
my $self = shift;
my $opts = shift;
my $block_devices = $self->get_block_devices();
for my $b_device ( @{ $block_devices } ) {
for my $line ( split /^/, `smartctl -i /dev/$b_device` ) {
if ( $line =~ /^Serial\s+Number(\s+)?:\s+(\S+)/i ) {
my $smartctl_serial = $2;
# Turns out, sas2ircu removes the - character from a serial. This
# causes problems when you are comparing serials as returned by
# sas2ircu and smartctl. Remedy this situation by removing the -
# character from the serial returned by smartctl. Clearly, one is left
# wondering if other characters are filtered out by sas2ircu.. add
# here if this turns out to be the case.
# --ssullivan Oct 29th, 2013
$smartctl_serial =~ tr/-//ds;
if ( $smartctl_serial eq $opts->{serial} ) {
return "/dev/$b_device";
}
}
}
}
$self->logger({ cat => 'w', msg => "Failed to find corresponding block device for discovered disk serial [$opts->{serial}]" });
return 'unknown';
}
sub get_base_cntrl_info {
my $self = shift;
my $opts = shift;
my $cntrl_info = {};
my $pd_counter = 0;
my $ld_counter = 0;
for my $line ( split /^/, $opts->{cntrl_info} ) {
# Controller model
if ( $line =~ /^\s+Controller\s+type(\s+)?:\s+(.+)/i ) {
$cntrl_info->{'cntrl_model'} = $2;
}
# Controller firmware version
elsif ( $line =~ /^\s+Firmware\s+version(\s+)?:\s+(.+)/i ) {
$cntrl_info->{'cntrl_firmware'} = $2;
}
# Controller numdrives
elsif ( $line =~ /^\s+Slot\s+#(\s+)?:\s+\d+/i ) {
$pd_counter++;
}
# Controller numlds
elsif ( $line =~ /^Device\s+is\s+a\s+Hard\s+disk/i ) {
#elsif ( $line =~ /^IR\s+volume\s+\d+/i ) {
$ld_counter++;
}
}
$cntrl_info->{'cntrl_numdrives'} = $pd_counter;
$cntrl_info->{'cntrl_numarrays'} = $ld_counter;
# sas2ircu doesn't provide a way to get the serial of the controller. So
# just use PCI address for now.
for my $line ( split /^/, `$icmd list` ) {
if ( $line =~ /^\s+$opts->{c}\s+\S+\s+\S+\s+\S+\s+(\S+)/i ) {
$cntrl_info->{'cntrl_serial'} = $1;
}
}
return $cntrl_info;
}
sub host_supports_info {
my ($self) = @_;
return 1;
}
1;