All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

Commit 67a0e58e authored by Leigh B Stoller's avatar Leigh B Stoller

Add automated cancellation of reservations that are not used:

* If unused at six hours, schedule for cancel in three hours and send
  email.

* If reservation becomes used within those three hours, rescind the
  cancellation.

* Add an override bit so that cancel/uncancel on the command line
  supercedes (so explicit cancel or rescinding a cancel, means do not
  make any more automated checks for unused).

* Rework cancel to be more library friendly.
parent df988724
......@@ -216,6 +216,9 @@ sub Lookup($$;$$$$)
$self->{'APPROVED'} = $record->{'a'};
$self->{'APPROVER'} = $record->{'approver'};
$self->{'UUID'} = $record->{'uuid'};
$self->{'NOTIFIED'} = $record->{'notified'};
$self->{'NOTIFIED_UNUSED'} = $record->{'notified_unused'};
$self->{'OVERRIDE_UNUSED'} = $record->{'override_unused'};
# For compat with history entries.
$self->{'DELETED'} = undef;
# Local temp datastore
......@@ -243,6 +246,9 @@ sub admin_notes($) { return $_[0]->{"ADMIN_NOTES"}; }
sub approved($) { return $_[0]->{"APPROVED"}; }
sub approver($) { return $_[0]->{"APPROVER"}; }
sub uuid($) { return $_[0]->{"UUID"}; }
sub notified($) { return $_[0]->{"NOTIFIED"}; }
sub notified_unused($) { return $_[0]->{"NOTIFIED_UNUSED"}; }
sub override_unused($) { return $_[0]->{"OVERRIDE_UNUSED"}; }
# For compat with history entries.
sub deleted($) { return $_[0]->{"DELETED"}; }
# Get/Set some temporary extra data.
......@@ -318,13 +324,6 @@ sub SetAdminNotes($$)
$self->{'ADMIN_NOTES'} = $notes;
}
sub SetCancel($$)
{
my ($self, $cancel) = @_;
$self->{'CANCEL'} = $cancel;
}
# 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.
......@@ -433,7 +432,6 @@ sub Book($;$)
my $admin_notes = DBQuoteSpecial( $self->admin_notes() );
my $approved = $self->approved();
my $approver = $self->approver();
my $cancel = $self->cancel();
my $base_query = "SET pid='$pid', " .
"pid_idx='$pid_idx', " .
......@@ -449,8 +447,6 @@ sub Book($;$)
", admin_notes=$admin_notes" : "" ) .
( defined( $approved ) ?
", approved=FROM_UNIXTIME($approved)" : "" ) .
( defined( $cancel ) ?
", cancel=FROM_UNIXTIME($cancel)" : ", cancel=null" ) .
( defined( $approver ) ? ", approver='$approver'" : "" );
my $query_result =
......@@ -1278,6 +1274,9 @@ sub LookupHistorical($$) {
$self->{'APPROVED'} = undef;
$self->{'APPROVER'} = undef;
$self->{'UUID'} = $record->{'uuid'};
$self->{'NOTIFIED'} = undef;
$self->{'NOTIFIED_UNUSED'} = undef;
$self->{'OVERRIDE_UNUSED'} = 0;
bless($self, $class);
# Local temp datastore
$self->{'DATA'} = {};
......@@ -1860,5 +1859,65 @@ sub ReservableTypes($)
return @result;
}
#
# Mark the reservation for cancellation because it has not been used.
# We also mark the notified_unused flag so that we do not interefere
# with command line cancellation (and cancel cancellation).
#
# We are wrapped in a transaction by the caller.
#
sub MarkUnused($$)
{
my ($self, $when) = @_;
my $idx = $self->idx();
if ($when) {
DBQueryWarn("update future_reservations set ".
" cancel=FROM_UNIXTIME($when), notified_unused=now() ".
"where idx='$idx'")
or return -1;
}
else {
DBQueryWarn("update future_reservations set ".
" cancel=null, notified_unused=null ".
"where idx='$idx'")
or return -1;
}
$self->{'CANCEL'} = $when;
$self->{'NOTIFIED_UNUSED'} = $when;
return 0
}
sub ClearUnused($) { Reservation::MarkUnused($_[0], undef); }
#
# Similiar operation for cancel, although in this case we also clear
# notified_unused flag so that the unused reservation checking code
# does not interfere with a command line cancel/abortcancel.
#
sub MarkCancel($$)
{
my ($self, $when) = @_;
my $idx = $self->idx();
if ($when) {
DBQueryWarn("update future_reservations set ".
" cancel=FROM_UNIXTIME($when), ".
" notified_unused=null, override_unused=1 ".
"where idx='$idx'")
or return -1;
}
else {
DBQueryWarn("update future_reservations set ".
" cancel=null, notified_unused=null, override_unused=1 ".
"where idx='$idx'")
or return -1;
}
$self->{'CANCEL'} = $when;
$self->{'NOTIFIED_UNUSED'} = undef;
$self->{'OVERRIDE_UNUSED'} = 1;
return 0;
}
sub ClearCancel($) { Reservation::MarkCancel($_[0], undef); }
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -2233,6 +2233,7 @@ CREATE TABLE `future_reservations` (
`approver` varchar(8) DEFAULT NULL,
`notified` datetime DEFAULT NULL,
`notified_unused` datetime DEFAULT NULL,
`override_unused` tinyint(1) NOT NULL default '0',
`uuid` varchar(40) NOT NULL default '',
PRIMARY KEY (`idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
......
use strict;
use libdb;
use emdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("future_reservations", "override_unused")) {
DBQueryFatal("alter table future_reservations add ".
" `override_unused` tinyint(1) NOT NULL default '0' ".
" after notified_unused");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
#!/usr/bin/perl -w
#
# Copyright (c) 2017 University of Utah and the Flux Group.
# Copyright (c) 2017-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -52,6 +52,9 @@ use emdb;
use libtestbed;
use emutil;
use Reservation;
use ResUtil;
use User;
use Project;
#
# Turn off line buffering on output
......@@ -194,4 +197,119 @@ while( my ($idx, $pid, $count, $type, $start, $end, $email, $cancel) =
$TBOPS);
}
#
# Handle reservations that are unused within the first six hours. The
# idea is that we start check after four hours. If idle we mark it for
# cancel in two hours, and then if they start using it, we cancel the
# cancelation. We stop checking after a while, in case we missed it
# cause the testbed was down; do not want to keep checking cause its
# expensive.
#
$query_result = DBQueryFatal("SELECT r.idx,".
" TIMESTAMPDIFF(MINUTE, r.start, now())/60 ".
" from future_reservations as r ".
"where r.approved IS NOT NULL AND " .
" r.start <= NOW() AND ".
" r.override_unused=0 AND ".
" (TIMESTAMPDIFF(MINUTE, r.start, now()) > ".
" (6 * 60)) AND ".
" (TIMESTAMPDIFF(MINUTE, r.start, now()) < ".
" (10 * 60))");
while (my ($idx, $hours) = $query_result->fetchrow_array()) {
my $res = Reservation->Lookup($idx);
next
if (!defined($res));
my $project = Project->Lookup($res->pid());
next
if (!defined($project));
eval {
my @records = CreateTimeline($project, $res);
ReservationUtilization($res, @records);
};
if ($@) {
print STDERR $@;
next;
}
my $utilization = $res->data("utilization");
print STDERR "$res - utilization:$utilization\n"
if ($debug);
if ($utilization == 0.0) {
# Skip if cancel already set; do not interfere with a command
# line operation that set the cancel.
next
if ($res->cancel());
# Mark for cancelation.
print "Marking unused reservation for cancellation.\n"
if ($debug);
next
if ($impotent);
my $cancel = time() + (3 * 3600);
while (1) {
if (!defined(Reservation->
BeginTransaction(Reservation->GetVersion()))) {
sleep(1);
next;
}
$res->MarkUnused($cancel);
Reservation->EndTransaction();
last;
}
my $user = User->Lookup($res->uid());
if (!defined($user)) {
print STDERR "Could not lookup user " . $res->uid() ."\n";
next;
}
my $email = $user->email();
my $count = $res->nodes();
my $pid = $res->pid();
my $type = $res->type();
my $start = TBDateStringUTC($res->start());
my $end = TBDateStringUTC($res->end());
$cancel = TBDateStringUTC($cancel);
SENDMAIL($email, "$SITE reservation",
"Your reservation for $count $type nodes in project $pid,\n" .
"starting at $start and ending at $end,\n".
"has not been used since it started.\n".
"\n".
"We have marked your reservation for cancellation at $cancel.\n".
"\n".
"If you use your reservation before then, we will rescind\n".
"the cancellation.\n",
$TBOPS);
}
elsif ($res->notified_unused()) {
#
# We only cancel our own cancellation. If the command line tool
# aborts the cancel (or even sets the cancel), we view that as an
# override on our cancel (the notified_unused flag is cleared on
# that path, and override_unused is set).
#
print "Clearing unused reservation cancellation.\n"
if ($debug);
next
if ($impotent);
while (1) {
if (!defined(Reservation->
BeginTransaction(Reservation->GetVersion()))) {
sleep(1);
next;
}
$res->ClearUnused();
Reservation->EndTransaction();
last;
}
}
}
exit( 0 );
......@@ -464,14 +464,17 @@ if ($cancel || $abortcancel) {
exit( 1 );
}
$res->SetCancel(($abortcancel ? undef : $cancel));
while (1) {
if (!defined(Reservation->BeginTransaction(Reservation->GetVersion()))) {
sleep(1);
next;
}
$res->Book($res->idx());
if ($abortcancel) {
$res->ClearCancel();
}
else {
$res->MarkCancel($cancel);
}
Reservation->EndTransaction();
last;
}
......
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