power_rpc27.pm.in 5.51 KB
Newer Older
1
#!/usr/bin/perl -wT
2 3

package power_rpc27;
4

5 6 7 8
use Exporter;
@ISA = ("Exporter");
@EXPORT = qw( rpc27ctrl );

9 10 11 12
use Socket;
use IO::Handle;
use lib "@prefix@/lib";
use libdb;
13

14 15 16 17 18
# A little perl module to power cycle something attached to an RPC27.
# Thats a serially controlled, power controller.
#
# XXX The little secretkey handshake is coded in here. If this changes
# in capture, you have to change it here too. Look for the "pack"
19
# statement below.
20 21 22
#
# XXX Note that if someone has already tipped to the power controller 
# in question, this won't work at all.
23 24 25 26 27

# Turn off line buffering on output
$| = 1;

# Set for more output.
28
my $debug = 0;
29 30

# RPC27 Prompt string
31
my $RPC27_PROMPT = "RPC-27>";
32 33 34 35
my %RPC27_CMD =
  ("cycle"=>"reboot",
   "on"    =>"on",
   "off"   =>"off");
36 37

# Main routine.
38 39 40 41
# usage: rpc27ctrl(cmd, controller, outlet)
# cmd = { "cycle" | "on" | "off" }
# controller = <node_id>
# outlet = int, 1 <= outlet <= 20
42 43 44
#
# Returns 0 on success. Non-zero on failure.
# 
45
sub rpc27ctrl {
46
    my($cmd, $controller, @outlets) = @_;
47 48
    my($TIP, $i, $insync);

49
    if (grep {$_ < 1 || $_ > 20} @outlets) {
50 51 52 53
	print STDERR "*** Invalid outlet '$outlet': Must be 1-20\n";
	return 1;
    }

54 55 56 57 58
    #
    # Make a comma-seperated string of all the outlets to reboot
    #
    my $outlet = join(",",@outlets);

59
    #
60 61 62
    # Form the connection to the controller via a "tip" line to the
    # capture process. Once we have that, we can just talk to the
    # controller directly.
63
    #
64 65 66 67 68
    if (! ($TIP = tipconnect($controller))) {
	print STDERR "*** Could not form TIP connection to $controller\n";
	return 1;
    }
    
69
    #
70 71 72
    # Send a couple of newlines to get the command prompt, and then wait
    # for it to print out the command prompt. This loop is set for a small
    # number since if it cannot get the prompt quickly, then something has
73 74 75 76
    # gone wrong.
    #
    $insync = 0;

77
    for ($i = 0; $i < 20; $i++) {
78 79 80 81
	my $line;

	if (syswrite($TIP, "\r\n") == 0) {
	    print STDERR
82
		"*** Power control sync write failed ($controller/$outlet)\n";
83 84 85 86 87
	    close($TIP);
	    return 1;
	}

	if (sysread($TIP, $line, 1024) == 0) {
88 89 90 91
	    print STDERR
		"*** Power control sync read failed ($controller/$outlet)\n";
	    close($TIP);
	    return 1;
92 93 94 95 96 97 98 99 100
	}
	if ($debug) {
	    print "Read: $line";
	}
	if ($line =~ /$RPC27_PROMPT/) {
	    if ($debug) {
		print "Matched prompt '$RPC27_PROMPT'!\n";
	    }
	    $insync = 1;
101 102
	    last;
	}
103
    }
104 105 106 107 108 109
    if (! $insync) {
	print STDERR "*** Could not sync with power controller! ".
	    "($controller/$outlet)\n";
	close($TIP);
	return 1;
    }
110

111
    if ($debug) {
112 113 114 115 116 117 118 119
	print "Sending '$RPC27_CMD{$cmd} $outlet' to $controller\n";
    }

    # Okay, got a prompt. Send it the string:
    if (syswrite($TIP, "$RPC27_CMD{$cmd} $outlet\r\n") == 0) {
	print STDERR "*** Power control write failed ($controller/$outlet)\n";
	close($TIP);
	return 1;
120
    }
121

122
    close($TIP);
123
    return 0;
124 125
}

126
#
127 128
# Connect up to the capture process. This should probably be in a library
# someplace.
129
#
130
sub tipconnect($) {
131
    my($controller) = $_[0];
132
    my($server, $portnum, $keylen, $keydata, $capreturn);
133 134 135 136 137
    my($inetaddr, $paddr, $proto);
    my(%powerid_row);
    local *TIP;

    my $query_result =
138
      DBQueryWarn("select * from tiplines where node_id='$controller'");
139 140 141

    if ($query_result->numrows < 1) {
	print STDERR "*** No such tipline: $controller\n";
142
	return 0;
143
    }
144 145 146 147 148 149 150 151 152 153 154
    %powerid_row = $query_result->fetchhash();

    $server  = $powerid_row{'server'};
    $portnum = $powerid_row{'portnum'};
    $keylen  = $powerid_row{'keylen'};
    $keydata = $powerid_row{'keydata'};

    if ($debug) {
	print "tipconnect: $server $portnum $keylen $keydata\n";
    }

155 156 157 158 159
    #
    # We have to send over the key. This is a little hokey, since we have
    # to make it look like the C struct.
    #
    my $secretkey = pack("iZ256", $keylen, $keydata);
160
    my $capret    = pack("i", 0);
161

162 163 164 165 166 167 168 169 170 171
    #
    # This stuff from the PERLIPC manpage.
    # 
    if (! ($inetaddr = inet_aton($server))) {
	print STDERR "*** Cannot map $server to IP address\n";
	return 0;
    }
    $paddr    = sockaddr_in($portnum, $inetaddr);
    $proto    = getprotobyname('tcp');

172
    for ($i = 0; $i < 20; $i++) {
173 174 175 176
	if (! socket(TIP, PF_INET, SOCK_STREAM, $proto)) {
	    print STDERR "*** Cannot create socket.\n";
	    return 0;
	}
177

178 179 180
	if (! connect(TIP, $paddr)) {
	    print STDERR
		"*** Cannot connect to $controller on $server($portnum)\n";
181
	    close(TIP);
182 183 184 185
	    return 0;
	}
	TIP->autoflush(1);

186 187 188 189 190 191 192 193 194
	#
	# While its a fatal error if the connect fails, the write and the
	# read that follows might fail because the tip is currently is
	# active. The handshake writes back a value and then immediately
	# closes the socket, which could manifest itself as a closed
	# connection on this end, even before we get a change to do these.
	# operations. In that case, just go around the loop again. We hope
	# to succeed at some point. 
	# 
195 196 197
	if (! syswrite(TIP, $secretkey)) {
	    print STDERR
		"*** Cannot write to $controller on $server($portnum)\n";
198
	    goto again;
199 200 201 202
	}
	if (! sysread(TIP, $capret, length($capret))) {
	    print STDERR
		"*** Cannot read from $controller on $server($portnum)\n";
203
	    goto again;
204 205 206 207 208 209 210
	}

	my $foo = unpack("i", $capret);
	if ($debug) {
	    print "Capture returned $foo\n";
	}
	if ($foo == 0) {
211 212
	    return(*TIP);
	}
213 214 215 216 217
	
      again:
	close(TIP);

	if ($i && (($i % 5) == 0)) {
218 219
	    printf STDERR
		"*** WARNING: $controller on $server($portnum) is busy\n".
220
		"    Waiting a bit before trying again. Pass $i.\n";
221 222
	}
	sleep(5);
223
    }
224
    
225
    print STDERR
226
	"*** $controller on $server($portnum) was busy for too long\n";
227
    return 0;
228 229 230
}

1;