????
| Current Path : /lib/raider/Raider/ |
| Current File : //lib/raider/Raider/Info.pm |
use strict;
use warnings;
package Raider::Info;
use base qw( Raider::Base );
use Raider::Jobs;
use Raider::Devices;
use JSON::Tiny qw(decode_json encode_json);
use File::Find ();
use vars qw/*name *dir *prune/;
*name = *File::Find::name;
*dir = *File::Find::dir;
*prune = *File::Find::prune;
use Cwd (); # Included in perl
=head1 NAME
Raider::Info - Base class for Info related tasks
=head1 DESCRIPTION
Base class for all info objects.
=head1 USAGE
use Raider::Info;
my $info_obj = Raider::Info->new();
=head1 METHODS
=head2 do_get_info()
Delegates the first steps in --get-info command line argument.
=head2 get_os()
Returns either redhat or debian. If neither, then error is thrown.
=head2 get_os_release()
Returns LSB OS release.
=head2 is_redhat()
Returns true if OS is Red Hat.
=head2 is_debian()
Returns true if OS is Debian (or fork Ubuntu).
=head2 write_json(\%args)
Write JSON to file.
=head2 get_pkg_mngr_cmd()
Returns what package manager command to use.
=head2 clean()
Clean .info file(s).
=head2 get_info_files()
Return present .info file(s).
=cut
sub do_get_info {
my $self = shift;
my $jobsObj = Raider::Jobs->new();
my $devObj = Raider::Devices->new();
my @jobs = @{ $jobsObj->get_job_list() };
if ( scalar @jobs == 0 ) {
$self->logger({ cat => 'i', msg => "do_get_info() called but no job files present, calling get_devices()..." });
$devObj->get_devices();
@jobs = @{ $jobsObj->get_job_list() };
}
$self->clean(); # removes all .info files
my $d_struct_controllers = { controllers => { }, };
my $d_struct_disks = { disks => { }, };
my $d_struct;
my ($d_struct_controllers_adaptec,$d_struct_disks_adaptec);
my ($d_struct_controllers_3ware,$d_struct_disks_3ware);
my ($d_struct_controllers_megaraidsas,$d_struct_disks_megaraidsas);
my ($d_struct_controllers_FusionMPTSAS2,$d_struct_disks_FusionMPTSAS2);
my ($d_struct_controllers_StorCli,$d_struct_disks_StorCli);
my ($d_struct_usb_storage_satascsiata,$d_struct_controllers_satascsiata,$d_struct_disks_satascsiata);
# Always start at zero.
my $cntrl_cnt = 0;
my $disk_cnt = 0;
for my $info_task ( @jobs ) {
if ( $info_task eq '3ware' ) {
use Raider::Info::3ware;
my $info3ware = Raider::Info::3ware->new();
($d_struct_controllers_3ware,$d_struct_disks_3ware,$cntrl_cnt,$disk_cnt) = $info3ware->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'MegaraidSAS' ) {
use Raider::Info::MegaraidSAS;
my $infoMegaraidSAS = Raider::Info::MegaraidSAS->new();
($d_struct_controllers_megaraidsas,$d_struct_disks_megaraidsas,$cntrl_cnt,$disk_cnt) = $infoMegaraidSAS->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'MegaraidSATA' ) {
use Raider::Info::MegaraidSATA;
my $infoMegaraidSATA = Raider::Info::MegaraidSATA->new();
$infoMegaraidSATA->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'Adaptec' ) {
use Raider::Info::Adaptec;
my $infoAdaptec = Raider::Info::Adaptec->new();
($d_struct_controllers_adaptec,$d_struct_disks_adaptec,$cntrl_cnt,$disk_cnt) = $infoAdaptec->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'satascsiata' ) {
use Raider::Info::satascsiata;
my $infoSatascsiata = Raider::Info::satascsiata->new();
($d_struct_usb_storage_satascsiata,$d_struct_controllers_satascsiata,$d_struct_disks_satascsiata,$cntrl_cnt,$disk_cnt) = $infoSatascsiata->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'Mdraid' ) {
next; # TODO "someday"? when MikeN wants to go over this... <-- update: October, 2019.. :(
}
elsif ( $info_task eq 'FusionMPTSAS2' ) {
use Raider::Info::FusionMPTSAS2;
my $infoFusionMPTSAS2 = Raider::Info::FusionMPTSAS2->new();
($d_struct_controllers_FusionMPTSAS2,$d_struct_disks_FusionMPTSAS2,$cntrl_cnt,$disk_cnt) = $infoFusionMPTSAS2->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
elsif ( $info_task eq 'StorCli' ) {
# hey, so past self was a bit of a jerk here.
use Raider::Info::StorCli;
my $info = Raider::Info::StorCli->new();
($d_struct_controllers_StorCli,$d_struct_disks_StorCli,$cntrl_cnt,$disk_cnt) = $info->get_info({
cntrl_cnt => $cntrl_cnt,
disk_cnt => $disk_cnt
});
}
else {
$self->logger({ cat => 'w', msg => "Unrecognized device file found ($info_task), ignoring..." });
}
};
$d_struct_disks = {
%{ $d_struct_disks_adaptec || { } },
%{ $d_struct_disks_3ware || { } },
%{ $d_struct_disks_megaraidsas || { } },
%{ $d_struct_disks_FusionMPTSAS2 || { } },
%{ $d_struct_disks_StorCli || { } },
%{ $d_struct_disks_satascsiata || { } }
};
$d_struct_controllers = {
%{ $d_struct_controllers_adaptec || { } },
%{ $d_struct_controllers_3ware || { } },
%{ $d_struct_controllers_megaraidsas || { } },
%{ $d_struct_controllers_FusionMPTSAS2 || { } },
%{ $d_struct_controllers_StorCli || { } },
%{ $d_struct_controllers_satascsiata || { } }
};
my $d_struct_usb_storage = {
%{ $d_struct_usb_storage_satascsiata || { } },
};
$d_struct = {
controllers => {
%{ $d_struct_controllers },
%{ $d_struct_usb_storage }
},
physical_disks => { %{ $d_struct_disks } }
};
$self->write_json({data => $d_struct, device => 'sonar'});
}
sub get_os {
my $self = shift;
if ( $self->is_redhat ) {
return 'redhat';
}
elsif ( $self->is_debian ) {
return 'debian';
}
else {
$self->logger({ cat => 'c', msg => "Unsupported operating system." });
}
}
sub get_os_release {
my $self = shift;
my $release = `lsb_release -r`;
$release =~ s/Release://g;
$release =~ s/\n//g;
$release = $self->strip_whitespace({ string => $release });
return $release;
}
sub fetch_cent_ver {
my $self = shift;
if ( $self->is_centos() ) {
my $os_rel = $self->get_os_release();
if ( $os_rel =~ /(\d)\.[0-9]+/ ) {
return $1;
}
else {
$self->logger({ cat => 'c', msg => "fetch_cent_ver() Failed to fetch CentOS major version!" });
}
}
else {
$self->logger({ cat => 'w', msg => "fetch_cent_ver() called on non-CentOS platform!" });
}
}
sub is_centos {
my $self = shift;
return 0 unless ( $self->is_redhat() );
return 1 if ( -e '/etc/centos-release' );
# The above centos-release file doesn't exist on CentOS 5 and earlier of course.
# We have to support back to CentOS 4.. so check redhat-release for centos.
if ( -e '/etc/redhat-release' ) {
my $rhrel = do {
local $/ = undef;
open my $fh, "<", '/etc/redhat-release' or $self->logger({ cat => 'c', msg => "Unable to open [/etc/redhat-release]: $!" });
<$fh>;
};
return 1 if ( $rhrel =~ /centos/i );
}
return 0;
}
sub is_fedora {
return ( -e '/etc/fedora-release' ) ? 1 : 0;
}
sub is_redhat {
my $ret = ( -e '/etc/redhat-release' ) ? 1 : 0;
return $ret;
}
sub is_debian {
my $ret = ( -e '/etc/debian_version' ) ? 1 : 0;
return $ret;
}
sub write_json {
my $self = shift;
my $opts = shift;
my $json_to_write = encode_json($opts->{data});
my $info_filename;
if ( $opts->{device} =~ /_device_map/i ) {
$info_filename = "$Raider::Base::base_conf{'data_path'}/$opts->{device}.info";
}
else {
$info_filename = "$Raider::Base::base_conf{'info_path'}/$opts->{device}.info";
}
open INFO_FILE, ">", "$info_filename" or $self->logger({ cat => 'c', msg => "Unable to save $info_filename; got system error: $!" });
print INFO_FILE $json_to_write;
close(INFO_FILE);
}
sub get_pkg_mngr_cmd {
my $self = shift;
my $os = $self->get_os();
if ( $os eq 'debian' ) {
## Don't use aptitude as of 01/12/12. Whether or not aptitude was able to install the package, its exit
## code will always be 0. This brings about much fail in detecting package manager cmd failures. --ssullivan
return 'apt-get -y';
}
elsif ( $os eq 'redhat' ) {
## Support legacy 'lpyum'...
if ( -e '/usr/local/lp/configs/yum/yum.conf' ) {
return 'yum -y -c /usr/local/lp/configs/yum/yum.conf';
}
else {
return 'yum -y';
}
}
$self->logger({cat => 'c', msg => "troubles detecting the package manager command os: [$os]"});
}
sub clean {
my $self = shift;
for my $info_file ( @{ $self->get_info_files() } ) {
if ( $info_file =~ /raider.lock/ ) { next; }
my $to_rm = "$Raider::Base::base_conf{'info_path'}/";
$to_rm .= $info_file;
unlink($to_rm);
};
}
sub get_info_files {
my $self = shift;
my $info_files_ref = $self->sysFindFile({ search_path => "$Raider::Base::base_conf{'info_path'}" });
return $info_files_ref;
}
sub get_smart_attribs {
my $self = shift;
my $opts = shift;
if ( $opts->{device} !~ /^\/dev\// ) {
$opts->{device} = "/dev/" . $opts->{device};
}
my $smart_attribs;
my $smart_attrib_output;
if ( $opts->{d_flag} ) {
$smart_attrib_output = `smartctl -A $opts->{device} -T permissive -d $opts->{d_flag} -s on`;
}
elsif ( $opts->{bus} && $opts->{bus} eq 'usb' ) {
$smart_attrib_output = `smartctl -A $opts->{device} -T permissive -d usbsunplus -s on`;
}
else {
$smart_attrib_output = `smartctl -A $opts->{device} -T permissive -s on`;
}
my $id_key;
for my $line ( split /^/, $smart_attrib_output ) {
if ( $line =~ /(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)/i ) {
my $id = $1;
$id_key = $id;
my $attribute_name = $2;
my $flag = $3;
my $value = $4;
my $worst = $5;
my $thresh = $6;
my $type = $7;
my $updated = $8;
my $when_failed = $9;
my $raw_value = $10;
# Turns out, some drives (like the Crucial_CT480M500SSD1 with the MU03 firmware)
# show invalid values for some SMART attributes. Example:
#
# [root@host ~]# smartctl -A /dev/sda|grep 194
# 194 Temperature_Celsius 0x0022 067 059 --- Old_age Always - 33 (0 41 255 246)
# 194 Temperature_Celsius <== Data Page | WARNING: PREVIOUS ATTRIBUTE HAS TWO
# 194 Temperature_Celsius <== Threshold Page | INCONSISTENT IDENTITIES IN THE DATA
# [root@host ~]#
#
# To account for this, if a ID is defined more than once, set all values
# of the attribute to "INVALID".
#
# --ssullivan Feb 13th, 2014
if ( defined($smart_attribs->{ $id }) ) {
$self->logger({ cat => 'w', msg => "get_smart_attribs() Attribute ID [$id] was already defined! Marking INVALID!" });
$smart_attribs->{ $id } = {};
$flag = 'INVALID';
$value = 'INVALID';
$worst = 'INVALID';
$thresh = 'INVALID';
$type = 'INVALID';
$updated = 'INVALID';
$when_failed = 'INVALID';
$raw_value = 'INVALID';
}
else {
# Only do RAW_VALUE_EXTRA if there is a space.
if ( $raw_value =~ /\s/ ) {
if ( $raw_value =~ /(\S+)(.+)/i ) {
$raw_value = $1;
if ( defined($2) && ! defined($smart_attribs->{ $id }) ) {
$smart_attribs->{ $id }->{ 'RAW_VALUE_EXTRA' } = $2;
}
}
}
}
$smart_attribs->{ $id }->{ 'ATTRIBUTE_NAME' } = $attribute_name;
$smart_attribs->{ $id }->{ 'FLAG' } = $flag;
$smart_attribs->{ $id }->{ 'VALUE' } = $value;
$smart_attribs->{ $id }->{ 'WORST' } = $worst;
$smart_attribs->{ $id }->{ 'THRESH' } = $thresh;
$smart_attribs->{ $id }->{ 'TYPE' } = $type;
$smart_attribs->{ $id }->{ 'UPDATED' } = $updated;
$smart_attribs->{ $id }->{ 'WHEN_FAILED' } = $when_failed;
$smart_attribs->{ $id }->{ 'RAW_VALUE' } = $raw_value;
}
}
# If this doesn't exist, there was an error. Perhaps more than one LSI controller exists, so our
# device port is incorrect for this controller.
if ( ! $id_key ) {
$self->logger({ cat => 'w', msg => "Info/get_smart_attribs() failed to get SMART attributes." });
$smart_attribs = { error => 1 };
}
return $smart_attribs;
}
sub get_smart_info {
my $self = shift;
my $opts = shift;
if ( $opts->{device} !~ /^\/dev\// ) {
$opts->{device} = "/dev/" . $opts->{device};
}
my $smart_info_output;
if ( $opts->{d_flag} ) {
$smart_info_output = `smartctl -i $opts->{device} -T permissive -d $opts->{d_flag}`;
}
else {
$smart_info_output = `smartctl -i $opts->{device} -T permissive`;
}
return $smart_info_output;
}
sub get_block_devices {
my $self = shift;
my $opts = shift;
my $sysfs_block = '/sys/block';
my @block_devices = ();
opendir my $dh, $sysfs_block || $self->logger({ cat => 'c', msg => "get_block_devices() failed to open [$sysfs_block]: $!" });
while (defined(my $name = readdir $dh)) {
@block_devices = grep {-d "$sysfs_block/$name" && ! /^\.{1,2}$/} readdir($dh);
}
closedir $dh;
return \@block_devices;
}
sub get_udev_block_device_info {
my $self = shift;
my $opts = shift;
my $id_serial = 0;
my $id_serial_short = 0;
my $model = 'unknown';
my $disk_bus = 'unknown';
my $id_vendor = 'unknown';
unless ( $opts->{block_device_name} ) {
$self->logger({ cat => 'w', msg => "get_udev_block_device_info() not passed block_device_name, bailing out.." });
return 'unknown';
}
# Older distros had a /dev/.udev/db/ dir, use that if it exists. Otherwise
# use udevadm.
# Ubuntu 12.04 (and greater?) doesn't seem to have a /dev/.udev/db..
my $file = 0;
if ( -e "/dev/.udev/db/block:$opts->{block_device_name}" ) {
$file = "/dev/.udev/db/block:$opts->{block_device_name}";
}
elsif ( -e "/dev/.udev/db/block\@$opts->{block_device_name}" ) {
$file = "/dev/.udev/db/block\@$opts->{block_device_name}";
}
if ( $file ) {
open my $fh, '<', $file || $self->logger({ cat => 'c', msg => "Unable to open $file [$!]" });
while ( defined( my $line = <$fh> ) ) {
if ( $line =~ /^E:ID_SERIAL=(.+)/i ) {
$id_serial = $1;
}
if ( $line =~ /^E:ID_SERIAL_SHORT=(.+)/i ) {
$id_serial_short = $1;
}
if ( $line =~ /^E:ID_MODEL=(.+)/i ) {
$model = $1;
}
if ( $line =~ /^E:ID_BUS=(.+)/i ) {
$disk_bus = $1;
}
if ( $line =~ /^E:ID_VENDOR=(.+)/i ) {
$id_vendor = $1;
}
};
close $fh;
}
else {
# Old distros like CentOS 4 do not have udevadm. Prevent non fatal split
# warning in this case.
if ( $self->in_path({ cmds => [ 'udevadm' ], mode => 'nofatal' }) eq 'present' ) {
for my $line ( split /^/, `udevadm info --query=property --path=/sys/block/$opts->{block_device_name}` ) {
if ( $line =~ /^ID_SERIAL=(.+)/i ) {
$id_serial = $1;
}
if ( $line =~ /^ID_SERIAL_SHORT=(.+)/i ) {
$id_serial_short = $1;
}
if ( $line =~ /^ID_MODEL=(.+)/i ) {
$model = $1;
}
if ( $line =~ /^ID_BUS=(.+)/i ) {
$disk_bus = $1;
}
if ( $line =~ /^ID_VENDOR=(.+)/i ) {
$id_vendor = $1;
}
}
}
else {
$self->logger({
cat => 'i',
msg => "Unable to fetch block device information from udev for [$opts->{block_device_name}]"
});
}
}
my $udev_info = {};
if ( $id_serial_short ) {
$udev_info = {
model => $model,
id_serial => $id_serial_short,
disk_bus => $disk_bus,
id_vendor => $id_vendor
};
}
elsif ( $id_serial ) {
$udev_info = {
model => $model,
id_serial => $id_serial,
disk_bus => $disk_bus,
id_vendor => $id_vendor
};
}
else {
$udev_info = {
model => $model,
id_serial => 'unknown',
disk_bus => $disk_bus,
id_vendor => $id_vendor
};
}
return $udev_info;
}
sub convert_to_mb {
my $self = shift;
my $opts = shift;
my $size_mb = 0;
chomp($opts->{unit}) if ($opts->{unit});
chomp($opts->{value}) if ($opts->{value});
$opts->{unit} = $self->strip_whitespace({ string => $opts->{unit} }) if ($opts->{unit});
$opts->{value} = $self->strip_whitespace({ string => $opts->{value} });
if ( $opts->{value} =~ /MB/i ) {
if ( $opts->{value} =~ /(\d+)/i ) {
return $1;
}
else {
$self->logger({ cat => 'c', msg => "convert_to_mb() got invalid value [$opts->{value}]" });
}
}
if ( $opts->{unit} =~ /kb|kib/i ) {
$size_mb = $opts->{value} / 1024;
}
elsif ( $opts->{unit} =~ /mb|mib/i ) {
$size_mb = $opts->{value};
}
elsif ( $opts->{unit} =~ /gb|gib/i ) {
$size_mb = $opts->{value} * 1024;
}
elsif ( $opts->{unit} =~ /tb|tib/i ) {
$size_mb = $opts->{value} * 1024 * 1024;
}
elsif ( $opts->{unit} =~ /block/i ) {
$size_mb = $opts->{value} * 512 / 1024 / 1024;
}
elsif ( $opts->{unit} =~ /b|bytes/i ) {
$size_mb = $opts->{value} / 1024 / 1024;
}
else {
$self->logger({ cat => 'w', msg => "convert_to_mb() unsupported unit [$opts->{unit}]" });
}
return $size_mb;
}
sub map_file_exists {
my $self = shift;
my $opts = shift;
unless ( $opts->{device} ) {
$self->logger({ cat => 'c', msg => "map_file_exists() not given device!" });
}
if ( -e "$Raider::Base::base_conf{'data_path'}/$opts->{device}.info" ) {
return 1;
}
else {
return 0;
}
}
sub find_ld_block_device {
my $self = shift;
my $opts = shift;
# This currently only applies to megaraidSAS and Adaptec modules. This is
# not needed for 3ware, because on a 3ware LD's block device the SMART info
# from smartctl -i actually tells us what LD# the block device belongs to.
# --ssullivan May 28th, 2013
if ( ! defined($opts->{icmd}) || ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{lds_array}) || ! defined($opts->{ld_num}) || ! defined($opts->{device}) ) {
$self->logger({ cat => 'c', msg => "find_ld_block_device() Missing required arguments!" });
}
my $block_device = 'unknown';
# Get block device list.
my $disc_block_devices = $self->get_block_devices();
my @controller_ld_block_devices = ();
# Find all LD's block devices that belong to controller.
for my $each_block_device ( @{ $disc_block_devices } ) {
# Get vendor from /sys/block now in case we need it later.
my $sys_block_vendor;
my $sys_block_vendor_file = "/sys/block/$each_block_device/device/vendor";
if ( -e $sys_block_vendor_file ) {
$sys_block_vendor = do {
local $/ = undef;
open my $fh, "<", $sys_block_vendor_file or $self->logger({ cat => 'c', msg => "Unable to open [$sys_block_vendor_file]: $!" });
<$fh>;
};
}
if ( $opts->{device} eq 'adaptec' ) {
for my $line ( split /^/, `smartctl -i /dev/$each_block_device` ) {
# Adaptec 7000 series controllers don't populate Adaptec as the
# vendor. Instead, they populate this field with the model. The sample
# model I saw was 'ASR7160', so i'm using ASR regex to attempt
# (admittedly hackishly) to catch more Adaptec controllers.
if ( $line =~ /(Device|Vendor)\s*:\s+(Adaptec|ASR\S+)/i ) {
# We found an Adaptec LD block device; add it.
push(@controller_ld_block_devices, $each_block_device);
last;
}
elsif ( $sys_block_vendor && $sys_block_vendor =~ /^Adaptec|^ASR/i ) {
push(@controller_ld_block_devices, $each_block_device);
last;
}
}
}
elsif ( $opts->{device} eq 'megaraidsas' ) {
for my $line ( split /^/, `smartctl -i /dev/$each_block_device` ) {
if ( $line =~ /(Device|Vendor)\s*:\s+LSI|megaraid|AVAGO/i ) {
# We found an LSI LD block device; add it.
push(@controller_ld_block_devices, $each_block_device);
last;
}
elsif ( $sys_block_vendor && $sys_block_vendor =~ /^LSI/i ) {
push(@controller_ld_block_devices, $each_block_device);
last;
}
}
}
else {
$self->logger({ cat => 'c', msg => "find_ld_block_device() Don't know what to do with device [$opts->{device}] !" });
}
}
# Filter out any LD's that do not belong to the given controller.
my $filtered_ld_block_devices_ref = $self->filter_ld_block_devices_by_controller({ ld_block_devices => \@controller_ld_block_devices, controller => $opts->{controller}, controllers => $opts->{controllers} });
my @filtered_ld_block_devices = @{ $filtered_ld_block_devices_ref };
my $lds_present = @filtered_ld_block_devices;
if ( $lds_present == 1 ) {
# One LD with a block device, so no need for further examination.
$block_device = "/dev/$filtered_ld_block_devices[0]";
$self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}]; only LD present with a block device." });
}
else {
# Two or more LD's with a block device found.
my $ld_size_match = 0;
my $numwrites_match = 0;
my $pci_pri_match = 0;
####################
## "Size match" ##
####################
# Get the size of $block_device , and each LD # on controller. If sizes are different, match based on size. If not, proceed to "numWrites".
# Our + || - range to match.
my $size_match_mb_range = '100';
my %ld_sizes = ();
my $match_comp = 0;
if ( $opts->{device} eq 'adaptec' ) {
for my $each_ld ( @{ $opts->{lds_array} } ) {
for my $line ( split /^/, `$opts->{icmd} getconfig $opts->{controller} LD $each_ld` ) {
if ( $line =~ /^\s+Size(\s+)?:\s+(\d+)\s+(\S+)/i ) {
$ld_sizes{ $each_ld } = $self->convert_to_mb({ unit => $3, value => $2 });
$match_comp = $ld_sizes{ $each_ld };
}
}
}
}
elsif ( $opts->{device} eq 'megaraidsas' ) {
for my $each_ld ( @{ $opts->{lds_array} } ) {
for my $line ( split /^/, `$opts->{icmd} -LDInfo -L$each_ld -a$opts->{controller}` ) {
if ( $line =~ /^Size(\s+)?:\s+(\S+)\s+(\S+)/i ) {
$ld_sizes{ $each_ld } = $self->convert_to_mb({ unit => $3, value => $2 });
$match_comp = $ld_sizes{ $each_ld };
}
}
}
}
# Get number of same values in hash.
my $num_matches_values_ld_sizes = grep {$_==$match_comp} values %ld_sizes;
# Get number of keys in hash.
my $num_keys_ld_sizes = keys(%ld_sizes);
# If same values in hash == number of keys in hash, all LD sizes are the same. Bail!
if ( $num_keys_ld_sizes == $num_matches_values_ld_sizes ) {
$self->logger({ cat => 'i', msg => "Two or more same size LD's detected. Moving on to numWrites algorithm..." });
}
else {
# Two or more same size LD's. Match on size.
for my $each_ld ( @{ $opts->{lds_array} } ) {
last if ( $ld_size_match );
# Define high and low range.
my $high_end = $ld_sizes{ $each_ld } + $size_match_mb_range;
my $low_end = $ld_sizes{ $each_ld } - $size_match_mb_range;
for my $controller_ld_block_device ( @filtered_ld_block_devices ) {
# Get size of block device as reported by OS.
my $os_block_device_size_file = "/sys/block/$controller_ld_block_device/size";
if ( ! -e $os_block_device_size_file ) {
$self->logger({ cat => 'c', msg => "find_ld_block_device() device size file [$os_block_device_size_file] does not exist!" });
}
my $block_device_size_os_level = 0;
my $block_device_size_os_level_MB = 0;
local $/ = undef;
open FILE, $os_block_device_size_file || $self->logger({ cat => 'c', msg => "Couldn't open file [$os_block_device_size_file]: $!"});
$block_device_size_os_level = <FILE>;
close FILE;
# Get size in MB.
$block_device_size_os_level_MB = $self->convert_to_mb({ unit => 'block', value => $block_device_size_os_level });
# If OS reported block device is within range of vendor tool reported size..
if ( grep {$_ == $ld_sizes{ $opts->{ld_num} }} $low_end..$high_end ) {
# If OS reported block device size (in MB) is within the allowed range of the vendor tool reported size (in MB), it's a match.
if ( grep {$_ == $block_device_size_os_level_MB} $low_end..$high_end ) {
# Found our match, update block_device.
$block_device = $controller_ld_block_device;
$ld_size_match = 1;
last;
}
}
}
}
}
if ( $ld_size_match ) {
$self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via LD Size." });
return $block_device;
}
####################
## "numWrites" ##
####################
# Check /sys/block/$block_device/stat , field 5. If it matches numwrites for the LD in the vendor tool, match based on this.
# Our + || - match range.
my $numwrites_range = '10';
my %lds_numwrites = ();
if ( $opts->{device} eq 'adaptec' ) {
for my $each_ld ( @{ $opts->{lds_array} } ) {
my $is_ld_status = 0;
my $is_ld_status_correct_ld = 0;
for my $line ( split /^/, `$opts->{icmd} getlogs $opts->{controller} stats tabular` ) {
if ( $line =~ /^\s+logicaldrivestats/i ) {
$is_ld_status = 1;
}
elsif ( $line =~ /^\s+interCmdTime/i ) {
$is_ld_status = 0;
$is_ld_status_correct_ld = 0;
}
if ( $is_ld_status ) {
if ( $line =~ /^\s+id\s+\S+\s+(\d+)/i ) {
my $disc_ld_writes = $1;
if ( $disc_ld_writes == $each_ld ) {
$is_ld_status_correct_ld = 1;
}
}
}
if ( $is_ld_status_correct_ld ) {
if ( $line =~ /^\s+numWrites\s+\S+\s+(\d+)/i ) {
$lds_numwrites{ $each_ld } = $1;
}
}
}
}
}
elsif ( $opts->{device} eq 'megaraidsas' ) {
# Unfortunately, MegaCLI doesn't have any machanism for showing numwrites on a LD.
}
# Determine which controller block_device is within our numWrites range.
for my $each_ld ( @{ $opts->{lds_array} } ) {
last if ( $opts->{device} eq 'megaraidsas' || $numwrites_match );
# Define high and low range.
my $high_end = $lds_numwrites{ $each_ld } + $numwrites_range;
my $low_end = $lds_numwrites{ $each_ld } - $numwrites_range;
for my $controller_ld_block_device ( @filtered_ld_block_devices ) {
# Get numwrites as reported by OS.
my $os_block_device_numwrites_file = "/sys/block/$controller_ld_block_device/stat";
my $os_block_device_numwrites_raw;
my $os_block_device_numwrites = 0;
if ( ! -e $os_block_device_numwrites_file ) {
$self->logger({ cat => 'c', msg => "find_ld_block_device() stat file [$os_block_device_numwrites_file] doesn't exist !" });
}
local $/ = undef;
open FILE, $os_block_device_numwrites_file || $self->logger({ cat => 'c', msg => "Couldn't open file [$os_block_device_numwrites_file]: $!"});
$os_block_device_numwrites_raw = <FILE>;
close FILE;
if ( $os_block_device_numwrites_raw =~ /^\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/ ) {
$os_block_device_numwrites = $1;
}
# See if numwrites as reported by OS is within range of vendor tool reported numwrites.
if ( $each_ld == $opts->{ld_num} ) {
if ( grep {$_ == $os_block_device_numwrites} $low_end..$high_end ) {
# Found our match, update block_device.
$block_device = $controller_ld_block_device;
$numwrites_match = 1;
last;
}
}
}
}
if ( $numwrites_match ) {
$self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via numWrites." });
return $block_device;
}
####################
## PCI "Priority" ##
####################
# Get the pci bus, channel, and location of all block devices that are of the same type as the controller. Aggregate those into an integer ("priority"), then
# compare the LD index to the "priority". The one with the lowest numeric "priority" is the lowest numbered LD etc.
my %pci_priority_map = ();
for my $each_ld_block_device ( @filtered_ld_block_devices ) {
# Get and assign PCI "priority" of block device.
$pci_priority_map{ $each_ld_block_device } = $self->get_pci_priority({ block_device => $each_ld_block_device });
}
# Sort pci priorities from lowest to highest.
my @sorted_block_devices = ();
foreach my $sorted_block_device ( sort {$pci_priority_map{$a} <=> $pci_priority_map{$b}} keys %pci_priority_map ) {
push(@sorted_block_devices, $sorted_block_device);
}
if ( defined($sorted_block_devices[$opts->{ld_num}]) ) {
$block_device = "/dev/$sorted_block_devices[$opts->{ld_num}]";
$pci_pri_match = 1;
}
else {
$self->logger({ cat => 'c', msg => "find_ld_block_device() Can't determine block device for ld_num [$opts->{ld_num}] !" });
}
if ( $pci_pri_match ) {
$self->logger({ cat => 'i', msg => "Matched [$block_device] to LD [$opts->{ld_num}] on controller [$opts->{controller}] via PCI Priority." });
return $block_device;
}
if ( ! $ld_size_match && ! $numwrites_match && ! $pci_pri_match ) {
$self->logger({ cat => 'c', msg => "Failed to find block device for LD [$opts->{ld_num}] with ld size match, numwrites, and PCI priority !" });
}
}
return $block_device;
}
sub get_pci_priority {
my $self = shift;
my $opts = shift;
if ( ! $opts->{block_device} ) {
$self->logger({ cat => 'c', msg => "get_pci_priority() not given block_device!" });
}
my $pci_priority = 0;
# Get all block devices.
my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();
for my $device_path ( keys %{ $all_bdev_ctrls } ) {
if ( $all_bdev_ctrls->{ $device_path } =~ /$opts->{block_device}/i ) {
my $pci_dev_str = $device_path;
# Remove non-numbers.
$pci_dev_str =~ s/[^\d+]//g;
# Add all numbers up, to determine pci_priority.
while ( $pci_dev_str =~ /(.)/g ) {
$pci_priority = $pci_priority + $1;
}
}
}
return $pci_priority;
}
sub filter_ld_block_devices_by_controller {
my $self = shift;
my $opts = shift;
if ( ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{ld_block_devices}) ) {
$self->logger({ cat => 'c', msg => "filter_ld_block_devices_by_controller() missing required args!" });
}
my $filtered_ld_block_devices = 0;
my $lds_by_controller = {};
# Get all block devices.
my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();
for my $each_block_device ( @{ $opts->{ld_block_devices} } ) {
# Find the entry that corresponds with each_block_device
for my $device_path ( keys %{ $all_bdev_ctrls } ) {
if ( $all_bdev_ctrls->{ $device_path } =~ /$each_block_device/i ) {
# Example, of the below, extracting [0000:00:02.0/0000:02:00.0/host0/target0]
# /sys/devices/pci0000:00/0000:00:02.0/0000:02:00.0/host0/target0:0:2/0:0:2:0/block
# /sys/devices/pci0000:00/0000:00:02.2/0000:04:00.0/host0/target0:2:0/0:2:0:0/block
# /sys/devices/pci0000:00/0000:00:03.0/0000:05:00.0/host1/target1:2:0/1:2:0:0/block
# /sys/devices/pci0000:00/0000:00:02.2/0000:04:00.0/host0/target0:2:1/0:2:1:0/block
# /sys/devices/pci0000:3a/0000:3a:00.0/0000:3b:00.0/host0/target0:2:0/0:2:0:0/block
if ( $device_path =~ /\/sys\/devices\/pci\d+:\S+?\/(\S+\/host\d+\/target\d+):/i ) {
my $pci_host_addr = $1;
$pci_host_addr =~ s/\D//g;
my $score = 0;
# Add all numbers up
while ( $pci_host_addr =~ /(.)/g ) {
$score = $score + $1;
}
push(@{ $lds_by_controller->{ $score } }, $each_block_device);
}
else {
$self->logger({ cat => 'c', msg => "device_path [$device_path] unknown format!" });
}
}
}
}
# Some controllers, like Adaptecs, number controllers starting at 1, not 0,
# like MegaraidSAS controllers do. Account for this by checking if the
# lowest numbered controller is 1 or 0.
my @sorted_controllers = sort { $a <=> $b } @{ $opts->{controllers} };
my $accessor;
if ( $sorted_controllers[0] == 0 ) {
$accessor = $opts->{controller};
}
elsif ( $sorted_controllers[0] == 1 ) {
$accessor = $opts->{controller} - 1;
$self->logger({
cat => 'i',
msg => "Lowest controller numbered 1, decremented passed controller to compensate."
});
}
else {
$self->logger({
cat => 'c',
msg => "Lowest numbered controller doesn't start with 0 or 1; I don't know how to handle this!"
});
}
# Now that our hashref is keyed up by PCI priority, we can select its block
# devices by controller number.
$filtered_ld_block_devices = $lds_by_controller->{ ( sort {$a <=> $b} keys %{ $lds_by_controller } )[$accessor] };
if ( ! $filtered_ld_block_devices ) {
$self->logger({ cat => 'c', msg => "filter_ld_block_devices_by_controller() Cannot find block devices for controller [$opts->{controller}] !" });
}
return $filtered_ld_block_devices;
}
sub isPvopsXenDom0 {
my $self = shift;
return 0 unless (-e '/sys/hypervisor/type');
open FILE, "</sys/hypervisor/type" or $self->logger({cat => 'c', msg => "Error opening /sys/hypervisor/type: $!"});
my $hypervisor_type = do { local $/; <FILE> };
close(FILE) or $self->logger({cat => 'c', msg => "Error closing /sys/hypervisor/type: $!"});
return 0 if ($hypervisor_type !~ /xen/);
open FILE, "</sys/hypervisor/uuid" or $self->logger({cat => 'c', msg => "Error opening /sys/hypervisor/uuid: $!"});
my $hyp_uuid = do { local $/; <FILE> };
close(FILE) or $self->logger({cat => 'c', msg => "Error closing /sys/hypervisor/uuid: $!"});
# uuid is always 00000000-0000-0000-0000-000000000000 if its a dom0
return 0 if ($hyp_uuid !~ /00000000-0000-0000-0000-000000000000/);
if (`modinfo xen_blkback 2>/dev/null` =~ /\/lib\/modules\//) {
$self->logger({cat => 'i', msg => "Xen pvops based dom0 detected, xen_blkback is loaded"});
return 1;
}
return 0;
}
sub get_sys_ctrls_with_bdevs {
my $self = shift;
my $opts = shift;
# pvops Xen dom0s do not populate a block file in /sys/devices..
if ($self->isPvopsXenDom0) {
# fudging data for now, since only use case of this is legacy xen zone and we dont need
# sonar.info to be strictly correct about this part on those.
my $fudged_data;
my $fudged_template_pci = '/sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:INCREMENT/block';
my $disc_block_devices = $self->get_block_devices();
my $device_cnt = 0;
for my $block_device (@{$disc_block_devices}) {
(my $my_pci_addr = $fudged_template_pci) =~ s/INCREMENT/$device_cnt/g;
$fudged_data->{ $my_pci_addr } = $block_device;
$device_cnt++;
}
return $fudged_data;
}
unless ( -d '/sys/devices' ) {
$self->logger({ cat => 'c', msg => "/sys/devices/ does not exist, cannot proceed!" });
}
# Xen (on Cent5 at least) shows devices like this:
# /sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:0/block:sda
# Where a non-Xen does this:
# /sys/devices/pci0000:00/0000:00:1c.0/0000:05:00.0/host0/target0:0:0/0:0:0:0/block
# Thus we get to look for both.
our @all_bdev_ctrls = ();
sub wanted_xen {
/^block:.*\z/s
&& push(@all_bdev_ctrls, $name);
}
sub wanted {
/^block\z/s
&& push(@all_bdev_ctrls, $name);
}
File::Find::find({
wanted => \&wanted_xen
},
'/sys/devices/'
);
File::Find::find({
wanted => \&wanted
},
'/sys/devices/'
);
my $data;
for my $device_path ( @all_bdev_ctrls ) {
if ( $device_path =~ /\/sys\/devices\/\S+\d+:\S+\/\d+:(\S+:\S+\.\d+)/ ) {
# On older distros like CentOS4 it be like this:
# /sys/devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block -> ../../../../../../block/sda
# On more modern ones it will be like this:
# root@host # ls -l /sys/devices/pci0000:00/0000:00:1f.2/host2/target2:0:0/2:0:0:0/block
# drwxr-xr-x 10 root root 0 Sep 10 13:30 sda
# root@host #
if ( -l $device_path ) {
# [root@host ~]# readlink -f /sys/devices/pci0000:00/0000:00:1f.2/host1/target1:0:0/1:0:0:0/block
# /sys/block/sda
# [root@host ~]#
my $readlinked = `readlink -f $device_path`;
if ( $readlinked =~ /\/sys\/block\/(\S+)/i ) {
$data->{ $device_path } = $1;
}
else {
$self->logger({ cat => 'c', msg => "Device [$device_path] readlinks into unknown format!" });
}
}
elsif ( -d $device_path ) {
my @device_path_entries = glob("$device_path/*");
if ( defined($device_path_entries[0]) ) {
unless ( -e $device_path_entries[0] ) {
$self->logger({ cat => 'c', msg => "Missing [$device_path_entries[0]]" });
}
my($dev_short) = $device_path_entries[0] =~ /.*\/(.*)/;
my $device_tmp = "/dev/$dev_short";
unless ( -e $device_tmp ) {
$self->logger({ cat => 'c', msg => "Extracted device [$device_tmp] non-existent!" });
}
$data->{ $device_path } = $dev_short;
}
else {
$self->logger({ cat => 'c', msg => "No entries for [$device_path]!" });
}
}
else {
$self->logger({ cat => 'c', msg => "Device [$device_path] is of unknown type!" });
}
}
else {
$self->logger({ cat => 'i', msg => "Skipping non-physical device [$device_path]" });
}
}
return $data;
}
sub find_suitable_block_device {
my $self = shift;
my $opts = shift;
if ( ! defined($opts->{controller}) || ! defined($opts->{controllers}) || ! defined($opts->{candidate_block_devs}) ) {
$self->logger({ cat => 'c', msg => "find_suitable_block_device() Missing required args!" });
}
my $suitable_block_device;
my @block_device_candidates = @{ $opts->{candidate_block_devs} };
# Determine if any LDs are on different controllers.
my %block_devices_by_pcihost = ();
my $pcihost_id = 0;
my $all_bdev_ctrls = $self->get_sys_ctrls_with_bdevs();
for my $each_cand_block_device ( @block_device_candidates ) {
if ( $each_cand_block_device =~ /\/dev\/(\S+)/ ) {
$each_cand_block_device = $1;
}
for my $device_path ( keys %{ $all_bdev_ctrls } ) {
if ( $all_bdev_ctrls->{ $device_path } =~ /$each_cand_block_device/i ) {
# /sys/devices/pci0000:00/0000:00:1f.2/host2/target2:0:0/2:0:0:0/block
# /sys/devices/pci0000:00/0000:00:02.0/0000:02:00.0/host0/target0:0:2/0:0:2:0/block
if ( $device_path =~ /\/sys\/devices\/pci\d+:\d+\/(\S+\/host\d+\/target\d+):/i ) {
my $pcihost = $1;
$pcihost =~ s/\D//g;
# Add pcihost numbers up.
my @chars = $pcihost =~ /./sg;
$pcihost_id = 0;
for my $num ( @chars ) {
$pcihost_id = $pcihost_id + $num;
}
$block_devices_by_pcihost{ $pcihost_id } = $each_cand_block_device;
}
}
}
}
# If there is more than one key, there is more than one controller.
my $key_count = keys %block_devices_by_pcihost;
if ( $key_count == 1 ) {
$suitable_block_device = $block_devices_by_pcihost{ $pcihost_id };
}
else {
# The lowest numbered key is the lowest numbered controller.
# Sort our controllers array from lowest to highest.
my @low_to_high_cntrls = sort { $a <=> $b } @{ $opts->{controllers} };
my $passed_controller_score = 0;
for my $sorted_cntrl ( @low_to_high_cntrls ) {
$passed_controller_score++;
last if ( $sorted_cntrl == $opts->{controller} );
}
my $loop_pass_sorted_devs_pcihost = 1;
for my $key ( sort { $a <=> $b } keys %block_devices_by_pcihost ) {
if ( $loop_pass_sorted_devs_pcihost == $passed_controller_score ) {
$suitable_block_device = $block_devices_by_pcihost{ $key };
}
$loop_pass_sorted_devs_pcihost++;
}
unless ( defined($suitable_block_device) ) {
$self->logger({
cat => 'c',
msg => "find_suitable_block_device() Unable to determine suitable block device for controller [$opts->{controller}]."
});
}
}
return $suitable_block_device;
}
sub get_device_id {
my $self = shift;
my $opts = shift;
my @x = split (/\//, $opts->{device_path});
if ( $x[4] =~ /^\d+:(\S+)/ ) {
return $1;
}
else {
$self->logger({
cat => 'c',
msg => "Unable to determine device ID from device path [$opts->{device_path}]"
});
}
}
1;