]> git.proxmox.com Git - proxmox-backup.git/commitdiff
tape: save 'bytes used' in tape inventory
authorDominik Csapak <d.csapak@proxmox.com>
Mon, 13 May 2024 10:46:09 +0000 (12:46 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Tue, 14 May 2024 08:07:57 +0000 (10:07 +0200)
and show them on the ui. This can help uses with seeing how much a tape
is used.

The value is updated on 'commit' and when the tape is changed during a
backup.

For drives not supporting the volume statistics, this is simply skipped.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
pbs-api-types/src/tape/media.rs
src/api2/tape/media.rs
src/tape/drive/lto/mod.rs
src/tape/drive/mod.rs
src/tape/drive/virtual_tape.rs
src/tape/inventory.rs
src/tape/media_pool.rs
src/tape/pool_writer/mod.rs
www/tape/TapeInventory.js

index 6792cd3c9af037a24c674a091be24026f7a6f252..6227f4634d8959728ae09d9747bf2e71e03f31f7 100644 (file)
@@ -81,6 +81,9 @@ pub struct MediaListEntry {
     /// Media Pool
     #[serde(skip_serializing_if = "Option::is_none")]
     pub pool: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    /// Bytes currently used
+    pub bytes_used: Option<u64>,
 }
 
 #[api(
index 07bed86a2726adf7f6f43de1b762244849149129..159906ba7903a011a46412c34d7719fcf0dca329 100644 (file)
@@ -204,6 +204,7 @@ pub async fn list_media(
                 media_set_uuid,
                 media_set_name,
                 seq_nr,
+                bytes_used: media.bytes_used(),
             });
         }
     }
@@ -232,6 +233,7 @@ pub async fn list_media(
                 media_set_ctime: None,
                 seq_nr: None,
                 pool: None,
+                bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
             });
         }
     }
@@ -279,6 +281,7 @@ pub async fn list_media(
             media_set_uuid,
             media_set_name,
             seq_nr,
+            bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
         });
     }
 
index 7de07cf956b628128da415d74e28586cfdd8b910..f3143c9074d8f73d7bab3e873514b882ca51a613 100644 (file)
@@ -268,6 +268,10 @@ impl TapeDriver for LtoTapeHandle {
         }
         Ok(())
     }
+
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
+        self.volume_statistics()
+    }
 }
 
 fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result<String, Error> {
index 39602461f72063fe13974a7141f8fce7040e55e7..b21a62d20b3d58f3fa289fc4ba44d661f04e1a0d 100644 (file)
@@ -242,6 +242,9 @@ pub trait TapeDriver {
         }
         Ok(())
     }
+
+    /// Returns volume statistics from a loaded tape
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error>;
 }
 
 /// A boxed implementor of [`MediaChange`].
index b13c58c4e910dddae228450f8493604c131ff938..c183e2681185e7d569cbd2cd97eb85eee334b0e6 100644 (file)
@@ -461,6 +461,10 @@ impl TapeDriver for VirtualTapeHandle {
         let status = VirtualDriveStatus { current_tape: None };
         self.store_status(&status)
     }
+
+    fn get_volume_statistics(&mut self) -> Result<pbs_api_types::Lp17VolumeStatistics, Error> {
+        Ok(Default::default())
+    }
 }
 
 impl MediaChange for VirtualTapeHandle {
index 7514d76c04e32293ddd9d46be0dd4dd9346ef61c..5e4318e21c738ab91ca3aaf3f7adedbdfda731e5 100644 (file)
@@ -84,6 +84,8 @@ struct MediaStateEntry {
     location: Option<MediaLocation>,
     #[serde(skip_serializing_if = "Option::is_none")]
     status: Option<MediaStatus>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    bytes_used: Option<u64>,
 }
 
 /// Media Inventory
@@ -211,6 +213,7 @@ impl Inventory {
                 } else {
                     previous.status
                 },
+                bytes_used: previous.bytes_used,
             };
             self.map.insert(uuid, entry);
         } else {
@@ -218,6 +221,7 @@ impl Inventory {
                 id: media_id,
                 location: None,
                 status: None,
+                bytes_used: None,
             };
             self.map.insert(uuid, entry);
         }
@@ -720,6 +724,32 @@ impl Inventory {
         self.set_media_location(uuid, Some(MediaLocation::Offline))
     }
 
+    /// Lock database, reload database, set bytes used for media, store database
+    pub fn set_media_bytes_used(
+        &mut self,
+        uuid: &Uuid,
+        bytes_used: Option<u64>,
+    ) -> Result<(), Error> {
+        let _lock = self.lock()?;
+        self.map = self.load_media_db()?;
+        if let Some(entry) = self.map.get_mut(uuid) {
+            entry.bytes_used = bytes_used;
+            self.update_helpers();
+            self.replace_file()?;
+            Ok(())
+        } else {
+            bail!("no such media '{}'", uuid);
+        }
+    }
+
+    /// Returns bytes used of the given media, if set
+    pub fn get_media_bytes_used(&self, uuid: &Uuid) -> Option<u64> {
+        match self.map.get(uuid) {
+            Some(entry) => entry.bytes_used,
+            None => None,
+        }
+    }
+
     /// Update online status
     pub fn update_online_status(&mut self, online_map: &OnlineStatusMap) -> Result<(), Error> {
         let _lock = self.lock()?;
index 8f2b0adda3e6c72fbe178d576239232ba4305b0e..1e8c739e7f9fa236c05c33dbe2bf8babf3025b66 100644 (file)
@@ -212,8 +212,11 @@ impl MediaPool {
         }
 
         let (status, location) = self.compute_media_state(&media_id);
+        let bytes_used = self.inventory.get_media_bytes_used(uuid);
 
-        Ok(BackupMedia::with_media_id(media_id, location, status))
+        Ok(BackupMedia::with_media_id(
+            media_id, location, status, bytes_used,
+        ))
     }
 
     /// List all media associated with this pool
@@ -224,7 +227,8 @@ impl MediaPool {
             .into_iter()
             .map(|media_id| {
                 let (status, location) = self.compute_media_state(&media_id);
-                BackupMedia::with_media_id(media_id, location, status)
+                let bytes_used = self.inventory.get_media_bytes_used(&media_id.label.uuid);
+                BackupMedia::with_media_id(media_id, location, status, bytes_used)
             })
             .collect()
     }
@@ -238,6 +242,15 @@ impl MediaPool {
         Ok(())
     }
 
+    /// Update bytes used for media in inventory
+    pub fn set_media_bytes_used(
+        &mut self,
+        uuid: &Uuid,
+        bytes_used: Option<u64>,
+    ) -> Result<(), Error> {
+        self.inventory.set_media_bytes_used(uuid, bytes_used)
+    }
+
     /// Make sure the current media set is usable for writing
     ///
     /// If not, starts a new media set. Also creates a new
@@ -715,15 +728,23 @@ pub struct BackupMedia {
     location: MediaLocation,
     /// Media status
     status: MediaStatus,
+    /// Bytes used
+    bytes_used: Option<u64>,
 }
 
 impl BackupMedia {
     /// Creates a new instance
-    pub fn with_media_id(id: MediaId, location: MediaLocation, status: MediaStatus) -> Self {
+    pub fn with_media_id(
+        id: MediaId,
+        location: MediaLocation,
+        status: MediaStatus,
+        bytes_used: Option<u64>,
+    ) -> Self {
         Self {
             id,
             location,
             status,
+            bytes_used,
         }
     }
 
@@ -776,4 +797,9 @@ impl BackupMedia {
     pub fn label_text(&self) -> &str {
         &self.id.label.label_text
     }
+
+    /// Returns the bytes used, if set
+    pub fn bytes_used(&self) -> Option<u64> {
+        self.bytes_used
+    }
 }
index 68e28714af1919e61d56222dd5f2c80cc46a9f48..1df297b6837743624ac2873c26f44b8617fce5ce 100644 (file)
@@ -203,6 +203,14 @@ impl PoolWriter {
         if let Some(ref mut status) = self.status {
             status.drive.sync()?; // sync all data to the tape
             status.bytes_written_after_sync = 0; // reset bytes written
+
+            // not all drives support that
+            if let Ok(stats) = status.drive.get_volume_statistics() {
+                self.pool.set_media_bytes_used(
+                    &status.media_uuid,
+                    Some(stats.total_used_native_capacity),
+                )?;
+            }
         }
         self.catalog_set.lock().unwrap().commit()?; // then commit the catalog
         Ok(())
@@ -237,7 +245,13 @@ impl PoolWriter {
         );
 
         if let Some(PoolWriterState { mut drive, .. }) = self.status.take() {
-            if last_media_uuid.is_some() {
+            if let Some(uuid) = &last_media_uuid {
+                // not all drives support that
+                if let Ok(stats) = drive.get_volume_statistics() {
+                    self.pool
+                        .set_media_bytes_used(uuid, Some(stats.total_used_native_capacity))?;
+                }
+
                 task_log!(worker, "eject current media");
                 drive.eject_media()?;
             }
index 47d19acc0f822ff4ce61c27d75faf59e9331a6c5..305134e3ea77b613b1a9787c1a894389da6ec2e2 100644 (file)
@@ -16,6 +16,7 @@ Ext.define('pbs-model-tapes', {
        'seq-nr',
        'status',
        'uuid',
+       'bytes-used',
     ],
     idProperty: 'uuid',
     proxy: {
@@ -326,5 +327,11 @@ Ext.define('PBS.TapeManagement.TapeInventory', {
            flex: 1,
            hidden: true,
        },
+       {
+           text: gettext("Bytes Used"),
+           dataIndex: 'bytes-used',
+           flex: 1,
+           renderer: Proxmox.Utils.render_size,
+       },
     ],
 });