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

Two changes to reservations:

1. Plumb through a prediction RPC to return the reservation system
   pressure and outstanding reservations for a list of projects. This is
   invoked from the instantiate page when loaded, using the projects
   the user has permission to create experiments in, the results are
   stored in a script global variable for someone else to make sense of.

2. When checking to see if a reservation can be accommodated, check with
   the admission control library first to see if the is a project limit
   on the type that would be violated. Need to do a little rearranging
   of the deck chairs in admission control library.
parent a5399b02
......@@ -39,6 +39,7 @@ sub usage()
print("Usage: manage_reservations [-a <urn>] delete pid idx\n");
print("Usage: manage_reservations [-a <urn>] approve idx\n");
print("Usage: manage_reservations [-a <urn>] systeminfo\n");
print("Usage: manage_reservations [-a <urn>] prediction\n");
exit(-1);
}
my $optlist = "dt:a:";
......@@ -93,6 +94,7 @@ sub DoList();
sub DoDelete();
sub DoApprove();
sub DoSystemInfo();
sub DoPrediction();
sub readfile($);
#
......@@ -154,6 +156,9 @@ elsif ($action eq "approve") {
elsif ($action eq "systeminfo") {
DoSystemInfo();
}
elsif ($action eq "prediction") {
DoPrediction();
}
else {
usage();
}
......@@ -647,6 +652,162 @@ sub DoSystemInfo()
fatal($errmsg);
}
#
# Get the prediction data.
#
sub DoPrediction()
{
my $optlist = "p:";
my $portal;
my $errmsg;
my @aggregates = ($authority);
my @webtasks = ();
my @projlist = ();
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"p"})) {
$portal = $options{"p"}
}
# Argument is a list of projects, else we generate one for user.
if (@ARGV) {
foreach my $pid (@ARGV) {
my $project = Project->Lookup($pid);
if (!defined($project)) {
fatal("No such project: $pid");
}
push(@projlist, $project->urn());
}
}
else {
my @plist;
if ($this_user->ProjectMembershipList(\@plist)) {
fatal("Could not get project membership list");
}
foreach my $project (@plist) {
if ($project->AccessCheck($this_user, TB_PROJECT_CREATEEXPT())) {
push(@projlist, $project->urn());
}
}
if (!@projlist) {
fatal("No projects to create experiments in");
}
}
#
# Portal argument says to ignore aggregate argument, and contact
# all aggregates listed for the portal.
#
if (defined($portal)) {
@aggregates = ();
my @list = APT_Aggregate->LookupForPortal($portal);
foreach my $aggregate (@list) {
my $authority = APT_Geni::GetAuthority($aggregate->urn());
if (!defined($authority)) {
$errmsg = "Cannot lookup authority for $aggregate";
goto bad;
}
push(@aggregates, $authority);
}
}
my $coderef = sub {
my ($blob) = @_;
my $authority = $blob->{"authority"};
my $webtask = $blob->{"webtask"};
# PortalRPC will use the root context in this case, which is
# essentially saying the caller is an admin. But thats okay
# for this call, it is just informational.
my $response =
APT_Geni::PortalRPC($authority, undef,
"ReservationPrediction",
{"projlist" => \@projlist});
if (GeniResponse::IsError($response)) {
#
# All errors are fatal.
#
if (defined($webtask)) {
$webtask->output($response->output());
$webtask->Exited(-1);
}
else {
print STDERR "$authority: " . $response->output() . "\n";
}
return -1;
}
if (defined($webtask)) {
$webtask->value($response->value());
$webtask->Exited(0);
}
return 0;
};
#
# Multiple aggregates, we use parrun. We need a webtask for each
# authority to communicate the results back to the parent, who
# then combines them all.
#
my @return_codes = ();
my @agglist = ();
foreach my $auth (@aggregates) {
my $temptask = WebTask->CreateAnonymous();
# For delete below.
push(@webtasks, $temptask);
push(@agglist, {"authority" => $auth,
"webtask" => $temptask});
}
if (ParRun({"maxwaittime" => 60,
"maxchildren" => scalar(@agglist)},
\@return_codes, $coderef, @agglist)) {
#
# The parent caught a signal. Leave things intact so that we can
# kill things cleanly later.
#
$errmsg = "Internal error get reservation prediction info";
goto bad;
}
#
# Check the exit codes, create return structure for the web interface.
#
my $blob = {};
foreach my $agg (@agglist) {
my $code = shift(@return_codes);
my $auth = $agg->{'authority'};
my $wtask = $agg->{'webtask'};
$wtask->Refresh();
if ($code) {
$errmsg = "$auth: " . $wtask->output();
goto bad;
}
$blob->{$auth->urn()} = $wtask->value();
}
if (defined($webtask)) {
$webtask->value($blob);
$webtask->Exited(0);
}
else {
print Dumper($blob);
}
foreach my $temptask (@webtasks) {
$temptask->Delete();
}
exit(0);
bad:
foreach my $temptask (@webtasks) {
$temptask->Delete();
}
fatal($errmsg);
}
sub fatal($)
{
my ($mesg) = @_;
......
......@@ -228,6 +228,10 @@ my %node_types = (); # Indexed by type, gives class
my %node_classes = (); # Indexed by class, gives class
my $assignflag = 0;
my %assign_classes= (); # For assign (wrapper).
# Policy tables.
my %user_policies = ();
my %group_policies = ();
my %global_policies = ();
# Constants.
sub TBADMINCTRL_TYPE_USER() { "user"; }
......@@ -750,15 +754,11 @@ sub TestGlobalPolicy($$$$$)
}
#
# Test policies that apply.
# Load the policies.
#
sub TestPolicies($$$$)
sub LoadPolicies($$$)
{
my ($uid, $pid, $eid, $gid) = @_;
my $failcount = 0;
my %user_policies = ();
my %group_policies = ();
my %global_policies = ();
my ($uid, $pid, $gid) = @_;
#
# Get the global policies.
......@@ -785,41 +785,45 @@ sub TestPolicies($$$$)
#
# Get user policies that apply. Ordering by uid will put the global
# policies last, which makes it easier in the loop below.
#
$query_result =
DBQueryWarn("select * from user_policies ".
"where uid='$uid' or uid='+' or uid='-' ".
"order by uid desc");
return -1
if (!$query_result);
#
if (defined($uid)) {
$query_result =
DBQueryWarn("select * from user_policies ".
"where uid='$uid' or uid='+' or uid='-' ".
"order by uid desc");
return -1
if (!$query_result);
while (my $rowref = $query_result->fetchrow_hashref()) {
my $puid = $rowref->{'uid'};
my $policy = $rowref->{'policy'};
my $auxdata = $rowref->{'auxdata'};
my $count = $rowref->{'count'};
while (my $rowref = $query_result->fetchrow_hashref()) {
my $puid = $rowref->{'uid'};
my $policy = $rowref->{'policy'};
my $auxdata = $rowref->{'auxdata'};
my $count = $rowref->{'count'};
if ($debug) {
print "User Policy: $puid, $policy, $count, $auxdata\n";
}
if ($debug) {
print "User Policy: $puid, $policy, $count, $auxdata\n";
}
if ($puid eq "+") {
$user_policies{"$policy:$auxdata"} = $rowref;
}
elsif ($puid eq "-") {
# Allow existing user policy to override this.
$user_policies{"$policy:$auxdata"} = $rowref
if (!exists($user_policies{"$policy:$auxdata"}));
}
else {
$user_policies{"$policy:$auxdata"} = $rowref;
if ($puid eq "+") {
$user_policies{"$policy:$auxdata"} = $rowref;
}
elsif ($puid eq "-") {
# Allow existing user policy to override this.
$user_policies{"$policy:$auxdata"} = $rowref
if (!exists($user_policies{"$policy:$auxdata"}));
}
else {
$user_policies{"$policy:$auxdata"} = $rowref;
}
}
}
my $gidclause = "pid=gid or gid='*'";
if (defined($gid)) {
$gidclause .= "or gid='$gid'";
}
$query_result =
DBQueryWarn("select distinct * from group_policies ".
"where (pid='$pid' and ".
" (pid=gid or gid='$gid' or gid='*')) or ".
"where (pid='$pid' and ($gidclause)) or ".
" pid='' or pid='-' ".
"order by pid,gid desc");
return -1
......@@ -832,7 +836,8 @@ sub TestPolicies($$$$)
my $count = $rowref->{'count'};
if ($debug) {
print "Group Policy: $pid, $gid, $policy, $count, $auxdata\n";
print "Group Policy: $pid, ".
(defined($gid) ? "$gid" : "") . ", $policy, $count, $auxdata\n";
}
if ($ppid eq "+") {
......@@ -847,6 +852,18 @@ sub TestPolicies($$$$)
$group_policies{"$policy:$auxdata"} = $rowref;
}
}
return 0;
}
#
# Test policies that apply.
#
sub TestPolicies($$$)
{
my ($uid, $pid, $gid) = @_;
my $failcount = 0;
LoadPolicies($uid, $pid, $gid);
#
# Now test the policies. Test them all so that we get feedback on
......@@ -915,6 +932,34 @@ sub TestPolicies($$$$)
return 1;
}
#
# For the reservation system, check to see if a node type has a maximum
# allowed allocation value in a given project or user.
#
sub MaximumAllowed($$)
{
my ($project, $type) = @_;
my $pid = $project->pid();
return -1
if (LoadPolicies(undef, $pid, undef));
foreach my $key (keys(%group_policies)) {
my $pref = $group_policies{$key};
my $ppid = $pref->{'pid'};
my $policy = $pref->{'policy'};
my $count = $pref->{'count'};
my $auxdata = $pref->{'auxdata'};
next if
($policy ne TBADMINCTRL_POLICY_TYPE());
return $count
if ($ppid eq $pid && $auxdata eq $type);
}
return undef;
}
#
# Update nodetypeXpid_permissions table.
#
......@@ -1153,7 +1198,7 @@ sub TBAdmissionControlCheck($$$)
LoadVirtNodeTypes($pid, $eid);
LoadCurrent($uid, $pid, $gid, $eid);
my $rval = TestPolicies($uid, $pid, $eid, $gid);
my $rval = TestPolicies($uid, $pid, $gid);
if ($debug && $assignflag) {
print "Assign type/class max counts:\n";
......
......@@ -51,6 +51,7 @@ use GeniCM;
use GeniHRN;
use GeniUtil;
use Reservation;
use libadminctrl;
use GeniCredential;
use GeniStd;
use English;
......@@ -734,6 +735,26 @@ sub Reserve($)
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN)
if ($reservation->pid() ne $project->pid());
my $type = $reservation->type();
}
#
# If admission control says they cannot have as many as wanted, then
# the reservation is rejected.
#
if (defined($count)) {
my $maximum = libadminctrl::MaximumAllowed($project, $type);
if (defined($maximum)) {
if ($maximum < 0) {
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
if ($count > $maximum) {
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Admission Control says limited "/
"to $maximum nodes");
}
}
}
# Use a webtask to get back output.
......@@ -744,7 +765,7 @@ sub Reserve($)
my $args = ($check ? "-n " : "") .
(defined($update) ? "-m $update " : "") .
"-T " . $webtask->task_id() . " ".
(defined($type) ? "-t $type " : "") .
(!defined($update) ? "-t $type " : "") .
(defined($start) ? "-s $start " : "") .
(defined($end) ? "-e $end " : "") .
(defined($update) && defined($count) ? "-S $count " : "") .
......@@ -990,29 +1011,10 @@ sub ReservationSystemInfo($)
my $fblob = {};
my $pblob = {};
my @alltypes = NodeType->AllTypes();
foreach my $type (@alltypes) {
next
if ($type->class() ne "pc");
my @types = Reservation->ReservableTypes();
foreach my $type (@types) {
my $type = $type->type();
#
# Skip if no physical testnodes of this type.
#
my $query_result =
DBQueryWarn("select count(node_id) from nodes ".
"where type='$type' and ".
" role='" . $Node::NODEROLE_TESTNODE . "'");
return GeniResponse->Create(GENIRESPONSE_ERROR)
if (!$query_result);
next
if (!$query_result->numrows);
my ($count) = $query_result->fetchrow_array();
next
if (!$count);
$fblob->{$type} = Reservation->FreeCount($type);
$pblob->{$type} = [ Reservation->Forecast($type) ];
}
......@@ -1023,6 +1025,94 @@ sub ReservationSystemInfo($)
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
}
#
# Get the Future Pressure and Outstanding Reservations.
#
sub ReservationPrediction($)
{
my ($argref) = @_;
#my $hasperm = CheckPermission(1);
#return $hasperm
# if (GeniResponse::IsError($hasperm));
return GeniResponse->MalformedArgsResponse("Missing project list")
if (!(exists($argref->{"projlist"}) && defined($argref->{"projlist"})));
my $projlist = $argref->{"projlist"};
if (ref($projlist) ne "ARRAY") {
return GeniResponse->MalformedArgsResponse("Malformed project list")
}
#
# Convert to local projects, skip if we do not have one yet,
# clearly no reservations for it.
#
my @projlist = ();
foreach my $projurn (@{$projlist}) {
my $project;
my $hrn = GeniHRN->new($projurn);
next
if (!defined($hrn));
if ($hrn->domain() eq $OURDOMAIN) {
$project = Project->Lookup($hrn->id());
}
else {
#
# We got a project URN but we need to lookup using the SA urn.
#
my $purn = GeniHRN::Generate($hrn->authority(), "authority", "sa");
$project = Project->LookupNonLocal($purn);
}
next
if (!defined($project));
push(@projlist, $project->pid());
}
my $pressure = {};
my $outstanding = {};
my $freecounts = {};
my $forecast = {};
my @types = Reservation->ReservableTypes();
foreach my $type (@types) {
my $type = $type->type();
$freecounts->{$type} = Reservation->FreeCount($type);
$forecast->{$type} = [ Reservation->Forecast($type) ];
}
foreach my $type (Reservation->ReservableTypes()) {
my $type = $type->type();
foreach my $pid (@projlist) {
my @pairs = Reservation->FuturePressure([$type],[$pid]);
next
if (!@pairs);
#print STDERR "P: $pid,$type,@pairs\n";
$pressure->{$type}->{$pid} = \@pairs;
}
}
foreach my $pid (@projlist) {
foreach my $type (Reservation->ReservableTypes()) {
my $type = $type->type();
my $stamp = Reservation->OutstandingReservation([$pid],[$type]);
next
if (!$stamp);
#print STDERR "O: $pid,$type,$stamp\n";
$outstanding->{$pid}->{$type} = $stamp;
}
}
my $blob = {
"freecounts" => $freecounts,
"forecast" => $forecast,
"pressure" => $pressure,
"reservations" => $outstanding,
};
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
}
#
# Map a project credential to local user/project.
#
......
......@@ -251,6 +251,7 @@ ProtoGeniDefs::AddModule("cluster",
"ApproveReservation" => \&GeniCluster::ApproveReservation,
"DeleteReservation" => \&GeniCluster::DeleteReservation,
"ReservationSystemInfo"=> \&GeniCluster::ReservationSystemInfo,
"ReservationPrediction"=> \&GeniCluster::ReservationPrediction,
}},
});
......
......@@ -55,6 +55,7 @@ $GENI_METHODS = {
"Reservations" => \&GeniCluster::Reservations,
"DeleteReservation" => \&GeniCluster::DeleteReservation,
"ReservationSystemInfo"=> \&GeniCluster::ReservationSystemInfo,
"ReservationPrediction"=> \&GeniCluster::ReservationPrediction,
};
1;
......
......@@ -111,7 +111,9 @@ $(function ()
var projcategories = MakeProfileCategories(profilelist);
// Fire this off right away.
LoadReservationInfo();
if (window.REGISTERED) {
LoadReservationInfo();
}
var html = mainTemplate({
formfields: decodejson('#form-json'),
......@@ -1743,7 +1745,7 @@ $(function ()
console.info("Could not get reservation info: " + json.value);
return;
}
console.info(json.value);
console.info("resinfo", json.value);
resinfo = json.value;
};
var $xmlthing =
......
......@@ -497,7 +497,7 @@ function Do_GetReservation()
}
#
# Ask for reservation system info, for a specific aggregate
# Ask for reservation system info, for a specific portal.
#
function Do_ReservationInfo()
{
......@@ -510,7 +510,7 @@ function Do_ReservationInfo()
$retval = SUEXEC($this_user->uid(), "nobody",
"webmanage_reservations ".
" -t $webtask_id systeminfo -p '$PORTAL_GENESIS'",
" -t $webtask_id prediction -p '$PORTAL_GENESIS'",
SUEXEC_ACTION_CONTINUE);
if ($retval) {
$webtask->Delete();
......
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