Commit 5c998ffc authored by Leigh Stoller's avatar Leigh Stoller

Lets make it easier to manage pre reservations (Mike, this was Rob's

idea).

New script and table to manage node pre reservations. Lets just look
at the script.

To create a reservation:

    myboss> wap prereserve -t pc850 testbed 2
    Node reservation request for 2 nodes has been created.

To see the reservation status for testbed

    myboss> wap prereserve -i testbed
    Project         Cnt (Cur)  Creator    When               Pri Types
    -------------------------------------------------------------
    testbed         1 (1)      stoller    2011-08-12 12:39:07 0   pc850

    which says 1 node is pending and 1 node has already been
    pre-reserved. 

To clear the above reservation request (and optionally, clean
reserved_pid from the nodes table).

    myboss> wap prereserve -c -r testbed

    The -r is optional, otherwise just the reservation request is
    cleared, and nodes continue to be pre-reserved to the project.

To see a list of all reservation requests:

    myboss> wap prereserve -l


So, when a node is released in nfree, we look at the reservation
status for the node and any pending reservation requests.

1. If the node has a reserved_pid and that request is still pending
   (still in the table), nothing is changed.

2. If the node has a reserved_pid, but the request has been cleared
   from the pending table, then clear reserved_pid.

3. If reserved_pid is null, and there are pending requests, then pick
   the highest priority, most recent dated, request, and set
   reserved_pid to that project.

Options:

* -n <pri> - is how you set a priority. Lowest is zero, choose a
  higher number if you want this reservation request to be considered
  before others. In a tie, look at the date of creation, and use the
  oldest.

* -t <typelist> - a comma separated list of types you want to
  consider. Types are considered in order, but not in the fancy way
  you might imagine.
