tape: improve MediaChange trait
We expose the whole MtxStatus, and we can load/store from/to specified slot numbers.
This commit is contained in:
parent
632756b6fb
commit
46a1863f88
@ -145,11 +145,11 @@ fn update_media_online_status(drive: &str) -> Result<bool, Error> {
|
|||||||
|
|
||||||
let mut has_changer = false;
|
let mut has_changer = false;
|
||||||
|
|
||||||
if let Ok(Some((changer, changer_name))) = media_changer(&config, drive) {
|
if let Ok(Some((mut changer, changer_name))) = media_changer(&config, drive) {
|
||||||
|
|
||||||
has_changer = true;
|
has_changer = true;
|
||||||
|
|
||||||
let changer_id_list = changer.list_media_changer_ids()?;
|
let changer_id_list = changer.online_media_changer_ids()?;
|
||||||
|
|
||||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||||
let mut inventory = Inventory::load(status_path)?;
|
let mut inventory = Inventory::load(status_path)?;
|
||||||
|
@ -32,7 +32,6 @@ use crate::{
|
|||||||
MEDIA_POOL_NAME_SCHEMA,
|
MEDIA_POOL_NAME_SCHEMA,
|
||||||
Authid,
|
Authid,
|
||||||
LinuxTapeDrive,
|
LinuxTapeDrive,
|
||||||
ScsiTapeChanger,
|
|
||||||
TapeDeviceInfo,
|
TapeDeviceInfo,
|
||||||
MediaIdFlat,
|
MediaIdFlat,
|
||||||
LabelUuidMap,
|
LabelUuidMap,
|
||||||
@ -50,8 +49,6 @@ use crate::{
|
|||||||
Inventory,
|
Inventory,
|
||||||
MediaCatalog,
|
MediaCatalog,
|
||||||
MediaId,
|
MediaId,
|
||||||
mtx_load,
|
|
||||||
mtx_unload,
|
|
||||||
linux_tape_device_list,
|
linux_tape_device_list,
|
||||||
open_drive,
|
open_drive,
|
||||||
media_changer,
|
media_changer,
|
||||||
@ -68,41 +65,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[api(
|
|
||||||
input: {
|
|
||||||
properties: {
|
|
||||||
drive: {
|
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
|
||||||
},
|
|
||||||
slot: {
|
|
||||||
description: "Source slot number",
|
|
||||||
minimum: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)]
|
|
||||||
/// Load media via changer from slot
|
|
||||||
pub async fn load_slot(
|
|
||||||
drive: String,
|
|
||||||
slot: u64,
|
|
||||||
_param: Value,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
|
|
||||||
let (config, _digest) = config::drive::config()?;
|
|
||||||
|
|
||||||
let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
|
||||||
|
|
||||||
let changer: ScsiTapeChanger = match drive_config.changer {
|
|
||||||
Some(ref changer) => config.lookup("changer", changer)?,
|
|
||||||
None => bail!("drive '{}' has no associated changer", drive),
|
|
||||||
};
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
let drivenum = drive_config.changer_drive_id.unwrap_or(0);
|
|
||||||
mtx_load(&changer.path, slot, drivenum)
|
|
||||||
}).await?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
@ -127,6 +89,31 @@ pub async fn load_media(drive: String, changer_id: String) -> Result<(), Error>
|
|||||||
changer.load_media(&changer_id)
|
changer.load_media(&changer_id)
|
||||||
}).await?
|
}).await?
|
||||||
}
|
}
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
drive: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
},
|
||||||
|
"source-slot": {
|
||||||
|
description: "Source slot number.",
|
||||||
|
minimum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Load media from the specified slot
|
||||||
|
///
|
||||||
|
/// Issue a media load request to the associated changer device.
|
||||||
|
pub async fn load_slot(drive: String, source_slot: u64) -> Result<(), Error> {
|
||||||
|
|
||||||
|
let (config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let (mut changer, _) = required_media_changer(&config, &drive)?;
|
||||||
|
changer.load_media_from_slot(source_slot)
|
||||||
|
}).await?
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
@ -134,7 +121,7 @@ pub async fn load_media(drive: String, changer_id: String) -> Result<(), Error>
|
|||||||
drive: {
|
drive: {
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
},
|
},
|
||||||
slot: {
|
"target-slot": {
|
||||||
description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
|
description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
|
||||||
minimum: 1,
|
minimum: 1,
|
||||||
optional: true,
|
optional: true,
|
||||||
@ -145,7 +132,7 @@ pub async fn load_media(drive: String, changer_id: String) -> Result<(), Error>
|
|||||||
/// Unload media via changer
|
/// Unload media via changer
|
||||||
pub async fn unload(
|
pub async fn unload(
|
||||||
drive: String,
|
drive: String,
|
||||||
slot: Option<u64>,
|
target_slot: Option<u64>,
|
||||||
_param: Value,
|
_param: Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
@ -153,19 +140,8 @@ pub async fn unload(
|
|||||||
|
|
||||||
let mut drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
let mut drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?;
|
||||||
|
|
||||||
let changer: ScsiTapeChanger = match drive_config.changer {
|
|
||||||
Some(ref changer) => config.lookup("changer", changer)?,
|
|
||||||
None => bail!("drive '{}' has no associated changer", drive),
|
|
||||||
};
|
|
||||||
|
|
||||||
let drivenum = drive_config.changer_drive_id.unwrap_or(0);
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
if let Some(slot) = slot {
|
drive_config.unload_media(target_slot)
|
||||||
mtx_unload(&changer.path, slot, drivenum)
|
|
||||||
} else {
|
|
||||||
drive_config.unload_media()
|
|
||||||
}
|
|
||||||
}).await?
|
}).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,7 +274,7 @@ pub async fn eject_media(drive: String) -> Result<(), Error> {
|
|||||||
let mut drive = open_drive(&config, &drive)?;
|
let mut drive = open_drive(&config, &drive)?;
|
||||||
drive.eject_media()?;
|
drive.eject_media()?;
|
||||||
}
|
}
|
||||||
changer.unload_media()?;
|
changer.unload_media(None)?;
|
||||||
} else {
|
} else {
|
||||||
let mut drive = open_drive(&config, &drive)?;
|
let mut drive = open_drive(&config, &drive)?;
|
||||||
drive.eject_media()?;
|
drive.eject_media()?;
|
||||||
@ -534,9 +510,9 @@ pub async fn inventory(
|
|||||||
let (config, _digest) = config::drive::config()?;
|
let (config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let (changer, changer_name) = required_media_changer(&config, &drive)?;
|
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
||||||
|
|
||||||
let changer_id_list = changer.list_media_changer_ids()?;
|
let changer_id_list = changer.online_media_changer_ids()?;
|
||||||
|
|
||||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||||
|
|
||||||
@ -619,7 +595,7 @@ pub fn update_inventory(
|
|||||||
|
|
||||||
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
||||||
|
|
||||||
let changer_id_list = changer.list_media_changer_ids()?;
|
let changer_id_list = changer.online_media_changer_ids()?;
|
||||||
if changer_id_list.is_empty() {
|
if changer_id_list.is_empty() {
|
||||||
worker.log(format!("changer device does not list any media labels"));
|
worker.log(format!("changer device does not list any media labels"));
|
||||||
}
|
}
|
||||||
@ -734,7 +710,7 @@ fn barcode_label_media_worker(
|
|||||||
|
|
||||||
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
||||||
|
|
||||||
let changer_id_list = changer.list_media_changer_ids()?;
|
let changer_id_list = changer.online_media_changer_ids()?;
|
||||||
|
|
||||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||||
|
|
||||||
|
@ -216,7 +216,7 @@ async fn eject_media(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
/// Load media
|
/// Load media with specified label
|
||||||
async fn load_media(
|
async fn load_media(
|
||||||
mut param: Value,
|
mut param: Value,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
@ -236,6 +236,77 @@ async fn load_media(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
drive: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"source-slot": {
|
||||||
|
description: "Source slot number.",
|
||||||
|
type: u64,
|
||||||
|
minimum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Load media from the specified slot
|
||||||
|
async fn load_media_from_slot(
|
||||||
|
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_LOAD_SLOT;
|
||||||
|
|
||||||
|
match info.handler {
|
||||||
|
ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
drive: {
|
||||||
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"target-slot": {
|
||||||
|
description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
|
||||||
|
type: u64,
|
||||||
|
minimum: 1,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Unload media via changer
|
||||||
|
async fn unload_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_UNLOAD;
|
||||||
|
|
||||||
|
match info.handler {
|
||||||
|
ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
@ -803,6 +874,17 @@ fn main() {
|
|||||||
.completion_cb("drive", complete_drive_name)
|
.completion_cb("drive", complete_drive_name)
|
||||||
.completion_cb("changer-id", complete_media_changer_id)
|
.completion_cb("changer-id", complete_media_changer_id)
|
||||||
)
|
)
|
||||||
|
.insert(
|
||||||
|
"load-media-from-slot",
|
||||||
|
CliCommand::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT)
|
||||||
|
.arg_param(&["slot"])
|
||||||
|
.completion_cb("drive", complete_drive_name)
|
||||||
|
)
|
||||||
|
.insert(
|
||||||
|
"unload",
|
||||||
|
CliCommand::new(&API_METHOD_UNLOAD_MEDIA)
|
||||||
|
.completion_cb("drive", complete_drive_name)
|
||||||
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
let mut rpcenv = CliEnvironment::new();
|
let mut rpcenv = CliEnvironment::new();
|
||||||
|
@ -59,18 +59,6 @@ pub fn drive_commands() -> CommandLineInterface {
|
|||||||
.completion_cb("path", complete_drive_path)
|
.completion_cb("path", complete_drive_path)
|
||||||
.completion_cb("changer", complete_changer_name)
|
.completion_cb("changer", complete_changer_name)
|
||||||
)
|
)
|
||||||
.insert(
|
|
||||||
"load",
|
|
||||||
CliCommand::new(&api2::tape::drive::API_METHOD_LOAD_SLOT)
|
|
||||||
.arg_param(&["drive"])
|
|
||||||
.completion_cb("drive", complete_linux_drive_name)
|
|
||||||
)
|
|
||||||
.insert(
|
|
||||||
"unload",
|
|
||||||
CliCommand::new(&api2::tape::drive::API_METHOD_UNLOAD)
|
|
||||||
.arg_param(&["drive"])
|
|
||||||
.completion_cb("drive", complete_linux_drive_name)
|
|
||||||
)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
cmd_def.into()
|
cmd_def.into()
|
||||||
|
@ -43,6 +43,30 @@ fn unload_to_free_slot(drive_name: &str, path: &str, status: &MtxStatus, drivenu
|
|||||||
|
|
||||||
impl MediaChange for LinuxTapeDrive {
|
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> {
|
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
|
||||||
|
|
||||||
if changer_id.starts_with("CLN") {
|
if changer_id.starts_with("CLN") {
|
||||||
@ -97,11 +121,10 @@ impl MediaChange for LinuxTapeDrive {
|
|||||||
Some(slot) => slot,
|
Some(slot) => slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
mtx_load(&changer.path, slot as u64, drivenum as u64)
|
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 (config, _digest) = crate::config::drive::config()?;
|
||||||
|
|
||||||
let changer: ScsiTapeChanger = match self.changer {
|
let changer: ScsiTapeChanger = match self.changer {
|
||||||
@ -111,40 +134,15 @@ impl MediaChange for LinuxTapeDrive {
|
|||||||
|
|
||||||
let drivenum = self.changer_drive_id.unwrap_or(0);
|
let drivenum = self.changer_drive_id.unwrap_or(0);
|
||||||
|
|
||||||
|
if let Some(target_slot) = target_slot {
|
||||||
|
mtx_unload(&changer.path, target_slot, drivenum)
|
||||||
|
} else {
|
||||||
let status = mtx_status(&changer)?;
|
let status = mtx_status(&changer)?;
|
||||||
|
|
||||||
unload_to_free_slot(&self.name, &changer.path, &status, drivenum)
|
unload_to_free_slot(&self.name, &changer.path, &status, drivenum)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn eject_on_unload(&self) -> bool {
|
fn eject_on_unload(&self) -> bool {
|
||||||
true
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,52 @@ use anyhow::Error;
|
|||||||
/// Interface to media change devices
|
/// Interface to media change devices
|
||||||
pub trait MediaChange {
|
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.
|
/// 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>;
|
fn load_media(&mut self, changer_id: &str) -> Result<(), Error>;
|
||||||
|
|
||||||
/// Unload media from drive
|
/// Unload media from drive
|
||||||
///
|
///
|
||||||
/// This is a nop on drives without autoloader.
|
/// 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
|
/// Returns true if unload_media automatically ejects drive media
|
||||||
fn eject_on_unload(&self) -> bool {
|
fn eject_on_unload(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List media changer IDs (barcodes)
|
/// List online media changer IDs (barcodes)
|
||||||
fn list_media_changer_ids(&self) -> Result<Vec<String>, Error>;
|
///
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ use anyhow::Error;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use proxmox::{
|
use proxmox::{
|
||||||
tools::Uuid,
|
|
||||||
api::schema::parse_property_string,
|
api::schema::parse_property_string,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -15,10 +14,8 @@ use crate::{
|
|||||||
ScsiTapeChanger,
|
ScsiTapeChanger,
|
||||||
},
|
},
|
||||||
tape::{
|
tape::{
|
||||||
Inventory,
|
|
||||||
changer::{
|
changer::{
|
||||||
MtxStatus,
|
MtxStatus,
|
||||||
ElementStatus,
|
|
||||||
parse_mtx_status,
|
parse_mtx_status,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -100,30 +97,3 @@ pub fn mtx_transfer(
|
|||||||
|
|
||||||
Ok(())
|
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
|
|
||||||
}
|
|
||||||
|
@ -14,6 +14,9 @@ use crate::{
|
|||||||
tape::{
|
tape::{
|
||||||
TapeWrite,
|
TapeWrite,
|
||||||
TapeRead,
|
TapeRead,
|
||||||
|
MtxStatus,
|
||||||
|
DriveStatus,
|
||||||
|
ElementStatus,
|
||||||
changer::MediaChange,
|
changer::MediaChange,
|
||||||
drive::{
|
drive::{
|
||||||
VirtualTapeDrive,
|
VirtualTapeDrive,
|
||||||
@ -75,14 +78,6 @@ pub struct VirtualTapeHandle {
|
|||||||
|
|
||||||
impl VirtualTapeHandle {
|
impl VirtualTapeHandle {
|
||||||
|
|
||||||
pub fn insert_tape(&self, _tape_filename: &str) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eject_tape(&self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status_file_path(&self) -> std::path::PathBuf {
|
fn status_file_path(&self) -> std::path::PathBuf {
|
||||||
let mut path = self.path.clone();
|
let mut path = self.path.clone();
|
||||||
path.push("drive-status.json");
|
path.push("drive-status.json");
|
||||||
@ -159,6 +154,25 @@ impl VirtualTapeHandle {
|
|||||||
replace_file(&path, raw.as_bytes(), options)?;
|
replace_file(&path, raw.as_bytes(), options)?;
|
||||||
Ok(())
|
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 {
|
impl TapeDriver for VirtualTapeHandle {
|
||||||
@ -348,6 +362,51 @@ impl TapeDriver for VirtualTapeHandle {
|
|||||||
|
|
||||||
impl MediaChange 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
|
/// Try to load media
|
||||||
///
|
///
|
||||||
/// We automatically create an empty virtual tape here (if it does
|
/// We automatically create an empty virtual tape here (if it does
|
||||||
@ -371,7 +430,8 @@ impl MediaChange for VirtualTapeHandle {
|
|||||||
self.store_status(&status)
|
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()?;
|
self.eject_media()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -379,36 +439,28 @@ impl MediaChange for VirtualTapeHandle {
|
|||||||
fn eject_on_unload(&self) -> bool {
|
fn eject_on_unload(&self) -> bool {
|
||||||
true
|
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 {
|
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> {
|
fn load_media(&mut self, changer_id: &str) -> Result<(), Error> {
|
||||||
let mut handle = self.open()?;
|
let mut handle = self.open()?;
|
||||||
handle.load_media(changer_id)
|
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()?;
|
let mut handle = self.open()?;
|
||||||
handle.eject_media()?;
|
handle.unload_media(target_slot)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,8 +468,8 @@ impl MediaChange for VirtualTapeDrive {
|
|||||||
true
|
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()?;
|
let handle = self.open()?;
|
||||||
handle.list_media_changer_ids()
|
handle.online_media_changer_ids()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,9 @@ use crate::{
|
|||||||
tape::{
|
tape::{
|
||||||
MediaChange,
|
MediaChange,
|
||||||
Inventory,
|
Inventory,
|
||||||
|
MtxStatus,
|
||||||
|
ElementStatus,
|
||||||
mtx_status,
|
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
|
/// Update online media status
|
||||||
///
|
///
|
||||||
/// Simply ask all changer devices.
|
/// 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")?;
|
let vtapes: Vec<VirtualTapeDrive> = config.convert_to_typed_array("virtual")?;
|
||||||
for vtape in vtapes {
|
for mut vtape in vtapes {
|
||||||
let media_list = match vtape.list_media_changer_ids() {
|
let media_list = match vtape.online_media_changer_ids() {
|
||||||
Ok(media_list) => media_list,
|
Ok(media_list) => media_list,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("unable to get changer '{}' status - {}", vtape.name, err);
|
eprintln!("unable to get changer '{}' status - {}", vtape.name, err);
|
||||||
|
Loading…
Reference in New Issue
Block a user