2021-01-22 14:53:51 +00:00
|
|
|
//! Datastote status
|
|
|
|
|
2020-06-09 08:01:13 +00:00
|
|
|
use proxmox::list_subdirs_api_method;
|
|
|
|
|
|
|
|
use anyhow::{Error};
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
2020-06-12 11:34:03 +00:00
|
|
|
use proxmox::api::{
|
|
|
|
api,
|
|
|
|
ApiMethod,
|
|
|
|
Permission,
|
|
|
|
Router,
|
|
|
|
RpcEnvironment,
|
|
|
|
SubdirMap,
|
|
|
|
};
|
|
|
|
|
2021-09-09 08:32:44 +00:00
|
|
|
use pbs_api_types::{
|
|
|
|
DATASTORE_SCHEMA, RRDMode, RRDTimeFrameResolution, Authid,
|
|
|
|
PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_BACKUP,
|
2020-06-12 11:34:03 +00:00
|
|
|
};
|
2021-09-27 06:24:26 +00:00
|
|
|
use pbs_datastore::DataStore;
|
|
|
|
use pbs_config::CachedUserInfo;
|
2020-06-09 08:01:13 +00:00
|
|
|
|
|
|
|
use crate::tools::statistics::{linear_regression};
|
|
|
|
|
|
|
|
#[api(
|
|
|
|
returns: {
|
|
|
|
description: "Lists the Status of the Datastores.",
|
|
|
|
type: Array,
|
|
|
|
items: {
|
|
|
|
description: "Status of a Datastore",
|
|
|
|
type: Object,
|
|
|
|
properties: {
|
|
|
|
store: {
|
|
|
|
schema: DATASTORE_SCHEMA,
|
|
|
|
},
|
|
|
|
total: {
|
|
|
|
type: Integer,
|
|
|
|
description: "The Size of the underlying storage in bytes",
|
|
|
|
},
|
|
|
|
used: {
|
|
|
|
type: Integer,
|
|
|
|
description: "The used bytes of the underlying storage",
|
|
|
|
},
|
|
|
|
avail: {
|
|
|
|
type: Integer,
|
|
|
|
description: "The available bytes of the underlying storage",
|
|
|
|
},
|
|
|
|
history: {
|
|
|
|
type: Array,
|
2021-05-21 10:40:10 +00:00
|
|
|
optional: true,
|
2020-06-09 08:01:13 +00:00
|
|
|
description: "A list of usages of the past (last Month).",
|
|
|
|
items: {
|
|
|
|
type: Number,
|
|
|
|
description: "The usage of a time in the past. Either null or between 0.0 and 1.0.",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"estimated-full-date": {
|
|
|
|
type: Integer,
|
|
|
|
optional: true,
|
|
|
|
description: "Estimation of the UNIX epoch when the storage will be full.\
|
|
|
|
This is calculated via a simple Linear Regression (Least Squares)\
|
|
|
|
of RRD data of the last Month. Missing if there are not enough data points yet.\
|
|
|
|
If the estimate lies in the past, the usage is decreasing.",
|
|
|
|
},
|
2021-05-21 10:40:10 +00:00
|
|
|
"error": {
|
|
|
|
type: String,
|
|
|
|
optional: true,
|
|
|
|
description: "An error description, for example, when the datastore could not be looked up.",
|
|
|
|
},
|
2020-06-09 08:01:13 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-09-03 12:49:22 +00:00
|
|
|
access: {
|
|
|
|
permission: &Permission::Anybody,
|
|
|
|
},
|
2020-06-09 08:01:13 +00:00
|
|
|
)]
|
|
|
|
/// List Datastore usages and estimates
|
2021-01-22 14:53:51 +00:00
|
|
|
pub fn datastore_status(
|
2020-06-09 08:01:13 +00:00
|
|
|
_param: Value,
|
|
|
|
_info: &ApiMethod,
|
|
|
|
rpcenv: &mut dyn RpcEnvironment,
|
|
|
|
) -> Result<Value, Error> {
|
|
|
|
|
2021-09-10 06:40:58 +00:00
|
|
|
let (config, _digest) = pbs_config::datastore::config()?;
|
2020-06-09 08:01:13 +00:00
|
|
|
|
2020-10-23 11:33:21 +00:00
|
|
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
2020-06-09 08:01:13 +00:00
|
|
|
let user_info = CachedUserInfo::new()?;
|
|
|
|
|
|
|
|
let mut list = Vec::new();
|
|
|
|
|
|
|
|
for (store, (_, _)) in &config.sections {
|
2020-10-23 11:33:21 +00:00
|
|
|
let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store]);
|
2020-06-09 08:01:13 +00:00
|
|
|
let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
|
|
|
|
if !allowed {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-05-21 10:40:10 +00:00
|
|
|
let datastore = match DataStore::lookup_datastore(&store) {
|
|
|
|
Ok(datastore) => datastore,
|
|
|
|
Err(err) => {
|
|
|
|
list.push(json!({
|
|
|
|
"store": store,
|
|
|
|
"total": -1,
|
|
|
|
"used": -1,
|
|
|
|
"avail": -1,
|
|
|
|
"error": err.to_string()
|
|
|
|
}));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
};
|
2020-06-09 08:01:13 +00:00
|
|
|
let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
|
|
|
|
|
|
|
|
let mut entry = json!({
|
|
|
|
"store": store,
|
|
|
|
"total": status.total,
|
|
|
|
"used": status.used,
|
|
|
|
"avail": status.avail,
|
2020-11-10 09:18:05 +00:00
|
|
|
"gc-status": datastore.last_gc_status(),
|
2020-06-09 08:01:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
let rrd_dir = format!("datastore/{}", store);
|
2020-09-12 13:10:47 +00:00
|
|
|
let now = proxmox::tools::time::epoch_f64();
|
2020-06-09 08:01:13 +00:00
|
|
|
|
2021-05-21 11:19:02 +00:00
|
|
|
let get_rrd = |what: &str| crate::rrd::extract_cached_data(
|
2020-06-09 08:01:13 +00:00
|
|
|
&rrd_dir,
|
2021-05-21 11:19:02 +00:00
|
|
|
what,
|
2020-06-10 10:02:58 +00:00
|
|
|
now,
|
2021-05-21 11:19:02 +00:00
|
|
|
RRDTimeFrameResolution::Month,
|
|
|
|
RRDMode::Average,
|
2020-06-10 10:02:58 +00:00
|
|
|
);
|
2020-06-09 08:01:13 +00:00
|
|
|
|
2021-05-21 11:19:02 +00:00
|
|
|
let total_res = get_rrd("total");
|
|
|
|
let used_res = get_rrd("used");
|
2020-06-10 10:02:58 +00:00
|
|
|
|
2021-01-19 11:09:33 +00:00
|
|
|
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(json!(usage));
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
history.push(json!(null))
|
2020-06-10 10:02:58 +00:00
|
|
|
}
|
2020-06-09 08:01:13 +00:00
|
|
|
}
|
2021-01-19 11:09:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
entry["history-start"] = start.into();
|
|
|
|
entry["history-delta"] = reso.into();
|
|
|
|
entry["history"] = history.into();
|
|
|
|
|
|
|
|
// we skip the calculation for datastores with not enough data
|
|
|
|
if usage_list.len() >= 7 {
|
2021-05-21 08:29:14 +00:00
|
|
|
entry["estimated-full-date"] = match linear_regression(&time_list, &usage_list) {
|
|
|
|
Some((a, b)) if b != 0.0 => Value::from(((1.0 - a) / b).floor() as u64),
|
|
|
|
_ => Value::from(0),
|
|
|
|
};
|
2021-01-19 11:09:33 +00:00
|
|
|
}
|
2020-06-09 08:01:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|