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