tape: implement inventory command
This commit is contained in:
		@ -25,18 +25,21 @@ use crate::{
 | 
				
			|||||||
        ScsiTapeChanger,
 | 
					        ScsiTapeChanger,
 | 
				
			||||||
        TapeDeviceInfo,
 | 
					        TapeDeviceInfo,
 | 
				
			||||||
        MediaLabelInfoFlat,
 | 
					        MediaLabelInfoFlat,
 | 
				
			||||||
 | 
					        LabelUuidMap,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tape::{
 | 
					    tape::{
 | 
				
			||||||
        TAPE_STATUS_DIR,
 | 
					        TAPE_STATUS_DIR,
 | 
				
			||||||
        TapeDriver,
 | 
					        TapeDriver,
 | 
				
			||||||
        MediaChange,
 | 
					        MediaChange,
 | 
				
			||||||
        Inventory,
 | 
					        Inventory,
 | 
				
			||||||
 | 
					        MediaStateDatabase,
 | 
				
			||||||
        MediaId,
 | 
					        MediaId,
 | 
				
			||||||
        mtx_load,
 | 
					        mtx_load,
 | 
				
			||||||
        mtx_unload,
 | 
					        mtx_unload,
 | 
				
			||||||
        linux_tape_device_list,
 | 
					        linux_tape_device_list,
 | 
				
			||||||
        open_drive,
 | 
					        open_drive,
 | 
				
			||||||
        media_changer,
 | 
					        media_changer,
 | 
				
			||||||
 | 
					        update_changer_online_status,
 | 
				
			||||||
        file_formats::{
 | 
					        file_formats::{
 | 
				
			||||||
            DriveLabel,
 | 
					            DriveLabel,
 | 
				
			||||||
            MediaSetLabel,
 | 
					            MediaSetLabel,
 | 
				
			||||||
@ -407,6 +410,118 @@ pub fn read_label(drive: String) -> Result<MediaLabelInfoFlat, Error> {
 | 
				
			|||||||
    Ok(info)
 | 
					    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<bool>,
 | 
				
			||||||
 | 
					    read_all_labels: Option<bool>,
 | 
				
			||||||
 | 
					) -> Result<Vec<LabelUuidMap>, 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]
 | 
					#[sortable]
 | 
				
			||||||
pub const SUBDIRS: SubdirMap = &sorted!([
 | 
					pub const SUBDIRS: SubdirMap = &sorted!([
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
@ -419,6 +534,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([
 | 
				
			|||||||
        &Router::new()
 | 
					        &Router::new()
 | 
				
			||||||
            .put(&API_METHOD_ERASE_MEDIA)
 | 
					            .put(&API_METHOD_ERASE_MEDIA)
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        "inventory",
 | 
				
			||||||
 | 
					        &Router::new()
 | 
				
			||||||
 | 
					            .get(&API_METHOD_INVENTORY)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
        "label-media",
 | 
					        "label-media",
 | 
				
			||||||
        &Router::new()
 | 
					        &Router::new()
 | 
				
			||||||
@ -429,6 +549,11 @@ pub const SUBDIRS: SubdirMap = &sorted!([
 | 
				
			|||||||
        &Router::new()
 | 
					        &Router::new()
 | 
				
			||||||
            .put(&API_METHOD_LOAD_SLOT)
 | 
					            .put(&API_METHOD_LOAD_SLOT)
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        "read-label",
 | 
				
			||||||
 | 
					        &Router::new()
 | 
				
			||||||
 | 
					            .get(&API_METHOD_READ_LABEL)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
    (
 | 
					    (
 | 
				
			||||||
        "rewind",
 | 
					        "rewind",
 | 
				
			||||||
        &Router::new()
 | 
					        &Router::new()
 | 
				
			||||||
 | 
				
			|||||||
@ -83,3 +83,14 @@ pub struct MediaLabelInfoFlat {
 | 
				
			|||||||
    #[serde(skip_serializing_if="Option::is_none")]
 | 
					    #[serde(skip_serializing_if="Option::is_none")]
 | 
				
			||||||
    pub media_set_ctime: Option<i64>,
 | 
					    pub media_set_ctime: Option<i64>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[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<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -249,6 +249,7 @@ fn read_label(
 | 
				
			|||||||
    mut param: Value,
 | 
					    mut param: Value,
 | 
				
			||||||
    rpcenv: &mut dyn RpcEnvironment,
 | 
					    rpcenv: &mut dyn RpcEnvironment,
 | 
				
			||||||
) -> Result<(), Error> {
 | 
					) -> Result<(), Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (config, _digest) = config::drive::config()?;
 | 
					    let (config, _digest) = config::drive::config()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    param["drive"] = lookup_drive_name(¶m, &config)?.into();
 | 
					    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);
 | 
					    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(())
 | 
					    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() {
 | 
					fn main() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let cmd_def = CliCommandMap::new()
 | 
					    let cmd_def = CliCommandMap::new()
 | 
				
			||||||
@ -292,6 +344,11 @@ fn main() {
 | 
				
			|||||||
            CliCommand::new(&API_METHOD_EJECT_MEDIA)
 | 
					            CliCommand::new(&API_METHOD_EJECT_MEDIA)
 | 
				
			||||||
                .completion_cb("drive", complete_drive_name)
 | 
					                .completion_cb("drive", complete_drive_name)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        .insert(
 | 
				
			||||||
 | 
					            "inventory",
 | 
				
			||||||
 | 
					            CliCommand::new(&API_METHOD_INVENTORY)
 | 
				
			||||||
 | 
					                .completion_cb("drive", complete_drive_name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        .insert(
 | 
					        .insert(
 | 
				
			||||||
            "read-label",
 | 
					            "read-label",
 | 
				
			||||||
            CliCommand::new(&API_METHOD_READ_LABEL)
 | 
					            CliCommand::new(&API_METHOD_READ_LABEL)
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,7 @@ use crate::{
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    tape::{
 | 
					    tape::{
 | 
				
			||||||
        TAPE_STATUS_DIR,
 | 
					        TAPE_STATUS_DIR,
 | 
				
			||||||
 | 
					        MediaLabelInfo,
 | 
				
			||||||
        file_formats::{
 | 
					        file_formats::{
 | 
				
			||||||
            DriveLabel,
 | 
					            DriveLabel,
 | 
				
			||||||
            MediaSetLabel,
 | 
					            MediaSetLabel,
 | 
				
			||||||
@ -46,6 +47,16 @@ pub struct MediaId {
 | 
				
			|||||||
    pub media_set_label: Option<MediaSetLabel>,
 | 
					    pub media_set_label: Option<MediaSetLabel>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<MediaLabelInfo> for MediaId {
 | 
				
			||||||
 | 
					    fn from(info: MediaLabelInfo) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            label: info.label.clone(),
 | 
				
			||||||
 | 
					            media_set_label: info.media_set_label.map(|(l, _)| l),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Media Set
 | 
					/// Media Set
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// A List of backup media
 | 
					/// A List of backup media
 | 
				
			||||||
@ -240,17 +251,6 @@ impl Inventory {
 | 
				
			|||||||
        Ok(())
 | 
					        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
 | 
					    /// Lookup media
 | 
				
			||||||
    pub fn lookup_media(&self, uuid: &Uuid) -> Option<&MediaId> {
 | 
					    pub fn lookup_media(&self, uuid: &Uuid) -> Option<&MediaId> {
 | 
				
			||||||
        self.map.get(uuid)
 | 
					        self.map.get(uuid)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user