tape: expose basic tape/changer functionality at api2/tape/

This commit is contained in:
Dietmar Maurer 2020-12-08 15:42:50 +01:00
parent 5ba83ed099
commit 5d90860688
9 changed files with 359 additions and 53 deletions

View File

@ -9,6 +9,7 @@ pub mod types;
pub mod version; pub mod version;
pub mod ping; pub mod ping;
pub mod pull; pub mod pull;
pub mod tape;
mod helpers; mod helpers;
use proxmox::api::router::SubdirMap; use proxmox::api::router::SubdirMap;
@ -27,6 +28,7 @@ pub const SUBDIRS: SubdirMap = &[
("pull", &pull::ROUTER), ("pull", &pull::ROUTER),
("reader", &reader::ROUTER), ("reader", &reader::ROUTER),
("status", &status::ROUTER), ("status", &status::ROUTER),
("tape", &tape::ROUTER),
("version", &version::ROUTER), ("version", &version::ROUTER),
]; ];

View File

@ -13,8 +13,6 @@ const SUBDIRS: SubdirMap = &[
("datastore", &datastore::ROUTER), ("datastore", &datastore::ROUTER),
("drive", &drive::ROUTER), ("drive", &drive::ROUTER),
("remote", &remote::ROUTER), ("remote", &remote::ROUTER),
("scan-changers", &changer::SCAN_CHANGERS),
("scan-drives", &drive::SCAN_DRIVES),
("sync", &sync::ROUTER), ("sync", &sync::ROUTER),
("verify", &verify::ROUTER) ("verify", &verify::ROUTER)
]; ];

View File

@ -11,7 +11,6 @@ use crate::{
LINUX_DRIVE_PATH_SCHEMA, LINUX_DRIVE_PATH_SCHEMA,
DriveListEntry, DriveListEntry,
ScsiTapeChanger, ScsiTapeChanger,
TapeDeviceInfo,
}, },
tape::{ tape::{
linux_tape_changer_list, linux_tape_changer_list,
@ -219,30 +218,6 @@ pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
Ok(()) 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<Vec<TapeDeviceInfo>, 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() const ITEM_ROUTER: Router = Router::new()
.get(&API_METHOD_GET_CONFIG) .get(&API_METHOD_GET_CONFIG)
.put(&API_METHOD_UPDATE_CHANGER) .put(&API_METHOD_UPDATE_CHANGER)

View File

@ -13,7 +13,6 @@ use crate::{
DriveListEntry, DriveListEntry,
LinuxTapeDrive, LinuxTapeDrive,
ScsiTapeChanger, ScsiTapeChanger,
TapeDeviceInfo,
}, },
tape::{ tape::{
linux_tape_device_list, linux_tape_device_list,
@ -224,31 +223,6 @@ pub fn delete_drive(name: String, _param: Value) -> Result<(), Error> {
Ok(()) 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<Vec<TapeDeviceInfo>, 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() const ITEM_ROUTER: Router = Router::new()
.get(&API_METHOD_GET_CONFIG) .get(&API_METHOD_GET_CONFIG)
.put(&API_METHOD_UPDATE_DRIVE) .put(&API_METHOD_UPDATE_DRIVE)

158
src/api2/tape/changer.rs Normal file
View File

@ -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<Vec<MtxStatusEntry>, 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<Vec<TapeDeviceInfo>, 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);

136
src/api2/tape/drive.rs Normal file
View File

@ -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<u64>,
_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<Vec<TapeDeviceInfo>, 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);

15
src/api2/tape/mod.rs Normal file
View File

@ -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);

View File

@ -111,3 +111,36 @@ pub struct DriveListEntry {
#[serde(skip_serializing_if="Option::is_none")] #[serde(skip_serializing_if="Option::is_none")]
pub serial: Option<String>, pub serial: Option<String>,
} }
#[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<String>,
/// The slot the drive was loaded from
#[serde(skip_serializing_if="Option::is_none")]
pub loaded_slot: Option<u64>,
}

View File

@ -57,6 +57,21 @@ pub fn mtx_unload(
Ok(()) 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 /// Extract the list of online media from MtxStatus
/// ///
/// Returns a HashSet containing all found media Uuid /// Returns a HashSet containing all found media Uuid