tape: more MediaChange cleanups
Try to provide generic implementation for complex operations: - unload_to_free_slot - load_media - export media - clean drive - online_media_changer_ids
This commit is contained in:
parent
483da89d03
commit
04df41cec1
@ -10,11 +10,17 @@ pub use mtx_wrapper::*;
|
|||||||
mod mtx;
|
mod mtx;
|
||||||
pub use mtx::*;
|
pub use mtx::*;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::{bail, Error};
|
||||||
|
|
||||||
/// Interface to media change devices
|
/// Interface to the media changer device for a single drive
|
||||||
pub trait MediaChange {
|
pub trait MediaChange {
|
||||||
|
|
||||||
|
/// Drive number inside changer
|
||||||
|
fn drive_number(&self) -> u64;
|
||||||
|
|
||||||
|
/// Drive name (used for debug messages)
|
||||||
|
fn drive_name(&self) -> &str;
|
||||||
|
|
||||||
/// Returns the changer status
|
/// Returns the changer status
|
||||||
fn status(&mut self) -> Result<MtxStatus, Error>;
|
fn status(&mut self) -> Result<MtxStatus, Error>;
|
||||||
|
|
||||||
@ -30,8 +36,63 @@ pub trait MediaChange {
|
|||||||
///
|
///
|
||||||
/// This unloads first if the drive is already loaded with another media.
|
/// This unloads first if the drive is already loaded with another media.
|
||||||
///
|
///
|
||||||
/// Note: This refuses to load media inside import/export slots.
|
/// Note: This refuses to load media inside import/export
|
||||||
fn load_media(&mut self, changer_id: &str) -> Result<(), Error>;
|
/// slots. Also, you cannot load cleaning units with this
|
||||||
|
/// interface.
|
||||||
|
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
|
if changer_id.starts_with("CLN") {
|
||||||
|
bail!("unable to load media '{}' (seems top be a a cleaning units)", changer_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut status = self.status()?;
|
||||||
|
|
||||||
|
let mut unload_drive = false;
|
||||||
|
|
||||||
|
// already loaded?
|
||||||
|
for (i, drive_status) in status.drives.iter().enumerate() {
|
||||||
|
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
|
||||||
|
if *tag == changer_id {
|
||||||
|
if i as u64 != self.drive_number() {
|
||||||
|
bail!("unable to load media '{}' - media in wrong drive ({} != {})",
|
||||||
|
changer_id, i, self.drive_number());
|
||||||
|
}
|
||||||
|
return Ok(()) // already loaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i as u64 == self.drive_number() {
|
||||||
|
match drive_status.status {
|
||||||
|
ElementStatus::Empty => { /* OK */ },
|
||||||
|
_ => unload_drive = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unload_drive {
|
||||||
|
self.unload_to_free_slot(status)?;
|
||||||
|
status = self.status()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut slot = None;
|
||||||
|
for (i, (import_export, element_status)) in status.slots.iter().enumerate() {
|
||||||
|
if let ElementStatus::VolumeTag(tag) = element_status {
|
||||||
|
if *tag == changer_id {
|
||||||
|
if *import_export {
|
||||||
|
bail!("unable to load media '{}' - inside import/export slot", changer_id);
|
||||||
|
}
|
||||||
|
slot = Some(i+1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let slot = match slot {
|
||||||
|
None => bail!("unable to find media '{}' (offline?)", changer_id),
|
||||||
|
Some(slot) => slot,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.load_media_from_slot(slot as u64)
|
||||||
|
}
|
||||||
|
|
||||||
/// Unload media from drive
|
/// Unload media from drive
|
||||||
///
|
///
|
||||||
@ -73,12 +134,124 @@ pub trait MediaChange {
|
|||||||
///
|
///
|
||||||
/// This fail if there is no cleaning cartridge online. Any media
|
/// This fail if there is no cleaning cartridge online. Any media
|
||||||
/// inside the drive is automatically unloaded.
|
/// inside the drive is automatically unloaded.
|
||||||
fn clean_drive(&mut self) -> Result<(), Error>;
|
fn clean_drive(&mut self) -> Result<(), Error> {
|
||||||
|
let status = self.status()?;
|
||||||
|
|
||||||
|
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(self.drive_number() as usize) {
|
||||||
|
match drive_status.status {
|
||||||
|
ElementStatus::Empty => { /* OK */ },
|
||||||
|
_ => self.unload_to_free_slot(status)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.load_media_from_slot(cleaning_cartridge_slot)?;
|
||||||
|
|
||||||
|
self.unload_media(Some(cleaning_cartridge_slot))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Export media
|
/// Export media
|
||||||
///
|
///
|
||||||
/// By moving the media to an empty import-export slot. Returns
|
/// By moving the media to an empty import-export slot. Returns
|
||||||
/// Some(slot) if the media was exported. Returns None if the media is
|
/// Some(slot) if the media was exported. Returns None if the media is
|
||||||
/// not online (already exported).
|
/// not online (already exported).
|
||||||
fn export_media(&mut self, changer_id: &str) -> Result<Option<u64>, Error>;
|
fn export_media(&mut self, changer_id: &str) -> Result<Option<u64>, Error> {
|
||||||
|
let status = self.status()?;
|
||||||
|
|
||||||
|
let mut unload_from_drive = false;
|
||||||
|
if let Some(drive_status) = status.drives.get(self.drive_number() as usize) {
|
||||||
|
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
|
||||||
|
if tag == changer_id {
|
||||||
|
unload_from_drive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 unload_from_drive {
|
||||||
|
match to {
|
||||||
|
Some(to) => {
|
||||||
|
self.unload_media(Some(to))?;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unload media to a free storage slot
|
||||||
|
///
|
||||||
|
/// If posible to the slot it was previously loaded from.
|
||||||
|
///
|
||||||
|
/// Note: This method consumes status - so please read again afterward.
|
||||||
|
fn unload_to_free_slot(&mut self, status: MtxStatus) -> Result<(), Error> {
|
||||||
|
|
||||||
|
let drive_status = &status.drives[self.drive_number() as usize];
|
||||||
|
if let Some(slot) = drive_status.loaded_slot {
|
||||||
|
// check if original slot is empty/usable
|
||||||
|
if let Some(info) = status.slots.get(slot as usize - 1) {
|
||||||
|
if let (_import_export, ElementStatus::Empty) = info {
|
||||||
|
return self.unload_media(Some(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut free_slot = None;
|
||||||
|
for i in 0..status.slots.len() {
|
||||||
|
if status.slots[i].0 { continue; } // skip import/export slots
|
||||||
|
if let ElementStatus::Empty = status.slots[i].1 {
|
||||||
|
free_slot = Some((i+1) as u64);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(slot) = free_slot {
|
||||||
|
self.unload_media(Some(slot))
|
||||||
|
} else {
|
||||||
|
bail!("drive '{}' unload failure - no free slot", self.drive_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ use crate::{
|
|||||||
tape::changer::{
|
tape::changer::{
|
||||||
MediaChange,
|
MediaChange,
|
||||||
MtxStatus,
|
MtxStatus,
|
||||||
ElementStatus,
|
|
||||||
mtx_status,
|
mtx_status,
|
||||||
mtx_transfer,
|
mtx_transfer,
|
||||||
mtx_load,
|
mtx_load,
|
||||||
@ -19,7 +18,7 @@ use crate::{
|
|||||||
/// Implements MediaChange using 'mtx' linux cli tool
|
/// Implements MediaChange using 'mtx' linux cli tool
|
||||||
pub struct MtxMediaChanger {
|
pub struct MtxMediaChanger {
|
||||||
drive_name: String, // used for error messages
|
drive_name: String, // used for error messages
|
||||||
drivenum: u64,
|
drive_number: u64,
|
||||||
config: ScsiTapeChanger,
|
config: ScsiTapeChanger,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,41 +33,22 @@ impl MtxMediaChanger {
|
|||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
drive_name: drive_config.name.clone(),
|
drive_name: drive_config.name.clone(),
|
||||||
drivenum: drive_config.changer_drive_id.unwrap_or(0),
|
drive_number: drive_config.changer_drive_id.unwrap_or(0),
|
||||||
config: changer_config,
|
config: changer_config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unload_to_free_slot(drive_name: &str, path: &str, status: &MtxStatus, drivenum: u64) -> Result<(), Error> {
|
|
||||||
|
|
||||||
if drivenum >= status.drives.len() as u64 {
|
|
||||||
bail!("unload drive '{}' got unexpected drive number '{}' - changer only has '{}' drives",
|
|
||||||
drive_name, drivenum, status.drives.len());
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
for i in 0..status.slots.len() {
|
|
||||||
if status.slots[i].0 { continue; } // skip import/export slots
|
|
||||||
if let ElementStatus::Empty = status.slots[i].1 {
|
|
||||||
free_slot = Some((i+1) as u64);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(slot) = free_slot {
|
|
||||||
mtx_unload(path, slot, drivenum)
|
|
||||||
} else {
|
|
||||||
bail!("drive '{}' unload failure - no free slot", drive_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MediaChange for MtxMediaChanger {
|
impl MediaChange for MtxMediaChanger {
|
||||||
|
|
||||||
|
fn drive_number(&self) -> u64 {
|
||||||
|
self.drive_number
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drive_name(&self) -> &str {
|
||||||
|
&self.drive_name
|
||||||
|
}
|
||||||
|
|
||||||
fn status(&mut self) -> Result<MtxStatus, Error> {
|
fn status(&mut self) -> Result<MtxStatus, Error> {
|
||||||
mtx_status(&self.config)
|
mtx_status(&self.config)
|
||||||
}
|
}
|
||||||
@ -78,151 +58,19 @@ impl MediaChange for MtxMediaChanger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> {
|
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> {
|
||||||
mtx_load(&self.config.path, slot, self.drivenum)
|
mtx_load(&self.config.path, slot, self.drive_number)
|
||||||
}
|
|
||||||
|
|
||||||
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
|
|
||||||
|
|
||||||
if changer_id.starts_with("CLN") {
|
|
||||||
bail!("unable to load media '{}' (seems top be a a cleaning units)", changer_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = self.status()?;
|
|
||||||
|
|
||||||
// already loaded?
|
|
||||||
for (i, drive_status) in status.drives.iter().enumerate() {
|
|
||||||
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
|
|
||||||
if *tag == changer_id {
|
|
||||||
if i as u64 != self.drivenum {
|
|
||||||
bail!("unable to load media '{}' - media in wrong drive ({} != {})",
|
|
||||||
changer_id, i, self.drivenum);
|
|
||||||
}
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i as u64 == self.drivenum {
|
|
||||||
match drive_status.status {
|
|
||||||
ElementStatus::Empty => { /* OK */ },
|
|
||||||
_ => unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut slot = None;
|
|
||||||
for (i, (import_export, element_status)) in status.slots.iter().enumerate() {
|
|
||||||
if let ElementStatus::VolumeTag(tag) = element_status {
|
|
||||||
if *tag == changer_id {
|
|
||||||
if *import_export {
|
|
||||||
bail!("unable to load media '{}' - inside import/export slot", changer_id);
|
|
||||||
}
|
|
||||||
slot = Some(i+1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let slot = match slot {
|
|
||||||
None => bail!("unable to find media '{}' (offline?)", changer_id),
|
|
||||||
Some(slot) => slot,
|
|
||||||
};
|
|
||||||
|
|
||||||
mtx_load(&self.config.path, slot as u64, self.drivenum)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error> {
|
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error> {
|
||||||
if let Some(target_slot) = target_slot {
|
if let Some(target_slot) = target_slot {
|
||||||
mtx_unload(&self.config.path, target_slot, self.drivenum)
|
mtx_unload(&self.config.path, target_slot, self.drive_number)
|
||||||
} else {
|
} else {
|
||||||
let status = self.status()?;
|
let status = self.status()?;
|
||||||
unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum)
|
self.unload_to_free_slot(status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eject_on_unload(&self) -> bool {
|
fn eject_on_unload(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_drive(&mut self) -> Result<(), Error> {
|
|
||||||
let status = self.status()?;
|
|
||||||
|
|
||||||
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(self.drivenum as usize) {
|
|
||||||
match drive_status.status {
|
|
||||||
ElementStatus::Empty => { /* OK */ },
|
|
||||||
_ => unload_to_free_slot(&self.drive_name, &self.config.path, &status, self.drivenum)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx_load(&self.config.path, cleaning_cartridge_slot, self.drivenum)?;
|
|
||||||
|
|
||||||
mtx_unload(&self.config.path, cleaning_cartridge_slot, self.drivenum)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export_media(&mut self, changer_id: &str) -> Result<Option<u64>, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ impl VirtualTapeDrive {
|
|||||||
|
|
||||||
Ok(VirtualTapeHandle {
|
Ok(VirtualTapeHandle {
|
||||||
_lock: lock,
|
_lock: lock,
|
||||||
|
drive_name: self.name.clone(),
|
||||||
max_size: self.max_size.unwrap_or(64*1024*1024),
|
max_size: self.max_size.unwrap_or(64*1024*1024),
|
||||||
path: std::path::PathBuf::from(&self.path),
|
path: std::path::PathBuf::from(&self.path),
|
||||||
})
|
})
|
||||||
@ -71,6 +72,7 @@ struct TapeIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct VirtualTapeHandle {
|
pub struct VirtualTapeHandle {
|
||||||
|
drive_name: String,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
max_size: usize,
|
max_size: usize,
|
||||||
_lock: File,
|
_lock: File,
|
||||||
@ -362,6 +364,14 @@ impl TapeDriver for VirtualTapeHandle {
|
|||||||
|
|
||||||
impl MediaChange for VirtualTapeHandle {
|
impl MediaChange for VirtualTapeHandle {
|
||||||
|
|
||||||
|
fn drive_number(&self) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drive_name(&self) -> &str {
|
||||||
|
&self.drive_name
|
||||||
|
}
|
||||||
|
|
||||||
fn status(&mut self) -> Result<MtxStatus, Error> {
|
fn status(&mut self) -> Result<MtxStatus, Error> {
|
||||||
|
|
||||||
let drive_status = self.load_status()?;
|
let drive_status = self.load_status()?;
|
||||||
@ -455,6 +465,14 @@ impl MediaChange for VirtualTapeHandle {
|
|||||||
|
|
||||||
impl MediaChange for VirtualTapeDrive {
|
impl MediaChange for VirtualTapeDrive {
|
||||||
|
|
||||||
|
fn drive_number(&self) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drive_name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
fn status(&mut self) -> Result<MtxStatus, Error> {
|
fn status(&mut self) -> Result<MtxStatus, Error> {
|
||||||
let mut handle = self.open()?;
|
let mut handle = self.open()?;
|
||||||
handle.status()
|
handle.status()
|
||||||
|
Loading…
Reference in New Issue
Block a user