From fba0b77469300cd52c47badc7351eeccbf1f2c49 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Mon, 14 Dec 2020 07:55:57 +0100 Subject: [PATCH] tape: add media api --- src/api2/tape/media.rs | 168 ++++++++++++++++++++++++++++++++++ src/api2/tape/mod.rs | 2 + src/bin/proxmox-tape.rs | 1 + src/bin/proxmox_tape/media.rs | 119 ++++++++++++++++++++++++ src/bin/proxmox_tape/mod.rs | 3 + 5 files changed, 293 insertions(+) create mode 100644 src/api2/tape/media.rs create mode 100644 src/bin/proxmox_tape/media.rs diff --git a/src/api2/tape/media.rs b/src/api2/tape/media.rs new file mode 100644 index 00000000..a7f10efd --- /dev/null +++ b/src/api2/tape/media.rs @@ -0,0 +1,168 @@ +use std::path::Path; + +use anyhow::Error; + +use proxmox::api::{api, Router, SubdirMap}; +use proxmox::list_subdirs_api_method; + +use crate::{ + config::{ + self, + }, + api2::types::{ + MEDIA_POOL_NAME_SCHEMA, + MediaPoolConfig, + MediaListEntry, + MediaStatus, + MediaLocationKind, + }, + tape::{ + TAPE_STATUS_DIR, + Inventory, + MediaStateDatabase, + MediaLocation, + MediaPool, + update_online_status, + }, +}; + +fn split_location(location: &MediaLocation) -> (MediaLocationKind, Option) { + match location { + MediaLocation::Online(changer_name) => { + (MediaLocationKind::Online, Some(changer_name.to_string())) + } + MediaLocation::Offline => { + (MediaLocationKind::Offline, None) + } + MediaLocation::Vault(vault) => { + (MediaLocationKind::Vault, Some(vault.to_string())) + } + } +} + +#[api( + input: { + properties: { + pool: { + schema: MEDIA_POOL_NAME_SCHEMA, + optional: true, + }, + }, + }, + returns: { + description: "List of registered backup media.", + type: Array, + items: { + type: MediaListEntry, + }, + }, +)] +/// List pool media +pub async fn list_media(pool: Option) -> Result, Error> { + + let (config, _digest) = config::media_pool::config()?; + + let status_path = Path::new(TAPE_STATUS_DIR); + + tokio::task::spawn_blocking(move || { + if let Err(err) = update_online_status(status_path) { + eprintln!("{}", err); + eprintln!("update online media status failed - using old state"); + } + }).await?; + + let mut list = Vec::new(); + + for (_section_type, data) in config.sections.values() { + let pool_name = match data["name"].as_str() { + None => continue, + Some(name) => name, + }; + if let Some(ref name) = pool { + if name != pool_name { + continue; + } + } + + let config: MediaPoolConfig = config.lookup("pool", pool_name)?; + + let pool = MediaPool::with_config(pool_name, status_path, &config)?; + + let current_time = proxmox::tools::time::epoch_i64(); + + for media in pool.list_media() { + let (location, location_hint) = split_location(&media.location()); + + let expired = pool.media_is_expired(&media, current_time); + + let media_set_uuid = media.media_set_label().as_ref() + .map(|set| set.uuid.to_string()); + + let seq_nr = media.media_set_label().as_ref() + .map(|set| set.seq_nr); + + let media_set_name = media.media_set_label().as_ref() + .map(|set| { + pool.generate_media_set_name(&set.uuid, config.template.clone()) + .unwrap_or_else(|_| set.uuid.to_string()) + }); + + list.push(MediaListEntry { + uuid: media.uuid().to_string(), + changer_id: media.changer_id().to_string(), + pool: Some(pool_name.to_string()), + location, + location_hint, + status: *media.status(), + expired, + media_set_uuid, + media_set_name, + seq_nr, + }); + } + } + + if pool.is_none() { + + let inventory = Inventory::load(status_path)?; + let state_db = MediaStateDatabase::load(status_path)?; + + for media_id in inventory.list_unassigned_media() { + + let (mut status, location) = state_db.status_and_location(&media_id.label.uuid); + let (location, location_hint) = split_location(&location); + + if status == MediaStatus::Unknown { + status = MediaStatus::Writable; + } + + list.push(MediaListEntry { + uuid: media_id.label.uuid.to_string(), + changer_id: media_id.label.changer_id.to_string(), + location, + location_hint, + status, + expired: false, + media_set_uuid: None, + media_set_name: None, + seq_nr: None, + pool: None, + }); + } + } + + Ok(list) +} + +const SUBDIRS: SubdirMap = &[ + ( + "list", + &Router::new() + .get(&API_METHOD_LIST_MEDIA) + ), +]; + + +pub const ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(SUBDIRS)) + .subdirs(SUBDIRS); diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs index 68484259..d99aed9b 100644 --- a/src/api2/tape/mod.rs +++ b/src/api2/tape/mod.rs @@ -4,10 +4,12 @@ use proxmox::list_subdirs_api_method; pub mod drive; pub mod changer; +pub mod media; pub const SUBDIRS: SubdirMap = &[ ("changer", &changer::ROUTER), ("drive", &drive::ROUTER), + ("media", &media::ROUTER), ]; pub const ROUTER: Router = Router::new() diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index dc087ff6..0a47dd76 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -455,6 +455,7 @@ fn main() { .insert("changer", changer_commands()) .insert("drive", drive_commands()) .insert("pool", pool_commands()) + .insert("media", media_commands()) .insert( "load-media", CliCommand::new(&API_METHOD_LOAD_MEDIA) diff --git a/src/bin/proxmox_tape/media.rs b/src/bin/proxmox_tape/media.rs new file mode 100644 index 00000000..783a4281 --- /dev/null +++ b/src/bin/proxmox_tape/media.rs @@ -0,0 +1,119 @@ +use anyhow::{Error}; +use serde_json::Value; + +use proxmox::{ + api::{ + api, + cli::*, + RpcEnvironment, + ApiHandler, + }, +}; + +use proxmox_backup::{ + api2::{ + self, + types::{ + MEDIA_POOL_NAME_SCHEMA, + MediaLocationKind, + MediaStatus, + MediaListEntry, + }, + }, + config::{ + media_pool::complete_pool_name, + }, +}; + +pub fn media_commands() -> CommandLineInterface { + + let cmd_def = CliCommandMap::new() + .insert( + "list", + CliCommand::new(&API_METHOD_LIST_MEDIA) + .completion_cb("pool", complete_pool_name) + ) + ; + + cmd_def.into() +} + +#[api( + input: { + properties: { + pool: { + schema: MEDIA_POOL_NAME_SCHEMA, + optional: true, + }, + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + }, + }, +)] +/// List pool media +async fn list_media( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::tape::media::API_METHOD_LIST_MEDIA; + let mut data = match info.handler { + ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?, + _ => unreachable!(), + }; + + fn render_location(_value: &Value, record: &Value) -> Result { + let record: MediaListEntry = serde_json::from_value(record.clone())?; + Ok(match record.location { + MediaLocationKind::Online => { + record.location_hint.unwrap_or(String::from("-")) + } + MediaLocationKind::Offline => String::from("offline"), + MediaLocationKind::Vault => { + format!("V({})", record.location_hint.unwrap_or(String::from("-"))) + } + }) + } + + fn render_status(_value: &Value, record: &Value) -> Result { + let record: MediaListEntry = serde_json::from_value(record.clone())?; + Ok(match record.status { + MediaStatus::Damaged | MediaStatus::Retired => { + serde_json::to_value(&record.status)? + .as_str().unwrap() + .to_string() + } + _ => { + if record.expired { + String::from("expired") + } else { + serde_json::to_value(&record.status)? + .as_str().unwrap() + .to_string() + } + } + }) + } + + let options = default_table_format_options() + .sortby("pool", false) + .sortby("media-set-uuid", false) + .sortby("seq-nr", false) + .sortby("changer-id", false) + .column(ColumnConfig::new("changer-id")) + .column(ColumnConfig::new("pool")) + .column(ColumnConfig::new("media-set-name")) + .column(ColumnConfig::new("seq-nr")) + .column(ColumnConfig::new("status").renderer(render_status)) + .column(ColumnConfig::new("location").renderer(render_location)) + .column(ColumnConfig::new("uuid")) + .column(ColumnConfig::new("media-set-uuid")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} diff --git a/src/bin/proxmox_tape/mod.rs b/src/bin/proxmox_tape/mod.rs index 4db89d44..04e6cf50 100644 --- a/src/bin/proxmox_tape/mod.rs +++ b/src/bin/proxmox_tape/mod.rs @@ -6,3 +6,6 @@ pub use drive::*; mod pool; pub use pool::*; + +mod media; +pub use media::*;