Commit a010ae44 authored by Leigh Stoller's avatar Leigh Stoller

Add a quick prototype implementation of an rspec to genilib converter. As

proof of concept, the NS converter path now uses ns2rspec piped into the
newly added rspec2genlib, which becomes the source code for NS based
experiments that are being converted to Portal experiments.
parent 47f21843
......@@ -31,12 +31,13 @@ include $(OBJDIR)/Makeconf
SUBDIRS =
BIN_SCRIPTS = manage_profile manage_instance manage_dataset \
create_instance rungenilib ns2rspec nsgenilib.py
create_instance rungenilib ns2rspec nsgenilib.py rspec2genilib \
ns2genilib
SBIN_SCRIPTS = apt_daemon aptevent_daemon portal_xmlrpc
LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm APT_Dataset.pm APT_Geni.pm \
APT_Aggregate.pm APT_Utility.pm
WEB_BIN_SCRIPTS = webmanage_profile webmanage_instance webmanage_dataset \
webcreate_instance webrungenilib webns2rspec
webcreate_instance webrungenilib webns2rspec webns2genilib
WEB_SBIN_SCRIPTS= webportal_xmlrpc
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
USERLIBEXEC = rungenilib.proxy genilib-jail genilib-iocage
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
use strict;
use English;
use Getopt::Std;
use File::Temp qw(tempfile :mktemp :POSIX );
use POSIX qw(:signal_h);
use POSIX ":sys_wait_h";
use File::stat;
#
# Convert an NS file into rspec using geni-lib and some lxml parsing.
#
sub usage()
{
print STDERR "Usage: ns2genilib [-a | -p pid] [-o filename] nsfile\n";
print STDERR " ns2genilib [-a | -p pid] [-o filename] -e pid,eid\n";
print STDERR "Options:\n";
print STDERR " -p pid : Run in context of project (permission checks)\n";
print STDERR " -e eid : Get NS file from Classic experiment\n";
print STDERR " -a : Run in anon mode (no project checks)\n";
print STDERR" -o file : Specify output file\n";
exit(-1);
}
my $optlist = "dao:p:e:";
my $debug = 0;
my $anonmode = 0;
my $ofile;
my $pid;
my $experiment;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $CONTROL = "@USERNODE@";
my $PARSENS = "$TB/libexec/parse-ns";
my $NSGENILIB = "$TB/bin/nsgenilib.py";
my $RS2GENILIB = "$TB/bin/rspec2genilib";
# Locals
my $nsfile;
# Protos
sub fatal($);
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/sbin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
if ($UID == 0) {
die("Please do not run this as root!");
}
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use Experiment;
use Project;
use User;
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"a"})) {
$anonmode = 1;
}
if (defined($options{"o"})) {
$ofile = $options{"o"};
}
if (defined($options{"p"})) {
$pid = $options{"p"};
}
if (defined($options{"e"})) {
$experiment = Experiment->Lookup($options{"e"});
if (!defined($experiment)) {
fatal("No such experiment");
}
}
else {
if (@ARGV != 1) {
usage();
}
$nsfile = $ARGV[0];
#
# Must taint check!
#
if ($nsfile =~ /^([-\w\/\.]+)$/) {
$nsfile = $1;
}
else {
die("Bad data in argument: $nsfile.");
}
}
my $this_user = User->ThisUser();
if (! defined($this_user)) {
Fatal("You ($UID) do not exist!");
}
if (defined($pid)) {
my $project = Project->Lookup($pid);
if (!defined($project)) {
Fatal("No such project");
}
if (! ($this_user->IsAdmin() || defined($project->LookupUser($this_user)))) {
Fatal("No a member of project $pid");
}
}
my $tmpdir = mktemp("/tmp/ns2genilib.XXXXXX");
my $irfile = "irfile.xml";
my $xmlfile = "rspec.xml";
my $pyfile = "genilib.py";
if (! mkdir("$tmpdir", 0755)) {
fatal("Could not create temp directory");
}
if (! chdir($tmpdir)) {
die("Could not chdir to $tmpdir: $!\n");
}
if (defined($experiment)) {
$nsfile = "nsfile.ns";
my $ns;
if ($experiment->GetNSFile(\$ns) || !defined($ns)) {
fatal("No NS file for experiment");
}
#
# Blech. We want to add an NS statement that sets the description
# since it is not metadata in a profile, it is part of the source.
#
my @lines = split("\n", $ns);
$ns = "";
foreach my $line (@lines) {
$ns .= "$line\n";
if ($line =~ /^set\s+(.*)\s+\[new Simulator/) {
$ns .= "\$" . $1 . " description \"" .
$experiment->description() . "\"\n";
}
}
open(NS, ">$nsfile")
or fatal("Could not open $nsfile for writing");
print NS $ns;
close(NS);
}
#
# When coming in from the web interface, we run in anonmode to parse without
# a project (since the portal sets the project later). Instead, we will rerun
# the parser again when the user creates the experiment sicne then we know the
# project context.
#
my $parseopts = ($anonmode ? "-n -p -a" : "-n -r $pid $pid");
open ERR, "$PARSENS -c $parseopts $nsfile 2>&1 > $irfile |";
#
# Now read in the results from stderr.
#
my $errs = "";
while (<ERR>) {
$errs .= $_;
}
close(ERR);
if ($?) {
my $tmp = $?;
print STDERR $errs;
system("/bin/rm -rf $tmpdir");
# This error is shown to the user.
if (defined($ofile)) {
if (open(OFILE, "> $ofile")) {
print OFILE $errs;
close(OFILE);
}
}
exit($tmp >> 8);
}
#
# Ick, first line is a tag.
#
open(IR, $irfile)
or fatal("Could not open $irfile");
open(XML, ">$xmlfile")
or fatal("Could not open $xmlfile");
while (<IR>) {
next
if ($_ =~ /^#/);
print XML $_;
}
close(IR);
close(XML);
system("/bin/mv $xmlfile $irfile") == 0 or
fatal("Could not rename irfile");
if ($debug) {
system("/bin/cat $irfile");
}
#
# The next thing is to feed the IR output of the NS parser into
# our geni-lib converter which produces the rspec.
#
open ERR, "$NSGENILIB $irfile 2>&1 > $xmlfile |";
$errs = "";
while (<ERR>) {
$errs .= $_;
}
close(ERR);
if ($?) {
my $tmp = $?;
print STDERR $errs;
system("/bin/cat $xmlfile")
if (-s $xmlfile);
system("/bin/rm -rf $tmpdir");
# This error is shown to the user.
if (defined($ofile)) {
if (open(OFILE, "> $ofile")) {
print OFILE $errs;
close(OFILE);
}
}
exit($tmp >> 8);
}
if ($debug) {
system("/bin/cat $xmlfile");
}
#
# Now feed the rspec into the rspec2genilib converter.
#
open ERR, "$RS2GENILIB $xmlfile 2>&1 > $pyfile |";
$errs = "";
while (<ERR>) {
$errs .= $_;
}
close(ERR);
if ($?) {
my $tmp = $?;
print STDERR $errs;
system("/bin/cat $pyfile")
if (-s $pyfile);
system("/bin/rm -rf $tmpdir");
# This error is shown to the user.
if (defined($ofile)) {
if (open(OFILE, "> $ofile")) {
print OFILE $errs;
close(OFILE);
}
}
exit($tmp >> 8);
}
if (defined($ofile)) {
system("cat $pyfile > $ofile");
}
else {
system("cat $pyfile");
}
system("/bin/rm -rf $tmpdir");
exit(0);
sub fatal($) {
my ($mesg) = $_[0];
print STDERR "*** $0:\n".
" $mesg\n";
system("/bin/rm -rf $tmpdir")
if (defined($tmpdir));
exit(-1);
}
This diff is collapsed.
......@@ -517,10 +517,9 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, aptforms,
if (window.CLONING) {
sup.ShowModal('#warn_pp_modal');
}
else if (_.has(window, "EXPUUID")) {
modified = true;
checkScript($('#profile_script_textarea').val());
}
}
else if (_.has(window, "EXPUUID")) {
ConvertFromExperiment();
}
}
}
......@@ -1111,6 +1110,47 @@ function (_, sup, filesize, JacksEditor, ShowImagingModal, moment, aptforms,
"pid" : $('#profile_pid').val()});
xmlthing.done(callback);
}
/*
* Convert from an NS file. The server will do the conversion and spit
* back a genilib script.
*/
function ConvertFromExperiment()
{
var callback = function(json) {
sup.HideModal("#waitwait-modal");
console.info(json.value);
if (json.code) {
sup.SpitOops("oops",
"<pre><code>" +
$('<div/>').text(json.value).html() +
"</code></pre>");
return;
}
$('#rspec_modal_download_button').addClass("hidden");
$('#rspec_modal_editbuttons').removeClass("hidden");
$('#rspec_modal_viewbuttons').addClass("hidden");
$('#modal_profile_rspec_textarea').prop("readonly", false);
$('#modal_profile_rspec_textarea').val(json.value.script);
$('#rspec_modal').modal({'backdrop':'static','keyboard':false});
$('#rspec_modal').modal('show');
}
/*
* Send along the project if one is selected; only makes sense
* for NS files, which need to do project based checks on a few
* things (images and blockstores being the most important).
* If this is a modification to an existing profile, we still
* have the project name in the same variable.
*/
sup.ShowModal("#waitwait-modal");
var xmlthing = sup.CallServerMethod(ajaxurl,
"manage_profile",
"ConvertClassic",
{"uuid" : window.EXPUUID,
"pid" : $('#profile_pid').val()});
xmlthing.done(callback);
}
$(document).ready(initialize);
});
......@@ -611,6 +611,70 @@ function GenGenilibKey()
return 0;
}
#
# Convert classic experiment to geni-lib script.
#
function Do_ConvertClassic()
{
global $this_user;
global $ajax_args, $TB_EXPT_MODIFY;
$this_idx = $this_user->uid_idx();
$this_uid = $this_user->uid();
if (!isset($ajax_args["uuid"])) {
SPITAJAX_ERROR(1, "Missing experiment uuid");
return;
}
$experiment = Experiment::LookupByUUID($ajax_args["uuid"]);
if (!$experiment) {
SPITAJAX_ERROR(1, "No such experiment.");
return;
}
if (!$experiment->AccessCheck($this_user, $TB_EXPT_MODIFY)) {
SPITAJAX_ERROR(1, "Not enough permission to create a profile from ".
"this classic emulab experiment");
return;
}
$command = "webns2genilib";
#
# We want to parse in the context of the project, so we can do
# project based checks.
#
if (isset($ajax_args["pid"]) && $ajax_args["pid"] != "") {
if (! TBvalid_pid($ajax_args["pid"])) {
SPITAJAX_ERROR(1, "Bad project name");
return;
}
$command .= " -p " . $ajax_args["pid"];
}
$outfname = tempnam("/tmp", "genilibout");
chmod($outfname, 0666);
#
# Invoke the backend.
#
$retval = SUEXEC($this_uid, "nobody", "$command -o $outfname ".
"-e " . $experiment->pid() . "," . $experiment->eid(),
SUEXEC_ACTION_IGNORE);
if ($retval != 0) {
if ($retval < 0) {
SUEXECERROR(SUEXEC_ACTION_CONTINUE);
SPITAJAX_ERROR(-1, "Internal error, we have been notified");
}
else {
$errors = file_get_contents($outfname);
SPITAJAX_ERROR(1, $errors);
}
}
else {
$script = file_get_contents($outfname);
SPITAJAX_RESPONSE(array("script" => $script));
}
unlink($outfname);
}
# Local Variables:
# mode:php
# End:
......
......@@ -428,23 +428,6 @@ if (! isset($create)) {
"this classic emulab experiment");
}
$defaults["profile_pid"] = $experiment->pid();
#
# Blech. We want to add an NS statement that sets the description
# since it is not metadata in a profile, it is part of the source.
#
$script = "";
$lines = preg_split("/\n/", $experiment->NSFile());
foreach ($lines as $line) {
$script .= "$line\n";
if (preg_match("/^set\s+(.*)\s+\[new Simulator/",
$line, $matches)) {
$script .= "\$" . $matches[1] . " description \"" .
CleanString($experiment->description()) . "\"\n";
}
}
$defaults["profile_script"] = $script;
}
}
SPITFORM($defaults, $errors);
......@@ -719,7 +702,7 @@ if ($profile) {
$uuid = $profile->uuid();
}
else {
header("Location: $APTBASE/myprofiles.php");
header("Location: $APTBASE/user-dashboard.php#profiles");
}
if ($action == "edit") {
$_SESSION["notifyupdate"] = 1;
......
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