proxmox-backup/src/api2/status.rs
Dietmar Maurer 762f7d15dc datastore status: factor out api type DataStoreStatusListItem
And use the rust type instead of json::Value.
2022-03-20 09:38:50 +01:00

154 lines
4.4 KiB
Rust

//! Datastote status
use anyhow::Error;
use serde_json::Value;
use proxmox_schema::api;
use proxmox_router::{
ApiMethod,
Permission,
Router,
RpcEnvironment,
SubdirMap,
};
use proxmox_router::list_subdirs_api_method;
use pbs_api_types::{
Authid, DataStoreStatusListItem, RRDMode, RRDTimeFrame,
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
};
use pbs_datastore::DataStore;
use pbs_config::CachedUserInfo;
use crate::tools::statistics::{linear_regression};
use crate::rrd_cache::extract_rrd_data;
#[api(
returns: {
description: "Lists the Status of the Datastores.",
type: Array,
items: {
type: DataStoreStatusListItem,
},
},
access: {
permission: &Permission::Anybody,
},
)]
/// List Datastore usages and estimates
pub fn datastore_status(
_param: Value,
_info: &ApiMethod,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<Vec<DataStoreStatusListItem>, Error> {
let (config, _digest) = pbs_config::datastore::config()?;
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
let mut list = Vec::new();
for (store, (_, _)) in &config.sections {
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", store]);
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
if !allowed {
continue;
}
let datastore = match DataStore::lookup_datastore(store) {
Ok(datastore) => datastore,
Err(err) => {
list.push(DataStoreStatusListItem {
store: store.clone(),
total: 0,
used: 0,
avail: 0,
history: None,
history_start: None,
history_delta: None,
estimated_full_date: None,
error: Some(err.to_string()),
});
continue;
}
};
let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
let mut entry = DataStoreStatusListItem {
store: store.clone(),
total: status.total,
used: status.used,
avail: status.avail,
history: None,
history_start: None,
history_delta: None,
estimated_full_date: None,
error: None,
};
let rrd_dir = format!("datastore/{}", store);
let get_rrd = |what: &str| extract_rrd_data(
&rrd_dir,
what,
RRDTimeFrame::Month,
RRDMode::Average,
);
let total_res = get_rrd("total")?;
let used_res = get_rrd("used")?;
if let (Some((start, reso, total_list)), Some((_, _, used_list))) = (total_res, used_res) {
let mut usage_list: Vec<f64> = Vec::new();
let mut time_list: Vec<u64> = Vec::new();
let mut history = Vec::new();
for (idx, used) in used_list.iter().enumerate() {
let total = if idx < total_list.len() {
total_list[idx]
} else {
None
};
match (total, used) {
(Some(total), Some(used)) if total != 0.0 => {
time_list.push(start + (idx as u64)*reso);
let usage = used/total;
usage_list.push(usage);
history.push(Some(usage));
},
_ => {
history.push(None)
}
}
}
entry.history_start = Some(start);
entry.history_delta = Some(reso);
entry.history = Some(history);
// we skip the calculation for datastores with not enough data
if usage_list.len() >= 7 {
entry.estimated_full_date = match linear_regression(&time_list, &usage_list) {
Some((a, b)) if b != 0.0 => Some(((1.0 - a) / b).floor() as i64),
_ => None,
};
}
}
list.push(entry);
}
Ok(list.into())
}
const SUBDIRS: SubdirMap = &[
("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
];
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))
.subdirs(SUBDIRS);