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 e1b6076f authored by Leigh B Stoller's avatar Leigh B Stoller

Changes related to extensions:

* Change the units of extension from days to hours along the extension
  path. The user does not see this directly, but it allows us to extend
  experiments to the hour before they are needed by a different
  reservation, both on the user extend modal and the admin extend modal.

  On the admin extend page, the input box still defaults to days, but
  you can also use xDyH to specify days and hours. Or just yH for just
  hours.

  But to make things easier, there is also a new "max" checkbox to
  extend an experiment out to the maximum allowed by the reservation
  system.

* Changes to "lockout" (disabling extensions). Add a reason field to the
  database, clicking the lockout checkbox will prompt for an optional
  reason.

  The user no longer sees the extension modal when extensions are
  disabled, we show an alert instead telling them extensions are
  disabled, and the reason.

  On the admin extend page there is a new checkbox to disable extensions
  when denying an extension or scheduling termination.

  Log extension disable/enable to the audit log.

* Clear out a bunch of old extension code that is no longer used (since
  the extension code was moved from php to perl).
parent 5d2cf35c
......@@ -70,6 +70,7 @@ use overload ('""' => 'Stringify');
# Configure variables
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $GENEXTENDCRED = "$TB/sbin/protogeni/genextendcred";
my $GENIUSER = "geniuser";
my $MAINSITE = @TBMAINSITE@;
......@@ -608,6 +609,14 @@ sub webURL($)
return $link;
}
sub adminURL($)
{
my ($self) = @_;
my $link = $self->Brand()->wwwBase();
$link = $link . "/adminextend.php?uuid=" . $self->uuid();
return $link;
}
#
# Find the profile for this instance.
......@@ -640,7 +649,7 @@ sub RecordHistory($$)
" gid,gid_idx, ".
" aggregate_urn,public_url,logfileid, ".
" created,now(),$expired, ".
" extension_count,extension_days, ".
" extension_count,extension_days,extension_hours, ".
" physnode_count,virtnode_count, ".
" servername,repourl,reponame,reporef,repohash, ".
" rspec,script,params,manifest ".
......@@ -1140,7 +1149,7 @@ sub ExtensionRequested($$$)
" extension_reason=$safe_text,".
" extension_requested=1, ".
" extension_count=extension_count+1, ".
" extension_days=extension_days+${granted} ".
" extension_hours=extension_hours+${granted} ".
"where uuid='$uuid'");
}
......@@ -1151,7 +1160,7 @@ sub BumpExtensionCount($$)
return DBQueryWarn("update apt_instances set ".
" extension_count=extension_count+1, ".
" extension_days=extension_days+${granted} ".
" extension_hours=extension_hours+${granted} ".
"where uuid='$uuid'");
}
......
......@@ -52,7 +52,7 @@ sub usage()
print("Usage: manage_instance writecreds instance directory\n");
print("Usage: manage_instance updatekeys instance [uid] \n");
print("Usage: manage_instance extend instance ".
"[-M] [-m message | -f filename] days\n");
"[-M] [-m message | -f filename] hours\n");
print("Usage: manage_instance denyextension instance [-m message] [filename]\n");
print("Usage: manage_instance checkreservation instance days\n");
print("Usage: manage_instance maxextension instance\n");
......@@ -1365,7 +1365,6 @@ sub DoTerminate()
sub DoExtend()
{
my $force = 0;
my $lockdown = 0;
my $errcode = 1;
my $autoextend_maximum = GetSiteVar("aptui/autoextend_maximum");
my $autoextend_maxage = GetSiteVar("aptui/autoextend_maxage");
......@@ -1382,11 +1381,12 @@ sub DoExtend()
my $extensions = $instance->Brand()->ExtensionsEmailAddress();
my $granted = 0;
my $needapproval = 0;
my $inhours = 0;
my $autoapprove_info;
my $message;
my $reason;
my $errmsg;
my $wantstring;
my $grantstring;
usage()
if (!@ARGV);
......@@ -1402,8 +1402,10 @@ sub DoExtend()
if (defined($options{"F"})) {
# Be careful
$force = 1
if (defined($this_user) && $this_user->IsAdmin());
if (!$this_user->IsAdmin()) {
fatal("Only admins can use the -F option");
}
$force = 1;
}
if (defined($options{"m"})) {
$reason = $options{"m"};
......@@ -1422,9 +1424,6 @@ sub DoExtend()
}
close(MSG);
}
if (defined($options{"h"})) {
$inhours = 1;
}
#
# Lock the slice in case it is doing something else, like taking
......@@ -1451,23 +1450,16 @@ sub DoExtend()
if ($wanted !~ /^\d+$/) {
my $when = str2time($wanted);
if (!$when) {
$errmsg = "Illegal number of days or date";
$errmsg = "Illegal number of hours or date";
$errcode = 1;
goto bad;
}
if ($when < time() + 3600) {
$wanted = int(($when - $expires_time) / 3600);
if ($wanted < 1) {
$errmsg = "Expiration is too soon";
$errcode = 1;
goto bad;
}
my $hours = int(($when - time()) / 3600);
if ($hours < 24) {
$wanted = $hours;
$inhours = 1;
}
else {
$wanted = int($hours / 24);
}
}
# Helper function.
......@@ -1477,16 +1469,15 @@ sub DoExtend()
my $howlong = $wanted - $granted;
my $new_expires = POSIX::strftime("20%y-%m-%d %H:%M:%S %Z",
localtime(str2time($slice->expires())+
($howlong * 3600 * 24)));
($howlong * 3600)));
my $created = POSIX::strftime("20%y-%m-%d %H:%M:%S %Z",
localtime(str2time($instance->created())));
$instance->Brand()->SendEmail($extensions,
"Experiment Extension Request: $name",
"A request to extend this experiment was made but requires\n".
"administrator approval" .
($message ? " $message" : "") . ".\n\n" .
"The request was for $wanted days, we granted $granted days, ".
"The request was for $wantstring, we granted $grantstring, ".
"the reason given is:\n\n".
$reason . "\n\n".
"This experiment was started on $created\n".
......@@ -1514,37 +1505,25 @@ sub DoExtend()
}
#
# If extension is in hours, always grant if < 24 hours.
# Always grant if <= 24 hours.
#
if ($inhours) {
if ($wanted >= 24) {
$errmsg = "If you want more then 24 hours, use days instead.";
goto bad;
}
if ($wanted <= 24) {
$message = "Short extension granted for $wanted hours.";
$reason = $message;
$reason = $message if (!defined($reason));
$granted = $wanted;
}
#
# Guest users are treated differently.
#
elsif (!defined($this_user)) {
# Only extend for 24 hours.
$granted = 1;
if ($expires_time > time() + (3600 * 24 * $granted)) {
$errmsg = "You still have a day left. Try again tomorrow";
$errcode = 1;
goto bad;
}
}
#
# Admin user, we do whatever it says to do.
#
elsif ($this_user->IsAdmin()) {
$message = "Your experiment was extended by the site administrator.";
$granted = $wanted;
}
elsif (1) {
$message = "Testing extension stuff";
$granted = 0;
$needapproval = 1;
}
else {
my $diff = $expires_time - time();
my $cdiff = time() - $created_time;
......@@ -1573,7 +1552,7 @@ sub DoExtend()
my $mindiff = $autoextend_freedays * 3600 * 24;
if ($diff < $mindiff) {
$granted = POSIX::ceil(($mindiff - $diff) / (3600 * 24));
$granted = int(($mindiff - $diff) / 3600);
}
else {
$granted = 0;
......@@ -1594,22 +1573,13 @@ sub DoExtend()
}
}
#
# Temporary for GEC23, this should be generalized next time.
#
elsif (0 && (time() + ($wanted * 3600 * 24) >
str2time("2015-06-15 12:00:00"))) {
$granted = 1;
$needapproval = 1;
$message = "because the testbed is mostly reserved for GEC23";
}
#
# Registered users are granted up to the autoextend_maximum
# automatically. Beyond that, requires approval, but we still
# give them whatever the free extension is, since we want to
# give them extra time until the next meeting of the "resource
# management committee."
#
elsif ($wanted > $autoextend_maximum) {
elsif ($wanted > $autoextend_maximum * 24) {
if (CheckAutoApprove($wanted, \$autoapprove_info) == 0) {
if ($autoapprove_info->{'approve'}) {
# Informational for now.
......@@ -1628,7 +1598,7 @@ sub DoExtend()
$granted = 0;
}
else {
$granted = $autoextend_maximum;
$granted = $autoextend_maximum * 24;
}
}
elsif ($diff > (3600 * 24 * 7)) {
......@@ -1646,11 +1616,10 @@ sub DoExtend()
# is a week and there are five days left and they asked
# for seven, we give them two.
#
if ($granted && ($expires_time + ($granted * 3600 * 24)) >
if ($granted && ($expires_time + ($granted * 3600)) >
(time() + (3600 * 24 * $autoextend_maximum))) {
$granted =
POSIX::ceil(((3600 * 24 * $autoextend_maximum) - $diff) /
(3600 * 24.0));
int(((3600 * 24 * $autoextend_maximum) - $diff) / 3600.0);
}
}
#
......@@ -1659,7 +1628,6 @@ sub DoExtend()
grant:
if ($granted) {
my $seconds = $granted * 3600;
$seconds *= 24 if (!$inhours);
if ($errcode = ExtendInternal($slice, $seconds, $force, 0, \$errmsg)) {
goto bad;
......@@ -1673,15 +1641,33 @@ sub DoExtend()
my $before = POSIX::strftime("20%y-%m-%d %H:%M:%S %Z",
localtime($expires_time));
#
# XXX If in hours, change to 1. Need to come back and fix this.
# Make these fields in the database hours or seconds.
#
if ($inhours) {
$wanted = 1;
$granted = 1;
# Format hours into days/hours.
my $wdays = int($wanted / 24);
my $whours = $wanted % 24;
my $gdays = int($granted / 24);
my $ghours = $granted % 24;
if ($wdays) {
$wantstring = "$wdays days";
if ($whours) {
$wantstring .= " ${whours} hours";
}
}
else {
$wantstring = "$whours hours";
}
if ($gdays) {
$grantstring = "$gdays days";
if ($ghours) {
$grantstring .= " ${ghours} hours";
}
}
elsif ($ghours) {
$grantstring = "$ghours hours";
}
else {
$grantstring = "nothing";
}
#
# New extension mechanism
#
......@@ -1730,7 +1716,7 @@ sub DoExtend()
#
my $text =
"Date: $now\n".
"Wanted: $wanted, Granted: $granted\n".
"Wanted: $wantstring, Granted: $grantstring\n".
"Before: $before\n".
"After $expires\n".
"Reason:\n".
......@@ -3851,7 +3837,7 @@ sub DoCheckAutoApprove()
if (!@ARGV);
my $days = shift(@ARGV);
$errcode = CheckAutoApprove($days, \$results);
$errcode = CheckAutoApprove($days * 24, \$results);
if ($errcode) {
goto bad;
}
......@@ -3869,7 +3855,7 @@ sub DoCheckAutoApprove()
sub CheckAutoApprove($$)
{
my ($days, $pref) = @_;
my ($hours, $pref) = @_;
my $errmsg;
my $errcode = 1;
my $utilization;
......@@ -3886,7 +3872,7 @@ sub CheckAutoApprove($$)
# We do not do auto extensions if more then 1000 physical node hours
# in the request.
#
if ($days * 24 * $instance->physnode_count() > 1000) {
if ($hours * $instance->physnode_count() > 1000) {
my $rval = {
"reason" => "greater then 1000 node hours requested",
"metrics" => undef,
......@@ -3911,7 +3897,7 @@ sub CheckAutoApprove($$)
# between current time and time at end of extension. This number needs
# calibration.
#
my $diff = ($expires_time + ($days * 3600 * 24)) - time();
my $diff = ($expires_time + ($hours * 3600)) - time();
if ($diff < 0) {
$errmsg = "Time is in the past. Hmm.\n";
$errcode= -1;
......
......@@ -690,7 +690,7 @@ sub SliceMaxExtension($)
TBDateStringGMT(0x7fff0000));
}
if (Reservation->MaxSliceExtension($slice, \$max, \$reserror)) {
if (Reservation->MaxSliceExtension($slice, \$max, \$reserror, 3600)) {
Reservation->FlushAll();
return GeniResponse->Create(GENIRESPONSE_REFUSED, undef, $reserror);
}
......
......@@ -222,8 +222,8 @@ CREATE TABLE `apt_instance_extension_info` (
`uid` varchar(8) NOT NULL default '',
`uid_idx` mediumint(8) unsigned NOT NULL default '0',
`action` enum('request','deny','info') NOT NULL default 'request',
`wanted` smallint(5) unsigned NOT NULL default '0',
`granted` smallint(5) unsigned default NULL,
`wanted` int(10) unsigned NOT NULL default '0',
`granted` int(10) unsigned default NULL,
`needapproval` tinyint(1) NOT NULL default '0',
`autoapproved` tinyint(1) NOT NULL default '0',
`autoapproved_reason` tinytext,
......@@ -286,6 +286,7 @@ CREATE TABLE `apt_instance_history` (
`expired` tinyint(1) NOT NULL default '0',
`extension_count` smallint(5) unsigned NOT NULL default '0',
`extension_days` smallint(5) unsigned NOT NULL default '0',
`extension_hours` int(10) unsigned NOT NULL default '0',
`physnode_count` smallint(5) unsigned NOT NULL default '0',
`virtnode_count` smallint(5) unsigned NOT NULL default '0',
`servername` tinytext,
......@@ -371,11 +372,14 @@ CREATE TABLE `apt_instances` (
`extension_reason` mediumtext,
`extension_history` mediumtext,
`extension_adminonly` tinyint(1) NOT NULL default '0',
`extension_disabled` tinyint(1) NOT NULL default '0',
`extension_disabled_reason` mediumtext,
`extension_requested` tinyint(1) NOT NULL default '0',
`extension_denied` tinyint(1) NOT NULL default '0',
`extension_denied_reason` mediumtext,
`extension_count` smallint(5) unsigned NOT NULL default '0',
`extension_days` smallint(5) unsigned NOT NULL default '0',
`extension_hours` int(10) unsigned NOT NULL default '0',
`physnode_count` smallint(5) unsigned NOT NULL default '0',
`virtnode_count` smallint(5) unsigned NOT NULL default '0',
`servername` tinytext,
......
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("apt_instances", "extension_hours")) {
DBQueryFatal("alter table apt_instances add ".
" `extension_hours` int(10) unsigned NOT NULL default '0'".
" after extension_days");
DBQueryFatal("update apt_instances set ".
" extension_hours=extension_days*24");
}
if (!DBSlotExists("apt_instance_history", "extension_hours")) {
DBQueryFatal("alter table apt_instance_history add ".
" `extension_hours` int(10) unsigned NOT NULL default '0'".
" after extension_days");
DBQueryFatal("update apt_instance_history set ".
" extension_hours=extension_days*24");
}
my $type = DBSlotType("apt_instance_extension_info", "wanted");
if ($type =~ /smallint/) {
DBQueryFatal("alter table apt_instance_extension_info change wanted ".
" `wanted` int(10) unsigned NOT NULL default '0'");
DBQueryFatal("update apt_instance_extension_info set ".
" wanted=wanted*24");
}
$type = DBSlotType("apt_instance_extension_info", "granted");
if ($type =~ /smallint/) {
DBQueryFatal("alter table apt_instance_extension_info change granted ".
" `granted` int(10) unsigned default NULL");
DBQueryFatal("update apt_instance_extension_info set ".
" granted=granted*24");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("apt_instances", "extension_disabled")) {
DBQueryFatal("alter table apt_instances add ".
" `extension_disabled` tinyint(1) NOT NULL default '0' ".
" after extension_adminonly");
DBQueryFatal("update apt_instances set ".
" extension_disabled=extension_adminonly");
}
if (!DBSlotExists("apt_instances", "extension_disabled_reason")) {
DBQueryFatal("alter table apt_instances add ".
" `extension_disabled_reason` mediumtext ".
" after extension_disabled");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
......@@ -48,6 +48,7 @@ if (!isset($uuid)) {
What experiment would you like to look at?
</p>
</div>\n";
SPITNULLREQUIRE();
SPITFOOTER();
return;
}
......@@ -59,6 +60,7 @@ if (!$instance) {
Experiment does not exist.
</p>
</div>\n";
SPITNULLREQUIRE();
SPITFOOTER();
return;
}
......@@ -67,7 +69,7 @@ $extensions = ExtensionInfo::LookupForInstance($instance);
#
# If we have an outstanding extension, look to see how much more is left.
#
$days = "null";
$hours = "null";
if ($instance->extension_requested()) {
#
......@@ -77,7 +79,7 @@ if ($instance->extension_requested()) {
foreach ($extensions as $extension) {
if ($extension->action() == "request") {
if ($extension->granted() < $extension->wanted()) {
$days = $extension->wanted() - $extension->granted();
$hours = $extension->wanted() - $extension->granted();
}
break;
}
......@@ -103,7 +105,7 @@ echo "<script type='text/javascript'>\n";
echo " window.UUID = '" . $uuid . "';\n";
echo " window.PID = '" . $pid . "';\n";
echo " window.CREATOR = '" . $creator . "';\n";
echo " window.DAYS = $days;\n";
echo " window.HOURS = $hours;\n";
echo "</script>\n";
echo "<link rel='stylesheet'
......
......@@ -121,9 +121,13 @@ class Instance
function user_lockdown(){ return $this->field('user_lockdown'); }
function extension_count() { return $this->field('extension_count'); }
function extension_days() { return $this->field('extension_days'); }
function extension_hours() { return $this->field('extension_hours'); }
function extension_reason() { return $this->field('extension_reason'); }
function extension_history() { return $this->field('extension_history'); }
function extension_lockout() { return $this->field('extension_adminonly'); }
function extension_disabled(){ return $this->field('extension_disabled'); }
function extension_disabled_reason(){
return $this->field('extension_disabled_reason');}
function extension_requested(){return $this->field('extension_requested');}
function extension_denied() { return $this->field('extension_denied');}
function extension_denied_reason(){
......@@ -181,6 +185,18 @@ class Instance
return !is_null($this->instance);
}
# URL to the status page.
function StatusURL() {
global $APTBASE;
return $APTBASE . "/status.php?uuid=" . $this->uuid();
}
function AdminURL() {
global $APTBASE;
return $APTBASE . "/adminextend.php?uuid=" . $this->uuid();
}
# Lookup up an instance by idx.
function Lookup($idx) {
$foo = new Instance($idx);
......@@ -473,15 +489,6 @@ class Instance
"where uuid='$uuid'");
}
function BumpExtensionCount($granted)
{
$uuid = $this->uuid();
DBQueryWarn("update apt_instances set ".
" extension_count=extension_count+1, ".
" extension_days=extension_days+${granted} ".
"where uuid='$uuid'");
}
#
# Permission check; does user have permission to view instance.
#
......@@ -943,6 +950,31 @@ class ExtensionInfo
$this->info = mysql_fetch_assoc($query_result);
$this->info["reason"] = trim($this->info["reason"]);
$this->info["message"] = trim($this->info["message"]);
#
# Convert wanted/granted hours to handy 5D14H string.
#
$wdays = intval($this->info["wanted"] / 24.0);
$whours = $this->info["wanted"] % 24;
$gdays = intval($this->info["granted"] / 24.0);
$ghours = $this->info["granted"] % 24;
if ($wdays) {
$wantstring = "${wdays}D" . "${whours}H";
}
else {
$wantstring = "${whours}H";
}
if ($gdays) {
$grantstring = "${gdays}D" . "${ghours}H";
}
elseif ($ghours) {
$grantstring = "${ghours}H";
}
else {
$grantstring = "0";
}
$this->info["wantedstring"] = $wantstring;
$this->info["grantedstring"] = $grantstring;
}
# accessors
function field($name) {
......
......@@ -87,8 +87,8 @@ $(function ()
});
// Default number of days.
if (window.DAYS) {
$('#days').val(window.DAYS);
if (window.HOURS) {
$('#howlong').val(convertHours(window.HOURS));
}
// Handlers for Extend and Deny buttons.
$('#deny-extension').click(function (event) {
......@@ -112,6 +112,71 @@ $(function ()
Action("terminate");
return false;
});
/*
* Handler for the Maximum Extension button, which just overwrites
* the value in the input box. We want to save off the current
* value to restore if later unchecked.
*/
var current_extension_input = null;
$('#maximum-extension-checkbox').change(function (e) {
if ($('#maximum-extension-checkbox').is(":checked")) {
current_extension_input = $('#howlong').val();
if (maxextension == null) {
alert("There is maximum extension!");
// Flip the checkbox back.
$('#maximum-extension-checkbox').prop("checked", false);
return;
}
// Kill the input field, it will be ignored.
$('#howlong').val("");
}
else {
$('#howlong').val(current_extension_input);
}
});
}
//
// Convert xDyH into hours. A plain integer is just days.
//
function getHowlong()
{
var howlong = $.trim($('#howlong').val());
// Nothing means zero.
if (howlong == "") {
return 0;
}
var matches = howlong.match(/^(\d+)(D|H)?$/i);
if (matches) {
if (matches[2] === undefined || matches[2] == "D") {
return parseInt(matches[1]) * 24;
}
return parseInt(matches[1]);
}
matches = howlong.match(/^(\d+)D(\d+)H$/i);
if (matches) {
return (parseInt(matches[1]) * 24) + parseInt(matches[2]);
}
return undefined;
}
function convertHours(hours)
{
/*
* Convert hours to handy 5D14H string or just days integer.
*/
var days = parseInt(hours / 24);
var hours = hours % 24;
var str;
if (days) {