tape: merge MediaStateDatabase into Inventory

This commit is contained in:
Dietmar Maurer
2021-01-01 16:15:13 +01:00
parent 54f4ecd46a
commit cfae8f0656
9 changed files with 205 additions and 313 deletions

View File

@ -26,9 +26,12 @@ use crate::{
api2::types::{
MediaSetPolicy,
RetentionPolicy,
MediaStatus,
MediaLocation,
},
tape::{
TAPE_STATUS_DIR,
OnlineStatusMap,
file_formats::{
MediaLabel,
MediaSetLabel,
@ -120,9 +123,18 @@ impl MediaSet {
}
}
#[derive(Serialize,Deserialize)]
struct MediaStateEntry {
id: MediaId,
#[serde(skip_serializing_if="Option::is_none")]
location: Option<MediaLocation>,
#[serde(skip_serializing_if="Option::is_none")]
status: Option<MediaStatus>,
}
/// Media Inventory
pub struct Inventory {
map: BTreeMap<Uuid, MediaId>,
map: BTreeMap<Uuid, MediaStateEntry>,
inventory_path: PathBuf,
lockfile_path: PathBuf,
@ -171,8 +183,8 @@ impl Inventory {
let mut set_start_times = HashMap::new();
for media in self.map.values() {
let set = match &media.media_set_label {
for entry in self.map.values() {
let set = match &entry.id.media_set_label {
None => continue,
Some(set) => set,
};
@ -189,21 +201,21 @@ impl Inventory {
open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)
}
fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaId>, Error> {
fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> {
let data = file_get_json(path, Some(json!([])))?;
let media_list: Vec<MediaId> = serde_json::from_value(data)?;
let media_list: Vec<MediaStateEntry> = serde_json::from_value(data)?;
let mut map = BTreeMap::new();
for item in media_list.into_iter() {
map.insert(item.label.uuid.clone(), item);
for entry in media_list.into_iter() {
map.insert(entry.id.label.uuid.clone(), entry);
}
Ok(map)
}
fn replace_file(&self) -> Result<(), Error> {
let list: Vec<&MediaId> = self.map.values().collect();
let list: Vec<&MediaStateEntry> = self.map.values().collect();
let raw = serde_json::to_string_pretty(&serde_json::to_value(list)?)?;
let backup_user = crate::backup::backup_user()?;
@ -219,22 +231,40 @@ impl Inventory {
}
/// Stores a single MediaID persistently
pub fn store(&mut self, mut media_id: MediaId) -> Result<(), Error> {
pub fn store(
&mut self,
mut media_id: MediaId,
clear_media_status: bool,
) -> Result<(), Error> {
let _lock = self.lock()?;
self.map = Self::load_media_db(&self.inventory_path)?;
// do not overwrite unsaved pool assignments
if media_id.media_set_label.is_none() {
if let Some(previous) = self.map.get(&media_id.label.uuid) {
if let Some(ref set) = previous.media_set_label {
let uuid = media_id.label.uuid.clone();
if let Some(previous) = self.map.remove(&media_id.label.uuid) {
// do not overwrite unsaved pool assignments
if media_id.media_set_label.is_none() {
if let Some(ref set) = previous.id.media_set_label {
if set.uuid.as_ref() == [0u8;16] {
media_id.media_set_label = Some(set.clone());
}
}
}
let entry = MediaStateEntry {
id: media_id,
location: previous.location,
status: if clear_media_status {
None
} else {
previous.status
},
};
self.map.insert(uuid, entry);
} else {
let entry = MediaStateEntry { id: media_id, location: None, status: None };
self.map.insert(uuid, entry);
}
self.map.insert(media_id.label.uuid.clone(), media_id);
self.update_helpers();
self.replace_file()?;
Ok(())
@ -252,14 +282,14 @@ impl Inventory {
/// Lookup media
pub fn lookup_media(&self, uuid: &Uuid) -> Option<&MediaId> {
self.map.get(uuid)
self.map.get(uuid).map(|entry| &entry.id)
}
/// find media by changer_id
pub fn find_media_by_changer_id(&self, changer_id: &str) -> Option<&MediaId> {
for (_uuid, media_id) in &self.map {
if media_id.label.changer_id == changer_id {
return Some(media_id);
for (_uuid, entry) in &self.map {
if entry.id.label.changer_id == changer_id {
return Some(&entry.id);
}
}
None
@ -271,8 +301,8 @@ impl Inventory {
pub fn lookup_media_pool(&self, uuid: &Uuid) -> Option<(&str, bool)> {
match self.map.get(uuid) {
None => None,
Some(media_id) => {
match media_id.media_set_label {
Some(entry) => {
match entry.id.media_set_label {
None => None, // not assigned to any pool
Some(ref set) => {
let is_empty = set.uuid.as_ref() == [0u8;16];
@ -287,8 +317,8 @@ impl Inventory {
pub fn list_pool_media(&self, pool: &str) -> Vec<MediaId> {
let mut list = Vec::new();
for (_uuid, media_id) in &self.map {
match media_id.media_set_label {
for (_uuid, entry) in &self.map {
match entry.id.media_set_label {
None => continue, // not assigned to any pool
Some(ref set) => {
if set.pool != pool {
@ -297,15 +327,14 @@ impl Inventory {
if set.uuid.as_ref() == [0u8;16] { // should we do this??
list.push(MediaId {
label: media_id.label.clone(),
label: entry.id.label.clone(),
media_set_label: None,
})
} else {
list.push(media_id.clone());
list.push(entry.id.clone());
}
}
}
}
list
@ -315,12 +344,12 @@ impl Inventory {
pub fn list_used_media(&self) -> Vec<MediaId> {
let mut list = Vec::new();
for (_uuid, media_id) in &self.map {
match media_id.media_set_label {
for (_uuid, entry) in &self.map {
match entry.id.media_set_label {
None => continue, // not assigned to any pool
Some(ref set) => {
if set.uuid.as_ref() != [0u8;16] {
list.push(media_id.clone());
list.push(entry.id.clone());
}
}
}
@ -333,9 +362,9 @@ impl Inventory {
pub fn list_unassigned_media(&self) -> Vec<MediaId> {
let mut list = Vec::new();
for (_uuid, media_id) in &self.map {
if media_id.media_set_label.is_none() {
list.push(media_id.clone());
for (_uuid, entry) in &self.map {
if entry.id.media_set_label.is_none() {
list.push(entry.id.clone());
}
}
@ -351,14 +380,14 @@ impl Inventory {
let mut last_pool = None;
for media in self.map.values() {
match media.media_set_label {
for entry in self.map.values() {
match entry.id.media_set_label {
None => continue,
Some(MediaSetLabel { ref uuid, .. }) => {
if uuid != media_set_uuid {
continue;
}
if let Some((pool, _)) = self.lookup_media_pool(&media.label.uuid) {
if let Some((pool, _)) = self.lookup_media_pool(&entry.id.label.uuid) {
if let Some(last_pool) = last_pool {
if last_pool != pool {
bail!("detected media set with inconsistent pool assignment - internal error");
@ -382,14 +411,14 @@ impl Inventory {
let mut set = MediaSet::with_data(media_set_uuid.clone(), Vec::new());
for media in self.map.values() {
match media.media_set_label {
for entry in self.map.values() {
match entry.id.media_set_label {
None => continue,
Some(MediaSetLabel { seq_nr, ref uuid, .. }) => {
if uuid != media_set_uuid {
continue;
}
set.insert_media(media.label.uuid.clone(), seq_nr)?;
set.insert_media(entry.id.label.uuid.clone(), seq_nr)?;
}
}
}
@ -402,8 +431,8 @@ impl Inventory {
let mut set_map: HashMap<Uuid, MediaSet> = HashMap::new();
for media in self.map.values() {
match media.media_set_label {
for entry in self.map.values() {
match entry.id.media_set_label {
None => continue,
Some(MediaSetLabel { seq_nr, ref uuid, .. }) => {
@ -411,7 +440,7 @@ impl Inventory {
MediaSet::with_data(uuid.clone(), Vec::new())
});
set.insert_media(media.label.uuid.clone(), seq_nr)?;
set.insert_media(entry.id.label.uuid.clone(), seq_nr)?;
}
}
}
@ -425,7 +454,7 @@ impl Inventory {
let mut last_set: Option<(Uuid, i64)> = None;
let set_list = self.map.values()
.filter_map(|media| media.media_set_label.as_ref())
.filter_map(|entry| entry.id.media_set_label.as_ref())
.filter(|set| &set.pool == &pool && set.uuid.as_ref() != [0u8;16]);
for set in set_list {
@ -448,7 +477,7 @@ impl Inventory {
// consistency check - must be the only set with that ctime
let set_list = self.map.values()
.filter_map(|media| media.media_set_label.as_ref())
.filter_map(|entry| entry.id.media_set_label.as_ref())
.filter(|set| &set.pool == &pool && set.uuid.as_ref() != [0u8;16]);
for set in set_list {
@ -466,7 +495,7 @@ impl Inventory {
fn media_set_next_start_time(&self, media_set_uuid: &Uuid) -> Option<i64> {
let (pool, ctime) = match self.map.values()
.filter_map(|media| media.media_set_label.as_ref())
.filter_map(|entry| entry.id.media_set_label.as_ref())
.find_map(|set| {
if &set.uuid == media_set_uuid {
Some((set.pool.clone(), set.ctime))
@ -479,7 +508,7 @@ impl Inventory {
};
let set_list = self.map.values()
.filter_map(|media| media.media_set_label.as_ref())
.filter_map(|entry| entry.id.media_set_label.as_ref())
.filter(|set| (&set.uuid != media_set_uuid) && (&set.pool == &pool));
let mut next_ctime = None;
@ -586,7 +615,7 @@ impl Inventory {
};
let uuid = label.uuid.clone();
self.store(MediaId { label, media_set_label: None }).unwrap();
self.store(MediaId { label, media_set_label: None }, false).unwrap();
uuid
}
@ -610,7 +639,7 @@ impl Inventory {
let set = MediaSetLabel::with_data(pool, [0u8; 16].into(), 0, ctime);
self.store(MediaId { label, media_set_label: Some(set) }).unwrap();
self.store(MediaId { label, media_set_label: Some(set) }, false).unwrap();
uuid
}
@ -629,12 +658,122 @@ impl Inventory {
};
let uuid = label.uuid.clone();
self.store(MediaId { label, media_set_label: Some(set) }).unwrap();
self.store(MediaId { label, media_set_label: Some(set) }, false).unwrap();
uuid
}
}
// Status/location handling
impl Inventory {
/// Returns status and location with reasonable defaults.
///
/// Default status is 'MediaStatus::Unknown'.
/// Default location is 'MediaLocation::Offline'.
pub fn status_and_location(&self, uuid: &Uuid) -> (MediaStatus, MediaLocation) {
match self.map.get(uuid) {
None => {
// no info stored - assume media is writable/offline
(MediaStatus::Unknown, MediaLocation::Offline)
}
Some(entry) => {
let location = entry.location.clone().unwrap_or(MediaLocation::Offline);
let status = entry.status.unwrap_or(MediaStatus::Unknown);
(status, location)
}
}
}
// Lock database, reload database, set status, store database
fn set_media_status(&mut self, uuid: &Uuid, status: Option<MediaStatus>) -> Result<(), Error> {
let _lock = self.lock()?;
self.map = Self::load_media_db(&self.inventory_path)?;
if let Some(entry) = self.map.get_mut(uuid) {
entry.status = status;
self.update_helpers();
self.replace_file()?;
Ok(())
} else {
bail!("no such media '{}'", uuid);
}
}
/// Lock database, reload database, set status to Full, store database
pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
self.set_media_status(uuid, Some(MediaStatus::Full))
}
/// Lock database, reload database, set status to Damaged, store database
pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> {
self.set_media_status(uuid, Some(MediaStatus::Damaged))
}
/// Lock database, reload database, set status to None, store database
pub fn clear_media_status(&mut self, uuid: &Uuid) -> Result<(), Error> {
self.set_media_status(uuid, None)
}
// Lock database, reload database, set location, store database
fn set_media_location(&mut self, uuid: &Uuid, location: Option<MediaLocation>) -> Result<(), Error> {
let _lock = self.lock()?;
self.map = Self::load_media_db(&self.inventory_path)?;
if let Some(entry) = self.map.get_mut(uuid) {
entry.location = location;
self.update_helpers();
self.replace_file()?;
Ok(())
} else {
bail!("no such media '{}'", uuid);
}
}
/// Lock database, reload database, set location to vault, store database
pub fn set_media_location_vault(&mut self, uuid: &Uuid, vault: &str) -> Result<(), Error> {
self.set_media_location(uuid, Some(MediaLocation::Vault(vault.to_string())))
}
/// Lock database, reload database, set location to offline, store database
pub fn set_media_location_offline(&mut self, uuid: &Uuid) -> Result<(), Error> {
self.set_media_location(uuid, Some(MediaLocation::Offline))
}
/// Update online status
pub fn update_online_status(&mut self, online_map: &OnlineStatusMap) -> Result<(), Error> {
let _lock = self.lock()?;
self.map = Self::load_media_db(&self.inventory_path)?;
for (uuid, entry) in self.map.iter_mut() {
if let Some(changer_name) = online_map.lookup_changer(uuid) {
entry.location = Some(MediaLocation::Online(changer_name.to_string()));
} else {
if let Some(MediaLocation::Online(ref changer_name)) = entry.location {
match online_map.online_map(changer_name) {
None => {
// no such changer device
entry.location = Some(MediaLocation::Offline);
}
Some(None) => {
// got no info - do nothing
}
Some(Some(_)) => {
// media changer changed
entry.location = Some(MediaLocation::Offline);
}
}
}
}
}
self.update_helpers();
self.replace_file()?;
Ok(())
}
}
// shell completion helper
/// List of known media uuids
@ -663,7 +802,7 @@ pub fn complete_media_set_uuid(
};
inventory.map.values()
.filter_map(|media| media.media_set_label.as_ref())
.filter_map(|entry| entry.id.media_set_label.as_ref())
.map(|set| set.uuid.to_string()).collect()
}
@ -678,5 +817,5 @@ pub fn complete_media_changer_id(
Err(_) => return Vec::new(),
};
inventory.map.values().map(|media| media.label.changer_id.clone()).collect()
inventory.map.values().map(|entry| entry.id.label.changer_id.clone()).collect()
}