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

Add (optional) experiment names; users now get to provide a pithy name for

their experiments, we fall back to naming it for them.
parent 4a27b0ce
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2014 University of Utah and the Flux Group.
# Copyright (c) 2007-2015 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -178,5 +178,37 @@ sub GeniContext()
return Genixmlrpc->Context($certificate);
}
#
# Generate an SA credential (sorta like an admin credential).
#
sub GenAuthCredential($;$)
{
my ($target, $privs) = @_;
my $certificate = GeniCertificate->LoadFromFile($SACERT);
if (!defined($certificate)) {
print STDERR "Could not load certificate from $SACERT\n";
return undef;
}
my $credential = GeniCredential->Create($target, $certificate);
if (!defined($credential)) {
print STDERR "Could not create credential for $target\n";
return undef;
}
# Add optional privs.
if (defined($privs)) {
foreach my $priv (@{ $privs }) {
$credential->AddCapability($priv, 0);
}
}
# And sign it.
if ($credential->Sign($GeniCredential::LOCALSA_FLAG) != 0) {
$credential->Delete();
print STDERR "Could not sign $target credential\n";
return undef
}
return $credential;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -143,9 +143,9 @@ sub Refresh($)
#
# Create an Instance
#
sub Create($$)
sub Create($$$)
{
my ($class, $argref) = @_;
my ($class, $argref, $perrmsg) = @_;
my $uuid;
if (exists($argref->{'uuid'})) {
......@@ -155,6 +155,8 @@ sub Create($$)
else {
$uuid = NewUUID();
}
my $pid_idx = $argref->{'pid_idx'};
my $name = $argref->{'name'};
#
# The uuid has to be unique, so lock the table for the check/insert.
......@@ -167,7 +169,16 @@ sub Create($$)
if ($query_result->numrows) {
DBQueryWarn("unlock tables");
tberror("Instance uuid $uuid already exists!");
$$perrmsg = "Instance uuid $uuid already exists!";
return undef;
}
$query_result =
DBQueryWarn("select name from apt_instances ".
"where name='$name' and pid_idx='$pid_idx'");
if ($query_result->numrows) {
DBQueryWarn("unlock tables");
$$perrmsg = "Instance name already in use!";
return undef;
}
......@@ -177,7 +188,7 @@ sub Create($$)
if (! DBQueryWarn($query)) {
DBQueryWarn("unlock tables");
tberror("Error inserting new apt_instance record for $uuid!");
$$perrmsg = "Error inserting new apt_instance record for $uuid!";
return undef;
}
DBQueryWarn("unlock tables");
......@@ -617,11 +628,9 @@ sub Terminate($)
if ($speaksfor_credential->IsExpired()) {
print STDERR "speaksfor credential has expired, generating a new one\n";
$slice_credential =
GeniCredential->CreateSigned($slice, $context,
$GeniCredential::LOCALSA_FLAG);
$slice_credential = APT_Geni::GenAuthCredential($slice);
if (!defined($slice_credential)) {
print STDERR "Could not generate SA credential\n";
print STDERR "Could not generate slice credential\n";
return undef;
}
$credentials = [$slice_credential->asString()];
......@@ -674,6 +683,7 @@ sub Terminate($)
sub Extend($$)
{
my ($self, $new_expires) = @_;
my $credentials;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->GetGeniUser();
my $slice = $self->GetGeniSlice();
......@@ -682,6 +692,33 @@ sub Extend($$)
if (! (defined($geniuser) && defined($authority) &&
defined($slice) && defined($context)));
my ($slice_credential, $speaksfor_credential) =
APT_Geni::GenCredentials($slice, $geniuser);
return undef
if (! (defined($speaksfor_credential) &&
defined($slice_credential)));
#
# Special case; if the speaksfor_credential has expired cause it
# was for a nonlocal user, we have no choice but to throw away
# these credentials and generate a new one issued to the local SA
# instead of the user.
#
if ($speaksfor_credential->IsExpired()) {
print STDERR "speaksfor credential has expired, generating a new one\n";
$slice_credential = APT_Geni::GenAuthCredential($slice);
if (!defined($slice_credential)) {
print STDERR "Could not generate slice credential\n";
return undef;
}
$credentials = [$slice_credential->asString()];
}
else {
$credentials = [$slice_credential->asString(),
$speaksfor_credential->asString()];
}
#
# We need a special credentential in case the aggregate is enforcing
# limits (as do Utah aggregates).
......@@ -696,7 +733,8 @@ sub Extend($$)
# is not in the same domain as the speaksfor, so we use the geni certificate
# that the trusted signer gave us and is stored in the DB.
#
if ($geniuser->IsLocal() && $geniuser->emulab_user()->IsNonLocal()) {
if ($geniuser->IsLocal() && $geniuser->emulab_user()->IsNonLocal() &&
!$speaksfor_credential->IsExpired()) {
my (undef, $certificate_string) =
$geniuser->emulab_user()->GetStoredCredential();
if (! defined($certificate_string)) {
......@@ -708,6 +746,7 @@ sub Extend($$)
print STDERR "Could not load stored certificate for $geniuser\n";
return undef;
}
# This file will be auto deleted.
$certfile = $certificate->WriteToFile();
$userarg = "-c $certfile";
}
......@@ -727,18 +766,10 @@ sub Extend($$)
unlink($credname);
chomp($extcred);
my ($slice_credential, $speaksfor_credential) =
APT_Geni::GenCredentials($slice, $geniuser);
return undef
if (! (defined($speaksfor_credential) &&
defined($slice_credential)));
my $args = {
"slice_urn" => $slice->urn(),
"expiration" => $new_expires,
"credentials" => [$slice_credential->asString(),
$speaksfor_credential->asString(),
$extcred],
"credentials" => [@$credentials, $extcred],
};
my $cmurl = $authority->url();
$cmurl =~ s/protogeni/protogeni\/stoller/ if ($usemydevtree);
......@@ -853,23 +884,19 @@ sub Lockdown($$)
{
my ($self, $clear) = @_;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->GetGeniUser();
my $slice = $self->GetGeniSlice();
my $context = APT_Geni::GeniContext();
return undef
if (! (defined($geniuser) && defined($authority) &&
if (! (defined($authority) &&
defined($slice) && defined($context)));
my ($slice_credential, $speaksfor_credential) =
APT_Geni::GenCredentials($slice, $geniuser);
my $slice_credential = APT_Geni::GenAuthCredential($slice);
return undef
if (! (defined($speaksfor_credential) &&
defined($slice_credential)));
if (! defined($slice_credential));
my $args = {
"slice_urn" => $slice->urn(),
"credentials" => [$slice_credential->asString(),
$speaksfor_credential->asString()],
"credentials" => [$slice_credential->asString()],
};
$args->{"clear"} = 1
if ($clear);
......
......@@ -254,7 +254,7 @@ foreach my $key ("username", "email", "profile") {
# Gather up args and sanity check.
#
my ($value, $user_urn, $user_uid, $user_hrn, $user_email, $project, $pid,
$sshkey, $profile, $profileid, $version, $rspecstr, $errmsg);
$sshkey, $profile, $profileid, $version, $rspecstr, $errmsg, $slice_id);
#
# Username and email has to be acceptable to Emulab user system.
......@@ -275,6 +275,19 @@ if (! TBcheck_dbslot($value, "users", "usr_email",
}
$user_email = $value;
#
# The instance name is optional, we will make one up if not supplied.
#
if (exists($xmlparse->{'attribute'}->{"instance_name"}) &&
$xmlparse->{'attribute'}->{"instance_name"}->{'value'} ne "") {
$value = $xmlparse->{'attribute'}->{"instance_name"}->{'value'};
if (! TBcheck_dbslot($value, "experiments", "eid",
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
fatal("Illegal instance name: $value");
}
$slice_id = $value;
}
#
# Profile.
#
......@@ -537,6 +550,10 @@ elsif (!$localuser) {
}
# Guest users get a holding project.
$pid = "aptguests";
$project = Project->Lookup($pid);
if (!defined($project)) {
fatal("Project $pid does not exist");
}
}
# There will be "internal" keys cause we pass the flag asking for them.
my @sshkeys;
......@@ -561,7 +578,9 @@ if (defined($profile)) {
# Now generate a slice registration and credential
#
my $safe_uid = $user_uid; $safe_uid =~ s/_/-/;
my $slice_id = $safe_uid . "-QV" . TBGetUniqueIndex('next_quickvm', 1);
if (! defined($slice_id)) {
$slice_id = $safe_uid . "-QV" . TBGetUniqueIndex('next_quickvm', 1);
}
my $slice_urn = GeniHRN::Generate("${OURDOMAIN}:${pid}", "slice", $slice_id);
my $slice_hrn = "${PGENIDOMAIN}.${slice_id}";
my $SERVER_NAME = (exists($ENV{"SERVER_NAME"}) ? $ENV{"SERVER_NAME"} : "");
......@@ -630,6 +649,7 @@ if (!defined($quickvm_uuid)) {
fatal("Could not generate a new uuid");
}
my $blob = {'uuid' => $quickvm_uuid,
'name' => $slice_id,
'profile_id' => $profileid,
'profile_version' => $version,
'slice_uuid' => $slice_uuid,
......@@ -645,10 +665,12 @@ if (defined($project)) {
$blob->{"pid"} = $project->pid();
$blob->{"pid_idx"} = $project->pid_idx();
}
my $instance = APT_Instance->Create($blob);
$errmsg = undef;
my $instance = APT_Instance->Create($blob, \$errmsg);
if (!defined($instance)) {
$slice->Delete();
fatal("Could not create instance record for $quickvm_uuid");
fatal(defined($errmsg) ? $errmsg :
"Could not create instance record for $quickvm_uuid");
}
#
# Create a webtask so that we can store additional information about
......
......@@ -94,8 +94,9 @@ if (isset($min) || isset($max)) {
$query_result =
DBQueryFatal("select h.uuid,h.profile_version,h.created,h.destroyed, ".
" h.creator,p.uuid as profile_uuid,p.name,p.pid,u.email, ".
" h.physnode_count,h.virtnode_count, ".
" h.creator,p.uuid as profile_uuid,p.pid,u.email, ".
" h.physnode_count,h.virtnode_count,".
" h.name as instance_name,p.name as profile_name, ".
" truncate(h.physnode_count * ".
" ((UNIX_TIMESTAMP(h.destroyed) - ".
" UNIX_TIMESTAMP(h.created)) / 3600.0),2) as phours ".
......@@ -115,7 +116,8 @@ if (mysql_num_rows($query_result) == 0) {
if (1) {
while ($row = mysql_fetch_array($query_result)) {
$pname = $row["name"];
$pname = $row["profile_name"];
$iname = $row["instance_name"];
$pproj = $row["pid"];
$puuid = $row["profile_uuid"];
$created = DateStringGMT($row["created"]);
......@@ -132,6 +134,9 @@ if (1) {
if (!isset($destroyed)) {
$destroyed = "";
}
if (!isset($iname)) {
$iname = " ";
}
# If a guest user, use email instead.
if (isset($email)) {
......@@ -141,7 +146,7 @@ if (1) {
# Save space with array instead of hash.
$instance =
array($pname, $pproj, $puuid, $pcount, $vcount,
$creator, $created, $destroyed, $phours);
$creator, $created, $destroyed, $phours, $iname);
$instances[] = $instance;
}
......
......@@ -63,6 +63,7 @@ class Instance
return (is_null($this->instance) ? -1 : $this->instance[$name]);
}
function uuid() { return $this->field('uuid'); }
function name() { return $this->field('name'); }
function slice_uuid() { return $this->field('slice_uuid'); }
function creator() { return $this->field('creator'); }
function creator_idx() { return $this->field('creator_idx'); }
......@@ -119,6 +120,22 @@ class Instance
return Instance::Lookup($uuid);
}
function LookupByName($project, $token) {
$safe_token = addslashes($token);
$pid_idx = $project->pid_idx();
$query_result =
DBQueryFatal("select uuid from apt_instances ".
"where pid_idx='$pid_idx' and name='$safe_token'");
if (! ($query_result && mysql_num_rows($query_result))) {
return null;
}
$row = mysql_fetch_row($query_result);
$uuid = $row[0];
return Instance::Lookup($uuid);
}
#
# Refresh an instance by reloading from the DB.
#
......
......@@ -482,6 +482,42 @@ function SPITFORM($formfields, $newuser, $errors)
echo "</div>\n";
}
#
# Spit out an experient name box, which is optional and prefilled
# with a default.
#
if ($this_user) {
$thisclass = "form-group";
if ($errors && array_key_exists("name", $errors)) {
$thisclass .= " has-error";
}
echo "<div class='form-horizontal'>
<div class='$thisclass'>
<label class='col-sm-4 control-label'
style='text-align: right;'>Name:</label>
<div class='col-sm-6'
data-toggle='popover'
data-delay='{hide:1500, show:500}'
data-html='true'
data-content='Provide a unique name to identity your
new experiment. If you are in
the habit of starting more then one experiment at
a time this is really handy when trying to tell
one experiment from another, or when referring to
an experiment when asking for help.'>
<input id='experiment_name'
placeholder='Optional'
class='form-control'
name='formfields[name]'
value='" . $formfields["name"] . "'>\n";
if ($errors && array_key_exists("name", $errors)) {
echo " <label class='control-label'>". $errors["name"] . "</label>";
}
echo " </div>
</div>
</div>\n";
}
#
# Spit out a project selection list if more then one project membership
#
......@@ -737,6 +773,23 @@ if ($this_user) {
else {
$args["pid"] = $project->pid_idx();
}
# Experiment name is optional, we generate one later.
if (isset($formfields["name"]) && $formfields["name"] != "") {
if (strlen($formfields["name"]) > 16) {
$errors["name"] = "Too long; must be <= 16 characters";
}
elseif (!TBvalid_eid($formfields["name"])) {
$errors["name"] = TBFieldErrorString();
}
elseif ($project &&
Instance::LookupByName($project, $formfields["name"])) {
$errors["name"] = "Already in use by another experiment";
}
else {
$args["instance_name"] = $formfields["name"];
}
}
}
#
......
......@@ -123,11 +123,8 @@ function SPITROWS($all, $name, $result)
echo " <table class='tablesorter' id='tablesorter_${name}'>
<thead>
<tr>
<th>Name</th>
<th>Profile</th>\n";
if (ISADMIN()) {
echo " <th>Slice</th>";
}
if ($all) {
echo " <th>Creator</th>\n";
}
......@@ -147,11 +144,12 @@ function SPITROWS($all, $name, $result)
$profile_id = $row["profile_id"];
$version = $row["profile_version"];
$uuid = $row["uuid"];
$name = $row["name"];
$status = $row["status"];
$created = DateStringGMT($row["created"]);
$expires = DateStringGMT($row["expires"]);
$creator_idx = $row["creator_idx"];
$profile_name = $profile_id;
$profile_name = "$profile_id:$version";
$creator_uid = $row["creator"];
$pid = $row["pid"];
$urn = $row["aggregate_urn"];
......@@ -176,18 +174,22 @@ function SPITROWS($all, $name, $result)
if ($row["expired"]) {
$status = "expired";
}
$profile = Profile::Lookup($profile_id, $version);
if ($profile) {
$profile_name = $profile->name();
$profile_name = $profile->name();
$profile_uuid = $profile->uuid();
}
echo " <tr>
<td>
<a href='status.php?uuid=$uuid'>$profile_name</a>
</td>";
if (ISADMIN()) {
echo "<td>$hrn</td>";
if (!isset($name)) {
$name = $hrn;
}
echo " <tr>\n";
echo "<td><a href='status.php?uuid=$uuid'>$name</a></td>";
if ($profile) {
echo "<td><a href='show-profile.php?uuid=$uuid'>
$profile_name</a></td>";
}
else {
echo "<td>$profile_name</td>\n";
}
if ($all) {
echo "<td>$creator</td>";
......
......@@ -17,6 +17,7 @@
<table class='tablesorter' id='activity_table'>
<thead>
<tr>
<th>Name</th>
<th>Profile</th>
<th>Pid</th>
<th>Creator</th>
......@@ -28,7 +29,7 @@
</tr>
<tr>
<th class='filter-false sorter-false'>Totals</th>
<th class='filter-false sorter-false' colspan=2></th>
<th class='filter-false sorter-false' colspan=3></th>
<th class='filter-false sorter-false' data-math='col-sum'>0</th>
<th class='filter-false sorter-false' data-math='col-sum'
data-math-mask='##0'>0</th>
......@@ -40,7 +41,7 @@
<tfoot>
<tr>
<th class='filter-false sorter-false'>Totals</th>
<th class='filter-false sorter-false' colspan=2></th>
<th class='filter-false sorter-false' colspan=3></th>
<th class='filter-false sorter-false' data-math='col-sum'>0</th>
<th class='filter-false sorter-false' data-math='col-sum'
data-math-mask='##0'>0</th>
......@@ -52,6 +53,7 @@
<tbody>
<% _.each(instances, function(instance) { %>
<tr>
<td><%- instance[0] %></a></td>
<td>
<a href='manage_profile.php?action=edit&uuid=<%- instance[2] %>'>
<%- instance[0] %></a>
......
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