Commit 2a3fc590 authored by Leigh B Stoller's avatar Leigh B Stoller

Various changes to allow image versions to be deleted. Note that

the version does not have to be the head version; any version can
now be deleted. We mark the DB row as deleted and delete the image
file when the purge option is given (which is most of the time).
parent 376b87da
......@@ -825,6 +825,11 @@ sub NewVersion($$$$)
my $ostablename = "os_info_versions" . $tableid;
my $imtablename = "image_versions" . $tableid;
if ($self->IsNewest()) {
print STDERR "Image::NewVersion: $self is not the head version\n";
return undef;
}
#
# Grab the current type list. Do this before table locking.
#
......@@ -942,7 +947,7 @@ sub NewVersion($$$$)
if (!$isdataset) {
DBQueryWarn("update $ostablename set ".
" uuid=uuid(), ".
" uuid=uuid(),deleted=null, ".
" vers='$clone_vers',".
" parent_osid=$parent_imageid,".
" parent_vers=$parent_version ".
......@@ -950,7 +955,7 @@ sub NewVersion($$$$)
or goto bad;
}
DBQueryWarn("update $imtablename set ".
" uuid=uuid(),ready=0,path='$path',released=0, ".
" uuid=uuid(),ready=0,path='$path',released=0,deleted=null, ".
" $part_vers default_vers='$clone_vers', ".
" version='$clone_vers',last_used=NULL, ".
" created=now(),nodetypes='$typelist', ".
......@@ -1010,6 +1015,8 @@ sub SetProvenance($$)
# Highest number version, rather then what is deemed most recent by the
# images table.
#
# We must include deleted images here.
#
sub LookupMostRecent($)
{
my ($self) = @_;
......@@ -1029,15 +1036,18 @@ sub LookupMostRecent($)
#
# Return a list of all image versions.
#
sub AllVersions($$)
sub AllVersions($$;$)
{
my ($self, $pref) = @_;
my ($self, $pref, $deleted) = @_;
my @result = ();
my $imageid = $self->imageid();
$deleted = 0 if (!defined($deleted));
my $query_result =
DBQueryWarn("select version from image_versions ".
"where imageid='$imageid' order by version desc");
"where imageid='$imageid' ".
($deleted ? "" : "and deleted is null ") .
"order by version desc");
return -1
if (!$query_result);
......@@ -1412,10 +1422,60 @@ sub Delete($;$)
}
#
# Delete a version of an image. This is not to be used, except when there
# is an error during clone, and we want to undo the creation of a new version.
# Mark a version as deleted. Since we are allowing the head version to
# be deleted, we might have to reset the current image/osinfo pointers.
#
sub DeleteVersion($)
{
my ($self) = @_;
my $imageid = $self->imageid();
my $version = $self->version();
DBQueryWarn("lock tables images write, image_versions write, ".
" os_info write, os_info_versions write")
or return -1;
#
# Find the new head version; the highest numbered version that
# is not deleted,
#
my $query_result =
DBQueryWarn("select max(version) from image_versions ".
"where imageid='$imageid' and deleted is null and ".
" version!='$version'");
goto bad
if (!$query_result);
if (!$query_result->numrows) {
print STDERR "Image::DeleteVersion: Cannot find a new head version\n";
goto bad;
}
my ($head) = $query_result->fetchrow_array();
goto bad
if (! (DBQueryWarn("update image_versions set deleted=now() ".
"where imageid='$imageid' and version='$version'") &&
DBQueryWarn("update os_info_versions set deleted=now() ".
"where osid='$imageid' and vers='$version'") &&
DBQueryWarn("update os_info set version='$head' ".
"where osid='$imageid'") &&
DBQueryWarn("update images set version='$head' ".
"where imageid='$imageid'")));
DBQueryWarn("unlock tables")
or return -1;
return 0;
bad:
DBQueryWarn("unlock tables");
return -1;
}
#
# Purge a version of an image. This is not to be used, except when there
# is an error during clone, and we want to undo the creation of a new version.
#
sub PurgeVersion($)
{
my ($self) = @_;
......@@ -1426,7 +1486,7 @@ sub DeleteVersion($)
or return -1;
#
# We do not allow the deletion if it is not the "head" version of the
# We do not allow the purge if it is not the "head" version of the
# image_versions. Maybe later.
#
my $query_result =
......@@ -1437,7 +1497,7 @@ sub DeleteVersion($)
my ($version) = $query_result->fetchrow_array();
if ($version != $self->version()) {
print STDERR "Image::DeleteVersion: not the head version of $self\n";
print STDERR "Image::PurgeVersion: not the head version of $self\n";
goto bad;
}
......@@ -1498,6 +1558,23 @@ sub GetGroup($)
return $group;
}
#
# Load the creator object for an image
#
sub GetCreator($)
{
my ($self) = @_;
require User;
my $user = User->Lookup($self->creator_idx());
if (! defined($user)) {
print("*** WARNING: Could not lookup user object for $self!\n");
return undef;
}
return $user;
}
#
# Check permissions. Note that root may ask permission, which comes in
# as an undef user.
......@@ -2355,12 +2432,14 @@ sub ListForURN($$)
if (! GeniHRN::IsValid($urn));
my $query_result =
DBQueryWarn("select distinct pid,imagename from image_versions ".
"where creator_urn='$urn' and deleted is null");
DBQueryWarn("select imageid,version from image_versions ".
"where creator_urn='$urn' and deleted is null and ".
" isdataset=0 ".
"order by pid,imagename,version");
while (my ($pid,$imagename) = $query_result->fetchrow_array()) {
while (my ($imageid,$version) = $query_result->fetchrow_array()) {
# Want latest version.
my $image = Image->Lookup($pid, $imagename);
my $image = Image->Lookup($imageid, $version);
next
if (!defined($image));
......
......@@ -458,15 +458,15 @@ sub LookupMostRecent($)
#
# Return a list of all image versions.
#
sub AllVersions($$)
sub AllVersions($$;$)
{
my ($self, $pref) = @_;
my ($self, $pref, $deleted) = @_;
my $imageid = $self->image()->imageid();
my @images = ();
my @result = ();
return -1
if ($self->image()->AllVersions(\@images));
if ($self->image()->AllVersions(\@images, $deleted));
foreach my $image (@images) {
my $version = $image->version();
......@@ -580,6 +580,16 @@ sub GetProject($)
return $self->image()->GetProject();
}
#
# Load the user object for an image
#
sub GetCreator($)
{
my ($self) = @_;
return $self->image()->GetCreator();
}
#
# Load the group object for an image
#
......
......@@ -380,7 +380,7 @@ if (defined($image)) {
$path .= "/";
}
if ($image->Update({"path" => $path})) {
$image->DeleteVersion();
$image->PurgeVersion();
fatal("Could not update path!");
}
}
......@@ -427,7 +427,7 @@ if (defined($image)) {
my $output = emutil::ExecQuiet("$CREATEIMAGE $opts $imagename $node_id");
if ($?) {
$image->DeleteVersion()
$image->PurgeVersion()
if ($needdelete);
print STDERR $output;
fatal("Failed to create image");
......
......@@ -38,10 +38,11 @@ sub usage()
" -r Rename the disk image file(s) instead (default)\n".
" -n Impotent mode, show what would be done.\n".
" -f Force deletion of system image\n".
" -V Delete only the version\n".
" -F Force deletion of global system image\n");
exit(-1);
}
my $optlist = "dFprnf";
my $optlist = "dFprnfV";
my $debug = 0;
my $purge = 0;
my $rename = 1;
......@@ -49,6 +50,7 @@ my $force = 0;
my $FORCE = 0;
my $impotent = 0;
my $needunlock = 0;
my $versonly = 0;
#
# Configure variables
......@@ -88,7 +90,7 @@ use EmulabFeatures;
use libEmulab;
use libtestbed;
use User;
use Image;
use OSImage;
# Protos
sub fatal($);
......@@ -114,6 +116,9 @@ if (defined($options{"r"})) {
if (defined($options{"n"})) {
$impotent = 1;
}
if (defined($options{"V"})) {
$versonly = 1;
}
if (defined($options{"F"})) {
$FORCE = 1;
}
......@@ -123,7 +128,7 @@ if (defined($options{"f"})) {
usage()
if (@ARGV != 1);
usage()
if ($purge && $rename);
if (! ($purge || $rename));
my $imageid = shift(@ARGV);
......@@ -134,7 +139,7 @@ my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
my $image = Image->Lookup($imageid);
my $image = OSImage->Lookup($imageid);
if (!defined($image)) {
fatal("Image does not exist in the DB!");
}
......@@ -159,6 +164,12 @@ if (!$impotent) {
$imageid = $image->imageid();
my $imagename = $image->imagename();
my $imagepid = $image->pid();
my $imagevers = $image->version();
# Sanity check; cannot delete a deleted version.
if ($versonly && defined($image->deleted())) {
fatal("Image version is already deleted");
}
#
# Need root to delete the image file later.
......@@ -169,9 +180,11 @@ $EUID = $UID;
# Be sure to kill off running frisbee. If a node is trying to load that
# image, well tough.
#
system("$friskiller -k $imageid");
if ($?) {
fatal("Could not kill running frisbee for $imageid!");
if (!$impotent) {
system("$friskiller -k $imageid");
if ($?) {
fatal("Could not kill running frisbee for $imageid!");
}
}
#
......@@ -198,17 +211,29 @@ if ($purge || $rename) {
#
# When doing image provenance, we have to deal with all versions
# of the image.
# of the image. This will not return deleted versions.
#
my @images = ();
if ($image->AllVersions(\@images)) {
fatal("Could not get list of image (versions)");
}
#
# When deleting just a single version, if this is the last or only
# version, then turn off version only. Makes no sense to have a
# descriptor with no non-deleted versions.
#
if ($versonly && scalar(@images) == 1) {
$versonly = 0;
}
if ($versonly) {
@images = ($image);
}
#
# If the path is a directory, we can just do a rename on it.
# But not if deleting just a single image version.
#
if ($isdirpath) {
if ($isdirpath && !$versonly) {
my $dirname = $image->path();
if ($purge) {
......@@ -284,7 +309,7 @@ if ($purge || $rename) {
print "Would kill version $vers that never came ready\n";
next;
}
$imageversion->DeleteVersion();
$imageversion->PurgeVersion();
}
$EUID = 0;
......@@ -305,7 +330,10 @@ if ($purge || $rename) {
$EUID = $UID;
#
# Skip this part if we did a directory rename above.
# Skip renames for directory based images.
# Note that when deleting a single version in an image directory,
# we do not want to do a rename. That would be confusing if some
# versions were deleted and then the entire image deleted later.
#
next
if ($isdirpath);
......@@ -320,8 +348,11 @@ if ($purge || $rename) {
my $dirname = dirname($imageversion->path()) .
"/" . $image->imagename() . "," . $image->imageid();
if (! -e $dirname && !$impotent) {
if (! mkdir("$dirname", 0775)) {
if (! -e $dirname) {
if ($impotent) {
print "Would mkdir($dirname)\n";
}
elsif (! mkdir("$dirname", 0775)) {
fatal("Could not mkdir $dirname");
}
}
......@@ -353,18 +384,31 @@ exit(0)
#
# 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 (!$versonly) {
# Do this before delete().
if (GetSiteVar("protogeni/use_imagetracker")) {
$image->SchedIMSDeletion() == 0
or fatal("Could not schedule IMS deletion");
}
if ($image->Delete() != 0) {
fatal("Could not delete image!");
}
$this_user->SendEmail("delete_image: Image has been deleted",
"Image $imagepid,$imagename ($imageid) has ".
"been deleted by $this_user\n");
}
if ($image->Delete() != 0) {
fatal("Could not delete image!");
else {
if ($image->DeleteVersion() != 0) {
fatal("Could not delete image version!");
}
# I know, we are unlocking something we just deleted. Its okay, relax.
$image->Unlock();
$this_user->SendEmail("delete_image: Image Version has been deleted",
"Version $imagevers of image $imagepid,$imagename".
"($imageid)\nhas been deleted by $this_user\n");
}
$this_user->SendEmail("delete_image: Image has been deleted",
"Image $imagepid,$imagename ($imageid) has ".
"been deleted by $this_user\n");
exit(0);
sub fatal($)
{
my ($mesg) = @_;
......
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