diff --git a/src/api2.rs b/src/api2.rs index 27ef2975..4783df63 100644 --- a/src/api2.rs +++ b/src/api2.rs @@ -9,6 +9,7 @@ pub mod types; pub mod version; pub mod ping; pub mod pull; +pub mod tape; mod helpers; use proxmox::api::router::SubdirMap; @@ -27,6 +28,7 @@ pub const SUBDIRS: SubdirMap = &[ ("pull", &pull::ROUTER), ("reader", &reader::ROUTER), ("status", &status::ROUTER), + ("tape", &tape::ROUTER), ("version", &version::ROUTER), ]; diff --git a/src/api2/config.rs b/src/api2/config.rs index 34b04d55..173c8783 100644 --- a/src/api2/config.rs +++ b/src/api2/config.rs @@ -13,8 +13,6 @@ const SUBDIRS: SubdirMap = &[ ("datastore", &datastore::ROUTER), ("drive", &drive::ROUTER), ("remote", &remote::ROUTER), - ("scan-changers", &changer::SCAN_CHANGERS), - ("scan-drives", &drive::SCAN_DRIVES), ("sync", &sync::ROUTER), ("verify", &verify::ROUTER) ]; diff --git a/src/api2/config/changer.rs b/src/api2/config/changer.rs index c6b0efcd..f97b6d05 100644 --- a/src/api2/config/changer.rs +++ b/src/api2/config/changer.rs @@ -11,7 +11,6 @@ use crate::{ LINUX_DRIVE_PATH_SCHEMA, DriveListEntry, ScsiTapeChanger, - TapeDeviceInfo, }, tape::{ linux_tape_changer_list, @@ -219,30 +218,6 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> { Ok(()) } -#[api( - input: { - properties: {}, - }, - returns: { - description: "The list of autodetected tape changers.", - type: Array, - items: { - type: TapeDeviceInfo, - }, - }, -)] -/// Scan for SCSI tape changers -pub fn scan_changers(_param: Value) -> Result, Error> { - - let list = linux_tape_changer_list(); - - Ok(list) -} - -pub const SCAN_CHANGERS: Router = Router::new() - .get(&API_METHOD_SCAN_CHANGERS); - - const ITEM_ROUTER: Router = Router::new() .get(&API_METHOD_GET_CONFIG) .put(&API_METHOD_UPDATE_CHANGER) diff --git a/src/api2/config/drive.rs b/src/api2/config/drive.rs index 1ab51e01..eb7eeb71 100644 --- a/src/api2/config/drive.rs +++ b/src/api2/config/drive.rs @@ -13,7 +13,6 @@ use crate::{ DriveListEntry, LinuxTapeDrive, ScsiTapeChanger, - TapeDeviceInfo, }, tape::{ linux_tape_device_list, @@ -224,31 +223,6 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> { Ok(()) } -#[api( - input: { - properties: {}, - }, - returns: { - description: "The list of autodetected tape drives.", - type: Array, - items: { - type: TapeDeviceInfo, - }, - }, -)] -/// Scan tape drives -pub fn scan_drives(_param: Value) -> Result, Error> { - - let list = linux_tape_device_list(); - - Ok(list) -} - - -pub const SCAN_DRIVES: Router = Router::new() - .get(&API_METHOD_SCAN_DRIVES); - - const ITEM_ROUTER: Router = Router::new() .get(&API_METHOD_GET_CONFIG) .put(&API_METHOD_UPDATE_DRIVE) diff --git a/src/api2/tape/changer.rs b/src/api2/tape/changer.rs new file mode 100644 index 00000000..77829d3a --- /dev/null +++ b/src/api2/tape/changer.rs @@ -0,0 +1,158 @@ +use anyhow::Error; +use serde_json::Value; + +use proxmox::api::{api, Router, SubdirMap}; +use proxmox::list_subdirs_api_method; + +use crate::{ + config, + api2::types::{ + CHANGER_ID_SCHEMA, + ScsiTapeChanger, + TapeDeviceInfo, + MtxStatusEntry, + MtxEntryKind, + }, + tape::{ + ElementStatus, + linux_tape_changer_list, + mtx_status, + mtx_transfer, + }, +}; + + +#[api( + input: { + properties: { + name: { + schema: CHANGER_ID_SCHEMA, + }, + }, + }, + returns: { + description: "A status entry for each drive and slot.", + type: Array, + items: { + type: MtxStatusEntry, + }, + }, +)] +/// Get tape changer status +pub fn get_status(name: String) -> Result, Error> { + + let (config, _digest) = config::drive::config()?; + + let data: ScsiTapeChanger = config.lookup("changer", &name)?; + + let status = mtx_status(&data.path)?; + + /* todo: update persistent state + let state_path = Path::new(MEDIA_POOL_STATUS_DIR); + let inventory = Inventory::load(state_path)?; + + let mut map = OnlineStatusMap::new(&config)?; + let online_set = mtx_status_to_online_set(&status, &inventory); + map.update_online_status(&name, online_set)?; + + let mut state_db = MediaStateDatabase::load(state_path)?; + state_db.update_online_status(&map)?; + */ + + let mut list = Vec::new(); + + for (id, drive_status) in status.drives.iter().enumerate() { + let entry = MtxStatusEntry { + entry_kind: MtxEntryKind::Drive, + entry_id: id as u64, + changer_id: match &drive_status.status { + ElementStatus::Empty => None, + ElementStatus::Full => Some(String::new()), + ElementStatus::VolumeTag(tag) => Some(tag.to_string()), + }, + loaded_slot: drive_status.loaded_slot, + }; + list.push(entry); + } + + for (id, slot_status) in status.slots.iter().enumerate() { + let entry = MtxStatusEntry { + entry_kind: MtxEntryKind::Slot, + entry_id: id as u64 + 1, + changer_id: match &slot_status { + ElementStatus::Empty => None, + ElementStatus::Full => Some(String::new()), + ElementStatus::VolumeTag(tag) => Some(tag.to_string()), + }, + loaded_slot: None, + }; + list.push(entry); + } + + Ok(list) +} + +#[api( + input: { + properties: { + name: { + schema: CHANGER_ID_SCHEMA, + }, + from: { + description: "Source slot number", + minimum: 1, + }, + to: { + description: "Destination slot number", + minimum: 1, + }, + }, + }, +)] +/// Transfers media from one slot to another +pub fn transfer( + name: String, + from: u64, + to: u64, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + let data: ScsiTapeChanger = config.lookup("changer", &name)?; + + mtx_transfer(&data.path, from, to)?; + + Ok(()) +} + +#[api( + input: { + properties: {}, + }, + returns: { + description: "The list of autodetected tape changers.", + type: Array, + items: { + type: TapeDeviceInfo, + }, + }, +)] +/// Scan for SCSI tape changers +pub fn scan_changers(_param: Value) -> Result, Error> { + + let list = linux_tape_changer_list(); + + Ok(list) +} + +const SUBDIRS: SubdirMap = &[ + ( + "scan", + &Router::new() + .get(&API_METHOD_SCAN_CHANGERS) + ), +]; + +pub const ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(SUBDIRS)) + .subdirs(SUBDIRS); diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs new file mode 100644 index 00000000..b7ccb2cc --- /dev/null +++ b/src/api2/tape/drive.rs @@ -0,0 +1,136 @@ +use anyhow::{bail, Error}; +use serde_json::Value; + +use proxmox::api::{api, Router, SubdirMap}; +use proxmox::list_subdirs_api_method; + +use crate::{ + config, + api2::types::{ + DRIVE_ID_SCHEMA, + LinuxTapeDrive, + ScsiTapeChanger, + TapeDeviceInfo, + }, + tape::{ + MediaChange, + mtx_load, + mtx_unload, + linux_tape_device_list, + }, +}; + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + slot: { + description: "Source slot number", + minimum: 1, + }, + }, + }, +)] +/// Load media via changer from slot +pub fn load_slot( + name: String, + slot: u64, + _param: Value, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + let drive: LinuxTapeDrive = config.lookup("linux", &name)?; + + let changer: ScsiTapeChanger = match drive.changer { + Some(ref changer) => config.lookup("changer", changer)?, + None => bail!("drive '{}' has no associated changer", name), + }; + + let drivenum = 0; + + mtx_load(&changer.path, slot, drivenum) +} + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + slot: { + description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.", + minimum: 1, + optional: true, + }, + }, + }, +)] +/// Unload media via changer +pub fn unload( + name: String, + slot: Option, + _param: Value, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + let mut drive: LinuxTapeDrive = config.lookup("linux", &name)?; + + let changer: ScsiTapeChanger = match drive.changer { + Some(ref changer) => config.lookup("changer", changer)?, + None => bail!("drive '{}' has no associated changer", name), + }; + + let drivenum: u64 = 0; + + if let Some(slot) = slot { + mtx_unload(&changer.path, slot, drivenum) + } else { + drive.unload_media() + } +} + +#[api( + input: { + properties: {}, + }, + returns: { + description: "The list of autodetected tape drives.", + type: Array, + items: { + type: TapeDeviceInfo, + }, + }, +)] +/// Scan tape drives +pub fn scan_drives(_param: Value) -> Result, Error> { + + let list = linux_tape_device_list(); + + Ok(list) +} + +pub const SUBDIRS: SubdirMap = &[ + ( + "load-slot", + &Router::new() + .put(&API_METHOD_LOAD_SLOT) + ), + ( + "scan", + &Router::new() + .get(&API_METHOD_SCAN_DRIVES) + ), + ( + "unload", + &Router::new() + .put(&API_METHOD_UNLOAD) + ), +]; + +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 new file mode 100644 index 00000000..68484259 --- /dev/null +++ b/src/api2/tape/mod.rs @@ -0,0 +1,15 @@ +use proxmox::api::router::SubdirMap; +use proxmox::api::Router; +use proxmox::list_subdirs_api_method; + +pub mod drive; +pub mod changer; + +pub const SUBDIRS: SubdirMap = &[ + ("changer", &changer::ROUTER), + ("drive", &drive::ROUTER), +]; + +pub const ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(SUBDIRS)) + .subdirs(SUBDIRS); diff --git a/src/api2/types/tape/drive.rs b/src/api2/types/tape/drive.rs index d3a47ec0..f9029db5 100644 --- a/src/api2/types/tape/drive.rs +++ b/src/api2/types/tape/drive.rs @@ -111,3 +111,36 @@ pub struct DriveListEntry { #[serde(skip_serializing_if="Option::is_none")] pub serial: Option, } + +#[api()] +#[derive(Serialize,Deserialize)] +#[serde(rename_all = "lowercase")] +/// Mtx Entry Kind +pub enum MtxEntryKind { + /// Drive + Drive, + /// Slot + Slot, +} + +#[api( + properties: { + "entry-kind": { + type: MtxEntryKind, + }, + }, +)] +#[derive(Serialize,Deserialize)] +#[serde(rename_all = "kebab-case")] +/// Mtx Status Entry +pub struct MtxStatusEntry { + pub entry_kind: MtxEntryKind, + /// The ID of the slot or drive + pub entry_id: u64, + /// The media label (volume tag) if the slot/drive is full + #[serde(skip_serializing_if="Option::is_none")] + pub changer_id: Option, + /// The slot the drive was loaded from + #[serde(skip_serializing_if="Option::is_none")] + pub loaded_slot: Option, +} diff --git a/src/tape/changer/mtx_wrapper.rs b/src/tape/changer/mtx_wrapper.rs index ae0ec099..ef0c3328 100644 --- a/src/tape/changer/mtx_wrapper.rs +++ b/src/tape/changer/mtx_wrapper.rs @@ -57,6 +57,21 @@ pub fn mtx_unload( Ok(()) } +/// Run 'mtx transfer' +pub fn mtx_transfer( + path: &str, + from_slot: u64, + to_slot: u64, +) -> Result<(), Error> { + + let mut command = std::process::Command::new("mtx"); + command.args(&["-f", path, "transfer", &from_slot.to_string(), &to_slot.to_string()]); + + run_command(command, None)?; + + Ok(()) +} + /// Extract the list of online media from MtxStatus /// /// Returns a HashSet containing all found media Uuid