diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index a9372975..4d9e9065 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -88,6 +88,7 @@ pub async fn load_media(drive: String, changer_id: String) -> Result<(), Error> changer.load_media(&changer_id) }).await? } + #[api( input: { properties: { @@ -114,6 +115,37 @@ pub async fn load_slot(drive: String, source_slot: u64) -> Result<(), Error> { }).await? } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + }, + "changer-id": { + schema: MEDIA_LABEL_SCHEMA, + }, + }, + }, + returns: { + description: "The import-export slot number the media was transfered to.", + type: u64, + minimum: 1, + }, +)] +/// Export media with specified label +pub async fn export_media(drive: String, changer_id: String) -> Result { + + let (config, _digest) = config::drive::config()?; + + tokio::task::spawn_blocking(move || { + let (mut changer, changer_name) = required_media_changer(&config, &drive)?; + match changer.export_media(&changer_id)? { + Some(slot) => Ok(slot), + None => bail!("media '{}' is not online (via changer '{}')", changer_id, changer_name), + } + }).await? +} + #[api( input: { properties: { diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index 92ad157e..4850008a 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -236,6 +236,39 @@ async fn load_media( Ok(()) } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + optional: true, + }, + "changer-id": { + schema: MEDIA_LABEL_SCHEMA, + }, + }, + }, +)] +/// Export media with specified label +async fn export_media( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + param["drive"] = lookup_drive_name(¶m, &config)?.into(); + + let info = &api2::tape::drive::API_METHOD_EXPORT_MEDIA; + + match info.handler { + ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, + _ => unreachable!(), + }; + + Ok(()) +} + #[api( input: { properties: { @@ -932,6 +965,13 @@ fn main() { CliCommand::new(&API_METHOD_UNLOAD_MEDIA) .completion_cb("drive", complete_drive_name) ) + .insert( + "export-media", + CliCommand::new(&API_METHOD_EXPORT_MEDIA) + .arg_param(&["changer-id"]) + .completion_cb("drive", complete_drive_name) + .completion_cb("changer-id", complete_media_changer_id) + ) ; let mut rpcenv = CliEnvironment::new(); diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs index aa2b35ff..954a879d 100644 --- a/src/tape/changer/mod.rs +++ b/src/tape/changer/mod.rs @@ -10,7 +10,7 @@ pub use mtx_wrapper::*; mod mtx; pub use mtx::*; -use anyhow::{bail, Error}; +use anyhow::Error; /// Interface to media change devices pub trait MediaChange { @@ -80,34 +80,5 @@ pub trait MediaChange { /// By moving the media to an empty import-export slot. Returns /// Some(slot) if the media was exported. Returns None if the media is /// not online (already exported). - fn export_media(&mut self, changer_id: &str) -> Result, Error> { - let status = self.status()?; - - let mut from = None; - let mut to = None; - - for (i, (import_export, element_status)) in status.slots.iter().enumerate() { - if *import_export { - if to.is_some() { continue; } - if let ElementStatus::Empty = element_status { - to = Some(i as u64 + 1); - } - } else { - if let ElementStatus::VolumeTag(ref tag) = element_status { - if tag == changer_id { - from = Some(i as u64 + 1); - } - } - } - } - match (from, to) { - (Some(from), Some(to)) => { - self.transfer_media(from, to)?; - Ok(Some(to)) - } - (Some(_from), None) => bail!("unable to find free export slot"), - (None, _) => Ok(None), // not online - } - } - + fn export_media(&mut self, changer_id: &str) -> Result, Error>; } diff --git a/src/tape/changer/mtx.rs b/src/tape/changer/mtx.rs index 0f5c497b..f4096429 100644 --- a/src/tape/changer/mtx.rs +++ b/src/tape/changer/mtx.rs @@ -48,6 +48,7 @@ fn unload_to_free_slot(drive_name: &str, path: &str, status: &MtxStatus, drivenu } let drive_status = &status.drives[drivenum as usize]; if let Some(slot) = drive_status.loaded_slot { + // fixme: check if slot is free mtx_unload(path, slot, drivenum) } else { let mut free_slot = None; @@ -174,4 +175,54 @@ impl MediaChange for MtxMediaChanger { Ok(()) } + + fn export_media(&mut self, changer_id: &str) -> Result, Error> { + let status = self.status()?; + + let mut from_drive = None; + if let Some(drive_status) = status.drives.get(self.drivenum as usize) { + if let ElementStatus::VolumeTag(ref tag) = drive_status.status { + if tag == changer_id { + from_drive = Some(self.drivenum); + } + } + } + + let mut from = None; + let mut to = None; + + for (i, (import_export, element_status)) in status.slots.iter().enumerate() { + if *import_export { + if to.is_some() { continue; } + if let ElementStatus::Empty = element_status { + to = Some(i as u64 + 1); + } + } else { + if let ElementStatus::VolumeTag(ref tag) = element_status { + if tag == changer_id { + from = Some(i as u64 + 1); + } + } + } + } + + if let Some(drivenum) = from_drive { + match to { + Some(to) => { + mtx_unload(&self.config.path, to, drivenum)?; + Ok(Some(to)) + } + None => bail!("unable to find free export slot"), + } + } else { + match (from, to) { + (Some(from), Some(to)) => { + self.transfer_media(from, to)?; + Ok(Some(to)) + } + (Some(_from), None) => bail!("unable to find free export slot"), + (None, _) => Ok(None), // not online + } + } + } } diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index 998201dc..ce6fe7c8 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -394,7 +394,11 @@ impl MediaChange for VirtualTapeHandle { } fn transfer_media(&mut self, _from: u64, _to: u64) -> Result<(), Error> { - bail!("medfia tranfer is not implemented!"); + bail!("media tranfer is not implemented!"); + } + + fn export_media(&mut self, _changer_id: &str) -> Result, Error> { + bail!("media export is not implemented!"); } fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> { @@ -461,6 +465,11 @@ impl MediaChange for VirtualTapeDrive { handle.transfer_media(from, to) } + fn export_media(&mut self, changer_id: &str) -> Result, Error> { + let mut handle = self.open()?; + handle.export_media(changer_id) + } + fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> { let mut handle = self.open()?; handle.load_media_from_slot(slot)