]> git.proxmox.com Git - proxmox-backup.git/blame - src/bin/proxmox-tape.rs
pbs-client: avoid mut self in http_client methods.
[proxmox-backup.git] / src / bin / proxmox-tape.rs
CommitLineData
efd2713a
DC
1use std::collections::HashMap;
2
3use anyhow::{bail, format_err, Error};
e92c7581 4use serde_json::{json, Value};
583a68a4 5
6ef1b649
WB
6use proxmox_io::ReadExt;
7use proxmox_router::RpcEnvironment;
8use proxmox_router::cli::*;
9use proxmox_schema::api;
10use proxmox_section_config::SectionConfigData;
11use proxmox_time::strftime_local;
583a68a4 12
01a08021 13use pbs_client::view_task_result;
770a36e5 14use pbs_tools::format::{
770a36e5
WB
15 render_epoch,
16 render_bytes_human_readable,
17};
18
1ce8e905 19use pbs_config::drive::complete_drive_name;
aad2d162 20use pbs_config::media_pool::complete_pool_name;
e7d4be9d 21use pbs_config::datastore::complete_datastore_name;
1ce8e905 22
6227654a
DM
23use pbs_api_types::{
24 Userid, Authid, DATASTORE_SCHEMA, DATASTORE_MAP_LIST_SCHEMA,
25 DRIVE_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA,
efd2713a 26 TAPE_RESTORE_SNAPSHOT_SCHEMA, GROUP_FILTER_LIST_SCHEMA, GroupListItem,
a58a5cf7 27 HumanByte
6227654a 28};
048b43af
DM
29use pbs_tape::{
30 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, BlockReadError, MediaContentHeader,
31};
6227654a 32
583a68a4 33use proxmox_backup::{
6227654a 34 api2,
e49f0c03 35 tape::{
25aa55b5
DM
36 drive::{
37 open_drive,
38 lock_tape_device,
926d05ef 39 set_tape_device_state,
25aa55b5 40 },
8446fbca 41 complete_media_label_text,
b9b4b312 42 complete_media_set_uuid,
0ecdaa0d 43 complete_media_set_snapshots,
ac461bd6 44 file_formats::{
f47e0357 45 proxmox_tape_magic_to_text,
ac461bd6 46 },
e49f0c03 47 },
01a08021 48 client_helpers::connect_to_localhost,
e6604cf3
DM
49};
50
51mod proxmox_tape;
52use proxmox_tape::*;
53
efd2713a
DC
54async fn get_backup_groups(store: &str) -> Result<Vec<GroupListItem>, Error> {
55 let client = connect_to_localhost()?;
56 let api_res = client
57 .get(&format!("api2/json/admin/datastore/{}/groups", store), None)
58 .await?;
59
60 match api_res.get("data") {
61 Some(data) => Ok(serde_json::from_value::<Vec<GroupListItem>>(data.to_owned())?),
62 None => bail!("could not get group list"),
63 }
64}
65
66// shell completion helper
67pub fn complete_datastore_group_filter(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
68
69 let mut list = Vec::new();
70
71 list.push("regex:".to_string());
72 list.push("type:ct".to_string());
73 list.push("type:host".to_string());
74 list.push("type:vm".to_string());
75
76 if let Some(store) = param.get("store") {
9a1b24b6 77 let groups = proxmox_async::runtime::block_on(async { get_backup_groups(store).await });
efd2713a
DC
78 if let Ok(groups) = groups {
79 list.extend(groups.iter().map(|group| format!("group:{}/{}", group.backup_type, group.backup_id)));
80 }
81 }
82
83 list
84}
85
85cdc4f3
DC
86pub fn extract_drive_name(
87 param: &mut Value,
583a68a4
DM
88 config: &SectionConfigData,
89) -> Result<String, Error> {
90
91 let drive = param["drive"]
92 .as_str()
93 .map(String::from)
94 .or_else(|| std::env::var("PROXMOX_TAPE_DRIVE").ok())
95 .or_else(|| {
96
97 let mut drive_names = Vec::new();
98
99 for (name, (section_type, _)) in config.sections.iter() {
100
101 if !(section_type == "linux" || section_type == "virtual") { continue; }
102 drive_names.push(name);
103 }
104
105 if drive_names.len() == 1 {
106 Some(drive_names[0].to_owned())
107 } else {
108 None
109 }
110 })
111 .ok_or_else(|| format_err!("unable to get (default) drive name"))?;
112
85cdc4f3
DC
113 if let Some(map) = param.as_object_mut() {
114 map.remove("drive");
115 }
116
583a68a4
DM
117 Ok(drive)
118}
119
120#[api(
121 input: {
122 properties: {
123 drive: {
49c965a4 124 schema: DRIVE_NAME_SCHEMA,
583a68a4
DM
125 optional: true,
126 },
127 fast: {
128 description: "Use fast erase.",
129 type: bool,
130 optional: true,
131 default: true,
132 },
eb1dfb02
DM
133 "output-format": {
134 schema: OUTPUT_FORMAT,
135 optional: true,
136 },
137 },
583a68a4
DM
138 },
139)]
e29f456e
DM
140/// Format media
141async fn format_media(mut param: Value) -> Result<(), Error> {
eb1dfb02 142
671c6a96 143 let output_format = extract_output_format(&mut param);
583a68a4 144
1ce8e905 145 let (config, _digest) = pbs_config::drive::config()?;
583a68a4 146
85cdc4f3 147 let drive = extract_drive_name(&mut param, &config)?;
583a68a4 148
d4877712 149 let client = connect_to_localhost()?;
583a68a4 150
e29f456e 151 let path = format!("api2/json/tape/drive/{}/format-media", drive);
eb1dfb02 152 let result = client.post(&path, Some(param)).await?;
583a68a4 153
d4877712 154 view_task_result(&client, result, &output_format).await?;
663ef859 155
583a68a4
DM
156 Ok(())
157}
158
5fb694e8
DM
159#[api(
160 input: {
161 properties: {
162 drive: {
49c965a4 163 schema: DRIVE_NAME_SCHEMA,
5fb694e8
DM
164 optional: true,
165 },
eb1dfb02
DM
166 "output-format": {
167 schema: OUTPUT_FORMAT,
168 optional: true,
169 },
5fb694e8
DM
170 },
171 },
172)]
173/// Rewind tape
85cdc4f3 174async fn rewind(mut param: Value) -> Result<(), Error> {
eb1dfb02 175
671c6a96 176 let output_format = extract_output_format(&mut param);
0098b712 177
1ce8e905 178 let (config, _digest) = pbs_config::drive::config()?;
5fb694e8 179
85cdc4f3 180 let drive = extract_drive_name(&mut param, &config)?;
5fb694e8 181
d4877712 182 let client = connect_to_localhost()?;
5fb694e8 183
eb1dfb02
DM
184 let path = format!("api2/json/tape/drive/{}/rewind", drive);
185 let result = client.post(&path, Some(param)).await?;
5fb694e8 186
d4877712 187 view_task_result(&client, result, &output_format).await?;
663ef859 188
5fb694e8
DM
189 Ok(())
190}
191
0098b712
DM
192#[api(
193 input: {
194 properties: {
195 drive: {
49c965a4 196 schema: DRIVE_NAME_SCHEMA,
0098b712
DM
197 optional: true,
198 },
41dacd5d
DM
199 "output-format": {
200 schema: OUTPUT_FORMAT,
201 optional: true,
202 },
0098b712
DM
203 },
204 },
205)]
206/// Eject/Unload drive media
85cdc4f3 207async fn eject_media(mut param: Value) -> Result<(), Error> {
41dacd5d 208
671c6a96 209 let output_format = extract_output_format(&mut param);
0098b712 210
1ce8e905 211 let (config, _digest) = pbs_config::drive::config()?;
0098b712 212
85cdc4f3 213 let drive = extract_drive_name(&mut param, &config)?;
0098b712 214
d4877712 215 let client = connect_to_localhost()?;
0098b712 216
41dacd5d
DM
217 let path = format!("api2/json/tape/drive/{}/eject-media", drive);
218 let result = client.post(&path, Some(param)).await?;
219
d4877712 220 view_task_result(&client, result, &output_format).await?;
0098b712
DM
221
222 Ok(())
223}
224
e49f0c03
DM
225#[api(
226 input: {
227 properties: {
228 drive: {
49c965a4 229 schema: DRIVE_NAME_SCHEMA,
e49f0c03
DM
230 optional: true,
231 },
8446fbca 232 "label-text": {
e49f0c03
DM
233 schema: MEDIA_LABEL_SCHEMA,
234 },
d0647e5a
DM
235 "output-format": {
236 schema: OUTPUT_FORMAT,
237 optional: true,
238 },
e49f0c03
DM
239 },
240 },
241)]
46a1863f 242/// Load media with specified label
85cdc4f3 243async fn load_media(mut param: Value) -> Result<(), Error> {
e49f0c03 244
671c6a96 245 let output_format = extract_output_format(&mut param);
d0647e5a 246
1ce8e905 247 let (config, _digest) = pbs_config::drive::config()?;
e49f0c03 248
85cdc4f3 249 let drive = extract_drive_name(&mut param, &config)?;
e49f0c03 250
d4877712 251 let client = connect_to_localhost()?;
e49f0c03 252
5243df47 253 let path = format!("api2/json/tape/drive/{}/load-media", drive);
d0647e5a
DM
254 let result = client.post(&path, Some(param)).await?;
255
d4877712 256 view_task_result(&client, result, &output_format).await?;
e49f0c03
DM
257
258 Ok(())
259}
260
483da89d
DM
261#[api(
262 input: {
263 properties: {
264 drive: {
265 schema: DRIVE_NAME_SCHEMA,
266 optional: true,
267 },
8446fbca 268 "label-text": {
483da89d
DM
269 schema: MEDIA_LABEL_SCHEMA,
270 },
271 },
272 },
273)]
274/// Export media with specified label
85cdc4f3 275async fn export_media(mut param: Value) -> Result<(), Error> {
483da89d 276
1ce8e905 277 let (config, _digest) = pbs_config::drive::config()?;
483da89d 278
85cdc4f3 279 let drive = extract_drive_name(&mut param, &config)?;
483da89d 280
d4877712 281 let client = connect_to_localhost()?;
483da89d 282
5243df47
DM
283 let path = format!("api2/json/tape/drive/{}/export-media", drive);
284 client.put(&path, Some(param)).await?;
483da89d
DM
285
286 Ok(())
287}
288
46a1863f
DM
289#[api(
290 input: {
291 properties: {
292 drive: {
293 schema: DRIVE_NAME_SCHEMA,
294 optional: true,
295 },
296 "source-slot": {
297 description: "Source slot number.",
298 type: u64,
299 minimum: 1,
300 },
301 },
302 },
303)]
304/// Load media from the specified slot
85cdc4f3 305async fn load_media_from_slot(mut param: Value) -> Result<(), Error> {
46a1863f 306
1ce8e905 307 let (config, _digest) = pbs_config::drive::config()?;
46a1863f 308
85cdc4f3 309 let drive = extract_drive_name(&mut param, &config)?;
46a1863f 310
d4877712 311 let client = connect_to_localhost()?;
46a1863f 312
5243df47
DM
313 let path = format!("api2/json/tape/drive/{}/load-slot", drive);
314 client.put(&path, Some(param)).await?;
46a1863f
DM
315
316 Ok(())
317}
318
319#[api(
320 input: {
321 properties: {
322 drive: {
323 schema: DRIVE_NAME_SCHEMA,
324 optional: true,
325 },
326 "target-slot": {
327 description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
328 type: u64,
329 minimum: 1,
330 optional: true,
331 },
d0647e5a
DM
332 "output-format": {
333 schema: OUTPUT_FORMAT,
334 optional: true,
335 },
46a1863f
DM
336 },
337 },
338)]
339/// Unload media via changer
85cdc4f3 340async fn unload_media(mut param: Value) -> Result<(), Error> {
46a1863f 341
671c6a96 342 let output_format = extract_output_format(&mut param);
d0647e5a 343
1ce8e905 344 let (config, _digest) = pbs_config::drive::config()?;
46a1863f 345
85cdc4f3 346 let drive = extract_drive_name(&mut param, &config)?;
46a1863f 347
d4877712 348 let client = connect_to_localhost()?;
46a1863f 349
5243df47 350 let path = format!("api2/json/tape/drive/{}/unload", drive);
d0647e5a
DM
351 let result = client.post(&path, Some(param)).await?;
352
d4877712 353 view_task_result(&client, result, &output_format).await?;
46a1863f
DM
354
355 Ok(())
356}
357
7bb720cb
DM
358#[api(
359 input: {
360 properties: {
361 pool: {
362 schema: MEDIA_POOL_NAME_SCHEMA,
363 optional: true,
364 },
365 drive: {
49c965a4 366 schema: DRIVE_NAME_SCHEMA,
7bb720cb
DM
367 optional: true,
368 },
8446fbca 369 "label-text": {
7bb720cb
DM
370 schema: MEDIA_LABEL_SCHEMA,
371 },
5243df47
DM
372 "output-format": {
373 schema: OUTPUT_FORMAT,
374 optional: true,
375 },
376 },
7bb720cb
DM
377 },
378)]
379/// Label media
85cdc4f3 380async fn label_media(mut param: Value) -> Result<(), Error> {
5243df47 381
671c6a96 382 let output_format = extract_output_format(&mut param);
7bb720cb 383
1ce8e905 384 let (config, _digest) = pbs_config::drive::config()?;
7bb720cb 385
85cdc4f3 386 let drive = extract_drive_name(&mut param, &config)?;
7bb720cb 387
d4877712 388 let client = connect_to_localhost()?;
7bb720cb 389
5243df47
DM
390 let path = format!("api2/json/tape/drive/{}/label-media", drive);
391 let result = client.post(&path, Some(param)).await?;
7bb720cb 392
d4877712 393 view_task_result(&client, result, &output_format).await?;
6dbad5b4 394
7bb720cb
DM
395 Ok(())
396}
397
4606f343
DM
398#[api(
399 input: {
400 properties: {
401 drive: {
49c965a4 402 schema: DRIVE_NAME_SCHEMA,
4606f343
DM
403 optional: true,
404 },
781da7f6
DM
405 inventorize: {
406 description: "Inventorize media",
407 type: bool,
408 optional: true,
409 },
410 "output-format": {
4606f343
DM
411 schema: OUTPUT_FORMAT,
412 optional: true,
413 },
414 },
415 },
416)]
417/// Read media label
85cdc4f3 418async fn read_label(mut param: Value) -> Result<(), Error> {
5243df47 419
671c6a96 420 let output_format = extract_output_format(&mut param);
83abc749 421
1ce8e905 422 let (config, _digest) = pbs_config::drive::config()?;
4606f343 423
85cdc4f3 424 let drive = extract_drive_name(&mut param, &config)?;
5243df47
DM
425
426 let client = connect_to_localhost()?;
427
428 let path = format!("api2/json/tape/drive/{}/read-label", drive);
429 let mut result = client.get(&path, Some(param)).await?;
430 let mut data = result["data"].take();
4606f343 431
4606f343 432 let info = &api2::tape::drive::API_METHOD_READ_LABEL;
4606f343
DM
433
434 let options = default_table_format_options()
8446fbca 435 .column(ColumnConfig::new("label-text"))
4606f343
DM
436 .column(ColumnConfig::new("uuid"))
437 .column(ColumnConfig::new("ctime").renderer(render_epoch))
438 .column(ColumnConfig::new("pool"))
439 .column(ColumnConfig::new("media-set-uuid"))
440 .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch))
8a0046f5 441 .column(ColumnConfig::new("encryption-key-fingerprint"))
4606f343
DM
442 ;
443
b2362a12 444 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
4606f343
DM
445
446 Ok(())
83abc749
DM
447}
448
449#[api(
450 input: {
451 properties: {
452 "output-format": {
453 schema: OUTPUT_FORMAT,
454 optional: true,
455 },
456 drive: {
49c965a4 457 schema: DRIVE_NAME_SCHEMA,
83abc749
DM
458 optional: true,
459 },
460 "read-labels": {
461 description: "Load unknown tapes and try read labels",
462 type: bool,
463 optional: true,
464 },
465 "read-all-labels": {
466 description: "Load all tapes and try read labels (even if already inventoried)",
467 type: bool,
468 optional: true,
469 },
470 },
471 },
472)]
e92c7581
DM
473/// List (and update) media labels (Changer Inventory)
474async fn inventory(
475 read_labels: Option<bool>,
476 read_all_labels: Option<bool>,
85cdc4f3 477 mut param: Value,
83abc749
DM
478) -> Result<(), Error> {
479
671c6a96 480 let output_format = extract_output_format(&mut param);
e92c7581 481
1ce8e905 482 let (config, _digest) = pbs_config::drive::config()?;
85cdc4f3 483 let drive = extract_drive_name(&mut param, &config)?;
83abc749 484
e92c7581
DM
485 let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false);
486
d4877712 487 let client = connect_to_localhost()?;
e68269fc
DM
488
489 let path = format!("api2/json/tape/drive/{}/inventory", drive);
490
e92c7581 491 if do_read {
e68269fc
DM
492
493 let mut param = json!({});
e92c7581
DM
494 if let Some(true) = read_all_labels {
495 param["read-all-labels"] = true.into();
496 }
e68269fc
DM
497
498 let result = client.put(&path, Some(param)).await?; // update inventory
d4877712 499 view_task_result(&client, result, &output_format).await?;
e92c7581 500 }
83abc749 501
e68269fc
DM
502 let mut result = client.get(&path, None).await?;
503 let mut data = result["data"].take();
e92c7581 504
e68269fc 505 let info = &api2::tape::drive::API_METHOD_INVENTORY;
4606f343 506
83abc749 507 let options = default_table_format_options()
8446fbca 508 .column(ColumnConfig::new("label-text"))
83abc749
DM
509 .column(ColumnConfig::new("uuid"))
510 ;
511
b2362a12 512 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
83abc749
DM
513
514 Ok(())
4606f343 515}
83abc749 516
bff7e3f3
DM
517#[api(
518 input: {
519 properties: {
520 pool: {
521 schema: MEDIA_POOL_NAME_SCHEMA,
522 optional: true,
523 },
524 drive: {
49c965a4 525 schema: DRIVE_NAME_SCHEMA,
bff7e3f3
DM
526 optional: true,
527 },
c297835b
DM
528 "output-format": {
529 schema: OUTPUT_FORMAT,
530 optional: true,
531 },
bff7e3f3
DM
532 },
533 },
534)]
535/// Label media with barcodes from changer device
85cdc4f3 536async fn barcode_label_media(mut param: Value) -> Result<(), Error> {
c297835b 537
671c6a96 538 let output_format = extract_output_format(&mut param);
bff7e3f3 539
1ce8e905 540 let (config, _digest) = pbs_config::drive::config()?;
bff7e3f3 541
85cdc4f3 542 let drive = extract_drive_name(&mut param, &config)?;
bff7e3f3 543
d4877712 544 let client = connect_to_localhost()?;
bff7e3f3 545
c297835b
DM
546 let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive);
547 let result = client.post(&path, Some(param)).await?;
bff7e3f3 548
d4877712 549 view_task_result(&client, result, &output_format).await?;
6dbad5b4 550
bff7e3f3
DM
551 Ok(())
552}
553
ce955e16
DM
554#[api(
555 input: {
556 properties: {
557 drive: {
558 schema: DRIVE_NAME_SCHEMA,
559 optional: true,
560 },
561 },
562 },
563)]
564/// Move to end of media (MTEOM, used to debug)
85cdc4f3 565fn move_to_eom(mut param: Value) -> Result<(), Error> {
ce955e16 566
1ce8e905 567 let (config, _digest) = pbs_config::drive::config()?;
ce955e16 568
85cdc4f3 569 let drive = extract_drive_name(&mut param, &config)?;
25aa55b5
DM
570
571 let _lock = lock_tape_device(&config, &drive)?;
926d05ef 572 set_tape_device_state(&drive, "moving to eom")?;
25aa55b5 573
ce955e16
DM
574 let mut drive = open_drive(&config, &drive)?;
575
7b11a809 576 drive.move_to_eom(false)?;
ce955e16
DM
577
578 Ok(())
579}
580
ac461bd6
DM
581#[api(
582 input: {
583 properties: {
584 drive: {
585 schema: DRIVE_NAME_SCHEMA,
586 optional: true,
587 },
588 },
589 },
590)]
591/// Rewind, then read media contents and print debug info
3f803af0
DM
592///
593/// Note: This reads unless the driver returns an IO Error, so this
594/// method is expected to fails when we reach EOT.
85cdc4f3 595fn debug_scan(mut param: Value) -> Result<(), Error> {
ac461bd6 596
1ce8e905 597 let (config, _digest) = pbs_config::drive::config()?;
ac461bd6 598
85cdc4f3 599 let drive = extract_drive_name(&mut param, &config)?;
25aa55b5
DM
600
601 let _lock = lock_tape_device(&config, &drive)?;
926d05ef 602 set_tape_device_state(&drive, "debug scan")?;
25aa55b5 603
ac461bd6
DM
604 let mut drive = open_drive(&config, &drive)?;
605
606 println!("rewinding tape");
607 drive.rewind()?;
608
609 loop {
610 let file_number = drive.current_file_number()?;
611
318b3106
DM
612 match drive.read_next_file() {
613 Err(BlockReadError::EndOfFile) => {
614 println!("filemark number {}", file_number);
ac461bd6 615 continue;
318b3106
DM
616 }
617 Err(BlockReadError::EndOfStream) => {
618 println!("got EOT");
619 return Ok(());
620 }
621 Err(BlockReadError::Error(err)) => {
622 return Err(err.into());
623 }
624 Ok(mut reader) => {
ac461bd6
DM
625 println!("got file number {}", file_number);
626
627 let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
628 match header {
629 Ok(header) => {
630 if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
631 println!("got MediaContentHeader with wrong magic: {:?}", header.magic);
f47e0357 632 } else if let Some(name) = proxmox_tape_magic_to_text(&header.content_magic) {
6334bdc1
FG
633 println!("got content header: {}", name);
634 println!(" uuid: {}", header.content_uuid());
635 println!(" ctime: {}", strftime_local("%c", header.ctime)?);
636 println!(" hsize: {}", HumanByte::from(header.size as usize));
637 println!(" part: {}", header.part_number);
ac461bd6 638 } else {
6334bdc1 639 println!("got unknown content header: {:?}", header.content_magic);
ac461bd6
DM
640 }
641 }
642 Err(err) => {
643 println!("unable to read content header - {}", err);
644 }
645 }
90461b76 646 let bytes = reader.skip_data()?;
3f803af0 647 println!("skipped {}", HumanByte::from(bytes));
90461b76
DM
648 if let Ok(true) = reader.has_end_marker() {
649 if reader.is_incomplete()? {
650 println!("WARNING: file is incomplete");
651 }
652 } else {
653 println!("WARNING: file without end marker");
654 }
ac461bd6
DM
655 }
656 }
657 }
658}
659
1e20f819
DM
660#[api(
661 input: {
662 properties: {
663 drive: {
664 schema: DRIVE_NAME_SCHEMA,
665 optional: true,
666 },
c297835b 667 "output-format": {
1e20f819
DM
668 schema: OUTPUT_FORMAT,
669 optional: true,
c297835b 670 },
1e20f819
DM
671 },
672 },
673)]
ee01737e 674/// Read Cartridge Memory (Medium auxiliary memory attributes)
85cdc4f3 675async fn cartridge_memory(mut param: Value) -> Result<(), Error> {
c297835b 676
671c6a96 677 let output_format = extract_output_format(&mut param);
1e20f819 678
1ce8e905 679 let (config, _digest) = pbs_config::drive::config()?;
1e20f819 680
85cdc4f3 681 let drive = extract_drive_name(&mut param, &config)?;
1e20f819 682
c297835b 683 let client = connect_to_localhost()?;
1e20f819 684
c297835b
DM
685 let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive);
686 let mut result = client.get(&path, Some(param)).await?;
687 let mut data = result["data"].take();
688
689 let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
1e20f819
DM
690
691 let options = default_table_format_options()
692 .column(ColumnConfig::new("id"))
693 .column(ColumnConfig::new("name"))
694 .column(ColumnConfig::new("value"))
695 ;
696
20128259 697 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
1e20f819
DM
698 Ok(())
699}
700
5f34d69b
DM
701#[api(
702 input: {
703 properties: {
704 drive: {
705 schema: DRIVE_NAME_SCHEMA,
706 optional: true,
707 },
c297835b 708 "output-format": {
5f34d69b
DM
709 schema: OUTPUT_FORMAT,
710 optional: true,
c297835b 711 },
5f34d69b
DM
712 },
713 },
714)]
715/// Read Volume Statistics (SCSI log page 17h)
85cdc4f3 716async fn volume_statistics(mut param: Value) -> Result<(), Error> {
c297835b 717
671c6a96 718 let output_format = extract_output_format(&mut param);
5f34d69b 719
1ce8e905 720 let (config, _digest) = pbs_config::drive::config()?;
5f34d69b 721
85cdc4f3 722 let drive = extract_drive_name(&mut param, &config)?;
5f34d69b 723
c297835b 724 let client = connect_to_localhost()?;
5f34d69b 725
c297835b
DM
726 let path = format!("api2/json/tape/drive/{}/volume-statistics", drive);
727 let mut result = client.get(&path, Some(param)).await?;
728 let mut data = result["data"].take();
729
730 let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
5f34d69b
DM
731
732 let options = default_table_format_options();
733
734 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
c297835b 735
5f34d69b
DM
736 Ok(())
737}
738
cb80d900
DM
739#[api(
740 input: {
741 properties: {
742 drive: {
743 schema: DRIVE_NAME_SCHEMA,
744 optional: true,
745 },
746 "output-format": {
747 schema: OUTPUT_FORMAT,
748 optional: true,
749 },
750 },
751 },
752)]
5ae86dfa 753/// Get drive/media status
85cdc4f3 754async fn status(mut param: Value) -> Result<(), Error> {
c297835b 755
671c6a96 756 let output_format = extract_output_format(&mut param);
cb80d900 757
1ce8e905 758 let (config, _digest) = pbs_config::drive::config()?;
cb80d900 759
85cdc4f3 760 let drive = extract_drive_name(&mut param, &config)?;
cb80d900 761
c297835b 762 let client = connect_to_localhost()?;
cb80d900 763
c297835b
DM
764 let path = format!("api2/json/tape/drive/{}/status", drive);
765 let mut result = client.get(&path, Some(param)).await?;
766 let mut data = result["data"].take();
767
768 let info = &api2::tape::drive::API_METHOD_STATUS;
cb80d900 769
337ff5a3
DM
770 let render_percentage = |value: &Value, _record: &Value| {
771 match value.as_f64() {
772 Some(wearout) => Ok(format!("{:.2}%", wearout*100.0)),
773 None => Ok(String::from("ERROR")), // should never happen
774 }
775 };
776
cb80d900
DM
777 let options = default_table_format_options()
778 .column(ColumnConfig::new("blocksize"))
779 .column(ColumnConfig::new("density"))
0892a512
DM
780 .column(ColumnConfig::new("compression"))
781 .column(ColumnConfig::new("buffer-mode"))
782 .column(ColumnConfig::new("write-protect"))
470f1c79 783 .column(ColumnConfig::new("alert-flags"))
cb80d900
DM
784 .column(ColumnConfig::new("file-number"))
785 .column(ColumnConfig::new("block-number"))
5ae86dfa
DM
786 .column(ColumnConfig::new("manufactured").renderer(render_epoch))
787 .column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable))
788 .column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable))
b40ab10d 789 .column(ColumnConfig::new("medium-passes"))
337ff5a3 790 .column(ColumnConfig::new("medium-wearout").renderer(render_percentage))
b40ab10d 791 .column(ColumnConfig::new("volume-mounts"))
cb80d900
DM
792 ;
793
794 format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
c297835b 795
cb80d900
DM
796 Ok(())
797}
798
df69a4fc
DM
799#[api(
800 input: {
801 properties: {
802 drive: {
803 schema: DRIVE_NAME_SCHEMA,
804 optional: true,
805 },
c297835b
DM
806 "output-format": {
807 schema: OUTPUT_FORMAT,
808 optional: true,
809 },
df69a4fc
DM
810 },
811 },
812)]
813/// Clean drive
85cdc4f3 814async fn clean_drive(mut param: Value) -> Result<(), Error> {
c297835b 815
671c6a96 816 let output_format = extract_output_format(&mut param);
df69a4fc 817
1ce8e905 818 let (config, _digest) = pbs_config::drive::config()?;
df69a4fc 819
85cdc4f3 820 let drive = extract_drive_name(&mut param, &config)?;
df69a4fc 821
d4877712 822 let client = connect_to_localhost()?;
df69a4fc 823
0320deb0
DC
824 let path = format!("api2/json/tape/drive/{}/clean", drive);
825 let result = client.put(&path, Some(param)).await?;
df69a4fc 826
d4877712 827 view_task_result(&client, result, &output_format).await?;
df69a4fc
DM
828
829 Ok(())
830}
831
88356646 832#[api(
5830e562 833 input: {
88356646 834 properties: {
0023cfa3
DM
835
836 // Note: We cannot use TapeBackupJobSetup, because drive needs to be optional here
837 //setup: {
838 // type: TapeBackupJobSetup,
839 // flatten: true,
840 //},
841
842 store: {
843 schema: DATASTORE_SCHEMA,
844 },
845 pool: {
846 schema: MEDIA_POOL_NAME_SCHEMA,
847 },
848 drive: {
849 schema: DRIVE_NAME_SCHEMA,
850 optional: true,
851 },
852 "eject-media": {
853 description: "Eject media upon job completion.",
854 type: bool,
855 optional: true,
856 },
857 "export-media-set": {
858 description: "Export media set upon job completion.",
859 type: bool,
860 optional: true,
861 },
862 "latest-only": {
863 description: "Backup latest snapshots only.",
864 type: bool,
865 optional: true,
edb90f6a 866 },
097ccfe1
DC
867 "notify-user": {
868 optional: true,
869 type: Userid,
870 },
efd2713a
DC
871 groups: {
872 schema: GROUP_FILTER_LIST_SCHEMA,
873 optional: true,
874 },
e384f16a
DC
875 "force-media-set": {
876 description: "Ignore the allocation policy and start a new media-set.",
877 optional: true,
878 type: bool,
879 default: false,
880 },
87f4be79
DM
881 "output-format": {
882 schema: OUTPUT_FORMAT,
883 optional: true,
884 },
88356646
DM
885 },
886 },
887)]
888/// Backup datastore to tape media pool
9883b54c 889async fn backup(mut param: Value) -> Result<(), Error> {
88356646 890
671c6a96 891 let output_format = extract_output_format(&mut param);
88356646 892
1ce8e905 893 let (config, _digest) = pbs_config::drive::config()?;
9883b54c 894
85cdc4f3 895 param["drive"] = extract_drive_name(&mut param, &config)?.into();
9883b54c 896
d4877712 897 let client = connect_to_localhost()?;
88356646 898
87f4be79
DM
899 let result = client.post("api2/json/tape/backup", Some(param)).await?;
900
d4877712 901 view_task_result(&client, result, &output_format).await?;
88356646
DM
902
903 Ok(())
904}
c297835b 905
b9b4b312
DM
906#[api(
907 input: {
908 properties: {
909 store: {
4c4e5c2b 910 schema: DATASTORE_MAP_LIST_SCHEMA,
b9b4b312 911 },
9883b54c
DM
912 drive: {
913 schema: DRIVE_NAME_SCHEMA,
914 optional: true,
915 },
b9b4b312
DM
916 "media-set": {
917 description: "Media set UUID.",
918 type: String,
919 },
5a5ee032
DC
920 "notify-user": {
921 type: Userid,
922 optional: true,
923 },
0ecdaa0d
DC
924 "snapshots": {
925 description: "List of snapshots.",
926 type: Array,
927 optional: true,
928 items: {
929 schema: TAPE_RESTORE_SNAPSHOT_SCHEMA,
930 },
931 },
e3613503
DC
932 owner: {
933 type: Authid,
934 optional: true,
935 },
c297835b
DM
936 "output-format": {
937 schema: OUTPUT_FORMAT,
938 optional: true,
939 },
b9b4b312
DM
940 },
941 },
942)]
943/// Restore data from media-set
9883b54c 944async fn restore(mut param: Value) -> Result<(), Error> {
c297835b 945
671c6a96 946 let output_format = extract_output_format(&mut param);
b9b4b312 947
1ce8e905 948 let (config, _digest) = pbs_config::drive::config()?;
9883b54c 949
85cdc4f3 950 param["drive"] = extract_drive_name(&mut param, &config)?.into();
9883b54c 951
d4877712 952 let client = connect_to_localhost()?;
b9b4b312 953
c297835b 954 let result = client.post("api2/json/tape/restore", Some(param)).await?;
b9b4b312 955
d4877712 956 view_task_result(&client, result, &output_format).await?;
b9b4b312
DM
957
958 Ok(())
959}
88356646 960
b017bbc4
DM
961#[api(
962 input: {
963 properties: {
964 drive: {
965 schema: DRIVE_NAME_SCHEMA,
966 optional: true,
967 },
968 force: {
969 description: "Force overriding existing index.",
970 type: bool,
971 optional: true,
972 },
c553407e
DM
973 scan: {
974 description: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
975 type: bool,
976 optional: true,
977 },
b017bbc4
DM
978 verbose: {
979 description: "Verbose mode - log all found chunks.",
980 type: bool,
981 optional: true,
982 },
983 "output-format": {
984 schema: OUTPUT_FORMAT,
985 optional: true,
986 },
987 },
988 },
989)]
990/// Scan media and record content
85cdc4f3 991async fn catalog_media(mut param: Value) -> Result<(), Error> {
c297835b 992
671c6a96 993 let output_format = extract_output_format(&mut param);
b017bbc4 994
1ce8e905 995 let (config, _digest) = pbs_config::drive::config()?;
b017bbc4 996
85cdc4f3 997 let drive = extract_drive_name(&mut param, &config)?;
b017bbc4 998
d4877712 999 let client = connect_to_localhost()?;
b017bbc4 1000
c297835b
DM
1001 let path = format!("api2/json/tape/drive/{}/catalog", drive);
1002 let result = client.post(&path, Some(param)).await?;
b017bbc4 1003
d4877712 1004 view_task_result(&client, result, &output_format).await?;
b017bbc4
DM
1005
1006 Ok(())
1007}
1008
e6604cf3
DM
1009fn main() {
1010
1011 let cmd_def = CliCommandMap::new()
88356646
DM
1012 .insert(
1013 "backup",
1014 CliCommand::new(&API_METHOD_BACKUP)
1015 .arg_param(&["store", "pool"])
5830e562 1016 .completion_cb("drive", complete_drive_name)
88356646
DM
1017 .completion_cb("store", complete_datastore_name)
1018 .completion_cb("pool", complete_pool_name)
efd2713a 1019 .completion_cb("groups", complete_datastore_group_filter)
88356646 1020 )
b9b4b312
DM
1021 .insert(
1022 "restore",
1023 CliCommand::new(&API_METHOD_RESTORE)
0ecdaa0d 1024 .arg_param(&["media-set", "store", "snapshots"])
b9b4b312
DM
1025 .completion_cb("store", complete_datastore_name)
1026 .completion_cb("media-set", complete_media_set_uuid)
0ecdaa0d 1027 .completion_cb("snapshots", complete_media_set_snapshots)
b9b4b312 1028 )
bff7e3f3
DM
1029 .insert(
1030 "barcode-label",
1031 CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
1032 .completion_cb("drive", complete_drive_name)
1033 .completion_cb("pool", complete_pool_name)
1034 )
5fb694e8
DM
1035 .insert(
1036 "rewind",
1037 CliCommand::new(&API_METHOD_REWIND)
1038 .completion_cb("drive", complete_drive_name)
1039 )
ac461bd6
DM
1040 .insert(
1041 "scan",
1042 CliCommand::new(&API_METHOD_DEBUG_SCAN)
1043 .completion_cb("drive", complete_drive_name)
1044 )
cb80d900
DM
1045 .insert(
1046 "status",
1047 CliCommand::new(&API_METHOD_STATUS)
1048 .completion_cb("drive", complete_drive_name)
1049 )
ce955e16
DM
1050 .insert(
1051 "eod",
1052 CliCommand::new(&API_METHOD_MOVE_TO_EOM)
1053 .completion_cb("drive", complete_drive_name)
1054 )
583a68a4 1055 .insert(
e29f456e
DM
1056 "format",
1057 CliCommand::new(&API_METHOD_FORMAT_MEDIA)
583a68a4
DM
1058 .completion_cb("drive", complete_drive_name)
1059 )
0098b712
DM
1060 .insert(
1061 "eject",
1062 CliCommand::new(&API_METHOD_EJECT_MEDIA)
1063 .completion_cb("drive", complete_drive_name)
83abc749
DM
1064 )
1065 .insert(
1066 "inventory",
1067 CliCommand::new(&API_METHOD_INVENTORY)
1068 .completion_cb("drive", complete_drive_name)
0098b712 1069 )
4606f343
DM
1070 .insert(
1071 "read-label",
1072 CliCommand::new(&API_METHOD_READ_LABEL)
1073 .completion_cb("drive", complete_drive_name)
1074 )
b017bbc4
DM
1075 .insert(
1076 "catalog",
1077 CliCommand::new(&API_METHOD_CATALOG_MEDIA)
1078 .completion_cb("drive", complete_drive_name)
1079 )
1e20f819 1080 .insert(
ee01737e
DM
1081 "cartridge-memory",
1082 CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
1e20f819
DM
1083 .completion_cb("drive", complete_drive_name)
1084 )
5f34d69b
DM
1085 .insert(
1086 "volume-statistics",
1087 CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
1088 .completion_cb("drive", complete_drive_name)
1089 )
df69a4fc
DM
1090 .insert(
1091 "clean",
1092 CliCommand::new(&API_METHOD_CLEAN_DRIVE)
1093 .completion_cb("drive", complete_drive_name)
1094 )
7bb720cb
DM
1095 .insert(
1096 "label",
1097 CliCommand::new(&API_METHOD_LABEL_MEDIA)
1098 .completion_cb("drive", complete_drive_name)
1099 .completion_cb("pool", complete_pool_name)
1100
1101 )
e6604cf3
DM
1102 .insert("changer", changer_commands())
1103 .insert("drive", drive_commands())
9700d537 1104 .insert("pool", pool_commands())
fba0b774 1105 .insert("media", media_commands())
d5a48b5c 1106 .insert("key", encryption_key_commands())
be327dbc 1107 .insert("backup-job", backup_job_commands())
e49f0c03
DM
1108 .insert(
1109 "load-media",
1110 CliCommand::new(&API_METHOD_LOAD_MEDIA)
8446fbca 1111 .arg_param(&["label-text"])
e49f0c03 1112 .completion_cb("drive", complete_drive_name)
8446fbca 1113 .completion_cb("label-text", complete_media_label_text)
e49f0c03 1114 )
46a1863f
DM
1115 .insert(
1116 "load-media-from-slot",
1117 CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT)
b63f833d 1118 .arg_param(&["source-slot"])
46a1863f
DM
1119 .completion_cb("drive", complete_drive_name)
1120 )
1121 .insert(
1122 "unload",
1123 CliCommand::new(&API_METHOD_UNLOAD_MEDIA)
1124 .completion_cb("drive", complete_drive_name)
1125 )
483da89d
DM
1126 .insert(
1127 "export-media",
1128 CliCommand::new(&API_METHOD_EXPORT_MEDIA)
8446fbca 1129 .arg_param(&["label-text"])
483da89d 1130 .completion_cb("drive", complete_drive_name)
8446fbca 1131 .completion_cb("label-text", complete_media_label_text)
483da89d 1132 )
e6604cf3
DM
1133 ;
1134
1135 let mut rpcenv = CliEnvironment::new();
1136 rpcenv.set_auth_id(Some(String::from("root@pam")));
1137
9a1b24b6 1138 proxmox_async::runtime::main(run_async_cli_command(cmd_def, rpcenv));
e6604cf3 1139}