use anyhow::{bail, format_err, Error};
use serde_json::Value;
-use proxmox::{
- try_block,
- api::{
- api,
- RpcEnvironment,
- RpcEnvironmentType,
- Router,
- Permission,
- },
+use proxmox_lang::try_block;
+use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
+use proxmox_schema::api;
+use proxmox_sys::{task_log, task_warn, WorkerTaskContext};
+
+use pbs_api_types::{
+ Authid, Userid, TapeBackupJobConfig, TapeBackupJobSetup, TapeBackupJobStatus, MediaPoolConfig,
+ UPID_SCHEMA, JOB_ID_SCHEMA, PRIV_DATASTORE_READ, PRIV_TAPE_AUDIT, PRIV_TAPE_WRITE,
+ GroupFilter,
};
+use pbs_datastore::{DataStore, StoreProgress, SnapshotReader};
+use pbs_datastore::backup_info::{BackupDir, BackupInfo, BackupGroup};
+use pbs_config::CachedUserInfo;
+use proxmox_rest_server::WorkerTask;
+
use crate::{
- task_log,
- task_warn,
- config::{
- self,
- cached_user_info::CachedUserInfo,
- acl::{
- PRIV_DATASTORE_READ,
- PRIV_TAPE_AUDIT,
- PRIV_TAPE_WRITE,
- },
- tape_job::{
- TapeBackupJobConfig,
- TapeBackupJobSetup,
- TapeBackupJobStatus,
- },
- },
server::{
lookup_user_email,
TapeBackupJobSummary,
compute_schedule_status,
},
},
- backup::{
- DataStore,
- BackupDir,
- BackupInfo,
- StoreProgress,
- },
- api2::types::{
- Authid,
- UPID_SCHEMA,
- JOB_ID_SCHEMA,
- MediaPoolConfig,
- Userid,
- },
- server::WorkerTask,
- task::TaskState,
tape::{
TAPE_STATUS_DIR,
Inventory,
PoolWriter,
MediaPool,
- SnapshotReader,
drive::{
media_changer,
lock_tape_device,
+ TapeLockError,
set_tape_device_state,
},
changer::update_changer_online_status,
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
let user_info = CachedUserInfo::new()?;
- let (config, digest) = config::tape_job::config()?;
+ let (job_config, digest) = pbs_config::tape_job::config()?;
+ let (pool_config, _pool_digest) = pbs_config::media_pool::config()?;
+ let (drive_config, _digest) = pbs_config::drive::config()?;
- let job_list_iter = config
+ let job_list_iter = job_config
.convert_to_typed_array("backup")?
.into_iter()
.filter(|_job: &TapeBackupJobConfig| {
});
let mut list = Vec::new();
+ let status_path = Path::new(TAPE_STATUS_DIR);
+ let current_time = proxmox_time::epoch_i64();
for job in job_list_iter {
let privs = user_info.lookup_privs(&auth_id, &["tape", "job", &job.id]);
let status = compute_schedule_status(&last_state, job.schedule.as_deref())?;
- list.push(TapeBackupJobStatus { config: job, status });
+ let next_run = status.next_run.unwrap_or(current_time);
+
+ let mut next_media_label = None;
+
+ if let Ok(pool) = pool_config.lookup::<MediaPoolConfig>("pool", &job.setup.pool) {
+ let mut changer_name = None;
+ if let Ok(Some((_, name))) = media_changer(&drive_config, &job.setup.drive) {
+ changer_name = Some(name);
+ }
+ if let Ok(mut pool) = MediaPool::with_config(status_path, &pool, changer_name, true) {
+ if pool.start_write_session(next_run, false).is_ok() {
+ if let Ok(media_id) = pool.guess_next_writable_media(next_run) {
+ next_media_label = Some(media_id.label.label_text);
+ }
+ }
+ }
+ }
+
+ list.push(TapeBackupJobStatus { config: job, status, next_media_label });
}
- rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+ rpcenv["digest"] = hex::encode(&digest).into();
Ok(list)
}
setup: TapeBackupJobSetup,
auth_id: &Authid,
schedule: Option<String>,
+ to_stdout: bool,
) -> Result<String, Error> {
let job_id = format!("{}:{}:{}:{}",
let datastore = DataStore::lookup_datastore(&setup.store)?;
- let (config, _digest) = config::media_pool::config()?;
+ let (config, _digest) = pbs_config::media_pool::config()?;
let pool_config: MediaPoolConfig = config.lookup("pool", &setup.pool)?;
- let (drive_config, _digest) = config::drive::config()?;
+ let (drive_config, _digest) = pbs_config::drive::config()?;
// for scheduled jobs we acquire the lock later in the worker
let drive_lock = if schedule.is_some() {
let upid_str = WorkerTask::new_thread(
&worker_type,
Some(job_id.clone()),
- auth_id.clone(),
- false,
+ auth_id.to_string(),
+ to_stdout,
move |worker| {
job.start(&worker.upid().to_string())?;
let mut drive_lock = drive_lock;
// for scheduled tape backup jobs, we wait indefinitely for the lock
task_log!(worker, "waiting for drive lock...");
loop {
- if let Ok(lock) = lock_tape_device(&drive_config, &setup.drive) {
- drive_lock = Some(lock);
- break;
- } // ignore errors
-
worker.check_abort()?;
+ match lock_tape_device(&drive_config, &setup.drive) {
+ Ok(lock) => {
+ drive_lock = Some(lock);
+ break;
+ }
+ Err(TapeLockError::TimeOut) => continue,
+ Err(TapeLockError::Other(err)) => return Err(err),
+ }
}
}
set_tape_device_state(&setup.drive, &worker.upid().to_string())?;
) -> Result<String, Error> {
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
- let (config, _digest) = config::tape_job::config()?;
+ let (config, _digest) = pbs_config::tape_job::config()?;
let backup_job: TapeBackupJobConfig = config.lookup("backup", &id)?;
check_backup_permission(
let job = Job::new("tape-backup-job", &id)?;
- let upid_str = do_tape_backup_job(job, backup_job.setup, &auth_id, None)?;
+ let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
+
+ let upid_str = do_tape_backup_job(job, backup_job.setup, &auth_id, None, to_stdout)?;
Ok(upid_str)
}
let datastore = DataStore::lookup_datastore(&setup.store)?;
- let (config, _digest) = config::media_pool::config()?;
+ let (config, _digest) = pbs_config::media_pool::config()?;
let pool_config: MediaPoolConfig = config.lookup("pool", &setup.pool)?;
- let (drive_config, _digest) = config::drive::config()?;
+ let (drive_config, _digest) = pbs_config::drive::config()?;
// early check/lock before starting worker
let drive_lock = lock_tape_device(&drive_config, &setup.drive)?;
let upid_str = WorkerTask::new_thread(
"tape-backup",
Some(job_id),
- auth_id,
+ auth_id.to_string(),
to_stdout,
move |worker| {
let _drive_lock = drive_lock; // keep lock guard
group_list.sort_unstable();
- let group_count = group_list.len();
- task_log!(worker, "found {} groups", group_count);
+ let (group_list, group_count) = if let Some(group_filters) = &setup.group_filter {
+ let filter_fn = |group: &BackupGroup, group_filters: &[GroupFilter]| {
+ group_filters.iter().any(|filter| group.matches(filter))
+ };
+
+ let group_count_full = group_list.len();
+ let list: Vec<BackupGroup> = group_list.into_iter().filter(|group| filter_fn(group, &group_filters)).collect();
+ let group_count = list.len();
+ task_log!(worker, "found {} groups (out of {} total)", group_count, group_count_full);
+ (list, group_count)
+ } else {
+ let group_count = group_list.len();
+ task_log!(worker, "found {} groups", group_count);
+ (group_list, group_count)
+ };
let mut progress = StoreProgress::new(group_count as u64);
let snapshot_list = group.list_backups(&datastore.base_path())?;
// filter out unfinished backups
- let mut snapshot_list = snapshot_list
+ let mut snapshot_list: Vec<_> = snapshot_list
.into_iter()
.filter(|item| item.is_finished())
.collect();
+ if snapshot_list.is_empty() {
+ task_log!(worker, "group {} was empty", group);
+ continue;
+ }
+
BackupInfo::sort_list(&mut snapshot_list, true); // oldest first
if latest_only {
// Try to update the the media online status
fn update_media_online_status(drive: &str) -> Result<Option<String>, Error> {
- let (config, _digest) = config::drive::config()?;
+ let (config, _digest) = pbs_config::drive::config()?;
if let Ok(Some((mut changer, changer_name))) = media_changer(&config, drive) {