diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 8a2b6cad..25b03bf9 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -480,6 +480,53 @@ pub async fn read_label(drive: String) -> Result { }).await? } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + }, + }, + }, + returns: { + schema: UPID_SCHEMA, + }, +)] +/// Clean drive +pub fn clean_drive( + drive: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + let (config, _digest) = config::drive::config()?; + + check_drive_exists(&config, &drive)?; // early check before starting worker + + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + + let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; + + let upid_str = WorkerTask::new_thread( + "clean-drive", + Some(drive.clone()), + auth_id, + to_stdout, + move |worker| { + + let (mut changer, _changer_name) = required_media_changer(&config, &drive)?; + + worker.log("Starting drive clean"); + + changer.clean_drive()?; + + worker.log("Drive cleaned sucessfully"); + + Ok(()) + })?; + + Ok(upid_str.into()) +} + #[api( input: { properties: { @@ -643,6 +690,7 @@ pub fn update_inventory( inventory.store(media_id, false)?; } } + changer.unload_media(None)?; } Ok(()) } @@ -940,6 +988,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([ &Router::new() .put(&API_METHOD_CATALOG_MEDIA) ), + ( + "clean", + &Router::new() + .put(&API_METHOD_CLEAN_DRIVE) + ), ( "eject-media", &Router::new() diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index d09bd8b2..1c06f334 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -677,6 +677,38 @@ fn status( Ok(()) } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_NAME_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Clean drive +async fn clean_drive( + 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_CLEAN_DRIVE; + + let result = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + wait_for_local_worker(result.as_str().unwrap()).await?; + + Ok(()) +} + #[api( input: { properties: { @@ -856,6 +888,11 @@ fn main() { CliCommand::new(&API_METHOD_CARTRIDGE_MEMORY) .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) diff --git a/src/tape/changer/linux_tape.rs b/src/tape/changer/linux_tape.rs index a8b6d349..068955d2 100644 --- a/src/tape/changer/linux_tape.rs +++ b/src/tape/changer/linux_tape.rs @@ -145,4 +145,47 @@ impl MediaChange for LinuxTapeDrive { fn eject_on_unload(&self) -> bool { true } + + fn clean_drive(&mut self) -> Result<(), Error> { + let (config, _digest) = crate::config::drive::config()?; + + let changer: ScsiTapeChanger = match self.changer { + Some(ref changer) => config.lookup("changer", changer)?, + None => bail!("unable to load cleaning cartridge - no associated changer device"), + }; + + let drivenum = self.changer_drive_id.unwrap_or(0); + + let status = mtx_status(&changer)?; + + let mut cleaning_cartridge_slot = None; + + for (i, (import_export, element_status)) in status.slots.iter().enumerate() { + if *import_export { continue; } + if let ElementStatus::VolumeTag(ref tag) = element_status { + if tag.starts_with("CLN") { + cleaning_cartridge_slot = Some(i + 1); + break; + } + } + } + + let cleaning_cartridge_slot = match cleaning_cartridge_slot { + None => bail!("clean failed - unable to find cleaning cartridge"), + Some(cleaning_cartridge_slot) => cleaning_cartridge_slot as u64, + }; + + if let Some(drive_status) = status.drives.get(drivenum as usize) { + match drive_status.status { + ElementStatus::Empty => { /* OK */ }, + _ => unload_to_free_slot(&self.name, &changer.path, &status, drivenum)?, + } + } + + mtx_load(&changer.path, cleaning_cartridge_slot, drivenum)?; + + mtx_unload(&changer.path, cleaning_cartridge_slot, drivenum)?; + + Ok(()) + } } diff --git a/src/tape/changer/mod.rs b/src/tape/changer/mod.rs index 6f1bed74..a3b97b1b 100644 --- a/src/tape/changer/mod.rs +++ b/src/tape/changer/mod.rs @@ -63,4 +63,10 @@ pub trait MediaChange { Ok(list) } + + /// Load/Unload cleaning cartridge + /// + /// This fail if there is no cleaning cartridge online. Any media + /// inside the drive is automatically unloaded. + fn clean_drive(&mut self) -> Result<(), Error>; } diff --git a/src/tape/drive/virtual_tape.rs b/src/tape/drive/virtual_tape.rs index 617dbce4..606c2202 100644 --- a/src/tape/drive/virtual_tape.rs +++ b/src/tape/drive/virtual_tape.rs @@ -439,6 +439,10 @@ impl MediaChange for VirtualTapeHandle { fn eject_on_unload(&self) -> bool { true } + + fn clean_drive(&mut self) -> Result<(), Error> { + Ok(()) + } } impl MediaChange for VirtualTapeDrive { @@ -472,4 +476,9 @@ impl MediaChange for VirtualTapeDrive { let handle = self.open()?; handle.online_media_changer_ids() } + + fn clean_drive(&mut self) -> Result<(), Error> { + Ok(()) + } + }