* [pbs-devel] [PATCH proxmox-backup 1/4] tape: media_pool: implement guess_next_writable_media()
@ 2021-08-04 8:10 Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 2/4] tape: compute next-media-label for each tape backup job Dietmar Maurer
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Dietmar Maurer @ 2021-08-04 8:10 UTC (permalink / raw)
To: pbs-devel
---
src/tape/media_pool.rs | 252 ++++++++++++++++++++++++++---------------
1 file changed, 160 insertions(+), 92 deletions(-)
diff --git a/src/tape/media_pool.rs b/src/tape/media_pool.rs
index 7bb7fbdb..64a61b3a 100644
--- a/src/tape/media_pool.rs
+++ b/src/tape/media_pool.rs
@@ -406,93 +406,110 @@ impl MediaPool {
Ok(())
}
+ // Get next unassigned media (media not assigned to any pool)
+ pub fn next_unassigned_media(&self, media_list: &[MediaId]) -> Option<MediaId> {
+ let mut free_media = Vec::new();
- /// Allocates a writable media to the current media set
- pub fn alloc_writable_media(&mut self, current_time: i64) -> Result<Uuid, Error> {
+ for media_id in media_list {
- if self.current_media_set_lock.is_none() {
- bail!("alloc_writable_media: media set is not locked - internal error");
- }
-
- let last_is_writable = self.current_set_usable()?;
+ let (status, location) = self.compute_media_state(&media_id);
+ if media_id.media_set_label.is_some() { continue; } // should not happen
- if last_is_writable {
- let last_uuid = self.current_media_set.last_media_uuid().unwrap();
- let media = self.lookup_media(last_uuid)?;
- return Ok(media.uuid().clone());
- }
+ if !self.location_is_available(&location) {
+ continue;
+ }
- // try to find empty media in pool, add to media set
+ // only consider writable media
+ if status != MediaStatus::Writable { continue; }
- { // limit pool lock scope
- let _pool_lock = lock_media_pool(&self.state_path, &self.name)?;
+ free_media.push(media_id);
+ }
- self.inventory.reload()?;
+ // sort free_media, newest first -> oldest last
+ free_media.sort_unstable_by(|a, b| {
+ let mut res = b.label.ctime.cmp(&a.label.ctime);
+ if res == std::cmp::Ordering::Equal {
+ res = b.label.label_text.cmp(&a.label.label_text);
+ }
+ res
+ });
- let media_list = self.list_media();
+ free_media.pop().map(|e| e.clone())
+ }
- let mut empty_media = Vec::new();
- let mut used_media = Vec::new();
+ // Get next empty media
+ pub fn next_empty_media(&self, media_list: &[BackupMedia]) -> Option<MediaId> {
+ let mut empty_media = Vec::new();
- for media in media_list.into_iter() {
- if !self.location_is_available(media.location()) {
- continue;
- }
- // already part of a media set?
- if media.media_set_label().is_some() {
- used_media.push(media);
- } else {
- // only consider writable empty media
- if media.status() == &MediaStatus::Writable {
- empty_media.push(media);
- }
- }
+ for media in media_list.into_iter() {
+ if !self.location_is_available(media.location()) {
+ continue;
}
-
- // sort empty_media, newest first -> oldest last
- empty_media.sort_unstable_by(|a, b| {
- let mut res = b.label().ctime.cmp(&a.label().ctime);
- if res == std::cmp::Ordering::Equal {
- res = b.label().label_text.cmp(&a.label().label_text);
+ // already part of a media set?
+ if media.media_set_label().is_none() {
+ // only consider writable empty media
+ if media.status() == &MediaStatus::Writable {
+ empty_media.push(media);
}
- res
- });
+ }
+ }
- if let Some(media) = empty_media.pop() {
- // found empty media, add to media set an use it
- let uuid = media.uuid().clone();
- self.add_media_to_current_set(media.into_id(), current_time)?;
- return Ok(uuid);
+ // sort empty_media, newest first -> oldest last
+ empty_media.sort_unstable_by(|a, b| {
+ let mut res = b.label().ctime.cmp(&a.label().ctime);
+ if res == std::cmp::Ordering::Equal {
+ res = b.label().label_text.cmp(&a.label().label_text);
}
+ res
+ });
- println!("no empty media in pool, try to reuse expired media");
+ empty_media.pop().map(|e| e.clone().into_id())
+ }
- let mut expired_media = Vec::new();
+ // Get next expired media
+ pub fn next_expired_media(&self, current_time: i64, media_list: &[BackupMedia]) -> Option<MediaId> {
+ let mut used_media = Vec::new();
- for media in used_media.into_iter() {
- if let Some(set) = media.media_set_label() {
- if &set.uuid == self.current_media_set.uuid() {
- continue;
- }
- } else {
+ for media in media_list.into_iter() {
+ if !self.location_is_available(media.location()) {
+ continue;
+ }
+ // already part of a media set?
+ if media.media_set_label().is_some() {
+ used_media.push(media);
+ }
+ }
+
+ let mut expired_media = Vec::new();
+
+ for media in used_media.into_iter() {
+ if let Some(set) = media.media_set_label() {
+ if &set.uuid == self.current_media_set.uuid() {
continue;
}
+ } else {
+ continue;
+ }
- if self.media_is_expired(&media, current_time) {
- println!("found expired media on media '{}'", media.label_text());
- expired_media.push(media);
- }
+ if !self.media_is_expired(&media, current_time) {
+ continue;
}
- // sort expired_media, newest first -> oldest last
- expired_media.sort_unstable_by(|a, b| {
- let mut res = b.media_set_label().unwrap().ctime.cmp(&a.media_set_label().unwrap().ctime);
- if res == std::cmp::Ordering::Equal {
- res = b.label().label_text.cmp(&a.label().label_text);
- }
- res
- });
+ expired_media.push(media);
+ }
+
+ // sort expired_media, newest first -> oldest last
+ expired_media.sort_unstable_by(|a, b| {
+ let mut res = b.media_set_label().unwrap().ctime.cmp(&a.media_set_label().unwrap().ctime);
+ if res == std::cmp::Ordering::Equal {
+ res = b.label().label_text.cmp(&a.label().label_text);
+ }
+ res
+ });
+ if self.no_media_set_locking {
+ expired_media.pop().map(|e| e.clone().into_id())
+ } else {
while let Some(media) = expired_media.pop() {
// check if we can modify the media-set (i.e. skip
// media used by a restore job)
@@ -501,49 +518,100 @@ impl MediaPool {
&media.media_set_label().unwrap().uuid,
Some(std::time::Duration::new(0, 0)), // do not wait
) {
- println!("reuse expired media '{}'", media.label_text());
- let uuid = media.uuid().clone();
- self.add_media_to_current_set(media.into_id(), current_time)?;
- return Ok(uuid);
+ return Some(media.clone().into_id());
}
}
+ None
}
+ }
- println!("no expired media in pool, try to find unassigned/free media");
+ /// Guess next writable media
+ ///
+ /// Like alloc_writable_media(), but does not really allocate
+ /// anything (thus it does not need any locks)
+ // Note: Please keep in sync with alloc_writable_media()
+ pub fn guess_next_writable_media(&self, current_time: i64) -> Result<MediaId, Error> {
+ let last_is_writable = self.current_set_usable()?;
- // try unassigned media
- let _lock = lock_unassigned_media_pool(&self.state_path)?;
+ if last_is_writable {
+ let last_uuid = self.current_media_set.last_media_uuid().unwrap();
+ let media = self.lookup_media(last_uuid)?;
+ return Ok(media.into_id());
+ }
- self.inventory.reload()?;
+ let media_list = self.list_media();
+ if let Some(media_id) = self.next_empty_media(&media_list) {
+ return Ok(media_id);
+ }
- let mut free_media = Vec::new();
+ if let Some(media_id) = self.next_expired_media(current_time, &media_list) {
+ return Ok(media_id);
+ }
- for media_id in self.inventory.list_unassigned_media() {
+ let unassigned_list = self.inventory.list_unassigned_media();
- let (status, location) = self.compute_media_state(&media_id);
- if media_id.media_set_label.is_some() { continue; } // should not happen
+ if let Some(media_id) = self.next_unassigned_media(&unassigned_list) {
+ return Ok(media_id);
+ }
- if !self.location_is_available(&location) {
- continue;
- }
+ bail!("guess_next_writable_media in pool '{}' failed: no usable media found", self.name());
+ }
- // only consider writable media
- if status != MediaStatus::Writable { continue; }
+ /// Allocates a writable media to the current media set
+ // Note: Please keep in sync with guess_next_writable_media()
+ pub fn alloc_writable_media(&mut self, current_time: i64) -> Result<Uuid, Error> {
- free_media.push(media_id);
+ if self.current_media_set_lock.is_none() {
+ bail!("alloc_writable_media: media set is not locked - internal error");
}
- // sort free_media, newest first -> oldest last
- free_media.sort_unstable_by(|a, b| {
- let mut res = b.label.ctime.cmp(&a.label.ctime);
- if res == std::cmp::Ordering::Equal {
- res = b.label.label_text.cmp(&a.label.label_text);
+ let last_is_writable = self.current_set_usable()?;
+
+ if last_is_writable {
+ let last_uuid = self.current_media_set.last_media_uuid().unwrap();
+ let media = self.lookup_media(last_uuid)?;
+ return Ok(media.uuid().clone());
+ }
+
+ { // limit pool lock scope
+ let _pool_lock = lock_media_pool(&self.state_path, &self.name)?;
+
+ self.inventory.reload()?;
+
+ let media_list = self.list_media();
+
+ // try to find empty media in pool, add to media set
+
+ if let Some(media_id) = self.next_empty_media(&media_list) {
+ // found empty media, add to media set an use it
+ println!("found empty media '{}'", media_id.label.label_text);
+ let uuid = media_id.label.uuid.clone();
+ self.add_media_to_current_set(media_id, current_time)?;
+ return Ok(uuid);
}
- res
- });
- if let Some(media_id) = free_media.pop() {
- println!("use free media '{}'", media_id.label.label_text);
+ println!("no empty media in pool, try to reuse expired media");
+
+ if let Some(media_id) = self.next_expired_media(current_time, &media_list) {
+ // found expired media, add to media set an use it
+ println!("reuse expired media '{}'", media_id.label.label_text);
+ let uuid = media_id.label.uuid.clone();
+ self.add_media_to_current_set(media_id, current_time)?;
+ return Ok(uuid);
+ }
+ }
+
+ println!("no empty or expired media in pool, try to find unassigned/free media");
+
+ // try unassigned media
+ let _lock = lock_unassigned_media_pool(&self.state_path)?;
+
+ self.inventory.reload()?;
+
+ let unassigned_list = self.inventory.list_unassigned_media();
+
+ if let Some(media_id) = self.next_unassigned_media(&unassigned_list) {
+ println!("use free/unassigned media '{}'", media_id.label.label_text);
let uuid = media_id.label.uuid.clone();
self.add_media_to_current_set(media_id, current_time)?;
return Ok(uuid);
--
2.30.2
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 2/4] tape: compute next-media-label for each tape backup job
2021-08-04 8:10 [pbs-devel] [PATCH proxmox-backup 1/4] tape: media_pool: implement guess_next_writable_media() Dietmar Maurer
@ 2021-08-04 8:10 ` Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 3/4] cli: proxmox-tape backup-job list: use status api and display next-run an d next-media-label Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: display next-media-label for tape backup jobs Dietmar Maurer
2 siblings, 0 replies; 4+ messages in thread
From: Dietmar Maurer @ 2021-08-04 8:10 UTC (permalink / raw)
To: pbs-devel
---
src/api2/tape/backup.rs | 28 +++++++++++++++++++++++++---
src/config/tape_job.rs | 3 +++
2 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/src/api2/tape/backup.rs b/src/api2/tape/backup.rs
index 8119482f..9f815cad 100644
--- a/src/api2/tape/backup.rs
+++ b/src/api2/tape/backup.rs
@@ -126,9 +126,11 @@ pub fn list_tape_backup_jobs(
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) = config::tape_job::config()?;
+ let (pool_config, _pool_digest) = config::media_pool::config()?;
+ let (drive_config, _digest) = config::drive::config()?;
- let job_list_iter = config
+ let job_list_iter = job_config
.convert_to_typed_array("backup")?
.into_iter()
.filter(|_job: &TapeBackupJobConfig| {
@@ -137,6 +139,8 @@ pub fn list_tape_backup_jobs(
});
let mut list = Vec::new();
+ let status_path = Path::new(TAPE_STATUS_DIR);
+ let current_time = proxmox::tools::time::epoch_i64();
for job in job_list_iter {
let privs = user_info.lookup_privs(&auth_id, &["tape", "job", &job.id]);
@@ -149,7 +153,25 @@ pub fn list_tape_backup_jobs(
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();
diff --git a/src/config/tape_job.rs b/src/config/tape_job.rs
index f09200fc..3c265b93 100644
--- a/src/config/tape_job.rs
+++ b/src/config/tape_job.rs
@@ -127,6 +127,9 @@ pub struct TapeBackupJobStatus {
pub config: TapeBackupJobConfig,
#[serde(flatten)]
pub status: JobScheduleStatus,
+ /// Next tape used (best guess)
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub next_media_label: Option<String>,
}
fn init() -> SectionConfig {
--
2.30.2
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 3/4] cli: proxmox-tape backup-job list: use status api and display next-run an d next-media-label
2021-08-04 8:10 [pbs-devel] [PATCH proxmox-backup 1/4] tape: media_pool: implement guess_next_writable_media() Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 2/4] tape: compute next-media-label for each tape backup job Dietmar Maurer
@ 2021-08-04 8:10 ` Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: display next-media-label for tape backup jobs Dietmar Maurer
2 siblings, 0 replies; 4+ messages in thread
From: Dietmar Maurer @ 2021-08-04 8:10 UTC (permalink / raw)
To: pbs-devel
---
src/bin/proxmox_tape/backup_job.rs | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/bin/proxmox_tape/backup_job.rs b/src/bin/proxmox_tape/backup_job.rs
index d9b25f20..f181868c 100644
--- a/src/bin/proxmox_tape/backup_job.rs
+++ b/src/bin/proxmox_tape/backup_job.rs
@@ -28,7 +28,8 @@ fn list_tape_backup_jobs(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Resul
let output_format = get_output_format(¶m);
- let info = &api2::config::tape_backup_job::API_METHOD_LIST_TAPE_BACKUP_JOBS;
+ //let info = &api2::config::tape_backup_job::API_METHOD_LIST_TAPE_BACKUP_JOBS;
+ let info = &api2::tape::backup::API_METHOD_LIST_TAPE_BACKUP_JOBS;
let mut data = match info.handler {
ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
_ => unreachable!(),
@@ -40,6 +41,8 @@ fn list_tape_backup_jobs(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Resul
.column(ColumnConfig::new("pool"))
.column(ColumnConfig::new("drive"))
.column(ColumnConfig::new("schedule"))
+ .column(ColumnConfig::new("next-run").renderer(pbs_tools::format::render_epoch))
+ .column(ColumnConfig::new("next-media-label"))
.column(ColumnConfig::new("comment"));
format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
--
2.30.2
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pbs-devel] [PATCH proxmox-backup 4/4] ui: display next-media-label for tape backup jobs
2021-08-04 8:10 [pbs-devel] [PATCH proxmox-backup 1/4] tape: media_pool: implement guess_next_writable_media() Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 2/4] tape: compute next-media-label for each tape backup job Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 3/4] cli: proxmox-tape backup-job list: use status api and display next-run an d next-media-label Dietmar Maurer
@ 2021-08-04 8:10 ` Dietmar Maurer
2 siblings, 0 replies; 4+ messages in thread
From: Dietmar Maurer @ 2021-08-04 8:10 UTC (permalink / raw)
To: pbs-devel
---
www/tape/BackupJobs.js | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/www/tape/BackupJobs.js b/www/tape/BackupJobs.js
index 84b260b9..6a80c97e 100644
--- a/www/tape/BackupJobs.js
+++ b/www/tape/BackupJobs.js
@@ -5,7 +5,7 @@ Ext.define('pbs-tape-backup-job-status', {
{ name: 'eject-media', type: 'boolean' },
{ name: 'export-media-set', type: 'boolean' },
{ name: 'latest-only', type: 'boolean' },
- 'next-run', 'last-run-upid', 'last-run-state', 'last-run-endtime',
+ 'next-run', 'next-media-label', 'last-run-upid', 'last-run-state', 'last-run-endtime',
{
name: 'duration',
calculate: function(data) {
@@ -255,6 +255,12 @@ Ext.define('PBS.config.TapeBackupJobView', {
width: 150,
sortable: true,
},
+ {
+ header: gettext('Next Media'),
+ dataIndex: 'next-media-label',
+ width: 100,
+ sortable: true,
+ },
{
header: gettext('Comment'),
dataIndex: 'comment',
--
2.30.2
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2021-08-04 8:11 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-04 8:10 [pbs-devel] [PATCH proxmox-backup 1/4] tape: media_pool: implement guess_next_writable_media() Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 2/4] tape: compute next-media-label for each tape backup job Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 3/4] cli: proxmox-tape backup-job list: use status api and display next-run an d next-media-label Dietmar Maurer
2021-08-04 8:10 ` [pbs-devel] [PATCH proxmox-backup 4/4] ui: display next-media-label for tape backup jobs Dietmar Maurer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox