Commit 9e9ac6ee authored by Leigh Stoller's avatar Leigh Stoller

Checkpoint.

* Add a .htaccess file that does the rewrites, instead of in the httpd
  confile file. Added Rob's stuff for rewriting urls to hide the .php
  although not sure this is working correctly yet.

* Add simple MyExperiments page so that logged in users can find their
  way back to running profiles.

* Move the DB table holding the running experiment records from the
  geni-sa DB into the main Emulab DB. Lots of little changes for that.

* Change logout to plain link instead of ajax call. That was a silly
  thing to do.

* Bug fixes to ssh keys and shell login from the status page.
parent 4341445a
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2014 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/>.
#
# }}}
#
package APT_Instance;
use strict;
use Carp;
use Exporter;
use vars qw(@ISA @EXPORT $AUTOLOAD);
@ISA = "Exporter";
@EXPORT = qw ( );
# Must come after package declaration!
use EmulabConstants;
use emdb;
use libtestbed;
use English;
use Data::Dumper;
use overload ('""' => 'Stringify');
# Configure variables
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
# Cache of instances to avoid regenerating them.
my %instances = ();
my $debug = 0;
#
# Lookup by uuid.
#
sub Lookup($$;$)
{
my ($class, $uuid) = @_;
if ($uuid !~ /^\w+\-\w+\-\w+\-\w+\-\w+$/) {
return undef
}
# Look in cache first
return $instances{"$uuid"}
if (exists($instances{"$uuid"}));
my $query_result =
DBQueryWarn("select * from apt_instances where uuid='$uuid'");
return undef
if (!$query_result || !$query_result->numrows);
my $self = {};
$self->{'INSTANCE'} = $query_result->fetchrow_hashref();
bless($self, $class);
# Add to cache.
$instances{"$uuid"} = $self;
return $self;
}
AUTOLOAD {
my $self = $_[0];
my $type = ref($self) or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
# A DB row proxy method call.
if (exists($self->{'INSTANCE'}->{$name})) {
return $self->{'INSTANCE'}->{$name};
}
carp("No such slot '$name' field in class $type");
return undef;
}
# Break circular reference someplace to avoid exit errors.
sub DESTROY {
my $self = shift;
$self->{'INSTANCE'} = undef;
}
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $uuid = $self->uuid();
my $query_result =
DBQueryWarn("select * from apt_instances where uuid='$uuid'");
return -1
if (!$query_result || !$query_result->numrows);
$self->{'INSTANCE'} = $query_result->fetchrow_hashref();
return 0;
}
#
# Create an Instance
#
sub Create($$)
{
my ($class, $argref) = @_;
my $uuid;
if (exists($argref->{'uuid'})) {
$uuid = $argref->{'uuid'};
delete($argref->{'uuid'});
}
else {
$uuid = NewUUID();
}
#
# The uuid has to be unique, so lock the table for the check/insert.
#
DBQueryWarn("lock tables apt_instances write")
or return undef;
my $query_result =
DBQueryWarn("select uuid from apt_instances where uuid='$uuid'");
if ($query_result->numrows) {
DBQueryWarn("unlock tables");
tberror("Instance uuid $uuid already exists!");
return undef;
}
my $query = "insert into apt_instances set created=now(),uuid='$uuid', ".
join(",", map("$_=" .
DBQuoteSpecial($argref->{$_}), keys(%{$argref})));
if (! DBQueryWarn($query)) {
DBQueryWarn("unlock tables");
tberror("Error inserting new apt_instance record for $uuid!");
return undef;
}
DBQueryWarn("unlock tables");
return Lookup($class, $uuid);
}
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
my $uuid = $self->uuid();
return "[APT_Instance: $uuid]";
}
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uuid = $self->uuid();
my $query = "update apt_instances set ".
join(",", map("$_=" . DBQuoteSpecial($argref->{$_}), keys(%{$argref})));
$query .= " where uuid='$uuid'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
sub Delete($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uuid = $self->uuid();
DBQueryWarn("delete from apt_instances where uuid='$uuid'") or
return -1;
return 0;
}
sub SetStatus($$)
{
my ($self,$status) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uuid = $self->uuid();
DBQueryWarn("update apt_instances set status='$status' ".
"where uuid='$uuid'") or
return -1;
$self->{'INSTANCE'}->{'status'} = $status;
return 0;
}
sub SetManifest($$)
{
my ($self,$manifest) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uuid = $self->uuid();
my $safe_manifest = DBQuoteSpecial($manifest);
DBQueryWarn("update apt_instances set manifest=$safe_manifest ".
"where uuid='$uuid'") or
return -1;
$self->{'INSTANCE'}->{'manifest'} = $manifest;
return 0;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -32,7 +32,7 @@ SUBDIRS =
BIN_SCRIPTS = manage_profile
SBIN_SCRIPTS =
LIB_SCRIPTS = APT_Profile.pm
LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm
WEB_BIN_SCRIPTS = webmanage_profile
WEB_SBIN_SCRIPTS=
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
......
......@@ -569,9 +569,9 @@ sub GetKeys($)
# This function is intended to be used only by the SA to get the
# key bundle from the emulab ssh keys for the local user.
#
sub GetKeyBundle($$)
sub GetKeyBundle($$;$)
{
my ($self, $pref) = @_;
my ($self, $pref, $ignored) = @_;
my @results = ();
return -1
......@@ -789,9 +789,9 @@ sub urn($)
# This function is intended to be used only by the SA to get the
# key bundle from the emulab ssh keys for the local user.
#
sub GetKeyBundle($$)
sub GetKeyBundle($$;$)
{
my ($self, $pref) = @_;
my ($self, $pref, $internal) = @_;
my @results = ();
return -1
......@@ -805,6 +805,19 @@ sub GetKeyBundle($$)
push(@results, {"type" => 'ssh',
"key" => $sshkey});
}
#
# Add in unencrypted keys if requested. Be careful with this, only
# from APT at the moment since we know those resources are local.
#
if (defined($internal) && $internal) {
@sshkeys = ();
$self->emulab_user()->GetDefaultSSHKeys(\@sshkeys);
foreach my $sshkey (@sshkeys) {
push(@results, {"type" => 'ssh',
"key" => $sshkey});
}
}
@$pref = @results;
return 0;
}
......
......@@ -86,6 +86,7 @@ use lib "@prefix@/lib";
use libtestbed;
use libaudit;
use APT_Profile;
use APT_Instance;
use User;
use OSinfo;
use emutil;
......@@ -369,9 +370,9 @@ if (!$localuser && defined($sshkey)) {
$geniuser->DeleteKeys();
$geniuser->AddKey($sshkey);
}
# There will be "internal" keys ...
# There will be "internal" keys cause we pass the flag asking for them.
my @sshkeys;
if ($geniuser->GetKeyBundle(\@sshkeys) < 0 || !@sshkeys) {
if ($geniuser->GetKeyBundle(\@sshkeys, 1) < 0 || !@sshkeys) {
fatal("No ssh keys to use for $geniuser!");
}
......@@ -444,24 +445,16 @@ my $quickvm_uuid = (defined($quickuuid) ? $quickuuid : NewUUID());
if (!defined($quickvm_uuid)) {
fatal("Could not generate a new uuid");
}
sub SetQuickVMStatus($$)
{
my ($uuid, $status) = @_;
GeniDB::DBQueryWarn("update quickvms set status='$status' ".
"where uuid='$uuid'")
or return -1;
return 0;
}
if (!GeniDB::DBQueryWarn("insert into quickvms set ".
" uuid='$quickvm_uuid', slice_uuid='$slice_uuid', ".
" creator_uuid='$user_uuid', status='created', ".
" profile='$profile'")) {
my $instance = APT_Instance->Create({'uuid' => $quickvm_uuid,
'profile_idx' => $profile,
'slice_uuid' => $slice_uuid,
'creator' => $geniuser->uid(),
'creator_idx' => $geniuser->idx(),
'creator_uuid' => $geniuser->uuid(),
'status' => "created"});
if (!defined($instance)) {
$slice->Delete();
fatal("Could not create quickvm record");
fatal("Could not create instance record for $quickvm_uuid");
}
#
......@@ -497,7 +490,7 @@ my $response =
if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) {
$slice->Delete();
SetQuickVMStatus($quickvm_uuid, "failed");
$instance->SetStatus("failed");
fatal("CreateSliver failed: ".
(defined($response) ? $response->output() : "") . "\n");
}
......@@ -508,13 +501,11 @@ if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) {
my $manifest = $response->value()->[1];
if (!defined($manifest)) {
$slice->UnLock();
SetQuickVMStatus($quickvm_uuid, "failed");
$instance->SetStatus("failed");
fatal("Could not find the manifest in the response!");
}
my $safe_manifest = DBQuoteSpecial($manifest);
GeniDB::DBQueryWarn("update quickvms set ".
" status='provisioned', manifest=$safe_manifest ".
"where uuid='$quickvm_uuid'");
$instance->SetStatus("provisioned");
$instance->SetManifest($manifest);
print STDERR "\n";
print STDERR "$manifest\n\n";
......@@ -555,7 +546,7 @@ while ($seconds > 0) {
}
elsif ($blob->{'status'} eq "failed") {
$failed = 1;
SetQuickVMStatus($quickvm_uuid, "failed");
$instance->SetStatus("failed");
last;
}
}
......@@ -563,11 +554,11 @@ if ($failed) {
fatal("$slice_urn failed.");
}
elsif (!$ready) {
SetQuickVMStatus($quickvm_uuid, "failed");
$instance->SetStatus("failed");
fatal("$slice_urn timed out.");
}
$slice->UnLock();
SetQuickVMStatus($quickvm_uuid, "ready");
$instance->SetStatus("ready");
exit(0);
sub fatal($) {
......@@ -592,23 +583,19 @@ sub UserError($) {
sub Terminate($)
{
my ($uuid) = @_;
my $safe_uuid = DBQuoteSpecial($uuid);
my $query_result =
DBQueryWarn("select * from quickvms where uuid=$safe_uuid");
if (! ($query_result && $query_result->numrows)) {
my $instance = APT_Instance->Lookup($uuid);
if (! defined($instance)) {
fatal("No such quick VM: $uuid");
}
my $row = $query_result->fetchrow_hashref();
my $status = $row->{"status"};
my $geniuser = GeniUser->Lookup($row->{'creator_uuid'}, 1);
my $geniuser = GeniUser->Lookup($instance->creator_uuid(), 1);
if (!defined($geniuser)) {
fatal("No creator for quick VM: $uuid");
}
my $slice = GeniSlice->Lookup($row->{'slice_uuid'});
my $slice = GeniSlice->Lookup($instance->slice_uuid());
if (!defined($slice)) {
if ($status eq "failed") {
if ($instance->status() eq "failed") {
goto done;
}
fatal("No slice for quick VM: $uuid");
......@@ -628,7 +615,7 @@ sub Terminate($)
fatal("Could not sign speaksfor credential")
if ($speaksfor_credential->Sign($GeniCredential::LOCALSA_FLAG));
SetQuickVMStatus($uuid, "terminating");
$instance->SetStatus("terminating");
#
# Exit and let caller poll for status.
......@@ -660,7 +647,7 @@ sub Terminate($)
}
$slice->Delete();
done:
DBQueryWarn("delete from quickvms where uuid=$safe_uuid");
$instance->Delete();
exit(0);
}
......@@ -670,21 +657,20 @@ sub Terminate($)
sub Extend($$)
{
my ($uuid, $seconds) = @_;
my $safe_uuid = DBQuoteSpecial($uuid);
my $query_result =
DBQueryWarn("select * from quickvms where uuid=$safe_uuid");
if (! ($query_result && $query_result->numrows)) {
my $instance = APT_Instance->Lookup($uuid);
if (! defined($instance)) {
fatal("No such quick VM: $uuid");
}
my $row = $query_result->fetchrow_hashref();
my $geniuser = GeniUser->Lookup($row->{'creator_uuid'}, 1);
my $geniuser = GeniUser->Lookup($instance->creator_uuid(), 1);
if (!defined($geniuser)) {
fatal("No creator for quick VM: $uuid");
}
my $slice = GeniSlice->Lookup($row->{'slice_uuid'});
my $slice = GeniSlice->Lookup($instance->slice_uuid());
if (!defined($slice)) {
if ($instance->status() eq "failed") {
goto done;
}
fatal("No slice for quick VM: $uuid");
}
......
#!/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
#
......@@ -124,6 +124,11 @@ use emutil;
use libEmulab;
use POSIX qw(strftime ceil);
# Experimental
if ($MAINSITE) {
require APT_Instance;
}
#
# So we know who/what we are acting as.
#
......@@ -354,8 +359,10 @@ sub ExpireSlices()
# Experimental
if ($MAINSITE) {
GeniDB::DBQueryWarn("delete from quickvms ".
"where slice_uuid='$slice_uuid'");
my $instance = APT_Instance->Lookup($slice_uuid);
if (defined($instance)) {
$instance->Delete();
}
}
# Needs to move.
......
......@@ -54,6 +54,25 @@ CREATE TABLE `active_checkups` (
PRIMARY KEY (`object`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `apt_instances`
--
DROP TABLE IF EXISTS `apt_instances`;
CREATE TABLE `apt_instances` (
`uuid` varchar(40) NOT NULL default '',
`profile_idx` int(10) unsigned NOT NULL default '0',
`slice_uuid` varchar(40) NOT NULL default '',
`creator` varchar(8) NOT NULL default '',
`creator_idx` mediumint(8) unsigned NOT NULL default '0',
`creator_uuid` varchar(40) NOT NULL default '',
`created` datetime default NULL,
`status` varchar(32) default NULL,
`extension_code` varchar(32) default NULL,
`manifest` mediumtext,
PRIMARY KEY (`uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `apt_profiles`
--
......
#
# Add APT instances table.
#
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if(!DBTableExists("apt_instances")) {
DBQueryFatal("CREATE TABLE `apt_instances` ( ".
" `uuid` varchar(40) NOT NULL default '', ".
" `profile_idx` int(10) unsigned NOT NULL default '0', ".
" `slice_uuid` varchar(40) NOT NULL default '', ".
" `creator` varchar(8) NOT NULL default '', ".
" `creator_idx` mediumint(8) ".
" unsigned NOT NULL default '0', ".
" `creator_uuid` varchar(40) NOT NULL default '', ".
" `created` datetime default NULL, ".
" `status` varchar(32) default NULL, ".
" `extension_code` varchar(32) default NULL, ".
" `manifest` mediumtext, ".
" PRIMARY KEY (`uuid`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
......@@ -2,3 +2,37 @@ ErrorDocument 404 /error.php
ErrorDocument 403 /error.php
DirectoryIndex instantiate.php
RewriteEngine on
# Old quickvm page.
RewriteRule ^/quickvm.php /instantiate.php [R=301,L]
# These work only on www.aptlab.net cause of the path prefix.
RewriteRule ^/p/([^/]+)/([\d]+)/?$ /instantiate.php?profile=$1&version=$2 [R=301,L]
RewriteRule ^/p/([^/]+)/([^/]+)/?$ /instantiate.php?project=$1&profile=$2 [R=301,L]
RewriteRule ^/p/([^/]+)/([^/]+)/([\d]+)/?$ /instantiate.php?project=$1&profile=$2&version=$3 [R=301,L]
RewriteRule ^/p/([^/]+)/? /instantiate.php?profile=$1 [R=301,L]
# Change urlpath.php to urlpath
## Only perform this rule if we're on the expected domain
RewriteCond %{HTTP_HOST} ^www\.aptlab\.net$ [NC]
## Don't perform this rule if we've already been redirected internally
RewriteCond %{QUERY_STRING} !internal=1 [NC]
## Redirect the user externally to the non PHP URL
RewriteRule ^(.*)\.php$ $1 [L,R=302]
# if the user requests /something we need to serve the php version if it exists
## Only perform this rule if we're on the expected domain
RewriteCond %{HTTP_HOST} ^www\.aptlab\.net$ [NC]
## Perform this rule only if a file with this name does not exist
RewriteCond %{REQUEST_FILENAME} !-f
## Perform this rule if the requested file doesn't end with '.php'
RewriteCond %{REQUEST_FILENAME} !\.php$ [NC]
## Only perform this rule if we're not requesting the index page
RewriteCond %{REQUEST_URI} !^/$
## Finally, rewrite the URL internally, passing through the user's query string
## using the [qsa] flag along with an 'internal=1' identifier so that our first
## RewriteRule knows we've already redirected once.
RewriteRule ^(.*)$ $1.php?internal=1 [L,QSA]
<?php
#
# Copyright (c) 2006-2014 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/>.
#
# }}}
#
#
class Instance
{
var $instance;
#
# Constructor by lookup on unique index.
#
function Instance($uuid) {
$safe_uuid = addslashes($uuid);
$query_result =
DBQueryWarn("select * from apt_instances ".
"where uuid='$safe_uuid'");
if (!$query_result || !mysql_num_rows($query_result)) {
$this->instance = null;
return;
}
$this->instance = mysql_fetch_array($query_result);
}
# accessors
function field($name) {
return (is_null($this->instance) ? -1 : $this->instance[$name]);
}
function uuid() { return $this->field('uuid'); }
function slice_uuid() { return $this->field('slice_uuid'); }
function creator() { return $this->field('creator'); }
function creator_idx() { return $this->field('creator_idx'); }
function creator_uuid() { return $this->field('creator_uuid'); }
function created() { return $this->field('created'); }
function profile_idx() { return $this->field('profile_idx'); }
function status() { return $this->field('status'); }
function manifest() { return $this->field('manifest'); }
# Hmm, how does one cause an error in a php constructor?
function IsValid() {
return !is_null($this->instance);
}
# Lookup up an instance by idx.
function Lookup($idx) {
$foo = new Instance($idx);
if ($foo->IsValid()) {
# Insert into cache.
return $foo;
}
return null;
}
function LookupByCreator($token) {
$safe_token = addslashes($token);
$query_result =
DBQueryFatal("select uuid from apt_instances ".
"where creator_uuid='$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.
#
function Refresh() {
if (! $this->IsValid())
return -1;
$uuid = $this->uuid();
$query_result =
DBQueryWarn("select * from apt_instances where uuid='$uuid'");
if (!$query_result || !mysql_num_rows($query_result)) {
$this->instance = NULL;
return -1;
}
$this->instance = mysql_fetch_array($query_result);
return 0;
}
}
?>
......@@ -27,6 +27,7 @@ include_once("osinfo_defs.php");
include_once("geni_defs.php");
chdir("apt");
include("quickvm_sup.php");
include("instance_defs.php");