#!/opt/vdops/bin/perl # This script automates the process of uploading a config file to an AP96xx # V Who When What # --------------------------------------------------------------------------- # 2.1.0 skendric 02-21-2011 Upgrade to Netops 1.4.0 # 2.0.3 skendric 06-25-2010 Allow $error{$target} to be undef # 2.0.2 skendric 06-17-2010 Single SET, fixed snmpSet to handle clumps # 2.0.1 skendric 06-15-2010 Send SETs one at a time, since clumping them # doesn't seem to work # 2.0.0 skendric 03-30-2010 Upgrade to perl 5.10.1 # 1.9.0 skendric 12-23-2009 Remove Net::SNMP support # 1.8.4 skendric 04-29-2009 Correct OID when setting tftp action # 1.8.3 skendric 12-30-2008 Support new snmpSet format # 1.8.2 skendric 04-18-2008 Handle tftp transfer result of 0 # 1.8.1 skendric 03-27-2008 Handle TFTP errors more robustly # 1.8.0 skendric 03-06-2008 Handle AP9630 cards # 1.7.9 skendric 10-22-2007 Refine detection of AP961x cards # 1.7.8 skendric 09-17-2007 More error checking # 1.7.7 skendric 08-17-2007 Cosmetic changes to output # 1.7.6 skendric 04-11-2007 Handle undefined status code from AP961x # in response to TFTP upload # 1.7.5 skendric 03-21-2007 Stylistic mods # 1.7.4 skendric 02-27-2007 For mfiletransferConfigTFTPServerAddress, # change type from IPADDR to OCTET_STRING # 1.7.3 skendric 11-16-2006 Replace Object Values with OIDs # 1.7.0 skendric 09-22-2006 Use TFTP instead of FTP # 1.6.3 skendric 09-21-2006 Support new ftp_file and new command-line args # 1.6.2 skendric 08-04-2006 More error checks # 1.6.1 skendric 11-05-2005 Upgrade to new WI::VDOPS module structure # 1.6.0 skendric 10-10-2005 Support AP961x+ cards # 1.5.0 skendric 05-09-2005 Support Netops.pm-1.2 # 1.4.0 skendric 05-10-2004 Migrate common functions to Netops.pm # 1.3.0 skendric 04-30-2004 Enhance command-line options # 1.2.4 skendric 02-04-2004 Debugging fix # 1.2.3 skendric 01-07-2004 Fix bug in poll routine # 1.2.2 skendric 11-16-2003 Use Net::Ping::External # 1.2.1 skendric 08-10-2003 Minor bug fixes # 1.2.0 skendric 03-16-2003 Tighted scoping, changed $debug to $debug # 1.1.0 skendric 11-17-2002 Added @version, improved build_target() # 1.0.0 skendric 10-21-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: # -Upload the new config file via ftp # # 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: # PowerNet-MIB # # -PERL modules: the WI::Netops collection # # # # Assumptions: # # # Tested on: # -APC 9606 Web/SNMP card, AP961x + AP963x Network Management Card # -perl-5.12.2 # -net-snmp-5.6 # # # Instructions: # -Create an AP960x config file using the i2c301.exe utility # (see ftp://ftp.apcc.com/apc/public/hardware/webcard/firmware/sumx/ # v321/addendum.pdf for instructions. See ../webcard/i2c for the # utility itself.) # -Or, create an AP961x/AP963x config file, which is simply a text file # following a standard format and typically named 'config.ini' # -Customize the script for your site: find the 'user-configurable # variables' section and modify as appropriate # -Type "apc-mod-config" 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 feature 'switch'; use Carp qw(carp cluck croak confess); use Data::Dumper; use English qw( -no_match_vars ); use File::Copy 'cp'; use File::Util; use Getopt::Std; use WI::Netops::APCTools 1.2.2; 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 $config_file; # Name of config file to upload my %xfer_result; # 'Success, Failure, or Unknown', keyed by target # Define global variables $program_name = 'apc-mod-config'; $usage = 'Usage: apc-mod-config -s {yes|no} [-d {integer}] -c {filename} [-a | -e {expr} | -f {filename} | target1 target2 target3 ...]'; $version = '2.1.0'; # TFTP Stuff $tftp_server = $EMPTY_STR; $tftp_server = get_my_ipaddr() if $tftp_server eq $EMPTY_STR; $tftp_server_name = get_nodename($tftp_server); # Grab arguments getopts('ad:c:e::f:s:', \%option); @target = @ARGV; # Check arguments $config_file = $option{c}; die 'Must specify -c config_file' unless defined $config_file; # 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(); # Check for major errors sanity_check(); # Check for major errors print_before(); # Tell operator what I will do do_the_work(); # Upload config file info_after(); # Gather information print_after(); # Tell the operator what I did } ##### End Main Program ############################################### ######################################################################## # Do the work: upload images ######################################################################## sub do_the_work { my $srcfile; # Debug trace trace_location('begin') if $debug; # Define local variables $srcfile = "$tftp_path/$config_file"; # Loop through @target, uploading config file for my $target (@target) { my ($val, @varbind); # Announce start say '-------------------------------------------------------' if $mode eq 'interactive'; print_it("Beginning to process $target"); # Set TFTP Server address in target push @varbind, 'mfiletransferConfigTFTPServerAddress', 0, $tftp_server, 'OCTET_STRING'; # Set TFTP file path in target push @varbind, 'mfiletransferConfigSettingsFilename', 0, $srcfile, 'OCTET_STRING'; # Upload config file push @varbind, 'mfiletransferControlInitiateFileTransfer', 0, 2, 'INTEGER'; if ($dome) { say "Uploading $srcfile to $target" if $debug; $val = snmpSet( {host => $target, varbind => \@varbind} ); } else { say "Pretending to upload $srcfile to $target" if $debug; } # Give the agent time to perform the upload sleep $short; # Get the result of the upload my %arg; %arg = (host => $target, oid => 'mfiletransferStatusLastTransferResult.0'); $val = snmpGet(\%arg); # Figure out what happened given ($val) { when (undef) { $xfer_result{$target} = 'Not responding'; print_it(' Not responding'); } when ('iThinkThisMeansSuccessfulSK') { $xfer_result{$target} = 'Success-SK'; print_it(" Uploaded $config_file"); say 'Successful-SK file transfer' if $debug; } when ('lastFileTransferResultSuccessful') { $xfer_result{$target} = 'Success'; print_it(" Uploaded $config_file"); say 'Successful file transfer' if $debug; } when ('lastFileTransferResultNotAvailable') { $xfer_result{$target} = 'Failure'; $error{$target} = 'No previous file transfer'; print_it(" Did not upload $config_file"); say 'No previous file transfer' if $debug; } when ('lastFileTransferResultFailureUnknown') { $xfer_result{$target} = 'Failure'; $error{$target} = 'Unknown file transfer failure'; print_it(" Did not upload $config_file"); say 'Unknown file transfer failure'; } when ('lastFileTransferResultFailureServerInaccessible') { $xfer_result{$target} = 'Failure'; $error{$target} = 'Server inaccessible'; print_it(" Did not upload $config_file"); say 'Server inaccessible' if $debug; } when ('lastFileTransferResultFailureServerAccessDenied') { $xfer_result{$target} = 'Failure'; $error{$target} = 'Server denied access'; print_it(" Did not upload $config_file"); say 'Server denied access' if $debug; } when ('lastFileTransferResultFailureFileNotFound') { $xfer_result{$target} = 'Failure'; $error{$target} = 'File not found'; print_it(" Did not upload $config_file"); say 'File not found' if $debug; } when ('lastFileTransferResultFailureFileTypeUnknown') { $xfer_result{$target} = 'Failure'; $error{$target} = 'File type unknown'; print_it(" Did not upload $config_file"); say 'File type unknown' if $debug; } when ('lastFileTransferResultFailureFileCorrupted') { $xfer_result{$target} = 'Failure'; $error{$target} = 'File corrupt'; print_it(" Did not upload $config_file"); say 'File corrupt' if $debug; } when (undef) { $xfer_result{$target} = 'Failure'; $error{$target} = 'No response to upload command'; print_it(" Did not upload $config_file"); say 'No status code' if $debug; } default { $xfer_result{$target} = 'Failure'; $error{$target} = "Unknown status code: $val"; print_it(" Unknown status code, not sure about $config_file"); say 'Unknown status code' if $debug; } } # Announce completion print_it("Done processing $target"); say "-------------------------------------------------------\n\n" if $mode eq 'interactive'; } # Finish 'loop through @target' # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Figure out which devices survived the experience ######################################################################## sub info_after { my $responded_ref; my $dead; my $alive_ref; my $dead_ref; my $unknown_ref; my $error_ref; # Debug trace trace_location('begin') if $debug; # Notify operator print_it('Pinging targets...'); # Loop through targets, polling devices until they answer ($responded_ref, $dead) = poll_by_ping(\@target, $tftp_wait); # If $dead is defined, then at least one target isn't answering pings: # investigate further if (defined $dead) { ($alive_ref, $dead_ref, $unknown_ref, $error_ref) = ping_list(\@target); for my $target (@$dead_ref) { $error{$target} = 'Cannot ping'; } } # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Tell operator what I did ######################################################################## sub print_after { my @troubled; my $shit_happens = 0; my $srcfile = "$tftp_dir/$config_file"; # Debug trace trace_location('begin') if $debug; # If running from cron, don't print report return 1 if $mode eq 'batch'; say "\n# Here is what I see"; # If we are running in test mode, then return unless ($dome) { print "Running in test mode, cannot print a meaningful report\n\n"; return 1; } # Iterate through @target, logging results, recording errors for my $target (@target) { $shit_happens++ if defined $error{$target}; given ($xfer_result{$target}) { when ('Unknown') { log_it("Attempted to configure $target using $srcfile, unknown result"); push @troubled, $target; } when ('Success') { log_it("Configured $target using $srcfile"); } when ('Failure') { log_it("Failed to configure $target using $srcfile"); push @troubled, $target; $shit_happens++; } default { log_it("Attempted to configure $target using $srcfile, unknown result"); push @troubled, $target; } } } say(''); print < 0) { say "\n\n\n"; print <new(); $filetype = sprintf "%s", $f->file_type("$tftp_dir/$config_file"); given ($filetype) { when (/ascii|plain|text/i) { $filetype = 'ascii' } when (/binary/) { $filetype = 'binary' } default { die "Unknown file type $filetype for $tftp_dir/$config_file\n"; } } # If we are modifying modern cards, require that $config_file be 'config.ini' if ($dome) { unless ($apc_card_model{$target[0]} =~ /AP960/) { unless ($config_file eq 'config.ini') { die "Please rename $config_file to 'config.ini'\n"; } } } # Loop through targets, removing non-APC devices SANITY: for my $target (@target) { # Identify manufacturer unless ($manufacturer{$target} eq 'APC') { say "\nManufacturer of $target is not APC, ignoring" if $debug; push @remove, $target; next SANITY; } # If the filetype of the config file is binary, then this config file # is destined for AP960x cards. Toss out any AP961x cards in the list. if ($filetype eq 'binary') { unless ($apc_card_model{$target} =~/AP960/) { say "\nConfig file is wrong type for $target, ignoring" if $debug; push @remove, $target; next SANITY; } } # If the file type of the config file is ASCII, then this config file # is destined for AP961x cards. Toss out any AP960x cards in the list. elsif ($filetype eq 'ascii') { if ($apc_card_model{$target} =~/AP960/) { say "\nConfig file is wrong type for $target, ignoring" if $debug; push @remove, $target; next SANITY; } } # Entertain operator print $BANG if $mode eq 'interactive'; } # Make things look pretty say "\n" if $mode eq 'interactive'; # Remove entries which failed checks prune_basic(@remove); # Debug trace trace_location('end') if $debug; return 1; } ######################################################################## # Output help ######################################################################## sub HELP_MESSAGE { print <