]> git.proxmox.com Git - pve-storage.git/commitdiff
fix #3307: make it possible to set protection for backups
authorFabian Ebner <f.ebner@proxmox.com>
Thu, 30 Sep 2021 11:42:08 +0000 (13:42 +0200)
committerFabian Grünbichler <f.gruenbichler@proxmox.com>
Mon, 8 Nov 2021 13:56:15 +0000 (14:56 +0100)
A protected backup is not removed by free_image and ignored when
pruning.

The protection_file_path function is introduced in Storage.pm, so that
it can also be used by vzdump itself and in archive_remove.

For pruning, renamed backups already behaved similiar to how protected
backups will, but there are a few reasons to not just use that for
implementing the new feature:
1. It wouldn't protect against removal.
2. It would make it necessary to rename notes and log files too.
3. It wouldn't naturally extend to other volumes if that's needed.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
PVE/API2/Storage/Content.pm
PVE/Storage.pm
PVE/Storage/DirPlugin.pm
PVE/Storage/Plugin.pm
test/prune_backups_test.pm

index b3dc593c08321758d51948f3ff5a1a659aa259ae..45b8de836a211f6ea5ddf6787ae7bac855044bff 100644 (file)
@@ -113,6 +113,11 @@ __PACKAGE__->register_method ({
                    },
                    optional => 1,
                },
+               protected => {
+                   description => "Protection status. Currently only supported for backups.",
+                   type => 'boolean',
+                   optional => 1,
+               },
            },
        },
        links => [ { rel => 'child', href => "{volid}" } ],
@@ -295,7 +300,12 @@ __PACKAGE__->register_method ({
                description => "Optional notes.",
                optional => 1,
                type => 'string',
-           }
+           },
+           protected => {
+               description => "Protection status. Currently only supported for backups.",
+               type => 'boolean',
+               optional => 1,
+           },
        },
     },
     code => sub {
@@ -321,12 +331,14 @@ __PACKAGE__->register_method ({
            format => $format,
        };
 
-       # keep going if fetching an optional attribute fails
-       eval {
-           my $notes = PVE::Storage::get_volume_attribute($cfg, $volid, 'notes');
-           $entry->{notes} = $notes if defined($notes);
-       };
-       warn $@ if $@;
+       for my $attribute (qw(notes protected)) {
+           # keep going if fetching an optional attribute fails
+           eval {
+               my $value = PVE::Storage::get_volume_attribute($cfg, $volid, $attribute);
+               $entry->{$attribute} = $value if defined($value);
+           };
+           warn $@ if $@;
+       }
 
        return $entry;
     }});
@@ -356,6 +368,11 @@ __PACKAGE__->register_method ({
                type => 'string',
                optional => 1,
            },
+           protected => {
+               description => "Protection status. Currently only supported for backups.",
+               type => 'boolean',
+               optional => 1,
+           },
        },
     },
     returns => { type => 'null' },
