View file File name : discover_lsi_raid.pl Content :#!/usr/bin/perl # Low level discovery of LSI RAID devices. # # There are multiple CLI tools that each work with some LSI RAID models and # chipsets. The LLD macro for the adapter is used to also pass the info about # which CLI command to use with the item prototypes. Not all macros are used or # returned for all types of hardware, depending on the limitations of the CLI # tool used. # # adapter, megacli = {#ADP_MEGA} # adapter, mpt-status = {#ADP_MPT} # adapter, sas2ircu = {#ADP_SAS2} # bbu = {#BBU} # enclosure = {#ENCLOSURE} # virtual drive = {#VIRTDRIVE} # physical drive = {#SLOT} # # LSI RAID devices are all either MegaRAID with PCI connections or devices # using SCSI. MegaRAID devices use megacli (the 'mega' name is a hint). # Non-MegaRAID devices use one of two other CLIs depending on the chipset # series: mpt-status for chipsets under 2000, and sas2ircu for all the more # recent ones. # # References used: # https://www.denniskanbier.nl/blog/monitoring/monitoring-disk-io-using-zabbix/ # https://zabbix.org/wiki/Docs/howto/Nested_LLD use strict; use warnings; use Getopt::Long; my $debug = 0; sub exit_empty { print '{ "data": [] }'; exit; } # Default action is to do any of the CLIs that are applicable to the hardware. my $return_only = "ANY"; GetOptions( "cli=s" => \$return_only, ); unless (($return_only eq "ANY") || ($return_only eq 'megacli') || ($return_only eq 'mpt-status') || ($return_only eq 'sas2ircu')) { warn "Unexpected CLI name passed in, bailing."; exit_empty(); } # Look for LSI devices. Expected output examples: # 02:00.0 RAID bus controller: Broadcom / LSI MegaRAID SAS 2108 [Liberator] (rev 05) # 02:00.0 SCSI storage controller: Broadcom / LSI SAS1068E PCI-Express Fusion-MPT SAS (rev 08) # 02:00.0 SCSI storage controller: LSI Logic / Symbios Logic SAS1068E PCI-Express Fusion-MPT SAS (rev 08) # 03:00.0 RAID bus controller: LSI Logic / Symbios Logic MegaRAID SAS 2108 [Liberator] (rev 05) my $found_devices= `/usr/bin/lspci | /bin/grep "LSI " | /bin/grep -v PATA`; # Bail if nothing found exit_empty() unless $found_devices; # Start making the json struture to return my $json = qq({ "data": [\n); # Determine which CLI(s) can be used to get component info, based on the # device(s) found. If there happen to be two devices that both use the same # CLI, we're going to only use the CLI once and let it report the duplicates # since that's easier. my $cli; # track which CLIs to use in here for my $device (split /\n/, $found_devices) { if ($device =~ /megaraid/i) { $cli->{"megacli"} = 1; } else { # Pull out the chipset integer from the device info. # Strip off everything before the token with the chipset info $device =~ s/^.*Logic //; # Remove any SAS prefix $device =~ s/^SAS ?//; # Capture the chipset number and discard the rest. $device =~ s/^(\d+)\D+.*$/$1/; if ($device >= 2000) { $cli->{"sas2ircu"} = 1; } else { $cli->{"mpt-status"} = 1; } } } # Use the appropriate CLI tool(s) to build the json rows for the discovered # components. if ($cli->{"megacli"} && ($return_only eq "megacli" || $return_only eq "ANY")) { print "Using CLI megacli\n" if $debug; megacli(); } if ($cli->{"mpt-status"} && ($return_only eq "mpt-status" || $return_only eq "ANY")) { print "Using CLI mpt-status\n" if $debug; mpt_status(); } if ($cli->{"sas2ircu"} && ($return_only eq "sas2ircu" || $return_only eq "ANY")) { print "Using CLI sas2ircu\n" if $debug; sas2ircu(); } # trim the last ',' off, json doesn't like trailing commas $json =~ s/,\n$/\n/; # close up shop $json .= qq(]}\n); print $json; exit; sub megacli { # Find the adapter(s) my $adapter_count = `/usr/sbin/megacli -AdpCount -NoLog| /bin/grep "Controller Count" | /usr/bin/awk '{print \$NF}'`; chomp $adapter_count; $adapter_count =~ s/\.//; # strip off trailing '.' from number print "There's $adapter_count adapters here.\n" if $debug; for (my $adapter_num = 0; $adapter_num < $adapter_count; $adapter_num++) { print "Adapter $adapter_num up now.\n" if $debug; $json .= qq( { "{#ADP_MEGA}":"$adapter_num" },\n); my $adapter_info_cmd = "/usr/sbin/megacli -AdpAllInfo -a$adapter_num -NoLog"; # This Adapter may have a BBU my $bbu_are_you_there = `$adapter_info_cmd | /bin/grep -A10 "HW Configuration" | /bin/grep "^BBU " | /usr/bin/awk '{print \$NF}'`; chomp $bbu_are_you_there; if ($bbu_are_you_there eq 'Present') { print "\tBBU is present.\n" if $debug; # We're not going to add BBU as a discovered item just yet. There's # more BBU logic down where physical drives are found. } # This Adapter may have Virtual Drives, numbered 0..N my $virtual_drive_count = `$adapter_info_cmd | /bin/grep -A10 "Device Present" | /bin/grep "Virtual Drives" | /usr/bin/awk '{print \$NF}'`; chomp $virtual_drive_count; print "\tVirtual drives on this adapter: $virtual_drive_count\n" if $debug; for (my $virt_drive_num = 0; $virt_drive_num < $virtual_drive_count; $virt_drive_num++) { $json .= qq( { "{#ADP_MEGA}":"$adapter_num", "{#VIRTDRIVE}":"$virt_drive_num"},\n); } # Each Adapter can have one or more Enclosures my $enclosure_info_cmd = "/usr/sbin/megacli -EncInfo -a$adapter_num -NoLog"; my $enclosure_info = `$enclosure_info_cmd`; my @lines = split /\n/, $enclosure_info; # Find the number of enclosures while ($lines[0] !~ m/Number of enclosures on adapter $adapter_num/) { shift @lines; if (scalar @lines == 0) { # Fake a result of zero $lines[0] = "Number of enclosures on adapter 0 -- 0"; last; } } (my $enclosure_count = $lines[0]) =~ s/.*-- //; print "\tFound $enclosure_count enclosures " if $debug; # Find the IDs of each enclosure my @enclosure_ids; for my $line (@lines) { next unless $line =~ /Device ID/; $line =~ s/.*: //; push @enclosure_ids, $line; } if ($debug) { for my $id (@enclosure_ids) { print " >$id< "; } print "\n"; } # Doublecheck we found the right number of enclosure IDs if ((scalar @enclosure_ids) != $enclosure_count) { printf "Found %s enclosure IDS for %s enclosures, WTH??\n", scalar @enclosure_ids, $enclosure_count if $debug; } for my $ID (@enclosure_ids) { $json .= qq( { "{#ADP_MEGA}":"$adapter_num", "{#ENCLOSURE}":"$ID"},\n); } # Each Adapter&Enclosure has multiple Physical Drives for my $enclosure_num (@enclosure_ids) { # Grab info on which slots have drives. my $physical_drive_slot_list = `/usr/sbin/megacli -PDList -a0 -NoLog|/bin/grep "Slot Number"|/usr/bin/awk '{print \$NF}'`; my @slots = split /\n/, $physical_drive_slot_list; print "\tAdapter $adapter_num enclosure $enclosure_num has drives in slots: " if $debug; my %drive_types; for my $slot (@slots) { print "$slot " if $debug; $json .= qq( { "{#ADP_MEGA}":"$adapter_num", "{#ENCLOSURE}":"$enclosure_num", "{#SLOT}":"$slot"},\n); # Check what type of drive this is (ex SSD, spinning platter, etc) my $drive_type = `sudo /usr/sbin/megacli -PDInfo -PhysDrv [$enclosure_num:$slot] -a$adapter_num -NoLog|/bin/grep "Media Type"|cut -d " " -f 3-`; chomp $drive_type; $drive_types{$drive_type} = 1; } print "\n"; if ($bbu_are_you_there eq 'Present') { # Determine if the BBU is actually needed. If all the drives # are SSD, they have their own batteries, so the BBU on the # RAID card is unnecessary and doesn't need monitoring. delete $drive_types{"Solid State Device"}; if (keys %drive_types > 0) { # There's only one BBU, it doesn't have an id or anything, so passing 0 # is just a placeholder $json .= qq( { "{#ADP_MEGA}":"$adapter_num", "{#BBU}":"0"},\n); if ($debug) { print "\tBBU is needed! Found non-SSD drive type(s) "; for my $type (keys %drive_types) { print "\"$type\" "; } print "\n"; } } } } } } sub mpt_status { my $component_data = `/usr/sbin/mpt-status`; for my $component (split /\n/, $component_data) { my @component = split / /, $component; # Get the 'adapter', really the SCSI ID #. Usually 0. my $adapter = $component[0]; $adapter =~ s/^ioc//; if ($component[1] eq 'vol_id') { print "Found vol_id $component[2].\n" if $debug; $json .= qq( { "{#ADP_MPT}":"$adapter", "{#VIRTDRIVE}":"$component[2]" },\n); } elsif ($component[1] eq 'phy') { print "Found physical drive $component[2].\n" if $debug; $json .= qq( { "{#ADP_MPT}":"$adapter", "{#SLOT}":"$component[2]" },\n); } } } sub sas2ircu { # Get the adapter info. The output is a nice looking table, designed for # human eyes, awkward to work with programatically. my $adapter_list = `/usr/sbin/sas2ircu list | /bin/grep -A10 'Index'`; my @adapter_lines = split /\n/, $adapter_list; # Toss the first two lines, they hold the column headers. shift @adapter_lines; shift @adapter_lines; my @adapter_ids; for my $line (@adapter_lines) { # Skip the final output line, its not an adapter. last if $line =~ /Completed Successfully/; # Grab just the index id number and save it. $line =~ s/^\s+(\d+)\s+.*$/$1/; push @adapter_ids, $line; } for my $adapter (@adapter_ids) { print "Adapter $adapter up now.\n" if $debug; # This CLI does not seem to report any status of the adapter itself, # but add it to the json as a unique item anyway. $json .= qq( { "{#ADP_SAS2}":"$adapter" },\n); # Grab *all* the info for all the devices on this adapter. Unlike # MegaCLI, there's no command line options or flags that can be used to # request a subset of that info, its all or nothing with sas2ircu. For # that reason, we're not bothering with any kind of grep as part of # this command. We'll do all our own parsing here in perl, examining # each line in turn. my $device_info = `/usr/sbin/sas2ircu $adapter display`; my @device_lines = split /\n/, $device_info; # Virtual Drive info is listed first, as 'IR Volume' # Work our way down to that section. while (scalar @device_lines > 0) { my $line = shift @device_lines; if ($line eq "IR Volume information") { shift @device_lines; # the '---' line last; } } # Look for volume numbers, or the section divider. while (scalar @device_lines > 0) { my $line = shift @device_lines; # Look out for the bottom of the virtual drive section. last if $line =~ /---/; if ($line =~ /IR volume/) { # Pull out the number and add it to the json. $line =~ s/^.* (\d+)$/$1/; print "\tVirtual Drive $line\n" if $debug; $json .= qq( { "{#ADP_SAS2}":"$adapter", "{#VIRTDRIVE}":"$line"},\n); } } # Physical Drive Info # Work our way down to that section. while (scalar @device_lines > 0) { my $line = shift @device_lines; if ($line eq "Physical device information") { shift @device_lines; # the '---' line last; } } # Look for the enclosure & slot #s for each physical drive. while (scalar @device_lines > 0) { my $line = shift @device_lines; # Look out for the bottom of the physical drive section. last if $line =~ /---/; if ($line =~ /Device is a Hard disk/) { # Get the location of the drive, the enclosure & slot #s. my $enclosure = shift @device_lines; $enclosure =~ s/^.*: (\d+)$/$1/; my $slot = shift @device_lines; $slot =~ s/^.*: (\d+)$/$1/; print "\tPhysical drive found in slot $slot in enclosure $enclosure\n" if $debug; $json .= qq( { "{#ADP_SAS2}":"$adapter", "{#ENCLOSURE}":"$enclosure", "{#SLOT}":"$slot"},\n); } } # Enclosure Info # This CLI does not seem to report any status of the enclosures, # but add it to the json as a unique item anyway. # It is odd to be doing the enclosure as a separate device after doing # the physical drives (which are in an enclosure) but that's the order # the output is in so that's the order we process it. # Work our way down to that section. while (scalar @device_lines > 0) { my $line = shift @device_lines; if ($line eq "Enclosure information") { shift @device_lines; # the '---' line last; } } # Look for the enclosure #s. while (scalar @device_lines > 0) { my $line = shift @device_lines; # Look out for the bottom of the enclosures section. last if $line =~ /---/; # Yes there is really no space there, unlike how it is for the # physical drives. I guess that diference is intentional. if ($line =~ /Enclosure#/) { $line =~ s/^.*: (\d+)$/$1/; $json .= qq( { "{#ADP_SAS2}":"$adapter", "{#ENCLOSURE}":"$line"},\n); } } } }