[Linux-cluster] fence_apc - Perl based CLI version

Benjamin Franz snowhare at nihongo.org
Tue Jan 8 21:31:58 UTC 2008


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
-------------- next part --------------
#!/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("");
	}
}




More information about the Linux-cluster mailing list