...
 
Commits (249)
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -25,6 +25,7 @@ use strict;
use English;
use Getopt::Long qw(:config no_ignore_case);
use POSIX qw(strftime);
use Date::Parse;
#
# Load the Testbed support stuff.
......@@ -54,6 +55,7 @@ my $urn;
my $oldkeyfile;
my $authority;
my $notca = 0;
my $days = 2000;
my $include_uuid = 0;
my %optlist = ( "debug" => \$debug,
"password=s" => \$password,
......@@ -312,8 +314,45 @@ if( defined( $oldkeyfile ) ) {
#
my $startdate = POSIX::strftime("%y%m%d%H%M%SZ", gmtime(time() - 3600));
#
# Check the expiration on the CA cert, we do not want the new
# certificate to expire after the CA (signer) cert expires.
#
$UID = 0;
my $expires = `$OPENSSL x509 -enddate -noout -in $certfile`;
if ($?) {
fatal("Could not get expiration from $certfile");
}
if ($expires =~ /^notAfter=(.*)$/i) {
my $tmp = str2time($1);
if (!defined($tmp)) {
fatal("Could not convert $certfile expiration to time: $1");
}
$expires = $tmp;
}
else {
fatal("Could not parse $certfile expiration: $expires");
}
if ($expires < time()) {
fatal("$certfile certificate has expired!");
}
# If the CA expires in less then 30 days, grind to a halt.
my $daystoexpire = int(($expires - time()) / (3600 * 24));
if ($daystoexpire <= 30) {
fatal("Refusing to sign new certificate; the $certfile expires in less ".
"then 30 days!");
}
if ($debug) {
print "CA certificate expires in $daystoexpire days.\n";
}
if ($days > $daystoexpire) {
$days = $daystoexpire - 1;
print "Shortening certificate expiration to $days\n";
}
system("$OPENSSL ca -batch -policy policy_sslxmlrpc -startdate $startdate ".
" -days $days ".
" -name CA_syscerts -config $CACONFIG ".
" -out syscert_cert.pem -cert $certfile -keyfile $keyfile ".
" -infiles syscert_req.pem $outline") == 0
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -24,6 +24,7 @@
use strict;
use English;
use Getopt::Std;
use Date::Parse;
#
# Load the Testbed support stuff.
......@@ -408,11 +409,47 @@ sub CreateNewCert() {
system("$OPENSSL req $reqopts -new -config usercert.cnf ".
"-key usercert_key.pem -out usercert_req.pem")
== 0 or fatal("Could not create certificate request");
#
# Check the expiration on the CA cert, we do not want the new
# certificate to expire after the CA (signer) cert expires.
#
$UID = 0;
my $expires = `$OPENSSL x509 -enddate -noout -in $EMULAB_CERT`;
if ($?) {
fatal("Could not get expiration from $EMULAB_CERT");
}
if ($expires =~ /^notAfter=(.*)$/i) {
my $tmp = str2time($1);
if (!defined($tmp)) {
fatal("Could not convert CA expiration to time: $1");
}
$expires = $tmp;
}
else {
fatal("Could not parse CA expiration: $expires");
}
if ($expires < time()) {
fatal("CA certificate has expired!");
}
# If the CA expires in less then 30 days, grind to a halt.
my $daystoexpire = int(($expires - time()) / (3600 * 24));
if ($daystoexpire <= 30) {
fatal("Refusing to sign new certificate; the CA expires in less ".
"then 30 days!");
}
if ($debug) {
print "CA certificate expires in $daystoexpire days.\n";
}
if ($days > $daystoexpire) {
$days = $daystoexpire - 1;
print "Shortening certificate expiration to $days\n";
}
#
# Sign the client cert request, creating a client certificate.
#
$UID = 0;
system("$OPENSSL ca -batch -policy policy_sslxmlrpc -days $days ".
" -name CA_usercerts -config $CACONFIG ".
" -out usercert_cert.pem -cert $EMULAB_CERT -keyfile $EMULAB_KEY ".
......
......@@ -60,6 +60,7 @@ my $silent = 0;
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $MAINSITE = @TBMAINSITE@;
my $TBAUDIT = "@TBAUDITEMAIL@";
my $CONTROL = "@USERNODE@";
my $BOSSNODE = "@BOSSNODE@";
......@@ -295,6 +296,7 @@ my $status = $target_user->status();
my $webonly = $target_user->webonly();
my $usr_shell = $target_user->shell();
my $usr_admin = $target_user->admin();
my $usr_uid = $target_user->uid();
my $wpswd = $target_user->w_pswd();
my $wikionly = $target_user->wikionly();
my $isnonlocal = $target_user->IsNonLocal();
......@@ -901,9 +903,21 @@ sub UpdateUser(;$)
# Admin users get a local shell.
if ($usr_admin) {
$locshellarg = "-s " . $shellpaths{"tcsh"};
if ($MAINSITE) {
$locshellarg .= " -d /home/$usr_uid";
if (! -e "/home/$usr_uid") {
$locshellarg .= " -m";
}
}
}
else {
$locshellarg = "-s $PBAG"
$locshellarg = "-s $PBAG";
if ($MAINSITE) {
my $homedir = (getpwnam($usr_uid))[7];
if ($homedir eq "/home/$usr_uid") {
$locshellarg .= " -d $HOMEDIR/$usr_uid";
}
}
}
if (!defined($usr_shell) ||
!exists($shellpaths{$usr_shell})) {
......
#
# Copyright (c) 2002-2015 University of Utah and the Flux Group.
# Copyright (c) 2002-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -47,9 +47,11 @@ CONFIG_GENI = httpd-geni.conf
CONFIG_FILES += $(CONFIG_GENI)
endif
#
# Move to Apache 22 ...
#
ifeq ($(APACHE_VERSION),24)
# For VPATH.
MOSTLY_SRCDIRS = ${SRCDIR}/v24
SCRIPT_HACK = 0
else
ifeq ($(APACHE_VERSION),22)
# For VPATH.
MOSTLY_SRCDIRS = ${SRCDIR}/v2
......@@ -57,6 +59,7 @@ SCRIPT_HACK = 0
else
MOSTLY_SRCDIRS = ${SRCDIR}/v1
endif
endif
INSTALL_PHP_CONFIG = /usr/local/etc
#
......@@ -140,7 +143,13 @@ ifeq ($(SCRIPT_HACK),1)
$(INSTALL) -m 755 $(SRCDIR)/apache-emulab /usr/local/etc/rc.d/apache.sh
endif
ifeq ($(APACHE_VERSION),22)
ifeq ($(APACHE_VERSION),1.3)
install: install-dirs install-scripts httpd.conf.fixed
$(INSTALL_DATA) httpd.conf.fixed $(INSTALL_APACHE_CONFIG)/httpd.conf
control-install: install-dirs install-scripts httpd.conf-ops.fixed
$(INSTALL_DATA) httpd.conf-ops.fixed $(INSTALL_APACHE_CONFIG)/httpd.conf
else
install: install-dirs install-scripts httpd.conf pgeni-install
$(INSTALL_DATA) httpd.conf $(INSTALL_APACHE_CONFIG)/httpd.conf
......@@ -153,12 +162,6 @@ ifeq ($(PGENISUPPORT),1)
endif
utah: httpd.conf.utah httpd.conf-ops.utah
else
install: install-dirs install-scripts httpd.conf.fixed
$(INSTALL_DATA) httpd.conf.fixed $(INSTALL_APACHE_CONFIG)/httpd.conf
control-install: install-dirs install-scripts httpd.conf-ops.fixed
$(INSTALL_DATA) httpd.conf-ops.fixed $(INSTALL_APACHE_CONFIG)/httpd.conf
endif
install-php-ini: php.ini
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -278,7 +278,7 @@ sub CheckStatus($$;$)
my ($self, $perrmsg, $portalrpc) = @_;
require APT_Geni;
if (0 || $self->disabled()) {
if ($self->disabled()) {
$$perrmsg = "The " . $self->name() . " cluster ".
"is currently offline, please try again later.";
return 1;
......
......@@ -225,6 +225,15 @@ sub GenCredentials($$;$$)
$gensacert = 1;
}
# Another case to watch for; the certificate chain is bad cause
# the signer (say, the Geni Portal) is expired.
if ($certificate->VerifySSLChain()) {
print STDERR "certificate for $geniuser has a bad chain\n";
goto bad
if (!$allowexpired);
$gensacert = 1;
}
if ($gensacert) {
# Be careful not to return this.
$speaksfor = undef;
......
......@@ -811,7 +811,8 @@ sub ComputeNodeCounts($)
if (defined($virtualization_type) &&
($virtualization_type eq "emulab-xen" ||
$virtualization_type eq "emulab-blockstore")) {
$virtualization_type eq "emulab-blockstore" ||
$virtualization_type eq "emulab-docker")) {
$vcount++;
next;
}
......@@ -2256,13 +2257,13 @@ sub UpdateSliverStatusAll($$)
# Helper functions for below.
sub ContextError()
{
return GeniResponse->Create(GENIRESPONSE_ERROR(), undef,
"Could not generate context for RPC");
return GeniResponse->new(GENIRESPONSE_ERROR(), -1,
"Could not generate context for RPC");
}
sub CredentialError()
{
return GeniResponse->Create(GENIRESPONSE_ERROR(), undef,
"Could not generate credentials for RPC");
return GeniResponse->new(GENIRESPONSE_ERROR(), -1,
"Could not generate credentials for RPC");
}
#
......@@ -2775,10 +2776,11 @@ sub ConsoleURL($$)
#
# Create an Image,
#
sub CreateImage($$$$;$$$$$)
sub CreateImage($$$$;$$$$$$)
{
my ($self, $sliver_urn, $imagename, $update_prepare,
$copyback_uuid, $bsname, $nosnapshot, $mustnotexist, $wholedisk) = @_;
$copyback_uuid, $bsname, $nosnapshot,
$mustnotexist, $wholedisk, $description) = @_;
my $authority = $self->GetGeniAuthority();
my $geniuser = $self->instance()->GetGeniUser();
my $slice = $self->instance()->GetGeniSlice();
......@@ -2817,6 +2819,8 @@ sub CreateImage($$$$;$$$$$)
if ($mustnotexist);
$args->{'wholedisk'} = 1
if ($wholedisk);
$args->{'description'} = $description
if (defined($description));
my $cmurl = $authority->url();
$cmurl = devurl($cmurl) if ($usemydevtree);
......
......@@ -860,8 +860,9 @@ sub CheckFirewall($$)
# and closed.
#
my $style = "closed";
if (defined($virtualization_type) &&
$virtualization_type eq "emulab-xen" && !@routable_control_ip) {
if (defined($virtualization_type) && !@routable_control_ip &&
($virtualization_type eq "emulab-xen"
|| $virtualization_type eq "emulab-docker")) {
$style = "basic";
}
......@@ -1532,16 +1533,16 @@ sub EncryptBlocks($$$)
#
# Add a portal element.
#
sub AddPortalTag($$$)
sub AddPortalTag($$$$)
{
my ($pxml, $tag, $pmsg) = @_;
my ($pxml, $tag, $url, $pmsg) = @_;
my $rspec = GeniXML::Parse($$pxml);
if (! defined($rspec)) {
$$pmsg = "AddPortalTag: Could not parse rspec";
return -1;
}
GeniXML::SetPortal($rspec, $tag);
GeniXML::SetPortal($rspec, $tag, $url);
$$pxml = GeniXML::Serialize($rspec);
return 0;
}
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2017 University of Utah and the Flux Group.
# Copyright (c) 2007-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -831,6 +831,8 @@ sub new($$$$$)
"jacks_site" => undef,
"xen_settings" => undef,
"xen_ptype" => undef,
"docker_settings" => undef,
"docker_ptype" => undef,
"instantiate_on" => undef,
"services" => [],
"statements" => [],
......@@ -1101,6 +1103,18 @@ sub addNode($$$)
$node->{"xen_settings"} = $settings;
last SWITCH;
};
# Docker settings
/^docker$/i && do {
my $settings = GeniXML::GetDockerSettings($ref->parentNode);
fatal("Failed to get docker settings")
if (!defined($settings));
$node->{"docker_settings"} = $settings;
last SWITCH;
};
/^docker_ptype$/i && do {
$node->{"docker_ptype"} = GetTextOrFail("name", $child);
last SWITCH;
};
/^(sliver_type_shaping|firewall_config)$/i && do {
# We handled this above.
last SWITCH;
......@@ -1291,7 +1305,7 @@ sub Compare($$)
last SWITCH;
};
(/^(component_id|component_manager_id|disk_image)$/i ||
/^(hardware_type|jacks_site|xen_ptype|instantiate_on)$/i ||
/^(hardware_type|jacks_site|xen_ptype|docker_ptype|instantiate_on)$/i ||
/^(adb_target|failure_action)$/i ||
/^(isexptfirewall|firewall_style)$/i ||
/^(use_type_default_image|routable_control_ip)$/i) && do {
......@@ -1304,7 +1318,7 @@ sub Compare($$)
# Handled up above in CompareNodes.
last SWITCH;
};
/^(xen_settings|desires|pipes|blockstores|attributes)$/i && do {
/^(xen_settings|docker_settings|desires|pipes|blockstores|attributes)$/i && do {
return 1
if (APT_Rspec::CompareHashes("Node: $client_id: $key",
$val1, $val2));
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2007-2016 University of Utah and the Flux Group.
# Copyright (c) 2007-2016, 2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -29,12 +29,14 @@ package APT_Utility;
use strict;
use English;
use Data::Dumper;
use Date::Parse;
use Carp;
use Exporter;
use POSIX qw(ceil);
use vars qw(@ISA @EXPORT);
@ISA = "Exporter";
@EXPORT = qw ( );
@EXPORT = qw (MapUserURN MapProjectURN ReadFile);
# Must come after package declaration!
use emdb;
......@@ -42,11 +44,15 @@ use libtestbed;
use APT_Instance;
use Project;
use Group;
use GeniHRN;
use GeniUser;
use emutil;
# Configure variables
my $TB = "@prefix@";
my $MAINSITE = @TBMAINSITE@;
my $TBOPS = "@TBOPSEMAIL@";
my $OURDOMAIN = "@OURDOMAIN@";
#
# Find all of the instances a user has (should have) an account on, and
......@@ -86,3 +92,137 @@ sub UpdateInstancesByUser($)
}
return 0;
}
#
# Map project urn to local project. Since these URNs are coming from
# remote clusters, it might not actually refer to a local
#
sub MapProjectURN($)
{
my ($urn) = @_;
my $hrn = GeniHRN->new($urn);
if (!defined($hrn)) {
print STDERR "MapProjectURN: Could not parse $urn\n";
return undef;
}
if ($hrn->domain() eq $OURDOMAIN && defined($hrn->project())) {
my $project = Project->Lookup($hrn->project());
return $project
if (defined($project));
}
return undef;
}
#
# Same as above, but for users.
#
sub MapUserURN($)
{
my ($urn) = @_;
my $hrn = GeniHRN->new($urn);
if (!defined($hrn)) {
print STDERR "MapUserURN: Could not parse $urn\n";
return undef;
}
my $geniuser = GeniUser->Lookup($urn, 1);
return $geniuser
if (defined($geniuser) && $geniuser->IsLocal());
return undef;
}
#
# Convenience
#
sub ReadFile($) {
local $/ = undef;
my ($filename) = @_;
open(FILE, $filename) or fatal("Could not open $filename: $!");
my $contents = <FILE>;
close(FILE);
return $contents;
}
#
# Given a reservation details hash, calculate a utilization number
# from the history array.
#
sub ReservationUtilization($$)
{
my ($res, $active) = @_;
my $count = $res->{'nodes'};
my $resstart = str2time($res->{'start'});
my $resend = ($active ? time() :
(str2time(defined($res->{'deleted'}) ?
$res->{'deleted'} : $res->{'end'})));
my $reshours = (($resend - $resstart) / 3600) * $count;
my $usedhours = 0;
my $inuse = 0;
my $laststamp = $resstart;
my $type = $res->{'type'};
my $remote_uid = $res->{'remote_uid'};
my @tmp = @{$res->{'history'}};
# Init for web interface.
$res->{'reshours'} = $reshours;
$res->{'usedhours'} = undef;
$res->{'utilization'} = undef;
#
# Scan past timeline entries that are *before* the start of the
# reservation; these are experiments that were running when the
# reservation started, and provide the number of nodes allocated
# at the time the reservation starts.
#
while (@tmp) {
my $ref = $tmp[0];
my $stamp = $ref->{'t'};
my $allocated = $ref->{'allocated'};
$ref->{'tt'} = TBDateStringGMT($ref->{'t'});
last
if ($stamp >= $resstart);
# Watch for nothing allocated by the user at this time stamp
my $using = 0;
if (exists($allocated->{$remote_uid})) {
$using = $allocated->{$remote_uid}->{$type};
}
$inuse = $using;
$inuse = $count if ($inuse > $count);
shift(@tmp);
}
foreach my $ref (@tmp) {
my $stamp = $ref->{'t'};
my $reserved = $ref->{'reserved'};
my $allocated = $ref->{'allocated'};
$ref->{'tt'} = TBDateStringGMT($ref->{'t'});
# If this stamp is after the reservation, we can stop. The
# last entry will be the current number of nodes used till
# the end of the reservation. This entry is typically for the
# end of an experiment start before the end of the reservation.
last
if ($stamp > $resend);
# Watch for nothing allocated by the user at this time stamp
my $using = 0;
if (exists($allocated->{$remote_uid})) {
$using = $allocated->{$remote_uid}->{$type};
}
$usedhours += (($stamp - $laststamp) / 3600) * $inuse;
$laststamp = $stamp;
$inuse = $using;
$inuse = $count if ($inuse > $count);
}
# And then a final entry for usage until the end of the reservation.
if ($laststamp) {
$usedhours += (($resend - $laststamp) / 3600) * $inuse;
}
$res->{'reshours'} = $reshours;
$res->{'usedhours'} = $usedhours;
$res->{'utilization'} = POSIX::ceil(($usedhours/$reshours) * 100.0);
$res->{'utilization'} = 100 if ($res->{'utilization'} > 100);
return 0;
}
......@@ -346,9 +346,20 @@ $version = $profile->version();
#
if (exists($xmlparse->{'attribute'}->{"rspec"})) {
$rspecstr = $xmlparse->{'attribute'}->{"rspec"}->{'value'};
# Trim()
$rspecstr =~ s/^\s+|\s+$//g;
if ($rspecstr eq "") {
UserError("Not a valid rspec");
}
}
else {
$rspecstr = $profile->CheckFirewall(!$localuser);
$rspecstr = $profile->rspec();
# Trim()
$rspecstr =~ s/^\s+|\s+$//g;
if ($rspecstr eq "") {
UserError("Profile does not have a valid rspec");
}
$rspecstr = $profile->CheckFirewall(!$localuser);
}
#
# Optional rspec and/or script, as for a repo-based profile.
......@@ -653,7 +664,7 @@ if ($localuser) {
$emulab_user, $project));
# Hack for Kobus' class, generalize someday.
if ($pid eq "CS4480-2018") {
if (0 && $pid eq "CS4480-2018") {
my $termination = str2time("2018-05-02");
# convert to hours till then
$duration = int(($termination - time()) / 3600);
......@@ -814,20 +825,6 @@ $tmp = APT_Profile::EncryptBlocks(\$rspecstr, $alt_certificate, \$errmsg);
if ($tmp) {
($tmp < 0 ? fatal($errmsg) : UserError($errmsg));
}
#
# Tell the CM to do normal NFS mounts if this is the "Emulab" portal
# making the request. The CM is of course free to ignore this.
#
# XXX Need to handle this differently if we use the stitcher.
#
if ($portal ne "emulab") {
if (APT_Profile::ClearPortalTag(\$rspecstr, $errmsg)) {
fatal($errmsg);
}
}
elsif (APT_Profile::AddPortalTag(\$rspecstr, $portal, $errmsg)) {
fatal($errmsg);
}
#
# Generate credentials we need.
......@@ -886,6 +883,22 @@ if ($instance->ApplyExtensionPolicies()) {
fatal("Error applying policies");
}
#
# Tell the CM the portal information, which is used by Emulab based
# CMs to send email links.
#
# Special: This also tells the CM to do normal NFS mounts if this is
# the "Emulab" portal making the request.
#
# XXX The local instance will not have these tags, but no big deal.
# XXX Need to handle this differently if we use the stitcher?
#
if (APT_Profile::AddPortalTag(\$rspecstr,
$portal, $instance->webURL(), \$errmsg)) {
$instance->Delete();
fatal($errmsg);
}
#
# Get the set of keys (accounts) that need to be sent along. We build
# them in CM format, but convert to AM format later if needed.
......
......@@ -279,8 +279,8 @@ sub DoCreate()
if ($size < 0) {
fatal("Could not parse size.");
}
if ($size <= 1) {
fatal("Size too small, try a little bigger");
if ($size <= 5) {
fatal("Size too small; minimum is 5MiB");
}
}
else {
......@@ -1345,8 +1345,14 @@ sub ExitWithError($)
$webtask->Exited($code);
}
print STDERR "*** $0:\n".
" $mesg\n";
exit(1);
" $mesg\n";
# Hmm, the apt_daemon cares about the response code, which is
# fine except that some response codes are too big. Damn.
if ($code > 255) {
$code = 1;
}
exit($code);
}
fatal($mesg);
}
......
......@@ -221,6 +221,8 @@ sub DoListImages()
if (!defined($authority));
# URN without the version.
$urn = GeniHRN::GenerateImage($auth,$ospid,$os,undef);
# Put it into the object so that PHP/JS code can find it easy.
$image->{'imagename'} = $os;
# Default to version zero, for old sites not reporting version.
my $vers = (defined($osvers) ? $osvers : 0);
......@@ -258,9 +260,16 @@ sub DoListImages()
#
if (exists($image0->{'project_urn'})) {
my $projhrn = GeniHRN->new($image0->{'project_urn'});
if ($projhrn->domain() eq $OURDOMAIN &&
defined($projhrn->project())) {
my $project = Project->Lookup($projhrn->project());
if ($projhrn->domain() eq $OURDOMAIN) {
my $project;
if (defined($projhrn->project())) {
$project = Project->Lookup($projhrn->project());
}
else {
# Backwards compat; we did not always send project urns.
$project = Project->Lookup($image0->{'pid'});
}
if (defined($project)) {
$ref->{'pid'} = $project->pid();
$ref->{'pid_idx'} = $project->pid_idx();
......@@ -271,6 +280,7 @@ sub DoListImages()
# Remote pid, set above
$ref->{'pid'} = $image0->{'pid'};
}
$ref->{'imagename'} = $image0->{'imagename'};
#
# Find profiles using the named image
......@@ -322,11 +332,18 @@ sub DoListImages()
while (@versions) {
my $image = shift(@versions);
my $urn = $image->{'urn'};
my $hrn = GeniHRN->new($urn);
my @using = ();
$image->{'using'} = [];
$image->{'candelete'} = 0;
$image->{'deleted'} = 0;
my (undef, undef, undef, $osvers) = $hrn->ParseImage();
# Default to version zero, for old sites not reporting version.
my $vers = (defined($osvers) ? $osvers : 0);
# Put it into the object so that PHP/JS code can find it easy.
$image->{'version'} = int($vers);
next
if (APT_Profile::ImageInfo::FindProfilesUsing($urn, \@using));
......@@ -395,7 +412,7 @@ sub DoDeleteImage()
print STDERR "Usage: manage_images delete [-a am_urn] <image_urn>\n";
exit(-1);
};
my $optlist = "a:d:n";
my $optlist = "a:d:v:n";
my $aggregate_urn = $MYURN;
my $impotent = 0;
my $profile;
......@@ -422,64 +439,24 @@ sub DoDeleteImage()
if ($profile->isLocked()) {
fatal("Profile is locked down, cannot be deleted");
}
#
# This argument says; delete any version of the specified
# profile, that reference the image being deleted. So we
# have to go through every version of the profile and check
# to see if its using this image. For any of those versions,
# we try to delete it.
# The caller tells us what versions of the image to delete,
#
if (!exists($options{"v"})) {
fatal("Missing version number list");
}
if ($options{"v"} !~ /^[\d,]+$/) {
fatal("Version number list should be comma separated integers");
}
my @todelete = ();
foreach my $version ($profile->AllVersions()) {
my $usingimage = 0;
my $conflict;
#
# Check image references for this version. We want to
# know if there are any other images associated with this
# version beside the one we are trying to delete. If so,
# we cannot delete the profile version since that will
# result in another image getting deleted.
#
my %irefs = %{ $version->images() };
foreach my $client_id (keys(%irefs)) {
my $imageinfo = $irefs{$client_id};
# We do not ever care about system images.
next
if ($imageinfo->ospid() eq "emulab-ops");
# The image we are trying to delete is okay
if ($imageinfo->image() eq $image_urn) {
$usingimage = 1;
next;
}
my $snapname = $profile->name() . "." . $client_id;
if ($imageinfo->os() eq $profile->name() ||
$imageinfo->os() eq $snapname) {
$conflict = $imageinfo;
}
}
if ($usingimage && $conflict) {
my $mesg =
"Version " . $version->version() . " of the " .
$version->name() . " profile has another ".
"image that would be deleted as well: ".
$conflict->image() . ". ".
"You will need to go to the profile page and delete ".
"that profile version before you can delete this image.";
foreach my $versnum (split(",", $options{"v"})) {
my $version = APT_Profile->Lookup($profile->profileid(), $versnum);
next
if (!defined($version));
if ($webtask) {
$webtask->reason("conflict");
$webtask->profile($version->uuid());
$webtask->image($conflict->image());
}
UserError($mesg);
}
if ($usingimage && $version->isLocked()) {
if ($version->isLocked()) {
my $mesg =
"Version " . $version->version() . " of the " .
$version->name() . " profile is locked down, ".
......@@ -491,11 +468,42 @@ sub DoDeleteImage()
}
UserError($mesg);
}
if ($usingimage) {
push(@todelete, $version);
print "Would delete version " . $version->version() .
" of profile " . $profile->name() . "\n";
#
# Check image references for this version. We want to
# know if there are any other images associated with this
# version beside the one we are trying to delete. If so,
# we cannot delete the profile version since that will
# result in another image getting deleted.
#
my %irefs = %{ $version->images() };
if (keys(%irefs) > 1) {
foreach my $client_id (keys(%irefs)) {
my $imageinfo = $irefs{$client_id};
# We do not ever care about system images.
next
if ($imageinfo->ospid() eq "emulab-ops");
my $mesg =
"Version " . $version->version() . " of the " .
$version->name() . " profile is using multiple ".
"images. As a safety measure, we require that you ".
"delete or edit that profile before you can delete ".
"this image.";
if ($webtask) {
$webtask->reason("conflict");
$webtask->profile($version->uuid());
$webtask->image($imageinfo->image());
}
UserError($mesg);
}
}
print "Would delete version " . $version->version() .
" of profile " . $profile->name() . "\n";
push(@todelete, $version);
}
foreach my $version (@todelete) {
my $vers = $version->version();
......
......@@ -104,6 +104,7 @@ use Project;
use APT_Profile;
use APT_Instance;
use APT_Geni;
use APT_Utility;
use GeniXML;
use GeniHRN;
use Genixmlrpc;
......@@ -144,6 +145,7 @@ sub WriteCredentials();
sub StartMonitor();
sub StartMonitorInternal(;$);
sub DoImageTrackerStuff($$$$$$$);
sub DoDestroy();
sub DenyExtensionInternal($);
sub ExtendInternal($$$$$);
sub CallMethodOnAggregates($$$@);
......@@ -222,6 +224,9 @@ elsif ($action eq "consoleurl") {
elsif ($action eq "terminate") {
DoTerminate()
}
elsif ($action eq "destroy") {
DoDestroy()
}
elsif ($action eq "schedterminate") {
DoSchedTerminate()
}
......@@ -294,6 +299,7 @@ sub DoSnapshot()
my $old_status = $instance->status();
my $node_id;
my $imagename;
my $description;
my $cloneprofile;
my $update_profile;
my $copyback_uuid;
......@@ -307,7 +313,7 @@ sub DoSnapshot()
my $usetracker = 0;
my $operation = "image-only"; # Default to just snapshot.
my $optlist = "n:i:u:Uc:O:Sse";
my $optlist = "n:i:u:Uc:O:SseD:";
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
......@@ -330,6 +336,9 @@ sub DoSnapshot()
if (defined($options{"U"})) {
$update_prepare = 1;
}
if (defined($options{"D"})) {
$description = ReadFile($options{"D"});
}
if (defined($options{"s"})) {
$nosnapshot = 1;
}
......@@ -599,12 +608,14 @@ sub DoSnapshot()
my $response =
$aggregate->CreateImage($sliver_urn, $imagename,
$update_prepare, $copyback_uuid,
undef, $nosnapshot, $mustnotexist, $wholedisk);
undef, $nosnapshot, $mustnotexist, $wholedisk,
$description);
if ($response->code() != GENIRESPONSE_SUCCESS) {
$errcode = $response->code();
($exitcode,$errmsg) = ResponseErrorMessage($aggregate, $response);
# Important to tell web user about these.
if ($response->code() == GENIRESPONSE_NOSPACE ||
$response->code() == GENIRESPONSE_FORBIDDEN ||
$response->code() == GENIRESPONSE_ALREADYEXISTS) {
$exitcode = 1;
}
......@@ -928,7 +939,7 @@ sub DoSnapshot()
$slice->UnLock()
if ($needunlock);
exit($errcode);
exit($exitcode);
}
sub DoImageTrackerStuff($$$$$$$)
......@@ -1388,6 +1399,40 @@ sub DoTerminate()
exit($exitcode);
}
#
# Destroy. Do not use this!
#
sub DoDestroy()
{
my $expired = $RECORDHISTORY_TERMINATED;
if (@ARGV) {
my $arg = shift(@ARGV);
if ($arg eq "-e") {
$expired = $RECORDHISTORY_EXPIRED;
}
else {
usage();
}
}
my $slice = $instance->GetGeniSlice();
if (!defined($slice)) {
#
# No slice (typically) means we never got far enough to the
# get the sliver created on the backend cluster.
#
goto killit;
}
$instance->SetStatus("terminated");
$slice->Delete();
$instance->RecordHistory($expired);
killit:
$instance->Delete();
exit(0);
}
#
# Request an extension; all this code used to be in PHP, that was silly.
#
......@@ -1955,10 +2000,18 @@ sub DoMaxExtension()
goto bad;
}
if ($debug) {
print "Max extension: " . TBDateStringLocal($result) . "\n";
print "Max extension: " .
TBDateStringLocal($result->{'maxextension'}) . "\n";
if (exists($result->{'reservations'})) {
if (keys(%{$result->{'reservations'}})) {
print Dumper($result->{'reservations'});
}
}
}
if (defined($webtask)) {
$webtask->MaxExtension(TBDateStringGMT($result));
$webtask->MaxExtension(TBDateStringGMT($result->{'maxextension'}));
$webtask->Reservations($result->{'reservations'})
if (exists($result->{'reservations'}));
$webtask->Exited(0);
}
exit(0);
......@@ -1968,6 +2021,9 @@ sub DoMaxExtension()
$webtask->output($errmsg) if (defined($errmsg));
$webtask->Exited($errcode);
}
if ($errcode < 0) {
print STDERR "\n\n" . $instance->webURL() . "\n";
}
exit($errcode);
}
......@@ -1978,6 +2034,7 @@ sub DoMaxExtensionInternal($$)
my $maxinfo;
my $errmsg;
my $newmax;
my $blob = {"maxextension" => undef, "reservations" => {}};
my @aggregates = ();
foreach my $aggregate ($instance->AggregateList()) {
......@@ -2001,15 +2058,17 @@ sub DoMaxExtensionInternal($$)
# Process the max extension from each aggregate
foreach my $aggregate (@aggregates) {
my $response = shift(@{$responses});
my $aptagg = $aggregate->GetAptAggregate();
my $result = $response->value();
my $code = $response->code();
my $reslist = {};
my $max;
if ($code) {
if ($code == GENIRESPONSE_REFUSED) {
# We want the user to see REFUSED.
$errmsg = "No extension possible at ".
$aggregate->GetAptAggregate()->name() . ": " .
$response->error();
$aptagg->name() . ": " . $response->error();
$errcode = $code;
}
else {
......@@ -2018,8 +2077,15 @@ sub DoMaxExtensionInternal($$)
}
goto bad;
}
my $max = str2time($result);
if (ref($result)) {
$max = $result->{'maxextension'};
$reslist = $result->{'reservations'};
}
else {
$max = $result;
}
$max = str2time($max);
if ($debug) {
print "$aggregate: $result, $max\n";
}
......@@ -2027,8 +2093,57 @@ sub DoMaxExtensionInternal($$)
if (!defined($newmax));
$newmax = $max
if ($max < $newmax);
# Map project/user to local users (if appropriate).
foreach my $res (values(%{$reslist})) {
my $project = APT_Utility::MapProjectURN($res->{'project'});
if (defined($project)) {
$res->{'remote_pid'} = $res->{'pid'};
$res->{'pid'} = $project->pid();
$res->{'pid_idx'} = $project->pid_idx();
}
else {
$res->{'remote_pid'} = $res->{'pid'};
}
my $geniuser = APT_Utility::MapUserURN($res->{'user'});
if (defined($geniuser)) {
$res->{'remote_uid'} = $res->{'uid'};
$res->{'uid'} = $geniuser->uid();
$res->{'uid_idx'} = $geniuser->uid_idx();
}
else {
$res->{'remote_uid'} = $res->{'uid'};
}
#
# Add these for the web interface since we are already messing
# with the results.
#
$res->{'cluster'} = $aptagg->urn();
$res->{'cluster_id'} = $aptagg->nickname();
# We need numbers.
$res->{'nodes'} = int($res->{'nodes'});
$res->{'using'} = int($res->{'using'});
#
# Hmm, undef/null is a pain with XMLRPC.
#
foreach my $key ("approved", "cancel", "deleted") {
if (!exists($res->{$key}) || $res->{$key} eq "") {
$res->{$key} = undef;
}
}
#
# Lets calculate a utilization numbers, this will update the
# res hash. Flag says not an active reservation.
#
APT_Utility::ReservationUtilization($res, 0);
}
$blob->{'reservations'}->{$aptagg->urn()} = $reslist;
}
$$prval = $newmax;
$blob->{'maxextension'} = $newmax;
$$prval = $blob;
return 0;
bad:
......@@ -4191,10 +4306,15 @@ sub fatal($)
if (defined($webtask)) {
$webtask->output($mesg);
$webtask->code(-1);
$webtask->Exited(-1);
}
print STDERR "*** $0:\n".
" $mesg\n";
# Helpful.
if (defined($instance)) {
print STDERR "\n\n" . $instance->webURL() . "\n";
}
# Exit with negative status so web interface treats it as system error.
exit(-1);
}
......@@ -4205,7 +4325,7 @@ sub UserError($)
if (defined($webtask)) {
$webtask->output($mesg);
$webtask->code(1);
$webtask->Exited(1);
}
print STDERR "*** $0:\n".
" $mesg\n";
......
This diff is collapsed.
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
# Copyright (c) 2000-2018 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -201,6 +201,45 @@ sub GenerateNodeStatements($)
$node->addTagStatement("InstantiateOn('$vhost')");
}
}
elsif ($ntype eq "emulab-docker") {
$node->addStatement("$ntag = request.DockerContainer('$client_id')");
#
# This is the only time we need to spit this out, since
# the default is False.
#
if (defined($node->{'exclusive'}) && $node->{'exclusive'}) {
$node->addTagStatement("exclusive = True");
}
if (defined($node->{'docker_settings'})) {
my $settings = $node->{'docker_settings'};
foreach my $setting (sort(keys(%{$settings}))) {
my $value = $settings->{$setting};
if ($setting eq "ram") {
$node->addTagStatement("ram = $value");
}
elsif ($setting eq "cores") {
$node->addTagStatement("cores = $value");
}
elsif ($setting eq "extimage") {
$node->addTagStatement("docker_extimage = $value");