]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/status.rs
api2/status: add type- and statusfilter to tasks api call
[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 };
14
15 use crate::api2::types::{
16 DATASTORE_SCHEMA,
17 RRDMode,
18 RRDTimeFrameResolution,
19 TaskListItem,
20 TaskStateType,
21 Userid,
22 };
23
24 use crate::server;
25 use crate::backup::{DataStore};
26 use crate::config::datastore;
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 access: {
78 permission: &Permission::Anybody,
79 },
80 )]
81 /// List Datastore usages and estimates
82 fn datastore_status(
83 _param: Value,
84 _info: &ApiMethod,
85 rpcenv: &mut dyn RpcEnvironment,
86 ) -> Result<Value, Error> {
87
88 let (config, _digest) = datastore::config()?;
89
90 let userid: Userid = rpcenv.get_user().unwrap().parse()?;
91 let user_info = CachedUserInfo::new()?;
92
93 let mut list = Vec::new();
94
95 for (store, (_, _)) in &config.sections {
96 let user_privs = user_info.lookup_privs(&userid, &["datastore", &store]);
97 let allowed = (user_privs & (PRIV_DATASTORE_AUDIT| PRIV_DATASTORE_BACKUP)) != 0;
98 if !allowed {
99 continue;
100 }
101
102 let datastore = DataStore::lookup_datastore(&store)?;
103 let status = crate::tools::disks::disk_usage(&datastore.base_path())?;
104
105 let mut entry = json!({
106 "store": store,
107 "total": status.total,
108 "used": status.used,
109 "avail": status.avail,
110 });
111
112 let rrd_dir = format!("datastore/{}", store);
113 let now = proxmox::tools::time::epoch_f64();
114 let rrd_resolution = RRDTimeFrameResolution::Month;
115 let rrd_mode = RRDMode::Average;
116
117 let total_res = crate::rrd::extract_cached_data(
118 &rrd_dir,
119 "total",
120 now,
121 rrd_resolution,
122 rrd_mode,
123 );
124
125 let used_res = crate::rrd::extract_cached_data(
126 &rrd_dir,
127 "used",
128 now,
129 rrd_resolution,
130 rrd_mode,
131 );
132
133 match (total_res, used_res) {
134 (Some((start, reso, total_list)), Some((_, _, used_list))) => {
135 let mut usage_list: Vec<f64> = Vec::new();
136 let mut time_list: Vec<u64> = Vec::new();
137 let mut history = Vec::new();
138
139 for (idx, used) in used_list.iter().enumerate() {
140 let total = if idx < total_list.len() {
141 total_list[idx]
142 } else {
143 None
144 };
145
146 match (total, used) {
147 (Some(total), Some(used)) if total != 0.0 => {
148 time_list.push(start + (idx as u64)*reso);
149 let usage = used/total;
150 usage_list.push(usage);
151 history.push(json!(usage));
152 },
153 _ => {
154 history.push(json!(null))
155 }
156 }
157 }
158
159 entry["history"] = history.into();
160
161 // we skip the calculation for datastores with not enough data
162 if usage_list.len() >= 7 {
163 if let Some((a,b)) = linear_regression(&time_list, &usage_list) {
164 if b != 0.0 {
165 let estimate = (1.0 - a) / b;
166 entry["estimated-full-date"] = Value::from(estimate.floor() as u64);
167 } else {
168 entry["estimated-full-date"] = Value::from(0);
169 }
170 }
171 }
172 },
173 _ => {},
174 }
175
176 list.push(entry);
177 }
178
179 Ok(list.into())
180 }
181
182 #[api(
183 input: {
184 properties: {
185 since: {
186 type: i64,
187 description: "Only list tasks since this UNIX epoch.",
188 optional: true,
189 },
190 typefilter: {
191 optional: true,
192 type: String,
193 description: "Only list tasks, whose type contains this string.",
194 },
195 statusfilter: {
196 optional: true,
197 type: Array,
198 description: "Only list tasks which have any one of the listed status.",
199 items: {
200 type: TaskStateType,
201 },
202 },
203 },
204 },
205 returns: {
206 description: "A list of tasks.",
207 type: Array,
208 items: { type: TaskListItem },
209 },
210 access: {
211 description: "Users can only see there own tasks, unless the have Sys.Audit on /system/tasks.",
212 permission: &Permission::Anybody,
213 },
214 )]
215 /// List tasks.
216 pub fn list_tasks(
217 since: Option<i64>,
218 typefilter: Option<String>,
219 statusfilter: Option<Vec<TaskStateType>>,
220 _param: Value,
221 rpcenv: &mut dyn RpcEnvironment,
222 ) -> Result<Vec<TaskListItem>, Error> {
223
224 let userid: Userid = rpcenv.get_user().unwrap().parse()?;
225 let user_info = CachedUserInfo::new()?;
226 let user_privs = user_info.lookup_privs(&userid, &["system", "tasks"]);
227
228 let list_all = (user_privs & PRIV_SYS_AUDIT) != 0;
229 let since = since.unwrap_or_else(|| 0);
230
231 let list: Vec<TaskListItem> = server::TaskListInfoIterator::new(false)?
232 .take_while(|info| {
233 match info {
234 Ok(info) => info.upid.starttime > since,
235 Err(_) => false
236 }
237 })
238 .filter_map(|info| {
239 match info {
240 Ok(info) => {
241 if list_all || info.upid.userid == userid {
242 if let Some(filter) = &typefilter {
243 if !info.upid.worker_type.contains(filter) {
244 return None;
245 }
246 }
247
248 if let Some(filters) = &statusfilter {
249 if let Some(state) = &info.state {
250 let statetype = match state {
251 server::TaskState::OK { .. } => TaskStateType::OK,
252 server::TaskState::Unknown { .. } => TaskStateType::Unknown,
253 server::TaskState::Error { .. } => TaskStateType::Error,
254 server::TaskState::Warning { .. } => TaskStateType::Warning,
255 };
256
257 if !filters.contains(&statetype) {
258 return None;
259 }
260 }
261 }
262
263 Some(Ok(TaskListItem::from(info)))
264 } else {
265 None
266 }
267 }
268 Err(err) => Some(Err(err))
269 }
270 })
271 .collect::<Result<Vec<TaskListItem>, Error>>()?;
272
273 Ok(list.into())
274 }
275
276 const SUBDIRS: SubdirMap = &[
277 ("datastore-usage", &Router::new().get(&API_METHOD_DATASTORE_STATUS)),
278 ("tasks", &Router::new().get(&API_METHOD_LIST_TASKS)),
279 ];
280
281 pub const ROUTER: Router = Router::new()
282 .get(&list_subdirs_api_method!(SUBDIRS))
283 .subdirs(SUBDIRS);