proxmox-backup-proxy: schedule tape backup jobs
This commit is contained in:
parent
7ca0ba4515
commit
8513626b9f
|
@ -74,6 +74,8 @@ pub fn create_tape_backup_job(
|
||||||
|
|
||||||
config::tape_job::save_config(&config)?;
|
config::tape_job::save_config(&config)?;
|
||||||
|
|
||||||
|
crate::server::jobstate::create_state_file("tape-backup-job", &job.id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +114,10 @@ pub enum DeletableProperty {
|
||||||
comment,
|
comment,
|
||||||
/// Delete the job schedule.
|
/// Delete the job schedule.
|
||||||
schedule,
|
schedule,
|
||||||
|
/// Delete the eject-media property
|
||||||
|
eject_media,
|
||||||
|
/// Delete the export-media-set property
|
||||||
|
export_media_set,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
@ -205,6 +211,8 @@ pub fn delete_tape_backup_job(
|
||||||
|
|
||||||
config::tape_job::save_config(&config)?;
|
config::tape_job::save_config(&config)?;
|
||||||
|
|
||||||
|
crate::server::jobstate::remove_state_file("tape-backup-job", &id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,11 @@ use proxmox::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
task_log,
|
task_log,
|
||||||
config,
|
config::{
|
||||||
|
self,
|
||||||
|
tape_job::TapeBackupJobConfig,
|
||||||
|
},
|
||||||
|
server::jobstate::Job,
|
||||||
backup::{
|
backup::{
|
||||||
DataStore,
|
DataStore,
|
||||||
BackupDir,
|
BackupDir,
|
||||||
|
@ -45,6 +49,75 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn do_tape_backup_job(
|
||||||
|
mut job: Job,
|
||||||
|
tape_job: TapeBackupJobConfig,
|
||||||
|
auth_id: &Authid,
|
||||||
|
schedule: Option<String>,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
|
||||||
|
let job_id = format!("{}:{}:{}:{}",
|
||||||
|
tape_job.store,
|
||||||
|
tape_job.pool,
|
||||||
|
tape_job.drive,
|
||||||
|
job.jobname());
|
||||||
|
|
||||||
|
let worker_type = job.jobtype().to_string();
|
||||||
|
|
||||||
|
let datastore = DataStore::lookup_datastore(&tape_job.store)?;
|
||||||
|
|
||||||
|
let (config, _digest) = config::media_pool::config()?;
|
||||||
|
let pool_config: MediaPoolConfig = config.lookup("pool", &tape_job.pool)?;
|
||||||
|
|
||||||
|
let (drive_config, _digest) = config::drive::config()?;
|
||||||
|
|
||||||
|
// early check/lock before starting worker
|
||||||
|
let drive_lock = lock_tape_device(&drive_config, &tape_job.drive)?;
|
||||||
|
|
||||||
|
let upid_str = WorkerTask::new_thread(
|
||||||
|
&worker_type,
|
||||||
|
Some(job_id.clone()),
|
||||||
|
auth_id.clone(),
|
||||||
|
false,
|
||||||
|
move |worker| {
|
||||||
|
let _drive_lock = drive_lock; // keep lock guard
|
||||||
|
|
||||||
|
job.start(&worker.upid().to_string())?;
|
||||||
|
|
||||||
|
let eject_media = false;
|
||||||
|
let export_media_set = false;
|
||||||
|
|
||||||
|
task_log!(worker,"Starting tape backup job '{}'", job_id);
|
||||||
|
if let Some(event_str) = schedule {
|
||||||
|
task_log!(worker,"task triggered by schedule '{}'", event_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
let job_result = backup_worker(
|
||||||
|
&worker,
|
||||||
|
datastore,
|
||||||
|
&tape_job.drive,
|
||||||
|
&pool_config,
|
||||||
|
eject_media,
|
||||||
|
export_media_set,
|
||||||
|
);
|
||||||
|
|
||||||
|
let status = worker.create_state(&job_result);
|
||||||
|
|
||||||
|
if let Err(err) = job.finish(status) {
|
||||||
|
eprintln!(
|
||||||
|
"could not finish job state for {}: {}",
|
||||||
|
job.jobtype().to_string(),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
job_result
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(upid_str)
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -100,9 +173,11 @@ pub fn backup(
|
||||||
let eject_media = eject_media.unwrap_or(false);
|
let eject_media = eject_media.unwrap_or(false);
|
||||||
let export_media_set = export_media_set.unwrap_or(false);
|
let export_media_set = export_media_set.unwrap_or(false);
|
||||||
|
|
||||||
|
let job_id = format!("{}:{}:{}", store, pool, drive);
|
||||||
|
|
||||||
let upid_str = WorkerTask::new_thread(
|
let upid_str = WorkerTask::new_thread(
|
||||||
"tape-backup",
|
"tape-backup",
|
||||||
Some(store),
|
Some(job_id),
|
||||||
auth_id,
|
auth_id,
|
||||||
to_stdout,
|
to_stdout,
|
||||||
move |worker| {
|
move |worker| {
|
||||||
|
|
|
@ -49,6 +49,7 @@ use proxmox_backup::tools::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use proxmox_backup::api2::pull::do_sync_job;
|
use proxmox_backup::api2::pull::do_sync_job;
|
||||||
|
use proxmox_backup::api2::tape::backup::do_tape_backup_job;
|
||||||
use proxmox_backup::server::do_verification_job;
|
use proxmox_backup::server::do_verification_job;
|
||||||
use proxmox_backup::server::do_prune_job;
|
use proxmox_backup::server::do_prune_job;
|
||||||
|
|
||||||
|
@ -312,6 +313,7 @@ async fn schedule_tasks() -> Result<(), Error> {
|
||||||
schedule_datastore_prune().await;
|
schedule_datastore_prune().await;
|
||||||
schedule_datastore_sync_jobs().await;
|
schedule_datastore_sync_jobs().await;
|
||||||
schedule_datastore_verify_jobs().await;
|
schedule_datastore_verify_jobs().await;
|
||||||
|
schedule_tape_backup_jobs().await;
|
||||||
schedule_task_log_rotate().await;
|
schedule_task_log_rotate().await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -550,6 +552,48 @@ async fn schedule_datastore_verify_jobs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn schedule_tape_backup_jobs() {
|
||||||
|
|
||||||
|
use proxmox_backup::config::tape_job::{
|
||||||
|
self,
|
||||||
|
TapeBackupJobConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = match tape_job::config() {
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("unable to read tape job config - {}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok((config, _digest)) => config,
|
||||||
|
};
|
||||||
|
for (job_id, (_, job_config)) in config.sections {
|
||||||
|
let job_config: TapeBackupJobConfig = match serde_json::from_value(job_config) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("tape backup job config from_value failed - {}", err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let event_str = match job_config.schedule {
|
||||||
|
Some(ref event_str) => event_str.clone(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let worker_type = "tape-backup-job";
|
||||||
|
let auth_id = Authid::root_auth_id().clone();
|
||||||
|
if check_schedule(worker_type, &event_str, &job_id) {
|
||||||
|
let job = match Job::new(&worker_type, &job_id) {
|
||||||
|
Ok(job) => job,
|
||||||
|
Err(_) => continue, // could not get lock
|
||||||
|
};
|
||||||
|
if let Err(err) = do_tape_backup_job(job, job_config, &auth_id, Some(event_str)) {
|
||||||
|
eprintln!("unable to start tape bvackup job {} - {}", &job_id, err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn schedule_task_log_rotate() {
|
async fn schedule_task_log_rotate() {
|
||||||
|
|
||||||
let worker_type = "logrotate";
|
let worker_type = "logrotate";
|
||||||
|
|
|
@ -42,6 +42,16 @@ lazy_static! {
|
||||||
drive: {
|
drive: {
|
||||||
schema: DRIVE_NAME_SCHEMA,
|
schema: DRIVE_NAME_SCHEMA,
|
||||||
},
|
},
|
||||||
|
"eject-media": {
|
||||||
|
description: "Eject media upon job completion.",
|
||||||
|
type: bool,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
"export-media-set": {
|
||||||
|
description: "Export media set upon job completion.",
|
||||||
|
type: bool,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
comment: {
|
comment: {
|
||||||
optional: true,
|
optional: true,
|
||||||
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||||
|
@ -62,6 +72,10 @@ pub struct TapeBackupJobConfig {
|
||||||
pub pool: String,
|
pub pool: String,
|
||||||
pub drive: String,
|
pub drive: String,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
eject_media: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
export_media_set: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub comment: Option<String>,
|
pub comment: Option<String>,
|
||||||
#[serde(skip_serializing_if="Option::is_none")]
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
pub schedule: Option<String>,
|
pub schedule: Option<String>,
|
||||||
|
|
14
www/Utils.js
14
www/Utils.js
|
@ -161,6 +161,17 @@ Ext.define('PBS.Utils', {
|
||||||
return `Datastore ${what} ${id}`;
|
return `Datastore ${what} ${id}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
render_tape_backup_id: function(id, what) {
|
||||||
|
const res = id.match(/^(\S+?):(\S+?):(\S+?)(:(.+))?$/);
|
||||||
|
if (res) {
|
||||||
|
let datastore = res[1];
|
||||||
|
let pool = res[2];
|
||||||
|
let drive = res[3];
|
||||||
|
return `${what} ${datastore} (pool ${pool}, drive ${drive})`;
|
||||||
|
}
|
||||||
|
return `${what} ${id}`;
|
||||||
|
},
|
||||||
|
|
||||||
// mimics Display trait in backend
|
// mimics Display trait in backend
|
||||||
renderKeyID: function(fingerprint) {
|
renderKeyID: function(fingerprint) {
|
||||||
return fingerprint.substring(0, 23);
|
return fingerprint.substring(0, 23);
|
||||||
|
@ -295,7 +306,8 @@ Ext.define('PBS.Utils', {
|
||||||
// do whatever you want here
|
// do whatever you want here
|
||||||
Proxmox.Utils.override_task_descriptions({
|
Proxmox.Utils.override_task_descriptions({
|
||||||
backup: (type, id) => PBS.Utils.render_datastore_worker_id(id, gettext('Backup')),
|
backup: (type, id) => PBS.Utils.render_datastore_worker_id(id, gettext('Backup')),
|
||||||
"tape-backup": ['Datastore', gettext('Tape Backup')],
|
"tape-backup": (type, id) => PBS.Utils.render_tape_backup_id(id, gettext('Tape Backup')),
|
||||||
|
"tape-backup-job": (type, id) => PBS.Utils.render_tape_backup_id(id, gettext('Tape Backup Job')),
|
||||||
"tape-restore": ['Datastore', gettext('Tape Restore')],
|
"tape-restore": ['Datastore', gettext('Tape Restore')],
|
||||||
"barcode-label-media": [gettext('Drive'), gettext('Barcode label media')],
|
"barcode-label-media": [gettext('Drive'), gettext('Barcode label media')],
|
||||||
dircreate: [gettext('Directory Storage'), gettext('Create')],
|
dircreate: [gettext('Directory Storage'), gettext('Create')],
|
||||||
|
|
Loading…
Reference in New Issue