Commit 980aa180 authored by Leigh B Stoller's avatar Leigh B Stoller

Add prototype EC2 image import plumbing.

To create a new descriptor that will be an import from EC2 (and thus
run under XEN), add ?ec2=1 to newimage_ez.php3. Eventually will link
it in someplace. The form will create a XEN based VM, but instead of
node to snapshot from, provide user@host for the EC2 instance.

On the image snapshot page, instead of node use user@host for the EC2
instance.

The backend script (create_image) will call over to ops and invoke
Srikanth's code. I have called that script ec2import-image.pl. See
create_image for how arguments are passed to the script.
parent 03febd79
...@@ -1883,6 +1883,7 @@ CREATE TABLE `images` ( ...@@ -1883,6 +1883,7 @@ CREATE TABLE `images` (
`locker_pid` int(11) default '0', `locker_pid` int(11) default '0',
`metadata_url` tinytext, `metadata_url` tinytext,
`imagefile_url` tinytext, `imagefile_url` tinytext,
`logfileid` varchar(40) default NULL,
PRIMARY KEY (`imageid`), PRIMARY KEY (`imageid`),
UNIQUE KEY `pid` (`pid`,`imagename`), UNIQUE KEY `pid` (`pid`,`imagename`),
KEY `gid` (`gid`), KEY `gid` (`gid`),
......
#
# Add logfiles to image.
#
use strict;
use libdb;
my $impotent = 0;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBSlotExists("images", "logfileid")) {
DBQueryFatal("alter table images add ".
" `logfileid` varchar(40) default NULL");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
...@@ -60,7 +60,7 @@ WEB_BIN_SCRIPTS = webcreate_image websetdest weblinkmon_ctl webspewevents \ ...@@ -60,7 +60,7 @@ WEB_BIN_SCRIPTS = webcreate_image websetdest weblinkmon_ctl webspewevents \
LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \ LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \
$(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
CTRLSBIN_SCRIPTS= opsdb_control.proxy daemon_wrapper CTRLSBIN_SCRIPTS= opsdb_control.proxy daemon_wrapper ec2import.proxy
# These scripts installed setuid, with sudo. # These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS = create_image SETUID_BIN_SCRIPTS = create_image
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# #
# }}} # }}}
# #
use strict;
use English; use English;
use Getopt::Std; use Getopt::Std;
use POSIX qw(setsid :sys_wait_h); use POSIX qw(setsid :sys_wait_h);
...@@ -124,6 +125,7 @@ sub run_with_ssh($$); ...@@ -124,6 +125,7 @@ sub run_with_ssh($$);
my $nodereboot = "$TB/bin/node_reboot"; my $nodereboot = "$TB/bin/node_reboot";
my $createimage = "/usr/local/bin/create-image"; my $createimage = "/usr/local/bin/create-image";
my $reboot_prep = "@CLIENT_BINDIR@/reboot_prepare"; my $reboot_prep = "@CLIENT_BINDIR@/reboot_prepare";
my $EC2SNAP = "$TB/sbin/ec2import.proxy";
my $friskiller = "$TB/sbin/frisbeehelper"; my $friskiller = "$TB/sbin/frisbeehelper";
my $osselect = "$TB/bin/os_select"; my $osselect = "$TB/bin/os_select";
my $checkquota = "$TB/sbin/checkquota"; my $checkquota = "$TB/sbin/checkquota";
...@@ -144,8 +146,12 @@ my $oldlogfile; ...@@ -144,8 +146,12 @@ my $oldlogfile;
my $needcleanup = 0; my $needcleanup = 0;
my $needunlock = 0; my $needunlock = 0;
my $isvirtnode = 0; my $isvirtnode = 0;
my $isec2node = 0;
my $onsharednode= 0; my $onsharednode= 0;
my $didbackup = 0; my $didbackup = 0;
my $node_id;
my $node;
my ($experiment,$pid);
# #
# Parse command arguments. Once we return from getopts, all that should be # Parse command arguments. Once we return from getopts, all that should be
...@@ -186,7 +192,7 @@ if (@ARGV != 2) { ...@@ -186,7 +192,7 @@ if (@ARGV != 2) {
} }
my $imagename = $ARGV[0]; my $imagename = $ARGV[0];
my $node_id = $ARGV[1]; my $target = $ARGV[1];
# #
# There is no reason to run as root unless we are taking a snapshot # There is no reason to run as root unless we are taking a snapshot
...@@ -198,14 +204,6 @@ $EUID = $UID; ...@@ -198,14 +204,6 @@ $EUID = $UID;
# #
# Untaint the arguments. # Untaint the arguments.
# #
if ($node_id =~ /^([-\w]+)$/) {
$node_id = $1;
}
else {
die("*** $0:\n".
" Bad data in $node_id\n");
}
if ($imagename =~ /^([-\w\.\+]+)$/) { if ($imagename =~ /^([-\w\.\+]+)$/) {
$imagename = $1; $imagename = $1;
} }
...@@ -257,25 +255,6 @@ my $user_uid = $this_user->uid(); ...@@ -257,25 +255,6 @@ my $user_uid = $this_user->uid();
my $user_name = $this_user->name(); my $user_name = $this_user->name();
my $user_email = $this_user->email(); my $user_email = $this_user->email();
# Check node and permission
my $node = Node->Lookup($node_id);
if (!defined($node)) {
die("*** $0:\n".
" Invalid node name $node_id!\n");
}
$isvirtnode = $node->isvirtnode();
$onsharednode = $node->sharing_mode()
if ($isvirtnode);
if ($UID && ! $this_user->IsAdmin()) {
$mereuser = 1;
if (! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
die("*** $0:\n".
" You do not have permission to create an image from $node\n");
}
}
# #
# Before doing anything else, check for overquota ... lets not waste # Before doing anything else, check for overquota ... lets not waste
# our time. Make sure user sees the error by exiting with 1. # our time. Make sure user sees the error by exiting with 1.
...@@ -285,35 +264,8 @@ if (system("$checkquota $user_uid") != 0) { ...@@ -285,35 +264,8 @@ if (system("$checkquota $user_uid") != 0) {
" You are over your disk quota on $CONTROL; ". " You are over your disk quota on $CONTROL; ".
"please login there and cleanup!\n"); "please login there and cleanup!\n");
} }
if ($UID && ! $this_user->IsAdmin()) {
# $mereuser = 1;
# 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
# directory that is going to be NFS mounted by default.
#
my $experiment = $node->Reservation();
if (!defined($experiment)) {
die("*** $0:\n".
" Could not map $node to its experiment object!\n");
}
my $pid = $experiment->pid();
my $eid = $experiment->eid();
#
# To avoid blowing a cavernous hole ("allow all TCP ports to boss")
# in the per-experiment firewall, we don't use the frisbee uploader if
# the node is firewalled.
#
if ($usefup && $experiment->IsFirewalled()) {
print "*** WARNING: $node_id is firewalled, not using Frisbee uploader\n";
$usefup = 0;
if ($NONFS) {
$usenfs = 0;
$usessh = 1;
} else {
$usenfs = 1;
$usessh = 0;
}
} }
# #
...@@ -334,6 +286,76 @@ if ($mereuser && ...@@ -334,6 +286,76 @@ if ($mereuser &&
" You do not have permission to use imageid $imageid!\n"); " You do not have permission to use imageid $imageid!\n");
} }
#
# Is it a local node or a remote EC2 node (need to generalize).
#
if ($target =~ /^.*@.*$/) {
if ($target =~ /^([-\w\@\+\.]+)$/) {
$target = $1;
}
else {
die("*** $0:\n".
" Bad data in $target\n");
}
$isec2node = 1;
$usefup = $usessh = 0;
$pid = $image->pid();
}
else {
if ($target =~ /^([-\w]+)$/) {
$node_id = $1;
}
else {
die("*** $0:\n".
" Bad data in $target\n");
}
# Check node and permission
$node = Node->Lookup($node_id);
if (!defined($node)) {
die("*** $0:\n".
" Invalid node name $node_id!\n");
}
$isvirtnode = $node->isvirtnode();
$onsharednode = $node->sharing_mode()
if ($isvirtnode);
if (! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
die("*** $0:\n".
" You do not have permission to create an image from $node\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
# directory that is going to be NFS mounted by default.
#
$experiment = $node->Reservation();
if (!defined($experiment)) {
die("*** $0:\n".
" Could not map $node to its experiment object!\n");
}
$pid = $experiment->pid();
#
# To avoid blowing a cavernous hole ("allow all TCP ports to boss")
# in the per-experiment firewall, we don't use the frisbee uploader if
# the node is firewalled.
#
if ($usefup && $experiment->IsFirewalled()) {
print "*** WARNING: $node_id is firewalled, not using Frisbee uploader\n";
$usefup = 0;
if ($NONFS) {
$usenfs = 0;
$usessh = 1;
} else {
$usenfs = 1;
$usessh = 0;
}
}
}
# #
# Make sure that the directory exists and is writeable for the user. # Make sure that the directory exists and is writeable for the user.
# We test this by creating the file. Its going to get wiped anyway. # We test this by creating the file. Its going to get wiped anyway.
...@@ -436,7 +458,7 @@ open(FILE, "> $tmp") or ...@@ -436,7 +458,7 @@ open(FILE, "> $tmp") or
close(FILE) or close(FILE) or
fatal("Could not truncate $tmp: $!"); fatal("Could not truncate $tmp: $!");
if (!$isvirtnode) { if (! ($isvirtnode || $isec2node)) {
# #
# Get the disktype for this node # Get the disktype for this node
# #
...@@ -475,7 +497,10 @@ if ($usefup) { ...@@ -475,7 +497,10 @@ if ($usefup) {
$command .= " -S $BOSSIP -F $id"; $command .= " -S $BOSSIP -F $id";
} }
if ($isvirtnode) { if ($isec2node) {
$command = "$EC2SNAP ";
}
elsif ($isvirtnode) {
$command .= " $node_id"; $command .= " $node_id";
} }
else { else {
...@@ -498,21 +523,22 @@ if ($usefup || $usessh) { ...@@ -498,21 +523,22 @@ if ($usefup || $usessh) {
# Go to the background since this is going to take a while. # Go to the background since this is going to take a while.
# #
if (! $foreground) { if (! $foreground) {
$logfile = Logfile->Create($experiment->gid_idx()); $logfile = Logfile->Create((defined($experiment) ?
$experiment->gid_idx() : $image->gid_idx()));
fatal("Could not create a logfile") fatal("Could not create a logfile")
if (!defined($logfile)); if (!defined($logfile));
# Mark it open since we are going to start using it right away. # Mark it open since we are going to start using it right away.
$logfile->Open(); $logfile->Open();
# Logfile becomes the current spew, but save off the old spew. # Logfile becomes the current spew.
$experiment->SetLogFile($logfile, \$oldlogfile); $image->SetLogFile($logfile);
if (my $childpid = TBBackGround($logfile->filename())) { if (my $childpid = TBBackGround($logfile->filename())) {
# #
# Parent exits normally, except if in waitmode. # Parent exits normally, except if in waitmode.
# #
if (!$waitmode) { if (!$waitmode) {
print("Your image from $node_id is being created\n". print("Your image from $target is being created\n".
"You will be notified via email when the image has been\n". "You will be notified via email when the image has been\n".
"completed, and you can load the image on another node.\n"); "completed, and you can load the image on another node.\n");
exit(0); exit(0);
...@@ -558,7 +584,8 @@ if (! $foreground) { ...@@ -558,7 +584,8 @@ if (! $foreground) {
$needcleanup = 1; $needcleanup = 1;
# Clear the bootlog; see below. # Clear the bootlog; see below.
$node->ClearBootLog(); $node->ClearBootLog()
if (defined($node));
# check_progress state # check_progress state
my $runticks = 0; my $runticks = 0;
...@@ -570,9 +597,26 @@ my $lastsize = 0; ...@@ -570,9 +597,26 @@ my $lastsize = 0;
my $result; my $result;
# #
# We can skip a lot of the stuff below for virtnodes. # We can skip a lot of the stuff below for virtnodes and ec2 nodes.
# #
if ($isvirtnode) { if ($isec2node) {
my $safe_target = User::escapeshellarg($target);
my $cmd = "$TB/bin/sshtb -host $CONTROL $EC2SNAP -u $user_uid ".
"$safe_target $pid $user_uid $imageid $filename";
print STDERR "About to: '$cmd'\n" if (1 || $debug);
my $SAVEUID = $UID;
$EUID = $UID = 0;
system($cmd);
fatal("'$cmd' failed")
if ($?);
$EUID = $UID = $SAVEUID;
goto ec2done;
}
elsif ($isvirtnode) {
# #
# XEN creates a problem; the physical host cannot actually # XEN creates a problem; the physical host cannot actually
# execute a command inside the guest, but we need to run # execute a command inside the guest, but we need to run
...@@ -724,6 +768,7 @@ if ($result != 0) { ...@@ -724,6 +768,7 @@ if ($result != 0) {
fatal("FAILED: Returned error code $result generating image ... \n"); fatal("FAILED: Returned error code $result generating image ... \n");
} }
ec2done:
# #
# Everything worked, create the hash signature file. # Everything worked, create the hash signature file.
# #
...@@ -786,8 +831,8 @@ if (defined($bootlog)) { ...@@ -786,8 +831,8 @@ if (defined($bootlog)) {
} }
SENDMAIL("$user_name <$user_email>", SENDMAIL("$user_name <$user_email>",
"Image Creation on $node_id Completed: $pid/$imagename", "Image Creation on $target Completed: $pid/$imagename",
"Image creation on $node_id has completed. As you requested, the\n". "Image creation on $target has completed. As you requested, the\n".
"image has been written to $filename.\n". "image has been written to $filename.\n".
"You may now os_load this image on other nodes in your experiment.\n". "You may now os_load this image on other nodes in your experiment.\n".
"$swmsg", "$swmsg",
...@@ -798,10 +843,7 @@ SENDMAIL("$user_name <$user_email>", ...@@ -798,10 +843,7 @@ SENDMAIL("$user_name <$user_email>",
if (defined($logfile)) { if (defined($logfile)) {
# Close up the log file so the webpage stops. # Close up the log file so the webpage stops.
$logfile->Close(); $logfile->Close();
# And restore the original logfile as current spew. $image->ClearLogFile();
$experiment->SetLogFile($oldlogfile)
if (defined($oldlogfile));
$logfile->Delete(1);
} }
$image->Unlock(); $image->Unlock();
exit 0; exit 0;
...@@ -810,7 +852,7 @@ sub cleanup () ...@@ -810,7 +852,7 @@ sub cleanup ()
{ {
$needcleanup = 0; $needcleanup = 0;
if ($isvirtnode) { if ($isvirtnode || $isec2node) {
# #
# Nothing to do; the clientside script rebooted the container. # Nothing to do; the clientside script rebooted the container.
# #
...@@ -858,7 +900,7 @@ sub fatal($) ...@@ -858,7 +900,7 @@ sub fatal($)
# Send a message to the testbed list. # Send a message to the testbed list.
# #
SENDMAIL("$user_name <$user_email>", SENDMAIL("$user_name <$user_email>",
"Image Creation Failure on $node_id: $pid/$imagename", "Image Creation Failure on $target: $pid/$imagename",
$mesg, $mesg,
"$user_name <$user_email>", "$user_name <$user_email>",
"Cc: $TBOPS", "Cc: $TBOPS",
...@@ -867,12 +909,7 @@ sub fatal($) ...@@ -867,12 +909,7 @@ sub fatal($)
if (defined($logfile)) { if (defined($logfile)) {
# Close up the log file so the webpage stops. # Close up the log file so the webpage stops.
$logfile->Close(); $logfile->Close();
# And restore the original logfile as current spew. $image->ClearLogFile();
$experiment->SetLogFile($oldlogfile)
if (defined($oldlogfile));
$logfile->Delete();
# This was mailed so no longer needed.
unlink("$logfile->filename()");
} }
$image->Unlock() $image->Unlock()
if ($needunlock); if ($needunlock);
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2013 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 BSD::Resource;
use POSIX qw(:signal_h);
#
# Wrapper for the EC2 image import script.
#
sub usage()
{
print STDOUT "Usage: ec2import.proxy -u user ...\n";
exit(-1);
}
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $EC2SNAP = "$TB/sbin/ec2import-image.pl";
my $errors = 0;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/usr/bin:/sbin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libtestbed;
#
# First option has to be the -u option, the user to run this script as.
#
if ($UID != 0 || $EUID != 0) {
die("*** $0:\n".
" Must be root to run this script!\n");
}
if ($ARGV[0] ne "-u") {
usage();
}
my $user = $ARGV[1];
shift(@ARGV);
shift(@ARGV);
my (undef,undef,$unix_uid) = getpwnam($user) or
die("*** $0:\n".
" No such user $user\n");
#
# Need the entire group list for the user, cause of subgroups, and
# cause thats the correct thing to do. Too bad perl does not have a
# getgrouplist function like the C library.
#
my $glist = `/usr/bin/id -G $user`;
if ($glist =~ /^([\d ]*)$/) {
$glist = $1;
}
else {
die("*** $0:\n".
" Unexpected results from 'id -G $user': $glist\n");
}
# Need to split off the first group and create a proper list for $GUID.
my @gglist = split(" ", $glist);
my $unix_gid = $gglist[0];
$glist = "$unix_gid $glist";
# Flip to user and never go back!
$GID = $unix_gid;
$EGID = $glist;
$EUID = $UID = $unix_uid;
$ENV{'USER'} = $user;
$ENV{'LOGNAME'} = $user;
#
# Invoke script with the rest of the args.
#
system("$EC2SNAP @ARGV");
exit($? >> 8);
...@@ -126,7 +126,7 @@ class Image ...@@ -126,7 +126,7 @@ class Image
$this->imageid = NULL; $this->imageid = NULL;
return -1; return -1;
} }
$this->imageid = mysql_fetch_array($query_result); $this->image = mysql_fetch_array($query_result);
# #
# Reload the type info. # Reload the type info.
...@@ -363,6 +363,7 @@ class Image ...@@ -363,6 +363,7 @@ class Image
function hash() { return $this->field("hash"); } function hash() { return $this->field("hash"); }
function metadata_url() { return $this->field("metadata_url"); } function metadata_url() { return $this->field("metadata_url"); }
function imagefile_url() { return $this->field("imagefile_url"); } function imagefile_url() { return $this->field("imagefile_url"); }
function logfileid() { return $this->field("logfileid"); }
# Return the DB data. # Return the DB data.
function DBData() { return $this->image; } function DBData() { return $this->image; }
...@@ -840,4 +841,12 @@ class Image ...@@ -840,4 +841,12 @@ class Image
} }
return 0; return 0;
} }
function GetLogfile() {
$this->Refresh();
if ($this->logfileid())
return Logfile::Lookup($this->logfileid());
return null;
}
} }
<?php <?php
# #
# Copyright (c) 2000-2012 University of Utah and the Flux Group. # Copyright (c) 2000-2013 University of Utah and the Flux Group.
# #
# {{{EMULAB-LICENSE # {{{EMULAB-LICENSE
# #
...@@ -39,7 +39,7 @@ include("showlogfile_sup.php3"); ...@@ -39,7 +39,7 @@ include("showlogfile_sup.php3");
# Verify page arguments. # Verify page arguments.
# #
$reqargs = RequiredPageArguments("image", PAGEARG_IMAGE); $reqargs = RequiredPageArguments("image", PAGEARG_IMAGE);
$optargs = OptionalPageArguments("node", PAGEARG_NODE,