#!/usr/bin/perl -wT
# # Testbed Power Control script # # power [on|off|cycle] <node> [<node>] ... # ############################################################
# # Configure variables # use lib "/usr/testbed/lib"; use libdb; use power_rpc27; use snmpit_ds72; use libtestbed; use strict; use English; use Getopt::Std; #this line is necessary for "getopts" function. see Perl getopts Howto #define usage() function 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; }
# # Un-taint path since this gets called from setuid scripts. # $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/testbed/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
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 my $exitval = 0;
# # Process command-line arguments # my %opt = (); getopts("v:he",\%opt); # the first parameter is a string for all options, second parameter is a hash
if ($opt{h}) { exit &usage; # "h" for help, execute usage }
# useful values are 0, 1, 2 and 3 my $verbose = 0; if ($opt{v}) { $verbose = $opt{v}; # opt{v} will return the values of verbose, that's why we need a colon in the opt string } print "VERBOSE ON: Set to level $verbose\n" if $verbose;
my $sendevent = 1; if ($opt{e}) { $sendevent = 0; }
# # Must have at least an op and a machine, so at least 2 ARGV # if (@ARGV < 2) { exit &usage; }
# # Read in ARGV # $op = shift (@ARGV); if ($op =~ /^(on|off|cycle)$/) { $op = $1; } else { exit &usage; }
# # Untaint the arguments. # @machines = @ARGV; # parse the values of machine to be powered foreach my $n (0..$#ARGV) { $machines[$n] =~ s/^([-\@\w.]+)$/$1/;
# Shark hack if ($machines[$n] =~ /^(sh\d+)-[1-8]$/) { print "WARNING: Rebooting $machines[$n] will reboot all of shelf $1!\n"; $machines[$n] = $1; } # End shark hack }
# # Lowercase nodenames and remove duplicates # my %all_nodes = (); foreach my $n (0..$#machines) { $all_nodes{"\L$machines[$n]"} = 1; # Lowercase it and use as hash key } @machines= sort keys %all_nodes;
# # Dump the args # print "do \"$op\" to @machines\n" if $verbose > 1;
# # Get table of users <--> machines for those nodes, to make sure # user is authorized to control the nodes #
my %timelimited = ();
# # 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) { if (!(($UID == 0) || TBNodeAccessCheck($UID,TB_NODEACCESS_POWERCYCLE,$node))) { warn "You are not authorized to control $node. Skipping...\n"; next; }
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"; next; }
my ($power_id, $outlet, $time_ok) = $result->fetchrow();
# # Check for rate-limiting, and update the last power cycle time # 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) # 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'"); } }
# # Associate this node with the power controller it is attached to # push @{$outlets{$power_id}}, [$node, $outlet]; }
print "machines= ",join(" ",@machines),"\n" if $verbose; 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 # my $result = DBQueryFatal("select n.type, i.IP ". "from nodes as n left join interfaces as i on n.node_id=i.node_id " . "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; } my ($type, $IP) = $result->fetchrow();
# # Finally, we look at the controller type and construct the proper type # of object # my $errors = 0; #the codes handling a snmp-controlled power device if ($type eq "DS72-RPC14") { my $device = new snmpit_ds72($IP,$verbose); if (!defined $device) { warn "Unable to contact controller for $nodestr. Skipping...\n"; next; } else { 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++; } } else { print "power: Unknown power type '$type'\n"; $errors++; }
if (!$errors) { foreach my $node (@nodes) { print "$node now ",($op eq "cycle" ? "rebooting" : $op),"\n"; if ($sendevent) { my $state = TBDB_NODESTATE_SHUTDOWN; TBSetNodeEventState($node,$state); } } } else { $exitval += $errors; }
}
# Return 0 on success. Return non-zero number of nodes that failed. exit $exitval;
|