#!/opt/vdops/bin/perl # This script queries MIB-Dell-10892 and MIB-Dell-CM variables to produce # a report summarizing firmware (and driver) versions # V Who When What # --------------------------------------------------------------------------- # 1.1.0 skendric 2010-01-23 Upgrade to perl 5.10.1 # 1.0.7 skendric 2009-04-07 Trim white space from fields # 1.0.6 skendric 2009-04-03 Handle model numbers containing letters # 1.0.5 skendric 2009-03-25 Sort by model and OS # 1.0.4 skendric 2009-03-25 More bugs # 1.0.3 skendric 2009-03-25 Bugs # 1.0.2 skendric 2009-03-25 Sort output in varying ways # 1.0.1 skendric 2009-03-24 Sort output by model and OS # 1.0.0 skendric 2009-03-17 First Version # # Author: Stuart Kendrick, sbk {put at sign here} skendric {put dot here} com # # Source: http://www.skendric.com/device # # This software is available under the GNU GENERAL PUBLIC LICENSE, see # http://www.fsf.org/licenses/gpl.html # # # This script takes the following approach: # -Parses the hosts table for a list of targets (or accepts a command- # line list) # -Queries a bunch of Dell-specific variables # -Produces a report # # # Requirements: # -The target(s) must be pingable # # -PERL modules: the FHCRC::Netops collection # # # Assumptions: # # # Tested on: # -PowerEdge 6650 # -perl-5.10.1 # -net-snmp-5.5 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Type "dell-firmware" to see the command-line options # -Try it out # # # # Caveats: # # # Known Bugs: # # # To do: # # Begin script # Load modules use strict; use warnings; use feature 'say'; use feature 'switch'; use Carp qw(carp cluck croak confess); use Data::Dumper; use English qw( -no_match_vars ); use Getopt::Std; use List::MoreUtils qw(uniq); use Text::Trim; use FHCRC::Netops::DellTools 1.0.0; use FHCRC::Netops::HostTools 1.0.3; use FHCRC::Netops::NetopsTools 2.0.7; use FHCRC::Netops::NetopsData 1.3.0; use FHCRC::Netops::PingTools 1.1.5; use FHCRC::Netops::SNMPTools 1.3.9; use FHCRC::Netops::Utilities 1.3.9; # Declare global variables my %systemManagementSoftwareVersionNumberName; my %chassisServiceTagName; my %chassisModelName; my %applicationDeviceIndex; my %applicationComponentType; my %applicationVersion; my %applicationDisplayString; my %applicationDisplayIid; # Inverse of %applicationDisplayString my %controller_firmware_fresh; # Boolean identifying whether or not the # controller firwmare meets minimum requirements # Define global variables $debug = 0; # 10 = Logging # 9 = Database SELECT operations # 8 = Per IP/MAC/Port processing # 7 = Database INSERT/UPDATE/DELETE # 6 = Dump SNMP var # 5 = Dump snmp_packets # 4 = Grody: print big var # 3 = Verbose: print mid var # 2 = Simple: print small var # 1 = Basic: subroutine trace # 0 = Disable debugging $program_name = 'dell-firmware'; $usage = 'Usage: dell-firmware -s yes|no [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.0.7'; # Define user-configurable variables # Binaries $grab_hosts = '/bin/cat /etc/hosts'; # Report stuff $institution = 'Widgets International'; $report_file = '/home/netops/rpts/dell-firmware.html'; $report_queries = 'bsmith@widgets.com'; $report_subject = 'Dell Firmware Report'; $report_url = 'http://nettools.fhcrc.org/reports/dell-firmware.html'; # Pause parameters $long = 30; $mid = 10; $short = 5; # Ping Stuff $ping_count = 3; $ping_timeout = 1; # SNMP Stuff # Optimize performance by sorting your community strings and SNMP version # list, most frequently used to the left, least frequently used to the right @snmp_read_list = qw/public public/; @snmp_version_list = qw/2/; $snmp_port = 161; $snmp_retries = 3; $snmp_timeout = 3000000; # I don't understand why the Dell agent barfs when max_repetitions is set high # (typical Netops default is 1000). But it does. So I set it to 10 $snmp_max_rep = 10; # Syslog stuff $syslog_facility = 'local5'; $syslog_host = 'localhost'; $syslog_port = 514; $syslog_priority = 'info'; $syslog_socket = 'unix'; # Other possibilites include 'udp' and # 'stream'; depending on the flavor of Unix, # I've employed each of these # Target details @skip_name = qw/example swamp test/; @suffixes = qw//; # Grab arguments getopts('ad:e:f:rs:', \%option); @target = @ARGV; # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } ### Begin Main Program ############################################### { check_args(); # Check arguments compile_mibs(); # Compile MIB files build_target(); # Populate @target target_check(); # Look for errors in @target basic_info(); # Gather information sanity_check(); # Check for major errors do_the_work(); # Do the work identify_alarms(); # Count devices with stale firmware print_report(); # Print report } ##### End Main Program ############################################### ######################################################################## # Build the %device hash ######################################################################## sub build_family { my $target = shift; my %hash; # Debug trace trace_location('begin') if $debug > 3; # Debug trace trace_location('end') if $debug > 3; return \%hash; } ######################################################################## # Do the work ######################################################################## sub do_the_work { # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Querying targets...'); unless ($dome) { sleep $short; return 1; } # Loop through targets for my $target (@target) { my (%arg, $val, $vb); # Debug info say "Processing $target" if $debug; # Acquire systemManagementSoftwareVersionNumberName say 'Getting systemManagementSoftwareVersionNumberName.1' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10892.1.100.2.0'); $val = snmpGet(\%arg); $systemManagementSoftwareVersionNumberName{$target} = $val; undef $val; # Acquire chassisServiceTagName say 'Getting chassisServiceTagName.1' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10892.1.300.10.1.11.1'); $val = snmpGet(\%arg); $chassisServiceTagName{$target} = $val; undef $val; # Acquire chassisModelName say 'Getting chassisModelName.1' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10892.1.300.10.1.9.1'); $val = snmpGet(\%arg); ($val = trim $val) =~ tr/ / /s; # Strip spaces ($val = $val) =~ s/"//g; $chassisModelName{$target} = $val; undef $val; # Acquire applicationDeviceIndex say 'Walking applicationDeviceIndex' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10899.1.6.1.2'); $vb = snmpWalk(\%arg); for my $varbind (@$vb) { my ($iid, $val); $iid = $varbind->{iid}; $val = $varbind->{val}; ($val = $val) =~ s/"//g; $applicationDeviceIndex{$target}->{$iid} = $val; say " index $iid = $val" if $debug > 2; } undef $vb; # Acquire applicationComponentType say 'Walking applicationComponentType' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10899.1.6.1.3'); $vb = snmpWalk(\%arg); for my $varbind (@$vb) { my ($iid, $val); $iid = $varbind->{iid}; $val = $varbind->{val}; ($val = $val) =~ s/"//g; $applicationComponentType{$target}->{$iid} = $val; say " index $iid = $val" if $debug > 2; } undef $vb; # Acquire applicationVersion say 'Walking applicationVersion' if $debug > 3; my %version; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10899.1.6.1.4'); $vb = snmpWalk(\%arg); for my $varbind (@$vb) { my ($iid, $val); $iid = $varbind->{iid}; $val = $varbind->{val}; ($val = $val) =~ s/"//g; $applicationVersion{$target}->{$iid} = $val; say " index $iid = $val" if $debug > 2; } undef $vb; # Acquire applicationDisplayString say 'Walking applicationDisplayString' if $debug > 3; %arg = (host => $target, oid => '.1.3.6.1.4.1.674.10899.1.6.1.5'); $vb = snmpWalk(\%arg); for my $varbind (@$vb) { my ($iid, $val); $iid = $varbind->{iid}; $val = $varbind->{val}; say " index $iid = $val" if $debug > 2; if ($val =~ /Intel/) { # Strip custom TEAM text ($val) = ($val) =~ /(Intel.*)/; } if ($val =~ /\(R\)/) { # Strip Registered symbol ($val = $val) =~ s/\(R\)//; } if ($val =~ /\(Microsoft Corporation\) /) { # Strip Microsoft text ($val = $val) =~ s/\(Microsoft Corporation\) //; } ($val = $val) =~ s/\s+/ /g; # Strip extra spaces ($val = $val) =~ s/"//g; # Strip quotes $applicationDisplayString{$target}->{$iid} = $val; $applicationDisplayIid{$target}->{$val}= $iid; } undef $vb; # Entertain operator print $BANG if $mode eq 'interactive'; } # Make things look pretty say "\n" if $mode eq 'interactive'; # Figure out whether or not controller firmware is stale for my $target (@target) { my $status = compare_dell_controller_firmware($target); $alarm_count{$target}++ if (defined $status and $status eq $QUERY); } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub print_report { my %device; # Hash of array refs listing targets, keyed by # OS flavor # %device = ( # 'PowerEdge 1650' => { # 'Linux' => ['family', 'genus', 'species'], # 'Windows' => ['bird', 'fish', 'mammal'] # }, # 'PowerEdge 1850' => { # 'Linux' => ['carbon', 'iron', 'nickel'], # 'Windows' => ['lion', 'tiger', 'bear'] # } # ); my $handle; my @models; # List of models underneath @target my $now = get_now(); my @os_flavors; # List of all OS flavors running on @target my $total = @target; # If we are running in test mode, skip this routine unless ($dome) { print_it("Running in test mode, cannot print a meaningful report\n"); return 1; } # Debug trace trace_location('begin') if $debug; # Direct output to file open $handle, '>', $report_file or die "Cannot open $report_file: $!\n"; print {$handle} < $report_subject
#
# Title:           $report_subject
#
# Institution:     $institution
#
# Date of Report:  $now
#
# Description:     This report lists Dell firmware and driver versions
#
# Active:          $total
#
# Stale Count:     $shit_happens
#
# Stale Firmware:  @alarm_list
#
# Questions:       If you have questions or comments regarding this
#                  report, please mail them to $report_queries
#
# Notes:           Entries in red indicate stale firmware
#
#
EOF # Build a list of OS flavors @os_flavors = uniq (values %os_flavor); say 'os_flavors = ', join $SPACE, sort @os_flavors if $debug; # Build a list of models @models = uniq (values %chassisModelName); say 'models = ', join ', ', sort @models if $debug; # Clump targets by model and OS flavor for my $target (@target) { push @{$device{$chassisModelName{$target}}->{$os_flavor{$target}}}, $target; } # Walk through devices for my $model (sort @models) { say "Processing model $model" if $debug; # Walk through OS flavors OS_FLAVOR: for my $os_flavor (sort @os_flavors) { my @ads; # Skip if we don't have any devices belonging to this model / OS # combination next OS_FLAVOR unless defined @{$device{$model}->{$os_flavor}}; # Extract list of devices matching this model / OS combination my @devices = @{$device{$model}->{$os_flavor}}; my $first_target = 1; say " Doing OS flavor $os_flavor" if $debug; # Develop list of applicationDisplayString column headings # for this model / OS combination for my $target (@devices) { my $ads_ref = $applicationDisplayString{$target}; for my $iid (sort keys %$ads_ref) { my $string = $ads_ref->{$iid}; push @ads, $string; } } @ads = uniq @ads; # Print title print {$handle} "
\n"; print {$handle} "

$model / $os_flavor

\n"; print {$handle} "\n"; # Walk through targets for this particular model / OS combination for my $target (sort @devices) { say " Focussing on target $target" if $debug; # Shrink hashes my $adi_ref = $applicationDeviceIndex{$target}; my $adiid_ref = $applicationDisplayIid{$target}; my $ads_ref = $applicationDisplayString{$target}; my $av_ref = $applicationVersion{$target}; # If this is the first target, print title and column headings if ($first_target) { # Print first three column headings print {$handle} " \n"; print {$handle} " \n"; print {$handle} " \n"; print {$handle} " \n"; # Print the applicatinDisplayString column headings for my $ads (sort @ads) { print {$handle} " \n"; } # Close the column headings row print {$handle} " \n"; # Don't do this again for this model / OS combination $first_target = 0; } # End 'If this is the first target ...' # Now print the data for this target print {$handle} " \n"; # Start new row print {$handle} " \n"; # Print target print {$handle} " \n"; print {$handle} " \n"; # Walk through the list of applicationDisplayString for this model / OS # combination for my $ads (sort @ads) { say " Handling $ads" if $debug > 3; # If this device knows about this applicationDisplayString, print it if (defined $adiid_ref->{$ads}) { my $index = $adiid_ref->{$ads}; # Highlight RAID controller firmware using color if ($ads_ref->{$index} =~ /RAID Controller.*Firmware/) { given ($controller_firmware_fresh{$target}) { when (0) { print {$handle} " \n"; } when (1) { print {$handle} " \n"; } default { print {$handle} " \n"; } } } # Otherwise, just print in black else { print {$handle} " \n"; } } # End 'If this device knows about this applicationDisplayString...' # Whereas, if the device does not know about this # applicationDisplayString, print a dash else { print {$handle} " \n"; # Empty cell } } # End 'Walk through the list of applicationDisplayString for this...' # Close this row print {$handle} " \n"; } # End 'Walk through targets for this particular model / OS combination' # Terminate table print {$handle} "
HostService TagMgmt$ads
$target$chassisServiceTagName{$target}$systemManagementSoftwareVersionNumberName{$target}$av_ref->{$index}<\/font>$av_ref->{$index}<\/font>$av_ref->{$index}$av_ref->{$index}$DASH
\n"; } # End 'Walk through OS flavors' } # End 'Walk through models' # Terminate HTML print {$handle} "\n"; print {$handle} "\n"; # Clean-up unless ($handle =~ /STDOUT/) { close $handle or warn "Cannot close $report_file: $!\n"; } # Notify operator log_it("Ending $PROGRAM_NAME"); say "Ending $PROGRAM_NAME" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Sanity check ######################################################################## sub sanity_check { my @remove; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Sanity check...'); # Loop through targets, removing non-Dell devices SANITY: for my $target (@target) { # Identify manufacturer unless ($manufacturer{$target} eq 'Dell') { say "\nManufacturer of $target is not Dell, ignoring" if $debug; push @remove, $target; next SANITY; } # Entertain operator print $BANG if $mode eq 'interactive'; } # Remove entries which failed checks prune_basic(@remove); # Make things look pretty say "\n" if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <