mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-10-04 13:57:45 +00:00
api: datastore/namespace: return backup groups delete stats on remove
Add and optionally expose the backup group delete statistics by adding the return type to the corresponding REST API endpoints. Clients can opt-into the new behaviour by setting the new `error-on-protected` flag to `false` when calling the api endpoints, which results in removal not erroring out when encountering protected snapshots. The default value for the flag remains `true` for now, to remain backwards compatible with older clients expecting this behaviour. Signed-off-by: Christian Ebner <c.ebner@proxmox.com> FG: reworded commit message slightly Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
parent
5462d9d44d
commit
4e50ef5193
@ -492,16 +492,22 @@ impl DataStore {
|
||||
///
|
||||
/// Does *not* descends into child-namespaces and doesn't remoes the namespace itself either.
|
||||
///
|
||||
/// Returns true if all the groups were removed, and false if some were protected.
|
||||
pub fn remove_namespace_groups(self: &Arc<Self>, ns: &BackupNamespace) -> Result<bool, Error> {
|
||||
/// Returns a tuple with the first item being true if all the groups were removed, and false if some were protected.
|
||||
/// The second item returns the remove statistics.
|
||||
pub fn remove_namespace_groups(
|
||||
self: &Arc<Self>,
|
||||
ns: &BackupNamespace,
|
||||
) -> Result<(bool, BackupGroupDeleteStats), Error> {
|
||||
// FIXME: locking? The single groups/snapshots are already protected, so may not be
|
||||
// necessary (depends on what we all allow to do with namespaces)
|
||||
log::info!("removing all groups in namespace {}:/{ns}", self.name());
|
||||
|
||||
let mut removed_all_groups = true;
|
||||
let mut stats = BackupGroupDeleteStats::default();
|
||||
|
||||
for group in self.iter_backup_groups(ns.to_owned())? {
|
||||
let delete_stats = group?.destroy()?;
|
||||
stats.add(&delete_stats);
|
||||
removed_all_groups = removed_all_groups && delete_stats.all_removed();
|
||||
}
|
||||
|
||||
@ -518,7 +524,7 @@ impl DataStore {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(removed_all_groups)
|
||||
Ok((removed_all_groups, stats))
|
||||
}
|
||||
|
||||
/// Remove a complete backup namespace optionally including all it's, and child namespaces',
|
||||
@ -530,13 +536,15 @@ impl DataStore {
|
||||
self: &Arc<Self>,
|
||||
ns: &BackupNamespace,
|
||||
delete_groups: bool,
|
||||
) -> Result<bool, Error> {
|
||||
) -> Result<(bool, BackupGroupDeleteStats), Error> {
|
||||
let store = self.name();
|
||||
let mut removed_all_requested = true;
|
||||
let mut stats = BackupGroupDeleteStats::default();
|
||||
if delete_groups {
|
||||
log::info!("removing whole namespace recursively below {store}:/{ns}",);
|
||||
for ns in self.recursive_iter_backup_ns(ns.to_owned())? {
|
||||
let removed_ns_groups = self.remove_namespace_groups(&ns?)?;
|
||||
let (removed_ns_groups, delete_stats) = self.remove_namespace_groups(&ns?)?;
|
||||
stats.add(&delete_stats);
|
||||
removed_all_requested = removed_all_requested && removed_ns_groups;
|
||||
}
|
||||
} else {
|
||||
@ -577,7 +585,7 @@ impl DataStore {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(removed_all_requested)
|
||||
Ok((removed_all_requested, stats))
|
||||
}
|
||||
|
||||
/// Remove a complete backup group including all snapshots.
|
||||
|
@ -34,10 +34,10 @@ use pxar::accessor::aio::Accessor;
|
||||
use pxar::EntryKind;
|
||||
|
||||
use pbs_api_types::{
|
||||
print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupNamespace, BackupType,
|
||||
Counts, CryptMode, DataStoreConfig, DataStoreListItem, DataStoreStatus,
|
||||
GarbageCollectionJobStatus, GroupListItem, JobScheduleStatus, KeepOptions, Operation,
|
||||
PruneJobOptions, SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA,
|
||||
print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupGroupDeleteStats,
|
||||
BackupNamespace, BackupType, Counts, CryptMode, DataStoreConfig, DataStoreListItem,
|
||||
DataStoreStatus, GarbageCollectionJobStatus, GroupListItem, JobScheduleStatus, KeepOptions,
|
||||
Operation, PruneJobOptions, SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA,
|
||||
BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_SCHEMA,
|
||||
DATASTORE_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, MAX_NAMESPACE_DEPTH, NS_MAX_DEPTH_SCHEMA,
|
||||
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_DATASTORE_PRUNE,
|
||||
@ -267,8 +267,17 @@ pub fn list_groups(
|
||||
type: pbs_api_types::BackupGroup,
|
||||
flatten: true,
|
||||
},
|
||||
"error-on-protected": {
|
||||
type: bool,
|
||||
optional: true,
|
||||
default: true,
|
||||
description: "Return error when group cannot be deleted because of protected snapshots",
|
||||
}
|
||||
},
|
||||
},
|
||||
returns: {
|
||||
type: BackupGroupDeleteStats,
|
||||
},
|
||||
access: {
|
||||
permission: &Permission::Anybody,
|
||||
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
|
||||
@ -279,9 +288,10 @@ pub fn list_groups(
|
||||
pub async fn delete_group(
|
||||
store: String,
|
||||
ns: Option<BackupNamespace>,
|
||||
error_on_protected: bool,
|
||||
group: pbs_api_types::BackupGroup,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
) -> Result<BackupGroupDeleteStats, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
@ -299,10 +309,13 @@ pub async fn delete_group(
|
||||
|
||||
let delete_stats = datastore.remove_backup_group(&ns, &group)?;
|
||||
if !delete_stats.all_removed() {
|
||||
bail!("group only partially deleted due to protected snapshots");
|
||||
if error_on_protected {
|
||||
bail!("group only partially deleted due to protected snapshots");
|
||||
} else {
|
||||
warn!("group only partially deleted due to protected snapshots");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Null)
|
||||
Ok(delete_stats)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
use anyhow::{bail, Error};
|
||||
use serde_json::Value;
|
||||
|
||||
use pbs_config::CachedUserInfo;
|
||||
use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment};
|
||||
use proxmox_schema::*;
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, BackupNamespace, NamespaceListItem, Operation, DATASTORE_SCHEMA, NS_MAX_DEPTH_SCHEMA,
|
||||
PROXMOX_SAFE_ID_FORMAT,
|
||||
Authid, BackupGroupDeleteStats, BackupNamespace, NamespaceListItem, Operation,
|
||||
DATASTORE_SCHEMA, NS_MAX_DEPTH_SCHEMA, PROXMOX_SAFE_ID_FORMAT,
|
||||
};
|
||||
|
||||
use pbs_datastore::DataStore;
|
||||
@ -138,6 +137,12 @@ pub fn list_namespaces(
|
||||
optional: true,
|
||||
default: false,
|
||||
},
|
||||
"error-on-protected": {
|
||||
type: bool,
|
||||
optional: true,
|
||||
default: true,
|
||||
description: "Return error when namespace cannot be deleted because of protected snapshots",
|
||||
}
|
||||
},
|
||||
},
|
||||
access: {
|
||||
@ -149,24 +154,32 @@ pub fn delete_namespace(
|
||||
store: String,
|
||||
ns: BackupNamespace,
|
||||
delete_groups: bool,
|
||||
error_on_protected: bool,
|
||||
_info: &ApiMethod,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
) -> Result<BackupGroupDeleteStats, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
check_ns_modification_privs(&store, &ns, &auth_id)?;
|
||||
|
||||
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
|
||||
|
||||
if !datastore.remove_namespace_recursive(&ns, delete_groups)? {
|
||||
if delete_groups {
|
||||
bail!("group only partially deleted due to protected snapshots");
|
||||
let (removed_all, stats) = datastore.remove_namespace_recursive(&ns, delete_groups)?;
|
||||
if !removed_all {
|
||||
let err_msg = if delete_groups {
|
||||
"group only partially deleted due to protected snapshots"
|
||||
} else {
|
||||
bail!("only partially deleted due to existing groups but `delete-groups` not true ");
|
||||
"only partially deleted due to existing groups but `delete-groups` not true"
|
||||
};
|
||||
|
||||
if error_on_protected {
|
||||
bail!(err_msg);
|
||||
} else {
|
||||
log::warn!("{err_msg}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Null)
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
pub const ROUTER: Router = Router::new()
|
||||
|
@ -650,10 +650,12 @@ fn check_and_remove_ns(params: &PullParameters, local_ns: &BackupNamespace) -> R
|
||||
check_ns_modification_privs(params.target.store.name(), local_ns, ¶ms.owner)
|
||||
.map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?;
|
||||
|
||||
params
|
||||
let (removed_all, _delete_stats) = params
|
||||
.target
|
||||
.store
|
||||
.remove_namespace_recursive(local_ns, true)
|
||||
.remove_namespace_recursive(local_ns, true)?;
|
||||
|
||||
Ok(removed_all)
|
||||
}
|
||||
|
||||
fn check_and_remove_vanished_ns(
|
||||
|
Loading…
Reference in New Issue
Block a user