@@ -371,8 +388,10 @@ __PACKAGE__->register_method ({
 
        PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $volid);
 
-       if (exists $param->{notes}) {
-           PVE::Storage::update_volume_attribute($cfg, $volid, 'notes', $param->{notes});
+       for my $attr (qw(notes protected)) {
+           if (exists $param->{$attr}) {
+               PVE::Storage::update_volume_attribute($cfg, $volid, $attr, $param->{$attr});
+           }
        }
 
        return undef;
index 223fbb5b4e94cfc6fe3823507dd234c116012095..fdc21d9128281eb2c137fc5bb402ec6f16ccf18f 100755 (executable)
@@ -1456,6 +1456,12 @@ sub decompressor_info {
     return $info;
 }
 
+sub protection_file_path {
+    my ($path) = @_;
+
+    return "${path}.protected";
+}
+
 sub archive_info {
     my ($archive) = shift;
     my $info;
@@ -1487,6 +1493,9 @@ sub archive_info {
 sub archive_remove {
     my ($archive_path) = @_;
 
+    die "cannot remove protected archive '$archive_path'\n"
+       if -e protection_file_path($archive_path);
+
     my $dirname = dirname($archive_path);
     my $archive_info = eval { archive_info($archive_path) } // {};
     my $logfn = $archive_info->{logfilename};
index 2cb55b832f23c0363e2651ece4d46aa589d25332..c60818b7504737ae10628127201b1f5861faabf3 100644 (file)
@@ -5,6 +5,7 @@ use warnings;
 
 use Cwd;
 use File::Path;
+use IO::File;
 use POSIX;
 
 use PVE::Storage::Plugin;
@@ -133,6 +134,14 @@ sub get_volume_attribute {
        return $class->get_volume_notes($scfg, $storeid, $volname);
     }
 
+    my ($vtype) = $class->parse_volname($volname);
+    return if $vtype ne 'backup';
+
+    if ($attribute eq 'protected') {
+       my $path = $class->filesystem_path($scfg, $volname);
+       return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
+    }
+
     return;
 }
 
@@ -143,6 +152,27 @@ sub update_volume_attribute {
        return $class->update_volume_notes($scfg, $storeid, $volname, $value);
     }
 
+    my ($vtype) = $class->parse_volname($volname);
+    die "only backups support attribute '$attribute'\n" if $vtype ne 'backup';
+
+    if ($attribute eq 'protected') {
+       my $path = $class->filesystem_path($scfg, $volname);
+       my $protection_path = PVE::Storage::protection_file_path($path);
+
+       return if !((-e $protection_path) xor $value); # protection status already correct
+
+       if ($value) {
+           my $fh = IO::File->new($protection_path, O_CREAT, 0644)
+               or die "unable to create protection file '$protection_path' - $!\n";
+           close($fh);
+       } else {
+           unlink $protection_path or $! == ENOENT
+               or die "could not delete protection file '$protection_path' - $!\n";
+       }
+
+       return;
+    }
+
     die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
 }
 
index 84de7855fcca68713593fa64c9805e868297a987..409a05cbf8ef328d73135a69f00a2b21c198133a 100644 (file)
@@ -828,6 +828,9 @@ sub alloc_image {
 sub free_image {
     my ($class, $storeid, $scfg, $volname, $isBase, $format) = @_;
 
+    die "cannot remove protected volume '$volname' on '$storeid'\n"
+       if $class->get_volume_attribute($scfg, $storeid, $volname, 'protected');
+
     my $path = $class->filesystem_path($scfg, $volname);
 
     if ($isBase) {
@@ -917,6 +920,7 @@ sub update_volume_notes {
 # Should die if there is an error fetching the attribute.
 # Possible attributes:
 # notes     - user-provided comments/notes.
+# protected - not to be removed by free_image, and for backups, ignored when pruning.
 sub get_volume_attribute {
     my ($class, $scfg, $storeid, $volname, $attribute) = @_;
 
@@ -1164,6 +1168,7 @@ my $get_subdir_files = sub {
                $info->{notes} = $notes if defined($notes);
            }
 
+           $info->{protected} = 1 if -e PVE::Storage::protection_file_path($original);
        } elsif ($tt eq 'snippets') {
 
            $info = {
@@ -1370,6 +1375,8 @@ sub prune_backups {
            $prune_entry->{mark} = 'protected';
        }
 
+       $prune_entry->{mark} = 'protected' if $backup->{protected};
+
        push @{$prune_list}, $prune_entry;
     }
 
index a87c4b3b38908c3231407a44a4417c60cdf366ff..8ad61449cdfa02f23f907316d47daa6954a48ea4 100644 (file)
@@ -29,6 +29,12 @@ foreach my $vmid (@vmids) {
            'ctime' => $basetime - 24*60*60 - 60*60,
            'vmid'  => $vmid,
        },
+       {
+           'volid' => "$storeid:backup/vzdump-qemu-$vmid-2019_12_31-11_18_51.tar.zst",
+           'ctime' => $basetime - 24*60*60 - 60*60 + 30,
+           'vmid'  => $vmid,
+           'protected' => 1,
+       },
        {
            'volid' => "$storeid:backup/vzdump-qemu-$vmid-2019_12_31-11_19_21.tar.zst",
            'ctime' => $basetime - 24*60*60 - 60*60 + 60,
@@ -140,6 +146,13 @@ sub generate_expected {
                'mark'  => $marks->[1],
                'vmid'  => $vmid,
            },
+           {
+               'volid' => "$storeid:backup/vzdump-qemu-$vmid-2019_12_31-11_18_51.tar.zst",
+               'type'  => 'qemu',
+               'ctime' => $basetime - 24*60*60 - 60*60 + 30,
+               'mark'  => 'protected',
+               'vmid'  => $vmid,
+           },
            {
                'volid' => "$storeid:backup/vzdump-qemu-$vmid-2019_12_31-11_19_21.tar.zst",
                'type'  => 'qemu',