]>
Commit | Line | Data |
---|---|---|
cad540e9 WB |
1 | use std::fs::File; |
2 | use std::io::{BufRead, BufReader}; | |
063ca5be | 3 | |
f7d4e4b5 | 4 | use anyhow::{Error}; |
063ca5be DM |
5 | use serde_json::{json, Value}; |
6 | ||
e7cb4dc5 | 7 | use proxmox::api::{api, Router, RpcEnvironment, Permission}; |
cad540e9 | 8 | use proxmox::api::router::SubdirMap; |
9ea4bce4 | 9 | use proxmox::{identity, list_subdirs_api_method, sortable}; |
552c2259 DM |
10 | |
11 | use crate::tools; | |
4ebf0eab | 12 | use crate::api2::types::*; |
768e10d0 | 13 | use crate::server::{self, UPID, TaskState, TaskListInfoIterator}; |
83b6a7cf | 14 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; |
720af9f6 DM |
15 | use crate::config::cached_user_info::CachedUserInfo; |
16 | ||
5a12c0e2 | 17 | |
83b6a7cf DM |
18 | #[api( |
19 | input: { | |
20 | properties: { | |
21 | node: { | |
22 | schema: NODE_SCHEMA, | |
23 | }, | |
24 | upid: { | |
25 | schema: UPID_SCHEMA, | |
26 | }, | |
27 | }, | |
28 | }, | |
29 | returns: { | |
9809772b | 30 | description: "Task status information.", |
83b6a7cf DM |
31 | properties: { |
32 | node: { | |
33 | schema: NODE_SCHEMA, | |
34 | }, | |
35 | upid: { | |
36 | schema: UPID_SCHEMA, | |
37 | }, | |
38 | pid: { | |
39 | type: i64, | |
40 | description: "The Unix PID.", | |
41 | }, | |
42 | pstart: { | |
43 | type: u64, | |
44 | description: "The Unix process start time from `/proc/pid/stat`", | |
45 | }, | |
46 | starttime: { | |
47 | type: i64, | |
48 | description: "The task start time (Epoch)", | |
49 | }, | |
50 | "type": { | |
51 | type: String, | |
52 | description: "Worker type (arbitrary ASCII string)", | |
53 | }, | |
54 | id: { | |
55 | type: String, | |
56 | optional: true, | |
57 | description: "Worker ID (arbitrary ASCII string)", | |
58 | }, | |
59 | user: { | |
60 | type: String, | |
61 | description: "The user who started the task.", | |
62 | }, | |
63 | status: { | |
64 | type: String, | |
65 | description: "'running' or 'stopped'", | |
66 | }, | |
67 | exitstatus: { | |
68 | type: String, | |
69 | optional: true, | |
70 | description: "'OK', 'Error: <msg>', or 'unkwown'.", | |
71 | }, | |
72 | }, | |
73 | }, | |
74 | access: { | |
9809772b | 75 | description: "Users can access their own tasks, or need Sys.Audit on /system/tasks.", |
720af9f6 | 76 | permission: &Permission::Anybody, |
83b6a7cf DM |
77 | }, |
78 | )] | |
79 | /// Get task status. | |
5751e495 | 80 | async fn get_task_status( |
5a12c0e2 | 81 | param: Value, |
720af9f6 | 82 | rpcenv: &mut dyn RpcEnvironment, |
5a12c0e2 DM |
83 | ) -> Result<Value, Error> { |
84 | ||
85 | let upid = extract_upid(¶m)?; | |
86 | ||
e7cb4dc5 | 87 | let userid: Userid = rpcenv.get_user().unwrap().parse()?; |
720af9f6 | 88 | |
e7cb4dc5 | 89 | if userid != upid.userid { |
720af9f6 | 90 | let user_info = CachedUserInfo::new()?; |
e7cb4dc5 | 91 | user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_AUDIT, false)?; |
720af9f6 DM |
92 | } |
93 | ||
c360bd73 DM |
94 | let mut result = json!({ |
95 | "upid": param["upid"], | |
96 | "node": upid.node, | |
97 | "pid": upid.pid, | |
98 | "pstart": upid.pstart, | |
99 | "starttime": upid.starttime, | |
100 | "type": upid.worker_type, | |
101 | "id": upid.worker_id, | |
e7cb4dc5 | 102 | "user": upid.userid, |
c360bd73 DM |
103 | }); |
104 | ||
5751e495 | 105 | if crate::server::worker_is_active(&upid).await? { |
c360bd73 | 106 | result["status"] = Value::from("running"); |
5a12c0e2 | 107 | } else { |
77bd2a46 | 108 | let exitstatus = crate::server::upid_read_status(&upid).unwrap_or(TaskState::Unknown { endtime: 0 }); |
c360bd73 | 109 | result["status"] = Value::from("stopped"); |
4c116baf | 110 | result["exitstatus"] = Value::from(exitstatus.to_string()); |
5a12c0e2 DM |
111 | }; |
112 | ||
113 | Ok(result) | |
114 | } | |
115 | ||
116 | fn extract_upid(param: &Value) -> Result<UPID, Error> { | |
117 | ||
118 | let upid_str = tools::required_string_param(¶m, "upid")?; | |
119 | ||
8560fe3e | 120 | upid_str.parse::<UPID>() |
5a12c0e2 DM |
121 | } |
122 | ||
83b6a7cf DM |
123 | #[api( |
124 | input: { | |
125 | properties: { | |
126 | node: { | |
127 | schema: NODE_SCHEMA, | |
128 | }, | |
129 | upid: { | |
130 | schema: UPID_SCHEMA, | |
131 | }, | |
132 | "test-status": { | |
133 | type: bool, | |
134 | optional: true, | |
135 | description: "Test task status, and set result attribute \"active\" accordingly.", | |
136 | }, | |
137 | start: { | |
138 | type: u64, | |
139 | optional: true, | |
140 | description: "Start at this line.", | |
141 | default: 0, | |
142 | }, | |
143 | limit: { | |
144 | type: u64, | |
145 | optional: true, | |
146 | description: "Only list this amount of lines.", | |
147 | default: 50, | |
148 | }, | |
149 | }, | |
150 | }, | |
151 | access: { | |
720af9f6 DM |
152 | description: "Users can access there own tasks, or need Sys.Audit on /system/tasks.", |
153 | permission: &Permission::Anybody, | |
83b6a7cf DM |
154 | }, |
155 | )] | |
156 | /// Read task log. | |
5751e495 | 157 | async fn read_task_log( |
5a12c0e2 | 158 | param: Value, |
e8d1da6a | 159 | mut rpcenv: &mut dyn RpcEnvironment, |
5a12c0e2 DM |
160 | ) -> Result<Value, Error> { |
161 | ||
162 | let upid = extract_upid(¶m)?; | |
6b508dd5 | 163 | |
e7cb4dc5 | 164 | let userid: Userid = rpcenv.get_user().unwrap().parse()?; |
720af9f6 | 165 | |
e7cb4dc5 | 166 | if userid != upid.userid { |
720af9f6 | 167 | let user_info = CachedUserInfo::new()?; |
e7cb4dc5 | 168 | user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_AUDIT, false)?; |
720af9f6 DM |
169 | } |
170 | ||
6b508dd5 DM |
171 | let test_status = param["test-status"].as_bool().unwrap_or(false); |
172 | ||
5a12c0e2 DM |
173 | let start = param["start"].as_u64().unwrap_or(0); |
174 | let mut limit = param["limit"].as_u64().unwrap_or(50); | |
6b508dd5 | 175 | |
5a12c0e2 DM |
176 | let mut count: u64 = 0; |
177 | ||
178 | let path = upid.log_path(); | |
179 | ||
180 | let file = File::open(path)?; | |
181 | ||
182 | let mut lines: Vec<Value> = vec![]; | |
183 | ||
184 | for line in BufReader::new(file).lines() { | |
185 | match line { | |
186 | Ok(line) => { | |
187 | count += 1; | |
188 | if count < start { continue }; | |
11377a47 | 189 | if limit == 0 { continue }; |
5a12c0e2 DM |
190 | |
191 | lines.push(json!({ "n": count, "t": line })); | |
192 | ||
193 | limit -= 1; | |
194 | } | |
195 | Err(err) => { | |
196 | log::error!("reading task log failed: {}", err); | |
197 | break; | |
198 | } | |
199 | } | |
200 | } | |
201 | ||
e8d1da6a | 202 | rpcenv["total"] = Value::from(count); |
d8d40dd0 | 203 | |
6b508dd5 | 204 | if test_status { |
5751e495 | 205 | let active = crate::server::worker_is_active(&upid).await?; |
e8d1da6a | 206 | rpcenv["active"] = Value::from(active); |
6b508dd5 DM |
207 | } |
208 | ||
5a12c0e2 DM |
209 | Ok(json!(lines)) |
210 | } | |
063ca5be | 211 | |
83b6a7cf DM |
212 | #[api( |
213 | protected: true, | |
214 | input: { | |
215 | properties: { | |
216 | node: { | |
217 | schema: NODE_SCHEMA, | |
218 | }, | |
219 | upid: { | |
220 | schema: UPID_SCHEMA, | |
221 | }, | |
222 | }, | |
223 | }, | |
224 | access: { | |
720af9f6 DM |
225 | description: "Users can stop there own tasks, or need Sys.Modify on /system/tasks.", |
226 | permission: &Permission::Anybody, | |
83b6a7cf DM |
227 | }, |
228 | )] | |
229 | /// Try to stop a task. | |
a665dea1 DM |
230 | fn stop_task( |
231 | param: Value, | |
720af9f6 | 232 | rpcenv: &mut dyn RpcEnvironment, |
a665dea1 DM |
233 | ) -> Result<Value, Error> { |
234 | ||
235 | let upid = extract_upid(¶m)?; | |
236 | ||
e7cb4dc5 | 237 | let userid: Userid = rpcenv.get_user().unwrap().parse()?; |
720af9f6 | 238 | |
e7cb4dc5 | 239 | if userid != upid.userid { |
720af9f6 | 240 | let user_info = CachedUserInfo::new()?; |
e7cb4dc5 | 241 | user_info.check_privs(&userid, &["system", "tasks"], PRIV_SYS_MODIFY, false)?; |
720af9f6 DM |
242 | } |
243 | ||
5751e495 | 244 | server::abort_worker_async(upid); |
a665dea1 DM |
245 | |
246 | Ok(Value::Null) | |
247 | } | |
248 | ||
2c4b303c DM |
249 | #[api( |
250 | input: { | |
251 | properties: { | |
252 | node: { | |
253 | schema: NODE_SCHEMA | |
254 | }, | |
255 | start: { | |
256 | type: u64, | |
257 | description: "List tasks beginning from this offset.", | |
258 | default: 0, | |
259 | optional: true, | |
260 | }, | |
261 | limit: { | |
262 | type: u64, | |
263 | description: "Only list this amount of tasks.", | |
264 | default: 50, | |
265 | optional: true, | |
266 | }, | |
267 | store: { | |
268 | schema: DATASTORE_SCHEMA, | |
269 | optional: true, | |
270 | }, | |
271 | running: { | |
272 | type: bool, | |
273 | description: "Only list running tasks.", | |
274 | optional: true, | |
ca9dfe5f | 275 | default: false, |
2c4b303c DM |
276 | }, |
277 | errors: { | |
278 | type: bool, | |
279 | description: "Only list erroneous tasks.", | |
280 | optional:true, | |
ca9dfe5f | 281 | default: false, |
2c4b303c DM |
282 | }, |
283 | userfilter: { | |
e7cb4dc5 | 284 | optional: true, |
2c4b303c DM |
285 | type: String, |
286 | description: "Only list tasks from this user.", | |
287 | }, | |
288 | }, | |
289 | }, | |
290 | returns: { | |
291 | description: "A list of tasks.", | |
292 | type: Array, | |
99384f79 | 293 | items: { type: TaskListItem }, |
2c4b303c | 294 | }, |
83b6a7cf | 295 | access: { |
720af9f6 DM |
296 | description: "Users can only see there own tasks, unless the have Sys.Audit on /system/tasks.", |
297 | permission: &Permission::Anybody, | |
83b6a7cf | 298 | }, |
2c4b303c DM |
299 | )] |
300 | /// List tasks. | |
8528fce8 | 301 | pub fn list_tasks( |
ca9dfe5f DM |
302 | start: u64, |
303 | limit: u64, | |
304 | errors: bool, | |
305 | running: bool, | |
005a5b96 | 306 | userfilter: Option<String>, |
063ca5be | 307 | param: Value, |
e8d1da6a | 308 | mut rpcenv: &mut dyn RpcEnvironment, |
99384f79 | 309 | ) -> Result<Vec<TaskListItem>, Error> { |
063ca5be | 310 | |
e7cb4dc5 | 311 | let userid: Userid = rpcenv.get_user().unwrap().parse()?; |
720af9f6 | 312 | let user_info = CachedUserInfo::new()?; |
e7cb4dc5 | 313 | let user_privs = user_info.lookup_privs(&userid, &["system", "tasks"]); |
720af9f6 DM |
314 | |
315 | let list_all = (user_privs & PRIV_SYS_AUDIT) != 0; | |
316 | ||
567d3e00 DM |
317 | let store = param["store"].as_str(); |
318 | ||
768e10d0 | 319 | let list = TaskListInfoIterator::new(running)?; |
d2a2e02b | 320 | |
768e10d0 DC |
321 | let result: Vec<TaskListItem> = list |
322 | .take_while(|info| !info.is_err()) | |
323 | .filter_map(|info| { | |
324 | let info = match info { | |
325 | Ok(info) => info, | |
326 | Err(_) => return None, | |
327 | }; | |
720af9f6 | 328 | |
768e10d0 | 329 | if !list_all && info.upid.userid != userid { return None; } |
063ca5be | 330 | |
005a5b96 | 331 | if let Some(userid) = &userfilter { |
768e10d0 | 332 | if !info.upid.userid.as_str().contains(userid) { return None; } |
d2a2e02b DM |
333 | } |
334 | ||
567d3e00 DM |
335 | if let Some(store) = store { |
336 | // Note: useful to select all tasks spawned by proxmox-backup-client | |
337 | let worker_id = match &info.upid.worker_id { | |
338 | Some(w) => w, | |
768e10d0 | 339 | None => return None, // skip |
567d3e00 DM |
340 | }; |
341 | ||
503995c7 DM |
342 | if info.upid.worker_type == "backup" || info.upid.worker_type == "restore" || |
343 | info.upid.worker_type == "prune" | |
344 | { | |
567d3e00 | 345 | let prefix = format!("{}_", store); |
768e10d0 | 346 | if !worker_id.starts_with(&prefix) { return None; } |
503995c7 | 347 | } else if info.upid.worker_type == "garbage_collection" { |
768e10d0 | 348 | if worker_id != store { return None; } |
567d3e00 | 349 | } else { |
768e10d0 | 350 | return None; // skip |
567d3e00 DM |
351 | } |
352 | } | |
353 | ||
768e10d0 | 354 | match info.state { |
df4827f2 | 355 | Some(_) if running => return None, |
768e10d0 DC |
356 | Some(crate::server::TaskState::OK { .. }) if errors => return None, |
357 | _ => {}, | |
063ca5be DM |
358 | } |
359 | ||
768e10d0 DC |
360 | Some(info.into()) |
361 | }).skip(start as usize) | |
362 | .take(limit as usize) | |
363 | .collect(); | |
063ca5be | 364 | |
768e10d0 DC |
365 | let mut count = result.len() + start as usize; |
366 | if result.len() > 0 && result.len() >= limit as usize { // we have a 'virtual' entry as long as we have any new | |
367 | count += 1; | |
063ca5be DM |
368 | } |
369 | ||
e8d1da6a | 370 | rpcenv["total"] = Value::from(count); |
063ca5be | 371 | |
0196b9bf | 372 | Ok(result) |
063ca5be DM |
373 | } |
374 | ||
552c2259 | 375 | #[sortable] |
83b6a7cf | 376 | const UPID_API_SUBDIRS: SubdirMap = &sorted!([ |
255f378a DM |
377 | ( |
378 | "log", &Router::new() | |
83b6a7cf | 379 | .get(&API_METHOD_READ_TASK_LOG) |
255f378a DM |
380 | ), |
381 | ( | |
382 | "status", &Router::new() | |
83b6a7cf | 383 | .get(&API_METHOD_GET_TASK_STATUS) |
255f378a | 384 | ) |
83b6a7cf | 385 | ]); |
255f378a DM |
386 | |
387 | pub const UPID_API_ROUTER: Router = Router::new() | |
388 | .get(&list_subdirs_api_method!(UPID_API_SUBDIRS)) | |
83b6a7cf | 389 | .delete(&API_METHOD_STOP_TASK) |
255f378a DM |
390 | .subdirs(&UPID_API_SUBDIRS); |
391 | ||
392 | pub const ROUTER: Router = Router::new() | |
2c4b303c | 393 | .get(&API_METHOD_LIST_TASKS) |
255f378a | 394 | .match_all("upid", &UPID_API_ROUTER); |