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)[EMAIL PROTECTED];
        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)[EMAIL PROTECTED];
        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
[email protected]
https://www.redhat.com/mailman/listinfo/linux-cluster

Reply via email to