all lists on 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal