#!/usr/bin/perl

###############################################################################
###############################################################################
##
##  Copyright (C) 2005 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.
##
###############################################################################
###############################################################################

# The following agent has been tested on:
#
#  Model 		DRAC Version	Firmware
#  -------------------	--------------	----------------------
#  PowerEdge 750	DRAC III/XT	3.20 (Build 10.25)
#

use Getopt::Std;
use Net::Telnet ();

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

my $telnet_timeout = 2;      # Seconds to wait for matching telent response
my $power_timeout = 20;      # time to wait in seconds for power state changes
$action = 'reboot';          # Default fence action.  

my $logged_in = 0;
my $quiet = 0;

my $t = new Net::Telnet;



# 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 DRAC\n";
	print "  -D <debugfile>   debugging output file\n";
	print "  -h               usage\n";
	print "  -l <name>        Login name\n";
	print "  -o <string>      Action: reboot (default), off or on\n";
	print "  -p <string>      Login password\n";
	print "  -q               quiet mode\n";
	print "  -V               version\n";
	print "\n";
	print "CCS Options:\n";
        print "  action = \"string\"      Action: reboot (default), off or on\n";
        print "  debug  = \"debugfile\"   debugging output file\n";
	print "  ipaddr = \"ip\"          IP address or hostname of DRAC\n";
	print "  login  = \"name\"        Login name\n";
        print "  passwd = \"string\"      Login password\n";

	exit 0;
}

sub msg
{
	($msg)=@_;
	print $msg."\n" unless $quiet;
}

sub fail
{
	($msg)=@_;
	print $msg."\n" unless $quiet;

	if (defined $t)
	{
		# make sure we don't get stuck in a loop due to errors
		$t->errmode('return');  

		logout() if $logged_in;
		$t->close 
	}
	exit 1;
}

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

sub version
{
	print "$pname $FENCE_RELEASE_NAME $BUILD_DATE\n";
	print "$SISTINA_COPYRIGHT\n" if ( $SISTINA_COPYRIGHT );
	exit 0;
}


sub login
{
	$t->open($address) or 
		fail "failed: telnet open failed: ". $t->errmsg."\n";
  
	# Expect 'Login: ' 
	$t->waitfor(Match => "/Login: /", Timeout=>15) or
		fail "failed: telnet failed: ". $t->errmsg."\n" ;

	# Send login
	$t->print($login);

	# Expect 'Password: ' 
	$t->waitfor("/Password: /") or 
		fail "failed: timeout waiting for password";

	# Send password
	$t->print($passwd);  

	# Expect '[$login]# '
	$t->waitfor($cmd_prompt) or
		fail "failed: invalid username or password"; 

	$logged_in = 1;
}

#
# Set the power status of the node 
#
sub set_power_status
{
	my ($state,$dummy) = @_;
	my $svr_action;

	if   ( $state =~ /^on$/)  { $svr_action = "powerup"   }
	elsif( $state =~ /^off$/) { $svr_action = "powerdown" }

	$t->print("serveraction -d 0 $svr_action");

	# TODO: process output and report back DRAC response
	# Expect '[$login]# '
	$t->waitfor($cmd_prompt) or
		fail "failed: unexpected serveraction response"; 
}


