As others have reported, the current fence_apc shipping with
RHEL5.1/CentOS5.1 simply does not work reliably on newer APC firmwares. It
breaks under all kinds of conditions (some as simple as 'works on some
ports but not on other ports').
Since I *really* need it to work, I hacked together a Perl version
(derived from the old fence_apc.pl in CVS) that uses the APC command line
interface and dispenses with the 'menu scraping' interface entirely.
I don't have any switches here that use a switchnum interface so I
couldn't hack anything together for that. But it appears to reliably do
what it is supposed to do (at least on my APC7900 switches running AOS
3.3.4): Fence.
It would make a great deal of sense for someone to add it to the Luci/CMAN
list of supported fences. Maybe "APC Power Device (CLI) / fence_apc_cli"?
--
Benjamin Franz
"It is moronic to predict without first establishing an error rate
for a prediction and keeping track of one’s past record of accuracy."
-- Nassim Nicholas Taleb, Fooled By Randomness
#!/usr/bin/perl
#########################################3
# CLI APC Fencing. This only works with APC AOS v2.7.0 or later
# but it is a LOT simpler and more robust than the old menu scraping code.
#
use strict;
use warnings;
use Getopt::Std;
use Net::Telnet ();
# WARNING!! Do not add code bewteen "#BEGIN_VERSION_GENERATION" and
# "#END_VERSION_GENERATION" It is generated by the Makefile
my ($FENCE_RELEASE_NAME, $REDHAT_COPYRIGHT, $BUILD_DATE);
#BEGIN_VERSION_GENERATION
$FENCE_RELEASE_NAME="";
$REDHAT_COPYRIGHT="";
$BUILD_DATE="";
#END_VERSION_GENERATION
###############################################################################
###############################################################################
##
## Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved.
## Copyright (C) 2004-2006 Red Hat, Inc. All rights reserved.
##
## This copyrighted material is made available to anyone wishing to use,
## modify, copy, or redistribute it subject to the terms and conditions
## of the GNU General Public License v.2.
##
###############################################################################
###############################################################################
# Get the program name from $0 and strip directory names
my $Program_Name = $0;
$Program_Name =~ s/.*\///;
my $login_prompt = '/ : /';
my $command_prompt = '/APC> $/';
my $debug_log = '/tmp/apclog'; # Location of debugging log when in verbose mode
my $telnet_timeout = 2; # Seconds to wait for matching telent response
my $open_wait = 5; # Seconds to wait between each telnet attempt
my $max_open_tries = 3; # How many telnet attempts to make. Because the
# APC can fail repeated login attempts, this number
# should be more than 1
my $reboot_duration = 30; # Number of seconds plugs are turned off during a reboot command
my $power_off_delay = 0; # Number of seconds to wait before actually turning off a plug
my $power_on_delay = 30; # Number of seconds to wait before actually turning on a plug
our %Opts = (
'o' => 'reboot',
);
our $SwitchNum;
our $Logged_In = 0;
our $t = Net::Telnet->new; # Our telnet object instance
### START MAIN #######################################################
if (@ARGV > 0) {
getopts("a:hl:n:o:p:qTvV", \%Opts) || fail_usage();
usage() if defined $Opts{'h'};
version() if defined $Opts{'V'};
fail_usage("Unknown parameter.") if (@ARGV > 0);
fail_usage("No '-a' flag specified.") unless defined $Opts{'a'};
fail_usage("No '-n' flag specified.") unless defined $Opts{'n'};
fail_usage("No '-l' flag specified.") unless defined $Opts{'l'};
fail_usage("No '-p' flag specified.") unless defined $Opts{'p'};
fail_usage("Unrecognised action '$Opts{'o'}' for '-o' flag") unless $Opts{'o'} =~ /^(Off|On|Reboot)$/i;
if ( $Opts{'n'} =~ /(\d+):(\d+)/ ) {
$SwitchNum = $1;
$Opts{'n'} = $2;
}
} else {
get_options_stdin();
fail("failed: no IP address") unless defined $Opts{'a'};
fail("failed: no plug number") unless defined $Opts{'n'};
fail("failed: no login name") unless defined $Opts{'l'};
fail("failed: no password") unless defined $Opts{'p'};
fail("failed: unrecognised action: $Opts{'o'}") unless $Opts{'o'} =~ /^(Off|On|Reboot)$/i;
}
my $option = lc($Opts{'o'});
my $plug = $Opts{'n'};
$t->prompt($command_prompt);
$t->timeout($telnet_timeout);
$t->input_log($debug_log) if $Opts{'v'};
$t->errmode('return');
login();
$t->errmode(\&telnet_error);
my $cmd_results = '';
my $ok;
if ($option eq 'reboot') {
$t->cmd( String => "rebootduration $plug:$reboot_duration", Output => \$cmd_results );
}
if ($option eq 'off') {
$t->cmd( String => "poweroffdelay $plug:$power_off_delay", Output => \$cmd_results );
}
if ($option eq 'on') {
$t->cmd( String => "powerondelay $plug:$power_on_delay", Output => \$cmd_results );
}
$ok = $t->cmd( String => "$option $plug", Output => \$cmd_results );
#print $cmd_results;
logout();
exit 0;
### END MAIN #######################################################
sub usage {
print <<"EOT";
Usage:
$Program_Name [options]
Options:
-a <ip> IP address or hostname of MasterSwitch
-h usage
-l <name> Login name
-n <num> Outlet number to change: [<switch>:]<outlet>
-o <string> Action: Reboot (default), Off or On
-p <string> Login password
-q quiet mode
-T Test mode (cancels action)
-V version
-v Log to file /tmp/apclog
EOT
exit 0;
}
sub fail {
my ($msg)=@_;
print $msg."\n" unless defined $Opts{'q'};
if (defined $t) {
# make sure we don't get stuck in a loop due to errors
$t->errmode('return');
if ($Logged_In) {
logout();
}
$t->close();
}
exit 1;
}
sub fail_usage {
my ($msg)=@_;
print STDERR $msg."\n" if $msg;
print STDERR "Please use '-h' for usage.\n";
exit 1;
}
sub version {
print "$Program_Name $FENCE_RELEASE_NAME $BUILD_DATE\n";
print "$REDHAT_COPYRIGHT\n" if ( $REDHAT_COPYRIGHT );
exit 0;
}
sub login {
for (my $i=0; $i<$max_open_tries; $i++) {
$t->open($Opts{'a'});
my ($prompt) = $t->waitfor($login_prompt);
# Expect 'User Name : '
if ((not defined $prompt) || ($prompt !~ /name/i)) {
$t->close();
sleep($open_wait);
next;
}
$t->print($Opts{'l'});
($prompt) = $t->waitfor($login_prompt);
# Expect 'Password : '
if ((not defined $prompt) || ($prompt !~ /password/i )) {
$t->close();
sleep($open_wait);
next;
}
# Send password
$t->print("$Opts{'p'} -c"); # The appended ' -c' activates the CLI interface
my ($dummy, $login_result) = $t->waitfor('/(APC>|(?i:user name|password)\s*:) /');
if ($login_result =~ m/APC> /) {
$Logged_In = 1;
# send newline to flush prompt
$t->print("");
return;
} else {
fail("invalid username or password ($login_result)");
}
}
fail("failed: telnet failed: " . $t->errmsg."\n");
}
sub logout {
$t->cmd("logout");
return;
}
sub get_options_stdin {
my $opt;
my $line = 0;
while( defined($opt = <>) ) {
chomp $opt;
# strip leading and trailing whitespace
$opt =~ s/^\s*//;
$opt =~ s/\s*$//;
# skip comments
next if ($opt =~ m/^#/);
$line += 1;
next if ($opt eq '');
my ($name, $val) = split(/\s*=\s*/, $opt);
if ( $name eq "" ) {
print STDERR "parse error: illegal name in option $line\n";
exit 2;
}
elsif ($name eq "agent" ) { } # DO NOTHING -- this field is used by fenced
elsif ($name eq "ipaddr" ) { $Opts{'a'} = $val; }
elsif ($name eq "login" ) { $Opts{'l'} = $val; }
elsif ($name eq "option" ) { $Opts{'o'} = $val; }
elsif ($name eq "passwd" ) { $Opts{'p'} = $val; }
elsif ($name eq "port" ) { $Opts{'n'} = $val; }
elsif ($name eq "switch" ) { $SwitchNum = $val; }
elsif ($name eq "test" ) { $Opts{'T'} = $val; }
elsif ($name eq "verbose" ) { $Opts{'v'} = $val; }
}
}
sub telnet_error {
if ($t->errmsg ne "pattern match timed-out") {
fail("failed: telnet returned: " . $t->errmsg . "\n");
} else {
$t->print("");
}
}
--
Linux-cluster mailing list
Linux-cluster@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/linux-cluster