api: datstore status: introduce proper structs and restore compatibility

by moving the properties of the storage status out again to the top
level object

also introduce proper structs for the types used, to get type-safety
and better documentation for the api calls

this changes the backup counts from an array of [groups,snapshots] to
an object/struct with { groups, snapshots } and include 'other' types
(though we do not have any at this moment)

this way it is better documented

this also adapts the ui code to cope with the api changes

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2020-10-29 11:51:26 +01:00 committed by Thomas Lamprecht
parent 9e733dae48
commit 14e0862509
4 changed files with 117 additions and 48 deletions

View File

@ -423,12 +423,18 @@ pub fn list_snapshots (
Ok(snapshots) Ok(snapshots)
} }
// returns a map from type to (group_count, snapshot_count) fn get_snapshots_count(store: &DataStore) -> Result<Counts, Error> {
fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize)>, Error> {
let base_path = store.base_path(); let base_path = store.base_path();
let backup_list = BackupInfo::list_backups(&base_path)?; let backup_list = BackupInfo::list_backups(&base_path)?;
let mut groups = HashSet::new(); let mut groups = HashSet::new();
let mut result: HashMap<String, (usize, usize)> = HashMap::new();
let mut result = Counts {
ct: None,
host: None,
vm: None,
other: None,
};
for info in backup_list { for info in backup_list {
let group = info.backup_dir.group(); let group = info.backup_dir.group();
@ -441,13 +447,23 @@ fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize
new_id = true; new_id = true;
} }
if let Some(mut counts) = result.get_mut(backup_type) { let mut counts = match backup_type {
counts.1 += 1; "ct" => result.ct.take().unwrap_or(Default::default()),
if new_id { "host" => result.host.take().unwrap_or(Default::default()),
counts.0 +=1; "vm" => result.vm.take().unwrap_or(Default::default()),
} _ => result.other.take().unwrap_or(Default::default()),
} else { };
result.insert(backup_type.to_string(), (1, 1));
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),
} }
} }
@ -463,21 +479,7 @@ fn get_snaphots_count(store: &DataStore) -> Result<HashMap<String, (usize, usize
}, },
}, },
returns: { returns: {
description: "The overall Datastore status and information.", type: DataStoreStatus,
type: Object,
properties: {
storage: {
type: StorageStatus,
},
counts: {
description: "Group and Snapshot counts per Type",
type: Object,
properties: { },
},
"gc-status": {
type: GarbageCollectionStatus,
},
},
}, },
access: { access: {
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true), permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
@ -488,19 +490,19 @@ pub fn status(
store: String, store: String,
_info: &ApiMethod, _info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment, _rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<DataStoreStatus, Error> {
let datastore = DataStore::lookup_datastore(&store)?; let datastore = DataStore::lookup_datastore(&store)?;
let storage_status = crate::tools::disks::disk_usage(&datastore.base_path())?; let storage = crate::tools::disks::disk_usage(&datastore.base_path())?;
let counts = get_snaphots_count(&datastore)?; let counts = get_snapshots_count(&datastore)?;
let gc_status = datastore.last_gc_status(); let gc_status = datastore.last_gc_status();
let res = json!({ Ok(DataStoreStatus {
"storage": storage_status, total: storage.total,
"counts": counts, used: storage.used,
"gc-status": gc_status, avail: storage.avail,
}); gc_status,
counts,
Ok(res) })
} }
#[api( #[api(

View File

@ -622,6 +622,71 @@ pub struct StorageStatus {
pub avail: u64, pub avail: u64,
} }
#[api()]
#[derive(Serialize, Deserialize, Default)]
/// Backup Type group/snapshot counts.
pub struct TypeCounts {
/// The number of groups of the type.
pub groups: u64,
/// The number of snapshots of the type.
pub snapshots: u64,
}
#[api(
properties: {
ct: {
type: TypeCounts,
optional: true,
},
host: {
type: TypeCounts,
optional: true,
},
vm: {
type: TypeCounts,
optional: true,
},
other: {
type: TypeCounts,
optional: true,
},
},
)]
#[derive(Serialize, Deserialize)]
/// Counts of groups/snapshots per BackupType.
pub struct Counts {
/// The counts for CT backups
pub ct: Option<TypeCounts>,
/// The counts for Host backups
pub host: Option<TypeCounts>,
/// The counts for VM backups
pub vm: Option<TypeCounts>,
/// The counts for other backup types
pub other: Option<TypeCounts>,
}
#[api(
properties: {
"gc-status": { type: GarbageCollectionStatus, },
counts: { type: Counts, }
},
)]
#[derive(Serialize, Deserialize)]
#[serde(rename_all="kebab-case")]
/// Overall Datastore status and useful information.
pub struct DataStoreStatus {
/// Total space (bytes).
pub total: u64,
/// Used space (bytes).
pub used: u64,
/// Available space (bytes).
pub avail: u64,
/// Status of last GC
pub gc_status: GarbageCollectionStatus,
/// Group/Snapshot counts
pub counts: Counts,
}
#[api( #[api(
properties: { properties: {
upid: { schema: UPID_SCHEMA }, upid: { schema: UPID_SCHEMA },

View File

@ -1674,7 +1674,7 @@ async fn status(param: Value) -> Result<Value, Error> {
let path = format!("api2/json/admin/datastore/{}/status", repo.store()); let path = format!("api2/json/admin/datastore/{}/status", repo.store());
let mut result = client.get(&path, None).await?; let mut result = client.get(&path, None).await?;
let mut data = result["data"]["storage"].take(); let mut data = result["data"].take();
record_repository(&repo); record_repository(&repo);

View File

@ -48,21 +48,23 @@ Ext.define('PBS.DataStoreInfo', {
let vm = me.getViewModel(); let vm = me.getViewModel();
let counts = store.getById('counts').data.value; let counts = store.getById('counts').data.value;
let storage = store.getById('storage').data.value; let total = store.getById('total').data.value;
let used = store.getById('used').data.value;
let used = Proxmox.Utils.format_size(storage.used); let percent = 100*used/total;
let total = Proxmox.Utils.format_size(storage.total); if (total === 0) {
let percent = 100*storage.used/storage.total;
if (storage.total === 0) {
percent = 0; percent = 0;
} }
let used_percent = `${percent.toFixed(2)}%`; let used_percent = `${percent.toFixed(2)}%`;
let usage = used_percent + ' (' + let usage = used_percent + ' (' +
Ext.String.format(gettext('{0} of {1}'), Ext.String.format(
used, total) + ')'; gettext('{0} of {1}'),
Proxmox.Utils.format_size(used),
Proxmox.Utils.format_size(total),
) + ')';
vm.set('usagetext', usage); vm.set('usagetext', usage);
vm.set('usage', storage.used/storage.total); vm.set('usage', used/total);
let gcstatus = store.getById('gc-status').data.value; let gcstatus = store.getById('gc-status').data.value;
@ -70,12 +72,12 @@ Ext.define('PBS.DataStoreInfo', {
(gcstatus['disk-bytes'] || Infinity); (gcstatus['disk-bytes'] || Infinity);
let countstext = function(count) { let countstext = function(count) {
return `${count[0]} ${gettext('Groups')}, ${count[1]} ${gettext('Snapshots')}`; return `${count.groups || 0} ${gettext('Groups')}, ${count.snapshots || 0} ${gettext('Snapshots')}`;
}; };
vm.set('ctcount', countstext(counts.ct || [0, 0])); vm.set('ctcount', countstext(counts.ct));
vm.set('vmcount', countstext(counts.vm || [0, 0])); vm.set('vmcount', countstext(counts.vm));
vm.set('hostcount', countstext(counts.host || [0, 0])); vm.set('hostcount', countstext(counts.host));
vm.set('deduplication', dedup.toFixed(2)); vm.set('deduplication', dedup.toFixed(2));
vm.set('stillbad', gcstatus['still-bad']); vm.set('stillbad', gcstatus['still-bad']);
vm.set('removedbytes', Proxmox.Utils.format_size(gcstatus['removed-bytes'])); vm.set('removedbytes', Proxmox.Utils.format_size(gcstatus['removed-bytes']));