public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [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(&param);
 
-    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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal