#!/opt/vdops/bin/perl # This script queries Catalyst devices running the CatOS for chassis and # module serial numbers and produces a report # V Who When What # --------------------------------------------------------------------------- # 1.5.0 skendric 02-21-2011 Upgrade to Netops 1.4.0 # 1.4.1 skendric 06-25-2010 Replace some OIDs with Object Values # 1.4.0 skendric 01-10-2010 Upgrade to perl 5.10.1 # 1.3.4 skendric 05-17-2009 Support SNMP.pm # 1.3.3 skendric 03-21-2007 Stylistic mods # 1.3.2 skendric 11-19-2006 Replace Object Values with OIDs # 1.3.1 skendric 11-05-2005 Upgrade to new WI::VDOPS module structure # 1.3.0 skendric 05-09-2005 Support Netops.pm-1.2 # 1.2.0 skendric 05-09-2004 Migrate common functions to Netops.pm # 1.1.0 skendric 04-30-2004 Enhance command-line options # 1.0.1 skendric 11-16-2003 Use Net::Ping::External # 1.0.0 skendric 11-04-2002 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 CatOS-specific variables # -Produces a report # # # Requirements: # -The target(s) must be pingable # # -The following MIB modules stashed in /opt/vdops/share/snmp/mibs, # or wherever it is that you store MIB modules: # CISCO-PRODUCTS-MIB.my # # -PERL modules: the WI::Netops collection # # # Assumptions: # # # Tested on: # -perl-5.12.2 # -net-snmp-5.6 # # # Instructions: # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Try it out # # # Caveats: # # # Known Bugs: # # # To do: # -Add support for SNMPv3 # # # 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 SNMP; use WI::Netops::CiscoTools 1.4.3; use WI::Netops::HostTools 1.0.4; use WI::Netops::NetopsTools 2.2.3; use WI::Netops::NetopsData 1.4.0; use WI::Netops::PingTools 1.1.7; use WI::Netops::SNMPTools 1.5.3; use WI::Netops::Utilities 1.4.4; # Declare global variables my %chassis_model; # Typically same as sysObjectID my %chassis_num_slots; # Number of slots in this chassis my %chassis_serial_number; my $module_mode; # Boolean telling us whether or not to # gather and print module serial numbers my %module_model; my %module_serial_number; my %module_type; # Translated using CISCO-STACK-MIB # Define global variables $program_name = 'catalyst-serial-num'; $usage = 'Usage: catalyst-serial-num -s {yes|no} [-d {integer}] [-r] [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '1.5.0'; # Module mode $module_mode = 'yes'; # Grab arguments getopts('ad:e:f:m:rs:', \%option); @target = @ARGV; given ($option{m}) { when (undef) { $module_mode = $module_mode } when ('yes') { $module_mode = 'yes' } when ('no') { $module_mode = 'no' } default { die "-m must be either yes or no\n" } } # Set mode if ($option{r}) { $mode = 'report' } elsif (-t STDIN) { $mode = 'interactive' } else { $mode = 'batch' } ### Begin Main Program ############################################### { check_args(); # Check arguments read_config(); # Read Netops config file compile_mibs(); # Compile MIB files build_target(); # Populate @target target_check(); # Look for errors in @target basic_info(); # Gather information sanity_check(); # Check for error conditions do_the_work(); # Do it print_report(); # Print report } ##### End Main Program ################################################# ######################################################################## # Use CatOS-specific variables to discover serial numbers ######################################################################## sub acquire_catos_serial_nums { my $host = shift; my $val; # Debug trace trace_location('begin') if $debug; # Acquire chassis_model $val = snmpGet( {host => $host, oid => 'chassis_model.0'} ); given ($val) { when (undef) { $chassis_model{$host} = $QUERY } when (/nosuch/i) { $chassis_model{$host} = $QUERY } default { $chassis_model{$host} = $val } } # Acquire chassis_serial_number $val = snmpGet( {host => $host, oid => 'chassis_serial_number.0'} ); given ($val) { when (undef) { $chassis_serial_number{$host} = $QUERY } when (/nosuch/i) { $chassis_serial_number{$host} = $QUERY } default { $chassis_serial_number{$host} = $val } } # If the operator wants us to gather module serial numbers, do it MODULE_MODE: if ($module_mode eq 'yes') { # Acquire chassis_num_slots $val = snmpGet( {host => $host, oid => 'chassis_num_slots.0'} ); given ($val) { when (undef) { $chassis_num_slots{$host} = $QUERY } when (/nosuch/i) { $chassis_num_slots{$host} = $QUERY } default { $chassis_num_slots{$host} = $val } } say "$host has $chassis_num_slots{$host} slots" if $debug > 2; last MODULE_MODE if $chassis_num_slots{$host} eq $QUERY; # Loop through slots SLOT: for (my $j = 1; $j <= $chassis_num_slots{$host}; $j++) { # Acquire module_model $val= snmpGet({host => $host, oid => "module_model.$j"} ); given ($val) { when (undef) { $module_model{$host}[$j] = $QUERY } when (/nosuch/i) { $module_model{$host}[$j] = $DASH } default { $module_model{$host}[$j] = $val } } # Acquire module_serial_number $val = snmpGet({host => $host, oid => "module_serial_number.$j"} ); given ($val) { when (undef) { $module_serial_number{$host}[$j] = $QUERY } when (/nosuch/i) { $module_serial_number{$host}[$j] = $DASH } default { $module_serial_number{$host}[$j] = $val } } # Acquire module_type $val = snmpGet( {host => $host, oid => "module_type.$j"} ); given ($val) { when (undef) { $module_type{$host}[$j] = $QUERY } when (/nosuch/i) { $module_type{$host}[$j] = $NA } default { $module_type{$host}[$j] = $val } } } # Terminate MODULE_MODE loop } # End 'if $module_mode' # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Use IOS-specific variables to discover serial numbers ######################################################################## sub acquire_ios_serial_nums { my @entPhysicalClass; # Tracks the iids of modules my $host = shift; my $val; # Debug trace trace_location('begin') if $debug; # Acquire entPhysicalModelName $val = snmpGet( {host => $host, oid => 'entPhysicalModelName.1'} ); given ($val) { when (undef) { $chassis_model{$host} = $QUERY } when (/nosuch/i) { $chassis_model{$host} = $QUERY } default { $chassis_model{$host} = $val } } # Acquire entPhysicalSerialNum $val = snmpGet( {host => $host, oid => 'entPhysicalSerialNum.1'} ); given ($val) { when (undef) { $chassis_serial_number{$host} = $QUERY } when (/nosuch/i) { $chassis_serial_number{$host} = $QUERY } default { $chassis_serial_number{$host} = $val } } # Gather module serial numbers MODULE_MODE: if ($module_mode eq 'yes') { my $vb; # Figure out how many slots this device has $val = snmpGet( {host => $host, oid => 'entPhysicalDescr.1'} ); $val //= $QUERY; ($chassis_num_slots{$host}) = ($val =~ /(\d+).slot/); # Walk entPhysicalClass, recording slots $vb = snmpWalk( {host => $host, oid => 'entPhysicalClass'} ); VARBIND: for my $varbind (@$vb) { # If this entity is a 'module' if ($varbind->{val} eq '9' or $varbind->{val} eq 'module') { my $iid = $varbind->{iid}; # If this entity looks like a card filling a slot say "Getting entPhysicalDescr.$iid" if $debug > 3; $val = snmpGet( {host => $host, oid => "entPhysicalDescr.$iid"} ); $val //= $QUERY; if ($val =~ /Supervisor|port/ or $val eq '10') { my $slot; # Find its slot number $val = snmpGet( {host => $host, oid => "entPhysicalName.$iid"} ); $val //= $QUERY; ($slot) = ($val =~ /(\d+)/); next VARBIND unless defined $slot; # Count this as a module and record its iid for future queries $chassis_num_slots{$host}++ unless defined $chassis_num_slots{$host}; $entPhysicalClass[$slot] = $iid; } # End 'if this entity looks like a card filling a slot' } # End 'if this entity looks like a module' } # End 'Walking entPhysicalClass, counting slots' # Perform house-keeping $chassis_num_slots{$host} = 0 unless defined $chassis_num_slots{$host}; last MODULE_MODE if $chassis_num_slots{$host} == 0; say "$host has $chassis_num_slots{$host} slots" if $debug > 2; # Loop through modules MODULE: for (my $j = 1; $j <= $chassis_num_slots{$host}; $j++) { my %arg; if (not defined $entPhysicalClass[$j]) { $module_model{$host}[$j] = $DASH; $module_serial_number{$host}[$j] = $DASH; next MODULE; } # Acquire entPhysicalModelName %arg = ( host => $host, oid => "entPhysicalModelName.$entPhysicalClass[$j]" ); $val = snmpGet(\%arg); given ($val) { when (undef) { $module_model{$host}[$j] = $QUERY } when (/nosuch/i) { $module_model{$host}[$j] = $NA } default { $module_model{$host}[$j] = $val } } # Acquire entPhysicalSerialNum %arg = ( host => $host, oid => "entPhysicalSerialNum.$entPhysicalClass[$j]" ); $val = snmpGet(\%arg); given ($val) { when (undef) { $module_serial_number{$host}[$j] = $QUERY } when (/nosuch/i) { $module_serial_number{$host}[$j] = $NA } default { $module_serial_number{$host}[$j] = $val } } } # Terminate module loop } # End 'if $module_mode' # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Query variables ######################################################################## sub do_the_work { my $val; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Querying targets...'); unless ($dome) { sleep $short; return 1; } # Loop through the list of targets for my $target (@target) { acquire_catos_serial_nums($target) if $os_flavor{$target} eq 'CatOS'; acquire_ios_serial_nums($target) if $os_flavor{$target} =~ /IOS/; print $BANG if $mode eq 'interactive'; } # Terminate target loop # Make things look pretty say('') if $mode eq 'interactive'; # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Translate CISCO-STACK-MIB module_type response to English ######################################################################## sub translate_module_type { my $english; # English translation of $type my $type = shift; # Numeric type code for module # Debug trace trace_location('begin') if $debug; # Sanity check carp 'No parameters!' unless defined $type; # Translate into English if (exists $INC{'SNMP.pm'}) { if (defined $type) { if ($english = SNMP::translateObj($type)) { # Success } else { $english = '?'; } } else { $english = '??'; } } # Debug trace trace_location('end') if $debug; return $english; } ######################################################################## # Tell the operator what I discovered ######################################################################## sub print_report { my $handle; my $j; # Iterates across modules my $total = @target; my $now = get_now(); # 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 screen or to file if ($mode eq 'interactive') { $handle = *STDOUT; } else { open $handle, '>', $report_file or die "Cannot open $report_file: $!\n"; } print {$handle} <