Commit e6f4e2a9 authored by Gary Wong's avatar Gary Wong

Implement reservation approval.

"reserve" will automatically approve sufficiently small reservation
requests.  Otherwise, it will make a note in the database but not actually
hold any resources.  Big reservation requests can be approved manually,
with the "reserve -a" option (either at the time of the initial request,
or subsequently with "reserve -a -m ...").

When a reservation request is feasible but not auto-approved, "reserve"
will send e-mail to testbed-ops and exit with status 2.
parent 47aac5fa
......@@ -90,6 +90,8 @@ sub CreateCommon($$$$$$$$)
$self->{'UID_IDX'} = $user->uid_idx();
$self->{'NOTES'} = undef;
$self->{'ADMIN_NOTES'} = undef;
$self->{'APPROVED'} = undef;
$self->{'APPROVER'} = undef;
bless($self, $class);
......@@ -193,6 +195,8 @@ sub Lookup($$;$$$$)
$self->{'UID_IDX'} = $record->{'uid_idx'};
$self->{'NOTES'} = $record->{'notes'};
$self->{'ADMIN_NOTES'} = $record->{'admin_notes'};
$self->{'APPROVED'} = $record->{'approved'};
$self->{'APPROVER'} = $record->{'approver'};
bless($self, $class);
......@@ -212,6 +216,8 @@ sub uid($) { return $_[0]->{"UID"}; }
sub uid_idx($) { return $_[0]->{"UID_IDX"}; }
sub notes($) { return $_[0]->{"NOTES"}; }
sub admin_notes($) { return $_[0]->{"ADMIN_NOTES"}; }
sub approved($) { return $_[0]->{"APPROVED"}; }
sub approver($) { return $_[0]->{"APPROVER"}; }
sub Stringify($)
{
......@@ -267,6 +273,19 @@ sub SetAdminNotes($$)
$self->{'ADMIN_NOTES'} = $notes;
}
# Mark the reservation as approved. This DOES NOT update the database
# state: to do so requires an admission control check! See BeginTransaction(),
# IsFeasible(), Book(), etc.
sub Approve($;$)
{
my ($self, $user) = @_;
$user = User->ThisUser() if( !defined( $user ) );
$self->{'APPROVED'} = time();
$self->{'APPROVER'} = $user->uid() if defined( $user );
}
# Retrieve the current reservation database version. This version must
# be retrieved and saved before validity checks on attempted updates,
# and then the same version supplied to BeginTransaction() before making
......@@ -342,6 +361,8 @@ sub Book($;$)
my $uid_idx = $self->uid_idx();
my $notes = DBQuoteSpecial( $self->notes() );
my $admin_notes = DBQuoteSpecial( $self->admin_notes() );
my $approved = $self->approved();
my $approver = $self->approver();
my $base_query = "SET pid='$pid', " .
"pid_idx='$pid_idx', " .
......@@ -353,7 +374,10 @@ sub Book($;$)
"uid_idx='$uid_idx' " .
( defined( $notes ) ? ", notes=$notes" : "" ) .
( defined( $admin_notes ) ?
", admin_notes=$admin_notes" : "" );
", admin_notes=$admin_notes" : "" ) .
( defined( $approved ) ?
", approved=FROM_UNIXTIME($approved)" : "" ) .
( defined( $approver ) ? ", approver='$approver'" : "" );
my $query_result =
DBQueryWarn( defined( $idx ) ? "UPDATE future_reservations " .
......@@ -476,9 +500,9 @@ sub Tidy($)
return 1;
}
sub LookupAll($$)
sub LookupAll($$;$)
{
my ($class, $type) = @_;
my ($class, $type, $include_pending) = @_;
return $cache{$type} if( exists( $cache{$type} ) );
......@@ -565,7 +589,10 @@ sub LookupAll($$)
$query_result = DBQueryWarn( "SELECT pid, uid, UNIX_TIMESTAMP( start ), " .
"UNIX_TIMESTAMP( end ), nodes, idx FROM " .
"future_reservations WHERE type='$type'" );
"future_reservations WHERE type='$type'" .
( $include_pending ? "" :
" AND approved IS NOT NULL" ) );
while( my ($pid, $uid, $start, $end, $nodes, $idx) =
$query_result->fetchrow_array() ) {
my $res = Create( $class, $pid, $uid, $start, $end, $type, $nodes );
......@@ -733,6 +760,7 @@ sub FreeCount($$;$) {
# Weight the nodes based on how far into the future the
# reservation is. The 0x10000 is chosen so that a node available
# for the next 24 hours only is worth about half a node available
# indefinitely.
$answer -= ( ( $free - $f->{'free'} ) * exp( -$deltat / 0x10000 ) );
$free = $f->{'free'};
......
......@@ -46,10 +46,10 @@ use WebTask;
sub usage()
{
print STDERR "Usage: reserve [-C] [-f] [-n] -t type [-s start] [-e end]\n" .
" [-u] [-U uid] [-N file] [-A file] pid count\n";
" [-u] [-U uid] [-N file] [-A file] [-a] pid count\n";
print STDERR " reserve -c idx\n";
print STDERR " reserve [-f] [-n] [-s start] [-e end] [-u]\n" .
" [-U uid] [-N file] [-A file] [-S size] -m idx \n";
" [-U uid] [-N file] [-A file] [-S size] [-a] -m idx \n";
print STDERR " reserve [-u] -i pid\n";
print STDERR " reserve [-u] -l\n";
print STDERR " -h This message\n";
......@@ -65,12 +65,13 @@ sub usage()
print STDERR " -l List all existing reservations\n";
print STDERR " -s Start time when reservation begins\n";
print STDERR " -e End time when reservation expires\n";
print STDERR " -a Approve reservation (auto for small, otherwise admin-only)\n";
print STDERR " -m Modify existing reservation\n";
print STDERR " -S Specify new size of modified reservation\n";
exit( -1 );
}
my $optlist = "c:de:fhilm:ns:t:uA:CN:S:U:T:";
my $optlist = "ac:de:fhilm:ns:t:uA:CN:S:U:T:";
my $debug = 0;
my $info = 0;
my $list = 0;
......@@ -83,6 +84,7 @@ my $starttime = time; # default to starting immediately
my $endtime = time + 24 * 60 * 60; # default to ending tomorrow
my $notes = undef;
my $adminnotes = undef;
my $approve = 0;
my $type;
my $pid;
my $count;
......@@ -220,6 +222,9 @@ if (defined($options{S})) {
fatal( "Invalid reservation size." );
}
}
if (defined($options{'a'})) {
$approve = 1;
}
if ($info) {
usage() if( @ARGV != 1 );
......@@ -273,18 +278,18 @@ else {
# List all pending reservations.
#
if ($list) {
my $query = $type ? "SELECT idx, pid, nodes, type, " .
my $query = $type ? "SELECT idx, pid, nodes, type, approved, " .
"UNIX_TIMESTAMP( start ) AS s, UNIX_TIMESTAMP( end ) AS e FROM " .
"future_reservations WHERE type='$type' ORDER BY s" :
"SELECT idx, pid, nodes, type, UNIX_TIMESTAMP( start ) AS s, " .
"SELECT idx, pid, nodes, type, approved, UNIX_TIMESTAMP( start ) AS s, " .
"UNIX_TIMESTAMP( end ) AS e FROM future_reservations " .
"ORDER BY s";
my $query_result = DBQueryFatal( $query );
if( $query_result->numrows ) {
print "Index Start End Project Nodes Type\n";
print "----- ----- --- ------- ----- ----\n";
print "A Index Start End Project Nodes Type\n";
print "- ----- ----- --- ------- ----- ----\n";
}
while( my $row = $query_result->fetchrow_hashref() ) {
......@@ -294,8 +299,9 @@ if ($list) {
my $type = $row->{'type'};
my $start = convert( $row->{'s'} );
my $end = convert( $row->{'e'} );
my $approved = defined( $row->{'approved'} ) ? "Y" : " ";
printf( "%5d %16s %16s %-19s %5d %s\n", $idx, $start, $end, $pid, $nodes, $type );
printf( "%1s %5d %16s %16s %-19s %5d %s\n", $approved, $idx, $start, $end, $pid, $nodes, $type );
}
exit(0);
......@@ -328,18 +334,18 @@ if( !defined( $clear_idx ) ) {
# Show and exit.
#
if ($info) {
my $query = $type ? "SELECT uid, nodes, type, " .
my $query = $type ? "SELECT uid, nodes, type, approved, " .
"UNIX_TIMESTAMP( start ) AS s, UNIX_TIMESTAMP( end ) AS e FROM " .
"future_reservations WHERE type='$type' AND pid_idx=$pid_idx " .
"ORDER BY s" : "SELECT uid, nodes, type, " .
"ORDER BY s" : "SELECT uid, nodes, type, approved, " .
"UNIX_TIMESTAMP( start ) AS s, UNIX_TIMESTAMP( end ) AS e FROM " .
"future_reservations WHERE pid_idx=$pid_idx ORDER BY s";
my $query_result = DBQueryFatal( $query );
if( $query_result->numrows ) {
print "Start End User Nodes Type\n";
print "----- --- ---- ----- ----\n";
print "A Start End User Nodes Type\n";
print "- ----- --- ---- ----- ----\n";
}
while( my $row = $query_result->fetchrow_hashref() ) {
......@@ -348,8 +354,9 @@ if ($info) {
my $type = $row->{'type'};
my $start = convert( $row->{'s'} );
my $end = convert( $row->{'e'} );
my $approved = defined( $row->{'approved'} ) ? "Y" : " ";
printf( "%16s %16s %-19s %5d %s\n", $start, $end, $uid, $nodes, $type );
printf( "%1s %16s %16s %-19s %5d %s\n", $approved, $start, $end, $uid, $nodes, $type );
}
exit(0);
......@@ -393,6 +400,14 @@ if ($clear || $clear_idx) {
exit( 0 );
}
# For now, auto-approve reservation requests up to 64 node-hours.
# Later we'll probably want this threshold to vary based on the node type,
# how far into the future the reservation starts, the phase of the moon...
# who knows.
if( $count * ( $endtime - $starttime ) / 3600 <= 0x40 ) {
$approve = 1;
}
#
# Do not allow this as root; we want proper history.
#
......@@ -414,6 +429,7 @@ if( defined( $modify_idx ) ) {
}
$res->SetNotes( $notes ) if( defined( $notes ) );
$res->SetAdminNotes( $adminnotes ) if( defined( $adminnotes ) );
$res->Approve( $target_user ) if( $approve );
print "$res\n" if( $debug );
......@@ -451,5 +467,28 @@ while( 1 ) {
next if( !defined( Reservation->BeginTransaction( $version ) ) );
$res->Book( $modify_idx );
Reservation->EndTransaction();
last;
if( $approve ) {
exit( 0 );
} else {
# We just booked a reservation we didn't pre-approve. It requires
# admin attention to be made effective.
my $s = convert( $starttime );
my $e = convert( $endtime );
my $idx = $res->idx();
print STDERR "reserve: reservation is feasible but has NOT yet been approved.\n";
SENDMAIL( $TBOPS, "Reservation request pending",
"User \"$uid\" has requested a reservation for $count $type nodes,\n" .
"starting at $s and ending at $e.\n" .
"\n" .
"The request was feasible at the time it was made, but administrator\n" .
"approval is required to hold the resources.\n" .
"\n" .
"You can approve the request by invoking:\n" .
" reserve -a -m $idx\n" .
"on boss.\n" );
exit( 2 );
}
}
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