mirror of
https://git.proxmox.com/git/proxmox-backup
synced 2025-10-04 22:02:47 +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.
|
/// 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.
|
/// Returns a tuple with the first item being 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> {
|
/// 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
|
// FIXME: locking? The single groups/snapshots are already protected, so may not be
|
||||||
// necessary (depends on what we all allow to do with namespaces)
|
// necessary (depends on what we all allow to do with namespaces)
|
||||||
log::info!("removing all groups in namespace {}:/{ns}", self.name());
|
log::info!("removing all groups in namespace {}:/{ns}", self.name());
|
||||||
|
|
||||||
let mut removed_all_groups = true;
|
let mut removed_all_groups = true;
|
||||||
|
let mut stats = BackupGroupDeleteStats::default();
|
||||||
|
|
||||||
for group in self.iter_backup_groups(ns.to_owned())? {
|
for group in self.iter_backup_groups(ns.to_owned())? {
|
||||||
let delete_stats = group?.destroy()?;
|
let delete_stats = group?.destroy()?;
|
||||||
|
stats.add(&delete_stats);
|
||||||
removed_all_groups = removed_all_groups && delete_stats.all_removed();
|
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',
|
/// Remove a complete backup namespace optionally including all it's, and child namespaces',
|
||||||
@ -530,13 +536,15 @@ impl DataStore {
|
|||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
ns: &BackupNamespace,
|
ns: &BackupNamespace,
|
||||||
delete_groups: bool,
|
delete_groups: bool,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<(bool, BackupGroupDeleteStats), Error> {
|
||||||
let store = self.name();
|
let store = self.name();
|
||||||
let mut removed_all_requested = true;
|
let mut removed_all_requested = true;
|
||||||
|
let mut stats = BackupGroupDeleteStats::default();
|
||||||
if delete_groups {
|
if delete_groups {
|
||||||
log::info!("removing whole namespace recursively below {store}:/{ns}",);
|
log::info!("removing whole namespace recursively below {store}:/{ns}",);
|
||||||
for ns in self.recursive_iter_backup_ns(ns.to_owned())? {
|
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;
|
removed_all_requested = removed_all_requested && removed_ns_groups;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -577,7 +585,7 @@ impl DataStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(removed_all_requested)
|
Ok((removed_all_requested, stats))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a complete backup group including all snapshots.
|
/// Remove a complete backup group including all snapshots.
|
||||||
|
@ -34,10 +34,10 @@ use pxar::accessor::aio::Accessor;
|
|||||||
use pxar::EntryKind;
|
use pxar::EntryKind;
|
||||||
|
|
||||||
use pbs_api_types::{
|
use pbs_api_types::{
|
||||||
print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupNamespace, BackupType,
|
print_ns_and_snapshot, print_store_and_ns, Authid, BackupContent, BackupGroupDeleteStats,
|
||||||
Counts, CryptMode, DataStoreConfig, DataStoreListItem, DataStoreStatus,
|
BackupNamespace, BackupType, Counts, CryptMode, DataStoreConfig, DataStoreListItem,
|
||||||
GarbageCollectionJobStatus, GroupListItem, JobScheduleStatus, KeepOptions, Operation,
|
DataStoreStatus, GarbageCollectionJobStatus, GroupListItem, JobScheduleStatus, KeepOptions,
|
||||||
PruneJobOptions, SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA,
|
Operation, PruneJobOptions, SnapshotListItem, SnapshotVerifyState, BACKUP_ARCHIVE_NAME_SCHEMA,
|
||||||
BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA, BACKUP_TYPE_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,
|
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,
|
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,
|
type: pbs_api_types::BackupGroup,
|
||||||
flatten: true,
|
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: {
|
access: {
|
||||||
permission: &Permission::Anybody,
|
permission: &Permission::Anybody,
|
||||||
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
|
description: "Requires on /datastore/{store}[/{namespace}] either DATASTORE_MODIFY for any \
|
||||||
@ -279,9 +288,10 @@ pub fn list_groups(
|
|||||||
pub async fn delete_group(
|
pub async fn delete_group(
|
||||||
store: String,
|
store: String,
|
||||||
ns: Option<BackupNamespace>,
|
ns: Option<BackupNamespace>,
|
||||||
|
error_on_protected: bool,
|
||||||
group: pbs_api_types::BackupGroup,
|
group: pbs_api_types::BackupGroup,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<BackupGroupDeleteStats, Error> {
|
||||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
@ -299,10 +309,13 @@ pub async fn delete_group(
|
|||||||
|
|
||||||
let delete_stats = datastore.remove_backup_group(&ns, &group)?;
|
let delete_stats = datastore.remove_backup_group(&ns, &group)?;
|
||||||
if !delete_stats.all_removed() {
|
if !delete_stats.all_removed() {
|
||||||
|
if error_on_protected {
|
||||||
bail!("group only partially deleted due to protected snapshots");
|
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?
|
.await?
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
use pbs_config::CachedUserInfo;
|
use pbs_config::CachedUserInfo;
|
||||||
use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment};
|
use proxmox_router::{http_bail, ApiMethod, Permission, Router, RpcEnvironment};
|
||||||
use proxmox_schema::*;
|
use proxmox_schema::*;
|
||||||
|
|
||||||
use pbs_api_types::{
|
use pbs_api_types::{
|
||||||
Authid, BackupNamespace, NamespaceListItem, Operation, DATASTORE_SCHEMA, NS_MAX_DEPTH_SCHEMA,
|
Authid, BackupGroupDeleteStats, BackupNamespace, NamespaceListItem, Operation,
|
||||||
PROXMOX_SAFE_ID_FORMAT,
|
DATASTORE_SCHEMA, NS_MAX_DEPTH_SCHEMA, PROXMOX_SAFE_ID_FORMAT,
|
||||||
};
|
};
|
||||||
|
|
||||||
use pbs_datastore::DataStore;
|
use pbs_datastore::DataStore;
|
||||||
@ -138,6 +137,12 @@ pub fn list_namespaces(
|
|||||||
optional: true,
|
optional: true,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"error-on-protected": {
|
||||||
|
type: bool,
|
||||||
|
optional: true,
|
||||||
|
default: true,
|
||||||
|
description: "Return error when namespace cannot be deleted because of protected snapshots",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
access: {
|
access: {
|
||||||
@ -149,24 +154,32 @@ pub fn delete_namespace(
|
|||||||
store: String,
|
store: String,
|
||||||
ns: BackupNamespace,
|
ns: BackupNamespace,
|
||||||
delete_groups: bool,
|
delete_groups: bool,
|
||||||
|
error_on_protected: bool,
|
||||||
_info: &ApiMethod,
|
_info: &ApiMethod,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<BackupGroupDeleteStats, Error> {
|
||||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
|
|
||||||
check_ns_modification_privs(&store, &ns, &auth_id)?;
|
check_ns_modification_privs(&store, &ns, &auth_id)?;
|
||||||
|
|
||||||
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
|
let datastore = DataStore::lookup_datastore(&store, Some(Operation::Write))?;
|
||||||
|
|
||||||
if !datastore.remove_namespace_recursive(&ns, delete_groups)? {
|
let (removed_all, stats) = datastore.remove_namespace_recursive(&ns, delete_groups)?;
|
||||||
if delete_groups {
|
if !removed_all {
|
||||||
bail!("group only partially deleted due to protected snapshots");
|
let err_msg = if delete_groups {
|
||||||
|
"group only partially deleted due to protected snapshots"
|
||||||
} else {
|
} 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()
|
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)
|
check_ns_modification_privs(params.target.store.name(), local_ns, ¶ms.owner)
|
||||||
.map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?;
|
.map_err(|err| format_err!("Removing {local_ns} not allowed - {err}"))?;
|
||||||
|
|
||||||
params
|
let (removed_all, _delete_stats) = params
|
||||||
.target
|
.target
|
||||||
.store
|
.store
|
||||||
.remove_namespace_recursive(local_ns, true)
|
.remove_namespace_recursive(local_ns, true)?;
|
||||||
|
|
||||||
|
Ok(removed_all)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_and_remove_vanished_ns(
|
fn check_and_remove_vanished_ns(
|
||||||
|
Loading…
Reference in New Issue
Block a user