power.in 7.33 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
3
4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
6
7
8
# All rights reserved.
#

9
10
11
#
# Testbed Power Control script
#
12
# power [on|off|cycle] <node> [<node>] ...
13
14
15
#
############################################################

16
17
18
#
# Configure variables
#
19
20
21
22
23
24
my $TB         = "@prefix@";
my $TBOPS      = "@TBOPSEMAIL@";
my $ELABINELAB = @ELABINELAB@;
my $RPCSERVER  = "@OUTERBOSS_NODENAME@";
my $RPCPORT    = "@OUTERBOSS_XMLRPCPORT@";
my $RPCCERT    = "@OUTERBOSS_SSLCERTNAME@";
25

26
use lib "@prefix@/lib";
27
use libdb;
28
use libxmlrpc;
29
use power_rpc27;
30
use power_sgmote;
31
use snmpit_apc;
32
use libtestbed;
33
use strict;
34
use English;
35
36
37
38
39
40
41
42
43
44
use Getopt::Std;

sub usage() {
    print << "END";
Usage: $0 [-v n] [-e] <on|off|cycle> <node ...>
-e     Surpress sending of event - for use by scripts that have already sent it
-v n   Run with verbosity level n
END
    1;
}
45

46
47
48
#
# Un-taint path since this gets called from setuid scripts.
#
49
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:@prefix@/bin';
50
51
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

52
53
54
55
56
57
my $op = "";			#stores operation (on/off/cyc)
my @machines = ();		#stores machines to operate on
my $ip = "";			#stores IP of a power controller
my $outlet = 0;			#stores number of an outlet
my %IPList = ();		#holds machine/ip pairs
my %OutletList = ();		#holds machine/outlet pairs
58
my $exitval = 0;
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#
# Process command-line arguments
#
my %opt = ();
getopts("v:he",\%opt);

if ($opt{h}) {
    exit &usage;
}

# useful values are 0, 1, 2 and 3
my $verbose = 0;
if ($opt{v}) {
    $verbose = $opt{v};
}
print "VERBOSE ON: Set to level $verbose\n" if $verbose;

my $sendevent = 1;
if ($opt{e}) {
    $sendevent = 0;
}

82
83
84
#
# Must have at least an op and a machine, so at least 2 ARGV
#
85
86
87
88
if (@ARGV < 2) {
    exit &usage;
}

89

90
91
92
#
# Read in ARGV
#
93
$op = shift (@ARGV);
94
if ($op =~ /^(on|off|cycle)$/) {
95
    $op = $1;
96
} else {
97
    exit &usage;
98
}
99
100
101
102

#
# Untaint the arguments.
#
103
@machines = @ARGV;
104
foreach my $n (0..$#ARGV) {
105
    $machines[$n] =~ s/^([-\@\w.]+)$/$1/;
106
107

    # Shark hack
108
109
110
111
    if ($machines[$n] =~ /^(sh\d+)-[1-8]$/) {
	print "WARNING: Rebooting $machines[$n] will reboot all of shelf $1!\n";
	$machines[$n] = $1;
    }
112
    # End shark hack
113
}
114
115
116
117

#
# Lowercase nodenames and remove duplicates
#
118
119
my %all_nodes = ();
foreach my $n (0..$#machines) {
120
    $all_nodes{"\L$machines[$n]"} = 1; # Lowercase it and use as hash key
121
}
122
@machines= sort keys %all_nodes;
123

124
125
126
127
#
# Dump the args
#
print "do \"$op\" to @machines\n" if $verbose > 1;
128

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#
# ElabinElab is special; Do local permission checks, build up a node list
# and then contact the proxy to do the actual work. No perl bindings yet,
# so have to use the python client program. 
#
if ($ELABINELAB) {
    my @nodelist = ();
    
    foreach my $node (@machines) {
	if (!(($UID == 0) ||
	      TBNodeAccessCheck($UID, TB_NODEACCESS_POWERCYCLE, $node))) {
	    warn "You are not authorized to control $node. Skipping...\n";
	    next;
	}
	push(@nodelist, $node);
    }

    exit(0)
	if (! @nodelist);
148

149
150
151
152
153
154
155
156
157
158
159
160
161
    libxmlrpc::Config({"server"  => $RPCSERVER,
		       "verbose" => 1,
		       "cert"    => $RPCCERT,
		       "portnum" => $RPCPORT});

    my $rval = libxmlrpc::CallMethod("elabinelab", "power",
				     {"op"    => "$op",
				      "nodes" => join(",", @nodelist)});
    if (!defined($rval)) {
	exit(-1);
    }
    exit($rval);
}
Mac Newbold's avatar
Mac Newbold committed
162

Mac Newbold's avatar
Mac Newbold committed
163
164
165
166
167
#
# Get table of users <--> machines for those nodes, to make sure
# user is authorized to control the nodes
#

168
169
my %timelimited = ();

