]>
Commit | Line | Data |
---|---|---|
bdfa6370 TL |
1 | use std::sync::{ |
2 | atomic::{AtomicUsize, Ordering}, | |
3 | Arc, | |
4 | }; | |
52f7a730 | 5 | |
f7d4e4b5 | 6 | use anyhow::{bail, Error}; |
bdfa6370 | 7 | use futures::*; |
4470eba5 | 8 | use serde_json::{json, Value}; |
52f7a730 | 9 | use tokio::signal::unix::{signal, SignalKind}; |
5a0b484b | 10 | |
6ef1b649 | 11 | use proxmox_router::cli::format_and_print_result; |
4470eba5 | 12 | |
577095e2 | 13 | use pbs_api_types::percent_encoding::percent_encode_component; |
4470eba5 | 14 | |
4805edc4 | 15 | use super::HttpClient; |
5a0b484b | 16 | |
52f7a730 DM |
17 | /// Display task log on console |
18 | /// | |
19 | /// This polls the task API and prints the log to the console. It also | |
158db8c0 FG |
20 | /// catches interrupt signals, and sends an abort request to the task if the |
21 | /// user presses CTRL-C and `forward_interrupt` is true. Two interrupts cause an | |
22 | /// immediate end of the loop. The task may still run in that case. | |
5a0b484b | 23 | pub async fn display_task_log( |
d4877712 | 24 | client: &HttpClient, |
5a0b484b DM |
25 | upid_str: &str, |
26 | strip_date: bool, | |
158db8c0 | 27 | forward_interrupt: bool, |
5a0b484b | 28 | ) -> Result<(), Error> { |
52f7a730 DM |
29 | let mut signal_stream = signal(SignalKind::interrupt())?; |
30 | let abort_count = Arc::new(AtomicUsize::new(0)); | |
31 | let abort_count2 = Arc::clone(&abort_count); | |
5a0b484b | 32 | |
52f7a730 DM |
33 | let abort_future = async move { |
34 | while signal_stream.recv().await.is_some() { | |
e10fccf5 | 35 | log::info!("got shutdown request (SIGINT)"); |
52f7a730 DM |
36 | let prev_count = abort_count2.fetch_add(1, Ordering::SeqCst); |
37 | if prev_count >= 1 { | |
e10fccf5 | 38 | log::info!("forced exit (task still running)"); |
52f7a730 DM |
39 | break; |
40 | } | |
41 | } | |
42 | Ok::<_, Error>(()) | |
43 | }; | |
5a0b484b | 44 | |
52f7a730 | 45 | let request_future = async move { |
52f7a730 DM |
46 | let mut start = 1; |
47 | let limit = 500; | |
5a0b484b | 48 | |
22fc132a TL |
49 | let upid_encoded = percent_encode_component(upid_str); |
50 | ||
52f7a730 | 51 | loop { |
52f7a730 DM |
52 | let abort = abort_count.load(Ordering::Relaxed); |
53 | if abort > 0 { | |
158db8c0 | 54 | if forward_interrupt { |
22fc132a | 55 | let path = format!("api2/json/nodes/localhost/tasks/{upid_encoded}"); |
158db8c0 FG |
56 | let _ = client.delete(&path, None).await?; |
57 | } else { | |
58 | return Ok(()); | |
59 | } | |
5a0b484b | 60 | } |
5a0b484b | 61 | |
52f7a730 DM |
62 | let param = json!({ "start": start, "limit": limit, "test-status": true }); |
63 | ||
22fc132a | 64 | let path = format!("api2/json/nodes/localhost/tasks/{upid_encoded}/log"); |
52f7a730 DM |
65 | let result = client.get(&path, Some(param)).await?; |
66 | ||
67 | let active = result["active"].as_bool().unwrap(); | |
68 | let total = result["total"].as_u64().unwrap(); | |
69 | let data = result["data"].as_array().unwrap(); | |
70 | ||
71 | let lines = data.len(); | |
72 | ||
73 | for item in data { | |
74 | let n = item["n"].as_u64().unwrap(); | |
75 | let t = item["t"].as_str().unwrap(); | |
bdfa6370 | 76 | if n != start { |
c4f36779 | 77 | bail!("got wrong line number in response data ({n} != {start}"); |
bdfa6370 | 78 | } |
52f7a730 DM |
79 | if strip_date && t.len() > 27 && &t[25..27] == ": " { |
80 | let line = &t[27..]; | |
c4f36779 | 81 | println!("{line}"); |
52f7a730 | 82 | } else { |
c4f36779 | 83 | println!("{t}"); |
52f7a730 DM |
84 | } |
85 | start += 1; | |
86 | } | |
87 | ||
88 | if start > total { | |
89 | if active { | |
90 | tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; | |
91 | } else { | |
92 | break; | |
93 | } | |
94 | } else if lines != limit { | |
c4f36779 | 95 | bail!("got wrong number of lines from server ({lines} != {limit})"); |
5a0b484b | 96 | } |
5a0b484b | 97 | } |
52f7a730 | 98 | |
bc0735fe GG |
99 | let status_path = format!("api2/json/nodes/localhost/tasks/{upid_encoded}/status"); |
100 | let status_result = client.get(&status_path, None).await?; | |
101 | if status_result["data"]["status"].as_str() == Some("stopped") | |
102 | && status_result["data"]["exitstatus"].as_str() != Some("OK") | |
103 | { | |
104 | bail!("task failed"); | |
105 | } | |
106 | ||
52f7a730 DM |
107 | Ok(()) |
108 | }; | |
109 | ||
bdfa6370 | 110 | futures::select! { |
52f7a730 DM |
111 | request = request_future.fuse() => request?, |
112 | abort = abort_future.fuse() => abort?, | |
113 | }; | |
5a0b484b DM |
114 | |
115 | Ok(()) | |
116 | } | |
4470eba5 DM |
117 | |
118 | /// Display task result (upid), or view task log - depending on output format | |
158db8c0 FG |
119 | /// |
120 | /// In case of a task log of a running task, this will forward interrupt signals | |
121 | /// to the task and potentially abort it! | |
4470eba5 | 122 | pub async fn view_task_result( |
d4877712 | 123 | client: &HttpClient, |
4470eba5 DM |
124 | result: Value, |
125 | output_format: &str, | |
126 | ) -> Result<(), Error> { | |
127 | let data = &result["data"]; | |
128 | if output_format == "text" { | |
129 | if let Some(upid) = data.as_str() { | |
158db8c0 | 130 | display_task_log(client, upid, true, true).await?; |
4470eba5 DM |
131 | } |
132 | } else { | |
9a37bd6c | 133 | format_and_print_result(data, output_format); |
4470eba5 DM |
134 | } |
135 | ||
136 | Ok(()) | |
137 | } |