Commit d1b9ac51 authored by Leigh B Stoller's avatar Leigh B Stoller

Various changes to support image deletion from the IMS when a cluster

deletes an image.
parent 9e27e737
......@@ -48,6 +48,7 @@ my $TBOPS = "@TBOPSEMAIL@";
my $TBAUDIT = "@TBAUDITEMAIL@";
my $TBBASE = "@TBBASE@";
my $WWWHOST = "@WWWHOST@";
my $OURDOMAIN = "@OURDOMAIN@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $DOPROVENANCE = @IMAGEPROVENANCE@;
my $TBWWW = "@TBWWW@";
......@@ -69,6 +70,17 @@ sub versname($)
return $self->pid() . "/" . $self->imagename() . ":" . $self->version();
}
sub urn($)
{
my ($self) = @_;
return undef
if (! $PGENISUPPORT);
require GeniHRN;
return GeniHRN::Generate($OURDOMAIN, "image",
$self->pid() . "//" . $self->imagename());
}
# Little helper and debug function.
sub mysystem($)
......@@ -2222,6 +2234,21 @@ sub SetRunsOnNodeType($$)
return 0;
}
#
# If using the image tracker, have to notify the IMS.
#
sub SchedIMSDeletion($)
{
my ($self) = @_;
my $urn = $self->urn();
my $uuid = $self->image_uuid();
# The expire daemon will pick this up.
DBQueryWarn("replace into image_deletions set ".
" urn='$urn',image_uuid='$uuid',deleted=now()");
}
#
# Is the image the newest version of the image.
#
......
......@@ -432,13 +432,44 @@ sub NewImage($)
{
my ($blob) = @_;
my $needdelete = 0;
my $image_urn = $blob->{'urn'};
my $image_uuid = $blob->{'image_uuid'};
my $version_uuid = $blob->{'version_uuid'};
my $safe_urn = DBQuoteSpecial($image_urn);
my $safe_uuid = DBQuoteSpecial($image_uuid);
#
# Create an entry in the images table if one does not exist.
# Okay, we have to deal with a image that was deleted at the origin
# cluster, and then recreated before we got the deletion message.
# To keep the logic simple, and because creating images takes a long
# time (so this will not happen often), we just assume that if the
# image uuid for a urn changes, the origin has done exactly that, and
# so we delete the existing image and all its versions, and start a
# new one.
#
my $query_result =
$DB->QueryWarn("select image_uuid from images ".
"where urn=$safe_urn and image_uuid!=$safe_uuid");
if (!$query_result) {
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
if ($query_result->numrows) {
print STDERR "Deleting existing image $image_urn with ".
"stale uuid $image_uuid\n";
$DB->QueryWarn("delete images,image_versions from images ".
"inner join image_versions on ".
" images.urn=image_versions.urn and ".
" images.image_uuid=image_versions.image_uuid ".
"where images.urn=$safe_urn and ".
" images.image_uuid=$safe_uuid")
or GeniResponse->Create(GENIRESPONSE_ERROR);
}
#
# Create an entry in the images table if one does not exist.
#
$query_result =
$DB->QueryWarn("select urn from images ".
"where image_uuid='$image_uuid'");
if (!$query_result) {
......@@ -577,7 +608,7 @@ sub GetImageInfo($)
if (defined($urn)) {
my $hrn = GeniHRN->new($urn);
return GeniResponse->MalformedArgsResponse("Could not parse URN")
if (!defined($urn));
if (!defined($hrn));
return GeniResponse->MalformedArgsResponse("Not an image URN")
if ($hrn->type() ne "image");
......@@ -688,5 +719,88 @@ sub GetImageInfo($)
return GeniResponse->Create(GENIRESPONSE_SUCCESS, $blob);
}
sub DeleteImageInfo($)
{
my ($argref) = @_;
my $cred = $argref->{'credential'};
my $urn = $argref->{'urn'};
my $uuid = $argref->{'uuid'};
my $query_result;
if (! defined($cred)) {
return GeniResponse->MalformedArgsResponse("Where's the credential?");
}
if (! (defined($urn) && defined($uuid))) {
return GeniResponse->MalformedArgsResponse("Missing urn or uuid args");
}
return GeniResponse->MalformedArgsResponse("Malformed URN")
if (!GeniHRN::IsValid($urn));
my $authority = GeniAuthority->Lookup($ENV{'MYURN'});
if (!defined($authority)) {
print STDERR "Could not find local authority object\n";
return GeniResponse->Create(GENIRESPONSE_ERROR);
}
my $credential = GeniCredential::CheckCredential($cred, $authority);
return $credential
if (GeniResponse::IsResponse($credential));
$credential->HasPrivilege("postimageinfo") or
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Insufficient permission to deleteimageinfo");
my $hrn = GeniHRN->new($urn);
return GeniResponse->MalformedArgsResponse("Could not parse URN")
if (!defined($hrn));
return GeniResponse->MalformedArgsResponse("Not an image URN")
if ($hrn->type() ne "image");
return GeniResponse->MalformedArgsResponse("Not a UUID")
if ($uuid !~ /^\w+\-\w+\-\w+\-\w+\-\w+$/);
#
# Verify that that the urn of the image is in the same domain as the
# the calling entitiy.
#
my $caller_hrn = GeniHRN->new($credential->owner_urn());
return GeniResponse->Create(GENIRESPONSE_FORBIDDEN, undef,
"Not allowed to deleteimageinfo for the image domain")
if ($hrn->domain() ne $caller_hrn->domain());
# The id might be version specific.
my (undef,$pid,$imagename,$version) = $hrn->ParseImage();
if (defined($version)) {
return GeniResponse->MalformedArgsResponse("Not allowed to delete ".
"an image version");
}
# IMS database handle.
InitDBHandle();
# This // vs : thing is a pain.
$urn =~ s/\/\//:/;
my $safe_urn = DBQuoteSpecial($urn);
#
# We use the uuid in this deletion so we do not delete a current
# image cause the deletion came in out of order with a new image
# creation (image created, deleted, created again).
#
$query_result =
$DB->QueryWarn("delete images,image_versions from images ".
"inner join image_versions on ".
" images.urn=image_versions.urn and ".
" images.image_uuid=image_versions.image_uuid ".
"where images.urn=$safe_urn and ".
" images.image_uuid='$uuid'");
return GeniResponse->Create(GENIRESPONSE_ERROR)
if (!defined($query_result));
return GeniResponse->Create(GENIRESPONSE_SUCCESS);
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -35,7 +35,7 @@ use Exporter;
use vars qw(@ISA @EXPORT);
@ISA = "Exporter";
@EXPORT = qw ( );
@EXPORT = qw (IMSURL);
use GeniDB;
use GeniCredential;
......@@ -61,10 +61,12 @@ my $TBAUDIT = "@TBAUDITEMAIL@";
my $BOSSNODE = "@BOSSNODE@";
my $OURDOMAIN = "@OURDOMAIN@";
my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
#my $IMSURL = "https://www.emulab.net:12369/protogeni/stoller/xmlrpc/ims";
my $IMSURL = "https://www.emulab.net:12369/protogeni/xmlrpc/ims";
my $CLIENTVERS = 1;
my $IMPORTER = "$TB/sbin/image_import";
if (0) {
$IMSURL = "https://www.emulab.net:12369/protogeni/stoller/xmlrpc/ims";
}
# Cached copy of credential to talk to IMS.
my $IMS_credential;
......@@ -72,6 +74,12 @@ my $IMS_credential;
# Debugging
my $usemydevtree = 0;
# Export.
sub IMSURL()
{
return $IMSURL;
}
#
# Get a credential to talk to the IMS.
#
......@@ -147,6 +155,64 @@ sub GetImageData($;$)
$response->value(), $errormsg);
}
#
# Tell the image server we have deleted an image we previously told
# it about. We send the uuid in addition to the URN to avoid consistency
# problems caused by out of order notifications to the server, say if
# an image is created, deleted, then created again. We always assign a
# unique uuid to the image, and the IMS has that. We could get fancier
# but this is a rare race condition.
#
sub DeleteImageData($$;$)
{
my ($urn, $uuid, $pmsg) = @_;
my $safe_urn = DBQuoteSpecial($urn);
my $errormsg;
my $credential = GetServerCredential();
if (!defined($credential)) {
$errormsg = "Could not get a credential to talk to the IMS";
goto bad;
}
my $response =
Genixmlrpc::CallMethod($IMSURL, undef, "DeleteImageInfo",
{"credential" => $credential->asString(),
"urn" => $urn,
"uuid" => $uuid});
if (!defined($response) || $response->code()) {
my $msg = "DeleteImageInfo failed for $urn/$uuid:";
if (!defined($response)) {
$errormsg = "$msg RPC error";
}
elsif ($response->code() == GENIRESPONSE_SEARCHFAILED) {
goto done;
}
elsif (defined($response->output())) {
$errormsg = "$msg " . $response->output();
}
else {
$errormsg = "$msg " . GENIRESPONSE_STRING($response->code());
}
goto bad;
}
#
# The remote cluster accepted, our responsibilty is done.
#
done:
emdb::DBQueryWarn("delete from image_deletions ".
"where urn=$safe_urn and image_uuid='$uuid'");
return 0;
bad:
if (defined($pmsg)) {
$$pmsg = $errormsg;
}
return -1;
}
#
# Map a URN to a local image. We do this by asking the tracker for
# the image info, then looking to see if we have that image locally,
......
......@@ -51,7 +51,8 @@ PSBIN_STUFF = register_resources expire_daemon gencrl postcrl \
maptoslice webmaptoslice setexpiration \
mondbd parsecert creategeniuser webcreategeniuser \
updategeniuser webupdategeniuser verifycert webverifycert \
postimagedata getimagedata triggerimageupdate
postimagedata getimagedata triggerimageupdate dumpcredential \
deleteimagedata
ifeq ($(ISCLEARINGHOUSE),1)
PSBIN_STUFF += ch_daemon
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2015 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
# GENI Public License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and/or hardware specification (the "Work") to
# deal in the Work without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Work, and to permit persons to whom the Work
# is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Work.
#
# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS
# IN THE WORK.
#
# }}}
#
use strict;
use English;
use Getopt::Std;
use Date::Parse;
use Data::Dumper;
#
# Delete image data from the image server.
#
sub usage()
{
print "Usage: deleteimagedata [-d] <image urn> <image uuid>\n";
exit(1);
}
my $optlist = "d";
my $debug = 0;
my $errormsg;
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
my $PGENIDOMAIN = "@PROTOGENI_DOMAIN@";
my $PGENISUPPORT = @PROTOGENI_SUPPORT@;
my $PGENILOCALUSER= @PROTOGENI_LOCALUSER@;
my $CMCERT = "$TB/etc/genicm.pem";
my $OURDOMAIN = "@OURDOMAIN@";
my $CLIENTVERS = 1;
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin:/usr/site/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Protos
sub fatal($);
#
# Turn off line buffering on output
#
$| = 1;
# Now we can load the libraries after setting the proper DB.
use lib '@prefix@/lib';
use libaudit;
use emutil;
use GeniDB;
use GeniCertificate;
use GeniAuthority;
use GeniImage;
use GeniHRN;
use libEmulab;
use Image;
use OSinfo;
if (!$PGENISUPPORT) {
print STDERR "You are not a protogeni site\n";
exit(0);
}
if (!GetSiteVar("protogeni/use_imagetracker")) {
print STDERR "Image tracking is not enabled for this site\n";
exit(0);
}
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
usage()
if (@ARGV != 2);
usage()
if (! GeniHRN::IsValid($ARGV[0]));
# Connect to the proper DB.
DBConnect(GENICM_DBNAME());
#
# Load the CM cert to act as caller context.
#
my $certificate = GeniCertificate->LoadFromFile($CMCERT);
if (!defined($certificate)) {
fatal("Could not load certificate from $CMCERT\n");
}
my $context = Genixmlrpc->Context($certificate);
if (!defined($context)) {
fatal("Could not create context to talk to image server");
}
Genixmlrpc->SetContext($context);
# Shorten default timeout.
Genixmlrpc->SetTimeout(15);
if (GeniImage::DeleteImageData($ARGV[0], $ARGV[1], \$errormsg)) {
fatal($errormsg);
}
exit(0);
sub fatal($)
{
my ($msg) = @_;
print STDERR "*** $0:\n".
" $msg\n";
# exit value important.
exit(-1);
}
......@@ -68,6 +68,7 @@ my $EMULAB_PEMFILE = "$TB/etc/genicm.pem";
my $REGISTER = "$TB/sbin/protogeni/register_sliver";
my $TRIGGERUPDATE = "$TB/sbin/protogeni/triggerimageupdate";
my $POSTIMAGEDATA = "$TB/sbin/protogeni/postimagedata";
my $DELETEIMAGEDATA = "$TB/sbin/protogeni/deleteimagedata";
my $IMPORTER = "$TB/sbin/image_import";
my $CLEANUPSLICE = "$TB/sbin/cleanupslice";
my $CLEANUPTICKET = "$TB/sbin/cleanupticket";
......@@ -1276,13 +1277,42 @@ sub HandleImageTracking()
if (!GetSiteVar("protogeni/use_imagetracker")) {
return;
}
#
# Look for images deletions.
#
my $query_result =
DBQueryWarn("select * from image_deletions");
return
if (!$query_result);
while (my $row = $query_result->fetchrow_hashref()) {
my $urn = $row->{'urn'};
my $uuid = $row->{'image_uuid'};
if ($impotent) {
print "Would trigger image delete for $urn\n";
next;
}
my $output = emutil::ExecQuiet("$DELETEIMAGEDATA '$urn' $uuid");
if ($?) {
print $output;
SENDMAIL($TBOPS,
"Could not delete image data at the IMS for $urn/$uuid",
$output,
$TBOPS);
next;
}
DBQueryWarn("delete from image_deletions ".
"where urn='$urn' and image_uuid='$uuid'");
}
#
# Look for images notifications; these are new local snapshots that need
# to be copied back to their origin, so we have to tell the origin of that
# image.
#
my $query_result =
$query_result =
DBQueryWarn("select * from image_notifications");
return
if (!$query_result);
......
......@@ -61,8 +61,6 @@ my $PGENILOCALUSER= @PROTOGENI_LOCALUSER@;
my $WITHPROVENANCE= @IMAGEPROVENANCE@;
my $CMCERT = "$TB/etc/genicm.pem";
my $OURDOMAIN = "@OURDOMAIN@";
#my $IMSURL = "https://www.emulab.net:12369/protogeni/stoller/xmlrpc/ims";
my $IMSURL = "https://www.emulab.net:12369/protogeni/xmlrpc/ims";
my $CLIENTVERS = 1;
# un-taint path
......@@ -96,6 +94,7 @@ use Group;
use OSinfo;
use emdb qw();
use EmulabFeatures;
use GeniImage;
$EmulabFeatures::verbose = 0;
my $TBOPSPID = TBOPSPID();
......@@ -423,7 +422,7 @@ sub PostImageInfo($)
# Push the blob to the image server.
#
my $response =
Genixmlrpc::CallMethod($IMSURL, undef, "PostImageInfo",
Genixmlrpc::CallMethod(IMSURL(), undef, "PostImageInfo",
{"credential" => $credential->asString(),
"imageinfo" => $blob});
if (!defined($response) || $response->code()) {
......@@ -459,7 +458,7 @@ sub GetServerCredential()
if (defined($IMS_credential));
my $response =
Genixmlrpc::CallMethod($IMSURL, undef, "GetCredential");
Genixmlrpc::CallMethod(IMSURL(), undef, "GetCredential");
if (!defined($response)) {
fatal("RPC error getting credential");
}
......
......@@ -47,6 +47,7 @@ $GENI_METHODS = {
"GetCredential" => \&GeniIMS::GetCredential,
"PostImageInfo" => \&GeniIMS::PostImageInfo,
"GetImageInfo" => \&GeniIMS::GetImageInfo,
"DeleteImageInfo" => \&GeniIMS::DeleteImageInfo,
};
1;
......
......@@ -2057,6 +2057,18 @@ CREATE TABLE `image_boot_status` (
KEY `stamp` (`stamp`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
---
--- Table structure for table `image_deletions`
---
DROP TABLE IF EXISTS `image_deletions`;
CREATE TABLE `image_deletions` (
`urn` varchar(128) default NULL,
`image_uuid` varchar(40) NOT NULL default '',
`deleted` datetime default NULL,
PRIMARY KEY (`image_uuid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `image_history`
--
......
use strict;
use libdb;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
if (!DBTableExists("image_deletions")) {
DBQueryFatal("CREATE TABLE `image_deletions` ( ".
" `urn` varchar(128) default NULL, ".
" `image_uuid` varchar(40) NOT NULL default '', ".
" `deleted` datetime default NULL, ".
" PRIMARY KEY (`image_uuid`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1");
}
return 0;
}
# Local Variables:
# mode:perl
# End:
......@@ -85,6 +85,7 @@ if ($EUID != 0) {
use lib "@prefix@/lib";
use EmulabConstants;
use EmulabFeatures;
use libEmulab;
use libtestbed;
use User;
use Image;
......@@ -348,6 +349,13 @@ if ($purge || $rename) {
exit(0)
if ($impotent);
#
# If using the image tracker, have to notify the IMS.
#
if (GetSiteVar("protogeni/use_imagetracker")) {
$image->SchedIMSDeletion() == 0
or fatal("Could not schedule IMS deletion");
}
if ($image->Delete() != 0) {
fatal("Could not delete image!");
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment