From e6604cf3910262d2d273ef8fbac48a89c9941ad5 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Wed, 9 Dec 2020 12:58:43 +0100 Subject: [PATCH] tape: add command line interface proxmox-tape --- src/bin/proxmox-tape.rs | 22 +++ src/bin/proxmox_tape/changer.rs | 341 ++++++++++++++++++++++++++++++++ src/bin/proxmox_tape/drive.rs | 339 +++++++++++++++++++++++++++++++ src/bin/proxmox_tape/mod.rs | 5 + 4 files changed, 707 insertions(+) create mode 100644 src/bin/proxmox-tape.rs create mode 100644 src/bin/proxmox_tape/changer.rs create mode 100644 src/bin/proxmox_tape/drive.rs create mode 100644 src/bin/proxmox_tape/mod.rs diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs new file mode 100644 index 00000000..bb5684c3 --- /dev/null +++ b/src/bin/proxmox-tape.rs @@ -0,0 +1,22 @@ +use proxmox::{ + api::{ + cli::*, + RpcEnvironment, + }, +}; + +mod proxmox_tape; +use proxmox_tape::*; + +fn main() { + + let cmd_def = CliCommandMap::new() + .insert("changer", changer_commands()) + .insert("drive", drive_commands()) + ; + + let mut rpcenv = CliEnvironment::new(); + rpcenv.set_auth_id(Some(String::from("root@pam"))); + + proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv)); +} diff --git a/src/bin/proxmox_tape/changer.rs b/src/bin/proxmox_tape/changer.rs new file mode 100644 index 00000000..7968d4b5 --- /dev/null +++ b/src/bin/proxmox_tape/changer.rs @@ -0,0 +1,341 @@ +use anyhow::{Error}; +use serde_json::Value; + +use proxmox::{ + api::{ + api, + cli::*, + RpcEnvironment, + ApiHandler, + }, +}; + +use proxmox_backup::{ + api2::{ + self, + types::{ + CHANGER_ID_SCHEMA, + LINUX_DRIVE_PATH_SCHEMA, + ScsiTapeChanger, + }, + }, + tape::{ + complete_changer_path, + }, + config::{ + self, + drive::{ + complete_drive_name, + complete_changer_name, + } + }, +}; + +pub fn changer_commands() -> CommandLineInterface { + + let cmd_def = CliCommandMap::new() + .insert("scan-for-changers", CliCommand::new(&API_METHOD_SCAN_FOR_CHANGERS)) + .insert("list", CliCommand::new(&API_METHOD_LIST_CHANGERS)) + .insert("config", + CliCommand::new(&API_METHOD_GET_CONFIG) + .arg_param(&["name"]) + .completion_cb("name", complete_changer_name) + ) + .insert( + "remove", + CliCommand::new(&API_METHOD_DELETE_CHANGER) + .arg_param(&["name"]) + .completion_cb("name", complete_changer_name) + ) + .insert( + "create", + CliCommand::new(&API_METHOD_CREATE_CHANGER) + .arg_param(&["name"]) + .completion_cb("name", complete_drive_name) + .completion_cb("path", complete_changer_path) + ) + .insert( + "update", + CliCommand::new(&API_METHOD_UPDATE_CHANGER) + .arg_param(&["name"]) + .completion_cb("name", complete_changer_name) + .completion_cb("path", complete_changer_path) + ) + .insert("status", + CliCommand::new(&API_METHOD_GET_STATUS) + .arg_param(&["name"]) + .completion_cb("name", complete_changer_name) + ) + .insert("transfer", + CliCommand::new(&API_METHOD_TRANSFER) + .arg_param(&["name"]) + .completion_cb("name", complete_changer_name) + ) + ; + + cmd_def.into() +} + +#[api( + input: { + properties: { + name: { + schema: CHANGER_ID_SCHEMA, + }, + path: { + schema: LINUX_DRIVE_PATH_SCHEMA, + }, + }, + }, +)] +/// Create a new changer device +fn create_changer( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::changer::API_METHOD_CREATE_CHANGER; + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + }, + }, +)] +/// List changers +fn list_changers( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::config::changer::API_METHOD_LIST_CHANGERS; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("name")) + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("vendor")) + .column(ColumnConfig::new("model")) + .column(ColumnConfig::new("serial")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + }, + }, +)] +/// Scan for SCSI tape changers +fn scan_for_changers( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::tape::changer::API_METHOD_SCAN_CHANGERS; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("vendor")) + .column(ColumnConfig::new("model")) + .column(ColumnConfig::new("serial")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + name: { + schema: CHANGER_ID_SCHEMA, + }, + }, + }, +)] +/// Get tape changer configuration +fn get_config( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::config::changer::API_METHOD_GET_CONFIG; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("name")) + .column(ColumnConfig::new("path")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: CHANGER_ID_SCHEMA, + }, + }, + }, +)] +/// Delete a tape changer configuration +fn delete_changer( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::changer::API_METHOD_DELETE_CHANGER; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: CHANGER_ID_SCHEMA, + }, + path: { + schema: LINUX_DRIVE_PATH_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Update a tape changer configuration +fn update_changer( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::changer::API_METHOD_UPDATE_CHANGER; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + name: { + schema: CHANGER_ID_SCHEMA, + }, + }, + }, +)] +/// Get tape changer status +fn get_status( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::tape::changer::API_METHOD_GET_STATUS; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("entry-kind")) + .column(ColumnConfig::new("entry-id")) + .column(ColumnConfig::new("changer-id")) + .column(ColumnConfig::new("loaded-slot")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[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 +fn transfer( + name: String, + from: u64, + to: u64, + _param: Value, +) -> Result<(), Error> { + + let (config, _digest) = config::drive::config()?; + + let data: ScsiTapeChanger = config.lookup("changer", &name)?; + + let mut command = std::process::Command::new("mtx"); + command.args(&["-f", &data.path, "transfer", &from.to_string(), &to.to_string()]); + + proxmox_backup::tools::run_command(command, None)?; + + Ok(()) +} diff --git a/src/bin/proxmox_tape/drive.rs b/src/bin/proxmox_tape/drive.rs new file mode 100644 index 00000000..5bbfb175 --- /dev/null +++ b/src/bin/proxmox_tape/drive.rs @@ -0,0 +1,339 @@ +use anyhow::Error; +use serde_json::Value; + +use proxmox::{ + api::{ + api, + cli::*, + RpcEnvironment, + ApiHandler, + }, +}; + +use proxmox_backup::{ + api2::{ + self, + types::{ + DRIVE_ID_SCHEMA, + CHANGER_ID_SCHEMA, + LINUX_DRIVE_PATH_SCHEMA, + }, + }, + tape::{ + complete_drive_path, + }, + config::drive::{ + complete_drive_name, + complete_changer_name, + complete_linux_drive_name, + }, +}; + +pub fn drive_commands() -> CommandLineInterface { + + let cmd_def = CliCommandMap::new() + .insert("scan-for-drives", CliCommand::new(&API_METHOD_SCAN_FOR_DRIVES)) + .insert("list", CliCommand::new(&API_METHOD_LIST_DRIVES)) + .insert("config", + CliCommand::new(&API_METHOD_GET_CONFIG) + .arg_param(&["name"]) + .completion_cb("name", complete_linux_drive_name) + ) + .insert( + "remove", + CliCommand::new(&API_METHOD_DELETE_DRIVE) + .arg_param(&["name"]) + .completion_cb("name", complete_linux_drive_name) + ) + .insert( + "create", + CliCommand::new(&API_METHOD_CREATE_LINUX_DRIVE) + .arg_param(&["name"]) + .completion_cb("name", complete_drive_name) + .completion_cb("path", complete_drive_path) + .completion_cb("changer", complete_changer_name) + ) + .insert( + "update", + CliCommand::new(&API_METHOD_UPDATE_LINUX_DRIVE) + .arg_param(&["name"]) + .completion_cb("name", complete_linux_drive_name) + .completion_cb("path", complete_drive_path) + .completion_cb("changer", complete_changer_name) + ) + .insert( + "load", + CliCommand::new(&API_METHOD_LOAD_SLOT) + .arg_param(&["name"]) + .completion_cb("name", complete_linux_drive_name) + ) + .insert( + "unload", + CliCommand::new(&API_METHOD_UNLOAD) + .arg_param(&["name"]) + .completion_cb("name", complete_linux_drive_name) + ) + ; + + cmd_def.into() +} + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + path: { + schema: LINUX_DRIVE_PATH_SCHEMA, + }, + changer: { + schema: CHANGER_ID_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Create a new drive +fn create_linux_drive( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::drive::API_METHOD_CREATE_DRIVE; + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + }, + }, +)] +/// List drives +fn list_drives( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::config::drive::API_METHOD_LIST_DRIVES; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("name")) + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("changer")) + .column(ColumnConfig::new("vendor")) + .column(ColumnConfig::new("model")) + .column(ColumnConfig::new("serial")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + }, + } +)] +/// Scan for drives +fn scan_for_drives( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::tape::drive::API_METHOD_SCAN_DRIVES; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("vendor")) + .column(ColumnConfig::new("model")) + .column(ColumnConfig::new("serial")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + + +#[api( + input: { + properties: { + "output-format": { + schema: OUTPUT_FORMAT, + optional: true, + }, + name: { + schema: DRIVE_ID_SCHEMA, + }, + }, + }, +)] +/// Get pool configuration +fn get_config( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let output_format = get_output_format(¶m); + let info = &api2::config::drive::API_METHOD_GET_CONFIG; + let mut data = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + let options = default_table_format_options() + .column(ColumnConfig::new("name")) + .column(ColumnConfig::new("path")) + .column(ColumnConfig::new("changer")) + ; + + format_and_print_result_full(&mut data, info.returns, &output_format, &options); + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + }, + }, +)] +/// Delete a drive configuration +fn delete_drive( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::drive::API_METHOD_DELETE_DRIVE; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + path: { + schema: LINUX_DRIVE_PATH_SCHEMA, + optional: true, + }, + changer: { + schema: CHANGER_ID_SCHEMA, + optional: true, + }, + }, + }, +)] +/// Update a drive configuration +fn update_linux_drive( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::config::drive::API_METHOD_UPDATE_DRIVE; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[api( + input: { + properties: { + name: { + schema: DRIVE_ID_SCHEMA, + }, + slot: { + type: u64, + description: "Source slot number", + minimum: 1, + }, + }, + }, +)] +/// Load media via changer from slot +fn load_slot( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::tape::drive::API_METHOD_LOAD_SLOT; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} + +#[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.", + type: u64, + minimum: 1, + optional: true, + }, + }, + }, +)] +/// Unload media via changer +fn unload( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::tape::drive::API_METHOD_UNLOAD; + + match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + Ok(()) +} diff --git a/src/bin/proxmox_tape/mod.rs b/src/bin/proxmox_tape/mod.rs new file mode 100644 index 00000000..5d7476c1 --- /dev/null +++ b/src/bin/proxmox_tape/mod.rs @@ -0,0 +1,5 @@ +mod changer; +pub use changer::*; + +mod drive; +pub use drive::*;