api: filter snapshot counts
unprivileged users should only see the counts related to their part of the datastore. while we're at it, switch to a list groups, filter groups, count snapshots approach (like list_snapshots) to speedup calls to this endpoint when many unprivileged users share a datastore. Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
parent
98afc7b152
commit
fdfcb74d67
|
@ -472,51 +472,40 @@ pub fn list_snapshots (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_snapshots_count(store: &DataStore) -> Result<Counts, Error> {
|
fn get_snapshots_count(store: &DataStore, filter_owner: Option<&Authid>) -> Result<Counts, Error> {
|
||||||
let base_path = store.base_path();
|
let base_path = store.base_path();
|
||||||
let backup_list = BackupInfo::list_backups(&base_path)?;
|
let groups = BackupInfo::list_backup_groups(&base_path)?;
|
||||||
let mut groups = HashSet::new();
|
|
||||||
|
|
||||||
let mut result = Counts {
|
groups.iter()
|
||||||
ct: None,
|
.filter(|group| {
|
||||||
host: None,
|
let owner = match store.get_owner(&group) {
|
||||||
vm: None,
|
Ok(owner) => owner,
|
||||||
other: None,
|
Err(err) => {
|
||||||
};
|
eprintln!("Failed to get owner of group '{}' - {}", group, err);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
for info in backup_list {
|
match filter_owner {
|
||||||
let group = info.backup_dir.group();
|
Some(filter) => check_backup_owner(&owner, filter).is_ok(),
|
||||||
|
None => true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.try_fold(Counts::default(), |mut counts, group| {
|
||||||
|
let snapshot_count = group.list_backups(&base_path)?.len() as u64;
|
||||||
|
|
||||||
let id = group.backup_id();
|
let type_count = match group.backup_type() {
|
||||||
let backup_type = group.backup_type();
|
"ct" => counts.ct.get_or_insert(Default::default()),
|
||||||
|
"vm" => counts.vm.get_or_insert(Default::default()),
|
||||||
|
"host" => counts.host.get_or_insert(Default::default()),
|
||||||
|
_ => counts.other.get_or_insert(Default::default()),
|
||||||
|
};
|
||||||
|
|
||||||
let mut new_id = false;
|
type_count.groups += 1;
|
||||||
|
type_count.snapshots += snapshot_count;
|
||||||
|
|
||||||
if groups.insert(format!("{}-{}", &backup_type, &id)) {
|
Ok(counts)
|
||||||
new_id = true;
|
})
|
||||||
}
|
|
||||||
|
|
||||||
let mut counts = match backup_type {
|
|
||||||
"ct" => result.ct.take().unwrap_or(Default::default()),
|
|
||||||
"host" => result.host.take().unwrap_or(Default::default()),
|
|
||||||
"vm" => result.vm.take().unwrap_or(Default::default()),
|
|
||||||
_ => result.other.take().unwrap_or(Default::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
counts.snapshots += 1;
|
|
||||||
if new_id {
|
|
||||||
counts.groups +=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
match backup_type {
|
|
||||||
"ct" => result.ct = Some(counts),
|
|
||||||
"host" => result.host = Some(counts),
|
|
||||||
"vm" => result.vm = Some(counts),
|
|
||||||
_ => result.other = Some(counts),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -546,15 +535,27 @@ pub fn status(
|
||||||
store: String,
|
store: String,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
_info: &ApiMethod,
|
_info: &ApiMethod,
|
||||||
_rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<DataStoreStatus, Error> {
|
) -> Result<DataStoreStatus, Error> {
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
let storage = crate::tools::disks::disk_usage(&datastore.base_path())?;
|
let storage = crate::tools::disks::disk_usage(&datastore.base_path())?;
|
||||||
let (counts, gc_status) = match verbose {
|
let (counts, gc_status) = if verbose {
|
||||||
true => {
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
(Some(get_snapshots_count(&datastore)?), Some(datastore.last_gc_status()))
|
let user_info = CachedUserInfo::new()?;
|
||||||
},
|
|
||||||
false => (None, None),
|
let store_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
|
||||||
|
let filter_owner = if store_privs & PRIV_DATASTORE_AUDIT != 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&auth_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
let counts = Some(get_snapshots_count(&datastore, filter_owner)?);
|
||||||
|
let gc_status = Some(datastore.last_gc_status());
|
||||||
|
|
||||||
|
(counts, gc_status)
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(DataStoreStatus {
|
Ok(DataStoreStatus {
|
||||||
|
|
|
@ -692,7 +692,7 @@ pub struct TypeCounts {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
/// Counts of groups/snapshots per BackupType.
|
/// Counts of groups/snapshots per BackupType.
|
||||||
pub struct Counts {
|
pub struct Counts {
|
||||||
/// The counts for CT backups
|
/// The counts for CT backups
|
||||||
|
|
Loading…
Reference in New Issue