plugins: allow limiting the number of protected backups per guest

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>
This commit is contained in:
Fabian Ebner
2022-03-29 14:53:13 +02:00
committed by Fabian Grünbichler
parent 89a7507c7a
commit 8009417d0d
9 changed files with 50 additions and 1 deletions

View File

@ -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);
}

View 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 },

View 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 },

View 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 },
};
}

View 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 },

View 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 },

View 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 },

View 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 },
};
}

View 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',