/// 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(
media_set_uuid,
media_set_name,
seq_nr,
+ bytes_used: media.bytes_used(),
});
}
}
media_set_ctime: None,
seq_nr: None,
pool: None,
+ bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
});
}
}
media_set_uuid,
media_set_name,
seq_nr,
+ bytes_used: inventory.get_media_bytes_used(&media_id.label.uuid),
});
}
}
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> {
}
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`].
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 {
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
} else {
previous.status
},
+ bytes_used: previous.bytes_used,
};
self.map.insert(uuid, entry);
} else {
id: media_id,
location: None,
status: None,
+ bytes_used: None,
};
self.map.insert(uuid, entry);
}
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()?;
}
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
.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()
}
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
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,
}
}
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
+ }
}
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(())
);
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()?;
}
'seq-nr',
'status',
'uuid',
+ 'bytes-used',
],
idProperty: 'uuid',
proxy: {
flex: 1,
hidden: true,
},
+ {
+ text: gettext("Bytes Used"),
+ dataIndex: 'bytes-used',
+ flex: 1,
+ renderer: Proxmox.Utils.render_size,
+ },
],
});