tape: improve MediaChange trait

We expose the whole MtxStatus, and we can load/store from/to
specified slot numbers.
This commit is contained in:
Dietmar Maurer
2021-01-07 14:26:43 +01:00
parent 632756b6fb
commit 46a1863f88
9 changed files with 300 additions and 174 deletions

View File

@ -43,6 +43,30 @@ fn unload_to_free_slot(drive_name: &str, path: &str, status: &MtxStatus, drivenu
impl MediaChange for LinuxTapeDrive {
fn status(&mut self) -> Result<MtxStatus, Error> {
let (config, _digest) = crate::config::drive::config()?;
let changer: ScsiTapeChanger = match self.changer {
Some(ref changer) => config.lookup("changer", changer)?,
None => bail!("drive '{}' has no associated changer", self.name),
};
mtx_status(&changer)
}
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> {
let (config, _digest) = crate::config::drive::config()?;
let changer: ScsiTapeChanger = match self.changer {
Some(ref changer) => config.lookup("changer", changer)?,
None => bail!("drive '{}' has no associated changer", self.name),
};
let drivenum = self.changer_drive_id.unwrap_or(0);
mtx_load(&changer.path, slot, drivenum as u64)
}
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
if changer_id.starts_with("CLN") {
@ -97,11 +121,10 @@ impl MediaChange for LinuxTapeDrive {
Some(slot) => slot,
};
mtx_load(&changer.path, slot as u64, drivenum as u64)
}
fn unload_media(&mut self) -> Result<(), Error> {
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error> {
let (config, _digest) = crate::config::drive::config()?;
let changer: ScsiTapeChanger = match self.changer {
@ -111,40 +134,15 @@ impl MediaChange for LinuxTapeDrive {
let drivenum = self.changer_drive_id.unwrap_or(0);
let status = mtx_status(&changer)?;
unload_to_free_slot(&self.name, &changer.path, &status, drivenum)
if let Some(target_slot) = target_slot {
mtx_unload(&changer.path, target_slot, drivenum)
} else {
let status = mtx_status(&changer)?;
unload_to_free_slot(&self.name, &changer.path, &status, drivenum)
}
}
fn eject_on_unload(&self) -> bool {
true
}
fn list_media_changer_ids(&self) -> Result<Vec<String>, Error> {
let (config, _digest) = crate::config::drive::config()?;
let changer: ScsiTapeChanger = match self.changer {
Some(ref changer) => config.lookup("changer", changer)?,
None => return Ok(Vec::new()),
};
let status = mtx_status(&changer)?;
let mut list = Vec::new();
for drive_status in status.drives.iter() {
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
list.push(tag.clone());
}
}
for (import_export, element_status) in status.slots.iter() {
if *import_export { continue; }
if let ElementStatus::VolumeTag(ref tag) = element_status {
list.push(tag.clone());
}
}
Ok(list)
}
}

View File

@ -15,21 +15,52 @@ use anyhow::Error;
/// Interface to media change devices
pub trait MediaChange {
/// Load media into drive
/// Returns the changer status
fn status(&mut self) -> Result<MtxStatus, Error>;
/// Load media from storage slot into drive
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error>;
/// Load media by changer-id into drive
///
/// This unloads first if the drive is already loaded with another media.
///
/// Note: This refuses to load media inside import/export slots.
fn load_media(&mut self, changer_id: &str) -> Result<(), Error>;
/// Unload media from drive
///
/// This is a nop on drives without autoloader.
fn unload_media(&mut self) -> Result<(), Error>;
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error>;
/// Returns true if unload_media automatically ejects drive media
fn eject_on_unload(&self) -> bool {
false
}
/// List media changer IDs (barcodes)
fn list_media_changer_ids(&self) -> Result<Vec<String>, Error>;
/// List online media changer IDs (barcodes)
///
/// List acessible (online) changer IDs. This does not include
/// media inside import-export slots or cleaning media.
fn online_media_changer_ids(&mut self) -> Result<Vec<String>, Error> {
let status = self.status()?;
let mut list = Vec::new();
for drive_status in status.drives.iter() {
if let ElementStatus::VolumeTag(ref tag) = drive_status.status {
list.push(tag.clone());
}
}
for (import_export, element_status) in status.slots.iter() {
if *import_export { continue; }
if let ElementStatus::VolumeTag(ref tag) = element_status {
if !tag.starts_with("CLN") { continue; }
list.push(tag.clone());
}
}
Ok(list)
}
}

View File

@ -4,7 +4,6 @@ use anyhow::Error;
use serde_json::Value;
use proxmox::{
tools::Uuid,
api::schema::parse_property_string,
};
@ -15,10 +14,8 @@ use crate::{
ScsiTapeChanger,
},
tape::{
Inventory,
changer::{
MtxStatus,
ElementStatus,
parse_mtx_status,
},
},
@ -100,30 +97,3 @@ pub fn mtx_transfer(
Ok(())
}
/// Extract the list of online media from MtxStatus
///
/// Returns a HashSet containing all found media Uuid
pub fn mtx_status_to_online_set(status: &MtxStatus, inventory: &Inventory) -> HashSet<Uuid> {
let mut online_set = HashSet::new();
for drive_status in status.drives.iter() {
if let ElementStatus::VolumeTag(ref changer_id) = drive_status.status {
if let Some(media_id) = inventory.find_media_by_changer_id(changer_id) {
online_set.insert(media_id.label.uuid.clone());
}
}
}
for (import_export, slot_status) in status.slots.iter() {
if *import_export { continue; }
if let ElementStatus::VolumeTag(ref changer_id) = slot_status {
if let Some(media_id) = inventory.find_media_by_changer_id(changer_id) {
online_set.insert(media_id.label.uuid.clone());
}
}
}
online_set
}

View File

@ -14,6 +14,9 @@ use crate::{
tape::{
TapeWrite,
TapeRead,
MtxStatus,
DriveStatus,
ElementStatus,
changer::MediaChange,
drive::{
VirtualTapeDrive,
@ -73,15 +76,7 @@ pub struct VirtualTapeHandle {
_lock: File,
}
impl VirtualTapeHandle {
pub fn insert_tape(&self, _tape_filename: &str) {
unimplemented!();
}
pub fn eject_tape(&self) {
unimplemented!();
}
impl VirtualTapeHandle {
fn status_file_path(&self) -> std::path::PathBuf {
let mut path = self.path.clone();
@ -159,6 +154,25 @@ impl VirtualTapeHandle {
replace_file(&path, raw.as_bytes(), options)?;
Ok(())
}
fn online_media_changer_ids(&self) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in std::fs::read_dir(&self.path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension() == Some(std::ffi::OsStr::new("json")) {
if let Some(name) = path.file_stem() {
if let Some(name) = name.to_str() {
if name.starts_with("tape-") {
list.push(name[5..].to_string());
}
}
}
}
}
Ok(list)
}
}
impl TapeDriver for VirtualTapeHandle {
@ -348,6 +362,51 @@ impl TapeDriver for VirtualTapeHandle {
impl MediaChange for VirtualTapeHandle {
fn status(&mut self) -> Result<MtxStatus, Error> {
let drive_status = self.load_status()?;
let mut drives = Vec::new();
if let Some(current_tape) = &drive_status.current_tape {
drives.push(DriveStatus {
loaded_slot: None,
status: ElementStatus::VolumeTag(current_tape.name.clone()),
});
}
// This implementation is lame, because we do not have fixed
// slot-assignment here.
let mut slots = Vec::new();
let changer_ids = self.online_media_changer_ids()?;
let max_slots = ((changer_ids.len() + 7)/8) * 8;
for i in 0..max_slots {
if let Some(changer_id) = changer_ids.get(i) {
slots.push((false, ElementStatus::VolumeTag(changer_id.clone())));
} else {
slots.push((false, ElementStatus::Empty));
}
}
Ok(MtxStatus { drives, slots })
}
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> {
if slot < 1 {
bail!("invalid slot ID {}", slot);
}
let changer_ids = self.online_media_changer_ids()?;
if slot > changer_ids.len() as u64 {
bail!("slot {} is empty", slot);
}
self.load_media(&changer_ids[slot as usize - 1])
}
/// Try to load media
///
/// We automatically create an empty virtual tape here (if it does
@ -371,7 +430,8 @@ impl MediaChange for VirtualTapeHandle {
self.store_status(&status)
}
fn unload_media(&mut self) -> Result<(), Error> {
fn unload_media(&mut self, _target_slot: Option<u64>) -> Result<(), Error> {
// Note: we currently simply ignore target_slot
self.eject_media()?;
Ok(())
}
@ -379,36 +439,28 @@ impl MediaChange for VirtualTapeHandle {
fn eject_on_unload(&self) -> bool {
true
}
fn list_media_changer_ids(&self) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in std::fs::read_dir(&self.path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension() == Some(std::ffi::OsStr::new("json")) {
if let Some(name) = path.file_stem() {
if let Some(name) = name.to_str() {
if name.starts_with("tape-") {
list.push(name[5..].to_string());
}
}
}
}
}
Ok(list)
}
}
impl MediaChange for VirtualTapeDrive {
fn status(&mut self) -> Result<MtxStatus, Error> {
let mut handle = self.open()?;
handle.status()
}
fn load_media_from_slot(&mut self, slot: u64) -> Result<(), Error> {
let mut handle = self.open()?;
handle.load_media_from_slot(slot)
}
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
let mut handle = self.open()?;
handle.load_media(changer_id)
}
fn unload_media(&mut self) -> Result<(), Error> {
fn unload_media(&mut self, target_slot: Option<u64>) -> Result<(), Error> {
let mut handle = self.open()?;
handle.eject_media()?;
handle.unload_media(target_slot)?;
Ok(())
}
@ -416,8 +468,8 @@ impl MediaChange for VirtualTapeDrive {
true
}
fn list_media_changer_ids(&self) -> Result<Vec<String>, Error> {
fn online_media_changer_ids(&mut self) -> Result<Vec<String>, Error> {
let handle = self.open()?;
handle.list_media_changer_ids()
handle.online_media_changer_ids()
}
}

View File

@ -14,8 +14,9 @@ use crate::{
tape::{
MediaChange,
Inventory,
MtxStatus,
ElementStatus,
mtx_status,
mtx_status_to_online_set,
},
};
@ -89,6 +90,34 @@ impl OnlineStatusMap {
}
}
/// Extract the list of online media from MtxStatus
///
/// Returns a HashSet containing all found media Uuid. This only
/// returns media found in Inventory.
pub fn mtx_status_to_online_set(status: &MtxStatus, inventory: &Inventory) -> HashSet<Uuid> {
let mut online_set = HashSet::new();
for drive_status in status.drives.iter() {
if let ElementStatus::VolumeTag(ref changer_id) = drive_status.status {
if let Some(media_id) = inventory.find_media_by_changer_id(changer_id) {
online_set.insert(media_id.label.uuid.clone());
}
}
}
for (import_export, slot_status) in status.slots.iter() {
if *import_export { continue; }
if let ElementStatus::VolumeTag(ref changer_id) = slot_status {
if let Some(media_id) = inventory.find_media_by_changer_id(changer_id) {
online_set.insert(media_id.label.uuid.clone());
}
}
}
online_set
}
/// Update online media status
///
/// Simply ask all changer devices.
@ -116,8 +145,8 @@ pub fn update_online_status(state_path: &Path) -> Result<OnlineStatusMap, Error>
}
let vtapes: Vec<VirtualTapeDrive> = config.convert_to_typed_array("virtual")?;
for vtape in vtapes {
let media_list = match vtape.list_media_changer_ids() {
for mut vtape in vtapes {
let media_list = match vtape.online_media_changer_ids() {
Ok(media_list) => media_list,
Err(err) => {
eprintln!("unable to get changer '{}' status - {}", vtape.name, err);