power_rpc27.pm.in 6.47 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
    # Make a comma-seperated strings of all the outlets to reboot. The RPCs
    # have a buffer limit of 31 characters, which limits us to 8 outlets
    # at a time (assuming the longest command and 2-digit outlet numbers)
58
    #
59 60 61 62 63 64 65 66 67 68 69 70
    my @outlet_strings = ();
    while (@outlets) {
	my @tmp_outlets = ();
	for (my $i = 0; ($i < 8) && (@outlets); $i++) {
	    push @tmp_outlets,shift(@outlets);
	}
	push @outlet_strings, join(",",@tmp_outlets);
    }
    if ($debug) {
	print "outlet_strings: ", join(" ",map("($_)",@outlet_strings)), "\n";
    }
    
71
    #
72 73 74
    # 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.
75
    #
76 77 78 79
    if (! ($TIP = tipconnect($controller))) {
	print STDERR "*** Could not form TIP connection to $controller\n";
	return 1;
    }
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

    foreach my $outlet (@outlet_strings) {
	my $command = "$RPC27_CMD{$cmd} $outlet";
	if (syncandsend($controller,$TIP,$command)) {
	    #
	    # On failure, syncandsend has already closed $TIP
	    #
	    return 1;
	}
    }

    close($TIP);
    return 0;

}

#
# Sync up with the power controller, and set it a command. $controller is the
# controller name, for error message purposes, $TIP is the connection to
# the controller opened with tipconnect, and $command is the whole command
# (ie. 'reboot 20,40') to send.
#
sub syncandsend($$) {
    my ($controller,$TIP,$cmd) = @_;

105
    #
106 107 108
    # 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
109 110
    # gone wrong.
    #
111
    my $insync = 0;
112

113
    for (my $i = 0; $i < 20; $i++) {
114 115 116 117
	my $line;

	if (syswrite($TIP, "\r\n") == 0) {
	    print STDERR
118
		"*** Power control sync write failed ($controller/$outlet)\n";
119 120 121 122 123
	    close($TIP);
	    return 1;
	}

	if (sysread($TIP, $line, 1024) == 0) {
124 125 126 127
	    print STDERR
		"*** Power control sync read failed ($controller/$outlet)\n";
	    close($TIP);
	    return 1;
128 129 130 131 132 133 134 135 136
	}
	if ($debug) {
	    print "Read: $line";
	}
	if ($line =~ /$RPC27_PROMPT/) {
	    if ($debug) {
		print "Matched prompt '$RPC27_PROMPT'!\n";
	    }
	    $insync = 1;
137 138
	    last;
	}
139
    }
140 141 142 143 144 145
    if (! $insync) {
	print STDERR "*** Could not sync with power controller! ".
	    "($controller/$outlet)\n";
	close($TIP);
	return 1;
    }
146

147
    if ($debug) {
148
	print "Sending '$cmd' to $controller\n";
149 150 151
    }

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

158
    return 0;
159 160
}

161
#
162 163
# Connect up to the capture process. This should probably be in a library
# someplace.
164
#
165
sub tipconnect($) {
166
    my($controller) = $_[0];
167
    my($server, $portnum, $keylen, $keydata, $capreturn);
168 169 170 171 172
    my($inetaddr, $paddr, $proto);
    my(%powerid_row);
    local *TIP;

    my $query_result =
173
      DBQueryWarn("select * from tiplines where node_id='$controller'");
174 175 176

    if ($query_result->numrows < 1) {
	print STDERR "*** No such tipline: $controller\n";
177
	return 0;
178
    }
179 180 181 182 183 184 185 186 187 188 189
    %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";
    }

190 191 192 193 194
    #
    # 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);
195
    my $capret    = pack("i", 0);
196

197 198 199 200 201 202 203 204 205 206
    #
    # 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');

207
    for (my $i = 0; $i < 20; $i++) {
208 209 210 211
	if (! socket(TIP, PF_INET, SOCK_STREAM, $proto)) {
	    print STDERR "*** Cannot create socket.\n";
	    return 0;
	}
212

213 214 215
	if (! connect(TIP, $paddr)) {
	    print STDERR
		"*** Cannot connect to $controller on $server($portnum)\n";
216
	    close(TIP);
217 218 219 220
	    return 0;
	}
	TIP->autoflush(1);

221 222 223 224 225 226 227 228 229
	#
	# 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. 
	# 
230 231 232
	if (! syswrite(TIP, $secretkey)) {
	    print STDERR
		"*** Cannot write to $controller on $server($portnum)\n";
233
	    goto again;
234 235 236 237
	}
	if (! sysread(TIP, $capret, length($capret))) {
	    print STDERR
		"*** Cannot read from $controller on $server($portnum)\n";
238
	    goto again;
239 240 241 242 243 244 245
	}

	my $foo = unpack("i", $capret);
	if ($debug) {
	    print "Capture returned $foo\n";
	}
	if ($foo == 0) {
246 247
	    return(*TIP);
	}
248 249 250 251 252
	
      again:
	close(TIP);

	if ($i && (($i % 5) == 0)) {
253 254
	    printf STDERR
		"*** WARNING: $controller on $server($portnum) is busy\n".
255
		"    Waiting a bit before trying again. Pass $i.\n";
256 257
	}
	sleep(5);
258
    }
259
    
260
    print STDERR
261
	"*** $controller on $server($portnum) was busy for too long\n";
262
    return 0;
263 264 265
}

1;