Commit 9e9ac6ee authored by Leigh B Stoller's avatar Leigh B 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 = ...@@ -32,7 +32,7 @@ SUBDIRS =
BIN_SCRIPTS = manage_profile BIN_SCRIPTS = manage_profile
SBIN_SCRIPTS = SBIN_SCRIPTS =
LIB_SCRIPTS = APT_Profile.pm LIB_SCRIPTS = APT_Profile.pm APT_Instance.pm
WEB_BIN_SCRIPTS = webmanage_profile WEB_BIN_SCRIPTS = webmanage_profile
WEB_SBIN_SCRIPTS= WEB_SBIN_SCRIPTS=
LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS) LIBEXEC_SCRIPTS = $(WEB_BIN_SCRIPTS) $(WEB_SBIN_SCRIPTS)
......
...@@ -569,9 +569,9 @@ sub GetKeys($) ...@@ -569,9 +569,9 @@ sub GetKeys($)
# This function is intended to be used only by the SA to get the # 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. # key bundle from the emulab ssh keys for the local user.
# #
sub GetKeyBundle($$) sub GetKeyBundle($$;$)
{ {
my ($self, $pref) = @_; my ($self, $pref, $ignored) = @_;
my @results = (); my @results = ();
return -1 return -1
...@@ -789,9 +789,9 @@ sub urn($) ...@@ -789,9 +789,9 @@ sub urn($)
# This function is intended to be used only by the SA to get the # 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. # key bundle from the emulab ssh keys for the local user.
# #
sub GetKeyBundle($$) sub GetKeyBundle($$;$)
{ {
my ($self, $pref) = @_; my ($self, $pref, $internal) = @_;
my @results = (); my @results = ();
return -1 return -1
...@@ -805,6 +805,19 @@ sub GetKeyBundle($$) ...@@ -805,6 +805,19 @@ sub GetKeyBundle($$)
push(@results, {"type" => 'ssh', push(@results, {"type" => 'ssh',
"key" => $sshkey}); "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; @$pref = @results;
return 0; return 0;
} }
......
...@@ -86,6 +86,7 @@ use lib "@prefix@/lib"; ...@@ -86,6 +86,7 @@ use lib "@prefix@/lib";
use libtestbed; use libtestbed;
use libaudit; use libaudit;
use APT_Profile; use APT_Profile;
use APT_Instance;
use User; use User;
use OSinfo; use OSinfo;
use emutil; use emutil;
...@@ -369,9 +370,9 @@ if (!$localuser && defined($sshkey)) { ...@@ -369,9 +370,9 @@ if (!$localuser && defined($sshkey)) {
$geniuser->DeleteKeys(); $geniuser->DeleteKeys();
$geniuser->AddKey($sshkey); $geniuser->AddKey($sshkey);
} }
# There will be "internal" keys ... # There will be "internal" keys cause we pass the flag asking for them.
my @sshkeys; my @sshkeys;
if ($geniuser->GetKeyBundle(\@sshkeys) < 0 || !@sshkeys) { if ($geniuser->GetKeyBundle(\@sshkeys, 1) < 0 || !@sshkeys) {
fatal("No ssh keys to use for $geniuser!"); fatal("No ssh keys to use for $geniuser!");
} }
...@@ -444,24 +445,16 @@ my $quickvm_uuid = (defined($quickuuid) ? $quickuuid : NewUUID()); ...@@ -444,24 +445,16 @@ my $quickvm_uuid = (defined($quickuuid) ? $quickuuid : NewUUID());
if (!defined($quickvm_uuid)) { if (!defined($quickvm_uuid)) {
fatal("Could not generate a new uuid"); fatal("Could not generate a new uuid");
} }
my $instance = APT_Instance->Create({'uuid' => $quickvm_uuid,
sub SetQuickVMStatus($$) 'profile_idx' => $profile,
{ 'slice_uuid' => $slice_uuid,
my ($uuid, $status) = @_; 'creator' => $geniuser->uid(),
'creator_idx' => $geniuser->idx(),
GeniDB::DBQueryWarn("update quickvms set status='$status' ". 'creator_uuid' => $geniuser->uuid(),
"where uuid='$uuid'") 'status' => "created"});
or return -1; if (!defined($instance)) {
return 0;
}
if (!GeniDB::DBQueryWarn("insert into quickvms set ".
" uuid='$quickvm_uuid', slice_uuid='$slice_uuid', ".
" creator_uuid='$user_uuid', status='created', ".
" profile='$profile'")) {
$slice->Delete(); $slice->Delete();
fatal("Could not create quickvm record"); fatal("Could not create instance record for $quickvm_uuid");
} }
# #
...@@ -497,7 +490,7 @@ my $response = ...@@ -497,7 +490,7 @@ my $response =
if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) { if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) {
$slice->Delete(); $slice->Delete();
SetQuickVMStatus($quickvm_uuid, "failed"); $instance->SetStatus("failed");
fatal("CreateSliver failed: ". fatal("CreateSliver failed: ".
(defined($response) ? $response->output() : "") . "\n"); (defined($response) ? $response->output() : "") . "\n");
} }
...@@ -508,13 +501,11 @@ if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) { ...@@ -508,13 +501,11 @@ if (!defined($response) || $response->code() != GENIRESPONSE_SUCCESS) {
my $manifest = $response->value()->[1]; my $manifest = $response->value()->[1];
if (!defined($manifest)) { if (!defined($manifest)) {
$slice->UnLock(); $slice->UnLock();
SetQuickVMStatus($quickvm_uuid, "failed"); $instance->SetStatus("failed");
fatal("Could not find the manifest in the response!"); fatal("Could not find the manifest in the response!");
} }
my $safe_manifest = DBQuoteSpecial($manifest); $instance->SetStatus("provisioned");
GeniDB::DBQueryWarn("update quickvms set ". $instance->SetManifest($manifest);
" status='provisioned', manifest=$safe_manifest ".
"where uuid='$quickvm_uuid'");
print STDERR "\n"; print STDERR "\n";
print STDERR "$manifest\n\n"; print STDERR "$manifest\n\n";
...@@ -555,7 +546,7 @@ while ($seconds > 0) { ...@@ -555,7 +546,7 @@ while ($seconds > 0) {
} }
elsif ($blob->{'status'} eq "failed") { elsif ($blob->{'status'} eq "failed") {
$failed = 1; $failed = 1;
SetQuickVMStatus($quickvm_uuid, "failed"); $instance->SetStatus("failed");
last; last;
} }
} }
...@@ -563,11 +554,11 @@ if ($failed) { ...@@ -563,11 +554,11 @@ if ($failed) {
fatal("$slice_urn failed."); fatal("$slice_urn failed.");
} }
elsif (!$ready) { elsif (!$ready) {
SetQuickVMStatus($quickvm_uuid, "failed"); $instance->SetStatus("failed");
fatal("$slice_urn timed out."); fatal("$slice_urn timed out.");
} }
$slice->UnLock(); $slice->UnLock();
SetQuickVMStatus($quickvm_uuid, "ready"); $instance->SetStatus("ready");
exit(0); exit(0);
sub fatal($) { sub fatal($) {
...@@ -592,23 +583,19 @@ sub UserError($) { ...@@ -592,23 +583,19 @@ sub UserError($) {
sub Terminate($) sub Terminate($)
{ {
my ($uuid) = @_; my ($uuid) = @_;
my $safe_uuid = DBQuoteSpecial($uuid);
my $query_result = my $instance = APT_Instance->Lookup($uuid);
DBQueryWarn("select * from quickvms where uuid=$safe_uuid"); if (! defined($instance)) {
if (! ($query_result && $query_result->numrows)) {
fatal("No such quick VM: $uuid"); 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)) { if (!defined($geniuser)) {
fatal("No creator for quick VM: $uuid"); 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 (!defined($slice)) {
if ($status eq "failed") { if ($instance->status() eq "failed") {
goto done; goto done;
} }
fatal("No slice for quick VM: $uuid"); fatal("No slice for quick VM: $uuid");
...@@ -628,7 +615,7 @@ sub Terminate($) ...@@ -628,7 +615,7 @@ sub Terminate($)
fatal("Could not sign speaksfor credential") fatal("Could not sign speaksfor credential")
if ($speaksfor_credential->Sign($GeniCredential::LOCALSA_FLAG)); if ($speaksfor_credential->Sign($GeniCredential::LOCALSA_FLAG));
SetQuickVMStatus($uuid, "terminating"); $instance->SetStatus("terminating");
# #
# Exit and let caller poll for status. # Exit and let caller poll for status.
...@@ -660,7 +647,7 @@ sub Terminate($) ...@@ -660,7 +647,7 @@ sub Terminate($)
} }
$slice->Delete(); $slice->Delete();
done: done:
DBQueryWarn("delete from quickvms where uuid=$safe_uuid"); $instance->Delete();
exit(0); exit(0);
} }
...@@ -670,21 +657,20 @@ sub Terminate($) ...@@ -670,21 +657,20 @@ sub Terminate($)
sub Extend($$) sub Extend($$)
{ {
my ($uuid, $seconds) = @_; my ($uuid, $seconds) = @_;
my $safe_uuid = DBQuoteSpecial($uuid);
my $query_result = my $instance = APT_Instance->Lookup($uuid);
DBQueryWarn("select * from quickvms where uuid=$safe_uuid"); if (! defined($instance)) {
if (! ($query_result && $query_result->numrows)) {
fatal("No such quick VM: $uuid"); fatal("No such quick VM: $uuid");
} }
my $row = $query_result->fetchrow_hashref(); my $geniuser = GeniUser->Lookup($instance->creator_uuid(), 1);
my $geniuser = GeniUser->Lookup($row->{'creator_uuid'}, 1);
if (!defined($geniuser)) { if (!defined($geniuser)) {
fatal("No creator for quick VM: $uuid"); 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 (!defined($slice)) {
if ($instance->status() eq "failed") {
goto done;
}
fatal("No slice for quick VM: $uuid"); fatal("No slice for quick VM: $uuid");
} }
......
#!/usr/bin/perl -w #!/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 # {{{GENIPUBLIC-LICENSE
# #
...@@ -124,6 +124,11 @@ use emutil; ...@@ -124,6 +124,11 @@ use emutil;
use libEmulab; use libEmulab;
use POSIX qw(strftime ceil); use POSIX qw(strftime ceil);
# Experimental
if ($MAINSITE) {
require APT_Instance;
}
# #
# So we know who/what we are acting as. # So we know who/what we are acting as.
# #
...@@ -354,8 +359,10 @@ sub ExpireSlices() ...@@ -354,8 +359,10 @@ sub ExpireSlices()
# Experimental # Experimental
if ($MAINSITE) { if ($MAINSITE) {
GeniDB::DBQueryWarn("delete from quickvms ". my $instance = APT_Instance->Lookup($slice_uuid);
"where slice_uuid='$slice_uuid'"); if (defined($instance)) {
$instance->Delete();
}
} }
# Needs to move. # Needs to move.
......
...@@ -54,6 +54,25 @@ CREATE TABLE `active_checkups` ( ...@@ -54,6 +54,25 @@ CREATE TABLE `active_checkups` (
PRIMARY KEY (`object`) PRIMARY KEY (`object`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1; ) 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`)