#!/usr/bin/perl

###############################################################################
###############################################################################
##
##  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.
##  Copyright (C) 2004 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.
##
###############################################################################
###############################################################################

use Getopt::Std;
use Net::SNMP; 

# Get the program name from $0 and strip directory names
$_=$0;
s/.*\///;
my $pname = $_;

my $sleep_time = 5; 
my $snmp_timeout = 10;
$opt_o = "reboot";
$opt_u = 161;

my $oid_powerState =  ".1.3.6.1.4.1.2.3.51.2.22.1.5.1.1.4";    # remoteControlBladePowerState
my $oid_powerChange = ".1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.7";    # powerOnOffBlade
my $oid_resetPower =  ".1.3.6.1.4.1.2.3.51.2.22.1.6.1.1.8";    # restartBlade

# WARNING!! Do not add code bewteen "#BEGIN_VERSION_GENERATION" and
# "#END_VERSION_GENERATION"  It is generated by the Makefile

#BEGIN_VERSION_GENERATION
$FENCE_RELEASE_NAME="";
$REDHAT_COPYRIGHT="";
$BUILD_DATE="";
#END_VERSION_GENERATION

sub usage
{
    print "Usage:\n";
    print "\n";
    print "$pname [options]\n";
    print "\n";
    print "Options:\n";
    print "  -a <ip>          IP address or hostname of BladeCenter\n";
    print "  -h               usage\n";
    print "  -c <community>   SNMP Community\n";
    print "  -n <num>         Port number to disable\n";
    print "  -o <string>      Action:  Reboot (default), On or Off\n";
    print "  -u <udpport>     UDP port to use (default: 161)\n"; 
    print "  -q               quiet mode\n";
    print "  -t               test power state\n"; 
    print "  -V               version\n";

    exit 0;
}

sub fail_usage
{
  ($msg)=@_;
  print STDERR $msg."\n" if $msg;
  print STDERR "Please use '-h' for usage.\n";
  exit 1;
}

sub fail
{
  ($msg) = @_;
  print $msg."\n" unless defined $opt_q;
  $t->close if defined $t;
  exit 1;
}

sub version
{
  print "$pname $FENCE_RELEASE_NAME $BUILD_DATE\n";
  print "$REDHAT_COPYRIGHT\n" if ( $REDHAT_COPYRIGHT );

  exit 0;
}

sub get_options_stdin
{
    my $opt;
    my $line = 0;
    while( defined($in = <>) )
    {
        $_ = $in;
        chomp;

	# strip leading and trailing whitespace
        s/^\s*//;
        s/\s*$//;

	# skip comments
        next if /^#/;

        $line+=1;
        $opt=$_;
        next unless $opt;

        ($name,$val)=split /\s*=\s*/, $opt;

        if ( $name eq "" )
        {  
           print STDERR "parse error: illegal name in option $line\n";
           exit 2;
	}
	
        # DO NOTHING -- this field is used by fenced
	elsif ($name eq "agent" ) { } 

        elsif ($name eq "ipaddr" ) 
	{
            $opt_a = $val;
        } 
	elsif ($name eq "community" ) 
	{
            $opt_c = $val;
        } 

        elsif ($name eq "option" )
        {
            $opt_o = $val;
        }
	elsif ($name eq "port" ) 
	{
            $opt_n = $val;
        }
	elsif ($name eq "udpport" )
	{
	    $opt_u = $val; 
	}

        # FIXME should we do more error checking?  
        # Excess name/vals will be eaten for now
	else 
	{
           fail "parse error: unknown option \"$opt\"";
        }
    }
}

# ---------------------------- MAIN --------------------------------

