????
| Current Path : /usr/lib/raider/Raider/Info/ |
| Current File : //usr/lib/raider/Raider/Info/StorCli.pm |
use strict;
use warnings;
package Raider::Info::StorCli;
use base qw( Raider::Info );
use Raider::Jobs::StorCli;
use JSON::Tiny qw(decode_json encode_json);
=head1 NAME
Raider::Info::StorCli - StorCli specific instructions for get-info
=head1 DESCRIPTION
StorCli specific methods to gather information required for populating the .info file.
=head1 USAGE
use Raider::Info::StorCli;
my $info = Raider::Info::StorCli->new();
=head1 METHODS
=head2 get_info({ cntrl_cnt => int, disk_cnt => int })
Initiates all needed steps to generate a finished StorCli info file.
Return tuple:
HASH, HASH, int, int
=head2 get_disk_info(jobsObj, int, int, string)
Given a jobs object, controller, did, and block device, return the info of the disk.
Returns:
{
dtype => string,
model => string,
port => int,
firmware => string,
serial => string,
state => string,
size_unparsed => string,
size_mb => int,
smart_attribs => HASH,
}
=head2 extract_LD_info(jobsObj, int, int)
Given a jobs object, controller, and array, return info on the array (LD, logical disk).
Retruns:
{
num_drives => int,
raid_level => int,
block_device => string,
raid_state => string,
stripe_size => string,
size_unparsed => string,
size_mb => int,
pds => [ "model::serial" ],
unidentifiable_disks => int,
}
=head2 get_controller_attributes(jobsObject, int)
Given a jobsObj, and a controller, return attributes of the controller.
Returns:
{
serial => string,
model => string,
firmware => string,
numdrives => int,
numarrays => int,
bbu_present => int,
memory => string,
}
=head2 get_cntrl_phys_disk_ids(jobsObj, int)
Given a Jobs object and a controller, return the DID's (disk identifiers) attached to the controller.
Returns:
[
int,
]
=head2 get_phys_disk_serial_firmware(int, string)
Given a did and a block device, return disk firmware and serial
Returns:
{
firmware => string,
serial => string,
}
=head2 host_supports_info()
Given no args, return whether or not this physical host supports get-info collection.
Returns:
BOOL (1 or 0)
=cut
sub get_info {
my $self = shift;
my $opts = shift;
my $jobs = Raider::Jobs::StorCli->new();
my @controller_list = @{ $jobs->get_controller_list() };
my $d_struct_disks = { }; # sonar consumer
my $d_struct_controllers = { }; # sonar consumer
my $storm_d_struct = { controllers => { }, }; # storm consumer
unless ($self->host_supports_info()) {
$self->logger({cat => 'w', msg => "this host doesnt support get-info collection for StorCli 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 ) {
###########################
# Controller data
###########################
my $cntrl_attribs = $self->get_controller_attributes($jobs, $controller);
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'identifier'} = "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}";
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'make'} = 'LSI';
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'model'} = $cntrl_attribs->{model};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'firmware'} = $cntrl_attribs->{firmware};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numdrives'} = $cntrl_attribs->{numdrives};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'numlds'} = $cntrl_attribs->{numarrays};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'serial'} = $cntrl_attribs->{serial};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'bbu_present'} = $cntrl_attribs->{bbu_present};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'memory'} = $cntrl_attribs->{memory};
$storm_d_struct->{'controllers'}->{ $controller } = { disks => { } };
$storm_d_struct->{'controllers'}->{ $controller }->{'make'} = 'LSI';
$storm_d_struct->{'controllers'}->{ $controller }->{'model'} = $cntrl_attribs->{model};
$storm_d_struct->{'controllers'}->{ $controller }->{'firmware'} = $cntrl_attribs->{firmware};
$storm_d_struct->{'controllers'}->{ $controller }->{'numdrives'} = $cntrl_attribs->{numdrives};
$storm_d_struct->{'controllers'}->{ $controller }->{'numarrays'} = $cntrl_attribs->{numarrays};
if ( ! defined($d_struct_map_file_new->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }) ) {
$d_struct_map_file_new = { "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" => {} };
}
###########################
# Logical disk data
###########################
my $unidentifiable_disks_total_controller = 0;
my $ld_info;
for my $array ( @{ $jobs->get_array_list($controller) } ) {
$ld_info = $self->extract_LD_info($jobs, $controller, $array);
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array } = {
identifier => "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}\:::$array",
numdrives => $ld_info->{num_drives},
ld_num => $array,
type => 'RAID', # TODO: support cachecade etc?
stripe_size => $ld_info->{stripe_size},
raidlevel => $ld_info->{raid_level},
state => $ld_info->{raid_state},
size_mb => $ld_info->{size_mb},
size_unparsed => $ld_info->{size_unparsed},
block_device => $ld_info->{block_device},
};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array }->{'unidentifiable_disks'} = $ld_info->{unidentifiable_disks};
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'logical_disks'}->{ $array }->{physical_disks} = $ld_info->{pds};
$storm_d_struct->{'controllers'}->{ $controller }->{'arrays'}->{ $array } = {
numdrives => $ld_info->{num_drives},
raidlevel => $ld_info->{raid_level},
state => $ld_info->{raid_state} =~ /^Optl/i ? 1 : 0, # prov wants this "bool"
};
$unidentifiable_disks_total_controller = $unidentifiable_disks_total_controller + $ld_info->{unidentifiable_disks};
}
# Populate unidentifiable_disks.
$d_struct_controllers->{ $opts->{cntrl_cnt} }->{'unidentifiable_disks'} = $unidentifiable_disks_total_controller;
###########################
# Physical disk data
###########################
for my $did ( @{ $self->get_cntrl_phys_disk_ids($jobs, $controller) } ) {
my $disk_info = $self->get_disk_info($jobs, $controller, $did, $ld_info->{block_device});
# when a disk fails, it typically isn't visible to the system anymore. Which means of course, we can't do
# things like fetch its serial from smartctl. Because we don't want failed disks to disappear yet from the
# server until its actually removed, this map stuff is below. This stems from a really old desire from two
# now ex employees.. as a part of the original raider+sonar integration (see: sonar.info). The idea at the
# time, was that this information would be used for aiding tracking inventory. That didn't really ever
# happen tbh.. but lets play along and not create a martyr at least for now.
# ssullivan - Oct 2019
if ( $disk_info->{model} =~ /^\[No|unknown/i || $disk_info->{serial} =~ /^\[No|unknown/i ) {
$self->logger({ cat => 'i', msg => "PD [$did] on StorCli controller [$controller] not responding to SMART inquiries." });
if ( $self->map_file_exists({ device => 'StorCli_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'}/StorCli_device_map.info";
open FILE, "<$device_map_file" or $self->logger({cat => 'c', msg => "failed opening [$Raider::Base::base_conf{'data_path'}/StorCli_device_map.info]: $!"});
my $device_map_file_text = do { local $/; <FILE> };
close(FILE) or $self->logger({cat => 'c', msg => "failed closing [$Raider::Base::base_conf{'data_path'}/StorCli_device_map.info]: $!"});
# Decode.
my $d_struct_map_file = eval { decode_json($device_map_file_text) };
if ( my $e = $@ ) {
$self->logger({ cat => 'w', msg => "failed json decoding [$device_map_file]: $e" });
}
else {
# Check and see if this device is identified in our decoded MAP file.
if ( defined($d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}) ) {
$disk_info->{model} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{model};
$disk_info->{serial} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{serial};
if ( defined($d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{dtype}) ) {
$disk_info->{dtype} = $d_struct_map_file->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }->{$did}->{dtype};
}
$self->logger({ cat => 'i', msg => "StorCli: Got model+serial from MAP file." });
}
else {
$self->logger({ cat => 'w', msg => "No device entry found in MAP file for [$did] on StorCli controller [$controller]" });
}
}
}
else {
$self->logger({ cat => 'w', msg => "PD [$did] on StorCli controller [$controller] not responding to SMART inquiries, and no RAIDER MAP entry. Disk will not identify properly!" });
}
}
# Only add to MAP file and sonar disks key if model+serial is valid.
else {
$d_struct_map_file_new->{ "$cntrl_attribs->{model}\:::$cntrl_attribs->{serial}" }{$did} = {
model => $disk_info->{model},
serial => $disk_info->{serial},
dtype => $disk_info->{dtype},
};
$d_struct_disks->{ $opts->{disk_cnt} } = {
identifier => "$disk_info->{model}\:::$disk_info->{serial}",
type => $disk_info->{dtype},
size_mb => $disk_info->{size_mb},
size_unparsed => $disk_info->{size_unparsed},
port => $did,
model => $disk_info->{model},
firmware => $disk_info->{firmware},
serial => $disk_info->{serial},
state => $disk_info->{state},
block_device => $ld_info->{block_device},
device_id => $did,
};
$d_struct_disks->{ $opts->{disk_cnt} }->{'smart_attributes'} = $disk_info->{smart_attribs};
}
$storm_d_struct->{'controllers'}->{ $controller }->{'disks'}->{ $did } = { type => $disk_info->{dtype} };
# Increment our passed disk counter.
$opts->{disk_cnt}++;
};
# Increment our passed controller counter.
$opts->{cntrl_cnt}++;
};
$self->write_json({data => $storm_d_struct, device => 'StorCli'});
if ( defined($d_struct_map_file_new) ) {
$self->write_json({data => $d_struct_map_file_new, device => 'StorCli_device_map'});
}
return ($d_struct_controllers, $d_struct_disks, $opts->{cntrl_cnt}, $opts->{disk_cnt});
}
sub extract_LD_info {
my ($self, $jobs, $controller, $array) = @_;
my $data = $jobs->runStorCliCmdJsonSingleton("/c$controller/v$array show all")->{'Response Data'};
my ($raid_level) = $data->{"/c$controller/v$array"}[0]->{TYPE} =~ /^RAID(\d+)/i;
my $block_device = $data->{"VD$array Properties"}{'OS Drive Name'};
my @phys_disks_in_ld;
my $unidentifiable_disks = 0;
if ($block_device) {
my $disks = $jobs->runStorCliCmdJsonSingleton("/c$controller/v$array show all")->{'Response Data'}{"PDs for VD $array"};
for my $disk (@{$disks}) {
my $model = $disk->{Model};
$model =~ tr/ //ds;
my $did = $disk->{DID};
unless ($did || $model) {
$unidentifiable_disks++;
next;
}
my $serial = $self->get_phys_disk_serial_firmware($did, $block_device)->{serial};
unless ($serial) {
$unidentifiable_disks++;
next;
}
push(@phys_disks_in_ld, "$model\:::$serial");
}
}
else {
$self->logger({cat => 'w', msg => "failed discovering block device of array [$array] on StorCli controller [$controller]. Some disk information will not be available."});
}
my %info = (
num_drives => scalar @{$data->{"PDs for VD $array"}},
raid_level => $raid_level //= 'unknown',
block_device => $block_device //= 'unknown',
raid_state => $data->{"/c$controller/v$array"}[0]->{State} //= 'unknown',
stripe_size => $data->{"VD$array Properties"}{'Strip Size'} //= 'unknown',
size_unparsed => $data->{"/c$controller/v$array"}[0]->{Size} //= 'unknown',
size_mb => 'unknown',
pds => \@phys_disks_in_ld,
unidentifiable_disks => $unidentifiable_disks,
);
if ($data->{"VD$array Properties"}{'Number of Blocks'}) {
$info{size_mb} = $self->convert_to_mb({ unit => 'block', value => $data->{"VD$array Properties"}{'Number of Blocks'} });
}
return \%info;
}
sub get_cntrl_phys_disk_ids {
my ($self, $jobs, $controller) = @_;
my @phys_disk_ids;
my $topology = $jobs->runStorCliCmdJsonSingleton("/c$controller/dall show")->{'Response Data'}{'Response Data'}{TOPOLOGY};
# "DID" is the disk identifier. We need this to fetch information from the physical disk.
for my $ld ( @{ $topology } ) {
next unless $ld->{Type} =~ /^DRIVE/i; # raid lds will show up here, go away
next unless defined $ld->{DID}; # shouldnt happen..
push @phys_disk_ids, $ld->{DID};
}
return \@phys_disk_ids;
}
sub get_disk_info {
my ($self, $jobs, $controller, $did, $block_device) = @_;
my %info = (
dtype => 'unknown',
model => 'unknown',
port => 'unknown',
firmware => 'unknown',
serial => 'unknown',
state => 'unknown',
size_unparsed => 'unknown',
size_mb => 'unknown',
smart_attribs => 'unsupported',
);
my $drives = $jobs->runStorCliCmdJsonSingleton("/c$controller/dall show all")->{'Response Data'}{'Response Data'}{'DG Drive LIST'};
for my $drive (@{ $drives } ) {
next unless defined $drive->{DID}; # 0 is valid
next if $drive->{DID} != $did;
$info{dtype} = $drive->{Intf};
$info{model} = $drive->{Model};
$info{model} =~ tr/ //ds;
$info{port} = $drive->{DID};
$info{size_unparsed} = $drive->{Size};
$info{state} = $drive->{State};
my ($size_value, $size_unit) = $drive->{Size} =~ /(\S+)\s+(\S+)/;
if ($size_value && $size_unit) {
$info{size_mb} = $self->convert_to_mb({ value => $size_value, unit => $size_unit });
}
}
my $extra_disk_data = $self->get_phys_disk_serial_firmware($did, $block_device);
$info{firmware} = $extra_disk_data->{firmware};
$info{serial} = $extra_disk_data->{serial};
my $smart_attribs = $self->get_smart_attribs({
device => $block_device,
d_flag => "megaraid,$did"
});
unless ($smart_attribs->{error}) {
$info{smart_attribs} = $smart_attribs;
}
return \%info;
}
sub get_phys_disk_serial_firmware {
my ($self, $did, $block_device) = @_;
# smartctl provides integrated support for megaraid powered controllers. Access is obtained as such:
# smartctl -i -d megaraid,N $block_device
# where <N> stands for the device id on the controller (DID in storcli).
my %info;
my $smartctl_info = `smartctl -i -d megaraid,$did $block_device -T permissive`;
for my $line (split /^/, $smartctl_info) {
if ( $line =~ /^Serial\s+number(\s+)?:(.+)/i ) {
$info{serial} = $2;
$info{serial} =~ tr/ //ds;
}
elsif ( $line =~ /^Firmware\s+Version(\s+)?:(.+)/i ) {
$info{firmware} = $2;
$info{firmware} =~ tr/ //ds;
}
elsif ( $line =~ /^Revision(\s+)?:(.+)/i ) {
$info{firmware} = $2;
$info{firmware} =~ tr/ //ds;
}
}
return \%info;
}
sub get_controller_attributes {
my ($self, $jobs, $controller) = @_;
my $data = $jobs->runStorCliCmdJsonSingleton("/c$controller show all")->{'Response Data'};
my %attribs = (
serial => $data->{'Basics'}{'Serial Number'} //= 'unknown',
model => $data->{'Basics'}{Model} //= 'unknown',
firmware => $data->{'Version'}{'Firmware Version'} //= 'unknown',
numdrives => $data->{'Physical Drives'} //= 'unknown',
numarrays => scalar @{ $jobs->get_array_list($controller) },
bbu_present => (ref($jobs->getBbuStatus($controller)) eq 'HASH') ? 1 : 0,
memory => $data->{HwCfg}{'On Board Memory Size'} //= 'unknown',
);
return \%attribs;
}
sub host_supports_info {
my ($self) = @_;
# in testing, the 1000:0014 device (AVAGO MegaRAID SAS 9460-16i) wouldn't fetch full details on the controller
# and backing disks (/opt/MegaRAID/storcli/storcli64 /c0 show) without hanging until I updated megaraid_sas
# kernel module to version 07.711.04.00 (MR_LINUX_DRIVER_7.11-07.711.04.00-1.tgz). All things required for jobs
# would work on the older module, but full controller details required for Info would only work with the newer
# version.
# kmod-megaraid_sas-07.711.04.00_el7.6-1.x86_64
my $min_ver_numeric = '077110400';
if (`lsmod` !~ /megaraid_sas/) {
$self->logger({cat => 'w', msg => "StorCli is registered, but megaraid_sas kernel module isn't loaded. Info collection will be disabled."});
return 0;
}
my $numeric_ver_running;
for my $line (split /^/, `modinfo megaraid_sas`) {
if ($line =~ /^version:\s+(\S+)/i) {
my $raw_version = $1;
$numeric_ver_running = $raw_version;
$numeric_ver_running =~ s/\D+//g;
}
}
if ($numeric_ver_running) {
if ($numeric_ver_running < $min_ver_numeric) {
return 0;
}
}
return 1;
}
1;