Commit f7219346 authored by Leigh B Stoller's avatar Leigh B Stoller

"improvements" to prereserve:

New option -s datetime to specify a starting time for the pre-reserve.
New option -e datetime to specify a ending time for the pre-reserve.

The idea is that you can schedule a pre-reserve to begin sometime later,
and you can optionally specify a time for a prereserve to terminate.
There is a new script that runs from cron that checks for pre-reserves
that need to be started or terminated.

For example:

boss> wap prereserve -s '2012-09-14 09:08:15' -e '2012-09-15' emulab-ops 50

You can use any datetime string that is valid for str2time. At some point
it would be nice to allow natural language dates ("tomorrow") but that
requires a another bunch of perl packages and I didn't want to bother.

NOTE: when using -e, -r is implied; in other words, when the
pre-reserve is terminated, the table entry is cleared *and* the
reserved_pid of all of the nodes is cleared. Any experiments using
those nodes is left alone, although if the user does a swapmod, they
could easily lose the nodes if another pre-reserve is set up that
promises those nodes to another project.
parent 43e9f520
......@@ -3335,7 +3335,7 @@ sub CheckPreReserve($$)
# not look in the object ($self) since it might be stale.
#
my $query_result =
DBQueryWarn("select reserved_pid,count from nodes ".
DBQueryWarn("select reserved_pid,count,active from nodes ".
"left join node_reservations on ".
" node_reservations.pid=nodes.reserved_pid ".
"where nodes.node_id='$node_id'");
......@@ -3348,10 +3348,10 @@ sub CheckPreReserve($$)
# if the reervation request is still active. If not, we can clear it,
# which will allow it to be set again below, if needed.
#
my ($pid,$count) = $query_result->fetchrow_array();
my ($pid,$count,$active) = $query_result->fetchrow_array();
if (defined($pid)) {
goto done
if (defined($count));
if (defined($count) && $active);
DBQueryWarn("update nodes set reserved_pid=null ".
"where node_id='$node_id'");
......@@ -3360,10 +3360,11 @@ sub CheckPreReserve($$)
print "Clearing pre reserve for $node_id\n";
}
}
# Find only active unfilled reservations.
$query_result =
DBQueryWarn("select pid,count from node_reservations ".
"where count>0 and ".
"where active=1 and count>0 and ".
" (types is null or ".
" FIND_IN_SET('$type', types)) ".
"order by priority desc, created asc ".
......@@ -3386,7 +3387,7 @@ sub CheckPreReserve($$)
if ($count == 1) {
SENDMAIL($TBOPS, "Pre Reservation for $pid has completed",
"The pre reservation request for project $pid, ".
"has been fullfilled\n");
"has been fullfilled\n", $TBOPS);
}
}
}
......
......@@ -2476,6 +2476,9 @@ CREATE TABLE `node_reservations` (
`creator` varchar(8) NOT NULL default '',
`creator_idx` mediumint(8) unsigned NOT NULL default '0',
`created` datetime default NULL,
`start` datetime default NULL,
`end` datetime default NULL,
`active` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`pid_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
......
#
# Add stuff to handle importing images from other locations.
#
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("node_reservations", "start")) {
DBQueryFatal("alter table node_reservations add ".
" `start` datetime default NULL");
}
if (!DBSlotExists("node_reservations", "end")) {
DBQueryFatal("alter table node_reservations add ".
" `end` datetime default NULL");
}
if (!DBSlotExists("node_reservations", "active")) {
DBQueryFatal("alter table node_reservations add ".
" `active` tinyint(1) NOT NULL default '0'");
}
DBQueryFatal("update node_reservations set active=1 ".
"where start is null");
return 0;
}
1;
# Local Variables:
# mode:perl
# End:
......@@ -30,7 +30,8 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
prereserve grantimage getimages localize_mfs \
management_iface sharevlan check-shared-bw \
addspecialdevice addspecialiface imagehash clone_image \
addvpubaddr imageinfo ctrladdr image_import
addvpubaddr imageinfo ctrladdr image_import \
prereserve_check
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage webdumpdescriptor
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2011 University of Utah and the Flux Group.
# Copyright (c) 2003-2012 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
use Date::Parse;
#
# Set up and clear node pre-reservations.
#
sub usage()
{
print STDERR "Usage: prereserve [-t typelist] [-n priority] pid count\n";
print STDERR "Usage: prereserve [-t typelist] [-n priority] ".
"[-s start] [-e end [-r]] pid count\n";
print STDERR " prereserve -c [-r] pid\n";
print STDERR " prereserve -i pid\n";
print STDERR " prereserve -a pid\n";
print STDERR " prereserve -l\n";
print STDERR " -h This message\n";
print STDERR " -t Comma separated list of node types\n";
......@@ -24,21 +27,30 @@ sub usage()
print STDERR " -r Revoke current prereserve for project (use with -c)\n";
print STDERR " -i Show pending prereserve for project\n";
print STDERR " -l List all pending prereserves\n";
print STDERR " -s Optional start time to begin pre reservation\n";
print STDERR " -e Optional end time for pre reservation.\n";
print STDERR " Implies -c -r options at termination time.\n";
print STDERR " -a Activate a pending reservation (internal option)\n";
exit(-1);
}
my $optlist = "hdct:n:ilr";
my $optlist = "hdct:n:ilre:s:ma";
my $priority = 0;
my $debug = 0;
my $info = 0;
my $list = 0;
my $clear = 0;
my $revoke = 0;
my $sendmail = 0;
my $activate = 0;
my $starttime;
my $endtime;
my $typelist;
my $pid;
my $count;
# Protos
sub fatal($);
sub StartReservation($);
#
# Please do not run as root. Hard to track what has happened.
......@@ -52,6 +64,7 @@ if ($UID == 0) {
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
#
# Testbed Support libraries
......@@ -98,6 +111,12 @@ if (defined($options{r})) {
if (defined($options{d})) {
$debug = 1;
}
if (defined($options{"m"})) {
$sendmail = 1;
}
if (defined($options{"a"})) {
$activate = 1;
}
if (defined($options{t})) {
$typelist = $options{t};
}
......@@ -107,7 +126,21 @@ if (defined($options{i})) {
if (defined($options{l})) {
$list = 1;
}
if ($info || $clear || $revoke) {
if (defined($options{"e"})) {
$endtime = $options{"e"};
if (!defined(str2time($endtime))) {
fatal("Could not parse -e option.");
}
}
if (defined($options{"s"})) {
$starttime = $options{"s"};
if (!defined(str2time($starttime))) {
fatal("Could not parse -s option.");
}
}
if ($info || $clear || ($revoke && !$endtime)) {
usage()
if (@ARGV != 1 || ($revoke && !$clear));
......@@ -117,6 +150,12 @@ elsif ($list) {
usage()
if (@ARGV);
}
elsif ($activate) {
usage()
if (@ARGV != 1);
exit(StartReservation($ARGV[0]));
}
else {
usage()
if (@ARGV != 2);
......@@ -129,19 +168,6 @@ else {
}
}
#
# Verify user, must be admin.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
if (!$this_user->IsAdmin()) {
fatal("You are not a testbed administrator!");
}
my $uid = $this_user->uid();
my $uid_idx = $this_user->uid_idx();
#
# List all pending prereserves.
#
......@@ -151,8 +177,8 @@ if ($list) {
"order by priority desc, pid asc");
if ($query_result->numrows) {
printf("%-15s %-10s %-10s %-18s %-3s %s\n",
"Project", "Cnt (Cur)", "Creator", "When", "Pri", "Types");
printf("%-18s %-4s %-3s %-10s %-18s %-3s %s\n",
"Project", "Need", "Got", "Creator", "Created", "Pri", "Types");
print "-------------------------------------------------------------\n";
}
......@@ -163,6 +189,9 @@ if ($list) {
my $creator = $row->{'creator'};
my $types = $row->{'types'} || "";
my $priority= $row->{'priority'};
my $starttime = $row->{'start'};
my $endtime = $row->{'end'};
my $active = $row->{'active'};
my $current = 0;
my $current_result =
......@@ -171,8 +200,20 @@ if ($list) {
($current) = $current_result->fetchrow_array()
if ($current_result && $current_result->numrows);
printf("%-15s %-10s %-10s %-18s %-3d %s\n",
$pid, "$count ($current)", $creator, $created, $priority, $types);
printf("%-20s %-4d %-3d %-10s %-18s %-3d %s\n",
$pid, $count, $current, $creator, $created,
$priority, $types);
if (defined($starttime)) {
print " *** Starts: $starttime";
if (! $active) {
print " (pending)";
}
if (defined($endtime)) {
print " Ends: $endtime";
}
print "\n";
}
}
exit(0);
}
......@@ -183,18 +224,6 @@ if (!defined($project)) {
}
my $pid_idx = $project->pid_idx();
#
# Clear and exit.
#
if ($clear) {
DBQueryFatal("delete from node_reservations where pid_idx='$pid_idx'");
if ($revoke) {
DBQueryFatal("update nodes set reserved_pid=null ".
"where reserved_pid='$pid'");
}
exit(0);
}
#
# Show and exit.
#
......@@ -219,12 +248,26 @@ if ($info) {
my $creator = $row->{'creator'};
my $types = $row->{'types'} || "*";
my $priority= $row->{'priority'};
my $starttime = $row->{'start'};
my $endtime = $row->{'end'};
my $active = $row->{'active'};
printf("%-15s %-10s %-10s %-18s %-3s %s\n",
"Project", "Cnt (Cur)", "Creator", "When", "Pri", "Types");
printf("%-4s %-3s %-10s %-18s %-3s %s\n",
"Need", "Got", "Creator", "When", "Pri", "Types");
print "-------------------------------------------------------------\n";
printf("%-15s %-10s %-10s %-18s %-3d %s\n",
$pid, "$count ($current)", $creator, $created, $priority, $types);
printf("%-4s %-3s %-10s %-18s %-3d %s\n",
$count, $current, $creator, $created, $priority, $types);
if (defined($starttime)) {
print "*** Starts: $starttime";
if (! $active) {
print " (pending)";
}
if (defined($endtime)) {
print " Ends: $endtime";
}
print "\n";
}
}
if ($current) {
print "-------------------------------------------------------------\n";
......@@ -246,10 +289,44 @@ if ($info) {
exit(0);
}
#
# Verify user, must be admin or root.
#
my $this_user;
if ($UID) {
$this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
if (!$this_user->IsAdmin()) {
fatal("You are not a testbed administrator!");
}
}
#
# Clear and exit.
#
if ($clear) {
DBQueryFatal("delete from node_reservations where pid_idx='$pid_idx'");
if ($revoke) {
DBQueryFatal("update nodes set reserved_pid=null ".
"where reserved_pid='$pid'");
}
exit(0);
}
#
# Do not allow this as root; we want proper history.
#
if ($UID == 0) {
fatal("Please do not run this as root!");
}
my $uid = $this_user->uid();
my $uid_idx = $this_user->uid_idx();
# Sanity check the type list.
my @types = ();
if (defined($typelist)) {
@types = split(",", $typelist);
my @types = split(",", $typelist);
foreach my $typename (@types) {
my $type = NodeType->Lookup($typename);
if (!defined($type)) {
......@@ -257,9 +334,6 @@ if (defined($typelist)) {
}
}
}
else {
@types = ("*");
}
#
# Lets say that a current request is an error. delete and recreate.
......@@ -272,82 +346,137 @@ if ($query_result->numrows) {
}
#
# First see if we can find enough (or any) nodes to satisfy the prereserve.
# Enter the table info, but mark as not active until later.
#
$query_result =
DBQueryFatal("select node_id from nodes where reserved_pid='$pid'");
my $typearg = (defined($typelist) ? ",type='$typelist'" : "");
my $startarg = "";
my $endarg = "";
my $current = $query_result->numrows;
if ($starttime) {
my $tmp = str2time($starttime);
$startarg = ",start=FROM_UNIXTIME($tmp)";
}
if ($endtime) {
my $tmp = str2time($endtime);
$endarg = ",end=FROM_UNIXTIME($tmp)";
}
DBQueryFatal("insert into node_reservations set ".
" pid='$pid', pid_idx='$pid_idx', count='$count', ".
" creator='$uid', creator_idx='$uid_idx', ".
" created=now(),active=0 $typearg $startarg $endarg");
print "Node reservation request for $count nodes has been created.\n";
if ($current) {
print "There are currently $current nodes with a pre-reservation ".
"for project $pid.\n";
if ($current >= $count) {
goto done;
}
#
# Activate, although note that the cron job (prereserve_check) might
# have beat us to it already. We check the active bit below.
#
if (!defined($starttime) || str2time($starttime) <= time()) {
exit(StartReservation($pid));
}
exit(0);
#
# First check free nodes.
# Activate a reservation request; find as many nodes as possible,
# and then mark it as active.
#
foreach my $type (@types) {
last
if ($current >= $count);
sub StartReservation($)
{
my ($pid) = @_;
my $tcount = 0;
my $tclause = "";
if ($type ne "*") {
$tclause = "and node_types.type='$type'";
DBQueryFatal("lock tables nodes write, node_types read, ".
" node_reservations write, reserved read");
my $query_result =
DBQueryFatal("select * from node_reservations where pid='$pid'");
if (!$query_result->numrows) {
fatal("No reservation defined for project");
}
my $row = $query_result->fetchrow_hashref();
my $active = $row->{'active'};
my $count = $row->{'count'};
my $types = $row->{'types'};
my @types = (defined($types) ? split(",", $types) : ("*"));
# Someone beat us to it.
if ($active) {
DBQueryFatal("unlock tables");
return 0;
}
#
# It is not possible to combine multiple table update and a limit.
# So, have to lock the nodes and reserved table for a moment, and
# do it the hard way.
# First see if we can find enough (or any) nodes to satisfy the prereserve.
#
DBQueryFatal("lock tables nodes write, node_types read, reserved read");
$query_result =
DBQueryFatal("select nodes.node_id from nodes ".
"left join reserved on reserved.node_id=nodes.node_id ".
"left join node_types on node_types.type=nodes.type ".
"where reserved.node_id is null and ".
" nodes.role='testnode' and ".
" node_types.class='pc' and ".
" nodes.reserved_pid is null $tclause");
while (my ($node_id) = $query_result->fetchrow_array()) {
DBQueryFatal("update nodes set reserved_pid='$pid' ".
"where node_id='$node_id'");
$current++;
$tcount++;
DBQueryFatal("select node_id from nodes where reserved_pid='$pid'");
my $current = $query_result->numrows;
if ($current) {
print "There are currently $current nodes with a pre-reservation ".
"for project $pid.\n";
if ($current >= $count) {
goto done;
}
}
#
# First check free nodes.
#
foreach my $type (@types) {
last
if ($current >= $count);
my $tcount = 0;
my $tclause = "";
if ($type ne "*") {
$tclause = "and node_types.type='$type'";
}
$query_result =
DBQueryFatal("select nodes.node_id from nodes ".
"left join reserved on ".
" reserved.node_id=nodes.node_id ".
"left join node_types on node_types.type=nodes.type ".
"where reserved.node_id is null and ".
" nodes.role='testnode' and ".
" node_types.class='pc' and ".
" nodes.reserved_pid is null $tclause");
while (my ($node_id) = $query_result->fetchrow_array()) {
DBQueryFatal("update nodes set reserved_pid='$pid' ".
"where node_id='$node_id'");
$current++;
$tcount++;
last
if ($current >= $count);
}
if ($tcount) {
print "Set reserved_pid for $tcount (free)" .
($type eq "*" ? "" : " $type") . " nodes.\n";
}
}
if ($tcount) {
print "Set reserved_pid for $tcount (free)" .
($type eq "*" ? "" : " $type") . " nodes.\n";
if ($current >= $count) {
print "Got as many nodes as you wanted from the free pool. Yippie!\n";
}
#
# Update the reservation entry, and mark as active.
#
done:
$count -= $current;
$count = 0 if ($count < 0);
DBQueryFatal("update node_reservations set ".
" count='$count',active=1 ".
"where pid='$pid'");
DBQueryFatal("unlock tables");
if ($count == 0 && $sendmail) {
SENDMAIL($TBOPS, "Pre Reservation for $pid has completed",
"The pre reservation request for project $pid, ".
"has been fullfilled\n", $TBOPS);
}
return 0
}
DBQueryFatal("unlock tables");
if ($current >= $count) {
print "Got as many nodes as you wanted from the free pool. Yippie!\n";
}
#
# Still need more nodes. Insert a node_reservation entry for nfree, for the
# remaining nodes we need.
#
done:
$count -= $current;
$count = 0 if ($count < 0);
$typelist = (defined($typelist) ? "'$typelist'" : "NULL");
DBQueryFatal("insert into node_reservations set ".
" pid='$pid', pid_idx='$pid_idx', count='$count', ".
" types=$typelist, creator='$uid', creator_idx='$uid_idx', ".
" created=now()");
print "Node reservation request for $count nodes has been created.\n";
exit(0);
sub fatal($)
{
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2012 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Look for pending pre-reserves that need to be activated.
#
sub usage()
{
print STDERR "Usage: prereserve_check [-d] [-n]\n";
exit(-1);
}
my $optlist = "dn";
my $debug = 0;
my $impotent = 0;
# Protos
sub fatal($);
#
# Configure variables
#
my $TB = "@prefix@";
my $PRERESERVE = "$TB/sbin/prereserve";
my $TBOPS = "@TBOPSEMAIL@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use libtestbed;
use emutil;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
usage()
if (@ARGV);
#
# Look for active pre reserves that need to be terminated.
#
my $query_result =
DBQueryFatal("select * from node_reservations ".
"where end is not null and ".
" UNIX_TIMESTAMP(end) < UNIX_TIMESTAMP(now())");
while (my $row = $query_result->fetchrow_hashref()) {
my $pid = $row->{'pid'};