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

Checkpoint Parameterized Profile support before changing the web

UI to not use modals.
parent 6351b521
......@@ -326,6 +326,9 @@ sub Create($$$$$$)
}
if (exists($argref->{'script'}) && $argref->{'script'} ne "") {
$vquery .= ",script=" . DBQuoteSpecial($argref->{'script'});
if (exists($argref->{'paramdefs'}) && $argref->{'paramdefs'} ne "") {
$vquery .= ",paramdefs=" . DBQuoteSpecial($argref->{'paramdefs'});
}
}
# Back to the main table.
......@@ -391,10 +394,11 @@ sub NewVersion($$)
if (! DBQueryWarn("insert into apt_profile_versions ".
" (name,profileid,version,pid,pid_idx, ".
" creator,creator_idx,created,uuid, ".
" parent_profileid,parent_version,rspec) ".
" parent_profileid,parent_version,rspec, ".
" script,paramdefs) ".
"select name,profileid,'$newvers',pid,pid_idx, ".
" '$uid','$uid_idx',now(),uuid(),parent_profileid, ".
" '$version',rspec ".
" '$version',rspec,script,paramdefs ".
"from apt_profile_versions as v ".
"where v.profileid='$profileid' and ".
" v.version='$version'"));
......
......@@ -31,17 +31,17 @@ include $(OBJDIR)/Makeconf
SUBDIRS =
BIN_SCRIPTS = manage_profile manage_instance manage_dataset \
create_instance rungenilib
create_instance rungenilib rungenilibN genilibparams
SBIN_SCRIPTS =
LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm APT_Dataset.pm APT_Geni.pm
WEB_BIN_SCRIPTS = webmanage_profile webmanage_instance webmanage_dataset \
webcreate_instance webrungenilib
WEB_SBIN_SCRIPTS=
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
USERLIBEXEC = rungenilib.proxy
USERLIBEXEC = rungenilib.proxy rungenilibN.proxy
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS = rungenilib
SETUID_BIN_SCRIPTS = rungenilib rungenilibN genilibparams
SETUID_SBIN_SCRIPTS =
SETUID_SUEXEC_SCRIPTS=
......
......@@ -225,6 +225,9 @@ if (!defined($cm_authority)) {
my $cmurl = $cm_authority->url();
#$cmurl =~ s/protogeni/protogeni\/stoller/;
my $iscloudlab =
($CMURN eq "urn:publicid:IDN+utah.cloudlab.us+authority+cm" ? 1 : 0);
#
# Must wrap the parser in eval since it exits on error.
#
......@@ -249,7 +252,7 @@ foreach my $key ("username", "email", "profile") {
# Gather up args and sanity check.
#
my ($value, $user_urn, $user_uid, $user_hrn, $user_email,
$sshkey, $profile, $version);
$sshkey, $profile, $profileid, $version, $rspecstr, $errmsg);
#
# Username and email has to be acceptable to Emulab user system.
......@@ -271,48 +274,52 @@ if (! TBcheck_dbslot($value, "users", "usr_email",
$user_email = $value;
#
# Not many choices; see if it exists.
# Profile.
#
$value = $xmlparse->{'attribute'}->{"profile"}->{'value'};
# This is a safe lookup.
my $profile_object = APT_Profile->Lookup($value);
if (!defined($profile_object)) {
$value = $xmlparse->{'attribute'}->{"profile"}->{'value'};
$profile = APT_Profile->Lookup($value);
if (!defined($profile)) {
fatal("No such profile: $value");
}
#
# Temp hack; replace URNs with URLs.
#
if (1 && $profile_object->ConvertDiskImages()) {
fatal("Not able to convert disk_image URNs to URLs.");
}
my $rspecstr = $profile_object->CheckFirewall(!$localuser);
$profile = $profile_object->profileid();
$version = $profile_object->version();
$profileid = $profile->profileid();
$version = $profile->version();
#
# Look for datasets; need to verify that the datasets being referenced
# still exist and are still permissible to use, and we have to generate
# credentials for those datasets (if not a global dataset). The tricky
# aspect is that while a dataset and a profile have project permissions,
# the experiment has no project association, so if the profile/dataset
# perms are okay, then we send over a credential that tells the CM to
# allow this experiment to use that dataset in that project.
# Optional rspec, as for a Parameterized Profile.
#
my $errmsg = "Bad dataset";
if (APT_Profile::CheckDatasets($rspecstr, $profile_object->pid(), \$errmsg)) {
UserError($errmsg);
if (exists($xmlparse->{'attribute'}->{"rspec"})) {
$rspecstr = $xmlparse->{'attribute'}->{"rspec"}->{'value'};
}
#
# A temporary hack to make sure that the user does not try to run
# an x386 image on the Cloudlab cluster (ARMs). This will eventually
# get replaced with Jon's constraint checking code.
#
my $iscloudlab =
($CMURN eq "urn:publicid:IDN+utah.cloudlab.us+authority+cm" ? 1 : 0);
if ($profile_object->CheckNodeConstraints($iscloudlab, \$errmsg)) {
UserError($errmsg);
else {
$rspecstr = $profile->CheckFirewall(!$localuser);
#
# Temp hack; replace URNs with URLs.
#
if (1 && $profile->ConvertDiskImages()) {
fatal("Not able to convert disk_image URNs to URLs.");
}
#
# Look for datasets; need to verify that the datasets being referenced
# still exist and are still permissible to use, and we have to generate
# credentials for those datasets (if not a global dataset). The tricky
# aspect is that while a dataset and a profile have project permissions,
# the experiment has no project association, so if the profile/dataset
# perms are okay, then we send over a credential that tells the CM to
# allow this experiment to use that dataset in that project.
#
$errmsg = "Bad dataset";
if (APT_Profile::CheckDatasets($rspecstr, $profile->pid(), \$errmsg)) {
UserError($errmsg);
}
#
# A temporary hack to make sure that the user does not try to run
# an x386 image on the Cloudlab cluster (ARMs). This will eventually
# get replaced with Jon's constraint checking code.
#
if ($profile->CheckNodeConstraints($iscloudlab, \$errmsg)) {
UserError($errmsg);
}
}
#
......@@ -450,19 +457,25 @@ if ($localuser) {
fatal("Could not update ssh keys for nonlocal user");
}
}
elsif (!$emulab_user->isEmulab() && defined($sshkey) &&
!$emulab_user->LookupSSHKey($sshkey)) {
elsif (defined($sshkey) && !$emulab_user->LookupSSHKey($sshkey)) {
#
# A local user created via the APT/Cloud interface. Rather then
# edit keys via the old web UI, they can change their one key
# by putting a new one in the web form. If the gave us a new one,
# insert it after deleting the old one.
# A local user. We mark keys that come through this path
# with the isaptkey flag (-a to addpubkey) so that we know
# which key in the DB it is. The reason for this is that the
# user might be a classic emulab user, but is now using the
# APT/Cloud UI. Class Emulab allows multiple keys, but the
# APT/Cloud UI only allows one (which is replaced in the DB
# if it changes). We do not want to expose the Emulab ssh key
# edit page, too messy. So always operate on the one apt key
# for all users.
#
$emulab_user->DeleteSSHKeys();
if (!$emulab_user->isEmulab()) {
$emulab_user->DeleteSSHKeys();
}
my ($fh, $keyfile) = tempfile(UNLINK => 0);
print $fh $sshkey;
if (system("$ADDPUBKEY -u $user_uid -f $keyfile")) {
if (system("$ADDPUBKEY -a -u $user_uid -f $keyfile")) {
fatal("Could not add new ssh pubkey");
}
close($fh);
......@@ -501,8 +514,9 @@ if ($geniuser->GetKeyBundle(\@sshkeys, 1) < 0 || !@sshkeys) {
# Generate the extra credentials that tells the backend this experiment
# can access the datasets.
my @dataset_credentials = ();
if (CreateDatasetCreds($rspecstr,
$profile_object->pid(), $geniuser,
if (defined($profile) &&
CreateDatasetCreds($rspecstr,
$profile->pid(), $geniuser,
\$errmsg, \@dataset_credentials)) {
fatal($errmsg);
}
......@@ -519,7 +533,9 @@ my $SERVER_NAME = (exists($ENV{"SERVER_NAME"}) ? $ENV{"SERVER_NAME"} : "");
print STDERR "\n";
print STDERR "User: $user_urn\n";
print STDERR "Email: $user_email" . (!$localuser ? " (guest)" : "") . "\n";
print STDERR "Profile: " . $profile_object->name() . ":${version}\n";
if (defined($profile)) {
print STDERR "Profile: " . $profile->name() . ":${version}\n";
}
print STDERR "Slice: $slice_urn\n";
print STDERR "Server: $SERVER_NAME\n";
print STDERR "Cluster: $CMURN\n";
......@@ -578,7 +594,7 @@ if (!defined($quickvm_uuid)) {
fatal("Could not generate a new uuid");
}
my $instance = APT_Instance->Create({'uuid' => $quickvm_uuid,
'profile_id' => $profile,
'profile_id' => $profileid,
'profile_version' => $version,
'slice_uuid' => $slice_uuid,
'creator' => $geniuser->uid(),
......
......@@ -25,6 +25,7 @@ use English;
use strict;
use Getopt::Std;
use XML::Simple;
use File::Temp qw(tempfile :POSIX );
use Data::Dumper;
use CGI;
use POSIX ":sys_wait_h";
......@@ -47,6 +48,7 @@ my $update = 0;
my $snap = 0;
my $uuid;
my $rspec;
my $script;
my $profile;
my $instance;
my $webtask;
......@@ -59,6 +61,7 @@ my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $MANAGEINSTANCE = "$TB/bin/manage_instance";
my $RUNGENILIB = "$TB/bin/rungenilib";
#
# Untaint the path
......@@ -253,6 +256,9 @@ foreach $key (keys(%{ $xmlparse->{'attribute'} })) {
if ($key eq "rspec") {
$rspec = $value;
}
elsif ($key eq "script") {
$script = $value;
}
}
UserError()
if (keys(%errors));
......@@ -278,6 +284,29 @@ if (defined($rspec)) {
}
}
#
# See if this is a Parameterized Profile. Generate and store the form
# data if it is.
#
if (defined($script) && $script ne "") {
my ($fh, $filename) = tempfile();
fatal("Could not open temporary file for script")
if (!defined($fh));
print $fh $script;
my $paramdefs = `$RUNGENILIB -p $filename`;
fatal("$RUNGENILIB failed")
if ($?);
chomp($paramdefs);
if ($paramdefs ne "") {
if ($update) {
$update_args{"paramdefs"} = $paramdefs;
}
else {
$new_args{"paramdefs"} = $paramdefs;
}
}
}
#
# Are we going to snapshot a node in an experiment? If so we
# sanity check to make sure there is just one node.
......@@ -324,11 +353,15 @@ if ($update) {
if (exists($update_args{"rspec"}));
$profile->UpdateVersion({"script" => $update_args{"script"}})
if (exists($update_args{"script"}));
$profile->UpdateVersion({"paramdefs" => $update_args{"paramdefs"}})
if (exists($update_args{"paramdefs"}));
}
delete($update_args{"rspec"})
if (exists($update_args{"rspec"}));
delete($update_args{"script"})
if (exists($update_args{"script"}));
delete($update_args{"paramdefs"})
if (exists($update_args{"paramdefs"}));
}
$profile->UpdateMetaData(\%update_args) == 0 or
fatal("Could not update profile record");
......
......@@ -22,7 +22,7 @@
#
# }}}
#
use strict;
use English;
use Getopt::Std;
use Socket;
......@@ -30,6 +30,7 @@ use File::Basename;
use File::Temp qw(tempfile :POSIX );
use POSIX qw(:signal_h);
use POSIX ":sys_wait_h";
use File::stat;
#
# Parse an ns file. Since the parser runs arbitrary NS file for the user,
......@@ -45,8 +46,10 @@ sub usage()
exit(-1);
}
my $optlist = "do:";
my $debug = 0;
my $optlist = "do:pb:";
my $debug = 0;
my $getparams = 0;
my $paramfile;
my $ofile;
#
......@@ -98,13 +101,26 @@ use User;
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"p"})) {
$getparams = 1;
}
if (defined($options{"b"})) {
$paramfile = $options{"b"};
# Must taint check!
if ($paramfile =~ /^([-\w\/\.]+)$/) {
$paramfile = $1;
}
else {
die("Bad data in argument: $paramfile.");
}
}
if (defined($options{"o"})) {
$ofile = $options{"o"};
}
......@@ -131,9 +147,6 @@ if (defined($ofile)) {
}
}
my $infile = tmpnam();
my $outfile = tmpnam();
#
# Get DB uid for sending over to ops.
#
......@@ -144,9 +157,21 @@ if (! defined($this_user)) {
# Run as the user for most of this script.
$EUID = $UID;
# Now append the import file to. This part is hokey. Fix later.
system("cat $file >> $infile") == 0
or fatal("Could not combine defs file and the script file!");
#
# If looking for the parameter definition block, then see if there
# are any define_parameter calls. If so, run the script to get the
# JSON block.
#
if ($getparams && system("egrep -q -s 'defineParameter' $file")) {
# No parameters, return empty block.
if (defined($ofile)) {
system("echo '' > $ofile");
}
exit(0);
}
my $infile = tmpnam();
my $outfile = tmpnam();
#
# Touch the output file, to avoid a root owned, 644 file.
......@@ -159,7 +184,32 @@ system("touch $outfile") == 0 or
# result back to a file if not in anonmode. Remember to tack on the
# user ID to flip to, when not in testmode.
#
my $cmdargs = "$TB/libexec/rungenilib.proxy -u " . $this_user->uid();
my $cmdargs = "$TB/libexec/rungenilibN.proxy ";
$cmdargs .= " -u " . $this_user->uid();
$cmdargs .= ($getparams ? " -p " : "");
#
# We want to send over both files via STDIN, so combine them, and pass
# the first file size with the -b option
#
if ($paramfile) {
system("cat $paramfile > $infile") == 0
or fatal("Could not copy $paramfile to $infile");
$cmdargs .= " -b " . stat($infile)->size;
system("cat $file >> $infile") == 0
or fatal("Could not concat $file to $infile");
}
else {
system("cat $file > $infile") == 0
or fatal("Could not copy $file to $infile");
}
$cmdargs = "sshtb -host $CONTROL $cmdargs < $infile";
if ($debug) {
print $cmdargs . "\n";
}
#
# Run parser, redirecting stdout to a file to capture the parser results.
......@@ -167,7 +217,7 @@ my $cmdargs = "$TB/libexec/rungenilib.proxy -u " . $this_user->uid();
# Must flip to real root to run ssh.
#
$EUID = $UID = 0;
open ERR, "sshtb -host $CONTROL $cmdargs < $infile 2>&1 >> $outfile |";
open ERR, "$cmdargs 2>&1 >> $outfile |";
$EUID = $UID = $SAVEUID;
#
......
......@@ -22,17 +22,15 @@
#
# }}}
#
use strict;
use English;
use Getopt::Long;
use Getopt::Std;
use BSD::Resource;
use POSIX qw(:signal_h);
#
# Simply a wrapper for the geni-lib python environment
#
# When run in "impotent" mode, there is no output, just an exit code.
#
sub usage()
{
print STDOUT
......@@ -41,6 +39,10 @@ sub usage()
exit(-1);
}
my $optlist = "u:vpb:";
my $user;
my $getparams= 0;
my $paramsize;
#
# Configure variables
......@@ -54,9 +56,11 @@ my $debug = 0;
# Locals
my $tempdir = "/tmp/genilib-$$";
my $ifile = "$$.py";
my $ofile = "$$.rspec";
my $ofile = "$$.out";
my $pfile = "$$.json";
my $optlist = "u:v";
# Protos
sub fatal($);
#
# Turn off line buffering on output
......@@ -76,15 +80,26 @@ $ENV{"PYTHONPATH"} = $GENILIB;
use lib "@prefix@/lib";
use libtestbed;
my $user;
#
# Parse command arguments. Once we return from getopts, all that should be
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
if (! GetOptions("u:s" => \$user)) {
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"p"})) {
$getparams = 1;
}
if (defined($options{"b"})) {
$paramsize = $options{"b"};
}
if (defined($options{"u"})) {
$user = $options{"u"};
}
#
# First option has to be the -u option, the user to run this script as.
......@@ -97,7 +112,7 @@ if (! $TESTMODE) {
" Must be root to run this script!");
}
(undef,undef,$unix_uid) = getpwnam($user) or
my (undef,undef,$unix_uid) = getpwnam($user) or
die("*** $0:\n".
" No such user $user\n");
......@@ -142,26 +157,43 @@ if (! $TESTMODE) {
}
#
# Take our input and write it to the temp file.
# Take our input and write it to the temp file. If we are also getting
# a parameter file in the stream, write that to another file.
#
if (defined($paramsize) && $paramsize) {
my $paramstr;
open(PTMP, ">$pfile") ||
fatal("Couldn't open $pfile\n");
my $cc = read(STDIN, $paramstr, $paramsize);
if (!defined($cc) || $cc != $paramsize) {
fatal("Could not read paramter block from stdin");
}
print PTMP $paramstr;
close(PTMP);
chmod(0644, $pfile);
}
open(TMP, ">$ifile") ||
fatal("Couldn't open $ifile\n");
#
# Prepend our little helper function that spits the rspec to a file.
#
print TMP "\n\n";
print TMP "def printRspec(rspec):\n";
print TMP " f = open('$ofile', 'w+')\n";
print TMP " f.write(str(rspec))\n";
print TMP " f.close()\n";
print TMP " pass\n\n";
while (<STDIN>) {
print TMP $_;
}
close(TMP);
chmod(0644, $ifile);
#
# Need to add things to the environment for the portal module.
#
$ENV{'GENILIB_PORTAL_MODE'} = "Yep";
$ENV{'GENILIB_PORTAL_REQUEST_PATH'} = $ofile;
if ($getparams) {
$ENV{'GENILIB_PORTAL_DUMPPARAMS_PATH'} = $ofile;
}
elsif (defined($paramsize) && $paramsize) {
$ENV{'GENILIB_PORTAL_PARAMS_PATH'} = $pfile;
}
#
# Fork a child process to run the parser in.
#
......@@ -239,4 +271,10 @@ unless($debug) {
}
exit($exit_status);
sub fatal($) {
my ($mesg) = $_[0];
print STDERR "*** $0:\n".
" $mesg\n";
exit(-1);
}
......@@ -55,6 +55,7 @@ function Do_GetProfile()
if ($profile->BestAggregate()) {
$amdefault = $profile->BestAggregate();
}
$ispp = ($profile->isParameterized() ? 1 : 0);
#
# Knowing the UUID means the user can instantiate it,
......@@ -62,8 +63,120 @@ function Do_GetProfile()
#
SPITAJAX_RESPONSE(array('rspec' => $profile->rspec(),
'name' => $profile->name(),
'ispprofile' => $ispp,
'amdefault' => $amdefault));
}
#
# Return parameter form fragment and default values.
#
function Do_GetParameters()
{
global $this_user;
global $ajax_args;
global $suexec_output, $suexec_output_array;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$profile = Profile::Lookup($ajax_args["uuid"]);
if (!$profile) {
SPITAJAX_ERROR(1, "Unknown profile uuid");
return;
}
if (!isset($this_user)) {
if (!$profile->ispublic()) {
SPITAJAX_ERROR(1, "Not enough permission to instantiate profile");
return;
}
}
elseif (!$profile->CanInstantiate($this_user)) {
SPITAJAX_ERROR(1, "Not enough permission to instantiate profile");
return;
}
if (!$profile->isParameterized()) {
SPITAJAX_ERROR(1, "Not a parameterized profile");
return;
}
list ($formfrag, $defaults) = $profile->GenerateFormFragment();
SPITAJAX_RESPONSE(array("formfrag" => htmlentities($formfrag),
"defaults" => $defaults));
}
#
# Instantiate profile (as the user)
#
function Do_Instantiate()
{
global $this_user, $am_array, $DEFAULT_AGGREGATE, $ISCLOUD;
global $ajax_args;
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing profile uuid");
return;
}
$profile = Profile::Lookup($ajax_args["uuid"]);
if (!$profile) {
SPITAJAX_ERROR(1, "Unknown profile uuid");
return;
}
if (!isset($this_user)) {
if (!$profile->ispublic()) {
SPITAJAX_ERROR(1, "Not enough permission to instantiate profile");
return;
}
}