Commit b74e3f9b authored by Mike Hibler's avatar Mike Hibler

Merge branch 'master' into imageversion

Conflicts:
	tbsetup/ptopgen.in
parents f2c59912 696fb08d
......@@ -48,3 +48,5 @@ replace into node_types set
replace into node_type_attributes set
type='external-switch',attrkey='forwarding_protocols',
attrvalue='ethernet',attrtype='string';
replace into node_attributes values ('procurve2', 'does_openflow', 'yes');
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2012 University of Utah and the Flux Group.
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -50,18 +50,23 @@ delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# No configure vars.
#
my $sudo;
my $sudo = "";
my $zipper = "/usr/local/bin/imagezip";
my $uploader = "/usr/local/bin/frisupload";
my $slice = "";
my $device;
my $filename;
for my $path (qw#/usr/local/bin /usr/bin#) {
#
# If we are running as a user, then we will need sudo
#
if ($EUID != 0) {
for my $path (qw#/usr/local/bin /usr/bin#) {
if (-e "$path/sudo") {
$sudo = "$path/sudo";
last;
$sudo = "$path/sudo";
last;
}
}
}
# Frisbee master server params
......@@ -168,8 +173,11 @@ if ($^O eq 'linux') {
#
# If imageid is defined, we use the frisbee uploader.
#
my $cmd = "$zipper $slice $device $filename";
my $cmd = "$sudo $zipper $slice $device $filename";
if (defined($imageid)) {
# use basic shell sleezy trick to capture exit status from imagezip
$cmd = "( $cmd || echo \$? > /tmp/imagezip.stat )";
$cmd .= " | $uploader -S $iserver -F $imageid -";
}
......@@ -178,9 +186,20 @@ if (defined($imageid)) {
# with proper trust should be able to zip up a disk. sudo will fail
# if the user is not in the proper group.
#
if (system("$sudo $cmd")) {
if (system("$cmd") || -e "/tmp/imagezip.stat") {
my $stat = sprintf("0x%04x", $?);
my $izstat = 0;
if (-e "/tmp/imagezip.stat") {
$izstat = `cat /tmp/imagezip.stat`;
chomp($izstat);
}
$izstat = sprintf("0x%04x", $izstat);
print STDERR "*** Failed to create image!\n";
print STDERR " command: '$sudo $cmd'\n";
print STDERR " command: '$cmd'\n";
print STDERR " status: $stat\n";
print STDERR " izstatus: $izstat\n"
if ($izstat);
exit 1;
}
......
......@@ -513,6 +513,7 @@ sub SliverStatus()
while ( my ($pgurn, $pgrstat) = each(%$details) ) {
my $child = {
'geni_urn' => $pgurn,
'geni_client_id' => $pgrstat->{'client_id'},
# XXX Need to massage status to one of the AM status values
'geni_status' => $pgrstat->{'status'},
'geni_error' => $pgrstat->{'error'},
......@@ -735,6 +736,7 @@ sub Describe
foreach my $sliver (@slivers) {
$sliver_blob = {
'geni_sliver_urn' => $sliver->sliver_urn(),
'geni_client_id' => $sliver->nickname(),
'geni_expires' => $slice->ExpirationUTC(),
'geni_allocation_status' => "geni_provisioned",
'geni_operational_status' => GetOpState($sliver),
......
......@@ -854,6 +854,41 @@ sub GetManifest($$)
return $xml;
}
#
# Store a manifest back.
#
sub UpdateManifest($$)
{
my ($self, $manifest) = @_;
my $slice_uuid = $self->slice_uuid();
my $manifest_string = DBQuoteSpecial(GeniXML::Serialize($manifest));
#
# We need the current idx.
#
my $query_result =
DBQueryWarn("select idx from geni_manifests ".
"where slice_uuid='$slice_uuid'");
return -1
if (!$query_result);
if ($query_result->numrows) {
my ($idx) = $query_result->fetchrow_array();
DBQueryWarn("update geni_manifests set ".
" manifest=$manifest_string " .
"where idx=$idx")
or return -1;
}
else {
DBQueryWarn("replace into geni_manifests set ".
" manifest=$manifest_string, " .
" idx=NULL, slice_uuid='$slice_uuid', created=now()")
or return -1;
}
return 0;
}
#
# Process the manifest. Just hand off to the slivers.
#
......
......@@ -1156,6 +1156,7 @@ sub SliverStatus($)
my $resource_id = $sliver->resource_id();
my $state = $sliver->state();
my $status = $sliver->status();
my $nickname = $sliver->nickname() || "";
my $error = "";
# New is the same as stopped. Separate state is handy.
......@@ -1167,6 +1168,7 @@ sub SliverStatus($)
}
$blob->{'details'}->{$sliver_urn} = {
"component_urn" => $resource_id,
"client_id" => $nickname,
"state" => $state,
"status" => $status,
"error" => $error,
......@@ -1746,6 +1748,25 @@ sub BindToSlice($)
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_ERROR());
}
#
# Update the manifest.
#
my $manifest = $aggregate->GetManifest(0);
foreach my $ref (GeniXML::FindNodes("n:node",
$manifest)->get_nodelist()) {
my $sliver_id = GeniXML::GetSliverId($ref);
my $sliver = GeniSliver->Lookup($sliver_id);
next
if (!defined($sliver));
my $node_manifest = $sliver->AnnotateManifest();
if (defined($node_manifest)) {
# And store into the new manifest.
GeniXML::ReplaceNode($ref, $node_manifest);
}
}
$aggregate->UpdateManifest($manifest);
}
$slice->UnLock();
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
......@@ -2257,7 +2278,8 @@ sub Credential2SliceAggregate($)
}
}
}
my (undef, $certtype, undef) = GeniHRN::Parse($target_cert->urn());
#
# Make sure the certificate has not changed. If it has, we have to
# check the UUID, since we need to support regen of the
......@@ -2265,7 +2287,8 @@ sub Credential2SliceAggregate($)
# a straight comparison will fail. So look to see if the UUID is
# the same. If so, we store the new certificate.
#
if (defined($slice) && !$target_cert->SameCert($slice)) {
if (defined($slice) && $certtype eq "slice" &&
!$target_cert->SameCert($slice)) {
if ($target_cert->uuid() eq $slice->uuid()) {
print STDERR "Updating certificate for $slice to $target_cert\n";
$slice->GetCertificate()->Delete();
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2013 University of Utah and the Flux Group.
# Copyright (c) 2008-2014 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -109,7 +109,7 @@ my $query_result;
if ($type eq "user") {
$query_result =
GeniDB::DBQueryFatal("select uuid from geni_slices ".
"where creator_token='$token'");
"where creator_urn='$token'");
}
elsif ($type eq "sliver") {
$query_result =
......
......@@ -212,6 +212,7 @@ use Node;
use NodeType;
use Lan;
use BlockstoreType;
use User;
tblog_stop_capture('stdout');
......@@ -346,6 +347,12 @@ if (defined($pid) && ! defined($options{"Z"})) {
}
}
# Figure out who is running this script, to be used in finding OSes
# with per-user permissions. We assume that the user we want
# to look up is the one running this script (and hence the mapper
# wrapper that calls it).
my $this_user = User->ThisUser()->uid() || "";
$fake_inet_switch = "internet";
$fake_inet_iface = "(null)";
$fake_air_switch = "airswitch";
......@@ -674,30 +681,33 @@ if (defined($pid)) {
#
# Read the table of which image types are supported on which hardware - we
# limit this to global images and ones that match the PID (if given) We do this
# limiting for two reasons:
# limit this to global images and ones that match the PID (if given) or user.
# We do this limiting for two reasons:
# 1) To avoid an explosion in the number of features for nodes
# 2) To avoid information leaks, allowing projects to see each other's images
#
my $osidquery = "select distinct o.osid,oi.type,o.osname,o.pid,ov.OS,".
"ov.version,ov.description,ov.protogeni_export,ov.osfeatures ".
" from os_info as o " .
" from os_info as o ".
"left join os_info_versions as ov on ".
" ov.osid=o.osid and ov.vers=o.version ".
"left join osidtoimageid as oi on o.osid = oi.osid " .
"left join osidtoimageid as oi on o.osid = oi.osid ".
"left join images as i on oi.imageid = i.imageid ".
"left join image_versions as iv on ".
" iv.imageid=i.imageid and iv.version=i.version ";
if ($pid) {
$osidquery .= "left join image_permissions as p1 on ".
"p1.imageid=i.imageid and p1.permission_type='group' ".
"left join groups as g on p1.permission_idx=g.gid_idx ";
}
$osidquery .= "where iv.global = 1 ";
" iv.imageid=i.imageid and iv.version=i.version ".
"left join image_permissions as p1 on p1.imageid=i.imageid ".
"left join groups as g on ".
" p1.permission_type='group' and p1.permission_idx=g.gid_idx ".
"left join users as u on ".
" p1.permission_type='user' and p1.permission_idx=u.uid_idx ".
"where iv.global = 1 ";
if ($pid) {
$osidquery .= " or i.pid='$pid' ".
" or (g.pid is not null and g.pid='$pid')";
}
if ($this_user) {
$osidquery .= " or (u.uid is not null and u.uid='$this_user')";
}
my $defaultosidquery =
"select distinct o.osid,t.type,o.osname,o.pid,ov.OS,".
......@@ -722,13 +732,29 @@ my $subosidquery =
"left join images as i2 on oi2.imageid = i2.imageid ".
"left join image_versions as iv2 on ".
" iv2.imageid=i2.imageid and iv2.version=i2.version ".
"left join image_permissions as ip1 on ip1.imageid=i1.imageid ".
"left join image_permissions as ip2 on ip2.imageid=i2.imageid ".
"left join groups as g1 on ".
" ip1.permission_type='group' and ip1.permission_idx=g1.gid_idx ".
"left join groups as g2 on ".
" ip2.permission_type='group' and ip2.permission_idx=g2.gid_idx ".
"left join users as u1 on ".
" ip1.permission_type='user' and ip1.permission_idx=u1.uid_idx ".
"left join users as u2 on ".
" ip2.permission_type='user' and ip2.permission_idx=u2.uid_idx ".
"where (i1.imageid is null or iv1.global = 1";
if ($pid) {
$subosidquery .= " or i1.pid='$pid'";
$subosidquery .= " or i1.pid='$pid' or g1.pid='$pid'";
}
if ($this_user) {
$subosidquery .= " or u1.uid='$this_user'"
}
$subosidquery .= ") and (iv2.global = 1";
if ($pid) {
$subosidquery .= " or i2.pid='$pid'";
$subosidquery .= " or i2.pid='$pid' or g2.pid='$pid'";
}
if ($this_user) {
$subosidquery .= " or u2.uid='$this_user'"
}
$subosidquery .= ")";
......
......@@ -853,6 +853,9 @@ sub getDeviceNames(@) {
my @ports = @_;
my %devices = ();
foreach my $port (@ports) {
if (!defined($port)) {
die("getDeviceNames: undefined port");
}
my $device = $port->switch_node_id();
$devices{$device} = 1;
......
......@@ -5373,8 +5373,19 @@ COMMAND_PROTOTYPE(doloadinfo)
* For virtual node reloading, it is convenient to tell it what
* partition to boot, for the case that it is a whole disk image
* and the client can not derive which partition to boot from.
*
* XXX 7/17/2014: retroactively add a version number check.
* "BOOTPART=" confuses the old rc.frisbee argument parsing
* which looks for "PART=" with the RE ".*PART=" which will
* match BOOTPART= instead. Thus an old script loading a
* whole disk image (PART=0) winds up trying to load it in
* partition 2 (BOOTPART=2). So we can pick one of two
* versions, the one in effect when rc.frisbee changed its
* argument parsing (v30, circa 6/28/2010) or the version
* in effect when BOOTPART was added (v36, circa 6/13/2013).
* We choose the latter.
*/
if (row[12] && row[12][0]) {
if (row[12] && row[12][0] && vers >= 36) {
bufp += OUTPUT(bufp, ebufp - bufp,
" BOOTPART=%s", row[12]);
}
......
......@@ -463,6 +463,11 @@ else {
" You do not have permission to create an image from $node\n");
}
if ($node->IsTainted()) {
die("*** $0:\n".
" $node is tainted - image creation denied.\n");
}
#
# We need the project id for test below. The target directory for the
# output file has to be the node project directory, since that is the
......
......@@ -164,11 +164,18 @@ else {
if (!$node->IsReserved()) {
die("*** $0:\n".
" Node $n is not reserved; reserve it first!\n");
}
if ($UID && !$this_user->IsAdmin() &&
! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
die("*** $0:\n".
" You are not allowed to put $node into admin mode!\n");
}
if ($UID && !$this_user->IsAdmin()) {
if (! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
die("*** $0:\n".
" You are not allowed to put $node into admin mode!\n");
}
if ($node->IsTainted("useronly") ||
$node->IsTainted("blackbox")) {
die("*** $0:\n".
" $node is running a restricted image. Admin mode ".
"not allowed!\n");
}
}
push(@nodes, $node);
}
......
......@@ -106,5 +106,86 @@ class Instance
$this->instance = mysql_fetch_array($query_result);
return 0;
}
#
# Class function to create a new Instance
#
function Instantiate($creator, $options, $args, &$errors) {
global $suexec_output, $suexec_output_array;
# So we can look up the slice after the backend creates it.
$uuid = NewUUID();
#
# Generate a temporary file and write in the XML goo.
#
$xmlname = tempnam("/tmp", "quickvm");
if (! $xmlname) {
TBERROR("Could not create temporary filename", 0);
$errors["error"] = "Transient error(1); please try again later.";
return null;
}
elseif (! ($fp = fopen($xmlname, "w"))) {
TBERROR("Could not open temp file $xmlname", 0);
$errors["error"] = "Transient error(2); please try again later.";
return null;
}
else {
fwrite($fp, "<quickvm>\n");
foreach ($args as $name => $value) {
fwrite($fp, "<attribute name=\"$name\">");
fwrite($fp, " <value>" . htmlspecialchars($value) .
"</value>");
fwrite($fp, "</attribute>\n");
}
fwrite($fp, "</quickvm>\n");
fclose($fp);
chmod($xmlname, 0666);
}
#
# This option is used to tell the backend that it is okay to look
# in the emulab users table.
#
$options .= ($creator ? " -l" : "");
if (isset($_SERVER['REMOTE_ADDR'])) {
putenv("REMOTE_ADDR=" . $_SERVER['REMOTE_ADDR']);
}
$retval = SUEXEC("nobody", "nobody",
"webquickvm $options -u $uuid $xmlname",
SUEXEC_ACTION_CONTINUE);
if ($retval != 0) {
if ($retval < 0) {
$errors["error"] =
"Transient error(3); please try again later.";
}
else {
if (count($suexec_output_array)) {
$line = $suexec_output_array[0];
$errors["error"] = $line;
}
else {
$errors["error"] =
"Transient error(4); please try again later.";
}
}
return null;
}
unlink($xmlname);
$instance = Instance::Lookup($uuid);
if (!$instance) {
$errors["error"] = "Transient error(5); please try again later.";
return null;
}
if (!$creator) {
$creator = GeniUser::Lookup("sa", $instance->creator_uuid());
}
if (!$creator) {
$errors["error"] = "Transient error(6); please try again later.";
return null;
}
return array($instance, $creator);
}
}
?>
......@@ -137,7 +137,7 @@ function SPITFORM($formfields, $newuser, $errors)
if ($errors) {
while (list ($key, $val) = each ($errors)) {
# Skip internal error, we want the html in those errors
# ands we know it is safe.
# and we know it is safe.
if ($key == "error") {
continue;
}
......@@ -536,99 +536,21 @@ if (!$this_user &&
}
}
#
# This is so we can look up the slice after the backend creates it.
# We tell the backend what uuid to use.
#
$quickvm_uuid = NewUUID();
# Admins can change aggregate.
$options = ($aggregate_urn != "" ? " -a '$aggregate_urn'" : "");
#
# Generate a temporary file and write in the XML goo.
# Invoke the backend.
#
$xmlname = tempnam("/tmp", "quickvm");
if (! $xmlname) {
TBERROR("Could not create temporary filename", 0);
$errors["internal"] = "Transient error(1); please try again later.";
}
elseif (! ($fp = fopen($xmlname, "w"))) {
TBERROR("Could not open temp file $xmlname", 0);
$errors["internal"] = "Transient error(2); please try again later.";
}
else {
fwrite($fp, "<quickvm>\n");
foreach ($args as $name => $value) {
fwrite($fp, "<attribute name=\"$name\">");
fwrite($fp, " <value>" . htmlspecialchars($value) . "</value>");
fwrite($fp, "</attribute>\n");
}
fwrite($fp, "</quickvm>\n");
fclose($fp);
chmod($xmlname, 0666);
}
if (count($errors)) {
SPITFORM($formfields, false, $errors);
SPITFOOTER();
return;
}
#
# Invoke the backend. This will create the user and the slice record
# in the SA database, and then fork off in the background. If the
# first part works, we can return to the user and use some nifty ajax
# and javascript to watch for progress. We use a cookie that holds
# the slice uuid so that the JS code can ask about it.
#
# This option is used to tell the backend that it is okay to look
# in the emulab users table.
#
if (isset($_SERVER['REMOTE_ADDR'])) {
putenv("REMOTE_ADDR=" . $_SERVER['REMOTE_ADDR']);
}
$opt = ($this_user ? "-l" : "");
$opt .= ($aggregate_urn != "" ? " -a '$aggregate_urn'" : "");
$retval = SUEXEC("nobody", "nobody",
"webquickvm $opt -u $quickvm_uuid $xmlname",
SUEXEC_ACTION_CONTINUE);
if ($retval != 0) {
if ($retval < 0) {
$errors["error"] = "Transient error(3); please try again later.";
}
else {
if (count($suexec_output_array)) {
$line = $suexec_output_array[0];
$errors["error"] = $line;
}
else {
$errors["error"] = "Transient error(4); please try again later.";
}
}
SPITFORM($formfields, false, $errors);
SPITFOOTER();
return;
}
unlink($xmlname);
list ($instance, $creator) =
Instance::Instantiate($this_user, $options, $args, $errors);
$instance = Instance::Lookup($quickvm_uuid);
if (!$instance) {
$errors["error"] = "Transient error(5); please try again later.";
SPITFORM($formfields, false, $errors);
SPITFOOTER();
return;
}
if ($this_user) {
$creator = $this_user;
}
else {
$creator = GeniUser::Lookup("sa", $instance->creator_uuid());
}
if (! $creator) {
$errors["error"] = "Transient error(6); please try again later.";
SPITFORM($formfields, false, $errors);
SPITFOOTER();
return;
}
#
# Remember the user and auth key so that we can verify.
#
......
......@@ -6,11 +6,13 @@ require(window.APT_OPTIONS.configObject,
'js/lib/text!template/showtopo-modal.html',
'js/lib/text!template/oops-modal.html',
'js/lib/text!template/rspectextview-modal.html',
'js/lib/text!template/guest-instantiate.html',
// jQuery modules
'filestyle','marked','jquery-ui','jquery-grid'],
function (_, sup, filesize, ShowImagingModal,
manageString, waitwaitString,
rendererString, showtopoString, oopsString, rspectextviewString)
rendererString, showtopoString, oopsString, rspectextviewString,
guestInstantiateString)
{
'use strict';
var editing = 0;
......@@ -24,6 +26,7 @@ function (_, sup, filesize, ShowImagingModal,
var showtopoTemplate = _.template(showtopoString);
var rspectextTemplate = _.template(rspectextviewString);
var oopsTemplate = _.template(oopsString);
var guestInstTemplate = _.template(guestInstantiateString);
function initialize()
{
......@@ -69,7 +72,9 @@ function (_, sup, filesize, ShowImagingModal,
$('#rspectext_div').html(rspectext_html);
var oops_html = oopsTemplate({});
$('#oops_div').html(oops_html);
var guest_html = guestInstTemplate({});
$('#guest_div').html(guest_html);
//
// Fix for filestyle problem; not a real class I guess, it
// runs at page load, and so the filestyle'd button in the
......@@ -201,6 +206,12 @@ function (_, sup, filesize, ShowImagingModal,
$('#renderer_modal_div').html(marked(text));
sup.ShowModal("#renderer_modal");
});
// Handler for guest instantiate submit button, which is in
// the modal.
$('#guest_instantiate_submit_button').click(function (event) {
event.preventDefault();
InstantiateAsGuest();
});
/*
* If we were given an rspec, suck the description and instructions
......@@ -501,6 +512,44 @@ function (_, sup, filesize, ShowImagingModal,
sup.maketopmap("#showtopo_nopicker", xml, null);
}
//
// Instantiate a profile as a guest User.
//
function InstantiateAsGuest()
{
var callback = function(json) {
sup.HideModal("#waitwait-modal");
console.info(json.value);
var message;
if (json.code) {
sup.SpitOops("oops", json.value);
return;
}
//
// Need to set the cookies we get back so that we can
// redirect to the status page.
//
document.cookie =
'quickvm_user=' + json.value.quickvm_user +
'; max-age=86400; path=/; secure';
document.cookie =
'quickvm_authkey=' + json.value.quickvm_authkey +
'; max-age=86400; path=/; secure';
var url = "status.php?uuid=" + json.value.quickvm_uuid;
window.location.replace(url);
}
sup.HideModal("#guest_instantiate_modal");
sup.ShowModal("#waitwait-modal");
var xmlthing = sup.CallServerMethod(ajaxurl,
"manage_profile",
"InstantiateAsGuest",
{"uuid" : uuid});
xmlthing.done(callback);
}
//
// Progress Modal
//
......@@ -540,12 +589,14 @@ function (_, sup, filesize, ShowImagingModal,
EnableButton("profile_delete_button");
EnableButton("profile_instantiate_button");
EnableButton("profile_submit_button");
EnableButton("guest_instantiate_button");
}
function DisableButtons()
{
DisableButton("profile_delete_button");
DisableButton("profile_instantiate_button");
DisableButton("profile_submit_button");
DisableButton("guest_instantiate_button");
}
function EnableButton(button)
{
......
......@@ -25,6 +25,7 @@ chdir("..");
include_once("webtask.php");
chdir("apt");
include_once("profile_defs.php");
include_once("instance_defs.php");
#
# Return clone status.
......@@ -79,6 +80,88 @@ function Do_CloneStatus()
SPITAJAX_RESPONSE($blob);
}
#
# Instantiate as Guest user. Simply a convenience, users could do
# this themselves.
#
# Note that this is going to log the user out. Big simplification,
# big headache otherwise.
#
function Do_GuestInstantiate()
{
global $this_user;
global $ajax_args;
$this_idx = $this_user->uid_idx();
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 ($this_idx != $profile->creator_idx() && !ISADMIN()) {
SPITAJAX_ERROR(1, "Not enough permission");
return;
}
#
# Need to form a guest id. Ideally, lets look for a guest user
# with the same email and use that.
#
$geniuser = GeniUser::LookupByEmail($this_user->email());
if ($geniuser) {
$guestid = $geniuser->uid();
$token = $geniuser->auth_token();
}