]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/status.rs
backup: only allow finished backups as base snapshot
[proxmox-backup.git] / src / api2 / status.rs
1 use proxmox::list_subdirs_api_method;
2
3 use anyhow::{Error};
4 use serde_json::{json, Value};
5
6 use proxmox::api::{
7 api,
8 ApiMethod,
9 Permission,
10 Router,
11 RpcEnvironment,
12 SubdirMap,
13 UserInformation,
14 };
15
16 use crate::api2::types::{
17 DATASTORE_SCHEMA,
18 RRDMode,
19 RRDTimeFrameResolution,
20 TaskListItem
21 };
22
23 use crate::server;
24 use crate::backup::{DataStore};
25 use crate::config::datastore;
26 use crate::tools::epoch_now_f64;
27 use crate::tools::statistics::{linear_regression};
28 use crate::config::cached_user_info::CachedUserInfo;
29 use crate::config::acl::{
30 PRIV_SYS_AUDIT,
31 PRIV_DATASTORE_AUDIT,
32 PRIV_DATASTORE_BACKUP,
33 };
34
35 #[api(
36 returns: {
37 description: "Lists the Status of the Datastores.",
38 type: Array,
39 items: {
40 description: "Status of a Datastore",
41 type: Object,
42 properties: {
43 store: {
44 schema: DATASTORE_SCHEMA,
45 },
46 total: {
47 type: Integer,
48 description: "The Size of the underlying storage in bytes",
49 },
50 used: {
51 type: Integer,
52 description: "The used bytes of the underlying storage",
53 },
54 avail: {
55 type: Integer,
56 description: "The available bytes of the underlying storage",
57 },
58 history: {
59 type: Array,
60 description: "A list of usages of the past (last Month).",
61 items: {
62 type: Number,
63 description: "The usage of a time in the past. Either null or between 0.0 and 1.0.",
64 }
65 },
66 "estimated-full-date": {
67 type: Integer,
68 optional: true,
69 description: "Estimation of the UNIX epoch when the storage will be full.\
70 This is calculated via a simple Linear Regression (Least Squares)\
71 of RRD data of the last Month. Missing if there are not enough data points yet.\
72 If the estimate lies in the past, the usage is decreasing.",
73 },
74 },
75 },
76 },
77 )]
78 /// List Datastore usages and estimates
79 fn datastore_status(
80 _param: Value,
81 _info: &ApiMethod,
82 rpcenv: &mut dyn RpcEnvironment,
83 ) -> Result<Value, Error> {
84
85 let (config, _digest) = datastore::config()?;
86
87 let username = rpcenv.get_user().unwrap();
88 let user_info = CachedUserInfo::new()?;
89
90 let mut list = Vec::new();
91
92 for (store, (_, _)) in &config.sections {
93 let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
94 let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
95 if !allowed {
96 continue;
97 }
98
99 let datastore = DataStore::lookup_datastore(&store)?;
100 let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
101
102 let mut entry = json!({
103 "store": store,
104 "total": status.total,
105 "used": status.used,
106 "avail": status.avail,
107 });
108
109 let rrd_dir = format!("datastore/{}", store);
110 let now = epoch_now_f64()?;
111 let rrd_resolution = RRDTimeFrameResolution::Month;
112 let rrd_mode = RRDMode::Average;
113
114 let total_res = crate::rrd::extract_cached_data(
115 &rrd_dir,
116 "total",
117 now,
118 rrd_resolution,
119 rrd_mode,
120 );
121
122 let used_res = crate::rrd::extract_cached_data(
123 &rrd_dir,
124 "used",
125 now,
126 rrd_resolution,
127 rrd_mode,
128 );
129
130 match (total_res, used_res) {
131 (Some((start, reso, total_list)), Some((_, _, used_list))) => {
132 let mut usage_list: Vec<f64> = Vec::new();
133 let mut time_list: Vec<u64> = Vec::new();
134 let mut history = Vec::new();
135
136 for (idx, used) in used_list.iter().enumerate() {
137 let total = if idx < total_list.len() {
138 total_list[idx]
139 } else {
140 None
141 };
142
143 match (total, used) {
144 (Some(total), Some(used)) if total != 0.0 => {
145 time_list.push(start + (idx as u64)*reso);
146 let usage = used/total;
147 usage_list.push(usage);
148 history.push(json!(usage));
149 },
150 _ => {
151 history.push(json!(null))
152 }
153 }
154 }
155
156 entry["history"] = history.into();
157
158 // we skip the calculation for datastores with not enough data
159 if usage_list.len() >= 7 {
160 if let Some((a,b)) = linear_regression(&time_list, &usage_list) {
161 if b != 0.0 {
162 let estimate = (1.0 - a) / b;
163 entry["estimated-full-date"] = Value::from(estimate.floor() as u64);
164 } else {
165 entry["estimated-full-date"] = Value::from(0);
166 }
167 }
168 }
169 },
170 _ => {},
171 }
172
173 list.push(entry);
174 }
175
176 Ok(list.into())
177 }
178
179 #[api(
180 input: {
181 properties: {
182 since: {
183 type: u64,
184 description: "Only list tasks since this UNIX epoch.",
185 optional: true,
186 },
187 },
188 },
189 returns: {
190 description: "A list of tasks.",
191 type: Array,
192 items: { type: TaskListItem },
193 },
194 access: {
195 description: "Users can only see there own tasks, unless the have Sys.Audit on /system/tasks.",
196 permission: &Permission::Anybody,
197 },
198 )]
199 /// List tasks.
200 pub fn list_tasks(
201 _param: Value,
202 rpcenv: &mut dyn RpcEnvironment,
203 ) -> Result<Vec<TaskListItem>, Error> {
204
205 let username = rpcenv.get_user().unwrap();
206 let user_info = CachedUserInfo::new()?;
207 let user_privs = user_info.lookup_privs(&username, &["system", "tasks"]);
208
209 let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
210
211 // TODO: replace with call that gets all task since 'since' epoch
212 let list: Vec<TaskListItem> = server::read_task_list()?
213 .into_iter()
214 .map(TaskListItem::from)
215 .filter(|entry| list_all || entry.user == username)
216 .collect();
217
218 Ok(list.into())
219 }
220
221 const SUBDIRS: SubdirMap = &[
222 ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
223 ("tasks", &Router::new().get(&API_METHOD_LIST_TASKS)),
224 ];
225
226 pub const ROUTER: Router = Router::new()
227 .get(&list_subdirs_api_method!(SUBDIRS))
228 .subdirs(SUBDIRS);