tape: merge MediaStateDatabase into Inventory
This commit is contained in:
parent
54f4ecd46a
commit
cfae8f0656
@ -34,7 +34,6 @@ use crate::{
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
PoolWriter,
|
||||
MediaPool,
|
||||
SnapshotReader,
|
||||
@ -154,12 +153,10 @@ fn update_media_online_status(drive: &str) -> Result<bool, Error> {
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
let mut state_db = MediaStateDatabase::load(status_path)?;
|
||||
|
||||
update_changer_online_status(
|
||||
&config,
|
||||
&mut inventory,
|
||||
&mut state_db,
|
||||
&changer_name,
|
||||
&changer_id_list,
|
||||
)?;
|
||||
|
@ -20,7 +20,6 @@ use crate::{
|
||||
ElementStatus,
|
||||
OnlineStatusMap,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
linux_tape_changer_list,
|
||||
mtx_status,
|
||||
mtx_status_to_online_set,
|
||||
@ -57,14 +56,13 @@ pub async fn get_status(name: String) -> Result<Vec<MtxStatusEntry>, Error> {
|
||||
}).await??;
|
||||
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
let inventory = Inventory::load(state_path)?;
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
|
||||
let mut map = OnlineStatusMap::new(&config)?;
|
||||
let online_set = mtx_status_to_online_set(&status, &inventory);
|
||||
map.update_online_status(&name, online_set)?;
|
||||
|
||||
let mut state_db = MediaStateDatabase::load(state_path)?;
|
||||
state_db.update_online_status(&map)?;
|
||||
inventory.update_online_status(&map)?;
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
|
@ -48,7 +48,6 @@ use crate::{
|
||||
MediaChange,
|
||||
MediaPool,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
MediaCatalog,
|
||||
MediaId,
|
||||
mtx_load,
|
||||
@ -421,7 +420,7 @@ fn write_media_label(
|
||||
MediaCatalog::overwrite(status_path, &media_id, false)?;
|
||||
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
inventory.store(media_id.clone())?;
|
||||
inventory.store(media_id.clone(), false)?;
|
||||
|
||||
drive.rewind()?;
|
||||
|
||||
@ -542,12 +541,10 @@ pub async fn inventory(
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
let mut state_db = MediaStateDatabase::load(state_path)?;
|
||||
|
||||
update_changer_online_status(
|
||||
&config,
|
||||
&mut inventory,
|
||||
&mut state_db,
|
||||
&changer_name,
|
||||
&changer_id_list,
|
||||
)?;
|
||||
@ -630,9 +627,8 @@ pub fn update_inventory(
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
let mut state_db = MediaStateDatabase::load(state_path)?;
|
||||
|
||||
update_changer_online_status(&config, &mut inventory, &mut state_db, &changer_name, &changer_id_list)?;
|
||||
update_changer_online_status(&config, &mut inventory, &changer_name, &changer_id_list)?;
|
||||
|
||||
for changer_id in changer_id_list.iter() {
|
||||
if changer_id.starts_with("CLN") {
|
||||
@ -668,7 +664,7 @@ pub fn update_inventory(
|
||||
continue;
|
||||
}
|
||||
worker.log(format!("inventorize media '{}' with uuid '{}'", changer_id, media_id.label.uuid));
|
||||
inventory.store(media_id)?;
|
||||
inventory.store(media_id, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -743,9 +739,8 @@ fn barcode_label_media_worker(
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
let mut state_db = MediaStateDatabase::load(state_path)?;
|
||||
|
||||
update_changer_online_status(&config, &mut inventory, &mut state_db, &changer_name, &changer_id_list)?;
|
||||
update_changer_online_status(&config, &mut inventory, &changer_name, &changer_id_list)?;
|
||||
|
||||
if changer_id_list.is_empty() {
|
||||
bail!("changer device does not list any media labels");
|
||||
@ -921,7 +916,7 @@ pub fn catalog_media(
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
inventory.store(media_id.clone())?;
|
||||
inventory.store(media_id.clone(), false)?;
|
||||
|
||||
let pool = match media_id.media_set_label {
|
||||
None => {
|
||||
|
@ -28,7 +28,6 @@ use crate::{
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
MediaPool,
|
||||
MediaCatalog,
|
||||
update_online_status,
|
||||
@ -118,11 +117,10 @@ pub async fn list_media(pool: Option<String>) -> Result<Vec<MediaListEntry>, Err
|
||||
if pool.is_none() {
|
||||
|
||||
let inventory = Inventory::load(status_path)?;
|
||||
let state_db = MediaStateDatabase::load(status_path)?;
|
||||
|
||||
for media_id in inventory.list_unassigned_media() {
|
||||
|
||||
let (mut status, location) = state_db.status_and_location(&media_id.label.uuid);
|
||||
let (mut status, location) = inventory.status_and_location(&media_id.label.uuid);
|
||||
|
||||
if status == MediaStatus::Unknown {
|
||||
status = MediaStatus::Writable;
|
||||
@ -184,9 +182,6 @@ pub fn destroy_media(changer_id: String, force: Option<bool>,) -> Result<(), Err
|
||||
|
||||
inventory.remove_media(&uuid)?;
|
||||
|
||||
let mut state_db = MediaStateDatabase::load(status_path)?;
|
||||
state_db.remove_media(&uuid)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ use crate::{
|
||||
MediaId,
|
||||
MediaSet,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
file_formats::{
|
||||
MediaLabel,
|
||||
MediaSetLabel,
|
||||
@ -47,7 +46,6 @@ pub struct MediaPool {
|
||||
use_offline_media: bool,
|
||||
|
||||
inventory: Inventory,
|
||||
state_db: MediaStateDatabase,
|
||||
|
||||
current_media_set: MediaSet,
|
||||
}
|
||||
@ -70,15 +68,12 @@ impl MediaPool {
|
||||
None => MediaSet::new(),
|
||||
};
|
||||
|
||||
let state_db = MediaStateDatabase::load(state_path)?;
|
||||
|
||||
Ok(MediaPool {
|
||||
name: String::from(name),
|
||||
media_set_policy,
|
||||
retention,
|
||||
use_offline_media,
|
||||
inventory,
|
||||
state_db,
|
||||
current_media_set,
|
||||
})
|
||||
}
|
||||
@ -104,7 +99,7 @@ impl MediaPool {
|
||||
|
||||
fn compute_media_state(&self, media_id: &MediaId) -> (MediaStatus, MediaLocation) {
|
||||
|
||||
let (status, location) = self.state_db.status_and_location(&media_id.label.uuid);
|
||||
let (status, location) = self.inventory.status_and_location(&media_id.label.uuid);
|
||||
|
||||
match status {
|
||||
MediaStatus::Full | MediaStatus::Damaged | MediaStatus::Retired => {
|
||||
@ -181,7 +176,7 @@ impl MediaPool {
|
||||
pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let media = self.lookup_media(uuid)?; // check if media belongs to this pool
|
||||
if media.status() != &MediaStatus::Full {
|
||||
self.state_db.set_media_status_full(uuid)?;
|
||||
self.inventory.set_media_status_full(uuid)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -304,7 +299,7 @@ impl MediaPool {
|
||||
|
||||
media.set_media_set_label(set);
|
||||
|
||||
self.inventory.store(media.id().clone())?; // store persistently
|
||||
self.inventory.store(media.id().clone(), true)?; // store persistently
|
||||
|
||||
self.current_media_set.add_media(media.uuid().clone());
|
||||
|
||||
@ -347,9 +342,9 @@ impl MediaPool {
|
||||
|
||||
media.set_media_set_label(set);
|
||||
|
||||
self.inventory.store(media.id().clone())?; // store persistently
|
||||
self.state_db.clear_media_status(media.uuid())?; // remove Full status
|
||||
|
||||
let clear_media_status = true; // remove Full status
|
||||
self.inventory.store(media.id().clone(), clear_media_status)?; // store persistently
|
||||
|
||||
self.current_media_set.add_media(media.uuid().clone());
|
||||
|
||||
return Ok(media.uuid().clone());
|
||||
|
@ -1,221 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::Error;
|
||||
use ::serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
||||
use proxmox::tools::{
|
||||
Uuid,
|
||||
fs::{
|
||||
open_file_locked,
|
||||
replace_file,
|
||||
file_get_json,
|
||||
CreateOptions,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
OnlineStatusMap,
|
||||
},
|
||||
api2::types::{
|
||||
MediaStatus,
|
||||
MediaLocation,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
struct MediaStateEntry {
|
||||
u: Uuid,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
l: Option<MediaLocation>,
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
s: Option<MediaStatus>,
|
||||
}
|
||||
|
||||
impl MediaStateEntry {
|
||||
fn new(uuid: Uuid) -> Self {
|
||||
MediaStateEntry { u: uuid, l: None, s: None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores MediaLocation and MediaState persistently
|
||||
pub struct MediaStateDatabase {
|
||||
|
||||
map: BTreeMap<Uuid, MediaStateEntry>,
|
||||
|
||||
database_path: PathBuf,
|
||||
lockfile_path: PathBuf,
|
||||
}
|
||||
|
||||
impl MediaStateDatabase {
|
||||
|
||||
pub const MEDIA_STATUS_DATABASE_FILENAME: &'static str = "media-status-db.json";
|
||||
pub const MEDIA_STATUS_DATABASE_LOCKFILE: &'static str = ".media-status-db.lck";
|
||||
|
||||
|
||||
/// Lock the database
|
||||
pub fn lock(&self) -> Result<std::fs::File, Error> {
|
||||
open_file_locked(&self.lockfile_path, std::time::Duration::new(10, 0), true)
|
||||
}
|
||||
|
||||
/// 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.l.clone().unwrap_or(MediaLocation::Offline);
|
||||
let status = entry.s.unwrap_or(MediaStatus::Unknown);
|
||||
(status, location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_media_db(path: &Path) -> Result<BTreeMap<Uuid, MediaStateEntry>, Error> {
|
||||
|
||||
let data = file_get_json(path, Some(json!([])))?;
|
||||
let list: Vec<MediaStateEntry> = serde_json::from_value(data)?;
|
||||
|
||||
let mut map = BTreeMap::new();
|
||||
for entry in list.into_iter() {
|
||||
map.insert(entry.u.clone(), entry);
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Load the database into memory
|
||||
pub fn load(base_path: &Path) -> Result<MediaStateDatabase, Error> {
|
||||
|
||||
let mut database_path = base_path.to_owned();
|
||||
database_path.push(Self::MEDIA_STATUS_DATABASE_FILENAME);
|
||||
|
||||
let mut lockfile_path = base_path.to_owned();
|
||||
lockfile_path.push(Self::MEDIA_STATUS_DATABASE_LOCKFILE);
|
||||
|
||||
Ok(MediaStateDatabase {
|
||||
map: Self::load_media_db(&database_path)?,
|
||||
database_path,
|
||||
lockfile_path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Lock database, reload database, set status to Full, store database
|
||||
pub fn set_media_status_full(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
|
||||
entry.s = Some(MediaStatus::Full);
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// 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.database_path)?;
|
||||
|
||||
for (_uuid, entry) in self.map.iter_mut() {
|
||||
if let Some(changer_name) = online_map.lookup_changer(&entry.u) {
|
||||
entry.l = Some(MediaLocation::Online(changer_name.to_string()));
|
||||
} else {
|
||||
if let Some(MediaLocation::Online(ref changer_name)) = entry.l {
|
||||
match online_map.online_map(changer_name) {
|
||||
None => {
|
||||
// no such changer device
|
||||
entry.l = Some(MediaLocation::Offline);
|
||||
}
|
||||
Some(None) => {
|
||||
// got no info - do nothing
|
||||
}
|
||||
Some(Some(_)) => {
|
||||
// media changer changed
|
||||
entry.l = Some(MediaLocation::Offline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uuid, changer_name) in online_map.changer_map() {
|
||||
if self.map.contains_key(uuid) { continue; }
|
||||
let mut entry = MediaStateEntry::new(uuid.clone());
|
||||
entry.l = Some(MediaLocation::Online(changer_name.to_string()));
|
||||
self.map.insert(uuid.clone(), entry);
|
||||
}
|
||||
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// Lock database, reload database, set status to Damaged, store database
|
||||
pub fn set_media_status_damaged(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
|
||||
entry.s = Some(MediaStatus::Damaged);
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// Lock database, reload database, set status to None, store database
|
||||
pub fn clear_media_status(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
|
||||
entry.s = None ;
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// Lock database, reload database, set location to vault, store database
|
||||
pub fn set_media_location_vault(&mut self, uuid: &Uuid, vault: &str) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
|
||||
entry.l = Some(MediaLocation::Vault(vault.to_string()));
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// Lock database, reload database, set location to offline, store database
|
||||
pub fn set_media_location_offline(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
let entry = self.map.entry(uuid.clone()).or_insert(MediaStateEntry::new(uuid.clone()));
|
||||
entry.l = Some(MediaLocation::Offline);
|
||||
self.store()
|
||||
}
|
||||
|
||||
/// Lock database, reload database, remove media, store database
|
||||
pub fn remove_media(&mut self, uuid: &Uuid) -> Result<(), Error> {
|
||||
let _lock = self.lock()?;
|
||||
self.map = Self::load_media_db(&self.database_path)?;
|
||||
self.map.remove(uuid);
|
||||
self.store()
|
||||
}
|
||||
|
||||
fn store(&self) -> Result<(), Error> {
|
||||
|
||||
let mut list = Vec::new();
|
||||
for entry in self.map.values() {
|
||||
list.push(entry);
|
||||
}
|
||||
|
||||
let raw = serde_json::to_string_pretty(&serde_json::to_value(list)?)?;
|
||||
|
||||
let backup_user = crate::backup::backup_user()?;
|
||||
let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
|
||||
let options = CreateOptions::new()
|
||||
.perm(mode)
|
||||
.owner(backup_user.uid)
|
||||
.group(backup_user.gid);
|
||||
|
||||
replace_file(&self.database_path, raw.as_bytes(), options)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -29,9 +29,6 @@ pub use changer::*;
|
||||
mod drive;
|
||||
pub use drive::*;
|
||||
|
||||
mod media_state_database;
|
||||
pub use media_state_database::*;
|
||||
|
||||
mod online_status_map;
|
||||
pub use online_status_map::*;
|
||||
|
||||
|
@ -14,7 +14,6 @@ use crate::{
|
||||
tape::{
|
||||
MediaChange,
|
||||
Inventory,
|
||||
MediaStateDatabase,
|
||||
mtx_status,
|
||||
mtx_status_to_online_set,
|
||||
},
|
||||
@ -97,7 +96,7 @@ pub fn update_online_status(state_path: &Path) -> Result<OnlineStatusMap, Error>
|
||||
|
||||
let (config, _digest) = crate::config::drive::config()?;
|
||||
|
||||
let inventory = Inventory::load(state_path)?;
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
|
||||
let changers: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
|
||||
|
||||
@ -135,8 +134,7 @@ pub fn update_online_status(state_path: &Path) -> Result<OnlineStatusMap, Error>
|
||||
map.update_online_status(&vtape.name, online_set)?;
|
||||
}
|
||||
|
||||
let mut state_db = MediaStateDatabase::load(state_path)?;
|
||||
state_db.update_online_status(&map)?;
|
||||
inventory.update_online_status(&map)?;
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
@ -145,7 +143,6 @@ pub fn update_online_status(state_path: &Path) -> Result<OnlineStatusMap, Error>
|
||||
pub fn update_changer_online_status(
|
||||
drive_config: &SectionConfigData,
|
||||
inventory: &mut Inventory,
|
||||
state_db: &mut MediaStateDatabase,
|
||||
changer_name: &str,
|
||||
changer_id_list: &Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
@ -158,7 +155,7 @@ pub fn update_changer_online_status(
|
||||
}
|
||||
}
|
||||
online_map.update_online_status(&changer_name, online_set)?;
|
||||
state_db.update_online_status(&online_map)?;
|
||||
inventory.update_online_status(&online_map)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user