if (@ARGV > 0) {
   getopts("a:hc:n:o:qu:tV") || fail_usage ;

   usage if defined $opt_h;
   version if defined $opt_V;

   fail_usage "Unknown parameter." if (@ARGV > 0);

   fail_usage "No '-a' flag specified." unless defined $opt_a;
   fail_usage "No '-n' flag specified." unless defined $opt_n;
   fail_usage "No '-c' flag specified." unless defined $opt_c;
   fail_usage "Unrecognised action '$opt_o' for '-o' flag"
      unless $opt_o =~ /^(reboot|on|off)$/i;

} else {
   get_options_stdin();

   fail "failed: no IP address" unless defined $opt_a;
   fail "failed: no plug number" unless defined $opt_n;
   fail "failed: no SNMP community" unless defined $opt_c;
   fail "failed: unrecognised action: $opt_o"
      unless $opt_o =~ /^(reboot|on|off)$/i;
}

my ($snmpsess, $error) = Net::SNMP->session ( 
	-hostname   => $opt_a, 
	-version    => "snmpv1", 
	-port       => $opt_u, 
	-community  => $opt_c,
	-timeout    => $snmp_timeout); 

if (!defined ($snmpsess)) { 
	printf("$FENCE_RELEASE_NAME ERROR: %s.\n", $error);
	exit 1; 
};

# first check in what state are we now
my $oid = $oid_powerState . "." . $opt_n;
my $oid_val = ""; 
my $result = $snmpsess->get_request ( 
	-varbindlist => [$oid]
);
if (!defined($result)) {
	printf("$FENCE_RELEASE_NAME ERROR: %s.\n", $snmpsess->error);
	$snmpsess->close;
	exit 1;
}

if (defined ($opt_t)) { 
	printf ("$FENCE_RELEASE_NAME STATE: Port %d on %s returned %d\n", $opt_n, $opt_a, $result->{$oid}); 
	exit 1; 
};

if ($opt_o =~ /^(reboot|off)$/i) { 
	if ($result->{$oid} == "0") { 
		printf ("$FENCE_RELEASE_NAME WARNING: Port %d on %s already down.\n", $opt_n, $opt_a); 
		$snmpsess->close; 
		exit 0; 
	}; 
} else { 
	if ($result->{$oid} == "1") { 
		printf ("$FENCE_RELEASE_NAME WARNING: Port %d on %s already up.\n", $opt_n, $opt_a); 
		$snmpsess->close; 
		exit 0; 
	};
};

# excellent, now change the state 
if ($opt_o =~ /^reboot$/i) { 
	# reboot
	$oid = $oid_resetPower . "." . $opt_n;
	$oid_val = "1"; 
} elsif ($opt_o =~ /^on$/i) { 
	# power on
	$oid = $oid_powerChange . "." . $opt_n; 
	$oid_val = "1"; 
} else { 
	# power down
	$oid = $oid_powerChange . "." . $opt_n; 
	$oid_val = "0"; 
};

$result = $snmpsess->set_request (
	-varbindlist => [$oid, INTEGER, $oid_val]
); 

if (!defined ($result)) { 
	# ignore this for now, seems like IBM BladeCenter has a broken SNMPd
	# it almost always timeouts
}; 

# now, wait a bit and see if we have done it
sleep($sleep_time); 

$oid = $oid_powerState . "." . $opt_n;

undef $result; 
$result = $snmpsess->get_request ( 
	-varbindlist => [$oid]
);

if (!defined($result)) {
	# this is a real error
	printf("$FENCE_RELEASE_NAME ERROR: %s.\n", $snmpsess->error);
	$snmpsess->close;
	exit 1;
}; 

if ($opt_o =~ /^(off)$/i) { 
	if ($result->{$oid} == "1") { 
		printf ("$FENCE_RELEASE_NAME ERROR: Port %d on %s still up.\n", $opt_n, $opt_a); 
		$snmpsess->close; 
		exit 1; 
	}; 
} else { 
	if ($result->{$oid} == "0") { 
		printf ("$FENCE_RELEASE_NAME ERROR: Port %d on %s still down.\n", $opt_n, $opt_a); 
		$snmpsess->close; 
		exit 1; 
	};
};

# everything's a ok :) 
$snmpsess->close; 

printf ("$FENCE_RELEASE_NAME SUCCESS: Port %d on %s changed state to %s\n", $opt_n, $opt_a, $opt_o) unless defined $opt_q;
exit 0; 

