idlepower.in 5.33 KB
Newer Older
1 2 3
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
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 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
#
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);
}