From 83abc7497d4de12fe0789e76ea64ca702f461733 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 11 Dec 2020 07:39:28 +0100 Subject: [PATCH] tape: implement inventory command --- src/api2/tape/drive.rs | 125 +++++++++++++++++++++++++++++++++++ src/api2/types/tape/media.rs | 11 +++ src/bin/proxmox-tape.rs | 61 ++++++++++++++++- src/tape/inventory.rs | 22 +++--- 4 files changed, 206 insertions(+), 13 deletions(-) diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index cb360364..1f40a63d 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -25,18 +25,21 @@ use crate::{ ScsiTapeChanger, TapeDeviceInfo, MediaLabelInfoFlat, + LabelUuidMap, }, tape::{ TAPE_STATUS_DIR, TapeDriver, MediaChange, Inventory, + MediaStateDatabase, MediaId, mtx_load, mtx_unload, linux_tape_device_list, open_drive, media_changer, + update_changer_online_status, file_formats::{ DriveLabel, MediaSetLabel, @@ -407,6 +410,118 @@ pub fn read_label(drive: String) -> Result { Ok(info) } +#[api( + input: { + properties: { + drive: { + schema: DRIVE_ID_SCHEMA, + }, + "read-labels": { + description: "Load unknown tapes and try read labels", + type: bool, + optional: true, + }, + "read-all-labels": { + description: "Load all tapes and try read labels (even if already inventoried)", + type: bool, + optional: true, + }, + }, + }, + returns: { + description: "The list of media labels with associated media Uuid (if any).", + type: Array, + items: { + type: LabelUuidMap, + }, + }, +)] +/// List (and update) media labels (Changer Inventory) +/// +/// Note: Only useful for drives with associated changer device. +/// +/// This method queries the changer to get a list of media labels. It +/// 'read-labels' is set, it then loads any unknown media into the +/// drive, reads the label, and store the result to the media +/// database. +pub fn inventory( + drive: String, + read_labels: Option, + read_all_labels: Option, +) -> Result, Error> { + + let (config, _digest) = config::drive::config()?; + + let (mut changer, changer_name) = media_changer(&config, &drive, false)?; + + let changer_id_list = changer.list_media_changer_ids()?; + + let state_path = Path::new(TAPE_STATUS_DIR); + + let mut inventory = Inventory::load(state_path)?; + let mut state_db = MediaStateDatabase::load(state_path)?; + + update_changer_online_status(&config, &mut inventory, &mut state_db, &changer_name, &changer_id_list)?; + + let mut list = Vec::new(); + + let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false); + + for changer_id in changer_id_list.iter() { + if changer_id.starts_with("CLN") { + // skip cleaning unit + continue; + } + + let changer_id = changer_id.to_string(); + + if !read_all_labels.unwrap_or(false) { + if let Some(media_id) = inventory.find_media_by_changer_id(&changer_id) { + list.push(LabelUuidMap { changer_id, uuid: Some(media_id.label.uuid.to_string()) }); + continue; + } + } + + if !do_read { + list.push(LabelUuidMap { changer_id, uuid: None }); + continue; + } + + if let Err(err) = changer.load_media(&changer_id) { + eprintln!("unable to load media '{}' - {}", changer_id, err); + list.push(LabelUuidMap { changer_id, uuid: None }); + continue; + } + + let mut drive = open_drive(&config, &drive)?; + match drive.read_label() { + Err(err) => { + eprintln!("unable to read label form media '{}' - {}", changer_id, err); + list.push(LabelUuidMap { changer_id, uuid: None }); + + } + Ok(None) => { + // no label on media (empty) + list.push(LabelUuidMap { changer_id, uuid: None }); + + } + Ok(Some(info)) => { + if changer_id != info.label.changer_id { + eprintln!("label changer ID missmatch ({} != {})", changer_id, info.label.changer_id); + list.push(LabelUuidMap { changer_id, uuid: None }); + continue; + } + let uuid = info.label.uuid.to_string(); + inventory.store(info.into())?; + list.push(LabelUuidMap { changer_id, uuid: Some(uuid) }); + } + } + } + + Ok(list) +} + + #[sortable] pub const SUBDIRS: SubdirMap = &sorted!([ ( @@ -419,6 +534,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([ &Router::new() .put(&API_METHOD_ERASE_MEDIA) ), + ( + "inventory", + &Router::new() + .get(&API_METHOD_INVENTORY) + ), ( "label-media", &Router::new() @@ -429,6 +549,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([ &Router::new() .put(&API_METHOD_LOAD_SLOT) ), + ( + "read-label", + &Router::new() + .get(&API_METHOD_READ_LABEL) + ), ( "rewind", &Router::new() diff --git a/src/api2/types/tape/media.rs b/src/api2/types/tape/media.rs index 703c7a39..a9dd5365 100644 --- a/src/api2/types/tape/media.rs +++ b/src/api2/types/tape/media.rs @@ -83,3 +83,14 @@ pub struct MediaLabelInfoFlat { #[serde(skip_serializing_if="Option::is_none")] pub media_set_ctime: Option, } + +#[api()] +#[derive(Serialize,Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Label with optional Uuid +pub struct LabelUuidMap { + /// Changer ID (label) + pub changer_id: String, + /// Associated Uuid (if any) + pub uuid: Option, +} diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index a038680a..1b1f3ddc 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -249,7 +249,8 @@ fn read_label( mut param: Value, rpcenv: &mut dyn RpcEnvironment, ) -> Result<(), Error> { - let (config, _digest) = config::drive::config()?; + + let (config, _digest) = config::drive::config()?; param["drive"] = lookup_drive_name(¶m, &config)?.into(); @@ -272,8 +273,59 @@ fn read_label( format_and_print_result_full(&mut data, info.returns, &output_format, &options); Ok(()) - } + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + drive: { + schema: DRIVE_ID_SCHEMA, + optional: true, + }, + "read-labels": { + description: "Load unknown tapes and try read labels", + type: bool, + optional: true, + }, + "read-all-labels": { + description: "Load all tapes and try read labels (even if already inventoried)", + type: bool, + optional: true, + }, + }, + }, +)] +/// List or update media labels (Changer Inventory) +fn inventory( + mut param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + param["drive"] = lookup_drive_name(¶m, &config)?.into(); + + let output_format = get_output_format(¶m); + let info = &api2::tape::drive::API_METHOD_INVENTORY; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("changer-id")) + .column(ColumnConfig::new("uuid")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + fn main() { let cmd_def = CliCommandMap::new() @@ -292,6 +344,11 @@ fn main() { CliCommand::new(&API_METHOD_EJECT_MEDIA) .completion_cb("drive", complete_drive_name) ) + .insert( + "inventory", + CliCommand::new(&API_METHOD_INVENTORY) + .completion_cb("drive", complete_drive_name) + ) .insert( "read-label", CliCommand::new(&API_METHOD_READ_LABEL) diff --git a/src/tape/inventory.rs b/src/tape/inventory.rs index 55e5bc3d..7ce1fb08 100644 --- a/src/tape/inventory.rs +++ b/src/tape/inventory.rs @@ -29,6 +29,7 @@ use crate::{ }, tape::{ TAPE_STATUS_DIR, + MediaLabelInfo, file_formats::{ DriveLabel, MediaSetLabel, @@ -46,6 +47,16 @@ pub struct MediaId { pub media_set_label: Option, } +impl From for MediaId { + fn from(info: MediaLabelInfo) -> Self { + Self { + label: info.label.clone(), + media_set_label: info.media_set_label.map(|(l, _)| l), + } + } +} + + /// Media Set /// /// A List of backup media @@ -240,17 +251,6 @@ impl Inventory { Ok(()) } - /* - /// Same a store, but extract MediaId form MediaLabelInfo - pub fn store_media_info(&mut self, info: &MediaLabelInfo) -> Result<(), Error> { - let media_id = MediaId { - label: info.label.clone(), - media_set_label: info.media_set_label.clone().map(|(l, _)| l), - }; - self.store(media_id) - } - */ - /// Lookup media pub fn lookup_media(&self, uuid: &Uuid) -> Option<&MediaId> { self.map.get(uuid)