idlepower.in 4.64 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use Getopt::Std;
use strict;

#
# This gets invoked from crontab.
#
sub usage()
{
    print STDOUT "Usage: idlepower [-n] [-s seconds-of-idle]\n";
    exit(-1);
}
# Hidden switch: -r = root mode - used by idlemail
my $optlist   = "nrs:d";

#
# Configure variables
#
my $TB		= "@prefix@";
my $DBNAME	= "@TBDBNAME@";
my $TBOPS	= "@TBOPSEMAIL@";
my $POWER	= "$TB/bin/power";

# Testbed Support libraries
use lib "@prefix@/lib";
use libdb;
use User;
use emutil;
use libtestbed;

# Locals.
my $no_action = 0;
my $seconds_of_idle;
my $rootokay  = 0;
my $debug     = 0;

# Untaint the path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

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

# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
my %options = ();
if (! getopts($optlist, \%options)) { usage(); }
if (defined($options{"d"})) { $debug = 1; }
if (defined($options{"n"})) { $no_action= 1; }
if (defined($options{"r"})) { $rootokay = 1; }
if (defined($options{"s"})) { 
    if ($options{"s"} =~ /^\d+$/) {
    	$seconds_of_idle = $options{'s'}; 
    }
}

# This script is setuid, so please do not run it as root. Hard to track
# what has happened.
if ($UID == 0 && (!defined($rootokay) || !$rootokay) ) {
    die("*** $0:\n".
	"    Please do not run this as root! Its already setuid!\n");
}

if (@ARGV != 0) {
    usage();
}

# Only admins can do this.
if ($UID) {
    my $this_user = User->ThisUser();
    if (! defined($this_user)) {
	die("You ($UID) do not exist!\n");
    }
    if (!$this_user->IsAdmin()) {
	die("*** $0:\n".
	    "    Only testbed administrators can issue an idlepower!\n");
    }
}

# Global enable
my $idlepower_enable;
if (! TBGetSiteVar("general/idlepower_enable", \$idlepower_enable)) {
    print "Error getting sitevar 'general/idlepower_enable'\n";
    exit(-1);
}
if (!$idlepower_enable) {
    print "Idle power saving is globally disabled. Exiting ...\n";
    exit(0);
}

# Default value for seconds of idle is a sitevar.
if (!defined($seconds_of_idle)) {
    if (! TBGetSiteVar("general/idlepower_idletime", \$seconds_of_idle)) {
	print "Error getting sitevar 'general/idlepower_idletime'\n";
	exit(-1);
    }
}

#
# Based in query in ptopgen ... the idea is to find free nodes sitting
# in PXEWAIT for longer then the idle threshold. We only look for nodes
# in PXEWAIT cause we know the power off will not scrog the disk. A node
# that is up and running from its disk might very well not reboot nicely.
#
# Note that we use the node_type_attributes "idlepower_enabled" to
# determine if a node type should be considered for powerdown on idle.
#
my $result =
    DBQueryFatal("select a.node_id,a.type,a.phys_nodeid,t.class,t.issubnode,".
		 "(unix_timestamp(now()) - a.state_timestamp)".
		 " as idle_time, ".
		 "(b.pid is not null and b.eid is not null), ".
		 "  np.reserved_pid is not null,np.eventstate ".
		 "from nodes as a ".
		 "left join reserved as b on a.node_id=b.node_id ".
		 "left join nodes as np on a.phys_nodeid=np.node_id ".
		 "left join node_types as t on t.type=a.type ".
		 "left outer join ". 
		 "  (select type,attrvalue ".
		 "   from node_type_attributes ".
		 "   where attrkey='idlepower_enable' ".
		 "   group by type) as idlepower_enabled ".
		 "  on t.type=idlepower_enabled.type ".
		 "where (b.node_id is null and t.class='pc' and ".
		 "    (np.eventstate='" . TBDB_NODESTATE_PXEWAIT . "')) and ".
		 "      (a.role='testnode' and t.isremotenode=0) and ".
		 "      idlepower_enabled.attrvalue is not NULL");

# Scan the results, checking permissions and adding to the list
# You get to use a node type if no pid was specified (that is, you get
# to use all nodes), or if there is no entry in the perms table for
# the type/class of node.
#
my @nodes;

while (my ($node,$type,$physnode,$class,$issubnode,$idle_time,$reserved,
        $prereserved,$eventstate) = $result->fetchrow_array) {
    next if ($issubnode || $reserved || $prereserved);
    next if ($idle_time < $seconds_of_idle);
    print "$node: $idle_time\n"
	if ($debug);
    push(@nodes, $node);
    if ($type || $physnode || $class || $eventstate) {}
}

if ($#nodes > 0) {
    print "Powering off @nodes\n";
} else {
    print "No nodes suitable for powering off.\n";
    exit(0);
}
exit(0)
    if ($no_action);

my $output = emutil::ExecQuiet("$POWER off @nodes");
if ($?) {
    print $output;
    
    SENDMAIL($TBOPS,
	     "idlepower failed",
	     "Failed to power off: @nodes\n\n".
	     "Power output:\n".
	     "$output\n",
	     $TBOPS);
    exit(-1);
}