]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-client/src/task_log.rs
fix #4343: updated `view_task_result` to bail on task failure
[proxmox-backup.git] / pbs-client / src / task_log.rs
CommitLineData
bdfa6370
TL
1use std::sync::{
2 atomic::{AtomicUsize, Ordering},
3 Arc,
4};
52f7a730 5
f7d4e4b5 6use anyhow::{bail, Error};
bdfa6370 7use futures::*;
4470eba5 8use serde_json::{json, Value};
52f7a730 9use tokio::signal::unix::{signal, SignalKind};
5a0b484b 10
6ef1b649 11use proxmox_router::cli::format_and_print_result;
4470eba5 12
577095e2 13use pbs_api_types::percent_encoding::percent_encode_component;
4470eba5 14
4805edc4 15use 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 23pub 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 122pub 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}