diff --git a/src/api2/tape/media.rs b/src/api2/tape/media.rs index c2833420..a8023676 100644 --- a/src/api2/tape/media.rs +++ b/src/api2/tape/media.rs @@ -17,32 +17,16 @@ use crate::{ 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: { @@ -94,8 +78,6 @@ pub async fn list_media(pool: Option) -> Result, Err 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() @@ -114,8 +96,7 @@ pub async fn list_media(pool: Option) -> Result, Err uuid: media.uuid().to_string(), changer_id: media.changer_id().to_string(), pool: Some(pool_name.to_string()), - location, - location_hint, + location: media.location().clone(), status: *media.status(), expired, media_set_uuid, @@ -133,7 +114,6 @@ pub async fn list_media(pool: Option) -> Result, Err 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; @@ -143,7 +123,6 @@ pub async fn list_media(pool: Option) -> Result, Err 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, diff --git a/src/api2/types/tape/media.rs b/src/api2/types/tape/media.rs index a9dd5365..e4c58e3b 100644 --- a/src/api2/types/tape/media.rs +++ b/src/api2/types/tape/media.rs @@ -4,26 +4,13 @@ use proxmox::api::api; use super::{ MediaStatus, + MediaLocation, }; -#[api()] -#[derive(Serialize,Deserialize)] -#[serde(rename_all = "lowercase")] -/// Media location -pub enum MediaLocationKind { - /// Ready for use (inside tape library) - Online, - /// Local available, but need to be mounted (insert into tape - /// drive) - Offline, - /// Media is inside a Vault - Vault, -} - #[api( properties: { location: { - type: MediaLocationKind, + type: MediaLocation, }, status: { type: MediaStatus, @@ -38,9 +25,7 @@ pub struct MediaListEntry { pub changer_id: String, /// Media Uuid pub uuid: String, - pub location: MediaLocationKind, - /// Media location hint (vault name, changer name) - pub location_hint: Option, + pub location: MediaLocation, pub status: MediaStatus, /// Expired flag pub expired: bool, diff --git a/src/api2/types/tape/media_location.rs b/src/api2/types/tape/media_location.rs new file mode 100644 index 00000000..31a15c65 --- /dev/null +++ b/src/api2/types/tape/media_location.rs @@ -0,0 +1,91 @@ +use anyhow::{bail, Error}; + +use proxmox::api::{ + schema::{ + Schema, + StringSchema, + ApiStringFormat, + parse_simple_value, + }, +}; + +use crate::api2::types::{ + PROXMOX_SAFE_ID_FORMAT, + CHANGER_NAME_SCHEMA, +}; + +pub const VAULT_NAME_SCHEMA: Schema = StringSchema::new("Vault name.") + .format(&PROXMOX_SAFE_ID_FORMAT) + .min_length(3) + .max_length(32) + .schema(); + +#[derive(Debug, PartialEq, Clone)] +/// Media location +pub enum MediaLocation { + /// Ready for use (inside tape library) + Online(String), + /// Local available, but need to be mounted (insert into tape + /// drive) + Offline, + /// Media is inside a Vault + Vault(String), +} + +proxmox::forward_deserialize_to_from_str!(MediaLocation); +proxmox::forward_serialize_to_display!(MediaLocation); + +impl MediaLocation { + pub const API_SCHEMA: Schema = StringSchema::new( + "Media location (e.g. 'offline', 'online-', 'vault-')") + .format(&ApiStringFormat::VerifyFn(|text| { + let location: MediaLocation = text.parse()?; + match location { + MediaLocation::Online(ref changer) => { + parse_simple_value(changer, &CHANGER_NAME_SCHEMA)?; + } + MediaLocation::Vault(ref vault) => { + parse_simple_value(vault, &VAULT_NAME_SCHEMA)?; + } + MediaLocation::Offline => { /* OK */} + } + Ok(()) + })) + .schema(); +} + + +impl std::fmt::Display for MediaLocation { + + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MediaLocation::Offline => { + write!(f, "offline") + } + MediaLocation::Online(changer) => { + write!(f, "online-{}", changer) + } + MediaLocation::Vault(vault) => { + write!(f, "vault-{}", vault) + } + } + } +} + +impl std::str::FromStr for MediaLocation { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s == "offline" { + return Ok(MediaLocation::Offline); + } + if let Some(changer) = s.strip_prefix("online-") { + return Ok(MediaLocation::Online(changer.to_string())); + } + if let Some(vault) = s.strip_prefix("vault-") { + return Ok(MediaLocation::Online(vault.to_string())); + } + + bail!("MediaLocation parse error"); + } +} diff --git a/src/api2/types/tape/mod.rs b/src/api2/types/tape/mod.rs index 7128206b..68b2cf12 100644 --- a/src/api2/types/tape/mod.rs +++ b/src/api2/types/tape/mod.rs @@ -15,5 +15,8 @@ pub use media_pool::*; mod media_status; pub use media_status::*; +mod media_location; +pub use media_location::*; + mod media; pub use media::*; diff --git a/src/bin/proxmox_tape/media.rs b/src/bin/proxmox_tape/media.rs index 51f69252..374cb891 100644 --- a/src/bin/proxmox_tape/media.rs +++ b/src/bin/proxmox_tape/media.rs @@ -15,7 +15,6 @@ use proxmox_backup::{ self, types::{ MEDIA_POOL_NAME_SCHEMA, - MediaLocationKind, MediaStatus, MediaListEntry, }, @@ -72,19 +71,6 @@ async fn list_media( _ => 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 { @@ -115,7 +101,7 @@ async fn list_media( .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("location")) .column(ColumnConfig::new("uuid")) .column(ColumnConfig::new("media-set-uuid")) ; diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs index 181eba41..71e183a5 100644 --- a/src/tape/media_pool.rs +++ b/src/tape/media_pool.rs @@ -16,6 +16,7 @@ use proxmox::tools::Uuid; use crate::{ api2::types::{ MediaStatus, + MediaLocation, MediaSetPolicy, RetentionPolicy, MediaPoolConfig, @@ -24,7 +25,6 @@ use crate::{ tape::{ MediaId, MediaSet, - MediaLocation, Inventory, MediaStateDatabase, file_formats::{ diff --git a/src/tape/media_state_database.rs b/src/tape/media_state_database.rs index dafb66bb..2804e284 100644 --- a/src/tape/media_state_database.rs +++ b/src/tape/media_state_database.rs @@ -21,21 +21,10 @@ use crate::{ }, api2::types::{ MediaStatus, + MediaLocation, }, }; -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -/// Media location -pub enum MediaLocation { - /// Ready for use (inside tape library) - Online(String), - /// Local available, but need to be mounted (insert into tape - /// drive) - Offline, - /// Media is inside a Vault - Vault(String), -} - #[derive(Serialize,Deserialize)] struct MediaStateEntry { u: Uuid,