-use anyhow::{format_err, Error};
+use std::collections::HashMap;
+
+use anyhow::{bail, format_err, Error};
use serde_json::{json, Value};
-use proxmox::{
- api::{
- api,
- cli::*,
- ApiHandler,
- RpcEnvironment,
- section_config::SectionConfigData,
- },
- tools::{
- time::strftime_local,
- io::ReadExt,
- },
+use proxmox_io::ReadExt;
+use proxmox_router::RpcEnvironment;
+use proxmox_router::cli::*;
+use proxmox_schema::api;
+use proxmox_section_config::SectionConfigData;
+use proxmox_time::strftime_local;
+
+use pbs_client::view_task_result;
+use pbs_tools::format::{
+ render_epoch,
+ render_bytes_human_readable,
+};
+
+use pbs_config::drive::complete_drive_name;
+use pbs_config::media_pool::complete_pool_name;
+use pbs_config::datastore::complete_datastore_name;
+
+use pbs_api_types::{
+ Userid, Authid, DATASTORE_SCHEMA, DATASTORE_MAP_LIST_SCHEMA,
+ DRIVE_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA,
+ TAPE_RESTORE_SNAPSHOT_SCHEMA, GROUP_FILTER_LIST_SCHEMA, GroupListItem,
+ HumanByte
+};
+use pbs_tape::{
+ PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, BlockReadError, MediaContentHeader,
};
use proxmox_backup::{
- tools::format::{
- HumanByte,
- render_epoch,
- render_bytes_human_readable,
- },
- server::{
- UPID,
- worker_is_active_local,
- },
- api2::{
- self,
- types::{
- DATASTORE_SCHEMA,
- DRIVE_NAME_SCHEMA,
- MEDIA_LABEL_SCHEMA,
- MEDIA_POOL_NAME_SCHEMA,
- },
- },
- config::{
- self,
- datastore::complete_datastore_name,
- drive::complete_drive_name,
- media_pool::complete_pool_name,
- },
+ api2,
tape::{
- open_drive,
- complete_media_changer_id,
+ drive::{
+ open_drive,
+ lock_tape_device,
+ set_tape_device_state,
+ },
+ complete_media_label_text,
complete_media_set_uuid,
+ complete_media_set_snapshots,
file_formats::{
- PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
- PROXMOX_BACKUP_CONTENT_NAME,
- MediaContentHeader,
+ proxmox_tape_magic_to_text,
},
},
+ client_helpers::connect_to_localhost,
};
mod proxmox_tape;
use proxmox_tape::*;
-// Note: local workers should print logs to stdout, so there is no need
-// to fetch/display logs. We just wait for the worker to finish.
-pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
+async fn get_backup_groups(store: &str) -> Result<Vec<GroupListItem>, Error> {
+ let client = connect_to_localhost()?;
+ let api_res = client
+ .get(&format!("api2/json/admin/datastore/{}/groups", store), None)
+ .await?;
- let upid: UPID = upid_str.parse()?;
+ match api_res.get("data") {
+ Some(data) => Ok(serde_json::from_value::<Vec<GroupListItem>>(data.to_owned())?),
+ None => bail!("could not get group list"),
+ }
+}
- let sleep_duration = core::time::Duration::new(0, 100_000_000);
+// shell completion helper
+pub fn complete_datastore_group_filter(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
- loop {
- if worker_is_active_local(&upid) {
- tokio::time::delay_for(sleep_duration).await;
- } else {
- break;
+ let mut list = Vec::new();
+
+ list.push("regex:".to_string());
+ list.push("type:ct".to_string());
+ list.push("type:host".to_string());
+ list.push("type:vm".to_string());
+
+ if let Some(store) = param.get("store") {
+ let groups = proxmox_async::runtime::block_on(async { get_backup_groups(store).await });
+ if let Ok(groups) = groups {
+ list.extend(groups.iter().map(|group| format!("group:{}/{}", group.backup_type, group.backup_id)));
}
}
- Ok(())
+
+ list
}
-fn lookup_drive_name(
- param: &Value,
+pub fn extract_drive_name(
+ param: &mut Value,
config: &SectionConfigData,
) -> Result<String, Error> {
})
.ok_or_else(|| format_err!("unable to get (default) drive name"))?;
+ if let Some(map) = param.as_object_mut() {
+ map.remove("drive");
+ }
+
Ok(drive)
}
optional: true,
default: true,
},
- },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ },
},
)]
-/// Erase media
-async fn erase_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+/// Format media
+async fn format_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_ERASE_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ let path = format!("api2/json/tape/drive/{}/format-media", drive);
+ let result = client.post(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Rewind tape
-async fn rewind(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn rewind(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_REWIND;
+ let drive = extract_drive_name(&mut param, &config)?;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/rewind", drive);
+ let result = client.post(&path, Some(param)).await?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Eject/Unload drive media
-async fn eject_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn eject_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_EJECT_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/eject-media", drive);
+ let result = client.post(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
- "changer-id": {
+ "label-text": {
schema: MEDIA_LABEL_SCHEMA,
},
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Load media with specified label
-async fn load_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn load_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_LOAD_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/load-media", drive);
+ let result = client.post(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ drive: {
+ schema: DRIVE_NAME_SCHEMA,
+ optional: true,
+ },
+ "label-text": {
+ schema: MEDIA_LABEL_SCHEMA,
+ },
+ },
+ },
+)]
+/// Export media with specified label
+async fn export_media(mut param: Value) -> Result<(), Error> {
+
+ let (config, _digest) = pbs_config::drive::config()?;
+
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/export-media", drive);
+ client.put(&path, Some(param)).await?;
Ok(())
}
},
)]
/// Load media from the specified slot
-async fn load_media_from_slot(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn load_media_from_slot(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let (config, _digest) = pbs_config::drive::config()?;
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let drive = extract_drive_name(&mut param, &config)?;
- let info = &api2::tape::drive::API_METHOD_LOAD_SLOT;
+ let client = connect_to_localhost()?;
- match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
+ let path = format!("api2/json/tape/drive/{}/load-slot", drive);
+ client.put(&path, Some(param)).await?;
Ok(())
}
minimum: 1,
optional: true,
},
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Unload media via changer
-async fn unload_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn unload_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_UNLOAD;
+ let drive = extract_drive_name(&mut param, &config)?;
- match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/unload", drive);
+ let result = client.post(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
- "changer-id": {
+ "label-text": {
schema: MEDIA_LABEL_SCHEMA,
},
- },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ },
},
)]
/// Label media
-async fn label_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn label_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_LABEL_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/label-media", drive);
+ let result = client.post(&path, Some(param)).await?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
- "output-format": {
+ inventorize: {
+ description: "Inventorize media",
+ type: bool,
+ optional: true,
+ },
+ "output-format": {
schema: OUTPUT_FORMAT,
optional: true,
},
},
)]
/// Read media label
-async fn read_label(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn read_label(mut param: Value) -> Result<(), Error> {
+
+ let output_format = extract_output_format(&mut param);
+
+ let (config, _digest) = pbs_config::drive::config()?;
- let (config, _digest) = config::drive::config()?;
+ let drive = extract_drive_name(&mut param, &config)?;
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/read-label", drive);
+ let mut result = client.get(&path, Some(param)).await?;
+ let mut data = result["data"].take();
- let output_format = get_output_format(¶m);
let info = &api2::tape::drive::API_METHOD_READ_LABEL;
- let mut data = match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
let options = default_table_format_options()
- .column(ColumnConfig::new("changer-id"))
+ .column(ColumnConfig::new("label-text"))
.column(ColumnConfig::new("uuid"))
.column(ColumnConfig::new("ctime").renderer(render_epoch))
.column(ColumnConfig::new("pool"))
.column(ColumnConfig::new("media-set-uuid"))
.column(ColumnConfig::new("media-set-ctime").renderer(render_epoch))
+ .column(ColumnConfig::new("encryption-key-fingerprint"))
;
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
async fn inventory(
read_labels: Option<bool>,
read_all_labels: Option<bool>,
- param: Value,
- rpcenv: &mut dyn RpcEnvironment,
+ mut param: Value,
) -> Result<(), Error> {
- let output_format = get_output_format(¶m);
+ let output_format = extract_output_format(&mut param);
- let (config, _digest) = config::drive::config()?;
- let drive = lookup_drive_name(¶m, &config)?;
+ let (config, _digest) = pbs_config::drive::config()?;
+ let drive = extract_drive_name(&mut param, &config)?;
let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false);
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/inventory", drive);
+
if do_read {
- let mut param = json!({
- "drive": &drive,
- });
+
+ let mut param = json!({});
if let Some(true) = read_all_labels {
param["read-all-labels"] = true.into();
}
- let info = &api2::tape::drive::API_METHOD_UPDATE_INVENTORY;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
- wait_for_local_worker(result.as_str().unwrap()).await?;
+
+ let result = client.put(&path, Some(param)).await?; // update inventory
+ view_task_result(&client, result, &output_format).await?;
}
- let info = &api2::tape::drive::API_METHOD_INVENTORY;
+ let mut result = client.get(&path, None).await?;
+ let mut data = result["data"].take();
- let param = json!({ "drive": &drive });
- let mut data = match info.handler {
- ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
- _ => unreachable!(),
- };
+ let info = &api2::tape::drive::API_METHOD_INVENTORY;
let options = default_table_format_options()
- .column(ColumnConfig::new("changer-id"))
+ .column(ColumnConfig::new("label-text"))
.column(ColumnConfig::new("uuid"))
;
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Label media with barcodes from changer device
-async fn barcode_label_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn barcode_label_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_BARCODE_LABEL_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive);
+ let result = client.post(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
},
)]
/// Move to end of media (MTEOM, used to debug)
-fn move_to_eom(param: Value) -> Result<(), Error> {
+fn move_to_eom(mut param: Value) -> Result<(), Error> {
+
+ let (config, _digest) = pbs_config::drive::config()?;
- let (config, _digest) = config::drive::config()?;
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let _lock = lock_tape_device(&config, &drive)?;
+ set_tape_device_state(&drive, "moving to eom")?;
- let drive = lookup_drive_name(¶m, &config)?;
let mut drive = open_drive(&config, &drive)?;
- drive.move_to_eom()?;
+ drive.move_to_eom(false)?;
Ok(())
}
///
/// Note: This reads unless the driver returns an IO Error, so this
/// method is expected to fails when we reach EOT.
-fn debug_scan(param: Value) -> Result<(), Error> {
+fn debug_scan(mut param: Value) -> Result<(), Error> {
+
+ let (config, _digest) = pbs_config::drive::config()?;
- let (config, _digest) = config::drive::config()?;
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let _lock = lock_tape_device(&config, &drive)?;
+ set_tape_device_state(&drive, "debug scan")?;
- let drive = lookup_drive_name(¶m, &config)?;
let mut drive = open_drive(&config, &drive)?;
println!("rewinding tape");
loop {
let file_number = drive.current_file_number()?;
- match drive.read_next_file()? {
- None => {
- println!("EOD");
+ match drive.read_next_file() {
+ Err(BlockReadError::EndOfFile) => {
+ println!("filemark number {}", file_number);
continue;
- },
- Some(mut reader) => {
+ }
+ Err(BlockReadError::EndOfStream) => {
+ println!("got EOT");
+ return Ok(());
+ }
+ Err(BlockReadError::Error(err)) => {
+ return Err(err.into());
+ }
+ Ok(mut reader) => {
println!("got file number {}", file_number);
let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
Ok(header) => {
if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
println!("got MediaContentHeader with wrong magic: {:?}", header.magic);
+ } else if let Some(name) = proxmox_tape_magic_to_text(&header.content_magic) {
+ println!("got content header: {}", name);
+ println!(" uuid: {}", header.content_uuid());
+ println!(" ctime: {}", strftime_local("%c", header.ctime)?);
+ println!(" hsize: {}", HumanByte::from(header.size as usize));
+ println!(" part: {}", header.part_number);
} else {
- if let Some(name) = PROXMOX_BACKUP_CONTENT_NAME.get(&header.content_magic) {
- println!("got content header: {}", name);
- println!(" uuid: {}", header.content_uuid());
- println!(" ctime: {}", strftime_local("%c", header.ctime)?);
- println!(" hsize: {}", HumanByte::from(header.size as usize));
- println!(" part: {}", header.part_number);
- } else {
- println!("got unknown content header: {:?}", header.content_magic);
- }
+ println!("got unknown content header: {:?}", header.content_magic);
}
}
Err(err) => {
println!("unable to read content header - {}", err);
}
}
- let bytes = reader.skip_to_end()?;
+ let bytes = reader.skip_data()?;
println!("skipped {}", HumanByte::from(bytes));
+ if let Ok(true) = reader.has_end_marker() {
+ if reader.is_incomplete()? {
+ println!("WARNING: file is incomplete");
+ }
+ } else {
+ println!("WARNING: file without end marker");
+ }
}
}
}
schema: DRIVE_NAME_SCHEMA,
optional: true,
},
- "output-format": {
+ "output-format": {
schema: OUTPUT_FORMAT,
optional: true,
- },
+ },
},
},
)]
/// Read Cartridge Memory (Medium auxiliary memory attributes)
-fn cartridge_memory(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn cartridge_memory(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let output_format = get_output_format(¶m);
- let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
+ let drive = extract_drive_name(&mut param, &config)?;
- let mut data = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive);
+ let mut result = client.get(&path, Some(param)).await?;
+ let mut data = result["data"].take();
+
+ let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
let options = default_table_format_options()
.column(ColumnConfig::new("id"))
Ok(())
}
+#[api(
+ input: {
+ properties: {
+ drive: {
+ schema: DRIVE_NAME_SCHEMA,
+ optional: true,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ },
+ },
+)]
+/// Read Volume Statistics (SCSI log page 17h)
+async fn volume_statistics(mut param: Value) -> Result<(), Error> {
+
+ let output_format = extract_output_format(&mut param);
+
+ let (config, _digest) = pbs_config::drive::config()?;
+
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/volume-statistics", drive);
+ let mut result = client.get(&path, Some(param)).await?;
+ let mut data = result["data"].take();
+
+ let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
+
+ let options = default_table_format_options();
+
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(())
+}
+
#[api(
input: {
properties: {
},
)]
/// Get drive/media status
-fn status(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn status(mut param: Value) -> Result<(), Error> {
+
+ let output_format = extract_output_format(&mut param);
- let (config, _digest) = config::drive::config()?;
+ let (config, _digest) = pbs_config::drive::config()?;
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/status", drive);
+ let mut result = client.get(&path, Some(param)).await?;
+ let mut data = result["data"].take();
- let output_format = get_output_format(¶m);
let info = &api2::tape::drive::API_METHOD_STATUS;
- let mut data = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
+ let render_percentage = |value: &Value, _record: &Value| {
+ match value.as_f64() {
+ Some(wearout) => Ok(format!("{:.2}%", wearout*100.0)),
+ None => Ok(String::from("ERROR")), // should never happen
+ }
};
let options = default_table_format_options()
.column(ColumnConfig::new("blocksize"))
.column(ColumnConfig::new("density"))
- .column(ColumnConfig::new("status"))
+ .column(ColumnConfig::new("compression"))
+ .column(ColumnConfig::new("buffer-mode"))
+ .column(ColumnConfig::new("write-protect"))
.column(ColumnConfig::new("alert-flags"))
.column(ColumnConfig::new("file-number"))
.column(ColumnConfig::new("block-number"))
.column(ColumnConfig::new("bytes-written").renderer(render_bytes_human_readable))
.column(ColumnConfig::new("bytes-read").renderer(render_bytes_human_readable))
.column(ColumnConfig::new("medium-passes"))
+ .column(ColumnConfig::new("medium-wearout").renderer(render_percentage))
.column(ColumnConfig::new("volume-mounts"))
;
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
Ok(())
}
#[api(
- input: {
+ input: {
+ properties: {
+ drive: {
+ schema: DRIVE_NAME_SCHEMA,
+ optional: true,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ },
+ },
+)]
+/// Clean drive
+async fn clean_drive(mut param: Value) -> Result<(), Error> {
+
+ let output_format = extract_output_format(&mut param);
+
+ let (config, _digest) = pbs_config::drive::config()?;
+
+ let drive = extract_drive_name(&mut param, &config)?;
+
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/clean", drive);
+ let result = client.put(&path, Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
+
+ Ok(())
+}
+
+#[api(
+ input: {
properties: {
+
+ // Note: We cannot use TapeBackupJobSetup, because drive needs to be optional here
+ //setup: {
+ // type: TapeBackupJobSetup,
+ // flatten: true,
+ //},
+
store: {
schema: DATASTORE_SCHEMA,
},
pool: {
schema: MEDIA_POOL_NAME_SCHEMA,
},
+ drive: {
+ schema: DRIVE_NAME_SCHEMA,
+ optional: true,
+ },
+ "eject-media": {
+ description: "Eject media upon job completion.",
+ type: bool,
+ optional: true,
+ },
+ "export-media-set": {
+ description: "Export media set upon job completion.",
+ type: bool,
+ optional: true,
+ },
+ "latest-only": {
+ description: "Backup latest snapshots only.",
+ type: bool,
+ optional: true,
+ },
+ "notify-user": {
+ optional: true,
+ type: Userid,
+ },
+ groups: {
+ schema: GROUP_FILTER_LIST_SCHEMA,
+ optional: true,
+ },
+ "force-media-set": {
+ description: "Ignore the allocation policy and start a new media-set.",
+ optional: true,
+ type: bool,
+ default: false,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Backup datastore to tape media pool
-async fn backup(
- param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn backup(mut param: Value) -> Result<(), Error> {
- let info = &api2::tape::backup::API_METHOD_BACKUP;
+ let output_format = extract_output_format(&mut param);
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let (config, _digest) = pbs_config::drive::config()?;
+
+ param["drive"] = extract_drive_name(&mut param, &config)?.into();
+
+ let client = connect_to_localhost()?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ let result = client.post("api2/json/tape/backup", Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
+
#[api(
input: {
properties: {
store: {
- schema: DATASTORE_SCHEMA,
+ schema: DATASTORE_MAP_LIST_SCHEMA,
+ },
+ drive: {
+ schema: DRIVE_NAME_SCHEMA,
+ optional: true,
},
"media-set": {
description: "Media set UUID.",
type: String,
},
+ "notify-user": {
+ type: Userid,
+ optional: true,
+ },
+ "snapshots": {
+ description: "List of snapshots.",
+ type: Array,
+ optional: true,
+ items: {
+ schema: TAPE_RESTORE_SNAPSHOT_SCHEMA,
+ },
+ },
+ owner: {
+ type: Authid,
+ optional: true,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
},
},
)]
/// Restore data from media-set
-async fn restore(
- param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn restore(mut param: Value) -> Result<(), Error> {
- let info = &api2::tape::restore::API_METHOD_RESTORE;
+ let output_format = extract_output_format(&mut param);
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let (config, _digest) = pbs_config::drive::config()?;
+
+ param["drive"] = extract_drive_name(&mut param, &config)?.into();
+
+ let client = connect_to_localhost()?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ let result = client.post("api2/json/tape/restore", Some(param)).await?;
+
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
type: bool,
optional: true,
},
+ scan: {
+ description: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
+ type: bool,
+ optional: true,
+ },
verbose: {
description: "Verbose mode - log all found chunks.",
type: bool,
},
)]
/// Scan media and record content
-async fn catalog_media(
- mut param: Value,
- rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn catalog_media(mut param: Value) -> Result<(), Error> {
- let (config, _digest) = config::drive::config()?;
+ let output_format = extract_output_format(&mut param);
- param["drive"] = lookup_drive_name(¶m, &config)?.into();
+ let (config, _digest) = pbs_config::drive::config()?;
- let info = &api2::tape::drive::API_METHOD_CATALOG_MEDIA;
+ let drive = extract_drive_name(&mut param, &config)?;
- let result = match info.handler {
- ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
- _ => unreachable!(),
- };
+ let client = connect_to_localhost()?;
+
+ let path = format!("api2/json/tape/drive/{}/catalog", drive);
+ let result = client.post(&path, Some(param)).await?;
- wait_for_local_worker(result.as_str().unwrap()).await?;
+ view_task_result(&client, result, &output_format).await?;
Ok(())
}
"backup",
CliCommand::new(&API_METHOD_BACKUP)
.arg_param(&["store", "pool"])
+ .completion_cb("drive", complete_drive_name)
.completion_cb("store", complete_datastore_name)
.completion_cb("pool", complete_pool_name)
+ .completion_cb("groups", complete_datastore_group_filter)
)
.insert(
"restore",
CliCommand::new(&API_METHOD_RESTORE)
- .arg_param(&["media-set", "store"])
+ .arg_param(&["media-set", "store", "snapshots"])
.completion_cb("store", complete_datastore_name)
.completion_cb("media-set", complete_media_set_uuid)
+ .completion_cb("snapshots", complete_media_set_snapshots)
)
.insert(
"barcode-label",
.completion_cb("drive", complete_drive_name)
)
.insert(
- "erase",
- CliCommand::new(&API_METHOD_ERASE_MEDIA)
+ "format",
+ CliCommand::new(&API_METHOD_FORMAT_MEDIA)
.completion_cb("drive", complete_drive_name)
)
.insert(
CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY)
.completion_cb("drive", complete_drive_name)
)
+ .insert(
+ "volume-statistics",
+ CliCommand::new(&API_METHOD_VOLUME_STATISTICS)
+ .completion_cb("drive", complete_drive_name)
+ )
+ .insert(
+ "clean",
+ CliCommand::new(&API_METHOD_CLEAN_DRIVE)
+ .completion_cb("drive", complete_drive_name)
+ )
.insert(
"label",
CliCommand::new(&API_METHOD_LABEL_MEDIA)
.insert("drive", drive_commands())
.insert("pool", pool_commands())
.insert("media", media_commands())
+ .insert("key", encryption_key_commands())
+ .insert("backup-job", backup_job_commands())
.insert(
"load-media",
CliCommand::new(&API_METHOD_LOAD_MEDIA)
- .arg_param(&["changer-id"])
+ .arg_param(&["label-text"])
.completion_cb("drive", complete_drive_name)
- .completion_cb("changer-id", complete_media_changer_id)
+ .completion_cb("label-text", complete_media_label_text)
)
.insert(
"load-media-from-slot",
CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT)
- .arg_param(&["slot"])
+ .arg_param(&["source-slot"])
.completion_cb("drive", complete_drive_name)
)
.insert(
CliCommand::new(&API_METHOD_UNLOAD_MEDIA)
.completion_cb("drive", complete_drive_name)
)
+ .insert(
+ "export-media",
+ CliCommand::new(&API_METHOD_EXPORT_MEDIA)
+ .arg_param(&["label-text"])
+ .completion_cb("drive", complete_drive_name)
+ .completion_cb("label-text", complete_media_label_text)
+ )
;
let mut rpcenv = CliEnvironment::new();
rpcenv.set_auth_id(Some(String::from("root@pam")));
- proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
+ proxmox_async::runtime::main(run_async_cli_command(cmd_def, rpcenv));
}