]>
Commit | Line | Data |
---|---|---|
bf78f708 DM |
1 | //! Datastote status |
2 | ||
762f7d15 DM |
3 | use anyhow::Error; |
4 | use serde_json::Value; | |
bda48e04 | 5 | |
6ef1b649 | 6 | use proxmox_router::list_subdirs_api_method; |
dc7a5b34 TL |
7 | use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment, SubdirMap}; |
8 | use proxmox_schema::api; | |
20b3094b | 9 | |
8cc3760e | 10 | use pbs_api_types::{ |
dc7a5b34 TL |
11 | Authid, DataStoreStatusListItem, Operation, RRDMode, RRDTimeFrame, PRIV_DATASTORE_AUDIT, |
12 | PRIV_DATASTORE_BACKUP, | |
20b3094b | 13 | }; |
09340f28 | 14 | |
6d5d305d | 15 | use pbs_config::CachedUserInfo; |
dc7a5b34 | 16 | use pbs_datastore::DataStore; |
bda48e04 | 17 | |
fae4f6c5 | 18 | use crate::rrd_cache::extract_rrd_data; |
dc7a5b34 | 19 | use crate::tools::statistics::linear_regression; |
bda48e04 | 20 | |
84de1012 TL |
21 | use crate::backup::can_access_any_namespace; |
22 | ||
bda48e04 DC |
23 | #[api( |
24 | returns: { | |
25 | description: "Lists the Status of the Datastores.", | |
26 | type: Array, | |
27 | items: { | |
762f7d15 | 28 | type: DataStoreStatusListItem, |
bda48e04 DC |
29 | }, |
30 | }, | |
ecd55041 FG |
31 | access: { |
32 | permission: &Permission::Anybody, | |
33 | }, | |
bda48e04 DC |
34 | )] |
35 | /// List Datastore usages and estimates | |
143ac7e6 | 36 | pub async fn datastore_status( |
bda48e04 DC |
37 | _param: Value, |
38 | _info: &ApiMethod, | |
39 | rpcenv: &mut dyn RpcEnvironment, | |
dc7a5b34 | 40 | ) -> Result<Vec<DataStoreStatusListItem>, Error> { |
e7d4be9d | 41 | let (config, _digest) = pbs_config::datastore::config()?; |
bda48e04 | 42 | |
e6dc35ac | 43 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
bda48e04 DC |
44 | let user_info = CachedUserInfo::new()?; |
45 | ||
46 | let mut list = Vec::new(); | |
47 | ||
48 | for (store, (_, _)) in &config.sections { | |
9a37bd6c | 49 | let user_privs = user_info.lookup_privs(&auth_id, &["datastore", store]); |
dc7a5b34 | 50 | let allowed = (user_privs & (PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP)) != 0; |
bda48e04 | 51 | if !allowed { |
e1db0670 | 52 | if let Ok(datastore) = DataStore::lookup_datastore(store, Some(Operation::Lookup)) { |
84de1012 TL |
53 | if can_access_any_namespace(datastore, &auth_id, &user_info) { |
54 | list.push(DataStoreStatusListItem::empty(store, None)); | |
55 | } | |
56 | } | |
bda48e04 DC |
57 | continue; |
58 | } | |
59 | ||
e1db0670 | 60 | let datastore = match DataStore::lookup_datastore(store, Some(Operation::Read)) { |
64591e73 TL |
61 | Ok(datastore) => datastore, |
62 | Err(err) => { | |
997c96d6 | 63 | list.push(DataStoreStatusListItem::empty(store, Some(err.to_string()))); |
64591e73 TL |
64 | continue; |
65 | } | |
66 | }; | |
143ac7e6 | 67 | let status = crate::tools::fs::fs_info(datastore.base_path()).await?; |
bda48e04 | 68 | |
762f7d15 DM |
69 | let mut entry = DataStoreStatusListItem { |
70 | store: store.clone(), | |
cbb478fa GG |
71 | total: Some(status.total), |
72 | used: Some(status.used), | |
73 | avail: Some(status.available), | |
762f7d15 DM |
74 | history: None, |
75 | history_start: None, | |
76 | history_delta: None, | |
77 | estimated_full_date: None, | |
78 | error: None, | |
8550de74 | 79 | gc_status: Some(datastore.last_gc_status()), |
762f7d15 | 80 | }; |
bda48e04 DC |
81 | |
82 | let rrd_dir = format!("datastore/{}", store); | |
fa49d0fd | 83 | |
dc7a5b34 TL |
84 | let get_rrd = |
85 | |what: &str| extract_rrd_data(&rrd_dir, what, RRDTimeFrame::Month, RRDMode::Average); | |
bda48e04 | 86 | |
1198f8d4 DM |
87 | let total_res = get_rrd("total")?; |
88 | let used_res = get_rrd("used")?; | |
f362f8f0 | 89 | let avail_res = get_rrd("available")?; |
ec8f0424 | 90 | |
f362f8f0 | 91 | if let Some(((total_entry, used), avail)) = total_res.zip(used_res).zip(avail_res) { |
b92cad09 FG |
92 | let mut usage_list: Vec<f64> = Vec::new(); |
93 | let mut time_list: Vec<u64> = Vec::new(); | |
94 | let mut history = Vec::new(); | |
95 | ||
f362f8f0 DT |
96 | for (idx, used) in used.data.iter().enumerate() { |
97 | let used = match used { | |
98 | Some(used) => used, | |
99 | _ => { | |
100 | history.push(None); | |
101 | continue; | |
102 | } | |
103 | }; | |
104 | ||
6d1f8b4b WB |
105 | let total = if let Some(avail) = avail.get(idx) { |
106 | avail + used | |
107 | } else if let Some(total) = total_entry.get(idx) { | |
108 | total | |
b92cad09 | 109 | } else { |
f362f8f0 DT |
110 | history.push(None); |
111 | continue; | |
b92cad09 FG |
112 | }; |
113 | ||
f362f8f0 DT |
114 | let usage = used / total; |
115 | time_list.push(total_entry.start + (idx as u64) * total_entry.resolution); | |
116 | usage_list.push(usage); | |
117 | history.push(Some(usage)); | |
b92cad09 FG |
118 | } |
119 | ||
f362f8f0 DT |
120 | entry.history_start = Some(total_entry.start); |
121 | entry.history_delta = Some(total_entry.resolution); | |
762f7d15 | 122 | entry.history = Some(history); |
b92cad09 FG |
123 | |
124 | // we skip the calculation for datastores with not enough data | |
125 | if usage_list.len() >= 7 { | |
762f7d15 DM |
126 | entry.estimated_full_date = match linear_regression(&time_list, &usage_list) { |
127 | Some((a, b)) if b != 0.0 => Some(((1.0 - a) / b).floor() as i64), | |
39ffb75d | 128 | Some((_, b)) if b == 0.0 => Some(0), // infinite estimate, set to past for gui to detect |
762f7d15 | 129 | _ => None, |
90761f0f | 130 | }; |
b92cad09 | 131 | } |
bda48e04 DC |
132 | } |
133 | ||
134 | list.push(entry); | |
135 | } | |
136 | ||
e1db0670 | 137 | Ok(list) |
bda48e04 DC |
138 | } |
139 | ||
dc7a5b34 TL |
140 | const SUBDIRS: SubdirMap = &[( |
141 | "datastore-usage", | |
142 | &Router::new().get(&API_METHOD_DATASTORE_STATUS), | |
143 | )]; | |
bda48e04 DC |
144 | |
145 | pub const ROUTER: Router = Router::new() | |
146 | .get(&list_subdirs_api_method!(SUBDIRS)) | |
147 | .subdirs(SUBDIRS); |