From b9b4b31284ef5418730fe4c0663ca1b9aa560c98 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Thu, 31 Dec 2020 10:26:48 +0100 Subject: [PATCH] tape: add basic restore api/command --- src/api2/tape/mod.rs | 1 + src/api2/tape/restore.rs | 128 ++++++++++++++++++++++++++++++++++++++- src/bin/proxmox-tape.rs | 38 ++++++++++++ 3 files changed, 165 insertions(+), 2 deletions(-) diff --git a/src/api2/tape/mod.rs b/src/api2/tape/mod.rs index d61b358d..a8725f3a 100644 --- a/src/api2/tape/mod.rs +++ b/src/api2/tape/mod.rs @@ -13,6 +13,7 @@ pub const SUBDIRS: SubdirMap = &[ ("changer", &changer::ROUTER), ("drive", &drive::ROUTER), ("media", &media::ROUTER), + ("restore", &restore::ROUTER), ]; pub const ROUTER: Router = Router::new() diff --git a/src/api2/tape/restore.rs b/src/api2/tape/restore.rs index 445c103d..843ac243 100644 --- a/src/api2/tape/restore.rs +++ b/src/api2/tape/restore.rs @@ -3,8 +3,16 @@ use std::ffi::OsStr; use std::convert::TryFrom; use anyhow::{bail, format_err, Error}; +use serde_json::Value; use proxmox::{ + api::{ + api, + RpcEnvironment, + RpcEnvironmentType, + Router, + section_config::SectionConfigData, + }, tools::{ Uuid, io::ReadExt, @@ -13,12 +21,20 @@ use proxmox::{ CreateOptions, }, }, - api::section_config::SectionConfigData, }; use crate::{ tools::compute_file_csum, - api2::types::Authid, + api2::types::{ + DATASTORE_SCHEMA, + UPID_SCHEMA, + Authid, + MediaPoolConfig, + }, + config::{ + self, + drive::check_drive_exists, + }, backup::{ archive_type, MANIFEST_BLOB_NAME, @@ -40,6 +56,8 @@ use crate::{ MediaCatalog, ChunkArchiveDecoder, TapeDriver, + MediaPool, + Inventory, request_and_load_media, file_formats::{ PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, @@ -52,6 +70,112 @@ use crate::{ }, }; +pub const ROUTER: Router = Router::new() + .post(&API_METHOD_RESTORE); + + +#[api( + input: { + properties: { + store: { + schema: DATASTORE_SCHEMA, + }, + "media-set": { + description: "Media set UUID.", + type: String, + }, + }, + }, + returns: { + schema: UPID_SCHEMA, + }, +)] +/// Restore data from media-set +pub fn restore( + store: String, + media_set: String, + rpcenv: &mut dyn RpcEnvironment, +) -> Result { + + let datastore = DataStore::lookup_datastore(&store)?; + + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; + + let status_path = Path::new(TAPE_STATUS_DIR); + let inventory = Inventory::load(status_path)?; + + let media_set_uuid = media_set.parse()?; + + let pool = inventory.lookup_media_set_pool(&media_set_uuid)?; + + let (config, _digest) = config::media_pool::config()?; + let pool_config: MediaPoolConfig = config.lookup("pool", &pool)?; + + let (drive_config, _digest) = config::drive::config()?; + // early check before starting worker + check_drive_exists(&drive_config, &pool_config.drive)?; + + let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; + + let upid_str = WorkerTask::new_thread( + "tape-restore", + Some(store.clone()), + auth_id.clone(), + to_stdout, + move |worker| { + + let _lock = MediaPool::lock(status_path, &pool)?; + + let members = inventory.compute_media_set_members(&media_set_uuid)?; + + let media_list = members.media_list().clone(); + + let mut media_id_list = Vec::new(); + + for (seq_nr, media_uuid) in media_list.iter().enumerate() { + match media_uuid { + None => { + bail!("media set {} is incomplete (missing member {}).", media_set_uuid, seq_nr); + } + Some(media_uuid) => { + media_id_list.push(inventory.lookup_media(media_uuid).unwrap()); + } + } + } + + let drive = &pool_config.drive; + + worker.log(format!("Restore mediaset '{}'", media_set)); + worker.log(format!("Pool: {}", pool)); + worker.log(format!("Datastore: {}", store)); + worker.log(format!("Drive: {}", drive)); + worker.log(format!( + "Required media list: {}", + media_id_list.iter() + .map(|media_id| media_id.label.changer_id.as_str()) + .collect::>() + .join(";") + )); + + for media_id in media_id_list.iter() { + request_and_restore_media( + &worker, + media_id, + &drive_config, + drive, + &datastore, + &auth_id, + )?; + } + + worker.log(format!("Restore mediaset '{}' done", media_set)); + Ok(()) + } + )?; + + Ok(upid_str.into()) +} + /// Request and restore complete media without using existing catalog (create catalog instead) pub fn request_and_restore_media( worker: &WorkerTask, diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index f488298c..8d762e2d 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -43,6 +43,7 @@ use proxmox_backup::{ tape::{ open_drive, complete_media_changer_id, + complete_media_set_uuid, file_formats::{ PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, PROXMOX_BACKUP_CONTENT_NAME, @@ -631,6 +632,36 @@ async fn backup( Ok(()) } +#[api( + input: { + properties: { + store: { + schema: DATASTORE_SCHEMA, + }, + "media-set": { + description: "Media set UUID.", + type: String, + }, + }, + }, +)] +/// Restore data from media-set +async fn restore( + param: Value, + rpcenv: &mut dyn RpcEnvironment, +) -> Result<(), Error> { + + let info = &api2::tape::restore::API_METHOD_RESTORE; + + let result = match info.handler { + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, + _ => unreachable!(), + }; + + wait_for_local_worker(result.as_str().unwrap()).await?; + + Ok(()) +} #[api( input: { @@ -688,6 +719,13 @@ fn main() { .completion_cb("store", complete_datastore_name) .completion_cb("pool", complete_pool_name) ) + .insert( + "restore", + CliCommand::new(&API_METHOD_RESTORE) + .arg_param(&["media-set", "store"]) + .completion_cb("store", complete_datastore_name) + .completion_cb("media-set", complete_media_set_uuid) + ) .insert( "barcode-label", CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)