#
# get the power status of the node and return it in $status and $_
#
sub get_power_status
{

	$t->print("getmodinfo");

	($_) = $t->waitfor($cmd_prompt);

	#Expect:
	#  #<group>     <module>    <presence>  <pwrState>  <health>  <svcTag>
	#   1  ---->     chassis    Present         ON      Normal    CQXYV61
	#  [root]# 
	if (/^$\#<group>\s+<module>\s+<presence>\s+<pwrState>\s+<health>\s+<svcTag>\n\s*(\S+)\s+---->\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/sm)
	{
		$status=$4;
	}
	else
	{
		fail "failed: unable to determine power state\n";
	}

	$_=$status;
	if(/^on$/i) { }
	elsif (/^off$/i){}
	else
	{
		fail "failed: unknown power state '$status'\n";
	}
}


# Wait upto $power_timeout seconds for node power state to change to 
# $state before erroring out.
#
# return 1 on success
# return 0 on failure
#
sub wait_power_status
{
	my ($state,$dummy) = @_;

	$state = lc $state;

	for (my $i=0; $i<$power_timeout ; $i++)
	{
		get_power_status;
		my $check = lc $status;

		if ($state eq $check ) { return 1 }
		sleep 1;
	}
	$_ = "timed out waiting to power $state";
	return 0;
}

#
# logout of the telnet session
#
sub logout 
{
	$t->print("");
	$t->print("exit");
}

#
# error routine for Net::Telnet instance
#
sub telnet_error
{
	fail "failed: telnet returned: ".$t->errmsg."\n";
}

#
# execute the action.  Valid actions are 'on' 'off' 'reboot' and 'status'.
# TODO: add 'configure' that uses racadm rpm to enable telnet on the drac
#
sub do_action
{
	&get_power_status;

	if ($action =~ /^on$/i)
	{
		if ($status =~ /^on$/i)
		{
			msg "success: already on";
			return;
		}
			
		set_power_status on;
		fail "failed: $_" unless wait_power_status on;

		if ($status =~ /^on$/i)
		{
			msg "success: powered on";
		}
		else
		{
			fail "failed: failed to turn on\n"
		}	
	}
	elsif ($action =~ /^off$/i)
	{
		if ($status =~ /^off$/i)
		{
			msg "success: already off";
			return;
		}

		set_power_status off;
		fail "failed: $_" unless wait_power_status off;
	
		if ($status =~ /^off$/i)
		{
			msg "success: powered off";
		}
		else
		{
			fail "failed: failed to turn off\n"
		}	
	}
	elsif ($action =~ /^reboot$/i)
	{
		if ( !($status =~ /^off$/i) )
		{
			set_power_status off;
		}
		fail "failed: $_" unless wait_power_status off;

		set_power_status on;
		fail "failed: $_" unless wait_power_status on;

		if ($status =~ /^on$/i)
		{
			msg "success: rebooted";
		}
		else
		{
			fail "failed: failed to reboot\n"
		}	
	}
	elsif ($action =~ /^status$/i)
	{
		msg "status: $status";
		return;
	}
	else 
	{
		fail "failed: unrecognised action: '$action'";
	}
}

#
# Decipher STDIN parameters
#
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" ) 
		{
			$address = $val;
		} 
		elsif ($name eq "login" ) 
		{
			$login = $val;
		} 
		elsif ($name eq "action" ) 
		{
			$action = $val;
		} 
		elsif ($name eq "passwd" ) 
		{
			$passwd = $val;
		} 
		elsif ($name eq "debug" ) 
		{
			$debug = $val;
		} 
		# Excess name/vals will fail
		else 
		{
			fail "parse error: unknown option \"$opt\"";
		}
	}
}


### MAIN #######################################################

#
# Check parameters
#
if (@ARGV > 0) {
	getopts("a:D:hl:o:p:qV") || fail_usage ;
	
	usage if defined $opt_h;
	version if defined $opt_V;
	
	$quiet = 1 if defined $opt_q;
	$debug = $opt_D; 

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

	fail_usage "No '-a' flag specified." unless defined $opt_a;
	$address = $opt_a;

	fail_usage "No '-l' flag specified." unless defined $opt_l;
	$login = $opt_l;

	fail_usage "No '-p' flag specified." unless defined $opt_p;
	$passwd = $opt_p;

	if ($opt_o)
	{
		fail_usage "Unrecognised action '$opt_o' for '-o' flag"
		unless $opt_o =~ /^(Off|On|Reboot|status)$/i;
		$action = $opt_o;
	}

} else {
	get_options_stdin();

	fail "failed: no IP address" unless defined $address;
	fail "failed: no login name" unless defined $login;
	fail "failed: no password" unless defined $passwd;
	fail "failed: unrecognised action: $action"
	unless $action =~ /^(Off|On|Reboot|status)$/i;
} 

$cmd_prompt="/\\[$login\\]# /";

$t->timeout($telnet_timeout);
$t->input_log($debug) if $debug;
$t->errmode('return');  

login;

# Abort on failure beyond here
$t->errmode(\&telnet_error);  

do_action;

logout;

exit 0;