170
171
172
173
174
175
176
#
# Though TBNodeAccessCheck can check all nodes at once, we do it one at
# a time, so that we can get a list of all nodes we have access to. This
# is primarily to preserve the pre-libification behavior of power
#
my %outlets = ();
foreach my $node (@machines) {
177
    if (!(($UID == 0) || TBNodeAccessCheck($UID,TB_NODEACCESS_POWERCYCLE,$node))) {
178
	warn "You are not authorized to control $node. Skipping...\n";
179
	next;
180
    }
181

182
183
184
185
186
187
188
189
190
191
192
    my $result = DBQueryFatal("select o.power_id, o.outlet, " .
	"(CURRENT_TIMESTAMP - power_time > last_power) " .
	"from outlets as o left join nodes as n on " .
	"(o.node_id = n.node_id) ".
	# Shark hack
	"or (n.node_id = concat(o.node_id,'-1')) " .
	# End shark hack
	"left join node_types as t on n.type=t.type ".
	"where o.node_id='$node'");
    if ($result->num_rows() == 0) {
	warn "No outlets table entry found for $node. Skipping...\n";
193
194
195
196
	SENDMAIL($TBOPS,
		 "No power outlet for $node",
		 "Unable to power '$op' $node; no outlets table entry!",
		 $TBOPS);
197
	next;
198
    }
Mac Newbold's avatar
Mac Newbold committed
199

200
201
202
203
    my ($power_id, $outlet, $time_ok) = $result->fetchrow();

    #
    # Check for rate-limiting, and update the last power cycle time
204
205
206
207
    # if it's been long enough. Root gets to bypass the checks, and
    # we only update the timestamp if it is being turned on or cycled,
    # to allow off then on without waiting (unless the on is too close 
    # to a previos on/cycle command)
208
    #
209
210
211
212
213
214
215
216
    if ( $op ne "off" ) {
	if (! ($time_ok || ($UID == 0)) ) {
	    warn "$node was power cycled recently. Skipping...\n";
	    next;
	} else {
	    DBQueryFatal("update outlets set last_power=CURRENT_TIMESTAMP " .
			 "where node_id = '$node'");
	}
217
    }
218

219
220
221
222
    #
    # Associate this node with the power controller it is attached to
    #
    push @{$outlets{$power_id}}, [$node, $outlet];
223
224
}

225
print "machines= ",join(" ",@machines),"\n" if $verbose;
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
print "devices= ", join(" ",keys %outlets),"\n" if $verbose;

foreach my $power_id (keys %outlets) {

    #
    # Get the list of outlet numbers used on this power controller
    #
    my @outlets = ();
    my @nodes = ();
    foreach my $node (@{$outlets{$power_id}}) {
	my ($node_id, $outlet) = @$node;
	push @outlets, $outlet;
	push @nodes, $node_id;

    }
    my $nodestr = join(",",@nodes);

    #
    # Find out some information about this power controller
    #
246
247
248
249
    my $result = DBQueryFatal("select n.type, i.IP, t.class ". 
	"from nodes as n " .
	"left join interfaces as i on n.node_id=i.node_id " .
	"left join node_types as t on n.type=t.type " .
250
251
252
253
254
255
	"where n.node_id='$power_id'");
    if ($result->num_rows() == 0) {
	warn "No entry found for power controller $power_id. Skipping " .
	    "$nodestr\n";
	$exitval++;
	next;
256
    }
257
    my ($type, $IP, $class) = $result->fetchrow();
258
259
260
261
262
263
264
265
266

    #
    # Finally, we look at the controller type and construct the proper type
    # of object
    #
    my $errors = 0;
    if ($type eq "APC") {
	my $device = new snmpit_apc($IP,$verbose);
	if (!defined $device) {
267
	    warn "Unable to contact controller for $nodestr. Skipping...\n";
268
	    next;
269
	} else {
270
271
272
273
274
275
276
277
278
279
	    print "Calling device->power($op,@outlets)\n"
		if $verbose > 1;
	    if ($device->power($op,@outlets)) {
		print "Control of $nodestr failed.\n";
		$errors++;
	    }
	}
    } elsif ($type eq "RPC27") {
	if (rpc27ctrl($op,$power_id,@outlets)) {
	    print "Control of $nodestr failed.\n"; $exitval++;
280
	}
281
282
283
284
285
286
287
    } elsif (($class eq "sg") || ($type eq "garcia")) {
	# XXX: 'garcia' is temporary until stargates are subnodes of
	# garcias
	if (sgmotectrl($op,@nodes)) {
	    print "Control of $nodestr failed.\n"; $exitval++;
	    $errors++;
	}
288
    } else {
289
290
291
292
293
294
295
	print "power: Unknown power type '$type'\n";
	$errors++;
    }

    if (!$errors) {
	foreach my $node (@nodes) {
	    print "$node now ",($op eq "cycle" ? "rebooting" : $op),"\n";
296
	    if ($sendevent) {
297
298
		my $state = TBDB_NODESTATE_SHUTDOWN;
		TBSetNodeEventState($node,$state);
299
	    }
300
	}
301
302
    } else {
	$exitval += $errors;
303
    }
304

305
}
306
307
308

# Return 0 on success. Return non-zero number of nodes that failed.
exit $exitval;