parent 13b6b1d7
......@@ -2916,5 +2916,84 @@ sub GetJailIP($;$)
return "${IPBASE1}.${pnet}.${pnode2}.${num}";
}
#
# Check for, and update a node pre reservation.
#
sub CheckPreReserve($$)
{
my ($self, $quiet) = @_;
my $result = undef;
#
# Look for a pre-reserve request.
#
my $type = $self->type();
my $node_id = $self->node_id();
DBQueryWarn("lock tables node_reservations write, nodes write")
or return undef;
#
# Need to check for existing reserved_pid, but have to go to the DB,
# not look in the object ($self) since it might be stale.
#
my $query_result =
DBQueryWarn("select reserved_pid,count from nodes ".
"left join node_reservations on ".
" node_reservations.pid=nodes.reserved_pid ".
"where nodes.node_id='$node_id'");
goto done
if (!defined($query_result) || !$query_result->numrows);
#
# If there is a reserved pid already set for this node, check to see
# 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();
if (defined($pid)) {
goto done
if (defined($count));
DBQueryWarn("update nodes set reserved_pid=null ".
"where node_id='$node_id'");
if (!$quiet) {
print "Clearing pre reserve for $node_id\n";
}
}
$query_result =
DBQueryWarn("select pid from node_reservations ".
"where count>0 and ".
" (types is null or ".
" FIND_IN_SET('$type', types)) ".
"order by priority desc, created asc ".
"limit 1");
goto done
if (!$query_result);
if ($query_result->numrows) {
my ($pid) = $query_result->fetchrow_array();
if (DBQueryWarn("update nodes set reserved_pid='$pid' ".
"where node_id='$node_id'")) {
DBQueryWarn("update node_reservations set count=count-1 ".
"where pid='$pid'");
$result = $pid;
}
}
done:
DBQueryWarn("unlock tables");
if (defined($result) && !$quiet) {
print "Setting pre reserve for $node_id to $result\n";
}
return $result;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -597,6 +597,9 @@ foreach my $node (@freed_nodes) {
DBQueryWarn("delete from last_reservation ".
"where node_id='$node_id'")
or $error++;
# Handle pre-reserve.
my $rpid = $node->CheckPreReserve($quiet);
}
next;
}
......@@ -612,6 +615,9 @@ foreach my $node (@freed_nodes) {
"values ($pid_idx, '$node_id', '$pid')");
}
# Handle pre-reserve.
my $rpid = $node->CheckPreReserve($quiet);
print "Releasing node '$node_id' ...\n"
if (!$quiet);
if (DBQueryWarn("delete from reserved where node_id='$node_id'")) {
......
......@@ -2355,6 +2355,23 @@ CREATE TABLE `node_idlestats` (
PRIMARY KEY (`node_id`,`tstamp`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `node_reservations`
--
DROP TABLE IF EXISTS `node_reservations`;
CREATE TABLE `node_reservations` (
`pid` varchar(12) NOT NULL default '',
`pid_idx` mediumint(8) unsigned NOT NULL default '0',
`priority` smallint(5) NOT NULL default '0',
`count` smallint(5) NOT NULL default '0',
`types` varchar(128) default NULL,
`creator` varchar(8) NOT NULL default '',
`creator_idx` mediumint(8) unsigned NOT NULL default '0',
`created` datetime default NULL,
PRIMARY KEY (`pid_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `node_rusage`
--
......
#
# Add node_reservations table
#
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBTableExists("node_reservations")) {
DBQueryFatal("CREATE TABLE `node_reservations` ( ".
" `pid` varchar(12) NOT NULL default '', ".
" `pid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `priority` smallint(5) NOT NULL default '0', ".
" `count` smallint(5) NOT NULL default '0', ".
" `types` varchar(128) default NULL, ".
" `creator` varchar(8) NOT NULL default '', ".
" `creator_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `created` datetime default NULL, ".
" PRIMARY KEY (`pid_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
return 0;
}
1;
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2010 University of Utah and the Flux Group.
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
......@@ -26,7 +26,8 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
wanodecheckin wanodecreate spewimage \
anonsendmail epmodeset fixexpinfo node_traffic \
dumpdescriptor subboss_tftpboot_sync testbed-control \
archive-expinfo grantfeature emulabfeature addblob readblob
archive-expinfo grantfeature emulabfeature addblob readblob \
prereserve
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Set up and clear node pre-reservations.
#
sub usage()
{
print STDERR "Usage: prereserve [-t typelist] [-n priority] pid count\n";
print STDERR " prereserve -c [-r] pid\n";
print STDERR " prereserve -i pid\n";
print STDERR " prereserve -l\n";
print STDERR " -h This message\n";
print STDERR " -t Comma separated list of node types\n";
print STDERR " -n Priority. Defaults to zero (least priority)\n";
print STDERR " -c Clear pending prereserve for project\n";
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";
exit(-1);
}
my $optlist = "hdct:n:ilr";
my $priority = 0;
my $debug = 0;
my $info = 0;
my $list = 0;
my $clear = 0;
my $revoke = 0;
my $typelist;
my $pid;
my $count;
# Protos
sub fatal($);
#
# Please do not run as root. Hard to track what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Configure variables
#
my $TB = "@prefix@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use NodeType;
use Node;
use libtestbed;
use Experiment;
use Project;
use User;
#
# 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{h})) {
usage();
}
if (defined($options{n})) {
$priority = $options{n};
}
if (defined($options{c})) {
$clear = 1;
}
if (defined($options{r})) {
$revoke = 1;
}
if (defined($options{d})) {
$debug = 1;
}
if (defined($options{t})) {
$typelist = $options{t};
}
if (defined($options{i})) {
$info = 1;
}
if (defined($options{l})) {
$list = 1;
}
if ($info || $clear || $revoke) {
usage()
if (@ARGV != 1 || ($revoke && !$clear));
$pid = $ARGV[0];
}
elsif ($list) {
usage()
if (@ARGV);
}
else {
usage()
if (@ARGV != 2);
$pid = $ARGV[0];
$count = $ARGV[1];
if ($priority && ! ($priority =~ /^\d*$/)) {
usage();
}
}
#
# 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.
#
if ($list) {
my $query_result =
DBQueryFatal("select * from node_reservations ".
"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");
print "-------------------------------------------------------------\n";
}
while (my $row = $query_result->fetchrow_hashref()) {
my $pid = $row->{'pid'};
my $count = $row->{'count'};
my $created = $row->{'created'};
my $creator = $row->{'creator'};
my $types = $row->{'types'} || "";
my $priority= $row->{'priority'};
my $current = 0;
my $current_result =
DBQueryFatal("select count(*) from nodes where reserved_pid='$pid'");
($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);
}
exit(0);
}
my $project = Project->Lookup($pid);
if (!defined($project)) {
fatal("No such project $pid\n");
}
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.
#
if ($info) {
my $current = 0;
my $query_result =
DBQueryFatal("select count(*) from nodes where reserved_pid='$pid'");
($current) = $query_result->fetchrow_array()
if ($query_result && $query_result->numrows);
$query_result =
DBQueryFatal("select * from node_reservations ".
"where pid_idx='$pid_idx'");
if ($query_result->numrows) {
my $row = $query_result->fetchrow_hashref();
my $pid = $row->{'pid'};
my $count = $row->{'count'};
my $created = $row->{'created'};
my $creator = $row->{'creator'};
my $types = $row->{'types'} || "*";
my $priority= $row->{'priority'};
printf("%-15s %-10s %-10s %-18s %-3s %s\n",
"Project", "Cnt (Cur)", "Creator", "When", "Pri", "Types");
print "-------------------------------------------------------------\n";
printf("%-15s %-10s %-10s %-18s %-3d %s\n",
$pid, "$count ($current)", $creator, $created, $priority, $types);
}
exit(0);
}
# Sanity check the type list.
my @types = ();
if (defined($typelist)) {
@types = split(",", $typelist);
foreach my $typename (@types) {
my $type = NodeType->Lookup($typename);
if (!defined($type)) {
fatal("No such node type $typename");
}
}
}
else {
@types = ("*");
}
#
# Lets say that a current request is an error. delete and recreate.
#
my $query_result =
DBQueryFatal("select * from node_reservations where pid_idx='$pid_idx'");
if ($query_result->numrows) {
fatal("Already have a reservation request for $pid, please clear it first");
}
#
# First see if we can find enough (or any) nodes to satisfy the prereserve.
#
$query_result =
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) {
print "Not doing anything since you have as many as you wanted.\n";
exit(0);
}
}
#
# 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'";
}
#
# 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.
#
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++;
last
if ($current >= $count);
}
if ($tcount) {
print "Set reserved_pid for $tcount (free)" .
($type eq "*" ? "" : " $type") . " nodes.\n";
}
}
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.
#
$count -= $current;
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($)
{
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment