]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-backup-manager.rs
src/bin/proxmox_backup_manager/disk.rs: add renderer for wearout
[proxmox-backup.git] / src / bin / proxmox-backup-manager.rs
CommitLineData
331b869d 1use std::collections::HashMap;
ea0b8b6e 2
b29d046e 3use anyhow::{format_err, Error};
9894469e 4use serde_json::{json, Value};
9894469e 5
380bd7df 6use proxmox::api::{api, cli::*, RpcEnvironment};
769f8c99
DM
7
8use proxmox_backup::tools;
a220a456 9use proxmox_backup::config;
9894469e 10use proxmox_backup::api2::{self, types::* };
769f8c99
DM
11use proxmox_backup::client::*;
12use proxmox_backup::tools::ticket::*;
13use proxmox_backup::auth_helpers::*;
14
a220a456
DM
15mod proxmox_backup_manager;
16use proxmox_backup_manager::*;
17
769f8c99
DM
18async fn view_task_result(
19 client: HttpClient,
20 result: Value,
21 output_format: &str,
22) -> Result<(), Error> {
23 let data = &result["data"];
24 if output_format == "text" {
25 if let Some(upid) = data.as_str() {
26 display_task_log(client, upid, true).await?;
27 }
28 } else {
29 format_and_print_result(&data, &output_format);
30 }
31
32 Ok(())
33}
211fabd7 34
dfb31de8 35// Note: local workers should print logs to stdout, so there is no need
707974fd
DM
36// to fetch/display logs. We just wait for the worker to finish.
37pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
38
39 let upid: proxmox_backup::server::UPID = upid_str.parse()?;
40
41 let sleep_duration = core::time::Duration::new(0, 100_000_000);
42
43 loop {
44 if proxmox_backup::server::worker_is_active_local(&upid) {
45 tokio::time::delay_for(sleep_duration).await;
46 } else {
47 break;
48 }
49 }
50 Ok(())
51}
52
47d47121
DM
53fn connect() -> Result<HttpClient, Error> {
54
55 let uid = nix::unistd::Uid::current();
56
d59dbeca 57 let mut options = HttpClientOptions::new()
5030b7ce 58 .prefix(Some("proxmox-backup".to_string()))
d59dbeca
DM
59 .verify_cert(false); // not required for connection to localhost
60
47d47121
DM
61 let client = if uid.is_root() {
62 let ticket = assemble_rsa_ticket(private_auth_key(), "PBS", Some("root@pam"), None)?;
d59dbeca
DM
63 options = options.password(Some(ticket));
64 HttpClient::new("localhost", "root@pam", options)?
47d47121 65 } else {
d59dbeca
DM
66 options = options.ticket_cache(true).interactive(true);
67 HttpClient::new("localhost", "root@pam", options)?
47d47121
DM
68 };
69
70 Ok(client)
71}
72
769f8c99
DM
73#[api(
74 input: {
75 properties: {
76 store: {
77 schema: DATASTORE_SCHEMA,
78 },
79 "output-format": {
80 schema: OUTPUT_FORMAT,
81 optional: true,
82 },
83 }
84 }
85)]
86/// Start garbage collection for a specific datastore.
87async fn start_garbage_collection(param: Value) -> Result<Value, Error> {
691c89a0 88
ac3faaf5 89 let output_format = get_output_format(&param);
691c89a0 90
769f8c99
DM
91 let store = tools::required_string_param(&param, "store")?;
92
47d47121 93 let mut client = connect()?;
769f8c99
DM
94
95 let path = format!("api2/json/admin/datastore/{}/gc", store);
96
97 let result = client.post(&path, None).await?;
98
99 view_task_result(client, result, &output_format).await?;
100
101 Ok(Value::Null)
102}
103
104#[api(
105 input: {
106 properties: {
107 store: {
108 schema: DATASTORE_SCHEMA,
109 },
110 "output-format": {
111 schema: OUTPUT_FORMAT,
112 optional: true,
113 },
114 }
115 }
116)]
117/// Show garbage collection status for a specific datastore.
118async fn garbage_collection_status(param: Value) -> Result<Value, Error> {
119
ac3faaf5 120 let output_format = get_output_format(&param);
769f8c99
DM
121
122 let store = tools::required_string_param(&param, "store")?;
123
47d47121 124 let client = connect()?;
769f8c99
DM
125
126 let path = format!("api2/json/admin/datastore/{}/gc", store);
127
9894469e
DM
128 let mut result = client.get(&path, None).await?;
129 let mut data = result["data"].take();
130 let schema = api2::admin::datastore::API_RETURN_SCHEMA_GARBAGE_COLLECTION_STATUS;
131
ac3faaf5 132 let options = default_table_format_options();
9894469e
DM
133
134 format_and_print_result_full(&mut data, schema, &output_format, &options);
769f8c99
DM
135
136 Ok(Value::Null)
137}
138
139fn garbage_collection_commands() -> CommandLineInterface {
691c89a0
DM
140
141 let cmd_def = CliCommandMap::new()
142 .insert("status",
769f8c99 143 CliCommand::new(&API_METHOD_GARBAGE_COLLECTION_STATUS)
49fddd98 144 .arg_param(&["store"])
9ac1045c 145 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 146 )
691c89a0 147 .insert("start",
769f8c99 148 CliCommand::new(&API_METHOD_START_GARBAGE_COLLECTION)
49fddd98 149 .arg_param(&["store"])
9ac1045c 150 .completion_cb("store", config::datastore::complete_datastore_name)
48ef3c33 151 );
691c89a0
DM
152
153 cmd_def.into()
154}
155
47d47121
DM
156#[api(
157 input: {
158 properties: {
159 limit: {
160 description: "The maximal number of tasks to list.",
161 type: Integer,
162 optional: true,
163 minimum: 1,
164 maximum: 1000,
165 default: 50,
166 },
167 "output-format": {
168 schema: OUTPUT_FORMAT,
169 optional: true,
170 },
171 all: {
172 type: Boolean,
173 description: "Also list stopped tasks.",
174 optional: true,
175 }
176 }
177 }
178)]
179/// List running server tasks.
180async fn task_list(param: Value) -> Result<Value, Error> {
181
ac3faaf5 182 let output_format = get_output_format(&param);
47d47121
DM
183
184 let client = connect()?;
185
186 let limit = param["limit"].as_u64().unwrap_or(50) as usize;
187 let running = !param["all"].as_bool().unwrap_or(false);
188 let args = json!({
189 "running": running,
190 "start": 0,
191 "limit": limit,
192 });
9894469e 193 let mut result = client.get("api2/json/nodes/localhost/tasks", Some(args)).await?;
47d47121 194
9894469e
DM
195 let mut data = result["data"].take();
196 let schema = api2::node::tasks::API_RETURN_SCHEMA_LIST_TASKS;
47d47121 197
ac3faaf5 198 let options = default_table_format_options()
4939255f
DM
199 .column(ColumnConfig::new("starttime").right_align(false).renderer(tools::format::render_epoch))
200 .column(ColumnConfig::new("endtime").right_align(false).renderer(tools::format::render_epoch))
93fbb4ef 201 .column(ColumnConfig::new("upid"))
4939255f 202 .column(ColumnConfig::new("status").renderer(tools::format::render_task_status));
9894469e
DM
203
204 format_and_print_result_full(&mut data, schema, &output_format, &options);
47d47121
DM
205
206 Ok(Value::Null)
207}
208
209#[api(
210 input: {
211 properties: {
212 upid: {
213 schema: UPID_SCHEMA,
214 },
215 }
216 }
217)]
218/// Display the task log.
219async fn task_log(param: Value) -> Result<Value, Error> {
220
221 let upid = tools::required_string_param(&param, "upid")?;
222
223 let client = connect()?;
224
225 display_task_log(client, upid, true).await?;
226
227 Ok(Value::Null)
228}
229
230#[api(
231 input: {
232 properties: {
233 upid: {
234 schema: UPID_SCHEMA,
235 },
236 }
237 }
238)]
239/// Try to stop a specific task.
240async fn task_stop(param: Value) -> Result<Value, Error> {
241
242 let upid_str = tools::required_string_param(&param, "upid")?;
243
244 let mut client = connect()?;
245
246 let path = format!("api2/json/nodes/localhost/tasks/{}", upid_str);
247 let _ = client.delete(&path, None).await?;
248
249 Ok(Value::Null)
250}
251
252fn task_mgmt_cli() -> CommandLineInterface {
253
254 let task_log_cmd_def = CliCommand::new(&API_METHOD_TASK_LOG)
255 .arg_param(&["upid"]);
256
257 let task_stop_cmd_def = CliCommand::new(&API_METHOD_TASK_STOP)
258 .arg_param(&["upid"]);
259
260 let cmd_def = CliCommandMap::new()
261 .insert("list", CliCommand::new(&API_METHOD_TASK_LIST))
262 .insert("log", task_log_cmd_def)
263 .insert("stop", task_stop_cmd_def);
264
265 cmd_def.into()
266}
267
4b4eba0b 268// fixme: avoid API redefinition
0eb0e024
DM
269#[api(
270 input: {
271 properties: {
eb506c83 272 "local-store": {
0eb0e024
DM
273 schema: DATASTORE_SCHEMA,
274 },
275 remote: {
167971ed 276 schema: REMOTE_ID_SCHEMA,
0eb0e024
DM
277 },
278 "remote-store": {
279 schema: DATASTORE_SCHEMA,
280 },
40dc1031
DC
281 "remove-vanished": {
282 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
4b4eba0b 283 optional: true,
4b4eba0b 284 },
0eb0e024
DM
285 "output-format": {
286 schema: OUTPUT_FORMAT,
287 optional: true,
288 },
289 }
290 }
291)]
eb506c83
DM
292/// Sync datastore from another repository
293async fn pull_datastore(
0eb0e024
DM
294 remote: String,
295 remote_store: String,
eb506c83 296 local_store: String,
40dc1031 297 remove_vanished: Option<bool>,
ac3faaf5 298 param: Value,
0eb0e024
DM
299) -> Result<Value, Error> {
300
ac3faaf5 301 let output_format = get_output_format(&param);
0eb0e024
DM
302
303 let mut client = connect()?;
304
8c877436 305 let mut args = json!({
eb506c83 306 "store": local_store,
94609e23 307 "remote": remote,
0eb0e024 308 "remote-store": remote_store,
0eb0e024
DM
309 });
310
8c877436
DC
311 if let Some(remove_vanished) = remove_vanished {
312 args["remove-vanished"] = Value::from(remove_vanished);
313 }
314
eb506c83 315 let result = client.post("api2/json/pull", Some(args)).await?;
0eb0e024
DM
316
317 view_task_result(client, result, &output_format).await?;
318
319 Ok(Value::Null)
320}
321
211fabd7
DM
322fn main() {
323
ac7513e3
DM
324 proxmox_backup::tools::setup_safe_path_env();
325
6460764d 326 let cmd_def = CliCommandMap::new()
ed3e60ae 327 .insert("acl", acl_commands())
48ef3c33 328 .insert("datastore", datastore_commands())
8e40aa63 329 .insert("disk", disk_commands())
14627d67 330 .insert("dns", dns_commands())
ca0e5347 331 .insert("network", network_commands())
579728c6 332 .insert("user", user_commands())
f357390c 333 .insert("remote", remote_commands())
47d47121 334 .insert("garbage-collection", garbage_collection_commands())
550e0d88 335 .insert("cert", cert_mgmt_cli())
a3016d65 336 .insert("sync-job", sync_job_commands())
0eb0e024
DM
337 .insert("task", task_mgmt_cli())
338 .insert(
eb506c83
DM
339 "pull",
340 CliCommand::new(&API_METHOD_PULL_DATASTORE)
341 .arg_param(&["remote", "remote-store", "local-store"])
342 .completion_cb("local-store", config::datastore::complete_datastore_name)
f357390c 343 .completion_cb("remote", config::remote::complete_remote_name)
331b869d 344 .completion_cb("remote-store", complete_remote_datastore_name)
0eb0e024 345 );
34d3ba52 346
7b22acd0
DM
347 let mut rpcenv = CliEnvironment::new();
348 rpcenv.set_user(Some(String::from("root@pam")));
525008f7 349
7b22acd0 350 proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
ea0b8b6e 351}
331b869d
DM
352
353// shell completion helper
354pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
355
356 let mut list = Vec::new();
357
9ea4bce4 358 let _ = proxmox::try_block!({
331b869d 359 let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?;
a220a456 360 let (remote_config, _digest) = config::remote::config()?;
331b869d 361
a220a456 362 let remote: config::remote::Remote = remote_config.lookup("remote", &remote)?;
331b869d 363
d59dbeca
DM
364 let options = HttpClientOptions::new()
365 .password(Some(remote.password.clone()))
366 .fingerprint(remote.fingerprint.clone());
367
331b869d
DM
368 let client = HttpClient::new(
369 &remote.host,
370 &remote.userid,
d59dbeca 371 options,
331b869d
DM
372 )?;
373
03ac286c 374 let result = crate::tools::runtime::block_on(client.get("api2/json/admin/datastore", None))?;
331b869d
DM
375
376 if let Some(data) = result["data"].as_array() {
377 for item in data {
378 if let Some(store) = item["store"].as_str() {
379 list.push(store.to_owned());
380 }
381 }
382 }
383
384 Ok(())
385 }).map_err(|_err: Error| { /* ignore */ });
386
387 list
388}