????
| Current Path : /usr/lib/raider/Raider/Info/ |
| Current File : //usr/lib/raider/Raider/Info/MegaraidSAS.pm |
use strict;
use warnings;
package Raider::Info::MegaraidSAS;
use base qw( Raider::Info );
use JSON::Tiny qw(decode_json encode_json);
=head1 NAME
Raider::Info::MegaraidSAS - MegaraidSAS specific instructions for get-info
=head1 DESCRIPTION
MegaraidSAS specific methods to gather information required for populating the .info
files.
=head1 USAGE
use Raider::Info::MegaraidSAS;
my $megaraidsasInfo = Raider::Info::MegaraidSAS->new();
=head1 METHODS
=head2 get_info()
Initiates all needed steps to generate a finished MegaraidSAS info file.
=head2 get_disk_info(\%args)
Return the disk info.
=head2 extract_LD_info(\%)
Return information on the given LD.
=head2 get_controller_info(\%)
Return information on the given controller.
=head2 host_supports_info()
Given no args, return whether or not this physical host supports get-info collection.
Returns:
BOOL (1 or 0)
=cut
use Raider::Jobs::MegaraidSAS;
sub get_info {
my $self = shift;
my $opts = shift;
my $jobsMegaraidSAS = Raider::Jobs::MegaraidSAS->new();
my $icmd = $jobsMegaraidSAS->get_icmd();
$jobsMegaraidSAS->icmd_in_path({ icmd => "$icmd" });
my $controller_list_ref = $jobsMegaraidSAS->get_controller_list({ icmd => "$icmd" });
my @controller_list = @$controller_list_ref;
my $d_struct_disks = { };
my $d_struct_controllers = { };
my $storm_d_struct = { controllers => { }, };
unless ($self->host_supports_info()) {
$self->logger({cat => 'w', msg => "this host doesnt support get-info collection for MegaraidSAS 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_numarrays,$cntrl_numdrives,$cntrl_firmware,$cntrl_model,$cntrl_serial,$bbu_present,$bbu_state,$cntrl_memory,
$cntrl_mem_correctable_errors,$cntrl_mem_uncorrectable_errors) = $self->get_controller_info({
controller => $controller,
icmd => $icmd
});
my $cntrl_numarrays_end = $cntrl_numarrays;
$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} }->{'serial'} = $cntrl_serial;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'firmware'} = $cntrl_firmware;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $cntrl_numdrives;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $cntrl_numarrays;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_present'} = $bbu_present;
if ( $bbu_present ) {
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_state'} = $bbu_state;
}
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory'} = $cntrl_memory;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory_correctable_errors'} = $cntrl_mem_correctable_errors;
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory_uncorrectable_errors'} = $cntrl_mem_uncorrectable_errors;
$storm_d_struct->{'controllers'}->{ $controller } = { disks => { } };
$storm_d_struct->{'controllers'}->{ $controller }->{'make'} = 'LSI';
$storm_d_struct->{'controllers'}->{ $controller }->{'model'} = $cntrl_model;
$storm_d_struct->{'controllers'}->{ $controller }->{'firmware'} = $cntrl_firmware;
$storm_d_struct->{'controllers'}->{ $controller }->{'numdrives'} = $cntrl_numdrives;
$storm_d_struct->{'controllers'}->{ $controller }->{'numarrays'} = $cntrl_numarrays;
$cntrl_numarrays--;
######################
## PD Level ##
######################
my $dnum = 0;
my $dcount = 0;
my $d_total;
for my $line ( split /^/, `$icmd -AdpAllInfo -a$controller` ) {
if ( $line =~ /^\s+Disks(\s+)?:\s+(\d+)/i ) {
$d_total = $2;
}
}
my $device_id_to_model_serial_map = {};
my $unidentifiable_disks_cntrl_total_counter = 0;
while ( $dcount < $d_total ) {
my ($disk_type,$size_mb,$port,$channel,$model,$firmware,$serial,$state,$smart_attribs,$block_device,$device_id,$size_unparsed,
$vendor_tool_serial,$vendor_tool_model,$vendor_tool_firmware) = $self->get_disk_info({
controller => $controller,
controllers => \@controller_list,
phys_disk => $dnum,
icmd => $icmd,
cntrl_firmware => $cntrl_firmware
});
if ( $disk_type ) {
if ( $model =~ /^\[No|unknown/i || $serial =~ /^\[No|unknown/i ) {
$self->logger({ cat => 'i', msg => "PD [$dnum] not responding to SMART inquiries." });
my $map_file_success = 0;
my $vendor_tool_success = 0;
# MAP file fallback.
if ( $self->map_file_exists({ device => 'megaraidsas_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'}/megaraidsas_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->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }) ) {
$model = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{model};
$serial = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{serial};
if ( defined($d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{disk_type}) ) {
$disk_type = $d_struct_map_file->{ "$cntrl_model\:::$cntrl_serial" }->{ "$channel\:::$port" }->{disk_type};
}
$map_file_success = 1;
$self->logger({ cat => 'i', msg => "MegaraidSAS: Got model+serial from MAP file." });
}
else {
$self->logger({ cat => 'w', msg => "No device entry found in MAP file for [$cntrl_model\:::$cntrl_serial -> $channel\:::$port]." });
}
}
}
# Vendor tool fallback.
if ( ! $map_file_success && $vendor_tool_serial && $vendor_tool_model) {
$model = $vendor_tool_model;
$serial = $vendor_tool_serial;
$vendor_tool_success = 1;
$self->logger({ cat => 'i', msg => "Discovered model+serial from vendor tool." });
}
# No MAP file, and cannot get info from Vendor tool.
if ( ! $map_file_success && ! $vendor_tool_success ) {
$self->logger({
cat => 'w',
msg => "PD [$dnum] not responding to SMART inquiries. No RAIDER MAP entry, and cannot get model+serial from vendor tool. Disk will not identify properly!"
});
}
}
if ( $firmware =~ /^\[No|unknown/i ) {
$firmware = $vendor_tool_firmware;
}
# Only add to MAP file and disks key if model-serial is valid.
if ( $model !~ /^\[No|unknown/i && $serial !~ /^\[No|unknown/i ) {
$d_struct_map_file_new->{ "$cntrl_model\:::$cntrl_serial" }{ "0\:::$port" } = {
model => "$model",
serial => "$serial",
disk_type => "$disk_type"
};
$d_struct_disks->{ $opts->{disk_cnt} } = {
identifier => "$model\:::$serial",
type => $disk_type,
size_mb => $size_mb,
size_unparsed => $size_unparsed,
port => $port,
channel => $channel,
model => $model,
firmware => $firmware,
serial => $serial,
state => $state,
block_device => $block_device,
device_id => $device_id
};
if ( $smart_attribs ne 'unsupported' ) {
$d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $smart_attribs;
}
# Increment our passed disks counter.
$opts->{disk_cnt}++;
$device_id_to_model_serial_map->{ $device_id } = "$model\:::$serial";
}
else {
# Increment unidentifiable_disks counter.
$unidentifiable_disks_cntrl_total_counter++;
}
$storm_d_struct->{'controllers'}->{ $controller }->{'disks'}->{ $dcount } = { type => $disk_type };
$dcount++;
}
$dnum++;
};
# Populate unidentifiable_disks.
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'unidentifiable_disks'} = $unidentifiable_disks_cntrl_total_counter;
######################
## Unit/array level ##
######################
my @unit_list = map { 0 + $_ } 0..$cntrl_numarrays;
my $ldnum_serial_map = {};
for my $each_unit ( @unit_list ) {
my $unidentifiable_disks_unit = 0;
# Parse -CfgDsply -a$controller and determine what disks belong to $each_unit
my $in_group = 0;
my @phys_disks_in_unit = ();
my $model_serial_key;
for my $line ( split /^/, `$icmd -CfgDsply -a$controller` ) {
if ( $line =~ /\(Target\s+Id(\s+)?:\s+(\d+)\)/i ) {
if ( $2 == $each_unit ) {
# This is our disk group, enable.
$in_group = 1;
next;
}
else {
# This is not our disk group, disable.
$in_group = 0;
next;
}
}
# Disk is in our unit. Record this.
if ( $in_group ) {
if ( $line =~ /^Device\s+Id(\s+)?:\s+(\d+)/i ) {
my $device_id = $2;
if ( $device_id_to_model_serial_map->{ $device_id } ){
# Any PD inside this LD will suffice for checking which block device this LD corresponds with.
$model_serial_key = $device_id_to_model_serial_map->{ $device_id };
push(@phys_disks_in_unit, $device_id_to_model_serial_map->{ $device_id });
}
else {
$unidentifiable_disks_unit++;
$self->logger({ cat => 'w', msg => "Failed to map device id [$device_id] to model-serial!" });
}
}
}
}
my $health_check = `$icmd -LDInfo -L$each_unit -a$controller`;
my $state = $jobsMegaraidSAS->array_is_ok({ health_check => $health_check });
my ($raid_level,$numdrives,$size,$ld_serial,$raid_state,$ld_type,$ld_has_cachecade_enabled,$stripe_size,$size_raw,
$ld_cachecade_backed_by_id,$ld_block_device,$disk_cache,$current_cache_policy,$default_cache_policy) = $self->extract_LD_info({
icmd => $icmd,
controller => $controller,
controllers => \@controller_list,
unit => $each_unit,
units => \@unit_list
});
$ldnum_serial_map->{ $each_unit } = {
serial => $ld_serial,
ld_type => $ld_type
};
# LSI controllers do not export a block device for CacheCade LD's. As a result,
# we must identify CacheCade LD's with a different formula.
#
# --ssullivan May 08, 2013
if ( $ld_type =~ /CacheCade/i ) {
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit } = {
identifier => "CacheCade\:::$each_unit",
ld_num => $each_unit,
type => $ld_type,
size_mb => $size,
size_unparsed => $size_raw,
state => $raid_state,
raidlevel => $raid_level,
unidentifiable_disks => $unidentifiable_disks_unit,
physical_disks => \@phys_disks_in_unit,
cachecade_backing_ldnum => $ld_cachecade_backed_by_id,
cachecade_backing_ld => "$cntrl_model\:::$cntrl_serial\:::$ldnum_serial_map->{ $ld_cachecade_backed_by_id }->{serial}"
};
}
else {
if ( ! defined($d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit }) ) {
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $each_unit } = {
identifier => "$cntrl_model\:::$cntrl_serial\:::$ld_serial",
ld_num => $each_unit,
numdrives => $numdrives,
raidlevel => $raid_level,
state => $raid_state,
size_mb => $size,
size_unparsed => $size_raw,
block_device => $ld_block_device,
serial => $ld_serial,
type => $ld_type,
stripe_size => $stripe_size,
cachecade_backed => $ld_has_cachecade_enabled,
unidentifiable_disks => $unidentifiable_disks_unit,
physical_disks => \@phys_disks_in_unit,
disk_cache => $disk_cache,
current_cache_policy => $current_cache_policy,
default_cache_policy => $default_cache_policy
};
}
else {
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'identifier' } = "$cntrl_model\:::$cntrl_serial\:::$ld_serial";
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'ld_num' } = $each_unit;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'numdrives' } = $numdrives;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'raidlevel' } = $raid_level;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'state' } = $raid_state;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'size_mb' } = $size;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'size_unparsed' } = $size_raw;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'block_device' } = $ld_block_device;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'serial' } = $ld_serial;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'type' } = $ld_type;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'stripe_size' } = $stripe_size;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'cachecade_backed' } = $ld_has_cachecade_enabled;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'unidentifiable_disks' } = $unidentifiable_disks_unit;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'physical_disks' } = \@phys_disks_in_unit;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'disk_cache' } = $disk_cache;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'current_cache_policy' } = $current_cache_policy;
$d_struct_controllers->{ $opts->{cntrl_cnt} }{'logical_disks'}{ $each_unit }{ 'default_cache_policy' } = $default_cache_policy;
}
}
# Provisioning cannot currently handle CacheCade LD (or more than one LD
# period it would appear). Solve this for now by fudging the data
# structure it looks at to remove CacheCade LDs, and decrement the
# numarrays counter, per CacheCade LD.
#
# TODO: We probably want to fix this someday.
#
# --ssullivan Jan, 22nd, 2014
if ( $raid_level =~ /CacheCade/i ) {
$storm_d_struct->{'controllers'}->{ $controller }->{ 'numarrays' }--;
}
else {
$storm_d_struct->{'controllers'}->{ $controller }->{'arrays'}->{ $each_unit } = { numdrives => $numdrives, raidlevel => $raid_level, state => $state };
}
};
# Increment our passed controller count.
$opts->{cntrl_cnt}++;
};
$self->write_json({data => $storm_d_struct, device => 'megaraidSAS'});
if ( defined($d_struct_map_file_new) ) {
$self->write_json({data => $d_struct_map_file_new, device => 'megaraidsas_device_map'});
}
return ($d_struct_controllers,$d_struct_disks,$opts->{cntrl_cnt},$opts->{disk_cnt});
}
sub extract_LD_info {
my $self = shift;
my $opts = shift;
my ($d,$primary,$size_unit);
my $ld_state = 'unknown';
my $ld_type = 'unknown';
my $stripe_size = 'unknown';
my $ld_size_raw = 'unknown';
my $ld_cachecade_backed_by_ld_num = 'unknown';
my $ld_block_device = 'unknown';
my $disk_cache = 'unknown';
my $current_cache_policy = 'unknown';
my $default_cache_policy = 'unknown';
my $ld_has_cachecade_enabled = 0;
my $size_mb = 0;
my $size_value = 0;
for my $line ( split /^/, `$opts->{icmd} -LDInfo -L$opts->{unit} -a$opts->{controller}` ) {
if ( $line =~ /^Disk\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
$disk_cache = $3;
}
if ( $line =~ /^Current\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
$current_cache_policy = $3;
}
if ( $line =~ /^Default\s+Cache\s+Policy(\s+)?:(\s+)(.+)/i ) {
$default_cache_policy = $3;
}
if ( $line =~ /^Number\s+Of\s+Drives(\s+)?:(\s+)?(\d+)/i || $line =~ /^Number\s+Of\s+Drives\s+per\s+span(\s+)?:(\s+)?(\d+)/i ) {
$d = $3;
}
if ( $line =~ /^RAID\s+Level(\s+)?:\s+Primary-(\d+)/i ) {
$primary = $2;
}
# value and unit have a space between them.
if ( $line =~ /^Size(\s+)?:(\s+)?(\S+)\s+(\S+)/i ) {
$size_value = $3;
$size_unit = $4;
}
# value and unit have no space between them.
elsif ( $line =~ /^Size(\s+)?:(\s+)?(\S+)/i ) {
my $size_dump = $3;
if ( $size_dump && $size_dump =~ /([[:alpha:]]+)/i ) {
$size_unit = $1;
$size_dump =~ s/$size_unit//;
$size_value = $size_dump;
}
}
if ( $line =~ /^Size(\s+)?:(\s+)?(.+)/i ) {
$ld_size_raw = $3;
}
if ( $line =~ /^State(\s+)?:\s+(.+)/i ) {
$ld_state = $2;
}
if ( $line =~ /^Virtual\s+Drive\s+Type(\s+)?:\s+(\S+)/i ) {
$ld_type = $2;
}
if ( $line =~ /^Cache\s+Cade\s+Type(\s+)?:\s+Read/i ) {
$ld_has_cachecade_enabled = 1;
}
if ( $line =~ /^Strip(\S)?\s+Size(\s+)?:\s+(.+)/i ) {
$stripe_size = $3;
}
# Could be more than one associated LD.
if ( $line =~ /^Target\s+Id\s+of\s+the\s+Associated\s+LDs(\s+)?:(\s+)?(.+)/i ) {
$ld_cachecade_backed_by_ld_num = $3;
}
}
if ( $ld_type !~ /CacheCade/i ) {
$ld_type = 'RAID';
}
if ( $opts->{ld_type_only} ) {
return $ld_type;
}
my $num_spans_array;
my $ld_found = 0;
for my $line ( split /^/, `$opts->{icmd} -LdPdInfo -a$opts->{controller}` ) {
if ( $line =~ /Virtual\s+Drive:\s+(\d+)/i || $line =~ /Virtual\s+Disk:\s+(\d+)/i ) {
if ( $1 == $opts->{unit} ) {
$ld_found = 1;
}
else {
$ld_found = 0;
}
}
if ( $ld_found ) {
if ( $line =~ /^Number\s+of\s+Spans(\s+)?:\s+(\d+)/i ) {
$num_spans_array = $2;
}
}
}
# Drives in array
my $num_drives;
if ( $d ) {
$num_drives = $d * $num_spans_array;
}
# Raid level
if ( $num_spans_array > 1 ) {
$primary .= 0;
}
$size_mb = $self->convert_to_mb({ unit => $size_unit, value => $size_value});
# CacheCade LD's do not have block devices.
my $udev_info;
if ( $ld_type !~ /CacheCade/i ) {
$ld_block_device = $self->find_ld_block_device({
ld_num => $opts->{unit},
device => 'megaraidsas',
lds_array => $opts->{units},
icmd => $opts->{icmd},
controller => $opts->{controller},
controllers => $opts->{controllers}
});
if ( $ld_block_device !~ /^\/dev\//i ) {
$ld_block_device = "/dev/$ld_block_device";
}
my $block_device_name;
if ( $ld_block_device =~ /^\/dev\/(\S+)/i ) {
$block_device_name = $1;
}
# Get serial of passed LD block device.
$udev_info = $self->get_udev_block_device_info({ block_device_name => $block_device_name });
}
# MegaCli-4 doesnt support -ShowSummary. But -ShowSummary on megacli-8 and newer always shows CacheCade raid
# level as 'CacheCade', so fudge it if not found (where megacli-4 is installed).
my $our_ld = 0;
for my $line ( split /^/, `$opts->{icmd} -ShowSummary -a$opts->{controller}` ) {
if ( $line =~ /^\s+Virtual\s+drive(\s+)?:\s+Target\s+Id\s+(\d+)/i ) {
if ( $2 == $opts->{unit} ) {
# This is LD we should inspect
$our_ld = 1;
next;
}
else {
$our_ld = 0;
}
}
if ( $our_ld ) {
if ( $line =~ /^\s+State(\s+)?:\s+(.+)/i ) {
$ld_state = $2;
}
if ( $line =~ /^\s+RAID\s+Level(\s+)?:\s+(\S+)/i ) {
$primary = $2;
}
}
};
if ( ref($udev_info) eq 'HASH' && $ld_type =~ /CacheCade/i ) {
$primary = 'CacheCade';
$udev_info->{id_serial} = 'cachecade_faked';
}
my $id_serial = 'unknown';
$id_serial = $udev_info->{id_serial} if ( ref($udev_info) eq 'HASH' );
return ($primary, $num_drives, $size_mb, $id_serial, $ld_state, $ld_type, $ld_has_cachecade_enabled, $stripe_size, $ld_size_raw, $ld_cachecade_backed_by_ld_num, $ld_block_device, $disk_cache, $current_cache_policy, $default_cache_policy);
}
sub get_disk_info {
my $self = shift;
my $opts = shift;
my $enclosure_id;
my $size_unparsed = 'unknown';
if ( ! defined($opts->{cntrl_firmware}) ) {
$self->logger({
cat => 'c',
msg => "Info/MegaraidSAS: get_disk_info() called with no cntrl_firmware given!"
})
}
for my $line ( split /^/, `$opts->{icmd} -EncInfo -a$opts->{controller}` ) {
if ( $line =~ /^\s+Device\s+ID(\s+)?:\s+(\d+)/i ) {
$enclosure_id = $2;
}
}
# If controllers firmware is 2.130.393-2551 or newer, it doesn't like a
# space after PhysDrv. If a space is there, it will error. This causes a
# infinite loop where we continously look for the phys_disk by incrementing
# it over and over. That said, without the space is how its been since the
# dawn of RAIDER, so I felt safer only taking the space out if its the
# confirmed firmware version I know its broken on with the space or newer.
# If its found that the space needs to go away with a firmware older than
# the one specified in $base_firmware, modify that variable below to equal
# the lowest known firmware version to require the space to not be there.
#
# --ssullivan Feb 5th, 2014
my $base_firmware = '2.130.393-2551';
$base_firmware =~ s/\D//g;
$opts->{cntrl_firmware} =~ s/\D//g;
my $pdInfoOut;
if ( $enclosure_id ) {
if ( $opts->{cntrl_firmware} >= $base_firmware ) {
$pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv[$enclosure_id:$opts->{phys_disk}] -a$opts->{controller}`;
}
else {
$pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv [$enclosure_id:$opts->{phys_disk}] -a$opts->{controller}`;
}
}
else {
if ( $opts->{cntrl_firmware} >= $base_firmware ) {
$pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv[:$opts->{phys_disk}] -a$opts->{controller}`;
}
else {
$pdInfoOut = `$opts->{icmd} -PDInfo -PhysDrv [:$opts->{phys_disk}] -a$opts->{controller}`;
}
}
# We need to initialize dtype as null here. This way we don't increment dnum counter
# if there is no physical disk found.
my $dtype;
my $port = 'unknown';
my $channel = 'unknown';
# Serial, firmware, and model of a disk in a LD can only be found in
# the inquiry data section from megacli (or of course smartctl).
my $model = 'unknown';
my $firmware = 'unknown';
my $serial = 'unknown';
my $state = 'unknown';
my $ld_block_device = 'unknown';
my $device_id;
my $size_value = 0;
my $size_unit;
my $inq_data_line;
for my $line ( split /^/, $pdInfoOut ) {
if ( $line =~ /^Device\s+Id(\s+)?:\s+(\d+)/i ) {
$device_id = $2;
}
if ( $line =~ /^PD\s+Type(\s+)?:\s+(\S+)/i ) {
$dtype = $2;
}
if ( $line =~ /^Inquiry\s+Data(\s+)?:\s+(.+)/i ) {
$inq_data_line = $line;
if ( $line =~ /INTEL|KINGSTON|CRUCIAL/i ) {
$dtype = 'SSD';
}
}
if ( $line =~ /^Raw\s+Size(\s+)?:\s+(\S+)\s+(\S+)/i ) {
$size_value = $2;
$size_unit = $3;
}
# Older megacli (version 1.x at least) is hateful and does this:
# Raw Size: 572325MB [0x45dd2fb0 Sectors]
# So we get to try and catch this as well.
if ( $line =~ /^Raw\s+Size(\s+)?:(\s+)?\s+(\S+)\s+\[/i ) {
my $size_dump = $3;
if ( $size_dump && $size_dump =~ /([[:alpha:]]+)/i ) {
$size_unit = $1;
$size_dump =~ s/$size_unit//;
$size_value = $size_dump;
}
else {
$self->logger({ cat => 'c', msg => "Failed to extract size_dump [$size_dump] from string [$line]" });
}
}
if ( $line =~ /^Raw\s+Size(\s+)?:(\s+)?(.+)/i ) {
$size_unparsed = $3;
}
if ( $line =~ /^Connected\s+Port\s+Number(\s+)?:\s+(\d+)\(path(\d+)\)/i ) {
$port = $2;
$channel = $3;
}
if ( $line =~ /^Firmware\s+state(\s+)?:\s+(.+)/i ) {
$state = $2;
}
}
# If device_id is undef, or not >= 0, not a valid device; return.
return unless ( defined($device_id) && $device_id >= 0 );
# MegaCli-1.01.39 at least with a MegaRAID SAS 8704ELP doesn't show the PD Type line we
# are looking for above. The result is in such a setup we would loop till we met timeout
# since $dtype wasn't getting populated. So if $dtype is presently empty, check if we are
# on MegaCli less than version 3. We know version 4 shows the output we expect.
unless ( $dtype ) {
if ( $self->get_megacli_ver({icmd => $opts->{icmd}}) <= 3 ) {
# FIXME: I dont see anywhere to get the actual physical disk type if PD Type isn't returned.
# Realistically, this only comes up on old MegaCli versions so for now just assume SATA in
# this specific case.
$dtype = 'SATA';
}
else {
$self->logger({ cat => 'c', msg => "Info/MegaraidSAS: Unable to detect dtype and MegaCli version is >= 4!" });
}
}
# -ShowSummary isn't in MegaCli-4; we won't ever get vendor or product ID on MegaCli-4...
my $vendor_tool_model;
my $vendor_tool_vendor_id;
my $is_our_search_disk = 0;
for my $line ( split /^/, `$opts->{icmd} -ShowSummary -a$opts->{controller}` ) {
if ( $line =~ /\s+Connector(\s+)?:\s+Port\s+$channel\s+-\s+\S+\s+Slot\s+$port/i ) {
$is_our_search_disk = 1;
}
# If we come across this line, but $channel-$port do not match, this isn't our disk.
elsif ( $line =~ /\s+Connector(\s+)?:\s+Port\s+\d+\s+-\s+\S+\s+Slot\s+\d+/i ) {
$is_our_search_disk = 0;
}
if ( $is_our_search_disk ) {
if ( $line =~ /^\s+Product\s+Id(\s+)?:(.+)/i ) {
$vendor_tool_model = $2;
}
if ( $line =~ /^\s+Vendor\s+Id(\s+)?:(.+)/i ) {
$vendor_tool_vendor_id = $2;
}
}
}
# Filter out MegaCli-4...
my ($vendor_tool_firmware,$vendor_tool_serial);
if ( $vendor_tool_model && $vendor_tool_vendor_id ) {
my $is_our_search_disk = 0;
for my $line ( split /^/, `$opts->{icmd} -PDList -a$opts->{controller}` ) {
if ( $line =~ /^Slot\s+Number(\s+)?:\s+$port/i ) {
$is_our_search_disk = 1;
}
elsif ( $line =~ /^Slot\s+Number(\s+)?:\s+\d+/i ) {
$is_our_search_disk = 0;
}
if ( $is_our_search_disk ) {
if ( $line =~ /^Device\s+Firmware\s+Level(\s+)?:(.+)/i ) {
$vendor_tool_firmware = $2;
}
}
}
# The inquiry data line is unreliable. It appears to just be a raw dump from the device, unparsed.
# This means field order can and does change from device to device, and some devices dont even have
# fields that others do (such as firmware). This logic attempts to account for this.
#
# Assumption:
# vendor_tool_serial = inq_data_line - vendor_tool_vendor_id - vendor_tool_model - vendor_tool_firmware
$vendor_tool_serial = $inq_data_line;
if ( $vendor_tool_serial =~ /Inquiry\s+Data(\s+)?:(.+)/i ) {
$vendor_tool_serial = $2;
}
$vendor_tool_vendor_id =~ tr/ //ds;
$vendor_tool_model =~ tr/ //ds;
$vendor_tool_firmware =~ tr/ //ds;
$vendor_tool_serial =~ s/$vendor_tool_vendor_id//;
$vendor_tool_serial =~ s/$vendor_tool_model//;
$vendor_tool_serial =~ s/$vendor_tool_firmware//;
$vendor_tool_serial =~ tr/ //ds;
$vendor_tool_model =~ tr/ //ds;
}
if ( $pdInfoOut =~ /Media\s+Type(\s+)?:\s+Solid\s+State\s+Device/i ) {
$dtype = 'SSD';
}
my $size_mb = $self->convert_to_mb({ unit => $size_unit, value => $size_value });
# Determine a compatible block device to query.
my $disc_block_devices_ref = $self->get_block_devices();
my $smart_attribs = 'unsupported';
my $failed_to_get_smart_info = 1;
my @block_device_candidates = ();
for my $block_device ( @{ $disc_block_devices_ref } ) {
last unless ( $failed_to_get_smart_info );
$failed_to_get_smart_info = 1;
for my $line ( split /^/, `smartctl -i /dev/$block_device` ) {
if ( $line =~ /Vendor(\s+)?:\s+LSI\s+|megaraid|AVAGO|Device(\s+)?:\s+LSI\s+/i ) {
push(@block_device_candidates, "/dev/$block_device");
}
}
}
my $candidates_num = scalar(@block_device_candidates);
if ( $candidates_num == 1 ) {
$ld_block_device = $block_device_candidates[0];
}
# More than one LD.
else {
# Find a block device that belongs to our given controller.
$ld_block_device = $self->find_suitable_block_device({
candidate_block_devs => \@block_device_candidates,
controller => $opts->{controller},
controllers => $opts->{controllers}
});
}
if ( $ld_block_device && $ld_block_device !~ /^\/dev\//i ) {
$ld_block_device = "/dev/$ld_block_device";
}
# Get SMART data, now that we have the block device.
$smart_attribs = $self->get_smart_attribs({
device => $ld_block_device,
d_flag => "sat+megaraid,$device_id",
});
$smart_attribs = 'unsupported' if ( defined($smart_attribs->{error}) );
my $smart_info = $self->get_smart_info({
device => $ld_block_device,
d_flag => "sat+megaraid,$device_id",
});
for my $line ( split /^/, $smart_info ) {
if ( $line =~ /Device\s+Model(\s+)?:(.+)/i ) {
$model = $2;
$model =~ tr/ //ds;
$failed_to_get_smart_info = 0;
}
if ( $line =~ /Serial\s+Number(\s+)?:(.+)/i ) {
$serial = $2;
$serial =~ tr/ //ds;
$failed_to_get_smart_info = 0;
}
if ( $line =~ /Firmware\s+Version(\s+)?:(.+)/i ) {
$firmware = $2;
$firmware =~ tr/ //ds;
$failed_to_get_smart_info = 0;
}
}
# If we dont have the smart info we are looking for still, attempt without
# SATA specific args.
if ( $failed_to_get_smart_info || $model =~ /NoInformationFound/i || $serial =~ /NoInformationFound/i ) {
my $smart_info_sas = $self->get_smart_info({
device => $ld_block_device,
d_flag => "megaraid,$device_id",
});
# $smart_info_sas is going to look like this:
#
# Vendor: FUJITSU
# Product: MBA3147RC
# Revision: 0103
# User Capacity: 147,086,327,808 bytes [147 GB]
# Logical block size: 512 bytes
# Logical Unit id: 0x500000e1171561f0
# Serial number: BJA0PB10GT6W
# Device type: disk
# Transport protocol: SAS
# Local Time is: Wed Apr 1 16:18:07 2015 CEST
# Device supports SMART and is Enabled
# Temperature Warning Enabled
# Informational Exceptions (SMART) enabled
# Temperature warning enabled
for my $line ( split /^/, $smart_info_sas ) {
if ( $line =~ /^Product(\s+)?:(.+)/i ) {
$model = $2;
$model =~ tr/ //ds;
$failed_to_get_smart_info = 1;
}
if ( $line =~ /^Serial\s+number(\s+)?:(.+)/i ) {
$serial = $2;
$serial =~ tr/ //ds;
$failed_to_get_smart_info = 1;
}
# Firmware version is not shown in this output..
$firmware = 'unknown';
}
}
if ( $failed_to_get_smart_info ) {
$self->logger({ cat => 'w', msg => "failed to get SMART info for device id [$device_id] block device [$ld_block_device]." });
$failed_to_get_smart_info = 1;
}
return ($dtype,$size_mb,$port,$channel,$model,$firmware,$serial,$state,$smart_attribs,$ld_block_device,$device_id,$size_unparsed,$vendor_tool_serial,$vendor_tool_model,$vendor_tool_firmware);
}
sub get_megacli_ver {
my $self = shift;
my $opts = shift;
for my $line ( split/^/, `$opts->{icmd} -v` ) {
if ( $line =~ /\s+Ver\s+(\d+)\.\d+/ ) {
return $1;
}
}
$self->logger({ cat => 'c', msg => 'get_megacli_ver(); failed to detect MegaCli version' });
}
sub get_controller_info {
my $self = shift;
my $opts = shift;
my $array_count;
my $disk_count;
my $cntrl_firmware = 'unknown';
my $cntrl_model = 'unknown';
my $cntrl_serial = 'unknown';
my $bbu_present = 0;
my $bbu_state = 'unknown';
my $cntrl_memory = 'unknown';
my $cntrl_memory_correctable_errors = 0;
my $cntrl_memory_uncorrectable_errors = 0;
for my $line ( split /^/, `$opts->{icmd} -AdpAllInfo -a$opts->{controller}` ) {
if ( $line =~ /^Virtual\s+Drives(\s+)?:\s+(\d+)/i ) {
$array_count = $2;
}
elsif ( $line =~ /^\s+Disks(\s+)?:\s+(\d+)/i ) {
$disk_count = $2;
}
elsif ( $line =~ /^FW\s+Version(\s+)?:\s+(\S+)/i ) {
$cntrl_firmware = $2;
}
elsif ( $line =~ /^Product\s+Name(\s+)?:\s+(.+)/i ) {
$cntrl_model = $2;
$cntrl_model =~ tr/ //ds;
}
elsif ( $line =~ /^Serial\s+No(\s+)?:\s+(\S+)/i ) {
$cntrl_serial = $2;
}
elsif ( $line =~ /^BBU(\s+)?:\s+Present/i ) {
$bbu_present = 1;
my $bbu_check_cmd = `$opts->{icmd} -AdpBbuCmd -GetBbuStatus -a$opts->{controller}`;
if ( $bbu_check_cmd =~ /Battery\s+State(\s+)?:\s+(.+)/i ) {
$bbu_state = $2;
}
}
elsif ( $line =~ /^Memory\s+Size(\s+)?:\s+(\S+)/i ) {
$cntrl_memory = $2;
}
elsif ( $line =~ /^Memory\s+Correctable\s+Errors(\s+)?:\s+(.+)/i ) {
$cntrl_memory_correctable_errors = $2;
}
elsif ( $line =~ /^Memory\s+Uncorrectable\s+Errors(\s+)?:\s+(.+)/i ) {
$cntrl_memory_uncorrectable_errors = $2;
}
}
$cntrl_memory_correctable_errors = $self->strip_whitespace({ string => $cntrl_memory_correctable_errors });
$cntrl_memory_uncorrectable_errors = $self->strip_whitespace({ string => $cntrl_memory_uncorrectable_errors });
# If empty, set to error.
$cntrl_model = 'error' if ( $cntrl_model !~ /\S/ );
$cntrl_serial = 'error' if ( $cntrl_serial !~ /\S/ );
return ($array_count,$disk_count,$cntrl_firmware,$cntrl_model,$cntrl_serial,$bbu_present,$bbu_state,$cntrl_memory,$cntrl_memory_correctable_errors,$cntrl_memory_uncorrectable_errors);
}
sub host_supports_info {
my ($self) = @_;
return 1;
}
1;