]> git.proxmox.com Git - pve-storage.git/commitdiff
plugins: allow limiting the number of protected backups per guest
authorFabian Ebner <f.ebner@proxmox.com>
Tue, 29 Mar 2022 12:53:13 +0000 (14:53 +0200)
committerFabian Grünbichler <f.gruenbichler@proxmox.com>
Wed, 6 Apr 2022 07:47:12 +0000 (09:47 +0200)
The ability to mark backups as protected broke the implicit assumption
in vzdump that remove=1 and current number of backups being the limit
(i.e. sum of all keep options) will result in a backup being removed.

Introduce a new storage property 'max-protected-backups' to limit the
number of protected backups per guest. Use 5 as a default value, as it
should cover most use cases, while still not having too big of a
potential overhead in many scenarios.

For external plugins that do not return the backup subtype in
list_volumes, all protected backups with the same ID will count
towards the limit.

An alternative would be to count the protected backups when pruning.
While that would avoid the need for a new property, it would break the
current semantics of protected backups being ignored for pruning. It
also would be less flexible, e.g. for PBS, it can make sense to have
both keep-all=1 and a limit for the number of protected snapshots on
the PVE side.

Signed-off-by: Fabian Ebner <f.ebner@proxmox.com>
PVE/Storage.pm
PVE/Storage/BTRFSPlugin.pm
PVE/Storage/CIFSPlugin.pm
PVE/Storage/CephFSPlugin.pm
PVE/Storage/DirPlugin.pm
PVE/Storage/GlusterfsPlugin.pm
PVE/Storage/NFSPlugin.pm
PVE/Storage/PBSPlugin.pm
PVE/Storage/Plugin.pm

index 3b8695602ed3044f4a97ca1c1d1d9b505c327e86..72458cf8b97ceddb4a09c75ead29839a3effe071 100755 (executable)
@@ -211,6 +211,17 @@ sub storage_can_replicate {
     return $plugin->storage_can_replicate($scfg, $storeid, $format);
 }
 
+sub get_max_protected_backups {
+    my ($scfg, $storeid) = @_;
+
+    return $scfg->{'max-protected-backups'} if defined($scfg->{'max-protected-backups'});
+
+    my $rpcenv = PVE::RPCEnvironment::get();
+    my $authuser = $rpcenv->get_user();
+
+    return $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Allocate'], 1) ? -1 : 5;
+}
+
 sub storage_ids {
     my ($cfg) = @_;
 
@@ -240,6 +251,30 @@ sub update_volume_attribute {
     my $scfg = storage_config($cfg, $storeid);
     my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
 
+    my ($vtype, undef, $vmid) = $plugin->parse_volname($volname);
+    my $max_protected_backups = get_max_protected_backups($scfg, $storeid);
+
+    if (
+       $vtype eq 'backup'
+       && $vmid
+       && $attribute eq 'protected'
+       && $value
+       && !$plugin->get_volume_attribute($scfg, $storeid, $volname, 'protected')
+       && $max_protected_backups > -1 # -1 is unlimited
+    ) {
+       my $backups = $plugin->list_volumes($storeid, $scfg, $vmid, ['backup']);
+       my ($backup_type) = map { $_->{subtype} } grep { $_->{volid} eq $volid } $backups->@*;
+
+       my $protected_count = grep {
+           $_->{protected} && (!$backup_type || ($_->{subtype} && $_->{subtype} eq $backup_type))
+       } $backups->@*;
+
+       if ($max_protected_backups <= $protected_count) {
+           die "The number of protected backups per guest is limited to $max_protected_backups ".
+               "on storage '$storeid'\n";
+       }
+    }
+
     return $plugin->update_volume_attribute($scfg, $storeid, $volname, $attribute, $value);
 }
 
index c8caa418ba29bf7bb5cbfbb9af1812352e3a4616..7dac34be400dcf24eaa65e8e73a0a1970b8046ac 100644 (file)
@@ -67,7 +67,8 @@ sub options {
        shared => { optional => 1 },
        disable => { optional => 1 },
        maxfiles => { optional => 1 },
-       'prune-backups'=> { optional => 1 },
+       'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        is_mountpoint => { optional => 1 },
index d5efa5f52f50f698af5a4989a7c89892a34cadaa..982040acc99f73e6a4ec83b7d92a0bb248c97f20 100644 (file)
@@ -134,6 +134,7 @@ sub options {
        disable => { optional => 1 },
        maxfiles => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        username => { optional => 1 },
index f75c1b87189319126ed4eba45c6e97e0ea62d250..497674729e20f5982b915391979395dd9050938f 100644 (file)
@@ -155,6 +155,7 @@ sub options {
        maxfiles => { optional => 1 },
        keyring => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        'fs-name' => { optional => 1 },
     };
 }
index c60818b7504737ae10628127201b1f5861faabf3..1baad63aa8550032d738f6300ef4f5b8b5d2e150 100644 (file)
@@ -58,6 +58,7 @@ sub options {
        disable => { optional => 1 },
        maxfiles => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        mkdir => { optional => 1 },
index d8d2b88f613b612a4da221dcfb283f2a91f3a7a3..ad386d2f3606c2ad315753621ac464783ad1c1fc 100644 (file)
@@ -133,6 +133,7 @@ sub options {
        disable => { optional => 1 },
        maxfiles => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
        mkdir => { optional => 1 },
index 0400c932b6ff7d4aae2f4dd0d1b69942366a6d75..5bd73132b3f2abae103fc6e1f5c8095d3a26776a 100644 (file)
@@ -85,6 +85,7 @@ sub options {
        disable => { optional => 1 },
        maxfiles => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        options => { optional => 1 },
        content => { optional => 1 },
        format => { optional => 1 },
index 4b3f3497b6f7ab4ce2beb722d0771337df8245fa..687901fb6352e2fa5bdde9139ad8b9f7ccdc281f 100644 (file)
@@ -73,6 +73,7 @@ sub options {
        'master-pubkey' => { optional => 1 },
        maxfiles => { optional => 1 },
        'prune-backups' => { optional => 1 },
+       'max-protected-backups' => { optional => 1 },
        fingerprint => { optional => 1 },
     };
 }
index 667340908f84e410726e75e125a36724392f00a1..521e3e9a491b98ca24b273aafaa9a0aeb2b9c622 100644 (file)
@@ -155,6 +155,13 @@ my $defaultData = {
            optional => 1,
        },
        'prune-backups' => get_standard_option('prune-backups'),
+       'max-protected-backups' => {
+           description => "Maximal number of protected backups per guest. Use '-1' for unlimited.",
+           type => 'integer',
+           minimum => -1,
+           optional => 1,
+           default => "Unlimited for users with Datastore.Allocate privilege, 5 for other users",
+       },
        shared => {
            description => "Mark storage as shared.",
            type => 'boolean',