tape: add basic restore api/command
This commit is contained in:
parent
b4772d1c43
commit
b9b4b31284
@ -13,6 +13,7 @@ pub const SUBDIRS: SubdirMap = &[
|
|||||||
("changer", &changer::ROUTER),
|
("changer", &changer::ROUTER),
|
||||||
("drive", &drive::ROUTER),
|
("drive", &drive::ROUTER),
|
||||||
("media", &media::ROUTER),
|
("media", &media::ROUTER),
|
||||||
|
("restore", &restore::ROUTER),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const ROUTER: Router = Router::new()
|
pub const ROUTER: Router = Router::new()
|
||||||
|
@ -3,8 +3,16 @@ use std::ffi::OsStr;
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use proxmox::{
|
use proxmox::{
|
||||||
|
api::{
|
||||||
|
api,
|
||||||
|
RpcEnvironment,
|
||||||
|
RpcEnvironmentType,
|
||||||
|
Router,
|
||||||
|
section_config::SectionConfigData,
|
||||||
|
},
|
||||||
tools::{
|
tools::{
|
||||||
Uuid,
|
Uuid,
|
||||||
io::ReadExt,
|
io::ReadExt,
|
||||||
@ -13,12 +21,20 @@ use proxmox::{
|
|||||||
CreateOptions,
|
CreateOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
api::section_config::SectionConfigData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
tools::compute_file_csum,
|
tools::compute_file_csum,
|
||||||
api2::types::Authid,
|
api2::types::{
|
||||||
|
DATASTORE_SCHEMA,
|
||||||
|
UPID_SCHEMA,
|
||||||
|
Authid,
|
||||||
|
MediaPoolConfig,
|
||||||
|
},
|
||||||
|
config::{
|
||||||
|
self,
|
||||||
|
drive::check_drive_exists,
|
||||||
|
},
|
||||||
backup::{
|
backup::{
|
||||||
archive_type,
|
archive_type,
|
||||||
MANIFEST_BLOB_NAME,
|
MANIFEST_BLOB_NAME,
|
||||||
@ -40,6 +56,8 @@ use crate::{
|
|||||||
MediaCatalog,
|
MediaCatalog,
|
||||||
ChunkArchiveDecoder,
|
ChunkArchiveDecoder,
|
||||||
TapeDriver,
|
TapeDriver,
|
||||||
|
MediaPool,
|
||||||
|
Inventory,
|
||||||
request_and_load_media,
|
request_and_load_media,
|
||||||
file_formats::{
|
file_formats::{
|
||||||
PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
|
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<Value, Error> {
|
||||||
|
|
||||||
|
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::<Vec<&str>>()
|
||||||
|
.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)
|
/// Request and restore complete media without using existing catalog (create catalog instead)
|
||||||
pub fn request_and_restore_media(
|
pub fn request_and_restore_media(
|
||||||
worker: &WorkerTask,
|
worker: &WorkerTask,
|
||||||
|
@ -43,6 +43,7 @@ use proxmox_backup::{
|
|||||||
tape::{
|
tape::{
|
||||||
open_drive,
|
open_drive,
|
||||||
complete_media_changer_id,
|
complete_media_changer_id,
|
||||||
|
complete_media_set_uuid,
|
||||||
file_formats::{
|
file_formats::{
|
||||||
PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
|
PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
|
||||||
PROXMOX_BACKUP_CONTENT_NAME,
|
PROXMOX_BACKUP_CONTENT_NAME,
|
||||||
@ -631,6 +632,36 @@ async fn backup(
|
|||||||
|
|
||||||
Ok(())
|
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(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
@ -688,6 +719,13 @@ fn main() {
|
|||||||
.completion_cb("store", complete_datastore_name)
|
.completion_cb("store", complete_datastore_name)
|
||||||
.completion_cb("pool", complete_pool_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(
|
.insert(
|
||||||
"barcode-label",
|
"barcode-label",
|
||||||
CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
|
CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
|
||||||
|
Loading…
Reference in New Issue
Block a user